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