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