volinfo: refactor volume and vnode handling code
[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
31 #include <rx/xdr.h>
32 #include <afs/afsint.h>
33 #include "nfs.h"
34 #include <afs/errors.h>
35 #include "lock.h"
36 #include "lwp.h"
37 #include <afs/afssyscalls.h>
38 #include "ihandle.h"
39 #include "vnode.h"
40 #include "volume.h"
41 #include "partition.h"
42 #include "viceinode.h"
43 #include <afs/afssyscalls.h>
44 #include <afs/afsutil.h>
45
46 static const char *progname = "volinfo";
47
48 int DumpVnodes = 0;             /* Dump everything, i.e. summary of all vnodes */
49 int DumpInodeNumber = 0;        /* Dump inode numbers with vnodes */
50 int DumpDate = 0;               /* Dump vnode date (server modify date) with vnode */
51 int InodeTimes = 0;             /* Dump some of the dates associated with inodes */
52 #if defined(AFS_NAMEI_ENV)
53 int PrintFileNames = 0;
54 #endif
55 int dheader = 0;
56 int dsizeOnly = 0;
57 int fixheader = 0, saveinodes = 0, orphaned = 0;
58 int VolumeChanged;
59
60 /**
61  * Volume size running totals
62  */
63 struct sizeTotals {
64     afs_uint64 diskused_k;      /**< volume size from disk data file, in kilobytes */
65     afs_uint64 auxsize;         /**< size of header files, in bytes  */
66     afs_uint64 auxsize_k;       /**< size of header files, in kilobytes */
67     afs_uint64 vnodesize;       /**< size of the large and small vnodes, in bytes */
68     afs_uint64 vnodesize_k;     /**< size of the large and small vnodes, in kilobytes */
69     afs_uint64 size_k;          /**< size of headers and vnodes, in kilobytes */
70 };
71
72 static struct sizeTotals volumeTotals = { 0, 0, 0, 0, 0, 0 };
73 static struct sizeTotals partitionTotals = { 0, 0, 0, 0, 0, 0 };
74 static struct sizeTotals serverTotals = { 0, 0, 0, 0, 0, 0 };
75
76 /* Forward Declarations */
77 void PrintHeader(Volume * vp);
78 void HandleAllPart(void);
79 void HandlePart(struct DiskPartition64 *partP);
80 void HandleVolume(struct DiskPartition64 *partP, char *name);
81 struct DiskPartition64 *FindCurrentPartition(void);
82 Volume *AttachVolume(struct DiskPartition64 *dp, char *volname,
83                      struct VolumeHeader *header);
84 void PrintVnode(afs_foff_t offset, VnodeDiskObject * vnode, VnodeId vnodeNumber,
85                 Inode ino, Volume * vp);
86 void PrintVnodes(Volume * vp, VnodeClass class);
87
88 char *
89 date(time_t date)
90 {
91 #define MAX_DATE_RESULT 100
92     static char results[8][MAX_DATE_RESULT];
93     static int next;
94     struct tm *tm = localtime(&date);
95     char buf[32];
96
97     (void)strftime(buf, 32, "%Y/%m/%d.%H:%M:%S", tm);   /* NT does not have %T */
98     snprintf(results[next = (next + 1) & 7], MAX_DATE_RESULT,
99              "%lu (%s)", (unsigned long)date, buf);
100     return results[next];
101 }
102
103 #ifndef AFS_NT40_ENV
104 #include "AFS_component_version_number.c"
105 #endif
106
107 char name[VMAXPATHLEN];
108
109 char BU[1000];
110
111 static int PrintingVolumeSizes = 0;     /* printing volume size lines */
112
113 /**
114  * Print the volume size table heading line, if needed.
115  *
116  * @return none
117  */
118 static void
119 PrintVolumeSizeHeading(void)
120 {
121     if (!PrintingVolumeSizes) {
122         printf
123             ("Volume-Id\t  Volsize  Auxsize Inodesize  AVolsize SizeDiff                (VolName)\n");
124         PrintingVolumeSizes = 1;
125     }
126 }
127
128 /**
129  * Accumulate totals.
130  *
131  * @return 0
132  */
133 static void
134 AddSizeTotals(struct sizeTotals *a, struct sizeTotals *b)
135 {
136     a->diskused_k += b->diskused_k;
137     a->auxsize += b->auxsize;
138     a->auxsize_k += b->auxsize_k;
139     a->vnodesize += b->vnodesize;
140     a->vnodesize_k += b->vnodesize_k;
141     a->size_k += b->size_k;
142 }
143
144 /**
145  * Print the sizes for a volume.
146  *
147  * @return none
148  */
149 static void
150 PrintVolumeSizes(Volume * vp)
151 {
152     afs_int64 diff_k = volumeTotals.size_k - volumeTotals.diskused_k;
153
154     PrintVolumeSizeHeading();
155     printf("%u\t%9llu%9llu%10llu%10llu%9lld\t%24s\n",
156            V_id(vp),
157            volumeTotals.diskused_k,
158            volumeTotals.auxsize_k, volumeTotals.vnodesize_k,
159            volumeTotals.size_k, diff_k, V_name(vp));
160 }
161
162 /**
163  * Print the size totals for the partition.
164  *
165  * @return none
166  */
167 static void
168 PrintPartitionTotals(afs_uint64 nvols)
169 {
170     afs_int64 diff_k = partitionTotals.size_k - partitionTotals.diskused_k;
171
172     PrintVolumeSizeHeading();
173     printf("\nPart Totals  %12llu%9llu%10llu%10llu%9lld (%llu volumes)\n\n",
174            partitionTotals.diskused_k, partitionTotals.auxsize,
175            partitionTotals.vnodesize, partitionTotals.size_k, diff_k, nvols);
176     PrintingVolumeSizes = 0;
177 }
178
179 /**
180  * Print the size totals for all the partitions.
181  *
182  * @return none
183  */
184 static void
185 PrintServerTotals(void)
186 {
187     afs_int64 diff_k = serverTotals.size_k - serverTotals.diskused_k;
188
189     printf("\nServer Totals%12lld%9lld%10lld%10lld%9lld\n",
190            serverTotals.diskused_k, serverTotals.auxsize,
191            serverTotals.vnodesize, serverTotals.size_k, diff_k);
192 }
193
194 int
195 ReadHdr1(IHandle_t * ih, char *to, int size, u_int magic, u_int version)
196 {
197     struct versionStamp *vsn;
198     int bad = 0;
199     int code;
200
201     vsn = (struct versionStamp *)to;
202
203     code = IH_IREAD(ih, 0, to, size);
204     if (code != size)
205         return -1;
206
207     if (vsn->magic != magic) {
208         bad++;
209         fprintf(stderr, "%s: Inode %s: Bad magic %x (%x): IGNORED\n",
210                 progname, PrintInode(NULL, ih->ih_ino), vsn->magic, magic);
211     }
212
213     /* Check is conditional, in case caller wants to inspect version himself */
214     if (version && vsn->version != version) {
215         bad++;
216         fprintf(stderr, "%s: Inode %s: Bad version %x (%x): IGNORED\n",
217                 progname,
218                 PrintInode(NULL, ih->ih_ino), vsn->version, version);
219     }
220     if (bad && fixheader) {
221         vsn->magic = magic;
222         vsn->version = version;
223         printf("Special index inode %s has a bad header. Reconstructing...\n",
224                PrintInode(NULL, ih->ih_ino));
225         code = IH_IWRITE(ih, 0, to, size);
226         if (code != size) {
227             fprintf(stderr,
228                     "%s: Write failed for inode %s; header left in damaged state\n",
229                     progname, PrintInode(NULL, ih->ih_ino));
230         }
231     } else {
232         if (!dsizeOnly && !saveinodes) {
233             printf("Inode %s: Good magic %x and version %x\n",
234                    PrintInode(NULL, ih->ih_ino), magic, version);
235         }
236     }
237     return 0;
238 }
239
240
241 Volume *
242 AttachVolume(struct DiskPartition64 * dp, char *volname,
243              struct VolumeHeader * header)
244 {
245     Volume *vp;
246     afs_int32 ec = 0;
247
248     vp = (Volume *) calloc(1, sizeof(Volume));
249     vp->specialStatus = 0;
250     vp->device = dp->device;
251     vp->partition = dp;
252     IH_INIT(vp->vnodeIndex[vLarge].handle, dp->device, header->parent,
253             header->largeVnodeIndex);
254     IH_INIT(vp->vnodeIndex[vSmall].handle, dp->device, header->parent,
255             header->smallVnodeIndex);
256     IH_INIT(vp->diskDataHandle, dp->device, header->parent,
257             header->volumeInfo);
258     IH_INIT(V_linkHandle(vp), dp->device, header->parent, header->linkTable);
259     vp->cacheCheck = 0;         /* XXXX */
260     vp->shuttingDown = 0;
261     vp->goingOffline = 0;
262     vp->nUsers = 1;
263     vp->header = (struct volHeader *)calloc(1, sizeof(*vp->header));
264     ec = ReadHdr1(V_diskDataHandle(vp), (char *)&V_disk(vp),
265                   sizeof(V_disk(vp)), VOLUMEINFOMAGIC, VOLUMEINFOVERSION);
266     if (!ec) {
267         struct IndexFileHeader iHead;
268         ec = ReadHdr1(vp->vnodeIndex[vSmall].handle, (char *)&iHead,
269                       sizeof(iHead), SMALLINDEXMAGIC, SMALLINDEXVERSION);
270     }
271     if (!ec) {
272         struct IndexFileHeader iHead;
273         ec = ReadHdr1(vp->vnodeIndex[vLarge].handle, (char *)&iHead,
274                       sizeof(iHead), LARGEINDEXMAGIC, LARGEINDEXVERSION);
275     }
276 #ifdef AFS_NAMEI_ENV
277     if (!ec) {
278         struct versionStamp stamp;
279         ec = ReadHdr1(V_linkHandle(vp), (char *)&stamp, sizeof(stamp),
280                       LINKTABLEMAGIC, LINKTABLEVERSION);
281     }
282 #endif
283     if (ec)
284         return (Volume *) 0;
285     return vp;
286 }
287
288
289 static int
290 handleit(struct cmd_syndesc *as, void *arock)
291 {
292     struct cmd_item *ti;
293     int err = 0;
294     afs_uint32 volumeId = 0;
295     char *partName = 0;
296     struct DiskPartition64 *partP = NULL;
297
298
299 #ifndef AFS_NT40_ENV
300     if (geteuid() != 0) {
301         fprintf(stderr, "%s: Must be run as root; sorry\n", progname);
302         exit(1);
303     }
304 #endif
305
306     if (as->parms[0].items) {
307         fprintf(stderr, "%s: -online not supported\n", progname);
308         return 1;
309     }
310     if (as->parms[1].items)
311         DumpVnodes = 1;
312     else
313         DumpVnodes = 0;
314     if (as->parms[2].items)
315         DumpDate = 1;
316     else
317         DumpDate = 0;
318     if (as->parms[3].items)
319         DumpInodeNumber = 1;
320     else
321         DumpInodeNumber = 0;
322     if (as->parms[4].items)
323         InodeTimes = 1;
324     else
325         InodeTimes = 0;
326     if ((ti = as->parms[5].items))
327         partName = ti->data;
328     if ((ti = as->parms[6].items))
329         volumeId = strtoul(ti->data, NULL, 10);
330     if (as->parms[7].items)
331         dheader = 1;
332     else
333         dheader = 0;
334     if (as->parms[8].items) {
335         dsizeOnly = 1;
336         dheader = 1;
337         DumpVnodes = 1;
338     } else
339         dsizeOnly = 0;
340     if (as->parms[9].items) {
341         fixheader = 1;
342     } else
343         fixheader = 0;
344     if (as->parms[10].items) {
345         saveinodes = 1;
346         dheader = 1;
347         DumpVnodes = 1;
348     } else
349         saveinodes = 0;
350     if (as->parms[11].items) {
351         orphaned = 1;
352         DumpVnodes = 1;
353     } else
354         orphaned = 0;
355 #if defined(AFS_NAMEI_ENV)
356     if (as->parms[12].items) {
357         PrintFileNames = 1;
358         DumpVnodes = 1;
359     } else
360         PrintFileNames = 0;
361 #endif
362
363     DInit(10);
364
365     err = VAttachPartitions();
366     if (err) {
367         fprintf(stderr, "%s: %d partitions had errors during attach.\n",
368                 progname, err);
369     }
370
371     if (partName) {
372         partP = VGetPartition(partName, 0);
373         if (!partP) {
374             fprintf(stderr, "%s: %s is not an AFS partition name on this server.\n",
375                    progname, partName);
376             exit(1);
377         }
378     }
379
380     if (!volumeId) {
381         if (!partP) {
382             HandleAllPart();
383         } else {
384             HandlePart(partP);
385         }
386     } else {
387         char name1[128];
388
389         if (!partP) {
390             partP = FindCurrentPartition();
391             if (!partP) {
392                 fprintf(stderr,
393                         "%s: Current partition is not a vice partition.\n",
394                         progname);
395                 exit(1);
396             }
397         }
398         snprintf(name1, sizeof name1, VFORMAT,
399                  afs_printable_uint32_lu(volumeId));
400         HandleVolume(partP, name1);
401     }
402     return 0;
403 }
404
405 #ifdef AFS_NT40_ENV
406 #include <direct.h>
407 struct DiskPartition64 *
408 FindCurrentPartition(void)
409 {
410     int dr = _getdrive();
411     struct DiskPartition64 *dp;
412
413     dr--;
414     for (dp = DiskPartitionList; dp; dp = dp->next) {
415         if (*dp->devName - 'A' == dr)
416             break;
417     }
418     if (!dp) {
419         fprintf(stderr, "%s: Current drive is not a valid vice partition.\n",
420                 progname);
421     }
422     return dp;
423 }
424 #else
425 struct DiskPartition64 *
426 FindCurrentPartition(void)
427 {
428     char partName[1024];
429     char tmp = '\0';
430     char *p;
431     struct DiskPartition64 *dp;
432
433     if (!getcwd(partName, 1023)) {
434         fprintf(stderr, "%s: Failed to get current working directory: ",
435                 progname);
436         perror("pwd");
437         exit(1);
438     }
439     p = strchr(&partName[1], OS_DIRSEPC);
440     if (p) {
441         tmp = *p;
442         *p = '\0';
443     }
444     if (!(dp = VGetPartition(partName, 0))) {
445         if (tmp)
446             *p = tmp;
447         fprintf(stderr, "%s: %s is not a valid vice partition.\n", progname,
448                 partName);
449         exit(1);
450     }
451     return dp;
452 }
453 #endif
454
455 void
456 HandleAllPart(void)
457 {
458     struct DiskPartition64 *partP;
459
460
461     for (partP = DiskPartitionList; partP; partP = partP->next) {
462         printf("Processing Partition %s:\n", partP->name);
463         HandlePart(partP);
464         if (dsizeOnly) {
465             AddSizeTotals(&serverTotals, &partitionTotals);
466         }
467     }
468
469     if (dsizeOnly) {
470         PrintServerTotals();
471     }
472 }
473
474
475 void
476 HandlePart(struct DiskPartition64 *partP)
477 {
478     afs_int64 nvols = 0;
479     DIR *dirp;
480     struct dirent *dp;
481 #ifdef AFS_NT40_ENV
482     char pname[64];
483     char *p = pname;
484     (void)sprintf(pname, "%s\\", VPartitionPath(partP));
485 #else
486     char *p = VPartitionPath(partP);
487 #endif
488
489     if ((dirp = opendir(p)) == NULL) {
490         fprintf(stderr, "%s: Can't read directory %s; giving up\n", progname,
491                 p);
492         exit(1);
493     }
494     while ((dp = readdir(dirp))) {
495         p = (char *)strrchr(dp->d_name, '.');
496         if (p != NULL && strcmp(p, VHDREXT) == 0) {
497             HandleVolume(partP, dp->d_name);
498             if (dsizeOnly) {
499                 nvols++;
500                 AddSizeTotals(&partitionTotals, &volumeTotals);
501             }
502         }
503     }
504     closedir(dirp);
505     if (dsizeOnly) {
506         PrintPartitionTotals(nvols);
507     }
508 }
509
510 /**
511  * Inspect a volume header special file.
512  *
513  * @param[in]  name       descriptive name of the type of header special file
514  * @param[in]  dp         partition object for this volume
515  * @param[in]  header     header object for this volume
516  * @param[in]  inode      fileserver inode number for this header special file
517  * @param[out] psize      total of the header special file
518  *
519  * @return none
520  */
521 void
522 HandleSpecialFile(const char *name, struct DiskPartition64 *dp,
523                   struct VolumeHeader *header, Inode inode,
524                   afs_sfsize_t * psize)
525 {
526     afs_sfsize_t size = 0;
527     IHandle_t *ih = NULL;
528     FdHandle_t *fdP = NULL;
529 #ifdef AFS_NAMEI_ENV
530     namei_t filename;
531 #endif /* AFS_NAMEI_ENV */
532
533     IH_INIT(ih, dp->device, header->parent, inode);
534     fdP = IH_OPEN(ih);
535     if (fdP == NULL) {
536         fprintf(stderr,
537                 "%s: Error opening header file '%s' for volume %u", progname,
538                 name, header->id);
539         perror("open");
540         goto error;
541     }
542     size = FDH_SIZE(fdP);
543     if (size == -1) {
544         fprintf(stderr,
545                 "%s: Error getting size of header file '%s' for volume %u",
546                 progname, name, header->id);
547         perror("fstat");
548         goto error;
549     }
550     if (!dsizeOnly && !saveinodes) {
551         printf("\t%s inode\t= %s (size = %lld)\n",
552                name, PrintInode(NULL, inode), size);
553 #ifdef AFS_NAMEI_ENV
554         namei_HandleToName(&filename, ih);
555         printf("\t%s namei\t= %s\n", name, filename.n_path);
556 #endif /* AFS_NAMEI_ENV */
557     }
558     *psize += size;
559
560   error:
561     if (fdP != NULL) {
562         FDH_REALLYCLOSE(fdP);
563     }
564     if (ih != NULL) {
565         IH_RELEASE(ih);
566     }
567 }
568
569 /**
570  * Inspect this volume header files.
571  *
572  * @param[in]  dp         partition object for this volume
573  * @param[in]  header_fd  volume header file descriptor
574  * @param[in]  header     volume header object
575  * @param[out] psize      total of the header special file
576  *
577  * @return none
578  */
579 void
580 HandleHeaderFiles(struct DiskPartition64 *dp, FD_t header_fd,
581                   struct VolumeHeader *header)
582 {
583     afs_sfsize_t size = 0;
584
585     if (!dsizeOnly && !saveinodes) {
586         size = OS_SIZE(header_fd);
587         printf("Volume header (size = %lld):\n", size);
588         printf("\tstamp\t= 0x%x\n", header->stamp.version);
589         printf("\tVolId\t= %u\n", header->id);
590         printf("\tparent\t= %u\n", header->parent);
591     }
592
593     HandleSpecialFile("Info", dp, header, header->volumeInfo, &size);
594     HandleSpecialFile("Small", dp, header, header->smallVnodeIndex,
595                       &size);
596     HandleSpecialFile("Large", dp, header, header->largeVnodeIndex,
597                       &size);
598 #ifdef AFS_NAMEI_ENV
599     HandleSpecialFile("Link", dp, header, header->linkTable, &size);
600 #endif /* AFS_NAMEI_ENV */
601
602     if (!dsizeOnly && !saveinodes) {
603         printf("Total aux volume size = %lld\n\n", size);
604     }
605
606     if (dsizeOnly) {
607         volumeTotals.auxsize = size;
608         volumeTotals.auxsize_k = size / 1024;
609     }
610 }
611
612 void
613 HandleVolume(struct DiskPartition64 *dp, char *name)
614 {
615     struct VolumeHeader header;
616     struct VolumeDiskHeader diskHeader;
617     FD_t fd = INVALID_FD;
618     Volume *vp;
619     char headerName[1024];
620     afs_sfsize_t n;
621
622     snprintf(headerName, sizeof headerName, "%s" OS_DIRSEP "%s",
623              VPartitionPath(dp), name);
624     if ((fd = OS_OPEN(headerName, O_RDONLY, 0666)) == INVALID_FD) {
625         fprintf(stderr, "%s: Cannot open volume header %s\n", progname, name);
626         return;
627     }
628     if (OS_SIZE(fd) < 0) {
629         fprintf(stderr, "%s: Cannot read volume header %s\n", progname, name);
630         OS_CLOSE(fd);
631         return;
632     }
633     n = OS_READ(fd, &diskHeader, sizeof(diskHeader));
634     if (n != sizeof(diskHeader)
635         || diskHeader.stamp.magic != VOLUMEHEADERMAGIC) {
636         fprintf(stderr, "%s: Error reading volume header %s\n", progname,
637                 name);
638         OS_CLOSE(fd);
639         return;
640     }
641     if (diskHeader.stamp.version != VOLUMEHEADERVERSION) {
642         fprintf(stderr,
643                 "%s: Volume %s, version number is incorrect; volume needs to be salvaged\n",
644                 progname, name);
645         OS_CLOSE(fd);
646         return;
647     }
648     DiskToVolumeHeader(&header, &diskHeader);
649     if (dheader) {
650         HandleHeaderFiles(dp, fd, &header);
651     }
652     OS_CLOSE(fd);
653     vp = AttachVolume(dp, name, &header);
654     if (!vp) {
655         fprintf(stderr, "%s: Error attaching volume header %s\n",
656                 progname, name);
657         return;
658     }
659
660     if (!dsizeOnly && !saveinodes) {
661         PrintHeader(vp);
662     }
663     if (DumpVnodes) {
664         if (!dsizeOnly && !saveinodes)
665             printf("\nLarge vnodes (directories)\n");
666         PrintVnodes(vp, vLarge);
667         if (!dsizeOnly && !saveinodes) {
668             printf("\nSmall vnodes(files, symbolic links)\n");
669             fflush(stdout);
670         }
671         if (saveinodes) {
672             printf("Saving all volume files to current directory ...\n");
673             PrintingVolumeSizes = 0;    /* -saveinodes interfers with -sizeOnly */
674         }
675         PrintVnodes(vp, vSmall);
676     }
677     if (dsizeOnly) {
678         volumeTotals.diskused_k = V_diskused(vp);
679         volumeTotals.size_k =
680             volumeTotals.auxsize_k + volumeTotals.vnodesize_k;
681         PrintVolumeSizes(vp);
682     }
683     free(vp->header);
684     free(vp);
685 }
686
687 int
688 main(int argc, char **argv)
689 {
690     struct cmd_syndesc *ts;
691     afs_int32 code;
692
693     ts = cmd_CreateSyntax(NULL, handleit, NULL, "Dump volume's internal state");
694     cmd_AddParm(ts, "-online", CMD_FLAG, CMD_OPTIONAL,
695                 "Get info from running fileserver");
696     cmd_AddParm(ts, "-vnode", CMD_FLAG, CMD_OPTIONAL, "Dump vnode info");
697     cmd_AddParm(ts, "-date", CMD_FLAG, CMD_OPTIONAL,
698                 "Also dump vnode's mod date");
699     cmd_AddParm(ts, "-inode", CMD_FLAG, CMD_OPTIONAL,
700                 "Also dump vnode's inode number");
701     cmd_AddParm(ts, "-itime", CMD_FLAG, CMD_OPTIONAL,
702                 "Dump special inode's mod times");
703     cmd_AddParm(ts, "-part", CMD_LIST, CMD_OPTIONAL,
704                 "AFS partition name (default current partition)");
705     cmd_AddParm(ts, "-volumeid", CMD_LIST, CMD_OPTIONAL, "Volume id");
706     cmd_AddParm(ts, "-header", CMD_FLAG, CMD_OPTIONAL,
707                 "Dump volume's header info");
708     cmd_AddParm(ts, "-sizeOnly", CMD_FLAG, CMD_OPTIONAL,
709                 "Dump volume's size");
710     cmd_AddParm(ts, "-fixheader", CMD_FLAG, CMD_OPTIONAL,
711                 "Try to fix header");
712     cmd_AddParm(ts, "-saveinodes", CMD_FLAG, CMD_OPTIONAL,
713                 "Try to save all inodes");
714     cmd_AddParm(ts, "-orphaned", CMD_FLAG, CMD_OPTIONAL,
715                 "List all dir/files without a parent");
716 #if defined(AFS_NAMEI_ENV)
717     cmd_AddParm(ts, "-filenames", CMD_FLAG, CMD_OPTIONAL, "Also dump vnode's namei filename");
718 #endif
719     code = cmd_Dispatch(argc, argv);
720     return code;
721 }
722
723 #define typestring(type) (type == RWVOL? "read/write": type == ROVOL? "readonly": type == BACKVOL? "backup": "unknown")
724
725 void
726 PrintHeader(Volume * vp)
727 {
728     printf("Volume header for volume %u (%s)\n", V_id(vp), V_name(vp));
729     printf("stamp.magic = %x, stamp.version = %u\n", V_stamp(vp).magic,
730            V_stamp(vp).version);
731     printf
732         ("inUse = %d, inService = %d, blessed = %d, needsSalvaged = %d, dontSalvage = %d\n",
733          V_inUse(vp), V_inService(vp), V_blessed(vp), V_needsSalvaged(vp),
734          V_dontSalvage(vp));
735     printf
736         ("type = %d (%s), uniquifier = %u, needsCallback = %d, destroyMe = %x\n",
737          V_type(vp), typestring(V_type(vp)), V_uniquifier(vp),
738          V_needsCallback(vp), V_destroyMe(vp));
739     printf
740         ("id = %u, parentId = %u, cloneId = %u, backupId = %u, restoredFromId = %u\n",
741          V_id(vp), V_parentId(vp), V_cloneId(vp), V_backupId(vp),
742          V_restoredFromId(vp));
743     printf
744         ("maxquota = %d, minquota = %d, maxfiles = %d, filecount = %d, diskused = %d\n",
745          V_maxquota(vp), V_minquota(vp), V_maxfiles(vp), V_filecount(vp),
746          V_diskused(vp));
747     printf("creationDate = %s, copyDate = %s\n", date(V_creationDate(vp)),
748            date(V_copyDate(vp)));
749     printf("backupDate = %s, expirationDate = %s\n", date(V_backupDate(vp)),
750            date(V_expirationDate(vp)));
751     printf("accessDate = %s, updateDate = %s\n", date(V_accessDate(vp)),
752            date(V_updateDate(vp)));
753     printf("owner = %u, accountNumber = %u\n", V_owner(vp),
754            V_accountNumber(vp));
755     printf
756         ("dayUse = %u; week = (%u, %u, %u, %u, %u, %u, %u), dayUseDate = %s\n",
757          V_dayUse(vp), V_weekUse(vp)[0], V_weekUse(vp)[1], V_weekUse(vp)[2],
758          V_weekUse(vp)[3], V_weekUse(vp)[4], V_weekUse(vp)[5],
759          V_weekUse(vp)[6], date(V_dayUseDate(vp)));
760     printf("volUpdateCounter = %u\n", V_volUpCounter(vp));
761 }
762
763 /* GetFileInfo
764  * OS independent file info. Die on failure.
765  */
766 #ifdef AFS_NT40_ENV
767 char *
768 NT_date(FILETIME * ft)
769 {
770     static char result[8][64];
771     static int next = 0;
772     SYSTEMTIME st;
773     FILETIME lft;
774
775     if (!FileTimeToLocalFileTime(ft, &lft)
776         || !FileTimeToSystemTime(&lft, &st)) {
777         fprintf(stderr, "%s: Time conversion failed.\n", progname);
778         exit(1);
779     }
780     sprintf(result[next = ((next + 1) & 7)], "%4d/%02d/%02d.%2d:%2d:%2d",
781             st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
782     return result[next];
783 }
784 #endif
785
786 static void
787 GetFileInfo(FD_t fd, int *size, char **ctime, char **mtime, char **atime)
788 {
789 #ifdef AFS_NT40_ENV
790     BY_HANDLE_FILE_INFORMATION fi;
791     if (!GetFileInformationByHandle(fd, &fi)) {
792         fprintf(stderr, "%s: GetFileInformationByHandle failed, exiting\n",
793                 progname);
794         exit(1);
795     }
796     *size = (int)fi.nFileSizeLow;
797     *ctime = "N/A";
798     *mtime = NT_date(&fi.ftLastWriteTime);
799     *atime = NT_date(&fi.ftLastAccessTime);
800 #else
801     struct afs_stat_st status;
802     if (afs_fstat(fd, &status) == -1) {
803         fprintf(stderr, "%s: fstat failed %d\n", progname, errno);
804         exit(1);
805     }
806     *size = (int)status.st_size;
807     *ctime = date(status.st_ctime);
808     *mtime = date(status.st_mtime);
809     *atime = date(status.st_atime);
810 #endif
811 }
812
813 /**
814  * Copy the inode data to a file in the current directory.
815  *
816  * @param[in] vp     volume object
817  * @param[in] vnode  vnode object
818  * @param[in] inode  inode of the source file
819  *
820  * @returns none
821  */
822 static void
823 SaveInode(Volume * vp, struct VnodeDiskObject *vnode, Inode ino)
824 {
825     IHandle_t *ih;
826     FdHandle_t *fdP;
827     char nfile[50], buffer[256];
828     int ofd = 0;
829     afs_foff_t total;
830     ssize_t len;
831
832     IH_INIT(ih, V_device(vp), V_parentId(vp), ino);
833     fdP = IH_OPEN(ih);
834     if (fdP == NULL) {
835         fprintf(stderr,
836                 "%s: Can't open inode %s error %d (ignored)\n",
837                 progname, PrintInode(NULL, ino), errno);
838         return;
839     }
840     snprintf(nfile, sizeof nfile, "TmpInode.%s", PrintInode(NULL, ino));
841     ofd = afs_open(nfile, O_CREAT | O_RDWR | O_TRUNC, 0600);
842     if (ofd < 0) {
843         fprintf(stderr,
844                 "%s: Can't create file %s; error %d (ignored)\n",
845                 progname, nfile, errno);
846
847         FDH_REALLYCLOSE(fdP);
848         IH_RELEASE(ih);
849         return;
850     }
851     total = 0;
852     while (1) {
853         ssize_t nBytes;
854         len = FDH_PREAD(fdP, buffer, sizeof(buffer), total);
855         if (len < 0) {
856             FDH_REALLYCLOSE(fdP);
857             IH_RELEASE(ih);
858             close(ofd);
859             unlink(nfile);
860             fprintf(stderr,
861                     "%s: Error while reading from inode %s (%d)\n",
862                     progname, PrintInode(NULL, ino), errno);
863             return;
864         }
865         if (len == 0)
866             break;              /* No more input */
867         nBytes = write(ofd, buffer, len);
868         if (nBytes != len) {
869             FDH_REALLYCLOSE(fdP);
870             IH_RELEASE(ih);
871             close(ofd);
872             unlink(nfile);
873             fprintf(stderr,
874                     "%s: Error while writing to \"%s\" (%d - ignored)\n",
875                     progname, nfile, errno);
876             return;
877         }
878         total += len;
879     }
880
881     FDH_REALLYCLOSE(fdP);
882     IH_RELEASE(ih);
883     close(ofd);
884     printf("... Copied inode %s to file %s (%lu bytes)\n",
885            PrintInode(NULL, ino), nfile, (unsigned long)total);
886 }
887
888 void
889 PrintVnodes(Volume * vp, VnodeClass class)
890 {
891     afs_int32 diskSize =
892         (class == vSmall ? SIZEOF_SMALLDISKVNODE : SIZEOF_LARGEDISKVNODE);
893     char buf[SIZEOF_LARGEDISKVNODE];
894     struct VnodeDiskObject *vnode = (struct VnodeDiskObject *)buf;
895     StreamHandle_t *file;
896     int vnodeIndex, nVnodes;
897     afs_foff_t offset = 0;
898     Inode ino;
899     IHandle_t *ih = vp->vnodeIndex[class].handle;
900     FdHandle_t *fdP;
901     int size;
902     char *ctime, *atime, *mtime;
903
904     fdP = IH_OPEN(ih);
905     if (fdP == NULL) {
906         fprintf(stderr, "%s: open failed: ", progname);
907         exit(1);
908     }
909
910     file = FDH_FDOPEN(fdP, "r");
911     if (!file) {
912         fprintf(stderr, "%s: fdopen failed\n", progname);
913         exit(1);
914     }
915
916     GetFileInfo(fdP->fd_fd, &size, &ctime, &atime, &mtime);
917     if (InodeTimes && !dsizeOnly) {
918         printf("ichanged : %s\nimodified: %s\niaccessed: %s\n\n", ctime,
919                mtime, atime);
920     }
921
922     nVnodes = (size / diskSize) - 1;
923     if (nVnodes > 0) {
924         STREAM_ASEEK(file, diskSize);
925     } else
926         nVnodes = 0;
927
928     for (vnodeIndex = 0;
929          nVnodes && STREAM_READ(vnode, diskSize, 1, file) == 1;
930          nVnodes--, vnodeIndex++, offset += diskSize) {
931
932         ino = VNDISK_GET_INO(vnode);
933
934         if (saveinodes) {
935             if (!VALID_INO(ino)) {
936                 continue;
937             }
938             if (dsizeOnly && (class == vLarge)) {
939                 afs_fsize_t fileLength;
940
941                 VNDISK_GET_LEN(fileLength, vnode);
942                 if (fileLength > 0) {
943                     volumeTotals.vnodesize += fileLength;
944                     volumeTotals.vnodesize_k += fileLength / 1024;
945                 }
946             } else if (class == vSmall) {
947                 SaveInode(vp, vnode, ino);
948             }
949         } else {
950             PrintVnode(offset, vnode,
951                        bitNumberToVnodeNumber(vnodeIndex, class), ino, vp);
952         }
953     }
954     STREAM_CLOSE(file);
955     FDH_CLOSE(fdP);
956 }
957
958 void
959 PrintVnode(afs_foff_t offset, VnodeDiskObject * vnode, VnodeId vnodeNumber,
960            Inode ino, Volume * vp)
961 {
962 #if defined(AFS_NAMEI_ENV)
963     IHandle_t *ihtmpp;
964     namei_t filename;
965 #endif
966     afs_fsize_t fileLength;
967
968     VNDISK_GET_LEN(fileLength, vnode);
969     if (fileLength > 0) {
970         volumeTotals.vnodesize += fileLength;
971         volumeTotals.vnodesize_k += fileLength / 1024;
972     }
973     if (dsizeOnly)
974         return;
975     if (orphaned && (fileLength == 0 || vnode->parent || !offset))
976         return;
977     printf
978         ("%10lld Vnode %u.%u.%u cloned: %u, length: %llu linkCount: %d parent: %u",
979          (long long)offset, vnodeNumber, vnode->uniquifier, vnode->dataVersion,
980          vnode->cloned, (afs_uintmax_t) fileLength, vnode->linkCount,
981          vnode->parent);
982     if (DumpInodeNumber)
983         printf(" inode: %s", PrintInode(NULL, ino));
984     if (DumpDate)
985         printf(" ServerModTime: %s", date(vnode->serverModifyTime));
986 #if defined(AFS_NAMEI_ENV)
987     if (PrintFileNames) {
988         IH_INIT(ihtmpp, V_device(vp), V_parentId(vp), ino);
989         namei_HandleToName(&filename, ihtmpp);
990 #if !defined(AFS_NT40_ENV)
991         printf(" UFS-Filename: %s", filename.n_path);
992 #else
993         printf(" NTFS-Filename: %s", filename.n_path);
994 #endif
995     }
996 #endif
997     printf("\n");
998 }