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