Imported Upstream version 2.4.5
[debian/amanda] / tape-src / tapetype.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Author: James da Silva, Systems Design and Analysis Group
24  *                         Computer Science Department
25  *                         University of Maryland at College Park
26  */
27 /*
28  * $Id: tapetype.c,v 1.3.2.3.4.3.2.9.2.2 2004/11/10 16:28:43 martinea Exp $
29  *
30  * tests a tape in a given tape unit and prints a tapetype entry for
31  * it.  */
32 #include "amanda.h"
33
34 #include "tapeio.h"
35
36 #define NBLOCKS 32                      /* number of random blocks */
37
38 extern int optind;
39
40 static char *sProgName;
41 static char *tapedev;
42 static int fd;
43
44 static int blockkb = 32;
45 static int blocksize;
46
47 static char *randombytes = (char *) NULL;
48
49 #if USE_RAND
50 /* If the C library does not define random(), try to use rand() by
51    defining USE_RAND, but then make sure you are not using hardware
52    compression, because the low-order bits of rand() may not be that
53    random... :-( */
54 #define random() rand()
55 #define srandom(seed) srand(seed)
56 #endif
57
58 static void allocrandombytes() {
59   int i, j, page_size;
60   char *p;
61
62   if (randombytes == (char *)NULL) {
63 #if defined(HAVE_GETPAGESIZE)
64     page_size = getpagesize();
65 #else
66     page_size = 1024;
67 #endif
68     j = (NBLOCKS * blocksize) + page_size;      /* buffer space plus one page */
69     j = am_round(j, page_size);                 /* even number of pages */
70     p = alloc(j);
71     i = (p - (char *)0) & (page_size - 1);      /* page boundary offset */
72     if(i != 0) {
73       randombytes = p + page_size - i;          /* round up to page boundary */
74     } else {
75       randombytes = p;                          /* alloc already on boundary */
76     }
77   }
78 }
79
80 static void initnotrandombytes() {
81   int i, j;
82   char *p;
83
84   allocrandombytes();
85   j =NBLOCKS * blocksize;
86   p = randombytes;
87   for(i=0; i < j; ++i) {
88     *p++ = (char) (i % 256);
89   }
90 }
91
92 static void initrandombytes() {
93   int i, j;
94   char *p;
95
96   allocrandombytes();
97   j = NBLOCKS * blocksize;
98   p = randombytes;
99   for(i=0; i < j; ++i) {
100     *p++ = (char)random();
101   }
102 }
103
104 static char *getrandombytes() {
105   static int counter = 0;
106
107   return randombytes + ((counter++ % NBLOCKS) * blocksize);
108 }
109
110 static int short_write;
111
112 int writeblock(fd)
113      int fd;
114 {
115   size_t w;
116
117   if ((w = tapefd_write(fd, getrandombytes(), blocksize)) == blocksize) {
118     return 1;
119   }
120   if (w >= 0) {
121     short_write = 1;
122   } else {
123     short_write = 0;
124   }
125   return 0;
126 }
127
128
129 /* returns number of blocks actually written */
130 size_t writeblocks(int fd, size_t nblks)
131 {
132   size_t blks = 0;
133
134   while (blks < nblks) {
135     if (! writeblock(fd)) {
136       return 0;
137     }
138     blks++;
139   }
140
141   return blks;
142 }
143
144
145 void usage()
146 {
147   fputs("usage: ", stderr);
148   fputs(sProgName, stderr);
149   fputs(" [-h]", stderr);
150   fputs(" [-c]", stderr);
151   fputs(" [-o]", stderr);
152   fputs(" [-b blocksize]", stderr);
153   fputs(" [-e estsize]", stderr);
154   fputs(" [-f tapedev]", stderr);
155   fputs(" [-t typename]", stderr);
156   fputc('\n', stderr);
157 }
158
159 void help()
160 {
161   usage();
162   fputs("\
163   -h                    display this message\n\
164   -c                    run hardware compression detection test only\n\
165   -o                    overwrite amanda tape\n\
166   -b blocksize          record block size (default: 32k)\n\
167   -e estsize            estimated tape size (default: 1g == 1024m)\n\
168   -f tapedev            tape device name (default: $TAPE)\n\
169   -t typename           tapetype name (default: unknown-tapetype)\n\
170 \n\
171 Note: disable hardware compression when running this program.\n\
172 ", stderr);
173 }
174
175
176 int do_tty;
177
178 void show_progress(blocks, files)
179   size_t *blocks, *files;
180 {
181   fprintf(stderr, "wrote %ld %dKb block%s in %ld file%s",
182           (long)*blocks, blockkb, (*blocks == 1) ? "" : "s",
183           (long)*files, (*files == 1) ? "" : "s");
184 }
185
186
187 void do_pass(size, blocks, files, seconds)
188   size_t size, *blocks, *files;
189   time_t *seconds;
190 {
191   size_t blks;
192   time_t start, end;
193   int save_errno;
194
195   if (tapefd_rewind(fd) == -1) {
196     fprintf(stderr, "%s: could not rewind %s: %s\n",
197             sProgName, tapedev, strerror(errno));
198     exit(1);
199   }
200
201   time(&start);
202
203   while(1) {
204
205     if ((blks = writeblocks(fd, size)) <= 0 || tapefd_weof(fd, 1) != 0)
206       break;
207     *blocks += blks;
208     (*files)++;
209     if(do_tty) {
210       putc('\r', stderr);
211       show_progress(blocks, files);
212     }
213   }
214   save_errno = errno;
215
216   time(&end);
217
218   if (*blocks == 0) {
219     fprintf(stderr, "%s: could not write any data in this pass: %s\n",
220             sProgName, short_write ? "short write" : strerror(save_errno));
221     exit(1);
222   }
223
224   if(end <= start) {
225     /*
226      * Just in case time warped backward or the device is really, really
227      * fast (e.g. /dev/null testing).
228      */
229     *seconds = 1;
230   } else {
231     *seconds = end - start;
232   }
233   if(do_tty) {
234     putc('\r', stderr);
235   }
236   show_progress(blocks, files);
237   fprintf(stderr, " in %ld second%s (%s)\n",
238           (long)*seconds, ((long)*seconds == 1) ? "" : "s",
239           short_write ? "short write" : strerror(save_errno));
240 }
241
242
243 void do_pass0(size, seconds, dorewind)
244   size_t size;
245   time_t *seconds;
246   int dorewind;
247 {
248   size_t blks;
249   time_t start, end;
250   int save_errno;
251
252   if (dorewind  &&  tapefd_rewind(fd) == -1) {
253     fprintf(stderr, "%s: could not rewind %s: %s\n",
254             sProgName, tapedev, strerror(errno));
255     exit(1);
256   }
257
258   time(&start);
259
260   blks = writeblocks(fd, size);
261   tapefd_weof(fd, 1);
262
263   save_errno = errno;
264
265   time(&end);
266
267   if (blks <= 0) {
268     fprintf(stderr, "%s: could not write any data in this pass: %s\n",
269             sProgName, short_write ? "short write" : strerror(save_errno));
270     exit(1);
271   }
272
273   if(end <= start) {
274     /*
275      * Just in case time warped backward or the device is really, really
276      * fast (e.g. /dev/null testing).
277      */
278     *seconds = 1;
279   } else {
280     *seconds = end - start;
281   }
282 }
283
284
285 int main(argc, argv)
286      int argc;
287      char *argv[];
288 {
289   size_t pass1blocks = 0;
290   size_t pass2blocks = 0;
291   time_t pass1time;
292   time_t pass2time;
293   time_t timediff;
294   size_t pass1files = 0;
295   size_t pass2files = 0;
296   size_t estsize;
297   size_t pass0size;
298   size_t pass1size;
299   size_t pass2size;
300   size_t blockdiff;
301   size_t filediff;
302   long filemark;
303   long speed;
304   size_t size;
305   char *sizeunits;
306   int ch;
307   char *suffix;
308   char *typename;
309   time_t now;
310   int hwcompr = 0;
311   int comprtstonly = 0;
312   int overwrite_label = 0;
313   int is_labeled = 0;
314   char *result;
315   char *datestamp = NULL;
316   char *label = NULL;
317
318
319   if ((sProgName = strrchr(*argv, '/')) == NULL) {
320     sProgName = *argv;
321   } else {
322     sProgName++;
323   }
324
325   estsize = 1024 * 1024;                        /* assume 1 GByte for now */
326   tapedev = getenv("TAPE");
327   typename = "unknown-tapetype";
328
329   while ((ch = getopt(argc, argv, "b:e:f:t:hco")) != EOF) {
330     switch (ch) {
331     case 'b':
332       blockkb = strtol(optarg, &suffix, 0);
333       if (*suffix == '\0' || *suffix == 'k' || *suffix == 'K') {
334       } else if (*suffix == 'm' || *suffix == 'M') {
335         blockkb *= 1024;
336       } else if (*suffix == 'g' || *suffix == 'G') {
337         blockkb *= 1024 * 1024;
338       } else {
339         fprintf(stderr, "%s: unknown size suffix \'%c\'\n", sProgName, *suffix);
340         return 1;
341       }
342       break;
343     case 'e':
344       estsize = strtol(optarg, &suffix, 0);
345       if (*suffix == '\0' || *suffix == 'k' || *suffix == 'K') {
346       } else if (*suffix == 'm' || *suffix == 'M') {
347         estsize *= 1024;
348       } else if (*suffix == 'g' || *suffix == 'G') {
349         estsize *= 1024 * 1024;
350       } else {
351         fprintf(stderr, "%s: unknown size suffix \'%c\'\n", sProgName, *suffix);
352         return 1;
353       }
354       break;
355     case 'f':
356       tapedev = stralloc(optarg);
357       break;
358     case 't':
359       typename = stralloc(optarg);
360       break;
361     case 'c':
362       comprtstonly = 1;
363       break;
364     case 'h':
365       help();
366       return 1;
367       break;
368     case 'o':
369       overwrite_label=1;
370       break;
371     default:
372       fprintf(stderr, "%s: unknown option \'%c\'\n", sProgName, ch);
373       /* fall through to ... */
374     case '?':
375       usage();
376       return 1;
377       break;
378     }
379   }
380   blocksize = blockkb * 1024;
381
382   if (tapedev == NULL || optind < argc) {
383     usage();
384     return 1;
385   }
386
387 /* verifier tape */
388
389
390   fd = tape_open(tapedev, O_RDONLY);
391   if (fd == -1) {
392     fprintf(stderr, "%s: could not open %s: %s\n",
393             sProgName, tapedev, strerror(errno));
394     return 1;
395   }
396
397   if((result = tapefd_rdlabel(fd, &datestamp, &label)) == NULL) {
398     is_labeled = 1;
399   }
400   else if (strcmp(result,"not an amanda tape") == 0) {
401     is_labeled = 2;
402   }
403
404   if(tapefd_rewind(fd) == -1) {
405     fprintf(stderr, "%s: could not rewind %s: %s\n",
406             sProgName, tapedev, strerror(errno));
407     tapefd_close(fd);
408     return 1;
409   }
410
411   tapefd_close(fd);
412
413   if(is_labeled == 1 && overwrite_label == 0) {
414     fprintf(stderr, "%s: The tape is an amanda tape, use -o to overwrite the tape\n",
415             sProgName);
416     return 1;
417   }
418   else if(is_labeled == 2 && overwrite_label == 0) {
419     fprintf(stderr, "%s: The tape is already used, use -o to overwrite the tape\n",
420             sProgName);
421     return 1;
422   }
423
424   fd = tape_open(tapedev, O_RDWR);
425   if (fd == -1) {
426     fprintf(stderr, "%s: could not open %s: %s\n",
427             sProgName, tapedev, strerror(errno));
428     return 1;
429   }
430
431   do_tty = isatty(fileno(stderr));
432
433   /*
434    * Estimate pass: write twice a small file, once with compressable
435    * data and once with uncompressable data.
436    * The theory is that if the drive is in hardware compression mode
437    * we notice a significant difference in writing speed between the two
438    * (at least if we can provide data as fast the tape streams).
439    */
440
441   initnotrandombytes();
442
443   fprintf(stderr, "Estimate phase 1...");
444   pass0size = 8 * 1024 / blockkb;
445   pass1time = 0;
446   pass2time = 0;
447   /*
448    * To get accurate results, we should write enough data
449    * so that rewind/start/stop time is small compared to
450    * the total time; let's take 10%.
451    * The timer has a 1 sec granularity, so the test
452    * should take at least 10 seconds to measure a
453    * difference with 10% accuracy; let's take 25 seconds.
454    */ 
455   while (pass1time < 25 || ((100*(pass2time-pass1time)/pass2time) >= 10) ) {
456     if (pass1time != 0) {
457       int i = pass1time;
458       do {
459           pass0size *= 2;
460           i *= 2;
461       } while (i < 25);
462     }
463     /*
464      * first a dummy pass to rewind, stop, start and
465      * get drive streaming, then do the real timing
466      */
467     do_pass0(pass0size, &pass2time, 1);
468     do_pass0(pass0size, &pass1time, 0);
469     if (pass0size >= 10 * 1024 * 1024) {
470       fprintf(stderr,
471         "\rTape device is too fast to detect hardware compression...\n");
472       break;    /* avoid loops if tape is superfast or broken */
473     }
474   }
475   fprintf(stderr, "\rWriting %d Mbyte   compresseable data:  %d sec\n",
476         (int)(blockkb * pass0size / 1024), (int)pass1time);
477
478   /*
479    * now generate uncompressable data and try again
480    */
481   time(&now);
482   srandom(now);
483   initrandombytes();
484
485   fprintf(stderr, "Estimate phase 2...");
486   do_pass0(pass0size, &pass2time, 1);   /* rewind and get drive streaming */
487   do_pass0(pass0size, &pass2time, 0);
488   fprintf(stderr, "\rWriting %d Mbyte uncompresseable data:  %d sec\n",
489         (int)(blockkb * pass0size / 1024), (int)pass2time);
490
491   /*
492    * Compute the time difference between writing the compressable and
493    * uncompressable data.  If it differs more than 20%, then warn
494    * user that the tape drive has probably hardware compression enabled.
495    */
496   if (pass1time > pass2time) {
497     /*
498      * Strange!  I would expect writing compresseable data to be
499      * much faster (or about equal, if hardware compression is disabled)
500      */
501     timediff = 0;
502   } else {
503     timediff = pass2time - pass1time;
504   }
505   if (((100 * timediff) / pass2time) >= 20) {   /* 20% faster? */
506     fprintf(stderr, "WARNING: Tape drive has hardware compression enabled\n");
507     hwcompr = 1;
508   }
509
510   /*
511    * Inform about estimated time needed to run the remaining of this program
512    */
513   fprintf(stderr, "Estimated time to write 2 * %d Mbyte: ", estsize / 1024);
514   pass1time = (time_t)(2.0 * pass2time * estsize / (1.0 * pass0size * blockkb));
515         /* avoid overflow and underflow by doing math in floating point */
516   fprintf(stderr, "%ld sec = ", pass1time);
517   fprintf(stderr, "%ld h %ld min\n", (pass1time/3600), ((pass1time%3600) / 60));
518
519   if (comprtstonly) {
520         exit(hwcompr);
521   }
522
523
524   /*
525    * Do pass 1 -- write files that are 1% of the estimated size until error.
526    */
527   pass1size = (estsize * 0.01) / blockkb;       /* 1% of estimate */
528   if(pass1size <= 0) {
529     pass1size = 2;                              /* strange end case */
530   }
531   do_pass(pass1size, &pass1blocks, &pass1files, &pass1time);
532
533   /*
534    * Do pass 2 -- write smaller files until error.
535    */
536   pass2size = pass1size / 2;
537   do_pass(pass2size, &pass2blocks, &pass2files, &pass2time);
538
539   /*
540    * Compute the size of a filemark as the difference in data written
541    * between pass 1 and pass 2 divided by the difference in number of
542    * file marks written between pass 1 and pass 2.  Note that we have
543    * to be careful in case size_t is unsigned (i.e. do not subtract
544    * things and then check for less than zero).
545    */
546   if (pass1blocks <= pass2blocks) {
547     /*
548      * If tape marks take up space, there should be fewer blocks in pass
549      * 2 than in pass 1 since we wrote twice as many tape marks.  But
550      * odd things happen, so make sure the result does not go negative.
551      */
552     blockdiff = 0;
553   } else {
554     blockdiff = pass1blocks - pass2blocks;
555   }
556   if (pass2files <= pass1files) {
557     /*
558      * This should not happen, but just in case ...
559      */
560     filediff = 1;
561   } else {
562     filediff = pass2files - pass1files;
563   }
564   filemark = blockdiff * blockkb / filediff;
565
566   /*
567    * Compute the length as the average of the two pass sizes including
568    * tape marks.
569    */
570   size = ((pass1blocks * blockkb + filemark * pass1files)
571            + (pass2blocks * blockkb + filemark * pass2files)) / 2;
572   if (size >= 1024 * 1024 * 1000) {
573     size /= 1024 * 1024;
574     sizeunits = "gbytes";
575   } else if (size >= 1024 * 1000) {
576     size /= 1024;
577     sizeunits = "mbytes";
578   } else {
579     sizeunits = "kbytes";
580   }
581
582   /*
583    * Compute the speed as the average of the two passes.
584    */
585   speed = (((double)pass1blocks * blockkb / pass1time)
586            + ((double)pass2blocks * blockkb / pass2time)) / 2;
587
588   /*
589    * Dump the tapetype.
590    */
591   printf("define tapetype %s {\n", typename);
592   printf("    comment \"just produced by tapetype prog (hardware compression %s)\"\n",
593         hwcompr ? "on" : "off");
594   printf("    length %ld %s\n", (long)size, sizeunits);
595   printf("    filemark %ld kbytes\n", filemark);
596   printf("    speed %ld kps\n", speed);
597   printf("}\n");
598
599   if (tapefd_rewind(fd) == -1) {
600     fprintf(stderr, "%s: could not rewind %s: %s\n",
601             sProgName, tapedev, strerror(errno));
602     return 1;
603   }
604
605   if (tapefd_close(fd) == -1) {
606     fprintf(stderr, "%s: could not close %s: %s\n",
607             sProgName, tapedev, strerror(errno));
608     return 1;
609   }
610
611   return 0;
612 }