Imported Upstream version 2.4.4p3
[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 2003/11/28 12:34:52 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(" [-b blocksize]", stderr);
152   fputs(" [-e estsize]", stderr);
153   fputs(" [-f tapedev]", stderr);
154   fputs(" [-t typename]", stderr);
155   fputc('\n', stderr);
156 }
157
158 void help()
159 {
160   usage();
161   fputs("\
162   -h                    display this message\n\
163   -c                    run hardware compression detection test only\n\
164   -b blocksize          record block size (default: 32k)\n\
165   -e estsize            estimated tape size (default: 1g == 1024m)\n\
166   -f tapedev            tape device name (default: $TAPE)\n\
167   -t typename           tapetype name (default: unknown-tapetype)\n\
168 \n\
169 Note: disable hardware compression when running this program.\n\
170 ", stderr);
171 }
172
173
174 int do_tty;
175
176 void show_progress(blocks, files)
177   size_t *blocks, *files;
178 {
179   fprintf(stderr, "wrote %ld %dKb block%s in %ld file%s",
180           (long)*blocks, blockkb, (*blocks == 1) ? "" : "s",
181           (long)*files, (*files == 1) ? "" : "s");
182 }
183
184
185 void do_pass(size, blocks, files, seconds)
186   size_t size, *blocks, *files;
187   time_t *seconds;
188 {
189   size_t blks;
190   time_t start, end;
191   int save_errno;
192
193   if (tapefd_rewind(fd) == -1) {
194     fprintf(stderr, "%s: could not rewind %s: %s\n",
195             sProgName, tapedev, strerror(errno));
196     exit(1);
197   }
198
199   time(&start);
200
201   while(1) {
202
203     if ((blks = writeblocks(fd, size)) <= 0 || tapefd_weof(fd, 1) != 0)
204       break;
205     *blocks += blks;
206     (*files)++;
207     if(do_tty) {
208       putc('\r', stderr);
209       show_progress(blocks, files);
210     }
211   }
212   save_errno = errno;
213
214   time(&end);
215
216   if (*blocks == 0) {
217     fprintf(stderr, "%s: could not write any data in this pass: %s\n",
218             sProgName, short_write ? "short write" : strerror(save_errno));
219     exit(1);
220   }
221
222   if(end <= start) {
223     /*
224      * Just in case time warped backward or the device is really, really
225      * fast (e.g. /dev/null testing).
226      */
227     *seconds = 1;
228   } else {
229     *seconds = end - start;
230   }
231   if(do_tty) {
232     putc('\r', stderr);
233   }
234   show_progress(blocks, files);
235   fprintf(stderr, " in %ld second%s (%s)\n",
236           (long)*seconds, ((long)*seconds == 1) ? "" : "s",
237           short_write ? "short write" : strerror(save_errno));
238 }
239
240
241 void do_pass0(size, seconds, dorewind)
242   size_t size;
243   time_t *seconds;
244   int dorewind;
245 {
246   size_t blks;
247   time_t start, end;
248   int save_errno;
249
250   if (dorewind  &&  tapefd_rewind(fd) == -1) {
251     fprintf(stderr, "%s: could not rewind %s: %s\n",
252             sProgName, tapedev, strerror(errno));
253     exit(1);
254   }
255
256   time(&start);
257
258   blks = writeblocks(fd, size);
259   tapefd_weof(fd, 1);
260
261   save_errno = errno;
262
263   time(&end);
264
265   if (blks <= 0) {
266     fprintf(stderr, "%s: could not write any data in this pass: %s\n",
267             sProgName, short_write ? "short write" : strerror(save_errno));
268     exit(1);
269   }
270
271   if(end <= start) {
272     /*
273      * Just in case time warped backward or the device is really, really
274      * fast (e.g. /dev/null testing).
275      */
276     *seconds = 1;
277   } else {
278     *seconds = end - start;
279   }
280 }
281
282
283 int main(argc, argv)
284      int argc;
285      char *argv[];
286 {
287   size_t pass1blocks = 0;
288   size_t pass2blocks = 0;
289   time_t pass1time;
290   time_t pass2time;
291   time_t timediff;
292   size_t pass1files = 0;
293   size_t pass2files = 0;
294   size_t estsize;
295   size_t pass0size;
296   size_t pass1size;
297   size_t pass2size;
298   size_t blockdiff;
299   size_t filediff;
300   long filemark;
301   long speed;
302   size_t size;
303   char *sizeunits;
304   int ch;
305   char *suffix;
306   char *typename;
307   time_t now;
308   int hwcompr = 0;
309   int comprtstonly = 0;
310
311   if ((sProgName = strrchr(*argv, '/')) == NULL) {
312     sProgName = *argv;
313   } else {
314     sProgName++;
315   }
316
317   estsize = 1024 * 1024;                        /* assume 1 GByte for now */
318   tapedev = getenv("TAPE");
319   typename = "unknown-tapetype";
320
321   while ((ch = getopt(argc, argv, "b:e:f:t:hc")) != EOF) {
322     switch (ch) {
323     case 'b':
324       blockkb = strtol(optarg, &suffix, 0);
325       if (*suffix == '\0' || *suffix == 'k' || *suffix == 'K') {
326       } else if (*suffix == 'm' || *suffix == 'M') {
327         blockkb *= 1024;
328       } else if (*suffix == 'g' || *suffix == 'G') {
329         blockkb *= 1024 * 1024;
330       } else {
331         fprintf(stderr, "%s: unknown size suffix \'%c\'\n", sProgName, *suffix);
332         return 1;
333       }
334       break;
335     case 'e':
336       estsize = strtol(optarg, &suffix, 0);
337       if (*suffix == '\0' || *suffix == 'k' || *suffix == 'K') {
338       } else if (*suffix == 'm' || *suffix == 'M') {
339         estsize *= 1024;
340       } else if (*suffix == 'g' || *suffix == 'G') {
341         estsize *= 1024 * 1024;
342       } else {
343         fprintf(stderr, "%s: unknown size suffix \'%c\'\n", sProgName, *suffix);
344         return 1;
345       }
346       break;
347     case 'f':
348       tapedev = stralloc(optarg);
349       break;
350     case 't':
351       typename = stralloc(optarg);
352       break;
353     case 'c':
354       comprtstonly = 1;
355       break;
356     case 'h':
357       help();
358       return 1;
359       break;
360     default:
361       fprintf(stderr, "%s: unknown option \'%c\'\n", sProgName, ch);
362       /* fall through to ... */
363     case '?':
364       usage();
365       return 1;
366       break;
367     }
368   }
369   blocksize = blockkb * 1024;
370
371   if (tapedev == NULL || optind < argc) {
372     usage();
373     return 1;
374   }
375
376   fd = tape_open(tapedev, O_RDWR);
377   if (fd == -1) {
378     fprintf(stderr, "%s: could not open %s: %s\n",
379             sProgName, tapedev, strerror(errno));
380     return 1;
381   }
382
383   do_tty = isatty(fileno(stderr));
384
385   /*
386    * Estimate pass: write twice a small file, once with compressable
387    * data and once with uncompressable data.
388    * The theory is that if the drive is in hardware compression mode
389    * we notice a significant difference in writing speed between the two
390    * (at least if we can provide data as fast the tape streams).
391    */
392
393   initnotrandombytes();
394
395   fprintf(stderr, "Estimate phase 1...");
396   pass0size = 8 * 1024 / blockkb;
397   pass1time = 0;
398   pass2time = 0;
399   /*
400    * To get accurate results, we should write enough data
401    * so that rewind/start/stop time is small compared to
402    * the total time; let's take 10%.
403    * The timer has a 1 sec granularity, so the test
404    * should take at least 10 seconds to measure a
405    * difference with 10% accuracy; let's take 25 seconds.
406    */ 
407   while (pass1time < 25 || ((100*(pass2time-pass1time)/pass2time) >= 10) ) {
408     if (pass1time != 0) {
409       int i = pass1time;
410       do {
411           pass0size *= 2;
412           i *= 2;
413       } while (i < 25);
414     }
415     /*
416      * first a dummy pass to rewind, stop, start and
417      * get drive streaming, then do the real timing
418      */
419     do_pass0(pass0size, &pass2time, 1);
420     do_pass0(pass0size, &pass1time, 0);
421     if (pass0size >= 10 * 1024 * 1024) {
422       fprintf(stderr,
423         "\rTape device is too fast to detect hardware compression...\n");
424       break;    /* avoid loops if tape is superfast or broken */
425     }
426   }
427   fprintf(stderr, "\rWriting %d Mbyte   compresseable data:  %d sec\n",
428         (int)(blockkb * pass0size / 1024), (int)pass1time);
429
430   /*
431    * now generate uncompressable data and try again
432    */
433   time(&now);
434   srandom(now);
435   initrandombytes();
436
437   fprintf(stderr, "Estimate phase 2...");
438   do_pass0(pass0size, &pass2time, 1);   /* rewind and get drive streaming */
439   do_pass0(pass0size, &pass2time, 0);
440   fprintf(stderr, "\rWriting %d Mbyte uncompresseable data:  %d sec\n",
441         (int)(blockkb * pass0size / 1024), (int)pass2time);
442
443   /*
444    * Compute the time difference between writing the compressable and
445    * uncompressable data.  If it differs more than 20%, then warn
446    * user that the tape drive has probably hardware compression enabled.
447    */
448   if (pass1time > pass2time) {
449     /*
450      * Strange!  I would expect writing compresseable data to be
451      * much faster (or about equal, if hardware compression is disabled)
452      */
453     timediff = 0;
454   } else {
455     timediff = pass2time - pass1time;
456   }
457   if (((100 * timediff) / pass2time) >= 20) {   /* 20% faster? */
458     fprintf(stderr, "WARNING: Tape drive has hardware compression enabled\n");
459     hwcompr = 1;
460   }
461
462   /*
463    * Inform about estimated time needed to run the remaining of this program
464    */
465   fprintf(stderr, "Estimated time to write 2 * %d Mbyte: ", estsize / 1024);
466   pass1time = (time_t)(2.0 * pass2time * estsize / (1.0 * pass0size * blockkb));
467         /* avoid overflow and underflow by doing math in floating point */
468   fprintf(stderr, "%ld sec = ", pass1time);
469   fprintf(stderr, "%ld h %ld min\n", (pass1time/3600), ((pass1time%3600) / 60));
470
471   if (comprtstonly) {
472         exit(hwcompr);
473   }
474
475
476   /*
477    * Do pass 1 -- write files that are 1% of the estimated size until error.
478    */
479   pass1size = (estsize * 0.01) / blockkb;       /* 1% of estimate */
480   if(pass1size <= 0) {
481     pass1size = 2;                              /* strange end case */
482   }
483   do_pass(pass1size, &pass1blocks, &pass1files, &pass1time);
484
485   /*
486    * Do pass 2 -- write smaller files until error.
487    */
488   pass2size = pass1size / 2;
489   do_pass(pass2size, &pass2blocks, &pass2files, &pass2time);
490
491   /*
492    * Compute the size of a filemark as the difference in data written
493    * between pass 1 and pass 2 divided by the difference in number of
494    * file marks written between pass 1 and pass 2.  Note that we have
495    * to be careful in case size_t is unsigned (i.e. do not subtract
496    * things and then check for less than zero).
497    */
498   if (pass1blocks <= pass2blocks) {
499     /*
500      * If tape marks take up space, there should be fewer blocks in pass
501      * 2 than in pass 1 since we wrote twice as many tape marks.  But
502      * odd things happen, so make sure the result does not go negative.
503      */
504     blockdiff = 0;
505   } else {
506     blockdiff = pass1blocks - pass2blocks;
507   }
508   if (pass2files <= pass1files) {
509     /*
510      * This should not happen, but just in case ...
511      */
512     filediff = 1;
513   } else {
514     filediff = pass2files - pass1files;
515   }
516   filemark = blockdiff * blockkb / filediff;
517
518   /*
519    * Compute the length as the average of the two pass sizes including
520    * tape marks.
521    */
522   size = ((pass1blocks * blockkb + filemark * pass1files)
523            + (pass2blocks * blockkb + filemark * pass2files)) / 2;
524   if (size >= 1024 * 1024 * 1000) {
525     size /= 1024 * 1024;
526     sizeunits = "gbytes";
527   } else if (size >= 1024 * 1000) {
528     size /= 1024;
529     sizeunits = "mbytes";
530   } else {
531     sizeunits = "kbytes";
532   }
533
534   /*
535    * Compute the speed as the average of the two passes.
536    */
537   speed = (((double)pass1blocks * blockkb / pass1time)
538            + ((double)pass2blocks * blockkb / pass2time)) / 2;
539
540   /*
541    * Dump the tapetype.
542    */
543   printf("define tapetype %s {\n", typename);
544   printf("    comment \"just produced by tapetype prog (hardware compression %s)\"\n",
545         hwcompr ? "on" : "off");
546   printf("    length %ld %s\n", (long)size, sizeunits);
547   printf("    filemark %ld kbytes\n", filemark);
548   printf("    speed %ld kps\n", speed);
549   printf("}\n");
550
551   if (tapefd_rewind(fd) == -1) {
552     fprintf(stderr, "%s: could not rewind %s: %s\n",
553             sProgName, tapedev, strerror(errno));
554     return 1;
555   }
556
557   if (tapefd_close(fd) == -1) {
558     fprintf(stderr, "%s: could not close %s: %s\n",
559             sProgName, tapedev, strerror(errno));
560     return 1;
561   }
562
563   return 0;
564 }