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