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