volinfo: refactor vnode handling
[openafs.git] / src / vol / vol-info.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 /*
11    System:              VICE-TWO
12    Module:              vol-info.c
13    Institution: The Information Technology Center, Carnegie-Mellon University
14
15    */
16
17 #include <afsconfig.h>
18 #include <afs/param.h>
19
20 #include <roken.h>
21
22 #include <ctype.h>
23
24 #ifdef HAVE_SYS_FILE_H
25 #include <sys/file.h>
26 #endif
27
28 #include <afs/cmd.h>
29 #include <afs/dir.h>
30 #include <afs/afsint.h>
31 #include <afs/errors.h>
32 #include <opr/queue.h>
33
34 #include "nfs.h"
35 #include "lock.h"
36 #include "ihandle.h"
37 #include "vnode.h"
38 #include "volume.h"
39 #include "partition.h"
40
41 #ifndef AFS_NT40_ENV
42 #include "AFS_component_version_number.c"
43 #endif
44
45 static const char *progname = "volinfo";
46
47 /* Command line options */
48 typedef enum {
49     P_ONLINE,
50     P_VNODE,
51     P_DATE,
52     P_INODE,
53     P_ITIME,
54     P_PART,
55     P_VOLUMEID,
56     P_HEADER,
57     P_SIZEONLY,
58     P_FIXHEADER,
59     P_SAVEINODES,
60     P_ORPHANED,
61     P_FILENAMES
62 } volinfo_parm_t;
63
64 /* Vnode details for vnode handling procedures */
65 struct VnodeDetails {
66     Volume *vp;
67     VnodeClass class;
68     VnodeDiskObject *vnode;
69     VnodeId vnodeNumber;
70     afs_foff_t offset;
71     int index;
72 };
73
74 /* Modes */
75 static int DumpInfo = 0;            /**< Dump volume information */
76 static int DumpHeader = 0;          /**< Dump volume header files info */
77 static int DumpVnodes = 0;          /**< Dump vnode info */
78 static int DumpInodeNumber = 0;     /**< Dump inode numbers with vnodes */
79 static int DumpDate = 0;            /**< Dump vnode date (server modify date) with vnode */
80 static int InodeTimes = 0;          /**< Dump some of the dates associated with inodes */
81 #if defined(AFS_NAMEI_ENV)
82 static int PrintFileNames = 0;      /**< Dump vnode and special file name filenames */
83 #endif
84 static int ShowOrphaned = 0;        /**< Show "orphaned" vnodes (vnodes with parent of 0) */
85 static int ShowSizes = 0;           /**< Show volume size summary */
86 static int SaveInodes = 0;          /**< Save vnode data to files */
87 static int FixHeader = 0;           /**< Repair header files magic and version fields. */
88
89 /**
90  * Volume size running totals
91  */
92 struct sizeTotals {
93     afs_uint64 diskused_k;      /**< volume size from disk data file, in kilobytes */
94     afs_uint64 auxsize;         /**< size of header files, in bytes  */
95     afs_uint64 auxsize_k;       /**< size of header files, in kilobytes */
96     afs_uint64 vnodesize;       /**< size of the large and small vnodes, in bytes */
97     afs_uint64 vnodesize_k;     /**< size of the large and small vnodes, in kilobytes */
98     afs_uint64 size_k;          /**< size of headers and vnodes, in kilobytes */
99 };
100
101 static struct sizeTotals volumeTotals = { 0, 0, 0, 0, 0, 0 };
102 static struct sizeTotals partitionTotals = { 0, 0, 0, 0, 0, 0 };
103 static struct sizeTotals serverTotals = { 0, 0, 0, 0, 0, 0 };
104 static int PrintingVolumeSizes = 0;     /*print volume size lines */
105
106 /**
107  * List of procedures to call when scanning vnodes.
108  */
109 struct VnodeScanProc {
110     struct opr_queue link;
111     const char *heading;
112     void (*proc) (struct VnodeDetails * vdp);
113 };
114 static struct opr_queue VnodeScanLists[nVNODECLASSES];
115
116 /* Forward Declarations */
117 void PrintHeader(Volume * vp);
118 void HandleAllPart(void);
119 void HandlePart(struct DiskPartition64 *partP);
120 void HandleVolume(struct DiskPartition64 *partP, char *name);
121 struct DiskPartition64 *FindCurrentPartition(void);
122 Volume *AttachVolume(struct DiskPartition64 *dp, char *volname,
123                      struct VolumeHeader *header);
124 void HandleVnodes(Volume * vp, VnodeClass class);
125 static void AddVnodeToSizeTotals(struct VnodeDetails *vdp);
126 static void SaveInode(struct VnodeDetails *vdp);
127 static void PrintVnode(struct VnodeDetails *vdp);
128
129 /**
130  * Format time as a timestamp string
131  *
132  * @param[in] date  time value to format
133  *
134  * @return timestamp string in the form YYYY/MM/DD.hh:mm:ss
135  *
136  * @note A static array of 8 strings are stored by this
137  *       function. The array slots are overwritten, so the
138  *       caller must not reference the returned string after
139  *       seven additional calls to this function.
140  */
141 char *
142 date(time_t date)
143 {
144 #define MAX_DATE_RESULT 100
145     static char results[8][MAX_DATE_RESULT];
146     static int next;
147     struct tm *tm = localtime(&date);
148     char buf[32];
149
150     (void)strftime(buf, 32, "%Y/%m/%d.%H:%M:%S", tm);   /* NT does not have %T */
151     snprintf(results[next = (next + 1) & 7], MAX_DATE_RESULT,
152              "%lu (%s)", (unsigned long)date, buf);
153     return results[next];
154 }
155
156 #ifdef AFS_NT40_ENV
157 /**
158  * Format file time as a timestamp string
159  *
160  * @param[in] ft  file time
161  *
162  * @return timestamp string in the form YYYY/MM/DD.hh:mm:ss
163  *
164  * @note A static array of 8 strings are stored by this
165  *       function. The array slots are overwritten, so the
166  *       caller must not reference the returned string after
167  *       seven additional calls to this function.
168  */
169 char *
170 NT_date(FILETIME * ft)
171 {
172     static char result[8][64];
173     static int next = 0;
174     SYSTEMTIME st;
175     FILETIME lft;
176
177     if (!FileTimeToLocalFileTime(ft, &lft)
178         || !FileTimeToSystemTime(&lft, &st)) {
179         fprintf(stderr, "%s: Time conversion failed.\n", progname);
180         exit(1);
181     }
182     sprintf(result[next = ((next + 1) & 7)], "%4d/%02d/%02d.%2d:%2d:%2d",
183             st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
184     return result[next];
185 }
186 #endif
187
188 /**
189  * Add vnode size to the running volume totals.
190  *
191  * @param[in]  vdp   vnode details object
192  *
193  * @return none
194  */
195 static void
196 AddVnodeToSizeTotals(struct VnodeDetails *vdp)
197 {
198     afs_fsize_t fileLength;
199
200     VNDISK_GET_LEN(fileLength, vdp->vnode);
201     if (fileLength > 0) {
202         volumeTotals.vnodesize += fileLength;
203         volumeTotals.vnodesize_k += fileLength / 1024;
204     }
205 }
206
207 /**
208  * Print the volume size table heading line, if needed.
209  *
210  * @return none
211  */
212 static void
213 PrintVolumeSizeHeading(void)
214 {
215     if (!PrintingVolumeSizes) {
216         printf
217             ("Volume-Id\t  Volsize  Auxsize Inodesize  AVolsize SizeDiff                (VolName)\n");
218         PrintingVolumeSizes = 1;
219     }
220 }
221
222 /**
223  * Accumulate totals.
224  *
225  * @return 0
226  */
227 static void
228 AddSizeTotals(struct sizeTotals *a, struct sizeTotals *b)
229 {
230     a->diskused_k += b->diskused_k;
231     a->auxsize += b->auxsize;
232     a->auxsize_k += b->auxsize_k;
233     a->vnodesize += b->vnodesize;
234     a->vnodesize_k += b->vnodesize_k;
235     a->size_k += b->size_k;
236 }
237
238 /**
239  * Print the sizes for a volume.
240  *
241  * @return none
242  */
243 static void
244 PrintVolumeSizes(Volume * vp)
245 {
246     afs_int64 diff_k = volumeTotals.size_k - volumeTotals.diskused_k;
247
248     PrintVolumeSizeHeading();
249     printf("%u\t%9llu%9llu%10llu%10llu%9lld\t%24s\n",
250            V_id(vp),
251            volumeTotals.diskused_k,
252            volumeTotals.auxsize_k, volumeTotals.vnodesize_k,
253            volumeTotals.size_k, diff_k, V_name(vp));
254 }
255
256 /**
257  * Print the size totals for the partition.
258  *
259  * @return none
260  */
261 static void
262 PrintPartitionTotals(afs_uint64 nvols)
263 {
264     afs_int64 diff_k = partitionTotals.size_k - partitionTotals.diskused_k;
265
266     PrintVolumeSizeHeading();
267     printf("\nPart Totals  %12llu%9llu%10llu%10llu%9lld (%llu volumes)\n\n",
268            partitionTotals.diskused_k, partitionTotals.auxsize,
269            partitionTotals.vnodesize, partitionTotals.size_k, diff_k, nvols);
270     PrintingVolumeSizes = 0;
271 }
272
273 /**
274  * Print the size totals for all the partitions.
275  *
276  * @return none
277  */
278 static void
279 PrintServerTotals(void)
280 {
281     afs_int64 diff_k = serverTotals.size_k - serverTotals.diskused_k;
282
283     printf("\nServer Totals%12lld%9lld%10lld%10lld%9lld\n",
284            serverTotals.diskused_k, serverTotals.auxsize,
285            serverTotals.vnodesize, serverTotals.size_k, diff_k);
286 }
287
288 /**
289  * Read a volume header file
290  *
291  * @param[in] ih        ihandle of the header file
292  * @param[in] to        destination
293  * @param[in] size      expected header size
294  * @param[in] magic     expected header magic number
295  * @param[in] version   expected header version number
296  *
297  * @return error code
298  *   @retval 0 success
299  *   @retval -1 failed to read file
300  */
301 int
302 ReadHdr1(IHandle_t * ih, char *to, int size, u_int magic, u_int version)
303 {
304     struct versionStamp *vsn;
305     int bad = 0;
306     int code;
307
308     vsn = (struct versionStamp *)to;
309
310     code = IH_IREAD(ih, 0, to, size);
311     if (code != size)
312         return -1;
313
314     if (vsn->magic != magic) {
315         bad++;
316         fprintf(stderr, "%s: Inode %s: Bad magic %x (%x): IGNORED\n",
317                 progname, PrintInode(NULL, ih->ih_ino), vsn->magic, magic);
318     }
319
320     /* Check is conditional, in case caller wants to inspect version himself */
321     if (version && vsn->version != version) {
322         bad++;
323         fprintf(stderr, "%s: Inode %s: Bad version %x (%x): IGNORED\n",
324                 progname,
325                 PrintInode(NULL, ih->ih_ino), vsn->version, version);
326     }
327     if (bad && FixHeader) {
328         vsn->magic = magic;
329         vsn->version = version;
330         printf("Special index inode %s has a bad header. Reconstructing...\n",
331                PrintInode(NULL, ih->ih_ino));
332         code = IH_IWRITE(ih, 0, to, size);
333         if (code != size) {
334             fprintf(stderr,
335                     "%s: Write failed for inode %s; header left in damaged state\n",
336                     progname, PrintInode(NULL, ih->ih_ino));
337         }
338     }
339     if (!bad && DumpInfo) {
340         printf("Inode %s: Good magic %x and version %x\n",
341                PrintInode(NULL, ih->ih_ino), magic, version);
342     }
343     return 0;
344 }
345
346 /**
347  * Simplified attach volume
348  *
349  * param[in] dp       vice disk partition object
350  * param[in] volname  volume header file name
351  * param[in] header   volume header object
352  *
353  * @return volume object or null on error
354  */
355 Volume *
356 AttachVolume(struct DiskPartition64 * dp, char *volname,
357              struct VolumeHeader * header)
358 {
359     Volume *vp;
360     afs_int32 ec = 0;
361
362     vp = (Volume *) calloc(1, sizeof(Volume));
363     if (!vp) {
364         fprintf(stderr, "%s: Failed to allocate volume object.\n", progname);
365         return NULL;
366     }
367     vp->specialStatus = 0;
368     vp->device = dp->device;
369     vp->partition = dp;
370     IH_INIT(vp->vnodeIndex[vLarge].handle, dp->device, header->parent,
371             header->largeVnodeIndex);
372     IH_INIT(vp->vnodeIndex[vSmall].handle, dp->device, header->parent,
373             header->smallVnodeIndex);
374     IH_INIT(vp->diskDataHandle, dp->device, header->parent,
375             header->volumeInfo);
376     IH_INIT(V_linkHandle(vp), dp->device, header->parent, header->linkTable);
377     vp->cacheCheck = 0;         /* XXXX */
378     vp->shuttingDown = 0;
379     vp->goingOffline = 0;
380     vp->nUsers = 1;
381     vp->header = (struct volHeader *)calloc(1, sizeof(*vp->header));
382     if (!vp->header) {
383         fprintf(stderr, "%s: Failed to allocate volume header.\n", progname);
384         free(vp);
385         return NULL;
386     }
387     ec = ReadHdr1(V_diskDataHandle(vp), (char *)&V_disk(vp),
388                   sizeof(V_disk(vp)), VOLUMEINFOMAGIC, VOLUMEINFOVERSION);
389     if (!ec) {
390         struct IndexFileHeader iHead;
391         ec = ReadHdr1(vp->vnodeIndex[vSmall].handle, (char *)&iHead,
392                       sizeof(iHead), SMALLINDEXMAGIC, SMALLINDEXVERSION);
393     }
394     if (!ec) {
395         struct IndexFileHeader iHead;
396         ec = ReadHdr1(vp->vnodeIndex[vLarge].handle, (char *)&iHead,
397                       sizeof(iHead), LARGEINDEXMAGIC, LARGEINDEXVERSION);
398     }
399 #ifdef AFS_NAMEI_ENV
400     if (!ec) {
401         struct versionStamp stamp;
402         ec = ReadHdr1(V_linkHandle(vp), (char *)&stamp, sizeof(stamp),
403                       LINKTABLEMAGIC, LINKTABLEVERSION);
404     }
405 #endif
406     if (ec)
407         return (Volume *) 0;
408     return vp;
409 }
410
411 /**
412  * Simplified detach volume
413  *
414  * param[in] vp       volume object from AttachVolume
415  *
416  * @return none
417  */
418 static void
419 DetachVolume(Volume * vp)
420 {
421     IH_RELEASE(vp->vnodeIndex[vLarge].handle);
422     IH_RELEASE(vp->vnodeIndex[vSmall].handle);
423     IH_RELEASE(vp->diskDataHandle);
424     IH_RELEASE(V_linkHandle(vp));
425     free(vp->header);
426     free(vp);
427 }
428
429 /**
430  * Convert the partition device number into a partition name.
431  *
432  * @param[in]   partId      partition number, 0 to 254
433  * @param[out]  partName    buffer to hold partition name (e.g. /vicepa)
434  * @param[in]   partNameSize    size of partName buffer
435  *
436  * @return status
437  *   @retval 0 success
438  *   @retval -1 error, partId is out of range
439  *   @retval -2 error, partition name exceeds partNameSize
440  */
441 static int
442 GetPartitionName(afs_uint32 partId, char *partName, afs_size_t partNameSize)
443 {
444     const int OLD_NUM_DEVICES = 26;     /* a..z */
445
446     if (partId < OLD_NUM_DEVICES) {
447         if (partNameSize < 8) {
448             return -2;
449         }
450         strlcpy(partName, "/vicep", partNameSize);
451         partName[6] = partId + 'a';
452         partName[7] = '\0';
453         return 0;
454     }
455     if (partId < VOLMAXPARTS) {
456         if (partNameSize < 9) {
457             return -2;
458         }
459         strlcpy(partName, "/vicep", partNameSize);
460         partId -= OLD_NUM_DEVICES;
461         partName[6] = 'a' + (partId / OLD_NUM_DEVICES);
462         partName[7] = 'a' + (partId % OLD_NUM_DEVICES);
463         partName[8] = '\0';
464         return 0;
465     }
466     return -1;
467 }
468
469 /**
470  * Scan the volumes in the partitions
471  *
472  * Scan the specified volume in the specified partition if both
473  * are given. Scan all the volumes in the specified partition if
474  * only the partition is given.  If neither a partition nor volume
475  * is given, scan all the volumes in all the partitions.  If only
476  * the volume is given, attempt to find it in the current working
477  * directory.
478  *
479  * @param[in] partNameOrId   partition name or id to be scannned
480  * @param[in] volumeId       volume id to be scanned
481  *
482  * @return 0 if successful
483  */
484 static int
485 ScanPartitions(char *partNameOrId, afs_uint32 volumeId)
486 {
487     int err = 0;
488     char partName[64] = "";
489     struct DiskPartition64 *partP = NULL;
490
491 #ifndef AFS_NT40_ENV
492     if (geteuid() != 0) {
493         fprintf(stderr, "%s: Must be run as root; sorry\n", progname);
494         return 1;
495     }
496 #endif
497
498     DInit(10);
499
500     /* Allow user to specify partition by name or id. */
501     if (partNameOrId) {
502         afs_uint32 partId = volutil_GetPartitionID(partNameOrId);
503         if (partId == -1) {
504             fprintf(stderr, "%s: Could not parse '%s' as a partition name.\n",
505                     progname, partNameOrId);
506             return 1;
507         }
508         if (GetPartitionName(partId, partName, sizeof(partName))) {
509             fprintf(stderr,
510                     "%s: Could not format '%s' as a partition name.\n",
511                     progname, partNameOrId);
512             return 1;
513         }
514     }
515
516     err = VAttachPartitions();
517     if (err) {
518         fprintf(stderr, "%s: %d partitions had errors during attach.\n",
519                 progname, err);
520         return 1;
521     }
522
523     if (partName[0]) {
524         partP = VGetPartition(partName, 0);
525         if (!partP) {
526             fprintf(stderr,
527                     "%s: %s is not an AFS partition name on this server.\n",
528                     progname, partName);
529             return 1;
530         }
531     }
532
533     if (!volumeId) {
534         if (!partP) {
535             HandleAllPart();
536         } else {
537             HandlePart(partP);
538         }
539     } else {
540         char name1[128];
541
542         if (!partP) {
543             partP = FindCurrentPartition();
544             if (!partP) {
545                 fprintf(stderr,
546                         "%s: Current partition is not a vice partition.\n",
547                         progname);
548                 return 1;
549             }
550         }
551         snprintf(name1, sizeof name1, VFORMAT,
552                  afs_printable_uint32_lu(volumeId));
553         HandleVolume(partP, name1);
554     }
555
556     return 0;
557 }
558
559 /**
560  * Add a vnode scanning procedure
561  *
562  * @param[in]   class   vnode class for this handler
563  * @param[in]   proc    handler proc to be called by HandleVnodes
564  * @param[in]   heading optional string to pring before scanning vnodes
565  *
566  * @return none
567  */
568 static void
569 AddVnodeHandler(VnodeClass class, void (*proc) (struct VnodeDetails * vdp),
570                 const char *heading)
571 {
572     struct VnodeScanProc *entry = malloc(sizeof(struct VnodeScanProc));
573     entry->proc = proc;
574     entry->heading = heading;
575     opr_queue_Append(&VnodeScanLists[class], (struct opr_queue *)entry);
576 }
577
578 /**
579  * Process command line options and start scanning
580  *
581  * @param[in] as     command syntax object
582  * @param[in] arock  opaque object; not used
583  *
584  * @return error code
585  */
586 static int
587 handleit(struct cmd_syndesc *as, void *arock)
588 {
589     struct cmd_item *ti;
590     afs_uint32 volumeId = 0;
591     char *partNameOrId = NULL;
592
593     DumpInfo = 1;               /* volinfo default mode */
594
595     if (as->parms[P_ONLINE].items) {
596         fprintf(stderr, "%s: -online not supported\n", progname);
597         return 1;
598     }
599     if (as->parms[P_VNODE].items) {
600         DumpVnodes = 1;
601     }
602     if (as->parms[P_DATE].items) {
603         DumpDate = 1;
604     }
605     if (as->parms[P_INODE].items) {
606         DumpInodeNumber = 1;
607     }
608     if (as->parms[P_ITIME].items) {
609         InodeTimes = 1;
610     }
611     if ((ti = as->parms[P_PART].items)) {
612         partNameOrId = ti->data;
613     }
614     if ((ti = as->parms[P_VOLUMEID].items)) {
615         volumeId = strtoul(ti->data, NULL, 10);
616     }
617     if (as->parms[P_HEADER].items) {
618         DumpHeader = 1;
619     }
620     if (as->parms[P_SIZEONLY].items) {
621         ShowSizes = 1;
622     }
623     if (as->parms[P_FIXHEADER].items) {
624         FixHeader = 1;
625     }
626     if (as->parms[P_SAVEINODES].items) {
627         SaveInodes = 1;
628     }
629     if (as->parms[P_ORPHANED].items) {
630         ShowOrphaned = 1;
631     }
632 #if defined(AFS_NAMEI_ENV)
633     if (as->parms[P_FILENAMES].items) {
634         PrintFileNames = 1;
635     }
636 #endif
637
638     /* -saveinodes and -sizeOnly override the default mode.
639      * For compatibility with old versions of volinfo, -orphaned
640      * and -filename options imply -vnodes */
641     if (SaveInodes || ShowSizes) {
642         DumpInfo = 0;
643         DumpHeader = 0;
644         DumpVnodes = 0;
645         InodeTimes = 0;
646         ShowOrphaned = 0;
647     } else if (ShowOrphaned) {
648         DumpVnodes = 1;         /* implied */
649 #ifdef AFS_NAMEI_ENV
650     } else if (PrintFileNames) {
651         DumpVnodes = 1;         /* implied */
652 #endif
653     }
654
655     if (SaveInodes) {
656         AddVnodeHandler(vSmall, SaveInode,
657                         "Saving all volume files to current directory ...\n");
658     }
659     if (ShowSizes) {
660         AddVnodeHandler(vLarge, AddVnodeToSizeTotals, NULL);
661         AddVnodeHandler(vSmall, AddVnodeToSizeTotals, NULL);
662     }
663     if (DumpVnodes) {
664         AddVnodeHandler(vLarge, PrintVnode, "\nLarge vnodes (directories)\n");
665         AddVnodeHandler(vSmall, PrintVnode,
666                         "\nSmall vnodes(files, symbolic links)\n");
667     }
668
669     return ScanPartitions(partNameOrId, volumeId);
670 }
671
672 /**
673  * Determine if the current directory is a vice partition
674  *
675  * @return disk partition object
676  */
677 #ifdef AFS_NT40_ENV
678 #include <direct.h>
679 struct DiskPartition64 *
680 FindCurrentPartition(void)
681 {
682     int dr = _getdrive();
683     struct DiskPartition64 *dp;
684
685     dr--;
686     for (dp = DiskPartitionList; dp; dp = dp->next) {
687         if (*dp->devName - 'A' == dr)
688             break;
689     }
690     if (!dp) {
691         fprintf(stderr, "%s: Current drive is not a valid vice partition.\n",
692                 progname);
693     }
694     return dp;
695 }
696 #else
697 struct DiskPartition64 *
698 FindCurrentPartition(void)
699 {
700     char partName[1024];
701     char tmp = '\0';
702     char *p;
703     struct DiskPartition64 *dp;
704
705     if (!getcwd(partName, 1023)) {
706         fprintf(stderr, "%s: Failed to get current working directory: ",
707                 progname);
708         perror("pwd");
709         return NULL;
710     }
711     p = strchr(&partName[1], OS_DIRSEPC);
712     if (p) {
713         tmp = *p;
714         *p = '\0';
715     }
716     if (!(dp = VGetPartition(partName, 0))) {
717         if (tmp)
718             *p = tmp;
719         fprintf(stderr, "%s: %s is not a valid vice partition.\n", progname,
720                 partName);
721         return NULL;
722     }
723     return dp;
724 }
725 #endif
726
727 /**
728  * Scan and handle all the partitions detected on this server
729  *
730  * @return none
731  */
732 void
733 HandleAllPart(void)
734 {
735     struct DiskPartition64 *partP;
736
737
738     for (partP = DiskPartitionList; partP; partP = partP->next) {
739         if (DumpInfo || SaveInodes || ShowSizes) {
740             printf("Processing Partition %s:\n", partP->name);
741         }
742         HandlePart(partP);
743         if (ShowSizes) {
744             AddSizeTotals(&serverTotals, &partitionTotals);
745         }
746     }
747
748     if (ShowSizes) {
749         PrintServerTotals();
750     }
751 }
752
753 /**
754  * Scan the partition and handle volumes
755  *
756  * @param[in] partP  disk partition to scan
757  *
758  * @return none
759  */
760 void
761 HandlePart(struct DiskPartition64 *partP)
762 {
763     afs_int64 nvols = 0;
764     DIR *dirp;
765     struct dirent *dp;
766 #ifdef AFS_NT40_ENV
767     char pname[64];
768     char *p = pname;
769     (void)sprintf(pname, "%s\\", VPartitionPath(partP));
770 #else
771     char *p = VPartitionPath(partP);
772 #endif
773
774     if ((dirp = opendir(p)) == NULL) {
775         fprintf(stderr, "%s: Can't read directory %s; giving up\n", progname,
776                 p);
777         return;
778     }
779     while ((dp = readdir(dirp))) {
780         p = (char *)strrchr(dp->d_name, '.');
781         if (p != NULL && strcmp(p, VHDREXT) == 0) {
782             HandleVolume(partP, dp->d_name);
783             if (ShowSizes) {
784                 nvols++;
785                 AddSizeTotals(&partitionTotals, &volumeTotals);
786             }
787         }
788     }
789     closedir(dirp);
790     if (ShowSizes) {
791         PrintPartitionTotals(nvols);
792     }
793 }
794
795 /**
796  * Inspect a volume header special file.
797  *
798  * @param[in]  name       descriptive name of the type of header special file
799  * @param[in]  dp         partition object for this volume
800  * @param[in]  header     header object for this volume
801  * @param[in]  inode      fileserver inode number for this header special file
802  * @param[out] psize      total of the header special file
803  *
804  * @return none
805  */
806 void
807 HandleSpecialFile(const char *name, struct DiskPartition64 *dp,
808                   struct VolumeHeader *header, Inode inode,
809                   afs_sfsize_t * psize)
810 {
811     afs_sfsize_t size = 0;
812     IHandle_t *ih = NULL;
813     FdHandle_t *fdP = NULL;
814 #ifdef AFS_NAMEI_ENV
815     namei_t filename;
816 #endif /* AFS_NAMEI_ENV */
817
818     IH_INIT(ih, dp->device, header->parent, inode);
819     fdP = IH_OPEN(ih);
820     if (fdP == NULL) {
821         fprintf(stderr,
822                 "%s: Error opening header file '%s' for volume %u", progname,
823                 name, header->id);
824         perror("open");
825         goto error;
826     }
827     size = FDH_SIZE(fdP);
828     if (size == -1) {
829         fprintf(stderr,
830                 "%s: Error getting size of header file '%s' for volume %u",
831                 progname, name, header->id);
832         perror("fstat");
833         goto error;
834     }
835     if (DumpInfo) {
836         printf("\t%s inode\t= %s (size = %lld)\n",
837                name, PrintInode(NULL, inode), size);
838 #ifdef AFS_NAMEI_ENV
839         namei_HandleToName(&filename, ih);
840         printf("\t%s namei\t= %s\n", name, filename.n_path);
841 #endif /* AFS_NAMEI_ENV */
842     }
843     *psize += size;
844
845   error:
846     if (fdP != NULL) {
847         FDH_REALLYCLOSE(fdP);
848     }
849     if (ih != NULL) {
850         IH_RELEASE(ih);
851     }
852 }
853
854 /**
855  * Inspect this volume header files.
856  *
857  * @param[in]  dp         partition object for this volume
858  * @param[in]  header_fd  volume header file descriptor
859  * @param[in]  header     volume header object
860  * @param[out] psize      total of the header special file
861  *
862  * @return none
863  */
864 void
865 HandleHeaderFiles(struct DiskPartition64 *dp, FD_t header_fd,
866                   struct VolumeHeader *header)
867 {
868     afs_sfsize_t size = 0;
869
870     if (DumpInfo) {
871         size = OS_SIZE(header_fd);
872         printf("Volume header (size = %lld):\n", size);
873         printf("\tstamp\t= 0x%x\n", header->stamp.version);
874         printf("\tVolId\t= %u\n", header->id);
875         printf("\tparent\t= %u\n", header->parent);
876     }
877
878     HandleSpecialFile("Info", dp, header, header->volumeInfo, &size);
879     HandleSpecialFile("Small", dp, header, header->smallVnodeIndex,
880                       &size);
881     HandleSpecialFile("Large", dp, header, header->largeVnodeIndex,
882                       &size);
883 #ifdef AFS_NAMEI_ENV
884     HandleSpecialFile("Link", dp, header, header->linkTable, &size);
885 #endif /* AFS_NAMEI_ENV */
886
887     if (DumpInfo) {
888         printf("Total aux volume size = %lld\n\n", size);
889     }
890
891     if (ShowSizes) {
892         volumeTotals.auxsize = size;
893         volumeTotals.auxsize_k = size / 1024;
894     }
895 }
896
897 /**
898  * Attach and scan the volume and handle the header and vnodes
899  *
900  * Print the volume header and vnode information, depending on the
901  * current modes.
902  *
903  * @param[in] dp    vice partition object for this volume
904  * @param[in] name  volume header file name
905  *
906  * @return none
907  */
908 void
909 HandleVolume(struct DiskPartition64 *dp, char *name)
910 {
911     struct VolumeHeader header;
912     struct VolumeDiskHeader diskHeader;
913     FD_t fd = INVALID_FD;
914     Volume *vp = NULL;
915     char headerName[1024];
916     afs_sfsize_t n;
917
918     snprintf(headerName, sizeof headerName, "%s" OS_DIRSEP "%s",
919              VPartitionPath(dp), name);
920     if ((fd = OS_OPEN(headerName, O_RDONLY, 0666)) == INVALID_FD) {
921         fprintf(stderr, "%s: Cannot open volume header %s\n", progname, name);
922         goto cleanup;
923     }
924     if (OS_SIZE(fd) < 0) {
925         fprintf(stderr, "%s: Cannot read volume header %s\n", progname, name);
926         goto cleanup;
927     }
928     n = OS_READ(fd, &diskHeader, sizeof(diskHeader));
929     if (n != sizeof(diskHeader)
930         || diskHeader.stamp.magic != VOLUMEHEADERMAGIC) {
931         fprintf(stderr, "%s: Error reading volume header %s\n", progname,
932                 name);
933         goto cleanup;
934     }
935     if (diskHeader.stamp.version != VOLUMEHEADERVERSION) {
936         fprintf(stderr,
937                 "%s: Volume %s, version number is incorrect; volume needs to be salvaged\n",
938                 progname, name);
939         goto cleanup;
940     }
941
942     DiskToVolumeHeader(&header, &diskHeader);
943     if (DumpHeader || ShowSizes) {
944         HandleHeaderFiles(dp, fd, &header);
945     }
946
947     vp = AttachVolume(dp, name, &header);
948     if (!vp) {
949         fprintf(stderr, "%s: Error attaching volume header %s\n",
950                 progname, name);
951         goto cleanup;
952     }
953
954     if (DumpInfo) {
955         PrintHeader(vp);
956     }
957
958     HandleVnodes(vp, vLarge);
959     HandleVnodes(vp, vSmall);
960
961     if (ShowSizes) {
962         volumeTotals.diskused_k = V_diskused(vp);
963         volumeTotals.size_k =
964             volumeTotals.auxsize_k + volumeTotals.vnodesize_k;
965         if (SaveInodes) {
966             PrintingVolumeSizes = 0;    /* print heading again */
967         }
968         PrintVolumeSizes(vp);
969     }
970
971   cleanup:
972     if (fd != INVALID_FD) {
973         OS_CLOSE(fd);
974     }
975     if (vp) {
976         DetachVolume(vp);
977     }
978 }
979
980 /**
981  * volinfo program entry
982  */
983 int
984 main(int argc, char **argv)
985 {
986     struct cmd_syndesc *ts;
987     afs_int32 code;
988
989     opr_queue_Init(&VnodeScanLists[vLarge]);
990     opr_queue_Init(&VnodeScanLists[vSmall]);
991
992     ts = cmd_CreateSyntax(NULL, handleit, NULL,
993                           "Dump volume's internal state");
994     cmd_AddParmAtOffset(ts, P_ONLINE, "-online", CMD_FLAG, CMD_OPTIONAL,
995                         "Get info from running fileserver");
996     cmd_AddParmAtOffset(ts, P_VNODE, "-vnode", CMD_FLAG, CMD_OPTIONAL,
997                         "Dump vnode info");
998     cmd_AddParmAtOffset(ts, P_DATE, "-date", CMD_FLAG, CMD_OPTIONAL,
999                         "Also dump vnode's mod date");
1000     cmd_AddParmAtOffset(ts, P_INODE, "-inode", CMD_FLAG, CMD_OPTIONAL,
1001                         "Also dump vnode's inode number");
1002     cmd_AddParmAtOffset(ts, P_ITIME, "-itime", CMD_FLAG, CMD_OPTIONAL,
1003                         "Dump special inode's mod times");
1004     cmd_AddParmAtOffset(ts, P_PART, "-part", CMD_LIST, CMD_OPTIONAL,
1005                         "AFS partition name or id (default current partition)");
1006     cmd_AddParmAtOffset(ts, P_VOLUMEID, "-volumeid", CMD_LIST, CMD_OPTIONAL,
1007                         "Volume id");
1008     cmd_AddParmAtOffset(ts, P_HEADER, "-header", CMD_FLAG, CMD_OPTIONAL,
1009                         "Dump volume's header info");
1010     cmd_AddParmAtOffset(ts, P_SIZEONLY, "-sizeonly", CMD_FLAG, CMD_OPTIONAL,
1011                         "Dump volume's size");
1012     cmd_AddParmAtOffset(ts, P_FIXHEADER, "-fixheader", CMD_FLAG,
1013                         CMD_OPTIONAL, "Try to fix header");
1014     cmd_AddParmAtOffset(ts, P_SAVEINODES, "-saveinodes", CMD_FLAG,
1015                         CMD_OPTIONAL, "Try to save all inodes");
1016     cmd_AddParmAtOffset(ts, P_ORPHANED, "-orphaned", CMD_FLAG, CMD_OPTIONAL,
1017                         "List all dir/files without a parent");
1018 #if defined(AFS_NAMEI_ENV)
1019     cmd_AddParmAtOffset(ts, P_FILENAMES, "-filenames", CMD_FLAG,
1020                         CMD_OPTIONAL, "Also dump vnode's namei filename");
1021 #endif
1022
1023     /* For compatibility with older versions. */
1024     cmd_AddParmAlias(ts, P_SIZEONLY, "-sizeOnly");
1025
1026     code = cmd_Dispatch(argc, argv);
1027     return code;
1028 }
1029
1030 /**
1031  * Return a display string for the volume type.
1032  *
1033  * @param[in]  type  volume type
1034  *
1035  * @return volume type description string
1036  */
1037 static_inline char *
1038 volumeTypeString(int type)
1039 {
1040     return
1041         (type == RWVOL ? "read/write" :
1042          (type == ROVOL ? "readonly" :
1043           (type == BACKVOL ? "backup" : "unknown")));
1044 }
1045
1046 /**
1047  * Print the volume header information
1048  *
1049  * @param[in]  volume object
1050  *
1051  * @return none
1052  */
1053 void
1054 PrintHeader(Volume * vp)
1055 {
1056     printf("Volume header for volume %u (%s)\n", V_id(vp), V_name(vp));
1057     printf("stamp.magic = %x, stamp.version = %u\n", V_stamp(vp).magic,
1058            V_stamp(vp).version);
1059     printf
1060         ("inUse = %d, inService = %d, blessed = %d, needsSalvaged = %d, dontSalvage = %d\n",
1061          V_inUse(vp), V_inService(vp), V_blessed(vp), V_needsSalvaged(vp),
1062          V_dontSalvage(vp));
1063     printf
1064         ("type = %d (%s), uniquifier = %u, needsCallback = %d, destroyMe = %x\n",
1065          V_type(vp), volumeTypeString(V_type(vp)), V_uniquifier(vp),
1066          V_needsCallback(vp), V_destroyMe(vp));
1067     printf
1068         ("id = %u, parentId = %u, cloneId = %u, backupId = %u, restoredFromId = %u\n",
1069          V_id(vp), V_parentId(vp), V_cloneId(vp), V_backupId(vp),
1070          V_restoredFromId(vp));
1071     printf
1072         ("maxquota = %d, minquota = %d, maxfiles = %d, filecount = %d, diskused = %d\n",
1073          V_maxquota(vp), V_minquota(vp), V_maxfiles(vp), V_filecount(vp),
1074          V_diskused(vp));
1075     printf("creationDate = %s, copyDate = %s\n", date(V_creationDate(vp)),
1076            date(V_copyDate(vp)));
1077     printf("backupDate = %s, expirationDate = %s\n", date(V_backupDate(vp)),
1078            date(V_expirationDate(vp)));
1079     printf("accessDate = %s, updateDate = %s\n", date(V_accessDate(vp)),
1080            date(V_updateDate(vp)));
1081     printf("owner = %u, accountNumber = %u\n", V_owner(vp),
1082            V_accountNumber(vp));
1083     printf
1084         ("dayUse = %u; week = (%u, %u, %u, %u, %u, %u, %u), dayUseDate = %s\n",
1085          V_dayUse(vp), V_weekUse(vp)[0], V_weekUse(vp)[1], V_weekUse(vp)[2],
1086          V_weekUse(vp)[3], V_weekUse(vp)[4], V_weekUse(vp)[5],
1087          V_weekUse(vp)[6], date(V_dayUseDate(vp)));
1088     printf("volUpdateCounter = %u\n", V_volUpCounter(vp));
1089 }
1090
1091 /**
1092  * Get the size and times of a file
1093  *
1094  * @param[in]  fd     file descriptor of file to stat
1095  * @param[out] size   size of the file
1096  * @param[out] ctime  ctime of file as a formatted string
1097  * @param[out] mtime  mtime of file as a formatted string
1098  * @param[out] atime  atime of file as a formatted string
1099  *
1100  * @return error code
1101  *   @retval 0 success
1102  *   @retval -1 failed to retrieve file information
1103  */
1104 static int
1105 GetFileInfo(FD_t fd, afs_sfsize_t * size, char **ctime, char **mtime,
1106             char **atime)
1107 {
1108 #ifdef AFS_NT40_ENV
1109     BY_HANDLE_FILE_INFORMATION fi;
1110     LARGE_INTEGER fsize;
1111     if (!GetFileInformationByHandle(fd, &fi)) {
1112         fprintf(stderr, "%s: GetFileInformationByHandle failed\n", progname);
1113         return -1;
1114     }
1115     if (!GetFileSizeEx(fd, &fsize)) {
1116         fprintf(stderr, "%s: GetFileSizeEx failed\n", progname);
1117         return -1;
1118     }
1119     *size = fsize.QuadPart;
1120     *ctime = "N/A";
1121     *mtime = NT_date(&fi.ftLastWriteTime);
1122     *atime = NT_date(&fi.ftLastAccessTime);
1123 #else
1124     struct afs_stat_st status;
1125     if (afs_fstat(fd, &status) == -1) {
1126         fprintf(stderr, "%s: fstat failed %d\n", progname, errno);
1127         return -1;
1128     }
1129     *size = status.st_size;
1130     *ctime = date(status.st_ctime);
1131     *mtime = date(status.st_mtime);
1132     *atime = date(status.st_atime);
1133 #endif
1134     return 0;
1135 }
1136
1137 /**
1138  * Copy the inode data to a file in the current directory.
1139  *
1140  * @param[in] vdp     vnode details object
1141  *
1142  * @return none
1143  */
1144 static void
1145 SaveInode(struct VnodeDetails *vdp)
1146 {
1147     IHandle_t *ih;
1148     FdHandle_t *fdP;
1149     char nfile[50], buffer[256];
1150     int ofd = 0;
1151     afs_foff_t total;
1152     ssize_t len;
1153     Inode ino = VNDISK_GET_INO(vdp->vnode);
1154
1155     if (!VALID_INO(ino)) {
1156         return;
1157     }
1158
1159     IH_INIT(ih, V_device(vdp->vp), V_parentId(vdp->vp), ino);
1160     fdP = IH_OPEN(ih);
1161     if (fdP == NULL) {
1162         fprintf(stderr,
1163                 "%s: Can't open inode %s error %d (ignored)\n",
1164                 progname, PrintInode(NULL, ino), errno);
1165         return;
1166     }
1167     snprintf(nfile, sizeof nfile, "TmpInode.%s", PrintInode(NULL, ino));
1168     ofd = afs_open(nfile, O_CREAT | O_RDWR | O_TRUNC, 0600);
1169     if (ofd < 0) {
1170         fprintf(stderr,
1171                 "%s: Can't create file %s; error %d (ignored)\n",
1172                 progname, nfile, errno);
1173
1174         FDH_REALLYCLOSE(fdP);
1175         IH_RELEASE(ih);
1176         return;
1177     }
1178     total = 0;
1179     while (1) {
1180         ssize_t nBytes;
1181         len = FDH_PREAD(fdP, buffer, sizeof(buffer), total);
1182         if (len < 0) {
1183             FDH_REALLYCLOSE(fdP);
1184             IH_RELEASE(ih);
1185             close(ofd);
1186             unlink(nfile);
1187             fprintf(stderr,
1188                     "%s: Error while reading from inode %s (%d)\n",
1189                     progname, PrintInode(NULL, ino), errno);
1190             return;
1191         }
1192         if (len == 0)
1193             break;              /* No more input */
1194         nBytes = write(ofd, buffer, len);
1195         if (nBytes != len) {
1196             FDH_REALLYCLOSE(fdP);
1197             IH_RELEASE(ih);
1198             close(ofd);
1199             unlink(nfile);
1200             fprintf(stderr,
1201                     "%s: Error while writing to \"%s\" (%d - ignored)\n",
1202                     progname, nfile, errno);
1203             return;
1204         }
1205         total += len;
1206     }
1207
1208     FDH_REALLYCLOSE(fdP);
1209     IH_RELEASE(ih);
1210     close(ofd);
1211     printf("... Copied inode %s to file %s (%lu bytes)\n",
1212            PrintInode(NULL, ino), nfile, (unsigned long)total);
1213 }
1214
1215 /**
1216  * Scan a volume index and handle each vnode
1217  *
1218  * @param[in] vp      volume object
1219  * @param[in] class   which index to scan
1220  *
1221  * @return none
1222  */
1223 void
1224 HandleVnodes(Volume * vp, VnodeClass class)
1225 {
1226     afs_int32 diskSize =
1227         (class == vSmall ? SIZEOF_SMALLDISKVNODE : SIZEOF_LARGEDISKVNODE);
1228     char buf[SIZEOF_LARGEDISKVNODE];
1229     struct VnodeDiskObject *vnode = (struct VnodeDiskObject *)buf;
1230     StreamHandle_t *file = NULL;
1231     int vnodeIndex;
1232     afs_sfsize_t nVnodes;
1233     afs_foff_t offset = 0;
1234     IHandle_t *ih = vp->vnodeIndex[class].handle;
1235     FdHandle_t *fdP = NULL;
1236     afs_sfsize_t size;
1237     char *ctime, *atime, *mtime;
1238     struct opr_queue *scanList = &VnodeScanLists[class];
1239     struct opr_queue *cursor;
1240
1241     if (opr_queue_IsEmpty(scanList)) {
1242         return;
1243     }
1244
1245     for (opr_queue_Scan(scanList, cursor)) {
1246         struct VnodeScanProc *entry = (struct VnodeScanProc *)cursor;
1247         if (entry->heading) {
1248             printf(entry->heading);
1249         }
1250     }
1251
1252     fdP = IH_OPEN(ih);
1253     if (fdP == NULL) {
1254         fprintf(stderr, "%s: open failed: ", progname);
1255         goto error;
1256     }
1257
1258     file = FDH_FDOPEN(fdP, "r");
1259     if (!file) {
1260         fprintf(stderr, "%s: fdopen failed\n", progname);
1261         goto error;
1262     }
1263
1264     if (GetFileInfo(fdP->fd_fd, &size, &ctime, &atime, &mtime) != 0) {
1265         goto error;
1266     }
1267     if (InodeTimes) {
1268         printf("ichanged : %s\nimodified: %s\niaccessed: %s\n\n", ctime,
1269                mtime, atime);
1270     }
1271
1272     nVnodes = (size / diskSize) - 1;
1273     if (nVnodes > 0) {
1274         STREAM_ASEEK(file, diskSize);
1275     } else
1276         nVnodes = 0;
1277
1278     for (vnodeIndex = 0;
1279          nVnodes && STREAM_READ(vnode, diskSize, 1, file) == 1;
1280          nVnodes--, vnodeIndex++, offset += diskSize) {
1281
1282         struct VnodeDetails vnodeDetails;
1283
1284         vnodeDetails.vp = vp;
1285         vnodeDetails.class = class;
1286         vnodeDetails.vnode = vnode;
1287         vnodeDetails.vnodeNumber = bitNumberToVnodeNumber(vnodeIndex, class);
1288         vnodeDetails.offset = offset;
1289         vnodeDetails.index = vnodeIndex;
1290
1291         for (opr_queue_Scan(scanList, cursor)) {
1292             struct VnodeScanProc *entry = (struct VnodeScanProc *)cursor;
1293             if (entry->proc) {
1294                 (*entry->proc) (&vnodeDetails);
1295             }
1296         }
1297     }
1298
1299   error:
1300     if (file) {
1301         STREAM_CLOSE(file);
1302     }
1303     if (fdP) {
1304         FDH_CLOSE(fdP);
1305     }
1306 }
1307
1308 /**
1309  * Print vnode information
1310  *
1311  * @param[in] vdp          vnode details object
1312  *
1313  * @return none
1314  */
1315 void
1316 PrintVnode(struct VnodeDetails *vdp)
1317 {
1318 #if defined(AFS_NAMEI_ENV)
1319     IHandle_t *ihtmpp;
1320     namei_t filename;
1321 #endif
1322     afs_foff_t offset = vdp->offset;
1323     VnodeDiskObject *vnode = vdp->vnode;
1324     afs_fsize_t fileLength;
1325     Inode ino;
1326
1327     ino = VNDISK_GET_INO(vnode);
1328     VNDISK_GET_LEN(fileLength, vnode);
1329
1330     /* The check for orphaned vnodes is currently limited to non-empty
1331      * vnodes with a parent of zero (and which are not the first entry
1332      * in the index). */
1333     if (ShowOrphaned && (fileLength == 0 || vnode->parent || !offset))
1334         return;
1335
1336     printf
1337         ("%10lld Vnode %u.%u.%u cloned: %u, length: %llu linkCount: %d parent: %u",
1338          (long long)offset, vdp->vnodeNumber, vnode->uniquifier,
1339          vnode->dataVersion, vnode->cloned, (afs_uintmax_t) fileLength,
1340          vnode->linkCount, vnode->parent);
1341     if (DumpInodeNumber)
1342         printf(" inode: %s", PrintInode(NULL, ino));
1343     if (DumpDate)
1344         printf(" ServerModTime: %s", date(vnode->serverModifyTime));
1345 #if defined(AFS_NAMEI_ENV)
1346     if (PrintFileNames) {
1347         IH_INIT(ihtmpp, V_device(vdp->vp), V_parentId(vdp->vp), ino);
1348         namei_HandleToName(&filename, ihtmpp);
1349 #if !defined(AFS_NT40_ENV)
1350         printf(" UFS-Filename: %s", filename.n_path);
1351 #else
1352         printf(" NTFS-Filename: %s", filename.n_path);
1353 #endif
1354     }
1355 #endif
1356     printf("\n");
1357 }