26077b1d90f156af2dd4e2bb99dedd8ee519ca6b
[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     g_fprintf(stderr,
214             plural(_("wrote %lld %zu Kb block"),
215                    _("wrote %lld %zu Kb blocks"),
216                    *blocks),
217             (long long)*blocks,
218             blockkb);
219     g_fprintf(stderr,
220             plural(_(" in %lld file"),
221                    _(" in %lld files"),
222                    *files),
223             (long long)*files);
224 }
225
226
227 static void
228 do_pass(
229     off_t       size,
230     off_t *     blocks,
231     off_t *     files,
232     time_t *    seconds)
233 {
234   off_t blks;
235   time_t start, end;
236   int save_errno;
237
238   if (tapefd_rewind(fd) == -1) {
239     g_fprintf(stderr, _("%s: could not rewind %s: %s\n"),
240             sProgName, tapedev, strerror(errno));
241     exit(1);
242   }
243   if (((-1 == tapefd_close(fd)) ||
244        (-1 == (fd = tape_open(tapedev, O_RDWR))))) {
245     g_fprintf(stderr, "%s: could not re-open %s: %s\n",
246             sProgName, tapedev, strerror(errno));
247     exit(1);
248   }
249
250   time(&start);
251
252   while(1) {
253
254     if ((blks = writeblocks(fd, size)) <= (off_t)0 || tapefd_weof(fd, (off_t)1) != 0)
255       break;
256     *blocks += blks;
257     *files += (off_t)1;
258     if(do_tty) {
259       putc('\r', stderr);
260       show_progress(blocks, files);
261     }
262   }
263   save_errno = errno;
264
265   time(&end);
266
267   if (*blocks == (off_t)0) {
268     g_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   if(do_tty) {
283     putc('\r', stderr);
284   }
285   show_progress(blocks, files);
286   g_fprintf(stderr,
287           plural(_(" in %jd second (%s)\n"),
288                  _(" in %jd seconds (%s)\n"),
289                  *seconds),
290           (intmax_t)*seconds,
291           short_write ? _("short write") : strerror(save_errno));
292 }
293
294
295 static void
296 do_pass0(
297     off_t       size,
298     time_t *    seconds,
299     int         dorewind)
300 {
301   off_t blks;
302   time_t start, end;
303   int save_errno;
304
305   if (dorewind  &&  tapefd_rewind(fd) == -1) {
306     g_fprintf(stderr, _("%s: could not rewind %s: %s\n"),
307             sProgName, tapedev, strerror(errno));
308     exit(1);
309   }
310
311   if (dorewind &&
312       ((-1 == tapefd_close(fd)) ||
313        (-1 == (fd = tape_open(tapedev, O_RDWR))))) {
314     g_fprintf(stderr, "%s: could not re-open %s: %s\n",
315             sProgName, tapedev, strerror(errno));
316     exit(1);
317   }
318
319
320   time(&start);
321
322   blks = writeblocks(fd, size);
323   tapefd_weof(fd, (off_t)1);
324
325   save_errno = errno;
326
327   time(&end);
328
329   if (blks <= (off_t)0) {
330     g_fprintf(stderr, _("%s: could not write any data in this pass: %s\n"),
331             sProgName, short_write ? _("short write") : strerror(save_errno));
332     exit(1);
333   }
334
335   if(end <= start) {
336     /*
337      * Just in case time warped backward or the device is really, really
338      * fast (e.g. /dev/null testing).
339      */
340     *seconds = 1;
341   } else {
342     *seconds = end - start;
343   }
344 }
345
346
347 int
348 main(
349     int         argc,
350     char **     argv)
351 {
352   off_t pass1blocks = (off_t)0;
353   off_t pass2blocks = (off_t)0;
354   time_t pass1time;
355   time_t pass2time;
356   time_t timediff;
357   off_t pass1files = (off_t)0;
358   off_t pass2files = (off_t)0;
359   off_t estsize;
360   off_t pass0size;
361   off_t pass1size;
362   off_t pass2size;
363   off_t blockdiff;
364   off_t filediff;
365   size_t filemark;
366   unsigned long speed;
367   off_t size;
368   char *sizeunits;
369   int ch;
370   char *suffix = NULL;
371   char *typename;
372   time_t now;
373   int hwcompr = 0;
374   int comprtstonly = 0;
375   int overwrite_label = 0;
376   int is_labeled = 0;
377   char *result;
378   char *datestamp = NULL;
379   char *label = NULL;
380
381   /*
382    * Configure program for internationalization:
383    *   1) Only set the message locale for now.
384    *   2) Set textdomain for all amanda related programs to "amanda"
385    *      We don't want to be forced to support dozens of message catalogs.
386    */  
387   setlocale(LC_MESSAGES, "C");
388   textdomain("amanda"); 
389
390   if ((sProgName = strrchr(*argv, '/')) == NULL) {
391     sProgName = *argv;
392   } else {
393     sProgName++;
394   }
395
396   /* Don't die when child closes pipe */
397   signal(SIGPIPE, SIG_IGN);
398
399   estsize = (off_t)0;
400   tapedev = getenv("TAPE");
401   typename = "unknown-tapetype";
402
403   while ((ch = getopt(argc, argv, "b:e:f:t:hco")) != EOF) {
404     switch (ch) {
405     case 'b':
406       blockkb = (size_t)strtol(optarg, &suffix, 0);
407       if (!(*suffix == '\0' || *suffix == 'k' || *suffix == 'K')) {
408         if (*suffix == 'm' || *suffix == 'M') {
409           blockkb *= 1024;
410         } else if (*suffix == 'g' || *suffix == 'G') {
411           blockkb *= (1024 * 1024);
412         } else {
413           g_fprintf(stderr, _("%s: unknown size suffix \'%c\'\n"),
414                   sProgName, *suffix);
415           return 1;
416         }
417       }
418       break;
419     case 'e':
420       estsize = OFF_T_STRTOL(optarg, &suffix, 0);
421       if (!(*suffix == '\0' || *suffix == 'k' || *suffix == 'K')) {
422         if (*suffix == 'm' || *suffix == 'M') {
423           estsize *= (off_t)1024;
424         } else if (*suffix == 'g' || *suffix == 'G') {
425           estsize *= (off_t)(1024 * 1024);
426         } else {
427           g_fprintf(stderr, _("%s: unknown size suffix \'%c\'\n"),
428                   sProgName, *suffix);
429           return 1;
430         }
431       }
432       break;
433
434     case 'f':
435       tapedev = stralloc(optarg);
436       break;
437
438     case 't':
439       typename = stralloc(optarg);
440       break;
441
442     case 'c':
443       comprtstonly = 1;
444       break;
445
446     case 'h':
447       help();
448       return 1;
449
450     case 'o':
451       overwrite_label=1;
452       break;
453
454     default:
455       g_fprintf(stderr, _("%s: unknown option \'%c\'\n"), sProgName, ch);
456       /*FALLTHROUGH*/
457
458     case '?':
459       usage();
460       return 1;
461     }
462   }
463   blocksize = blockkb * 1024;
464
465   if (tapedev == NULL) {
466     g_fprintf(stderr, _("%s: No tapedev specified\n"), sProgName);
467     usage();
468     return 1;
469   }
470   if (optind < argc) {
471     usage();
472     return 1;
473   }
474
475   if (estsize == 0) {
476     if (comprtstonly) {
477       estsize = (off_t)(1024 * 1024);           /* assume 1 GByte for now */
478     } else {
479       g_fprintf(stderr, _("%s: please specify estimated tape capacity (e.g. '-e 4g')\n"), sProgName);
480       usage();
481       return 1;
482     }
483   }
484
485
486 /* verifier tape */
487
488
489   fd = tape_open(tapedev, O_RDONLY, 0);
490   if (fd == -1) {
491     g_fprintf(stderr, _("%s: could not open %s: %s\n"),
492             sProgName, tapedev, strerror(errno));
493     return 1;
494   }
495
496   if((result = tapefd_rdlabel(fd, &datestamp, &label)) == NULL) {
497     is_labeled = 1;
498   }
499   else if (strcmp(result,_("not an amanda tape")) == 0) {
500     is_labeled = 2;
501   }
502
503   if(tapefd_rewind(fd) == -1) {
504     g_fprintf(stderr, _("%s: could not rewind %s: %s\n"),
505             sProgName, tapedev, strerror(errno));
506     tapefd_close(fd);
507     return 1;
508   }
509
510   tapefd_close(fd);
511
512   if(is_labeled == 1 && overwrite_label == 0) {
513     g_fprintf(stderr, _("%s: The tape is an amanda tape, use -o to overwrite the tape\n"),
514             sProgName);
515     return 1;
516   }
517   else if(is_labeled == 2 && overwrite_label == 0) {
518     g_fprintf(stderr, _("%s: The tape is already used, use -o to overwrite the tape\n"),
519             sProgName);
520     return 1;
521   }
522
523   fd = tape_open(tapedev, O_RDWR, 0);
524   if (fd == -1) {
525     g_fprintf(stderr, _("%s: could not open %s: %s\n"),
526             sProgName, tapedev, strerror(errno));
527     return 1;
528   }
529
530   do_tty = isatty(fileno(stderr));
531
532   /*
533    * Estimate pass: write twice a small file, once with compressable
534    * data and once with uncompressable data.
535    * The theory is that if the drive is in hardware compression mode
536    * we notice a significant difference in writing speed between the two
537    * (at least if we can provide data as fast the tape streams).
538    */
539
540   initnotrandombytes();
541
542   g_fprintf(stderr, _("Estimate phase 1..."));
543   pass0size = (off_t)(8 * 1024 / blockkb);
544   pass1time = 0;
545   pass2time = 0;
546   /*
547    * To get accurate results, we should write enough data
548    * so that rewind/start/stop time is small compared to
549    * the total time; let's take 10%.
550    * The timer has a 1 sec granularity, so the test
551    * should take at least 10 seconds to measure a
552    * difference with 10% accuracy; let's take 25 seconds.
553    */ 
554   while (pass1time < 25 || ((100*(pass2time-pass1time)/pass2time) >= 10) ) {
555     if (pass1time != 0) {
556       time_t t = pass1time;
557       do {
558           pass0size *= (off_t)2;
559           t *= 2;
560       } while (t < 25);
561     }
562     /*
563      * first a dummy pass to rewind, stop, start and
564      * get drive streaming, then do the real timing
565      */
566     do_pass0(pass0size, &pass2time, 1);
567     do_pass0(pass0size, &pass1time, 0);
568     if (pass0size >= (off_t)(10 * 1024 * 1024)) {
569       g_fprintf(stderr, "\r");
570       g_fprintf(stderr,
571         _("Tape device is too fast to detect hardware compression...\n"));
572       break;    /* avoid loops if tape is superfast or broken */
573     }
574   }
575   g_fprintf(stderr, "\r");
576   g_fprintf(stderr,
577         _("Writing %lld Mbyte   compresseable data:  %jd sec\n"),
578         (long long)((off_t)blockkb * pass0size / (off_t)1024),
579         (intmax_t)pass1time);
580
581   /*
582    * now generate uncompressable data and try again
583    */
584   time(&now);
585   srandom((unsigned)now);
586   initrandombytes();
587
588   g_fprintf(stderr, _("Estimate phase 2..."));
589   do_pass0(pass0size, &pass2time, 1);   /* rewind and get drive streaming */
590   do_pass0(pass0size, &pass2time, 0);
591   g_fprintf(stderr, "\r");
592   g_fprintf(stderr, _("Writing %lld Mbyte uncompresseable data: %jd sec\n"),
593         (long long)((off_t)blockkb * pass0size / (off_t)1024),
594         (intmax_t)pass2time);
595
596   /*
597    * Compute the time difference between writing the compressable and
598    * uncompressable data.  If it differs more than 20%, then warn
599    * user that the tape drive has probably hardware compression enabled.
600    */
601   if (pass1time > pass2time) {
602     /*
603      * Strange!  I would expect writing compresseable data to be
604      * much faster (or about equal, if hardware compression is disabled)
605      */
606     timediff = 0;
607   } else {
608     timediff = pass2time - pass1time;
609   }
610   if (((100 * timediff) / pass2time) >= 20) {   /* 20% faster? */
611     g_fprintf(stderr, _("WARNING: Tape drive has hardware compression enabled\n"));
612     hwcompr = 1;
613   }
614
615   /*
616    * Inform about estimated time needed to run the remaining of this program
617    */
618   g_fprintf(stderr, _("Estimated time to write 2 * %lu Mbyte: "), (unsigned long) (estsize / (off_t)1024));
619   pass1time = (time_t)(2.0 * (double)pass2time * (double)estsize /
620                 (1.0 * (double)pass0size * (double)blockkb));
621         /* avoid overflow and underflow by doing math in floating point */
622   g_fprintf(stderr, _("%jd sec = %jd h %jd min\n"),
623           (intmax_t)pass1time,
624           (intmax_t)(pass1time/(time_t)3600),
625           (intmax_t)((pass1time%(time_t)3600) / (time_t)60));
626
627   if (comprtstonly) {
628         exit(hwcompr);
629   }
630
631
632   /*
633    * Do pass 1 -- write files that are 1% of the estimated size until error.
634    */
635   pass1size = (off_t)(((double)estsize * 0.01) / (double)blockkb); /* 1% of estimate */
636   if(pass1size <= (off_t)0) {
637     pass1size = (off_t)2;                               /* strange end case */
638   }
639   do_pass(pass1size, &pass1blocks, &pass1files, &pass1time);
640
641   /*
642    * Do pass 2 -- write smaller files until error.
643    */
644   pass2size = pass1size / (off_t)2;
645   do_pass(pass2size, &pass2blocks, &pass2files, &pass2time);
646
647   /*
648    * Compute the size of a filemark as the difference in data written
649    * between pass 1 and pass 2 divided by the difference in number of
650    * file marks written between pass 1 and pass 2.  Note that we have
651    * to be careful in case size_t is unsigned (i.e. do not subtract
652    * things and then check for less than zero).
653    */
654   if (pass1blocks <= pass2blocks) {
655     /*
656      * If tape marks take up space, there should be fewer blocks in pass
657      * 2 than in pass 1 since we wrote twice as many tape marks.  But
658      * odd things happen, so make sure the result does not go negative.
659      */
660     blockdiff = (off_t)0;
661   } else {
662     blockdiff = pass1blocks - pass2blocks;
663   }
664   if (pass2files <= pass1files) {
665     /*
666      * This should not happen, but just in case ...
667      */
668     filediff = (off_t)1;
669   } else {
670     filediff = pass2files - pass1files;
671   }
672   filemark = (size_t)((blockdiff * (off_t)blockkb) / filediff);
673
674   /*
675    * Compute the length as the average of the two pass sizes including
676    * tape marks.
677    */
678   size = ((pass1blocks * (off_t)blockkb + (off_t)filemark * pass1files)
679            + (pass2blocks * (off_t)blockkb + (off_t)filemark * pass2files))
680            / (off_t)2;
681   if (size >= (off_t)(1024 * 1024 * 1000)) {
682     size /= (off_t)(1024 * 1024);
683     sizeunits = "gbytes";
684   } else if (size >= (off_t)(1024 * 1000)) {
685     size /= (off_t)1024;
686     sizeunits = "mbytes";
687   } else {
688     sizeunits = "kbytes";
689   }
690
691   /*
692    * Compute the speed as the average of the two passes.
693    */
694   speed = (unsigned long)((((double)pass1blocks
695            * (double)blockkb / (double)pass1time)
696            + ((double)pass2blocks * (double)blockkb / (double)pass2time)) / 2.0);
697
698   /*
699    * Dump the tapetype.
700    */
701   g_printf("define tapetype %s {\n", typename);
702   g_printf(_("    comment \"just produced by tapetype prog (hardware compression %s)\"\n"),
703         hwcompr ? _("on") : _("off"));
704   g_printf("    length %lld %s\n", (long long)size, sizeunits);
705   g_printf("    filemark %zu kbytes\n", filemark);
706   g_printf("    speed %lu kps\n", speed);
707   g_printf("}\n");
708
709   if (tapefd_rewind(fd) == -1) {
710     g_fprintf(stderr, _("%s: could not rewind %s: %s\n"),
711             sProgName, tapedev, strerror(errno));
712     return 1;
713   }
714
715   if (tapefd_close(fd) == -1) {
716     g_fprintf(stderr, _("%s: could not close %s: %s\n"),
717             sProgName, tapedev, strerror(errno));
718     return 1;
719   }
720
721   return 0;
722 }