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