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