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