joe-beuhler-patches-20031122
[openafs.git] / src / bucoord / commands.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 RCSID
14     ("$Header$");
15
16 #include <afs/stds.h>
17 #include <sys/types.h>
18 #include <afs/cmd.h>
19 #ifdef AFS_NT40_ENV
20 #include <winsock2.h>
21 #else
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <netdb.h>
25 #include <strings.h>
26 #endif
27 #include <errno.h>
28 #include <afs/com_err.h>
29 #include <afs/budb.h>
30 #include <afs/butc.h>
31 #include <afs/bubasics.h>       /* PA */
32 #include <afs/volser.h>
33 #include <afs/voldefs.h>        /* PA */
34 #include <afs/vldbint.h>        /* PA */
35 #include <afs/ktime.h>          /* PA */
36 #include <time.h>
37
38 #include <string.h>
39 #include <lock.h>
40 #include <afs/butc.h>
41 #include <afs/tcdata.h>
42 #include <afs/butx.h>
43 #include "bc.h"
44 #include "error_macros.h"
45
46
47 extern struct bc_config *bc_globalConfig;
48 extern char *ktime_GetDateUsage();
49 extern struct bc_dumpSchedule *bc_FindDumpSchedule();
50 extern struct bc_volumeSet *bc_FindVolumeSet(struct bc_config *cfg,
51                                              char *name);
52 extern struct bc_dumpTask bc_dumpTasks[BC_MAXSIMDUMPS];
53 extern struct ubik_client *cstruct;
54 extern int bc_Dumper();         /* function to do dumps */
55 extern int bc_Restorer();       /* function to do restores */
56 extern void bc_HandleMisc();
57 extern afs_int32 ScanDumpHdr();
58 extern afs_int32 ScanTapeVolume();
59 extern char *whoami;
60 extern int VL_ListEntry();
61 extern int VL_ListAttributesN2();
62 extern struct ktc_token ttoken;
63 extern char *tailCompPtr();
64 extern statusP createStatusNode();
65
66 char *loadFile;
67 extern afs_int32 lastTaskCode;
68
69 #ifdef AFS_DEC_ENV
70 #define HOSTADDR(sockaddr) (sockaddr)->sin_addr.S_un.S_addr
71 #else
72 #define HOSTADDR(sockaddr) (sockaddr)->sin_addr.s_addr
73 #endif
74
75 int
76 bc_EvalVolumeSet(aconfig, avs, avols, uclient)
77      struct bc_config *aconfig;
78      register struct bc_volumeSet *avs;
79      struct bc_volumeDump **avols;
80      struct ubik_client *uclient;
81 {                               /*bc_EvalVolumeSet */
82     int code;
83     static afs_int32 use = 2;
84
85     if (use == 2) {             /* Use EvalVolumeSet2() */
86         code = EvalVolumeSet2(aconfig, avs, avols, uclient);
87         if (code == RXGEN_OPCODE)
88             use = 1;
89     }
90     if (use == 1) {             /* Use EvalVolumeSet1() */
91         code = EvalVolumeSet1(aconfig, avs, avols, uclient);
92     }
93     return code;
94 }                               /*bc_EvalVolumeSet */
95
96 struct partitionsort {
97     afs_int32 part;
98     struct bc_volumeDump *vdlist;
99     struct bc_volumeDump *lastvdlist;
100     struct bc_volumeDump *dupvdlist;
101     struct bc_volumeEntry *vole;
102     struct partitionsort *next;
103 };
104 struct serversort {
105     afs_uint32 ipaddr;
106     struct partitionsort *partitions;
107     struct serversort *next;
108 };
109
110 afs_int32
111 getSPEntries(server, partition, serverlist, ss, ps)
112      afs_uint32 server;
113      afs_int32 partition;
114      struct serversort **serverlist, **ss;
115      struct partitionsort **ps;
116 {
117     if (!(*ss) || ((*ss)->ipaddr != server)) {
118         *ps = 0;
119         for ((*ss) = *serverlist; (*ss); *ss = (*ss)->next) {
120             if ((*ss)->ipaddr == server)
121                 break;
122         }
123     }
124     /* No server entry added. Add one */
125     if (!(*ss)) {
126         *ss = (struct serversort *)malloc(sizeof(struct serversort));
127         if (!(*ss)) {
128             com_err(whoami, BC_NOMEM, "");
129             *ss = 0;
130             return (BC_NOMEM);
131         }
132         memset(*ss, 0, sizeof(struct serversort));
133         (*ss)->ipaddr = server;
134         (*ss)->next = *serverlist;
135         *serverlist = *ss;
136     }
137
138
139     if (!(*ps) || ((*ps)->part != partition)) {
140         for (*ps = (*ss)->partitions; *ps; *ps = (*ps)->next) {
141             if ((*ps)->part == partition)
142                 break;
143         }
144     }
145     /* No partition entry added. Add one */
146     if (!(*ps)) {
147         *ps = (struct partitionsort *)malloc(sizeof(struct partitionsort));
148         if (!(*ps)) {
149             com_err(whoami, BC_NOMEM, "");
150             free(*ss);
151             *ps = 0;
152             *ss = 0;
153             return (BC_NOMEM);
154         }
155         memset(*ps, 0, sizeof(struct partitionsort));
156         (*ps)->part = partition;
157         (*ps)->next = (*ss)->partitions;
158         (*ss)->partitions = *ps;
159     }
160     return 0;
161 }
162
163 afs_int32
164 randSPEntries(serverlist, avols)
165      struct serversort *serverlist;
166      struct bc_volumeDump **avols;
167 {
168     struct serversort *ss, **pss;
169     struct partitionsort *ps, **pps;
170     afs_int32 r;
171     afs_int32 scount, pcount;
172
173     *avols = 0;
174
175     /* Seed random number generator */
176     r = time(0) + getpid();
177     srand(r);
178
179     /* Count number of servers, remove one at a time */
180     for (scount = 0, ss = serverlist; ss; ss = ss->next, scount++);
181     for (; scount; scount--) {
182         /* Pick a random server in list and remove it */
183         r = (rand() >> 4) % scount;
184         for (pss = &serverlist, ss = serverlist; r;
185              pss = &ss->next, ss = ss->next, r--);
186         *pss = ss->next;
187
188         /* Count number of partitions, remove one at a time */
189         for (pcount = 0, ps = ss->partitions; ps; ps = ps->next, pcount++);
190         for (; pcount; pcount--) {
191             /* Pick a random parition in list and remove it */
192             r = (rand() >> 4) % pcount;
193             for (pps = &ss->partitions, ps = ss->partitions; r;
194                  pps = &ps->next, ps = ps->next, r--);
195             *pps = ps->next;
196
197             ps->lastvdlist->next = *avols;
198             *avols = ps->vdlist;
199             free(ps);
200         }
201         free(ss);
202     }
203 }
204
205 int
206 EvalVolumeSet2(aconfig, avs, avols, uclient)
207      struct bc_config *aconfig;
208      struct bc_volumeSet *avs;
209      struct bc_volumeDump **avols;
210      struct ubik_client *uclient;
211 {                               /*EvalVolumeSet2 */
212     struct bc_volumeEntry *tve;
213     struct bc_volumeDump *tavols;
214     struct VldbListByAttributes attributes;
215     afs_int32 si, nsi;          /* startIndex and nextStartIndex */
216     afs_int32 nentries, e, ei, et, add, l;
217     nbulkentries bulkentries;
218     struct nvldbentry *entries = 0;
219     struct bc_volumeDump *tvd;
220     afs_int32 code = 0, tcode;
221     afs_int32 count = 0;
222     struct serversort *servers = 0, *lastserver = 0, *ss = 0;
223     struct partitionsort *ps = 0;
224
225     *avols = (struct bc_volumeDump *)0;
226     bulkentries.nbulkentries_len = 0;
227     bulkentries.nbulkentries_val = 0;
228
229     /* For each of the volume set entries - collect the volumes that match it */
230     for (tve = avs->ventries; tve; tve = tve->next) {
231         /* Put together a call to the vlserver for this vlentry. The 
232          * performance gain is from letting the vlserver expand the
233          * volumeset and not this routine.
234          */
235         attributes.Mask = 0;
236         if (tve->server.sin_addr.s_addr) {      /* The server */
237             attributes.Mask |= VLLIST_SERVER;
238             attributes.server = tve->server.sin_addr.s_addr;
239         }
240         if (tve->partition != -1) {     /* The partition */
241             attributes.Mask |= VLLIST_PARTITION;
242             attributes.partition = tve->partition;
243         }
244
245         /* Now make the call to the vlserver */
246         for (si = 0; si != -1; si = nsi) {
247             nentries = 0;
248             bulkentries.nbulkentries_len = 0;
249             bulkentries.nbulkentries_val = 0;
250             nsi = -1;
251             tcode =
252                 ubik_Call(VL_ListAttributesN2, uclient, 0, &attributes,
253                           tve->name, si, &nentries, &bulkentries, &nsi);
254             if (tcode)
255                 ERROR(tcode);
256
257             /* The 3.4 vlserver has a VL_ListAttributesN2() RPC call, but
258              * it is not complete. The only way to tell if it is not complete
259              * is if et == 0 (which we check for below). Also, if the call didn't
260              * match any entries, then we don't know what version the vlserver
261              * is. In both cases, we return RXGEN_OPCODE and the calling routine
262              * will switch to the EvalVolumeSet1() call.
263              */
264             if (nentries == 0)
265                 ERROR(RXGEN_OPCODE);    /* Use EvalVolumeSet1 */
266
267             /* Step through each entry and add it to the list of volumes */
268             entries = bulkentries.nbulkentries_val;
269             for (e = 0; e < nentries; e++) {
270                 ei = entries[e].matchindex & 0xffff;
271                 et = (entries[e].matchindex >> 16) & 0xffff;
272                 switch (et) {
273                 case ITSRWVOL:{
274                         et = RWVOL;
275                         break;
276                     }
277                 case ITSBACKVOL:{
278                         et = BACKVOL;
279                         break;
280                     }
281                 case ITSROVOL:{
282                         et = ROVOL;
283                         break;
284                     }
285                 default:
286                     ERROR(RXGEN_OPCODE);        /* Use EvalVolumeSet1 */
287                 }
288
289                 /* Find server and partiton structure to hang the entry off of */
290                 tcode =
291                     getSPEntries(entries[e].serverNumber[ei],
292                                  entries[e].serverPartition[ei], &servers,
293                                  &ss, &ps);
294                 if (tcode) {
295                     com_err(whoami, tcode, "");
296                     ERROR(tcode);
297                 }
298
299                 /* Detect if this entry should be added (not a duplicate).
300                  * Use ps->dupvdlist and ps->vole to only search volumes from
301                  * previous volume set entries.
302                  */
303                 add = 1;
304                 if (tve != avs->ventries) {
305                     l = strlen(entries[e].name);
306                     if (ps->vole != tve) {
307                         ps->vole = tve;
308                         ps->dupvdlist = ps->vdlist;
309                     }
310                     for (tavols = ps->dupvdlist; add && tavols;
311                          tavols = tavols->next) {
312                         if (strncmp(tavols->name, entries[e].name, l) == 0) {
313                             if ((strcmp(&entries[e].name[l], ".backup") == 0)
314                                 || (strcmp(&entries[e].name[l], ".readonly")
315                                     == 0)
316                                 || (strcmp(&entries[e].name[l], "") == 0))
317                                 add = 0;
318                         }
319                     }
320                 }
321
322                 if (add) {
323                     /* Allocate a volume dump structure and its name */
324                     tvd = (struct bc_volumeDump *)
325                         malloc(sizeof(struct bc_volumeDump));
326                     if (!tvd) {
327                         com_err(whoami, BC_NOMEM, "");
328                         ERROR(BC_NOMEM);
329                     }
330                     memset(tvd, 0, sizeof(*tvd));
331
332                     tvd->name = (char *)malloc(strlen(entries[e].name) + 10);
333                     if (!(tvd->name)) {
334                         com_err(whoami, BC_NOMEM, "");
335                         free(tvd);
336                         ERROR(BC_NOMEM);
337                     }
338
339                     /* Fill it in and thread onto avols list */
340                     strcpy(tvd->name, entries[e].name);
341                     if (et == BACKVOL)
342                         strcat(tvd->name, ".backup");
343                     else if (et == ROVOL)
344                         strcat(tvd->name, ".readonly");
345                     tvd->vid = entries[e].volumeId[et];
346                     tvd->entry = tve;
347                     tvd->volType = et;
348                     tvd->partition = entries[e].serverPartition[ei];
349                     tvd->server.sin_addr.s_addr = entries[e].serverNumber[ei];
350                     tvd->server.sin_port = 0;   /* default FS port */
351                     tvd->server.sin_family = AF_INET;
352 #ifdef STRUCT_SOCKADDR_HAS_SA_LEN
353                     tvd->server.sin_len = sizeof(struct sockaddr_in);
354 #endif
355
356                     /* String tvd off of partition struct */
357                     tvd->next = ps->vdlist;
358                     ps->vdlist = tvd;
359                     if (!tvd->next)
360                         ps->lastvdlist = tvd;
361
362                     count++;
363                 }
364             }
365
366             /* Free memory allocated during VL call */
367             if (bulkentries.nbulkentries_val) {
368                 free((char *)bulkentries.nbulkentries_val);
369                 bulkentries.nbulkentries_val = 0;
370                 entries = 0;
371             }
372         }
373     }
374
375     /* Randomly link the volumedump entries together */
376     randSPEntries(servers, avols);
377     fprintf(stderr, "Total number of volumes : %u\n", count);
378
379   error_exit:
380     if (bulkentries.nbulkentries_val) {
381         free((char *)bulkentries.nbulkentries_val);
382     }
383     return (code);
384 }                               /*EvalVolumeSet2 */
385
386 /*-----------------------------------------------------------------------------
387  * EvalVolumeSetOld
388  *
389  * Description:
390  *      Takes the entries in a volumeset and expands them into a list of 
391  *      volumes. Every VLDB volume entry is looked at and compared to the
392  *      volumeset entries.
393  *
394  *      When matching a VLDB volume entry to a volumeset entry, 
395  *       1. If the RW volume entry matches, that RW volume is used.
396  *       2. Otherwise, if the BK volume entry matches, the BK volume is used.
397  *       3. Finally, if the RO volume entry matches, the RO volume is used.
398  *      For instance: A volumeset entry of ".* .* user.t.*" will match volume 
399  *                    "user.troy" and "user.troy.backup". The rules will use 
400  *                    the RW volume "user.troy".
401  *
402  *      When a VLDB volume entry matches a volumeset entry (be it RW, BK or RO),
403  *      that volume is used and matches against any remaining volumeset entries 
404  *      are not even done.
405  *      For instance: A 1st volumeset entry ".* .* .*.backup" will match with
406  *                    "user.troy.backup". Its 2nd volumeset entry ".* .* .*" 
407  *                    would have matched its RW volume "user.troy", but the first 
408  *                    match is used and the second match isn't even done.
409  *
410  * Arguments:
411  *      aconfig : Global configuration info.
412  *      avs     : 
413  *      avols   : Ptr to linked list of entries describing volumes to dump.
414  *      uclient : Ptr to Ubik client structure.
415  *
416  * Returns:
417  *      0 on successful volume set evaluation,
418  *      Lower-level codes otherwise.
419  *
420  * Environment:
421  *      Expand only the single volume set provided (avs); don't go down its chain.
422  *
423  * Side Effects:
424  *      None.
425  *-----------------------------------------------------------------------------
426  */
427 int
428 EvalVolumeSet1(aconfig, avs, avols, uclient)
429      struct bc_config *aconfig;
430      struct bc_volumeSet *avs;
431      struct bc_volumeDump **avols;
432      struct ubik_client *uclient;
433 {                               /*EvalVolumeSet1 */
434     afs_int32 code;             /*Result of various calls */
435     char *errm;
436     struct bc_volumeDump *tvd;  /*Ptr to new dump instance */
437     struct bc_volumeEntry *tve, *ctve;  /*Ptr to new volume entry instance */
438     char patt[256];             /*Composite regex; also, target string */
439     int volType;                /*Type of volume that worked */
440     afs_int32 index;            /*Current VLDB entry index */
441     afs_int32 count;            /*Needed by VL_ListEntry() */
442     afs_int32 next_index;       /*Next index to list */
443     struct vldbentry entry;     /*VLDB entry */
444     int srvpartpair;            /*Loop counter: server/partition pair */
445     afs_int32 total = 0;
446     int found, foundentry;
447     struct serversort *servers = 0, *ss = 0;
448     struct partitionsort *ps = 0;
449
450     *avols = (struct bc_volumeDump *)0;
451     ctve = (struct bc_volumeEntry *)0;  /* no compiled entry */
452
453     /* For each vldb entry.
454      * Variable next_index is set to the index of the next VLDB entry
455      * in the enumeration.
456      */
457     for (index = 0; 1; index = next_index) {    /*w */
458         memset(&entry, 0, sizeof(entry));
459         code = ubik_Call(VL_ListEntry,  /*Routine to invoke */
460                          uclient,       /*Ubik client structure */
461                          0,     /*Ubik flags */
462                          index, /*Current index */
463                          &count,        /*Ptr to working variable */
464                          &next_index,   /*Ptr to next index value to list */
465                          &entry);       /*Ptr to entry to fill */
466         if (code)
467             return code;
468         if (!next_index)
469             break;              /* If the next index is invalid, bail out now. */
470
471         /* For each entry in the volume set */
472         found = 0;              /* No match in volume set yet */
473         for (tve = avs->ventries; tve; tve = tve->next) {       /*ve */
474             /* for each server in the vldb entry */
475             for (srvpartpair = 0; srvpartpair < entry.nServers; srvpartpair++) {        /*s */
476                 /* On the same server */
477                 if (tve->server.sin_addr.s_addr
478                     && !VLDB_IsSameAddrs(tve->server.sin_addr.s_addr,
479                                          entry.serverNumber[srvpartpair],
480                                          &code)) {
481                     if (code)
482                         return (code);
483                     continue;
484                 }
485
486
487                 /* On the same partition */
488                 if ((tve->partition != -1)
489                     && (tve->partition != entry.serverPartition[srvpartpair]))
490                     continue;
491
492                 /* If the volume entry is not compiled, then compile it */
493                 if (ctve != tve) {
494                     sprintf(patt, "^%s$", tve->name);
495                     errm = (char *)re_comp(patt);
496                     if (errm) {
497                         com_err(whoami, 0,
498                                 "Can't compile regular expression '%s': %s",
499                                 patt, errm);
500                         return (-1);
501                     }
502                     ctve = tve;
503                 }
504
505                 /* If the RW name matches the volume set entry, take
506                  * it and exit. First choice is to use the RW volume.
507                  */
508                 if (entry.serverFlags[srvpartpair] & ITSRWVOL) {
509                     if (entry.flags & RW_EXISTS) {
510                         sprintf(patt, "%s", entry.name);
511                         code = re_exec(patt);
512                         if (code == 1) {
513                             found = 1;
514                             foundentry = srvpartpair;
515                             volType = RWVOL;
516                             break;
517                         }
518                     }
519
520                     /* If the BK name matches the volume set entry, take 
521                      * it and exit. Second choice is to use the BK volume.
522                      */
523                     if (entry.flags & BACK_EXISTS) {
524                         sprintf(patt, "%s.backup", entry.name);
525                         code = re_exec(patt);
526                         if (code == 1) {
527                             found = 1;
528                             foundentry = srvpartpair;
529                             volType = BACKVOL;
530                             break;
531                         }
532                     }
533                 }
534
535                 /* If the RO name matches the volume set entry, remember
536                  * it, but continue searching. Further entries may be
537                  * RW or backup entries that will match.
538                  */
539                 else if (!found && (entry.serverFlags[srvpartpair] & ITSROVOL)
540                          && (entry.flags & RO_EXISTS)) {
541                     sprintf(patt, "%s.readonly", entry.name);
542                     code = re_exec(patt);
543                     if (code == 1) {
544                         found = 1;
545                         foundentry = srvpartpair;
546                         volType = ROVOL;
547                     }
548                 }
549
550                 if (code < 0)
551                     com_err(whoami, 0, "Internal error in regex package");
552             }                   /*s */
553
554             /* If found a match, then create a new volume dump entry */
555             if (found) {        /*f */
556                 /* Find server and partition structure to hang the entry off of */
557                 code =
558                     getSPEntries(entry.serverNumber[foundentry],
559                                  entry.serverPartition[foundentry], &servers,
560                                  &ss, &ps);
561                 if (code) {
562                     com_err(whoami, code, "");
563                     return (code);
564                 }
565
566                 total++;
567                 tvd = (struct bc_volumeDump *)
568                     malloc(sizeof(struct bc_volumeDump));
569                 if (!tvd) {
570                     com_err(whoami, BC_NOMEM, "");
571                     return (BC_NOMEM);
572                 }
573                 memset(tvd, 0, sizeof(*tvd));
574
575                 tvd->name = (char *)malloc(strlen(entry.name) + 10);
576                 if (!(tvd->name)) {
577                     com_err(whoami, BC_NOMEM, "");
578                     free(tvd);
579                     return (BC_NOMEM);
580                 }
581
582                 strcpy(tvd->name, entry.name);
583                 if (volType == BACKVOL)
584                     strcat(tvd->name, ".backup");
585                 else if (volType == ROVOL)
586                     strcat(tvd->name, ".readonly");
587                 tvd->vid = entry.volumeId[volType];
588                 tvd->entry = tve;
589                 tvd->volType = volType;
590                 tvd->partition = entry.serverPartition[foundentry];
591                 tvd->server.sin_addr.s_addr = entry.serverNumber[foundentry];
592                 tvd->server.sin_port = 0;       /* default FS port */
593                 tvd->server.sin_family = AF_INET;
594
595                 /* String tvd off of partition struct */
596                 tvd->next = ps->vdlist;
597                 ps->vdlist = tvd;
598                 if (!tvd->next)
599                     ps->lastvdlist = tvd;
600
601                 break;
602             }                   /*f */
603         }                       /*ve */
604     }                           /*w */
605
606     /* Randomly link the volumedump entries together */
607     randSPEntries(servers, avols);
608
609     fprintf(stderr, "Total number of volumes : %u\n", total);
610     return (0);
611 }                               /*EvalVolumeSet1 */
612
613 /* compactDateString
614  *      print out a date in compact format, 16 chars, format is
615  *      mm/dd/yyyy hh:mm
616  * entry:
617  *      date_long - ptr to a long containing the time
618  * exit:
619  *      ptr to a string containing a representation of the date
620  */
621 char *
622 compactDateString(date_long, string, size)
623      afs_int32 *date_long, size;
624      char *string;
625 {
626     struct tm *ltime;
627
628     if (!string)
629         return 0;
630
631     if (*date_long == NEVERDATE) {
632         sprintf(string, "NEVER");
633     } else {
634         ltime = localtime(date_long);
635         /* prints date in U.S. format of mm/dd/yyyy */
636         strftime(string, size, "%m/%d/%Y %H:%M", ltime);
637     }
638     return (string);
639 }
640
641 afs_int32
642 bc_SafeATOI(anum)
643      char *anum;
644 {
645     afs_int32 total = 0;
646
647     for (; *anum; anum++) {
648         if ((*anum < '0') || (*anum > '9'))
649             return -1;
650         total = (10 * total) + (afs_int32) (*anum - '0');
651     }
652     return total;
653 }
654
655 /* bc_FloatATOI:
656  *    Take a string and parse it for a number (could be float) followed
657  *    by a character representing the units (K,M,G,T). Default is 'K'.
658  *    Return the size in KBytes.
659  */
660 afs_int32
661 bc_FloatATOI(anum)
662      char *anum;
663 {
664     float total = 0;
665     afs_int32 rtotal;
666     afs_int32 fraction = 0;     /* > 0 if past the decimal */
667
668     for (; *anum; anum++) {
669         if ((*anum == 't') || (*anum == 'T')) {
670             total *= 1024 * 1024 * 1024;
671             break;
672         }
673         if ((*anum == 'g') || (*anum == 'G')) {
674             total *= 1024 * 1024;
675             break;
676         }
677         if ((*anum == 'm') || (*anum == 'M')) {
678             total *= 1024;
679             break;
680         }
681         if ((*anum == 'k') || (*anum == 'K')) {
682             break;
683         }
684         if (*anum == '.') {
685             fraction = 10;
686             continue;
687         }
688         if ((*anum < '0') || (*anum > '9'))
689             return -1;
690
691         if (!fraction) {
692             total = (10. * total) + (float)(*anum - '0');
693         } else {
694             total += ((float)(*anum - '0')) / (float)fraction;
695             fraction *= 10;
696         }
697     }
698
699     total += 0.5;               /* Round up */
700     if (total > 0x7fffffff)     /* Don't go over 2G */
701         total = 0x7fffffff;
702     rtotal = (afs_int32) total;
703     return (rtotal);
704 }
705
706 /* make a copy of a string so that it can be freed later */
707 char *
708 bc_CopyString(astring)
709      char *astring;
710 {
711     afs_int32 tlen;
712     char *tp;
713
714     if (!astring)
715         return (NULL);          /* propagate null strings easily */
716     tlen = strlen(astring);
717     tp = (char *)malloc(tlen + 1);      /* don't forget the terminating null */
718     if (!tp) {
719         com_err(whoami, BC_NOMEM, "");
720         return (tp);
721     }
722     strcpy(tp, astring);
723     return tp;
724 }
725
726 /* concatParams
727  * 
728  *    Concatenates the parameters of an option and returns the string.
729  *
730  */
731
732 char *
733 concatParams(itemPtr)
734      struct cmd_item *itemPtr;
735 {
736     struct cmd_item *tempPtr;
737     afs_int32 length = 0;
738     char *string;
739
740     /* compute the length of string required */
741     for (tempPtr = itemPtr; tempPtr; tempPtr = tempPtr->next) {
742         length += strlen(tempPtr->data);
743         length++;               /* space or null terminator */
744     }
745
746     if (length == 0) {          /* no string (0 length) */
747         com_err(whoami, 0, "Can't have zero length date and time string");
748         return (NULL);
749     }
750
751     string = (char *)malloc(length);    /* allocate the string */
752     if (!string) {
753         com_err(whoami, BC_NOMEM, "");
754         return (NULL);
755     }
756     string[0] = 0;
757
758     tempPtr = itemPtr;          /* now assemble the string */
759     while (tempPtr) {
760         strcat(string, tempPtr->data);
761         tempPtr = tempPtr->next;
762         if (tempPtr)
763             strcat(string, " ");
764     }
765
766     return (string);            /* return the string */
767 }
768
769 /* printIfStatus
770  *      print out an interface status node as received from butc
771  */
772
773 printIfStatus(statusPtr)
774      struct tciStatusS *statusPtr;
775 {
776     printf("Task %d: %s: ", statusPtr->taskId, statusPtr->taskName);
777     if (statusPtr->nKBytes)
778         printf("%ld Kbytes transferred", statusPtr->nKBytes);
779     if (strlen(statusPtr->volumeName) != 0) {
780         if (statusPtr->nKBytes)
781             printf(", ");
782         printf("volume %s", statusPtr->volumeName);
783     }
784
785     /* orphan */
786
787     if (statusPtr->flags & ABORT_REQUEST)
788         printf(" [abort request rcvd]");
789
790     if (statusPtr->flags & ABORT_DONE)
791         printf(" [abort complete]");
792
793     if (statusPtr->flags & OPR_WAIT)
794         printf(" [operator wait]");
795
796     if (statusPtr->flags & CALL_WAIT)
797         printf(" [callout in progress]");
798
799     if (statusPtr->flags & DRIVE_WAIT)
800         printf(" [drive wait]");
801
802     if (statusPtr->flags & TASK_DONE)
803         printf(" [done]");
804     printf("\n");
805 }
806
807 afs_int32
808 getPortOffset(port)
809      char *port;
810 {
811     afs_int32 portOffset;
812
813     portOffset = bc_SafeATOI(port);
814
815     if (portOffset < 0) {
816         com_err(whoami, 0, "Can't decode port offset '%s'", port);
817         return (-1);
818     } else if (portOffset > BC_MAXPORTOFFSET) {
819         com_err(whoami, 0, "%u exceeds max port offset %u", portOffset,
820                 BC_MAXPORTOFFSET);
821         return (-1);
822     }
823     return (portOffset);
824 }
825
826 /* bc_GetTapeStatusCmd
827  *      display status of all tasks on a particular tape coordinator
828  */
829
830 bc_GetTapeStatusCmd(as, arock)
831      struct cmd_syndesc *as;
832      char *arock;
833 {
834     afs_int32 code;
835     struct rx_connection *tconn;
836     afs_int32 portOffset = 0;
837
838     int ntasks;
839     afs_uint32 flags;
840     afs_uint32 taskId;
841     struct tciStatusS status;
842
843     code = bc_UpdateHosts();
844     if (code) {
845         com_err(whoami, code, "; Can't retrieve tape hosts");
846         return (code);
847     }
848
849     if (as->parms[0].items) {
850         portOffset = getPortOffset(as->parms[0].items->data);
851         if (portOffset < 0)
852             return (BC_BADARG);
853     }
854
855     code = ConnectButc(bc_globalConfig, portOffset, &tconn);
856     if (code)
857         return (code);
858
859     flags = TSK_STAT_FIRST;
860     taskId = 0;
861     ntasks = 0;
862
863     while ((flags & TSK_STAT_END) == 0) {
864         code = TC_ScanStatus(tconn, &taskId, &status, &flags);
865         if (code) {
866             if (code == TC_NOTASKS)
867                 break;
868             com_err(whoami, code, "; Can't get status from butc");
869             return (-1);
870         }
871         if ((flags & TSK_STAT_NOTFOUND))
872             break;              /* Can't find the task id */
873         flags &= ~TSK_STAT_FIRST;       /* turn off flag */
874
875         printIfStatus(&status);
876         ntasks++;
877     }
878
879     if (ntasks == 0)
880         printf("Tape coordinator is idle\n");
881
882     if (flags & TSK_STAT_ADSM)
883         printf("TSM Tape coordinator\n");
884     else if (flags & TSK_STAT_XBSA)
885         printf("XBSA Tape coordinator\n");
886
887     return (0);
888 }
889
890 extern struct Lock dispatchLock;
891
892 /* bc_WaitForNoJobs
893  *      wait for all jobs to terminate
894  */
895 bc_WaitForNoJobs()
896 {
897     afs_int32 wcode = 0;
898     int i;
899     int waitmsg = 1;
900     int usefulJobRunning = 1;
901
902     extern dlqlinkT statusHead;
903
904     com_err(whoami, 0, "waiting for job termination");
905
906     while (usefulJobRunning) {
907         usefulJobRunning = (dlqEmpty(&statusHead) ? 0 : 1);
908         if (dispatchLock.excl_locked)
909             usefulJobRunning = 1;
910         for (i = 0; (!usefulJobRunning && (i < BC_MAXSIMDUMPS)); i++) {
911             if (bc_dumpTasks[i].flags & BC_DI_INUSE)
912                 usefulJobRunning = 1;
913         }
914
915         /* Wait 5 seconds and check again */
916         if (usefulJobRunning)
917             IOMGR_Sleep(5);
918     }
919     return (lastTaskCode);
920 }
921
922 /* bc_JobsCmd
923  *      print status on running jobs
924  * parameters
925  *      ignored - a null "as" prints only jobs.
926  */
927
928 bc_JobsCmd(as, arock)
929      struct cmd_syndesc *as;
930      char *arock;
931 {
932     afs_int32 prevTime;
933     dlqlinkP ptr;
934     statusP statusPtr;
935     char ds[50];
936
937     statusP youngest;
938     dlqlinkT atJobsHead;
939     extern dlqlinkT statusHead;
940
941     dlqInit(&atJobsHead);
942
943     lock_Status();
944     ptr = (&statusHead)->dlq_next;
945     while (ptr != &statusHead) {
946         statusPtr = (statusP) ptr;
947         ptr = ptr->dlq_next;
948
949         if (statusPtr->scheduledDump) {
950             dlqUnlink((dlqlinkP) statusPtr);
951             dlqLinkb(&atJobsHead, (dlqlinkP) statusPtr);
952         } else {
953             printf("Job %d:", statusPtr->jobNumber);
954
955             printf(" %s", statusPtr->taskName);
956
957             if (statusPtr->dbDumpId)
958                 printf(": DumpID %u", statusPtr->dbDumpId);
959             if (statusPtr->nKBytes)
960                 printf(", %ld Kbytes", statusPtr->nKBytes);
961             if (strlen(statusPtr->volumeName) != 0)
962                 printf(", volume %s", statusPtr->volumeName);
963
964             if (statusPtr->flags & CONTACT_LOST)
965                 printf(" [butc contact lost]");
966
967             if (statusPtr->flags & ABORT_REQUEST)
968                 printf(" [abort request]");
969
970             if (statusPtr->flags & ABORT_SENT)
971                 printf(" [abort sent]");
972
973             if (statusPtr->flags & OPR_WAIT)
974                 printf(" [operator wait]");
975
976             if (statusPtr->flags & CALL_WAIT)
977                 printf(" [callout in progress]");
978
979             if (statusPtr->flags & DRIVE_WAIT)
980                 printf(" [drive wait]");
981             printf("\n");
982         }
983     }
984
985     /* 
986      * Now print the scheduled dumps.
987      */
988     if (!dlqEmpty(&statusHead) && as)
989         printf("\n");           /* blank line between running and scheduled dumps */
990
991     prevTime = 0;
992     while (!dlqEmpty(&atJobsHead)) {
993         ptr = (&atJobsHead)->dlq_next;
994         youngest = (statusP) ptr;
995
996         ptr = ptr->dlq_next;
997         while (ptr != &atJobsHead) {    /* Find the dump that starts the earliest */
998             statusPtr = (statusP) ptr;
999             if (statusPtr->scheduledDump < youngest->scheduledDump)
1000                 youngest = statusPtr;
1001             ptr = ptr->dlq_next;
1002         }
1003
1004         /* Print token expiration time */
1005         if ((ttoken.endTime > prevTime)
1006             && (ttoken.endTime <= youngest->scheduledDump) && as
1007             && (ttoken.endTime != NEVERDATE)) {
1008             if (ttoken.endTime > time(0)) {
1009                 compactDateString(&ttoken.endTime, ds, 50);
1010                 printf("       %16s: TOKEN EXPIRATION\n", ds);
1011             } else {
1012                 printf("       TOKEN HAS EXPIRED\n");
1013             }
1014         }
1015         prevTime = youngest->scheduledDump;
1016
1017         /* Print the info */
1018         compactDateString(&youngest->scheduledDump, ds, 50);
1019         printf("Job %d:", youngest->jobNumber);
1020         printf(" %16s: %s", ds, youngest->cmdLine);
1021         printf("\n");
1022
1023         /* return to original list */
1024         dlqUnlink((dlqlinkP) youngest);
1025         dlqLinkb(&statusHead, (dlqlinkP) youngest);
1026     }
1027
1028     /* Print token expiration time if havn't already */
1029     if ((ttoken.endTime == NEVERDATE) && as)
1030         printf("     : TOKEN NEVER EXPIRES\n");
1031     else if ((ttoken.endTime > prevTime) && as) {
1032         if (ttoken.endTime > time(0)) {
1033             compactDateString(&ttoken.endTime, ds, 50);
1034             printf("       %16s: TOKEN EXPIRATION\n", ds);
1035         } else {
1036             printf("     : TOKEN HAS EXPIRED\n");
1037         }
1038     }
1039
1040     unlock_Status();
1041     return 0;
1042 }
1043
1044 bc_KillCmd(as, arock)
1045      struct cmd_syndesc *as;
1046      char *arock;
1047 {
1048     afs_int32 i;
1049     afs_int32 slot;
1050     struct bc_dumpTask *td;
1051     char *tp;
1052     char tbuffer[256];
1053
1054     dlqlinkP ptr;
1055     statusP statusPtr;
1056
1057     extern dlqlinkT statusHead;
1058     extern statusP findStatus();
1059
1060
1061     tp = as->parms[0].items->data;
1062     if (strchr(tp, '.') == 0) {
1063         slot = bc_SafeATOI(tp);
1064         if (slot == -1) {
1065             com_err(whoami, 0, "Bad syntax for number '%s'", tp);
1066             return -1;
1067         }
1068
1069         lock_Status();
1070         ptr = (&statusHead)->dlq_next;
1071         while (ptr != &statusHead) {
1072             statusPtr = (statusP) ptr;
1073             if (statusPtr->jobNumber == slot) {
1074                 statusPtr->flags |= ABORT_REQUEST;
1075                 unlock_Status();
1076                 return (0);
1077             }
1078             ptr = ptr->dlq_next;
1079         }
1080         unlock_Status();
1081
1082         fprintf(stderr, "Job %d not found\n", slot);
1083         return (-1);
1084     } else {
1085         /* vol.dump */
1086         td = bc_dumpTasks;
1087         for (i = 0; i < BC_MAXSIMDUMPS; i++, td++) {
1088             if (td->flags & BC_DI_INUSE) {
1089                 /* compute name */
1090                 strcpy(tbuffer, td->volSetName);
1091                 strcat(tbuffer, ".");
1092                 strcat(tbuffer, tailCompPtr(td->dumpName));
1093                 if (strcmp(tbuffer, tp) == 0)
1094                     break;
1095             }
1096         }
1097         if (i >= BC_MAXSIMDUMPS) {
1098             com_err(whoami, 0, "Can't find job %s", tp);
1099             return -1;
1100         }
1101
1102         lock_Status();
1103         statusPtr = findStatus(td->dumpID);
1104
1105         if (statusPtr == 0) {
1106             com_err(whoami, 0, "Can't locate status - internal error");
1107             unlock_Status();
1108             return (-1);
1109         }
1110         statusPtr->flags |= ABORT_REQUEST;
1111         unlock_Status();
1112         return (0);
1113     }
1114     return 0;
1115 }
1116
1117 /* restore a volume or volumes */
1118 bc_VolRestoreCmd(as, arock)
1119      struct cmd_syndesc *as;
1120      char *arock;
1121 {
1122     /*
1123      * parm 0 is the new server to restore to
1124      * parm 1 is the new partition to restore to
1125      * parm 2 is volume(s) to restore
1126      * parm 3 is the new extension, if any, for the volume name.
1127      * parm 4 gives the new volume # to restore this volume as (removed).
1128      * parm 4 date is a string representing the date
1129      *
1130      * We handle four types of restores.  If old is set, then we restore the
1131      * volume with the same name and ID.  If old is not set, we allocate
1132      * a new volume ID for the restored volume.  If a new extension is specified,
1133      * we add that extension to the volume name of the restored volume.
1134      */
1135     struct bc_volumeEntry tvolumeEntry; /* entry within the volume set */
1136     struct bc_volumeDump *volsToRestore = (struct bc_volumeDump *)0;
1137     struct bc_volumeDump *lastVol = (struct bc_volumeDump *)0;
1138     struct bc_volumeDump *tvol; /* temp for same */
1139     struct sockaddr_in destServ;        /* machine to which to restore volumes */
1140     afs_int32 destPartition;    /* partition to which to restore volumes */
1141     char *tp;
1142     struct cmd_item *ti;
1143     afs_int32 code;
1144     int oldFlag;
1145     afs_int32 fromDate;
1146     char *newExt, *timeString;
1147     afs_int32 i;
1148     afs_int32 *ports = NULL;
1149     afs_int32 portCount = 0;
1150     int dontExecute;
1151
1152     code = bc_UpdateHosts();
1153     if (code) {
1154         com_err(whoami, code, "; Can't retrieve tape hosts");
1155         return (code);
1156     }
1157
1158     /* specified other destination host */
1159     if (as->parms[0].items) {
1160         tp = as->parms[0].items->data;
1161         if (bc_ParseHost(tp, &destServ)) {
1162             com_err(whoami, 0, "Failed to locate destination host '%s'", tp);
1163             return -1;
1164         }
1165     }
1166
1167     /* specified other destination partition */
1168     if (as->parms[1].items) {
1169         tp = as->parms[1].items->data;
1170         if (bc_GetPartitionID(tp, &destPartition)) {
1171             com_err(whoami, 0, "Can't parse destination partition '%s'", tp);
1172             return -1;
1173         }
1174     }
1175
1176     for (ti = as->parms[2].items; ti; ti = ti->next) {
1177         /* build list of volume items */
1178         tvol = (struct bc_volumeDump *)malloc(sizeof(struct bc_volumeDump));
1179         if (!tvol) {
1180             com_err(whoami, BC_NOMEM, "");
1181             return BC_NOMEM;
1182         }
1183         memset(tvol, 0, sizeof(struct bc_volumeDump));
1184
1185         tvol->name = (char *)malloc(VOLSER_MAXVOLNAME + 1);
1186         if (!tvol->name) {
1187             com_err(whoami, BC_NOMEM, "");
1188             return BC_NOMEM;
1189         }
1190         strncpy(tvol->name, ti->data, VOLSER_OLDMAXVOLNAME);
1191         tvol->entry = &tvolumeEntry;
1192
1193         if (lastVol)
1194             lastVol->next = tvol;       /* thread onto end of list */
1195         else
1196             volsToRestore = tvol;
1197         lastVol = tvol;
1198     }
1199
1200     if (as->parms[4].items) {
1201         timeString = concatParams(as->parms[4].items);
1202         if (!timeString)
1203             return (-1);
1204
1205         code = ktime_DateToLong(timeString, &fromDate);
1206         free(timeString);
1207         if (code) {
1208             com_err(whoami, 0, "Can't parse restore date and time");
1209             com_err(whoami, 0, "%s", ktime_GetDateUsage());
1210             return code;
1211         }
1212     } else {
1213         fromDate = 0x7fffffff;  /* latest one */
1214     }
1215
1216     newExt = (as->parms[3].items ? as->parms[3].items->data : NULL);
1217     oldFlag = 0;
1218
1219     /* Read all the port offsets into the ports array. The first element in the
1220      * array is for full restore and the rest are for incremental restores 
1221      */
1222     if (as->parms[5].items) {
1223         for (ti = as->parms[5].items; ti; ti = ti->next)
1224             portCount++;
1225         ports = (afs_int32 *) malloc(portCount * sizeof(afs_int32));
1226         if (!ports) {
1227             com_err(whoami, BC_NOMEM, "");
1228             return BC_NOMEM;
1229         }
1230
1231         for (ti = as->parms[5].items, i = 0; ti; ti = ti->next, i++) {
1232             ports[i] = getPortOffset(ti->data);
1233             if (ports[i] < 0)
1234                 return (BC_BADARG);
1235         }
1236     }
1237
1238     dontExecute = (as->parms[6].items ? 1 : 0); /* -n */
1239
1240     /*
1241      * Perform the call to start the restore.
1242      */
1243     code =
1244         bc_StartDmpRst(bc_globalConfig, "volume", "restore", volsToRestore,
1245                        &destServ, destPartition, fromDate, newExt, oldFlag,
1246                        /*parentDump */ 0, /*dumpLevel */ 0,
1247                        bc_Restorer, ports, portCount,
1248                        /*dumpSched */ 0, /*append */ 0, dontExecute);
1249     if (code)
1250         com_err(whoami, code, "; Failed to queue restore");
1251
1252     return (code);
1253 }
1254
1255 /* restore a whole partition or server */
1256
1257 /* bc_DiskRestoreCmd
1258  *      restore a whole partition
1259  * params:
1260  *      first, reqd - machine (server) to restore
1261  *      second, reqd - partition to restore
1262  *      various optional
1263  */
1264
1265 bc_DiskRestoreCmd(as, arock)
1266      struct cmd_syndesc *as;
1267      char *arock;
1268 {
1269     struct bc_volumeSet tvolumeSet;     /* temporary volume set for EvalVolumeSet call */
1270     struct bc_volumeEntry tvolumeEntry; /* entry within the volume set */
1271     struct bc_volumeDump *volsToRestore = (struct bc_volumeDump *)0;
1272     struct sockaddr_in destServ;        /* machine to which to restore volumes */
1273     afs_int32 destPartition;    /* partition to which to restore volumes */
1274     char *tp;
1275     afs_int32 code;
1276     int oldFlag;
1277     afs_int32 fromDate;
1278     char *newExt;
1279     afs_int32 *ports = NULL;
1280     afs_int32 portCount = 0;
1281     int dontExecute;
1282     struct bc_volumeDump *prev, *tvol, *nextvol;
1283     struct cmd_item *ti;
1284     afs_int32 i;
1285
1286     /* parm 0 is the server to restore
1287      * parm 1 is the partition to restore
1288      
1289      * parm 8 and above as in VolRestoreCmd:
1290      * parm 8 is the new server to restore to
1291      * parm 9 is the new partition to restore to
1292      */
1293
1294     code = bc_UpdateVolumeSet();
1295     if (code) {
1296         com_err(whoami, code, "; Can't retrieve volume sets");
1297         return (code);
1298     }
1299     code = bc_UpdateHosts();
1300     if (code) {
1301         com_err(whoami, code, "; Can't retrieve tape hosts");
1302         return (code);
1303     }
1304
1305     /* create a volume set corresponding to the volume pattern we've been given */
1306     memset(&tvolumeSet, 0, sizeof(tvolumeSet));
1307     memset(&tvolumeEntry, 0, sizeof(tvolumeEntry));
1308     tvolumeSet.name = "TempVolumeSet";
1309     tvolumeSet.ventries = &tvolumeEntry;
1310     tvolumeEntry.serverName = as->parms[0].items->data;
1311     tvolumeEntry.partname = as->parms[1].items->data;
1312
1313     if (bc_GetPartitionID(tvolumeEntry.partname, &tvolumeEntry.partition)) {
1314         com_err(whoami, 0, "Can't parse partition '%s'",
1315                 tvolumeEntry.partname);
1316         return -1;
1317     }
1318
1319     if (bc_ParseHost(tvolumeEntry.serverName, &tvolumeEntry.server)) {
1320         com_err(whoami, 0, "Can't locate host '%s'", tvolumeEntry.serverName);
1321         return -1;
1322     }
1323
1324     /* specified other destination host */
1325     if (as->parms[8].items) {
1326         tp = as->parms[8].items->data;
1327         if (bc_ParseHost(tp, &destServ)) {
1328             com_err(whoami, 0, "Can't locate destination host '%s'", tp);
1329             return -1;
1330         }
1331     } else                      /* use destination host == original host */
1332         memcpy(&destServ, &tvolumeEntry.server, sizeof(destServ));
1333
1334     /* specified other destination partition */
1335     if (as->parms[9].items) {
1336         tp = as->parms[9].items->data;
1337         if (bc_GetPartitionID(tp, &destPartition)) {
1338             com_err(whoami, 0, "Can't parse destination partition '%s'", tp);
1339             return -1;
1340         }
1341     } else                      /* use original partition */
1342         destPartition = tvolumeEntry.partition;
1343
1344     tvolumeEntry.name = ".*";   /* match all volumes (this should be a parameter) */
1345
1346     if (as->parms[2].items) {
1347         for (ti = as->parms[2].items; ti; ti = ti->next)
1348             portCount++;
1349         ports = (afs_int32 *) malloc(portCount * sizeof(afs_int32));
1350         if (!ports) {
1351             com_err(whoami, BC_NOMEM, "");
1352             return BC_NOMEM;
1353         }
1354
1355         for (ti = as->parms[2].items, i = 0; ti; ti = ti->next, i++) {
1356             ports[i] = getPortOffset(ti->data);
1357             if (ports[i] < 0)
1358                 return (BC_BADARG);
1359         }
1360     }
1361
1362     newExt = (as->parms[10].items ? as->parms[10].items->data : NULL);
1363     dontExecute = (as->parms[11].items ? 1 : 0);        /* -n */
1364
1365     /*
1366      * Expand out the volume set into its component list of volumes, by calling VLDB server.
1367      */
1368     code =
1369         bc_EvalVolumeSet(bc_globalConfig, &tvolumeSet, &volsToRestore,
1370                          cstruct);
1371     if (code) {
1372         com_err(whoami, code, "; Failed to evaluate volume set");
1373         return (-1);
1374     }
1375
1376     /* Since we want only RW volumes, remove any 
1377      * BK or RO volumes from the list.
1378      */
1379     for (prev = 0, tvol = volsToRestore; tvol; tvol = nextvol) {
1380         nextvol = tvol->next;
1381         if (BackupName(tvol->name)) {
1382             if (tvol->name)
1383                 fprintf(stderr,
1384                         "Will not restore volume %s (its RW does not reside on the partition)\n",
1385                         tvol->name);
1386
1387             if (tvol == volsToRestore) {
1388                 volsToRestore = nextvol;
1389             } else {
1390                 prev->next = nextvol;
1391             }
1392             if (tvol->name)
1393                 free(tvol->name);
1394             free(tvol);
1395         } else {
1396             prev = tvol;
1397         }
1398     }
1399
1400     fromDate = 0x7fffffff;      /* last one before this date */
1401     oldFlag = 1;                /* do restore same volid (and name) */
1402
1403     /*
1404      * Perform the call to start the dump.
1405      */
1406     code =
1407         bc_StartDmpRst(bc_globalConfig, "disk", "restore", volsToRestore,
1408                        &destServ, destPartition, fromDate, newExt, oldFlag,
1409                        /*parentDump */ 0, /*dumpLevel */ 0,
1410                        bc_Restorer, ports, portCount,
1411                        /*dumpSched */ 0, /*append */ 0, dontExecute);
1412     if (code)
1413         com_err(whoami, code, "; Failed to queue restore");
1414
1415     return (code);
1416 }
1417
1418 /* bc_VolsetRestoreCmd
1419  *      restore a volumeset or list of volumes.
1420  */
1421
1422 bc_VolsetRestoreCmd(as, arock)
1423      struct cmd_syndesc *as;
1424      char *arock;
1425 {
1426     int oldFlag;
1427     long fromDate;
1428     char *newExt;
1429
1430     int dontExecute;
1431     afs_int32 *ports = NULL;
1432     afs_int32 portCount = 0;
1433     afs_int32 code = 0;
1434     afs_int32 portoffset = 0;
1435     char *volsetName;
1436     struct bc_volumeSet *volsetPtr;     /* Ptr to list of generated volume info */
1437     struct bc_volumeDump *volsToRestore = (struct bc_volumeDump *)0;
1438     struct bc_volumeDump *lastVol = (struct bc_volumeDump *)0;
1439     struct sockaddr_in destServer;      /* machine to which to restore volume */
1440     afs_int32 destPartition;    /* partition to which to restore volumes */
1441     struct cmd_item *ti;
1442     afs_int32 i;
1443
1444     code = bc_UpdateVolumeSet();
1445     if (code) {
1446         com_err(whoami, code, "; Can't retrieve volume sets");
1447         return (code);
1448     }
1449     code = bc_UpdateHosts();
1450     if (code) {
1451         com_err(whoami, code, "; Can't retrieve tape hosts");
1452         return (code);
1453     }
1454
1455     if (as->parms[0].items) {
1456         if (as->parms[1].items) {
1457             com_err(whoami, 0, "Can't have both -name and -file options");
1458             return (-1);
1459         }
1460
1461         volsetName = as->parms[0].items->data;
1462         volsetPtr = bc_FindVolumeSet(bc_globalConfig, volsetName);
1463         if (!volsetPtr) {
1464             com_err(whoami, 0,
1465                     "Can't find volume set '%s' in backup database",
1466                     volsetName);
1467             return (-1);
1468         }
1469
1470         /* Expand out the volume set into its component list of volumes. */
1471         code =
1472             bc_EvalVolumeSet(bc_globalConfig, volsetPtr, &volsToRestore,
1473                              cstruct);
1474         if (code) {
1475             com_err(whoami, code, "; Failed to evaluate volume set");
1476             return (-1);
1477         }
1478     } else if (as->parms[1].items) {
1479         FILE *fd;
1480         char line[256];
1481         char server[50], partition[50], volume[50], rest[256];
1482         long count;
1483         struct bc_volumeDump *tvol;
1484
1485         fd = fopen(as->parms[1].items->data, "r");
1486         if (!fd) {
1487             com_err(whoami, errno, "; Cannot open file '%s'",
1488                     as->parms[1].items->data);
1489             return (-1);
1490         }
1491
1492         while (fgets(line, 255, fd)) {
1493             count =
1494                 sscanf(line, "%s %s %s %s", server, partition, volume, rest);
1495
1496             if (count <= 0)
1497                 continue;
1498             if (count < 3) {
1499                 fprintf(stderr,
1500                         "Invalid volumeset file format: Wrong number of arguments: Ignoring\n");
1501                 fprintf(stderr, "     %s", line);
1502                 continue;
1503             }
1504
1505             if (bc_ParseHost(server, &destServer)) {
1506                 com_err(whoami, 0, "Failed to locate host '%s'", server);
1507                 continue;
1508             }
1509
1510             if (bc_GetPartitionID(partition, &destPartition)) {
1511                 com_err(whoami, 0,
1512                         "Failed to parse destination partition '%s'",
1513                         partition);
1514                 continue;
1515             }
1516
1517             /* Allocate a volumeDump structure and link it in */
1518             tvol =
1519                 (struct bc_volumeDump *)malloc(sizeof(struct bc_volumeDump));
1520             memset(tvol, 0, sizeof(struct bc_volumeDump));
1521
1522             tvol->name = (char *)malloc(VOLSER_MAXVOLNAME + 1);
1523             if (!tvol->name) {
1524                 com_err(whoami, BC_NOMEM, "");
1525                 return BC_NOMEM;
1526             }
1527             strncpy(tvol->name, volume, VOLSER_OLDMAXVOLNAME);
1528             memcpy(&tvol->server, &destServer, sizeof(destServer));
1529             tvol->partition = destPartition;
1530
1531             if (lastVol)
1532                 lastVol->next = tvol;   /* thread onto end of list */
1533             else
1534                 volsToRestore = tvol;
1535             lastVol = tvol;
1536         }
1537         fclose(fd);
1538     } else {
1539         com_err(whoami, 0, "-name or -file option required");
1540         return (-1);
1541     }
1542
1543
1544     /* Get the port offset for the restore */
1545     if (as->parms[2].items) {
1546         for (ti = as->parms[2].items; ti; ti = ti->next)
1547             portCount++;
1548         ports = (afs_int32 *) malloc(portCount * sizeof(afs_int32));
1549         if (!ports) {
1550             com_err(whoami, BC_NOMEM, "");
1551             return BC_NOMEM;
1552         }
1553
1554         for (ti = as->parms[2].items, i = 0; ti; ti = ti->next, i++) {
1555             ports[i] = getPortOffset(ti->data);
1556             if (ports[i] < 0)
1557                 return (BC_BADARG);
1558         }
1559     }
1560
1561     newExt = (as->parms[3].items ? as->parms[3].items->data : NULL);
1562     dontExecute = (as->parms[4].items ? 1 : 0);
1563
1564     fromDate = 0x7fffffff;      /* last one before this date */
1565     oldFlag = 1;                /* do restore same volid (and name) */
1566
1567     /* Perform the call to start the restore */
1568     code = bc_StartDmpRst(bc_globalConfig, "disk", "restore", volsToRestore,
1569                           /*destserver */ 0, /*destpartition */ 0, fromDate,
1570                           newExt, oldFlag,
1571                           /*parentDump */ 0, /*dumpLevel */ 0,
1572                           bc_Restorer, ports, portCount,
1573                           /*dumpSched */ 0, /*append */ 0, dontExecute);
1574     if (code)
1575         com_err(whoami, code, "; Failed to queue restore");
1576
1577     return code;
1578 }
1579
1580 /*-----------------------------------------------------------------------------
1581  * bc_DumpCmd
1582  *
1583  * Description:
1584  *      Perform a dump of the set of volumes provided.
1585  *      user specifies: -volumeset .. -dump .. [-portoffset ..] [-n]
1586  *
1587  * Arguments:
1588  *      as      : Parsed command line information.
1589  *      arock   : Ptr to misc stuff; not used.
1590  *
1591  * Returns:
1592  *      -1 on errors,
1593  *      The result of bc_StartDump() otherwise
1594  *
1595  * Environment:
1596  *      Nothing special.
1597  *
1598  * Side Effects:
1599  *      As advertised.
1600  *---------------------------------------------------------------------------
1601  */
1602 int dontExecute;
1603
1604 int
1605 bc_DumpCmd(as, arock)
1606      struct cmd_syndesc *as;
1607      char *arock;
1608 {                               /*bc_DumpCmd */
1609     static char rn[] = "bc_DumpCmd";    /*Routine name */
1610     char *dumpPath, *vsName;    /*Ptrs to various names */
1611     struct bc_volumeSet *tvs;   /*Ptr to list of generated volume info */
1612     struct bc_dumpSchedule *tds, *baseds;       /*Ptr to dump schedule node */
1613     struct bc_volumeDump *tve, *volsToDump;     /*Ptr to individual vols to be dumped */
1614     struct budb_dumpEntry dumpEntry, de, fde;   /* dump entry */
1615     afs_uint32 d;
1616
1617     afs_int32 parent;           /* parent dump */
1618     afs_int32 level;            /* this dump's level # */
1619     afs_int32 problemFindingDump;       /* can't find parent(s) */
1620
1621     afs_int32 *portp = NULL;
1622     afs_int32 portCount = 0;
1623     afs_int32 doAt, atTime;     /* Time a timed-dump is to start at */
1624     afs_int32 length;
1625     char *timeString;
1626     int doAppend;               /* Append the dump to dump set */
1627     afs_int32 code;             /* Return code */
1628     int loadfile;               /* whether to load a file or not */
1629
1630     statusP statusPtr;
1631
1632     extern struct bc_dumpTask bc_dumpTasks[];
1633     extern afs_int32 bcdb_FindLastVolClone();
1634     extern afs_int32 volImageTime();
1635
1636     code = bc_UpdateDumpSchedule();
1637     if (code) {
1638         com_err(whoami, code, "; Can't retrieve dump schedule");
1639         return (code);
1640     }
1641     code = bc_UpdateVolumeSet();
1642     if (code) {
1643         com_err(whoami, code, "; Can't retrieve volume sets");
1644         return (code);
1645     }
1646     code = bc_UpdateHosts();
1647     if (code) {
1648         com_err(whoami, code, "; Can't retrieve tape hosts");
1649         return (code);
1650     }
1651
1652     /* 
1653      * Some parameters cannot be specified together
1654      * The "-file" option cannot exist with the "-volume", "-dump", 
1655      * "-portoffset", or "-append" option 
1656      */
1657     if (as->parms[6].items) {
1658         loadfile = 1;
1659         if (as->parms[0].items || as->parms[1].items || as->parms[2].items
1660             || as->parms[4].items) {
1661             com_err(whoami, 0, "Invalid option specified with -file option");
1662             return -1;
1663         }
1664     } else {
1665         loadfile = 0;
1666         if (!as->parms[0].items || !as->parms[1].items) {
1667             com_err(whoami, 0,
1668                     "Must specify volume set name and dump level name");
1669             return -1;
1670         }
1671     }
1672
1673     /* 
1674      * Get the time we are to perform this dump
1675      */
1676     if (as->parms[3].items) {
1677         doAt = 1;
1678
1679         timeString = concatParams(as->parms[3].items);
1680         if (!timeString)
1681             return (-1);
1682
1683         /*
1684          * Now parse this string for the time to start.
1685          */
1686         code = ktime_DateToLong(timeString, &atTime);
1687         free(timeString);
1688         if (code) {
1689             com_err(whoami, 0, "Can't parse dump start date and time");
1690             com_err(whoami, 0, "%s", ktime_GetDateUsage());
1691             return (1);
1692         }
1693     } else
1694         doAt = 0;
1695
1696     dontExecute = (as->parms[5].items ? 1 : 0); /* -n */
1697
1698     /* 
1699      * If this dump is not a load file, then check the parameters.
1700      */
1701     if (!loadfile) {            /*6 */
1702         vsName = as->parms[0].items->data;      /* get volume set name */
1703         dumpPath = as->parms[1].items->data;    /* get dump path */
1704
1705         /* get the port number, if one was specified */
1706         if (as->parms[2].items) {
1707             portCount = 1;
1708             portp = (afs_int32 *) malloc(sizeof(afs_int32));
1709             if (!portp) {
1710                 com_err(whoami, BC_NOMEM, "");
1711                 return BC_NOMEM;
1712             }
1713
1714             *portp = getPortOffset(as->parms[2].items->data);
1715             if (*portp < 0)
1716                 return (BC_BADARG);
1717         }
1718
1719         doAppend = (as->parms[4].items ? 1 : 0);        /* -append */
1720
1721         /*
1722          * Get a hold of the given volume set and dump set.
1723          */
1724         tvs = bc_FindVolumeSet(bc_globalConfig, vsName);
1725         if (!tvs) {
1726             com_err(whoami, 0,
1727                     "Can't find volume set '%s' in backup database", vsName);
1728             return (-1);
1729         }
1730         baseds = bc_FindDumpSchedule(bc_globalConfig, dumpPath);
1731         if (!baseds) {
1732             com_err(whoami, 0,
1733                     "Can't find dump schedule '%s' in backup database",
1734                     dumpPath);
1735             return (-1);
1736         }
1737
1738     }
1739
1740     /*6 */
1741     /*
1742      * If given the "-at" option, then add this to the jobs list and return 
1743      * with no error.
1744      *
1745      * Create a status node for this timed dump.
1746      * Fill in the time to dump and the cmd line for the dump leaving off
1747      * the -at option.  If the -n option is there, it is scheduled with 
1748      * the Timed dump as opposed to not scheduling the time dump at all.
1749      */
1750     if (doAt) {
1751         if (atTime < time(0)) {
1752             com_err(whoami, 0,
1753                     "Time of dump is earlier then current time - not added");
1754         } else {
1755             statusPtr = createStatusNode();
1756             lock_Status();
1757             statusPtr->scheduledDump = atTime;
1758
1759             /* Determine length of the dump command */
1760             length = 0;
1761             length += 4;        /* "dump" */
1762             if (loadfile) {
1763                 length += 6;    /* " -file" */
1764                 length += 1 + strlen(as->parms[6].items->data); /* " <file>" */
1765             } else {
1766                 /* length += 11; *//* " -volumeset" */
1767                 length += 1 + strlen(as->parms[0].items->data); /* " <volset> */
1768
1769                 /* length += 6; *//* " -dump" */
1770                 length += 1 + strlen(as->parms[1].items->data); /* " <dumpset> */
1771
1772                 if (as->parms[2].items) {
1773                     /* length += 12; *//* " -portoffset" */
1774                     length += 1 + strlen(as->parms[2].items->data);     /* " <port>" */
1775                 }
1776
1777                 if (as->parms[4].items)
1778                     length += 8;        /* " -append" */
1779             }
1780
1781             if (dontExecute)
1782                 length += 3;    /* " -n" */
1783             length++;           /* end-of-line */
1784
1785             /* Allocate status block for this timed dump */
1786             sprintf(statusPtr->taskName, "Scheduled Dump");
1787             statusPtr->jobNumber = bc_jobNumber();
1788             statusPtr->scheduledDump = atTime;
1789             statusPtr->cmdLine = (char *)malloc(length);
1790             if (!statusPtr->cmdLine) {
1791                 com_err(whoami, BC_NOMEM, "");
1792                 return BC_NOMEM;
1793             }
1794
1795             /* Now reconstruct the dump command */
1796             statusPtr->cmdLine[0] = 0;
1797             strcat(statusPtr->cmdLine, "dump");
1798             if (loadfile) {
1799                 strcat(statusPtr->cmdLine, " -file");
1800                 strcat(statusPtr->cmdLine, " ");
1801                 strcat(statusPtr->cmdLine, as->parms[6].items->data);
1802             } else {
1803                 /* strcat(statusPtr->cmdLine, " -volumeset"); */
1804                 strcat(statusPtr->cmdLine, " ");
1805                 strcat(statusPtr->cmdLine, as->parms[0].items->data);
1806
1807                 /* strcat(statusPtr->cmdLine, " -dump"); */
1808                 strcat(statusPtr->cmdLine, " ");
1809                 strcat(statusPtr->cmdLine, as->parms[1].items->data);
1810
1811                 if (as->parms[2].items) {
1812                     /* strcat(statusPtr->cmdLine, " -portoffset"); */
1813                     strcat(statusPtr->cmdLine, " ");
1814                     strcat(statusPtr->cmdLine, as->parms[2].items->data);
1815                 }
1816
1817                 if (as->parms[4].items)
1818                     strcat(statusPtr->cmdLine, " -append");
1819             }
1820             if (dontExecute)
1821                 strcat(statusPtr->cmdLine, " -n");
1822
1823             printf("Add scheduled dump as job %d\n", statusPtr->jobNumber);
1824             if ((atTime > ttoken.endTime) && (ttoken.endTime != NEVERDATE))
1825                 com_err(whoami, 0,
1826                         "Warning: job %d starts after expiration of AFS token",
1827                         statusPtr->jobNumber);
1828
1829             unlock_Status();
1830         }
1831
1832         return (0);
1833     }
1834
1835     /* 
1836      * Read and execute the load file if specified.  The work of reading is done
1837      * in the main routine prior the dispatch call. loadFile and dontExecute are
1838      * global variables so this can take place in main.
1839      */
1840     if (loadfile) {
1841         loadFile = (char *)malloc(strlen(as->parms[6].items->data) + 1);
1842         if (!loadFile) {
1843             com_err(whoami, BC_NOMEM, "");
1844             return BC_NOMEM;
1845         }
1846         strcpy(loadFile, as->parms[6].items->data);
1847         return 0;
1848     }
1849
1850     /* 
1851      * We are doing a real dump (no load file or timed dump).
1852      */
1853     printf("Starting dump of volume set '%s' (dump level '%s')\n", vsName,
1854            dumpPath);
1855
1856     /* For each dump-level above this one, see if the volumeset was dumped
1857      * at the level. We search all dump-levels since a higher dump-level
1858      * may have actually been done AFTER a lower dump level.
1859      */
1860     parent = level = problemFindingDump = 0;
1861     for (tds = baseds->parent; tds; tds = tds->parent) {
1862         /* Find the most recent dump of the volume-set at this dump-level.
1863          * Raise problem flag if didn't find a dump and a parent not yet found.
1864          */
1865         code = bcdb_FindLatestDump(vsName, tds->name, &dumpEntry);
1866         if (code) {
1867             if (!parent)
1868                 problemFindingDump = 1; /* skipping a dump level */
1869             continue;
1870         }
1871
1872         /* We found the most recent dump at this level. Now check
1873          * if we should use it by seeing if its full dump hierarchy 
1874          * exists. If it doesn't, we don't want to base our incremental
1875          * off of this dump. 
1876          */
1877         if (!parent || (dumpEntry.id > parent)) {
1878             /* Follow the parent dumps to see if they are all there */
1879             for (d = dumpEntry.parent; d; d = de.parent) {
1880                 code = bcdb_FindDumpByID(d, &de);
1881                 if (code)
1882                     break;
1883             }
1884
1885             /* If we found the entire level, remember it. Otherwise raise flag.
1886              * If we had already found a dump, raise the problem flag.
1887              */
1888             if (!d && !code) {
1889                 if (parent)
1890                     problemFindingDump = 1;
1891                 parent = dumpEntry.id;
1892                 level = dumpEntry.level + 1;
1893                 memcpy(&fde, &dumpEntry, sizeof(dumpEntry));
1894             } else {
1895                 /* Dump hierarchy not complete so can't base off the latest */
1896                 problemFindingDump = 1;
1897             }
1898         }
1899     }
1900
1901     /* If the problemflag was raise, it means we are not doing the 
1902      * dump at the level we requested it be done at.
1903      */
1904     if (problemFindingDump) {
1905         com_err(whoami, 0,
1906                 "Warning: Doing level %d dump due to missing higher-level dumps",
1907                 level);
1908         if (parent) {
1909             printf("Parent dump: dump %s (DumpID %u)\n", fde.name, parent);
1910         }
1911     } else if (parent) {
1912         printf("Found parent: dump %s (DumpID %u)\n", fde.name, parent);
1913     }
1914
1915     /* Expand out the volume set into its component list of volumes. */
1916     code = bc_EvalVolumeSet(bc_globalConfig, tvs, &volsToDump, cstruct);
1917     if (code) {
1918         com_err(whoami, code, "; Failed to evaluate volume set");
1919         return (-1);
1920     }
1921     if (!volsToDump) {
1922         printf("No volumes to dump\n");
1923         return (0);
1924     }
1925
1926     /* Determine what the clone time of the volume was when it was
1927      * last dumped (tve->date). This is the time from when an
1928      * incremental should be done (remains zero if a full dump).
1929      */
1930     if (parent) {
1931         for (tve = volsToDump; tve; tve = tve->next) {
1932             code = bcdb_FindClone(parent, tve->name, &tve->date);
1933             if (code)
1934                 tve->date = 0;
1935
1936             /* Get the time the volume was last cloned and see if the volume has
1937              * changed since then. Only do this when the "-n" flag is specified
1938              * because butc will get the cloneDate at time of dump.
1939              */
1940             if (dontExecute) {
1941                 code =
1942                     volImageTime(tve->server.sin_addr.s_addr, tve->partition,
1943                                  tve->vid, tve->volType, &tve->cloneDate);
1944                 if (code)
1945                     tve->cloneDate = 0;
1946
1947                 if (tve->cloneDate && (tve->cloneDate == tve->date)) {
1948                     com_err(whoami, 0,
1949                             "Warning: Timestamp on volume %s unchanged from previous dump",
1950                             tve->name);
1951                 }
1952             }
1953         }
1954     }
1955
1956     if (dontExecute)
1957         printf("Would have dumped the following volumes:\n");
1958     else
1959         printf("Preparing to dump the following volumes:\n");
1960     for (tve = volsToDump; tve; tve = tve->next) {
1961         printf("\t%s (%u)\n", tve->name, tve->vid);
1962     }
1963     if (dontExecute)
1964         return (0);
1965
1966     code = bc_StartDmpRst(bc_globalConfig, dumpPath, vsName, volsToDump,
1967                           /*destServer */ 0, /*destPartition */ 0,
1968                           /*fromDate */ 0,
1969                           /*newExt */ 0, /*oldFlag */ 0,
1970                           parent, level, bc_Dumper, portp, /*portCount */ 1,
1971                           baseds, doAppend, dontExecute);
1972     if (code)
1973         com_err(whoami, code, "; Failed to queue dump");
1974
1975     return (code);
1976 }                               /*bc_DumpCmd */
1977
1978
1979 /* bc_QuitCmd
1980  *      terminate the backup process. Insists that that all running backup
1981  *      jobs be terminated before it will quit
1982  * parameters:
1983  *      ignored
1984  */
1985
1986 bc_QuitCmd(as, arock)
1987      struct cmd_syndesc *as;
1988      char *arock;
1989 {
1990     int i;
1991     struct bc_dumpTask *td;
1992     extern dlqlinkT statusHead;
1993     dlqlinkP ptr;
1994     statusP statusPtr;
1995
1996     /* Check the status list for outstanding jobs */
1997     lock_Status();
1998     for (ptr = (&statusHead)->dlq_next; ptr != &statusHead;
1999          ptr = ptr->dlq_next) {
2000         statusPtr = (statusP) ptr;
2001         if (!(statusPtr->flags & ABORT_REQUEST)) {
2002             unlock_Status();
2003             com_err(whoami, 0, "Job %d still running (and not aborted)",
2004                     statusPtr->jobNumber);
2005             com_err(whoami, 0,
2006                     "You must at least 'kill' all running jobs before quitting");
2007             return -1;
2008         }
2009     }
2010     unlock_Status();
2011
2012     /* A job still being initialized (but no status structure or job number since it
2013      * has not been handed to a butc process yet)
2014      */
2015     for (td = bc_dumpTasks, i = 0; i < BC_MAXSIMDUMPS; i++, td++) {
2016         if (td->flags & BC_DI_INUSE) {
2017             com_err(whoami, 0, "A job is still running");
2018             com_err(whoami, 0,
2019                     "You must at least 'kill' all running jobs before quitting");
2020             return -1;
2021         }
2022     }
2023
2024 #ifdef AFS_NT40_ENV
2025     /* close the all temp text files before quitting */
2026     for (i = 0; i < TB_NUM; i++)
2027         bc_closeTextFile(&bc_globalConfig->configText[i],
2028                          &bc_globalConfig->tmpTextFileNames[i][0]);
2029 #endif
2030     exit(lastTaskCode);
2031 }
2032
2033 /* bc_LabelTapeCmd
2034  *      Labels a tape i.e. request the tape coordinator to perform this
2035  *      operation
2036  */
2037
2038 bc_LabelTapeCmd(as, arock)
2039      struct cmd_syndesc *as;
2040      char *arock;
2041 {
2042     char *tapename = 0, *pname = 0;
2043     afs_int32 size;
2044     afs_int32 code;
2045     afs_int32 port = 0;
2046
2047     code = bc_UpdateHosts();
2048     if (code) {
2049         com_err(whoami, code, "; Can't retrieve tape hosts");
2050         return (code);
2051     }
2052
2053     if (as->parms[0].items) {   /* -name */
2054         tapename = as->parms[0].items->data;
2055         if (strlen(tapename) >= TC_MAXTAPELEN) {
2056             com_err(whoami, 0, "AFS tape name '%s' is too long", tapename);
2057             return -1;
2058         }
2059     }
2060
2061     if (as->parms[3].items) {   /* -pname */
2062         if (tapename) {
2063             com_err(whoami, 0, "Can only specify -name or -pname");
2064             return -1;
2065         }
2066         pname = as->parms[3].items->data;
2067         if (strlen(pname) >= TC_MAXTAPELEN) {
2068             com_err(whoami, 0, "Permanent tape name '%s' is too long", pname);
2069             return -1;
2070         }
2071     }
2072
2073     if (as->parms[1].items) {
2074         size = bc_FloatATOI(as->parms[1].items->data);
2075         if (size == -1) {
2076             com_err(whoami, 0, "Bad syntax for tape size '%s'",
2077                     as->parms[1].items->data);
2078             return -1;
2079         }
2080     } else
2081         size = 0;
2082
2083     if (as->parms[2].items) {
2084         port = getPortOffset(as->parms[2].items->data);
2085         if (port < 0)
2086             return (BC_BADARG);
2087     }
2088
2089     code = bc_LabelTape(tapename, pname, size, bc_globalConfig, port);
2090     if (code)
2091         return code;
2092     return 0;
2093 }
2094
2095 /* bc_ReadLabelCmd
2096  *      read the label on a tape
2097  * params:
2098  *      optional port number
2099  */
2100
2101 bc_ReadLabelCmd(as, arock)
2102      struct cmd_syndesc *as;
2103      char *arock;
2104 {
2105     afs_int32 code;
2106     afs_int32 port = 0;
2107
2108     code = bc_UpdateHosts();
2109     if (code) {
2110         com_err(whoami, code, "; Can't retrieve tape hosts");
2111         return (code);
2112     }
2113
2114     if (as->parms[0].items) {
2115         port = getPortOffset(as->parms[0].items->data);
2116         if (port < 0)
2117             return (BC_BADARG);
2118     }
2119
2120     code = bc_ReadLabel(bc_globalConfig, port);
2121     if (code)
2122         return code;
2123     return 0;
2124 }
2125
2126 /* bc_ScanDumpsCmd
2127  *      read content information from dump tapes, and if user desires,
2128  *      add it to the database
2129  */
2130
2131 bc_ScanDumpsCmd(as, arock)
2132      struct cmd_syndesc *as;
2133      char *arock;
2134 {
2135     afs_int32 port = 0;
2136     afs_int32 dbAddFlag = 0;
2137     afs_int32 code;
2138
2139     code = bc_UpdateHosts();
2140     if (code) {
2141         com_err(whoami, code, "; Can't retrieve tape hosts");
2142         return (code);
2143     }
2144
2145     /* check for flag */
2146     if (as->parms[0].items != 0) {      /* add scan to database */
2147         dbAddFlag++;
2148     }
2149
2150     /* check for port */
2151     if (as->parms[1].items) {
2152         port = getPortOffset(as->parms[1].items->data);
2153         if (port < 0)
2154             return (BC_BADARG);
2155     }
2156
2157     code = bc_ScanDumps(bc_globalConfig, dbAddFlag, port);
2158     return (code);
2159 }
2160
2161 /* bc_ParseExpiration
2162  *
2163  * Notes:
2164  *      dates are specified as absolute or relative, the syntax is:
2165  *      absolute:       at %d/%d/%d [%d:%d]     where [..] is optional
2166  *      relative:       in [%dy][%dm][%dd]      where at least one component
2167  *                                              must be specified
2168  */
2169
2170 afs_int32
2171 bc_ParseExpiration(paramPtr, expType, expDate)
2172      struct cmd_parmdesc *paramPtr;
2173      afs_int32 *expType;
2174      afs_int32 *expDate;
2175 {
2176     struct cmd_item *itemPtr;
2177     struct ktime_date kt;
2178     char *dateString = 0;
2179     afs_int32 code = 0;
2180
2181     *expType = BC_NO_EXPDATE;
2182     *expDate = 0;
2183
2184     if (!paramPtr->items)
2185         ERROR(0);               /* no expiration specified */
2186
2187     /* some form of expiration date specified. First validate the prefix */
2188     itemPtr = paramPtr->items;
2189
2190     if (strcmp(itemPtr->data, "at") == 0) {
2191         *expType = BC_ABS_EXPDATE;
2192
2193         dateString = concatParams(itemPtr->next);
2194         if (!dateString)
2195             ERROR(1);
2196
2197         code = ktime_DateToLong(dateString, expDate);
2198         if (code)
2199             ERROR(1);
2200     } else if (strcmp(itemPtr->data, "in") == 0) {
2201         *expType = BC_REL_EXPDATE;
2202
2203         dateString = concatParams(itemPtr->next);
2204         if (!dateString)
2205             ERROR(1);
2206
2207         code = ParseRelDate(dateString, &kt);
2208         if (code)
2209             ERROR(1);
2210         *expDate = ktimeRelDate_ToLong(&kt);
2211     } else {
2212         dateString = concatParams(itemPtr);
2213         if (!dateString)
2214             ERROR(1);
2215
2216         if (ktime_DateToLong(dateString, expDate) == 0) {
2217             *expType = BC_ABS_EXPDATE;
2218             code = ktime_DateToLong(dateString, expDate);
2219             if (code)
2220                 ERROR(1);
2221         } else if (ParseRelDate(dateString, &kt) == 0) {
2222             *expType = BC_REL_EXPDATE;
2223             *expDate = ktimeRelDate_ToLong(&kt);
2224         } else {
2225             ERROR(1);
2226         }
2227     }
2228
2229   error_exit:
2230     if (dateString)
2231         free(dateString);
2232     return (code);
2233 }
2234
2235 /* database lookup command and routines */
2236
2237 /* bc_dblookupCmd
2238  *      Currently a single option, volumename to search for. Reports
2239  *      all dumps containing the specified volume
2240  */
2241
2242 bc_dblookupCmd(as, arock)
2243      struct cmd_syndesc *as;
2244      char *arock;
2245 {
2246     struct cmd_item *ciptr;
2247     afs_int32 code;
2248
2249     ciptr = as->parms[0].items;
2250     if (ciptr == 0)             /* no argument specified */
2251         return (-1);
2252
2253     code = DBLookupByVolume(ciptr->data);
2254     return (code);
2255 }
2256
2257
2258
2259 /* for ubik version */
2260
2261 bc_dbVerifyCmd(as, arock)
2262      struct cmd_syndesc *as;
2263      char *arock;
2264 {
2265     afs_int32 status;
2266     afs_int32 orphans;
2267     afs_int32 host;
2268
2269     struct hostent *hostPtr;
2270     int detail;
2271     afs_int32 code = 0;
2272
2273     extern struct udbHandleS udbHandle;
2274
2275     detail = (as->parms[0].items ? 1 : 0);      /* print more details */
2276
2277     code =
2278         ubik_Call(BUDB_DbVerify, udbHandle.uh_client, 0, &status, &orphans,
2279                   &host);
2280
2281     if (code) {
2282         com_err(whoami, code, "; Unable to verify database");
2283         return (-1);
2284     }
2285
2286     /* verification call succeeded */
2287
2288     if (status == 0)
2289         printf("Database OK\n");
2290     else
2291         com_err(whoami, status, "; Database is NOT_OK");
2292
2293     if (detail) {
2294         printf("Orphan blocks %d\n", orphans);
2295
2296         if (!host)
2297             printf("Unable to lookup host id\n");
2298         else {
2299             hostPtr = gethostbyaddr((char *)&host, sizeof(host), AF_INET);
2300             if (hostPtr == 0)
2301                 printf("Database checker was %d.%d.%d.%d\n",
2302                        ((host & 0xFF000000) >> 24), ((host & 0xFF0000) >> 16),
2303                        ((host & 0xFF00) >> 8), (host & 0xFF));
2304             else
2305                 printf("Database checker was %s\n", hostPtr->h_name);
2306         }
2307     }
2308     return ((status ? -1 : 0));
2309 }
2310
2311 /* deleteDump:
2312  * Delete a dump. If port is >= 0, it means try to delete from XBSA server
2313  */
2314 deleteDump(dumpid, port, force)
2315      afs_uint32 dumpid;         /* The dumpid to delete */
2316      afs_int32 port;            /* port==-1 means don't go to butc */
2317      afs_int32 force;
2318 {
2319     afs_int32 code = 0, tcode;
2320     struct budb_dumpEntry dumpEntry;
2321     struct rx_connection *tconn = 0;
2322     afs_int32 i, taskflag, xbsadump;
2323     statusP statusPtr = 0;
2324     budb_dumpsList dumps;
2325     afs_uint32 taskId;
2326
2327     /* If the port is set, we will try to send a delete request to the butc */
2328     if (port >= 0) {
2329         tcode = bc_UpdateHosts();
2330         if (tcode) {
2331             com_err(whoami, tcode, "; Can't retrieve tape hosts");
2332             ERROR(tcode);
2333         }
2334
2335         /* Find the dump in the backup database */
2336         tcode = bcdb_FindDumpByID(dumpid, &dumpEntry);
2337         if (tcode) {
2338             com_err(whoami, tcode, "; Unable to locate dumpID %u in database",
2339                     dumpid);
2340             ERROR(tcode);
2341         }
2342         xbsadump = (dumpEntry.flags & (BUDB_DUMP_ADSM | BUDB_DUMP_BUTA));
2343
2344         /* If dump is to an XBSA server, connect to butc and send it
2345          * the dump to delete. Butc will contact the XBSA server.
2346          * The dump will not be an appended dump because XBSA butc 
2347          * does not support the append option.
2348          */
2349         if (xbsadump && dumpEntry.nVolumes) {
2350             tcode = ConnectButc(bc_globalConfig, port, &tconn);
2351             if (tcode)
2352                 ERROR(tcode);
2353
2354             tcode = TC_DeleteDump(tconn, dumpid, &taskId);
2355             if (tcode) {
2356                 if (tcode == RXGEN_OPCODE)
2357                     tcode = BC_VERSIONFAIL;
2358                 com_err(whoami, tcode,
2359                         "; Unable to delete dumpID %u via butc", dumpid);
2360                 ERROR(tcode);
2361             }
2362
2363             statusPtr = createStatusNode();
2364             lock_Status();
2365             statusPtr->taskId = taskId;
2366             statusPtr->port = port;
2367             statusPtr->jobNumber = bc_jobNumber();
2368             statusPtr->flags |= (SILENT | NOREMOVE);    /* No msg & keep statusPtr */
2369             statusPtr->flags &= ~STARTING;      /* clearstatus to examine */
2370             sprintf(statusPtr->taskName, "DeleteDump");
2371             unlock_Status();
2372
2373             /* Wait for task to finish */
2374             taskflag = waitForTask(taskId);
2375             if (taskflag & (TASK_ERROR | ABORT_DONE)) {
2376                 com_err(whoami, BUTX_DELETEOBJFAIL,
2377                         "; Unable to delete dumpID %u via butc", dumpid);
2378                 ERROR(BUTX_DELETEOBJFAIL);      /* the task failed */
2379             }
2380         }
2381     }
2382
2383   error_exit:
2384     if (statusPtr)
2385         deleteStatusNode(statusPtr);    /* Clean up statusPtr - because NOREMOVE */
2386     if (tconn)
2387         rx_DestroyConnection(tconn);    /* Destroy the connection */
2388
2389     /* Remove the dump from the backup database */
2390     if (!code || force) {
2391         dumps.budb_dumpsList_len = 0;
2392         dumps.budb_dumpsList_val = 0;
2393
2394         tcode = bcdb_deleteDump(dumpid, 0, 0, &dumps);
2395         if (tcode) {
2396             com_err(whoami, tcode,
2397                     "; Unable to delete dumpID %u from database", dumpid);
2398             dumps.budb_dumpsList_len = 0;
2399             if (!code)
2400                 code = tcode;
2401         }
2402
2403         /* Display the dumps that were deleted - includes appended dumps */
2404         for (i = 0; i < dumps.budb_dumpsList_len; i++)
2405             printf("     %u%s\n", dumps.budb_dumpsList_val[i],
2406                    (i > 0) ? " Appended Dump" : "");
2407         if (dumps.budb_dumpsList_val)
2408             free(dumps.budb_dumpsList_val);
2409     }
2410
2411     return code;
2412 }
2413
2414 /* bc_deleteDumpCmd
2415  *      Delete a specified dump from the database
2416  * entry:
2417  *      dump id - single required arg as param 0.
2418  */
2419
2420 bc_deleteDumpCmd(as, arock)
2421      struct cmd_syndesc *as;
2422      char *arock;
2423 {
2424     afs_uint32 dumpid;
2425     afs_int32 code = 0;
2426     afs_int32 rcode = 0;
2427     afs_int32 groupId = 0, havegroupid, sflags, noexecute;
2428     struct cmd_item *ti;
2429     afs_uint32 fromTime = 0, toTime = 0, havetime = 0;
2430     char *timeString;
2431     budb_dumpsList dumps, flags;
2432     int i;
2433     afs_int32 port = -1, dbonly = 0, force;
2434
2435     /* Must specify at least one of -dumpid, -from, or -to */
2436     if (!as->parms[0].items && !as->parms[1].items && !as->parms[2].items
2437         && !as->parms[4].items) {
2438         com_err(whoami, 0, "Must specify at least one field");
2439         return (-1);
2440     }
2441
2442     /* Must have -to option with -from option */
2443     if (as->parms[1].items && !as->parms[2].items) {
2444         com_err(whoami, 0, "Must specify '-to' field with '-from' field");
2445         return (-1);
2446     }
2447
2448     /* Get the time to delete from */
2449     if (as->parms[1].items) {   /* -from */
2450         timeString = concatParams(as->parms[1].items);
2451         if (!timeString)
2452             return (-1);
2453
2454         /*
2455          * Now parse this string for the time to start.
2456          */
2457         code = ktime_DateToLong(timeString, &fromTime);
2458         free(timeString);
2459         if (code) {
2460             com_err(whoami, 0, "Can't parse 'from' date and time");
2461             com_err(whoami, 0, "%s", ktime_GetDateUsage());
2462             return (-1);
2463         }
2464         havetime = 1;
2465     }
2466
2467     port = (as->parms[3].items ? getPortOffset(as->parms[3].items->data) : 0);  /* -port */
2468     if (as->parms[5].items)     /* -dbonly */
2469         port = -1;
2470
2471     force = (as->parms[6].items ? 1 : 0);
2472
2473     havegroupid = (as->parms[4].items ? 1 : 0);
2474     if (havegroupid)
2475         groupId = atoi(as->parms[4].items->data);
2476
2477     noexecute = (as->parms[7].items ? 1 : 0);
2478
2479     /* Get the time to delete to */
2480     if (as->parms[2].items) {   /* -to */
2481         timeString = concatParams(as->parms[2].items);
2482         if (!timeString)
2483             return (-1);
2484
2485         /*
2486          * Now parse this string for the time to start. Simce
2487          * times are at minute granularity, add 59 seconds.
2488          */
2489         code = ktime_DateToLong(timeString, &toTime);
2490         free(timeString);
2491         if (code) {
2492             com_err(whoami, 0, "Can't parse 'to' date and time");
2493             com_err(whoami, 0, "%s", ktime_GetDateUsage());
2494             return (-1);
2495         }
2496         toTime += 59;
2497         havetime = 1;
2498     }
2499
2500     if (fromTime > toTime) {
2501         com_err(whoami, 0,
2502                 "'-from' date/time cannot be later than '-to' date/time");
2503         return (-1);
2504     }
2505
2506     /* Remove speicific dump ids - if any */
2507     printf("The following dumps %s deleted:\n",
2508            (noexecute ? "would have been" : "were"));
2509     for (ti = as->parms[0].items; ti != 0; ti = ti->next) {     /* -dumpid */
2510         dumpid = atoi(ti->data);
2511         if (!noexecute) {
2512             code = deleteDump(dumpid, port, force);
2513         } else {
2514             printf("     %u\n", dumpid);
2515         }
2516     }
2517
2518     /*
2519      * Now remove dumps between to and from dates.
2520      */
2521     if (havegroupid || havetime) {
2522         dumps.budb_dumpsList_len = 0;
2523         dumps.budb_dumpsList_val = 0;
2524         flags.budb_dumpsList_len = 0;
2525         flags.budb_dumpsList_val = 0;
2526         sflags = 0;
2527         if (havegroupid)
2528             sflags |= BUDB_OP_GROUPID;
2529         if (havetime)
2530             sflags |= BUDB_OP_DATES;
2531
2532         code =
2533             bcdb_listDumps(sflags, groupId, fromTime, toTime, &dumps, &flags);
2534         if (code) {
2535             com_err(whoami, code,
2536                     "; Error while deleting dumps from %u to %u", fromTime,
2537                     toTime);
2538             rcode = -1;
2539         }
2540
2541         for (i = 0; i < dumps.budb_dumpsList_len; i++) {
2542             if (flags.budb_dumpsList_val[i] & BUDB_OP_DBDUMP)
2543                 continue;
2544
2545             if (!noexecute) {
2546                 if (flags.budb_dumpsList_val[i] & BUDB_OP_APPDUMP)
2547                     continue;
2548                 code = deleteDump(dumps.budb_dumpsList_val[i], port, force);
2549                 /* Ignore code and continue */
2550             } else {
2551                 printf("     %u%s%s\n", dumps.budb_dumpsList_val[i],
2552                        (flags.
2553                         budb_dumpsList_val[i] & BUDB_OP_APPDUMP) ?
2554                        " Appended Dump" : "",
2555                        (flags.
2556                         budb_dumpsList_val[i] & BUDB_OP_DBDUMP) ?
2557                        " Database Dump" : "");
2558
2559             }
2560         }
2561
2562         if (dumps.budb_dumpsList_val)
2563             free(dumps.budb_dumpsList_val);
2564         dumps.budb_dumpsList_len = 0;
2565         dumps.budb_dumpsList_val = 0;
2566         if (flags.budb_dumpsList_val)
2567             free(flags.budb_dumpsList_val);
2568         flags.budb_dumpsList_len = 0;
2569         flags.budb_dumpsList_val = 0;
2570     }
2571
2572     return (rcode);
2573 }
2574
2575 bc_saveDbCmd(as, arock)
2576      struct cmd_syndesc *as;
2577      char *arock;
2578 {
2579     struct rx_connection *tconn;
2580     afs_int32 portOffset = 0;
2581     statusP statusPtr;
2582     afs_uint32 taskId;
2583     afs_int32 code;
2584     afs_uint32 toTime;
2585     char *timeString;
2586
2587     code = bc_UpdateHosts();
2588     if (code) {
2589         com_err(whoami, code, "; Can't retrieve tape hosts");
2590         return (code);
2591     }
2592
2593     if (as->parms[0].items) {
2594         portOffset = getPortOffset(as->parms[0].items->data);
2595         if (portOffset < 0)
2596             return (BC_BADARG);
2597     }
2598
2599     /* Get the time to delete to */
2600     if (as->parms[1].items) {
2601         timeString = concatParams(as->parms[1].items);
2602         if (!timeString)
2603             return (-1);
2604
2605         /*
2606          * Now parse this string for the time. Since
2607          * times are at minute granularity, add 59 seconds.
2608          */
2609         code = ktime_DateToLong(timeString, &toTime);
2610         free(timeString);
2611         if (code) {
2612             com_err(whoami, 0, "Can't parse '-archive' date and time");
2613             com_err(whoami, 0, "%s", ktime_GetDateUsage());
2614             return (-1);
2615         }
2616         toTime += 59;
2617     } else
2618         toTime = 0;
2619
2620     code = ConnectButc(bc_globalConfig, portOffset, &tconn);
2621     if (code)
2622         return (code);
2623
2624     code = TC_SaveDb(tconn, toTime, &taskId);
2625     if (code) {
2626         com_err(whoami, code, "; Failed to save database");
2627         goto exit;
2628     }
2629
2630     /* create status monitor block */
2631     statusPtr = createStatusNode();
2632     lock_Status();
2633     statusPtr->taskId = taskId;
2634     statusPtr->port = portOffset;
2635     statusPtr->jobNumber = bc_jobNumber();
2636     statusPtr->flags &= ~STARTING;      /* clearstatus to examine */
2637     sprintf(statusPtr->taskName, "SaveDb");
2638     unlock_Status();
2639
2640   exit:
2641     rx_DestroyConnection(tconn);
2642     return (code);
2643 }
2644
2645 bc_restoreDbCmd(as, arock)
2646      struct cmd_syndesc *as;
2647      char *arock;
2648 {
2649     struct rx_connection *tconn;
2650     afs_int32 portOffset = 0;
2651     statusP statusPtr;
2652     afs_uint32 taskId;
2653     afs_int32 code;
2654
2655     code = bc_UpdateHosts();
2656     if (code) {
2657         com_err(whoami, code, "; Can't retrieve tape hosts");
2658         return (code);
2659     }
2660
2661     if (as->parms[0].items) {
2662         portOffset = getPortOffset(as->parms[0].items->data);
2663         if (portOffset < 0)
2664             return (BC_BADARG);
2665     }
2666
2667     code = ConnectButc(bc_globalConfig, portOffset, &tconn);
2668     if (code)
2669         return (code);
2670
2671     code = TC_RestoreDb(tconn, &taskId);
2672     if (code) {
2673         com_err(whoami, code, "; Failed to restore database");
2674         goto exit;
2675     }
2676
2677     /* create status monitor block */
2678     statusPtr = createStatusNode();
2679     lock_Status();
2680     statusPtr->taskId = taskId;
2681     statusPtr->port = portOffset;
2682     statusPtr->jobNumber = bc_jobNumber();
2683     statusPtr->flags &= ~STARTING;      /* clearstatus to examine */
2684     sprintf(statusPtr->taskName, "RestoreDb");
2685     unlock_Status();
2686
2687   exit:
2688     rx_DestroyConnection(tconn);
2689     return (code);
2690 }
2691
2692 /* ----------------------------------
2693  * supporting routines for database examination 
2694  * ----------------------------------
2695  */
2696
2697 /* structures and defines for DBLookupByVolume */
2698
2699 #define DBL_MAX_VOLUMES 20      /* max. for each dump */
2700
2701 /* dumpedVol - saves interesting information so that we can print it out
2702  *      later
2703  */
2704
2705 struct dumpedVol {
2706     struct dumpedVol *next;
2707     afs_int32 dumpID;
2708     afs_int32 initialDumpID;
2709     char tapeName[BU_MAXTAPELEN];
2710     afs_int32 level;
2711     afs_int32 parent;
2712     afs_int32 createTime;
2713     afs_int32 incTime;          /* actually the clone time */
2714 };
2715
2716 /* -----------------------------------------
2717  * routines for examining the database
2718  * -----------------------------------------
2719  */
2720
2721 /* DBLookupByVolume
2722  *      Lookup the volumename in the backup database and print the results
2723  * entry:
2724  *      volumeName - volume to lookup
2725  */
2726
2727 DBLookupByVolume(volumeName)
2728      char *volumeName;
2729 {
2730     struct budb_dumpEntry dumpEntry;
2731     struct budb_volumeEntry volumeEntry[DBL_MAX_VOLUMES];
2732     afs_int32 numEntries;
2733     afs_int32 tapedumpid;
2734     afs_int32 last, next;
2735
2736     struct dumpedVol *dvptr = 0;
2737     struct dumpedVol *tempPtr = 0;
2738     afs_int32 code = 0;
2739     int i, pass;
2740     char vname[BU_MAXNAMELEN];
2741     char ds[50];
2742
2743     for (pass = 0; pass < 2; pass++) {
2744         /*p */
2745         /* On second pass, search for backup volume */
2746         if (pass == 1) {
2747             if (!BackupName(volumeName)) {
2748                 strcpy(vname, volumeName);
2749                 strcat(vname, ".backup");
2750                 volumeName = vname;
2751             } else {
2752                 continue;
2753             }
2754         }
2755
2756         last = next = 0;
2757         while (next != -1) {    /*w */
2758             code =
2759                 bcdb_LookupVolume(volumeName, &volumeEntry[0], last, &next,
2760                                   DBL_MAX_VOLUMES, &numEntries);
2761             if (code)
2762                 break;
2763
2764             /* add the volumes to the list */
2765             for (i = 0; i < numEntries; i++) {  /*f */
2766                 struct dumpedVol *insPtr, **prevPtr;
2767
2768                 tempPtr =
2769                     (struct dumpedVol *)malloc(sizeof(struct dumpedVol));
2770                 if (!tempPtr)
2771                     ERROR(BC_NOMEM);
2772
2773                 memset(tempPtr, 0, sizeof(*tempPtr));
2774                 tempPtr->incTime = volumeEntry[i].clone;
2775                 tempPtr->dumpID = volumeEntry[i].dump;
2776                 strncpy(tempPtr->tapeName, volumeEntry[i].tape,
2777                         BU_MAXTAPELEN);
2778
2779                 /* check if we need to null terminate it - just for safety */
2780                 if (strlen(volumeEntry[i].tape) >= BU_MAXTAPELEN)
2781                     tempPtr->tapeName[BU_MAXTAPELEN - 1] = 0;
2782
2783                 code = bcdb_FindDumpByID(tempPtr->dumpID, &dumpEntry);
2784                 if (code) {
2785                     free(tempPtr);
2786                     ERROR(code);
2787                 }
2788
2789                 tempPtr->initialDumpID = dumpEntry.initialDumpID;
2790                 tempPtr->parent = dumpEntry.parent;
2791                 tempPtr->level = dumpEntry.level;
2792                 tempPtr->createTime = dumpEntry.created;
2793
2794                 /* add volume to list in reverse chronological order */
2795                 prevPtr = &dvptr;
2796                 insPtr = dvptr;
2797
2798                 while ((insPtr != 0)
2799                        && (insPtr->createTime > tempPtr->createTime)
2800                     ) {
2801                     prevPtr = &insPtr->next;
2802                     insPtr = insPtr->next;
2803                 }
2804
2805                 /* now at the right place - insert the block */
2806                 tempPtr->next = *prevPtr;
2807                 *prevPtr = tempPtr;
2808             }                   /*f */
2809
2810             last = next;
2811         }                       /*w */
2812     }                           /*p */
2813
2814     if (dvptr) {
2815         printf
2816             ("DumpID    lvl parentID creation date     clone date       tape name\n");
2817         for (tempPtr = dvptr; tempPtr; tempPtr = tempPtr->next) {
2818             /* For the user, the tape name is its name and initial dump id */
2819             tapedumpid =
2820                 (tempPtr->initialDumpID ? tempPtr->initialDumpID : tempPtr->
2821                  dumpID);
2822
2823             /* beware the static items in compactDateString */
2824             compactDateString(&tempPtr->createTime, ds, 50);
2825             printf("%-9d %-2d %-9d %16s", tempPtr->dumpID, tempPtr->level,
2826                    tempPtr->parent, ds);
2827             compactDateString(&tempPtr->incTime, ds, 50);
2828             printf("  %16s %s (%u)\n", ds, tempPtr->tapeName, tapedumpid);
2829         }
2830         code = 0;
2831     }
2832
2833   error_exit:
2834     for (tempPtr = dvptr; tempPtr; tempPtr = dvptr) {
2835         dvptr = dvptr->next;
2836         free(tempPtr);
2837     }
2838
2839     if (code)
2840         com_err(whoami, code, "");
2841     return (code);
2842 }
2843
2844 /* structures for dumpInfo */
2845
2846 struct volumeLink {
2847     struct volumeLink *nextVolume;
2848     struct budb_volumeEntry volumeEntry;
2849 };
2850
2851 struct tapeLink {
2852     struct tapeLink *nextTape;
2853     struct budb_tapeEntry tapeEntry;
2854     struct volumeLink *firstVolume;
2855 };
2856
2857
2858 /* dumpInfo
2859  *      print information about a dump and all its tapes and volumes.
2860  */
2861
2862 afs_int32
2863 dumpInfo(dumpid, detailFlag)
2864      afs_int32 dumpid;
2865      afs_int32 detailFlag;
2866 {
2867     struct budb_dumpEntry dumpEntry;
2868     struct tapeLink *head = 0;
2869     struct tapeLink *tapeLinkPtr, *lastTapeLinkPtr;
2870     struct volumeLink **link, *volumeLinkPtr, *lastVolumeLinkPtr;
2871
2872     budb_volumeList vl;
2873     afs_int32 last, next, dbTime;
2874     afs_int32 tapedumpid;
2875
2876     int tapeNumber;
2877     int i;
2878     int dbDump;
2879     afs_int32 code = 0;
2880     char ds[50];
2881
2882     extern struct udbHandleS udbHandle;
2883
2884     tapeLinkPtr = 0;
2885     lastTapeLinkPtr = 0;
2886     volumeLinkPtr = 0;
2887     lastVolumeLinkPtr = 0;
2888
2889     /* first get information about the dump */
2890
2891     code = bcdb_FindDumpByID(dumpid, &dumpEntry);
2892     if (code)
2893         ERROR(code);
2894
2895     /* For the user, the tape name is its name and initial dump id */
2896     tapedumpid = (dumpEntry.initialDumpID ? dumpEntry.initialDumpID : dumpid);
2897
2898     /* Is this a database dump id or not */
2899     if (strcmp(dumpEntry.name, DUMP_TAPE_NAME) == 0)
2900         dbDump = 1;
2901     else
2902         dbDump = 0;
2903
2904     /* print out the information about the dump */
2905     if (detailFlag) {
2906         printf("\nDump\n");
2907         printf("----\n");
2908         printDumpEntry(&dumpEntry);
2909     } else {
2910         if (dbDump)
2911             printf("Dump: id %u, created: %s\n", dumpEntry.id,
2912                    ctime(&dumpEntry.created));
2913         else
2914             printf("Dump: id %u, level %d, volumes %d, created: %s\n",
2915                    dumpEntry.id, dumpEntry.level, dumpEntry.nVolumes,
2916                    ctime(&dumpEntry.created));
2917     }
2918
2919     if (!detailFlag && (strlen(dumpEntry.tapes.tapeServer) > 0)
2920         && (dumpEntry.flags & (BUDB_DUMP_ADSM | BUDB_DUMP_BUTA))) {
2921         printf("Backup Service: TSM: Server: %s\n",
2922                dumpEntry.tapes.tapeServer);
2923     }
2924
2925     /* now get the list of tapes */
2926     for (tapeNumber = dumpEntry.tapes.b; tapeNumber <= dumpEntry.tapes.maxTapes; tapeNumber++) {        /*f */
2927         tapeLinkPtr = (struct tapeLink *)malloc(sizeof(struct tapeLink));
2928         if (!tapeLinkPtr) {
2929             com_err(whoami, BC_NOMEM, "");
2930             ERROR(BC_NOMEM);
2931         }
2932
2933         memset(tapeLinkPtr, 0, sizeof(*tapeLinkPtr));
2934         code = bcdb_FindTapeSeq(dumpid, tapeNumber, &tapeLinkPtr->tapeEntry);
2935         if (code) {
2936             code = 0;
2937             free(tapeLinkPtr);
2938             continue;
2939         }
2940
2941         /* add this tape to  previous chain */
2942         if (lastTapeLinkPtr) {
2943             lastTapeLinkPtr->nextTape = tapeLinkPtr;
2944             lastTapeLinkPtr = tapeLinkPtr;
2945         }
2946
2947         if (head == 0) {
2948             head = tapeLinkPtr;
2949             lastTapeLinkPtr = head;
2950         }
2951
2952         next = 0;
2953         while (next != -1) {    /*wn */
2954             vl.budb_volumeList_len = 0;
2955             vl.budb_volumeList_val = 0;
2956             last = next;
2957
2958             /* now get all the volumes in this dump. */
2959             code = ubik_Call_SingleServer(BUDB_GetVolumes, udbHandle.uh_client, UF_SINGLESERVER, BUDB_MAJORVERSION, BUDB_OP_DUMPID | BUDB_OP_TAPENAME, tapeLinkPtr->tapeEntry.name,     /* tape name */
2960                                           dumpid,       /* dumpid (not initial dumpid) */
2961                                           0,    /* end */
2962                                           last, /* last */
2963                                           &next,        /* nextindex */
2964                                           &dbTime,      /* update time */
2965                                           &vl);
2966
2967             if (code) {
2968                 if (code == BUDB_ENDOFLIST) {   /* 0 volumes on tape */
2969                     code = 0;
2970                     break;
2971                 }
2972                 ERROR(code);
2973             }
2974
2975             for (i = 0; i < vl.budb_volumeList_len; i++) {
2976                 link = &tapeLinkPtr->firstVolume;
2977
2978                 volumeLinkPtr =
2979                     (struct volumeLink *)malloc(sizeof(struct volumeLink));
2980                 if (!volumeLinkPtr) {
2981                     com_err(whoami, BC_NOMEM, "");
2982                     ERROR(BC_NOMEM);
2983                 }
2984                 memset(volumeLinkPtr, 0, sizeof(*volumeLinkPtr));
2985
2986                 memcpy(&volumeLinkPtr->volumeEntry,
2987                        &vl.budb_volumeList_val[i],
2988                        sizeof(struct budb_volumeEntry));
2989
2990                 /* now insert it onto the right place */
2991                 while ((*link != 0)
2992                        && (volumeLinkPtr->volumeEntry.position >
2993                            (*link)->volumeEntry.position)) {
2994                     link = &((*link)->nextVolume);
2995                 }
2996
2997                 /* now link it in */
2998                 volumeLinkPtr->nextVolume = *link;
2999                 *link = volumeLinkPtr;
3000             }
3001
3002             if (vl.budb_volumeList_val)
3003                 free(vl.budb_volumeList_val);
3004         }                       /*wn */
3005     }                           /*f */
3006
3007     for (tapeLinkPtr = head; tapeLinkPtr; tapeLinkPtr = tapeLinkPtr->nextTape) {
3008         if (detailFlag) {
3009             printf("\nTape\n");
3010             printf("----\n");
3011             printTapeEntry(&tapeLinkPtr->tapeEntry);
3012         } else {
3013             printf("Tape: name %s (%u)\n", tapeLinkPtr->tapeEntry.name,
3014                    tapedumpid);
3015             printf("nVolumes %d, ", tapeLinkPtr->tapeEntry.nVolumes);
3016             compactDateString(&tapeLinkPtr->tapeEntry.written, ds, 50);
3017             printf("created %16s", ds);
3018             if (tapeLinkPtr->tapeEntry.expires != 0) {
3019                 compactDateString(&tapeLinkPtr->tapeEntry.expires, ds, 50);
3020                 printf(", expires %16s", ds);
3021             }
3022             printf("\n\n");
3023         }
3024
3025         /* print out all the volumes */
3026
3027         /* print header for volume listing - db dumps have no volumes */
3028         if ((detailFlag == 0) && !dbDump)
3029             printf("%4s %16s %9s %-s\n", "Pos", "Clone time", "Nbytes",
3030                    "Volume");
3031
3032         for (volumeLinkPtr = tapeLinkPtr->firstVolume; volumeLinkPtr;
3033              volumeLinkPtr = volumeLinkPtr->nextVolume) {
3034             if (detailFlag) {
3035                 printf("\nVolume\n");
3036                 printf("------\n");
3037                 printVolumeEntry(&volumeLinkPtr->volumeEntry);
3038             } else {
3039                 compactDateString(&volumeLinkPtr->volumeEntry.clone, ds, 50),
3040                     printf("%4d %s %10u %16s\n",
3041                            volumeLinkPtr->volumeEntry.position, ds,
3042                            volumeLinkPtr->volumeEntry.nBytes,
3043                            volumeLinkPtr->volumeEntry.name);
3044             }
3045         }
3046     }
3047
3048   error_exit:
3049     if (code)
3050         com_err("dumpInfo", code, "; Can't get dump information");
3051
3052     /* free all allocated structures */
3053     tapeLinkPtr = head;
3054     while (tapeLinkPtr) {
3055         volumeLinkPtr = tapeLinkPtr->firstVolume;
3056         while (volumeLinkPtr) {
3057             lastVolumeLinkPtr = volumeLinkPtr;
3058             volumeLinkPtr = volumeLinkPtr->nextVolume;
3059             free(lastVolumeLinkPtr);
3060         }
3061
3062         lastTapeLinkPtr = tapeLinkPtr;
3063         tapeLinkPtr = tapeLinkPtr->nextTape;
3064         free(lastTapeLinkPtr);
3065     }
3066     return (code);
3067 }
3068
3069 compareDump(ptr1, ptr2)
3070      struct budb_dumpEntry *ptr1, *ptr2;
3071 {
3072     if (ptr1->created < ptr2->created)
3073         return (-1);
3074     else if (ptr1->created > ptr2->created)
3075         return (1);
3076     return (0);
3077 }
3078
3079 afs_int32
3080 printRecentDumps(ndumps)
3081      int ndumps;
3082 {
3083     afs_int32 code = 0;
3084     afs_int32 nextindex, index = 0;
3085     afs_int32 dbTime;
3086     budb_dumpList dl;
3087     struct budb_dumpEntry *dumpPtr;
3088     int i;
3089     char ds[50];
3090
3091     extern struct udbHandleS udbHandle;
3092     extern compareDump();
3093
3094     do {                        /* while (nextindex != -1) */
3095         /* initialize the dump list */
3096         dl.budb_dumpList_len = 0;
3097         dl.budb_dumpList_val = 0;
3098
3099         /* outline algorithm */
3100         code = ubik_Call(BUDB_GetDumps, udbHandle.uh_client, 0, BUDB_MAJORVERSION, BUDB_OP_NPREVIOUS, "",       /* no name */
3101                          0,     /* start */
3102                          ndumps,        /* end */
3103                          index, /* index */
3104                          &nextindex, &dbTime, &dl);
3105         if (code) {
3106             if (code == BUDB_ENDOFLIST)
3107                 return 0;
3108             com_err("dumpInfo", code, "; Can't get dump information");
3109             return (code);
3110         }
3111
3112         /* No need to sort, it's already sorted */
3113
3114         if (dl.budb_dumpList_len && (index == 0))
3115             printf("%10s %10s %2s %-16s %2s %5s dump name\n", "dumpid",
3116                    "parentid", "lv", "created", "nt", "nvols");
3117
3118         dumpPtr = dl.budb_dumpList_val;
3119         for (i = 1; i <= dl.budb_dumpList_len; i++) {
3120             compactDateString(&dumpPtr->created, ds, 50),
3121                 printf("%10u %10u %-2d %16s %2d %5d %s", dumpPtr->id,
3122                        dumpPtr->parent, dumpPtr->level, ds,
3123                        dumpPtr->tapes.maxTapes - dumpPtr->tapes.b + 1,
3124                        dumpPtr->nVolumes, dumpPtr->name);
3125             if (dumpPtr->initialDumpID) /* an appended dump */
3126                 printf(" (%u)", dumpPtr->initialDumpID);
3127             else if (dumpPtr->appendedDumpID)   /* has appended dumps */
3128                 printf(" (%u)", dumpPtr->id);
3129             printf("\n");
3130
3131             dumpPtr++;
3132         }
3133
3134         if (dl.budb_dumpList_val)
3135             free(dl.budb_dumpList_val);
3136         index = nextindex;
3137     } while (nextindex != -1);
3138
3139     return (code);
3140 }
3141
3142 /* bc_dumpInfoCmd 
3143  *      list the dumps and contens of the dumps.
3144  * params:
3145  *      as - name of tape
3146  *      arock -
3147  */
3148 bc_dumpInfoCmd(as, arock)
3149      struct cmd_syndesc *as;
3150      char *arock;
3151 {
3152     afs_int32 dumpid;
3153     afs_int32 detailFlag;
3154     afs_int32 ndumps;
3155     afs_int32 code = 0;
3156
3157     afs_int32 dumpInfo();
3158
3159     if (as->parms[0].items) {
3160         if (as->parms[1].items) {
3161             com_err(whoami, 0,
3162                     "These options are exclusive - select only one");
3163             return (BC_BADARG);
3164         }
3165         ndumps = atoi(as->parms[0].items->data);
3166         if (ndumps <= 0) {
3167             com_err(whoami, 0, "Must provide a positive number");
3168             return -1;
3169         }
3170
3171         code = printRecentDumps(ndumps);
3172     } else if (as->parms[1].items) {
3173         detailFlag = (as->parms[2].items ? 1 : 0);      /* 1 = detailed listing */
3174         dumpid = atoi(as->parms[1].items->data);
3175         code = dumpInfo(dumpid, detailFlag);
3176     } else {
3177         code = printRecentDumps(10);
3178     }
3179
3180     return (code);
3181 }