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