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