c8818a3dedf77c3827bf3bd045158b5df0a26398
[openafs.git] / src / butc / read_tape.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12
13 #include <roken.h>
14
15 #include <afs/cmd.h>
16 #include <lock.h>
17 #include <afs/tcdata.h>
18 #include <afs/usd.h>
19
20 usd_handle_t fd;
21 usd_handle_t ofd;
22 int ofdIsOpen = 0;
23 afs_int32 nrestore, nskip;
24 int ask, printlabels, printheaders, verbose;
25 char filename[100];
26 char *outfile;
27
28 #define TAPE_MAGIC  1100000009  /* Defined in file_tm.c */
29 #define BLOCK_MAGIC 1100000005
30 #define FILE_MAGIC  1000000007
31 #define FILE_BEGIN     0
32 #define FILE_END       1
33 #define FILE_EOD      -1
34
35 struct tapeLabel {              /* also in file_tm.c */
36     afs_int32 magic;
37     struct butm_tapeLabel label;
38 };
39 struct fileMark {               /* also in file_tm.c */
40     afs_int32 magic;
41     afs_uint32 nBytes;
42 };
43
44 /* Read a tape block of size 16K */
45 afs_int32
46 readblock(char *buffer)
47 {
48     u_int nread, total = 0;
49     int rc, fmcount = 0;
50
51     while (total < BUTM_BLOCKSIZE) {
52         rc = USD_READ(fd, buffer + total, BUTM_BLOCKSIZE - total, &nread);
53         if (rc != 0) {
54             return (rc);
55         } else if ((nread == 0) && (total == 0)) {
56             if (verbose)
57                 fprintf(stderr, "Hardware file mark\n");
58             if (++fmcount > 3) {
59                 if (verbose)
60                     fprintf(stderr,
61                             "Greater than 3 hardware file marks in a row - done\n");
62                 return -1;
63             }
64         } else if (nread == 0) {
65             fprintf(stderr, "Reached unexpected end of dump file\n");
66             return -1;
67         } else {
68             total += nread;
69         }
70     }
71     return 0;
72 }
73
74 void
75 printLabel(struct tapeLabel *tapeLabelPtr)
76 {
77     tapeLabelPtr->label.dumpid = ntohl(tapeLabelPtr->label.dumpid);
78     tapeLabelPtr->label.creationTime =
79         ntohl(tapeLabelPtr->label.creationTime);
80     tapeLabelPtr->label.expirationDate =
81         ntohl(tapeLabelPtr->label.expirationDate);
82     tapeLabelPtr->label.structVersion =
83         ntohl(tapeLabelPtr->label.structVersion);
84     tapeLabelPtr->label.useCount = ntohl(tapeLabelPtr->label.useCount);
85     tapeLabelPtr->label.size = ntohl(tapeLabelPtr->label.size);
86
87     fprintf(stderr, "\nDUMP       %u\n", tapeLabelPtr->label.dumpid);
88     if (printlabels) {
89         time_t t;
90
91         fprintf(stderr, "   AFS Tape  Name   : %s\n",
92                 tapeLabelPtr->label.AFSName);
93         fprintf(stderr, "   Permanent Name   : %s\n",
94                 tapeLabelPtr->label.pName);
95         fprintf(stderr, "   Dump Id          : %u\n",
96                 tapeLabelPtr->label.dumpid);
97         t = tapeLabelPtr->label.dumpid;
98         fprintf(stderr, "   Dump Id Time     : %.24s\n",
99                 ctime(&t));
100         t = tapeLabelPtr->label.creationTime;
101         fprintf(stderr, "   Date Created     : %.24s\n",
102                 ctime(&t));
103         t = tapeLabelPtr->label.expirationDate;
104         fprintf(stderr, "   Date Expires     : %.24s\n",
105                 ctime(&t));
106         fprintf(stderr, "   Version Number   : %d\n",
107                 tapeLabelPtr->label.structVersion);
108         fprintf(stderr, "   Tape Use Count   : %d\n",
109                 tapeLabelPtr->label.useCount);
110         fprintf(stderr, "   Tape Size        : %u\n",
111                 tapeLabelPtr->label.size);
112         fprintf(stderr, "   Comment          : %s\n",
113                 tapeLabelPtr->label.comment);
114         fprintf(stderr, "   Dump Path        : %s\n",
115                 tapeLabelPtr->label.dumpPath);
116         fprintf(stderr, "   Cell Name        : %s\n",
117                 tapeLabelPtr->label.cell);
118         fprintf(stderr, "   Creator Name     : %s\n",
119                 tapeLabelPtr->label.creator.name);
120         fprintf(stderr, "   Creator Instance : %s\n",
121                 tapeLabelPtr->label.creator.instance);
122         fprintf(stderr, "   Creator Cell     : %s\n",
123                 tapeLabelPtr->label.creator.cell);
124     }
125 }
126
127 void
128 printHeader(struct volumeHeader *headerPtr, afs_int32 *isvolheader)
129 {
130     static int volcount = 0;
131
132     *isvolheader = 0;
133     headerPtr->volumeID = ntohl(headerPtr->volumeID);
134     headerPtr->server = ntohl(headerPtr->server);
135     headerPtr->part = ntohl(headerPtr->part);
136     headerPtr->from = ntohl(headerPtr->from);
137     headerPtr->frag = ntohl(headerPtr->frag);
138     headerPtr->magic = ntohl(headerPtr->magic);
139     headerPtr->contd = ntohl(headerPtr->contd);
140     headerPtr->dumpID = ntohl(headerPtr->dumpID);
141     headerPtr->level = ntohl(headerPtr->level);
142     headerPtr->parentID = ntohl(headerPtr->parentID);
143     headerPtr->endTime = ntohl(headerPtr->endTime);
144     headerPtr->versionflags = ntohl(headerPtr->versionflags);
145     headerPtr->cloneDate = ntohl(headerPtr->cloneDate);
146
147     if (headerPtr->magic == TC_VOLBEGINMAGIC) {
148         time_t t;
149
150         *isvolheader = 1;
151         if (verbose)
152             fprintf(stderr, "Volume header\n");
153         t = headerPtr->from;
154         fprintf(stderr,
155                 "VOLUME %3d %s (%u) - %s dump from %.24s",
156                 ++volcount, headerPtr->volumeName, headerPtr->volumeID,
157                 (headerPtr->level ? "Incr" : "Full"),
158                 (t ? (char *)ctime(&t) : "0"));
159         /* do not include two ctime() calls in the same fprintf call as
160          * the same string buffer will be returned by each call. */
161         t = headerPtr->cloneDate;
162         fprintf(stderr, " till %.24s\n", ctime(&t));
163         if (printheaders) {
164             fprintf(stderr, "   Volume Name    = %s\n",
165                     headerPtr->volumeName);
166             fprintf(stderr, "   Volume ID      = %u\n", headerPtr->volumeID);
167             t = headerPtr->cloneDate;
168             fprintf(stderr, "   Clone Date     = %.24s\n",
169                     ctime(&t));
170             fprintf(stderr, "   Vol Fragment   = %d\n", headerPtr->frag);
171             fprintf(stderr, "   Vol Continued  = 0x%x\n", headerPtr->contd);
172             fprintf(stderr, "   DumpSet Name   = %s\n",
173                     headerPtr->dumpSetName);
174             fprintf(stderr, "   Dump ID        = %u\n", headerPtr->dumpID);
175             fprintf(stderr, "   Dump Level     = %d\n", headerPtr->level);
176             t = headerPtr->from;
177             fprintf(stderr, "   Dump Since     = %.24s\n",
178                     ctime(&t));
179             fprintf(stderr, "   parent Dump ID = %u\n", headerPtr->parentID);
180         }
181     } else if (headerPtr->magic == TC_VOLENDMAGIC) {
182         if (verbose)
183             fprintf(stderr, "Volume Trailer\n");
184     } else {
185         fprintf(stderr, "Unrecognized Volume Header/Trailer\n");
186     }
187 }
188
189 int
190 openOutFile(struct volumeHeader *headerPtr)
191 {
192     afs_int32 len;
193     int ch;
194     int rc;
195     int oflag;
196     int skip, first;
197     afs_hyper_t size;
198
199     /* If we were asked to skip this volume, then skip it */
200     if (nskip) {
201         nskip--;
202         return 0;
203     }
204     /* Skip if we are not to restore any */
205     if (!nrestore)
206         return 0;
207
208     /* Get the volume name and strip off the BK or RO extension */
209     if (outfile) {
210         strcpy(filename, outfile);
211     } else {
212         strcpy(filename, headerPtr->volumeName);
213         len = strlen(filename);
214         if ((len > 7) && (strcmp(".backup", filename + len - 7) == 0)) {
215             filename[len - 7] = 0;
216         } else if ((len > 9)
217                    && (strcmp(".readonly", filename + len - 9) == 0)) {
218             filename[len - 9] = 0;
219         }
220     }
221
222     if (ask) {
223         first = 1;
224         skip = 0;
225         printf("Press return to retreive volume %s (%u) to file %s; " "s"
226                " to skip\n", headerPtr->volumeName, headerPtr->volumeID,
227                filename);
228         do {
229             ch = getchar();
230             if ((first == 1) && (ch == 's'))
231                 skip = 1;
232             if ((first == 1) && (ch == 'q'))
233                 exit(0);
234             first = 0;
235         } while (ch != '\n');
236         if (skip) {
237             printf("Will not restore volume %s\n", headerPtr->volumeName);
238             return 0;
239         }
240     } else {
241         printf("Retrieve volume %s (%u) to file %s\n", headerPtr->volumeName,
242                headerPtr->volumeID, filename);
243     }
244
245     /* Should I append the date onto the end of the name? */
246
247     /* Open the file to write to */
248     if (headerPtr->contd == TC_VOLCONTD) {
249         /* Continuation of dump */
250         oflag = USD_OPEN_RDWR;
251     } else {
252         /* An all new dump */
253         oflag = USD_OPEN_RDWR | USD_OPEN_CREATE;
254     }
255     rc = usd_Open(filename, oflag, 0664, &ofd);
256     if (rc != 0) {
257         fprintf(stderr, "Unable to open file %s. Skipping. Code = %d\n",
258                 filename, rc);
259         nrestore--;
260         return 0;
261     }
262     if (headerPtr->contd != TC_VOLCONTD) {
263         hzero(size);
264         rc = USD_IOCTL(ofd, USD_IOCTL_SETSIZE, &size);
265         if (rc != 0) {
266             fprintf(stderr, "Unable to open file %s. Skipping. Code = %d\n",
267                     filename, rc);
268             USD_CLOSE(ofd);
269             nrestore--;
270             return 0;
271         }
272     }
273     ofdIsOpen = 1;
274     return 0;
275 }
276
277 static void
278 writeData(char *data, afs_int32 size)
279 {
280     int rc;
281     u_int nwritten;
282
283     if (!ofdIsOpen)
284         return;
285     rc = USD_WRITE(ofd, data, (u_int) size, &nwritten);
286     if (rc != 0) {
287         fprintf(stderr, "Unable to write volume data to file. Code = %d\n",
288                 rc);
289     }
290     return;
291 }
292
293 int
294 writeLastBlocks(char *lastblock, char *lastblock2)
295 {
296     char trailer[12];
297     struct blockMark *bmark, *bmark2;
298     char *data;
299     char *data2 = NULL;
300     int count, count2;
301     int tlen, skip, pos;
302
303     if (!ofdIsOpen)
304         return 0;
305
306     bmark = (struct blockMark *)lastblock;
307     data = &lastblock[sizeof(struct blockMark)];
308     count = ntohl(bmark->count);
309     if (lastblock2) {
310         bmark2 = (struct blockMark *)lastblock2;
311         data2 = &lastblock2[sizeof(struct blockMark)];
312         count2 = ntohl(bmark2->count);
313     } else {
314         count2 = 0;
315     }
316
317     /*
318      * Strip off all but the last twelve bytes of the volume trailer
319      */
320     skip = sizeof(struct volumeHeader) - 12;
321     if (count >= skip) {
322         count = count - skip;
323     } else if (count + count2 >= skip) {
324         count2 = count2 - (skip - count);
325         count = 0;
326     } else {
327         fprintf(stderr, "Failed to strip off volume trailer (1).\n");
328         return 0;
329     }
330
331     /* volume trailer is somewhere in the last 12 bytes of the tape file.
332      * The volume trailer may span tape blocks. */
333     if (count >= 12) {
334         tlen = 0;
335         memcpy(trailer, data + (count - 12), 12);
336     } else {
337         tlen = 12 - count;
338         memcpy(trailer, data2 + (count2 - tlen), tlen);
339         if (count != 0) {
340             memcpy(trailer + tlen, data, count);
341         }
342     }
343
344     for (pos = 0; pos <= 2; pos++) {
345         if (strncmp(&trailer[pos], "H++NAME#", 8) == 0) {
346             break;
347         }
348         if (tlen > 0) {
349             tlen--;
350         }
351     }
352
353     if (pos == 3) {
354         fprintf(stderr, "Failed to strip off volume trailer (2).\n");
355     } else {
356         if (count2 - tlen > 0) {
357             writeData(data2, count2 - tlen);
358         }
359         if ((tlen == 0) && (count > 12 - pos)) {
360             writeData(data, count - (12 - pos));
361         }
362     }
363     return 0;
364 }
365
366 int
367 closeOutFile(void)
368 {
369     if (!ofdIsOpen)
370         return 0;
371
372     USD_CLOSE(ofd);
373     ofdIsOpen = 0;
374
375     /* Decrement the number of volumes to restore */
376     nrestore--;
377     return 0;
378 }
379
380 static int
381 WorkerBee(struct cmd_syndesc *as, void *arock)
382 {
383     char *tapedev;
384     struct tapeLabel *label;
385     struct fileMark *fmark;
386     afs_int32 fmtype;
387     struct blockMark *bmark;
388     afs_int32 isheader, isdatablock;
389     char *data;
390     char *tblock;
391     afs_int32 code;
392     struct volumeHeader *volheaderPtr = NULL;
393     int eod = 1;
394     int rc;
395     char *nextblock;            /* We cycle through three tape blocks so we  */
396     char *lastblock;            /* can trim off the volume trailer from the  */
397     char *lastblock2;           /* end of each volume without having to back */
398     char *tapeblock1;           /* up the output stream.                     */
399     char *tapeblock2;
400     char *tapeblock3;
401
402     tapedev = as->parms[0].items->data; /* -tape     */
403     nrestore =
404         (as->parms[1].items ? atol(as->parms[1].items->data) : 0x7fffffff);
405     nskip = (as->parms[2].items ? atol(as->parms[2].items->data) : 0);
406     if (as->parms[4].items)
407         nskip = 0x7fffffff;     /* -scan     */
408     outfile = (as->parms[3].items ? as->parms[3].items->data : 0);
409     ask = (as->parms[5].items ? 0 : 1); /* -noask    */
410     printlabels = (as->parms[6].items ? 1 : 0); /* -label    */
411     printheaders = (as->parms[7].items ? 1 : 0);        /* -vheaders */
412     verbose = (as->parms[8].items ? 1 : 0);     /* -verbose  */
413
414     /* Open the tape device */
415     rc = usd_Open(tapedev, USD_OPEN_RDONLY, 0, &fd);
416     if (rc != 0) {
417         printf("Failed to open tape device %s. Code = %d\n", tapedev, rc);
418         exit(1);
419     }
420
421     /*
422      * Initialize the tape block buffers
423      */
424     tapeblock1 = (char *)malloc(3 * 16384);
425     if (tapeblock1 == NULL) {
426         printf("Failed to allocate I/O buffers.\n");
427         exit(1);
428     }
429     tapeblock2 = tapeblock1 + 16384;
430     tapeblock3 = tapeblock2 + 16384;
431
432     nextblock = tapeblock1;
433     lastblock = NULL;
434     lastblock2 = NULL;
435
436     /* Read each tape block deciding what to do with it */
437     do {                        /* while ((nskip!=0) && (nrestore!=0)) */
438         code = readblock(nextblock);
439         if (code) {
440             if (!eod)
441                 fprintf(stderr, "Tape device read error: %d\n", code);
442             break;
443         }
444         isdatablock = 0;
445
446         /* A data block can be either a volume header, volume trailer,
447          * or actual data from a dump.
448          */
449         bmark = (struct blockMark *)nextblock;
450         label = (struct tapeLabel *)nextblock;
451         fmark = (struct fileMark *)nextblock;
452         if (ntohl(bmark->magic) == BLOCK_MAGIC) {
453             if (verbose)
454                 printf("Data block\n");
455             isdatablock = 1;
456             isheader = 0;
457             data = &nextblock[sizeof(struct blockMark)];
458             if (strncmp(data, "H++NAME#", 8) == 0) {
459                 volheaderPtr = (struct volumeHeader *)data;
460                 printHeader(volheaderPtr, &isheader);
461             }
462             if (isheader) {
463                 code = openOutFile(volheaderPtr);
464                 nextblock = tapeblock1;
465                 lastblock = NULL;
466                 lastblock2 = NULL;
467             } else {
468                 if (lastblock2 != NULL) {
469                     data = &lastblock2[sizeof(struct blockMark)];
470                     bmark = (struct blockMark *)lastblock2;
471                     writeData(data, ntohl(bmark->count));
472                     tblock = lastblock2;
473                 } else if (lastblock != NULL) {
474                     tblock = tapeblock2;
475                 } else {
476                     tblock = tapeblock3;
477                 }
478                 lastblock2 = lastblock;
479                 lastblock = nextblock;
480                 nextblock = tblock;
481             }
482         }
483
484         /* Filemarks come in 3 forms: BEGIN, END, and EOD.
485          * There is no information stored in filemarks.
486          */
487         else if (ntohl(fmark->magic) == FILE_MAGIC) {
488             fmtype = ntohl(fmark->nBytes);
489             if (fmtype == FILE_BEGIN) {
490                 if (verbose)
491                     fprintf(stderr, "File mark volume begin\n");
492             } else if (fmtype == FILE_END) {
493                 if (verbose)
494                     fprintf(stderr, "File mark volume end\n");
495             } else if (fmtype == FILE_EOD) {
496                 if (verbose)
497                     fprintf(stderr, "File mark end-of-dump\n");
498                 eod = 1;
499             }
500         }
501
502         /* A dump label */
503         else if (ntohl(label->magic) == TAPE_MAGIC) {
504             if (verbose)
505                 fprintf(stderr, "Dump label\n");
506             printLabel(label);
507             eod = 0;
508         }
509
510         else {
511             if (verbose) {
512                 fprintf(stderr, "Unrecognized tape block - end\n");
513             }
514         }
515
516         /* Anything other than a data block closes the out file.
517          * At this point nextblock contains the end of tape file mark,
518          * lastblock contains the last data block for the current volume,
519          * and lastblock2 contains the second to last block for the current
520          * volume. If the volume fits in a single block, lastblock2 will
521          * be NULL. Call writeLastBlocks to strip off the dump trailer before
522          * writing the last of the volume data to the dump file. The dump
523          * trailer may span block boundaries.
524          */
525         if (!isdatablock && lastblock) {
526             writeLastBlocks(lastblock, lastblock2);
527             closeOutFile();
528             nextblock = tapeblock1;
529             lastblock = NULL;
530             lastblock2 = NULL;
531         }
532     } while ((nskip != 0) || (nrestore != 0));
533
534     free(tapeblock1);
535
536     return 0;
537 }
538
539 int
540 main(int argc, char **argv)
541 {
542     struct cmd_syndesc *ts;
543
544     setlinebuf(stdout);
545
546     ts = cmd_CreateSyntax(NULL, WorkerBee, NULL,
547                           "Restore volumes from backup tape");
548     cmd_AddParm(ts, "-tape", CMD_SINGLE, CMD_REQUIRED, "tape device");
549     cmd_AddParm(ts, "-restore", CMD_SINGLE, CMD_OPTIONAL,
550                 "# volumes to restore");
551     cmd_AddParm(ts, "-skip", CMD_SINGLE, CMD_OPTIONAL, "# volumes to skip");
552     cmd_AddParm(ts, "-file", CMD_SINGLE, CMD_OPTIONAL, "filename");
553     cmd_AddParm(ts, "-scan", CMD_FLAG, CMD_OPTIONAL, "Scan the tape");
554     cmd_AddParm(ts, "-noask", CMD_FLAG, CMD_OPTIONAL,
555                 "Prompt for each volume");
556     cmd_AddParm(ts, "-label", CMD_FLAG, CMD_OPTIONAL, "Display dump label");
557     cmd_AddParm(ts, "-vheaders", CMD_FLAG, CMD_OPTIONAL,
558                 "Display volume headers");
559     cmd_AddParm(ts, "-verbose", CMD_FLAG, CMD_OPTIONAL, "verbose");
560
561     return cmd_Dispatch(argc, argv);
562 }