prototypes-fixes-20020821
[openafs.git] / src / vol / vol-salvage.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-salvage.c
13  *      Institution:    The Information Technology Center, Carnegie-Mellon University
14  */
15
16 /*  1.2 features:
17         Correct handling of bad "." and ".." entries.
18         Message if volume has "destroyMe" flag set--but doesn't delete yet.
19         Link count bug fixed--bug was that vnodeEssence link count was unsigned
20         14 bits.  Needs to be signed.
21
22     1.3 features:
23         Change to DirHandle stuff to make sure that cache entries are reused at the
24         right time (this parallels the file server change, but is not identical).
25
26         Added calls to directory salvager routines; doesn't salvage dir unless debug=1.
27
28     1.4 features:
29         Fixed bug which was causing inode link counts to go bad (thus leaking
30         disk blocks).
31 Vnodes with 0 inode pointers in RW volumes are now deleted.
32         An inode with a matching inode number to the vnode is preferred to an
33         inode with a higer data version.
34         Bug is probably fixed that was causing data version to remain wrong,
35         despite assurances from the salvager to the contrary.
36
37     1.5 features:
38         Added limited salvaging:  unless ForceSalvage is on, then the volume will
39         not be salvaged if the dontSalvage flag is set in the Volume Header.
40         The ForceSalvage flag is turned on if an individual volume is salvaged or
41         if the file FORCESALVAGE exists in the partition header of the file system
42         being salvaged.  This isn't used for anything but could be set by vfsck.
43         A -f flag was also added to force salvage.
44
45     1.6 features:
46         It now deletes obsolete volume inodes without complaining
47
48     1.7 features:
49         Repairs rw volume headers (again).
50
51     1.8 features:
52         Correlates volume headers & inodes correctly, thus preventing occasional deletion
53         of read-only volumes...
54         No longer forces a directory salvage for volume 144 (which may be a good volume
55         at some other site!)
56         Some of the messages are cleaned up or made more explicit.  One or two added.
57         Logging cleaned up.
58         A bug was fixed which forced salvage of read-only volumes without a corresponding
59         read/write volume.
60
61     1.9 features:
62         When a volume header is recreated, the new name will be "bogus.volume#"
63
64     2.0 features:
65         Directory salvaging turned on!!!
66
67     2.1 features:
68         Prints warning messages for setuid programs.
69
70     2.2 features:
71         Logs missing inode numbers.
72
73     2.3 features:
74             Increments directory version number by 200 (rather than by 1) when it is salvaged, in order to prevent problems due to the fact that a version number can be promised to a workstation before it is written to disk.  If the server crashes, it may have an older version.  Salvaging it could bring the version number up to the same version the workstation believed it already had a call back on. 
75
76     2.4 features:
77             Locks the file /vice/vol/salvage.lock before starting.  Aborts if it can't acquire the lock.
78             Time stamps on log entries.
79             Fcntl on stdout to cause all entries to be appended.
80             Problems writing to temporary files are now all detected.
81             Inode summary files are now dynamically named (so that multiple salvagers wouldn't conflict).
82             Some cleanup of error messages.
83 */
84
85
86 #define SalvageVersion "2.4"
87
88 /* Main program file. Define globals. */
89 #define MAIN 1
90
91 #include <afsconfig.h>
92 #include <afs/param.h>
93
94 RCSID("$Header$");
95
96 #include <stdlib.h>
97 #include <stdio.h>
98 #include <string.h>
99 #include <dirent.h>
100 #include <sys/stat.h>
101 #include <time.h>
102 #include <errno.h>
103 #ifdef AFS_NT40_ENV
104 #include <io.h>
105 #include <WINNT/afsevent.h>
106 #else
107 #include <sys/param.h>
108 #include <sys/file.h>
109 #ifndef ITIMER_REAL
110 #include <sys/time.h>
111 #endif /* ITIMER_REAL */
112 #endif
113 #if     defined(AFS_AIX_ENV) || defined(AFS_SUN4_ENV)
114 #define WCOREDUMP(x)    (x & 0200)
115 #endif
116 #include <rx/xdr.h>
117 #include <afs/afsint.h>
118 #include <afs/assert.h>
119 #if !defined(AFS_SGI_ENV) && !defined(AFS_NT40_ENV)
120 #if defined(AFS_VFSINCL_ENV)
121 #include <sys/vnode.h>
122 #ifdef  AFS_SUN5_ENV
123 #include <sys/fs/ufs_inode.h>
124 #else
125 #if defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
126 #include <ufs/ufs/dinode.h>
127 #include <ufs/ffs/fs.h>
128 #else
129 #include <ufs/inode.h>
130 #endif
131 #endif
132 #else /* AFS_VFSINCL_ENV */
133 #ifdef  AFS_OSF_ENV
134 #include <ufs/inode.h>
135 #else   /* AFS_OSF_ENV */
136 #if !defined(AFS_LINUX20_ENV) && !defined(AFS_XBSD_ENV)
137 #include <sys/inode.h>
138 #endif
139 #endif
140 #endif /* AFS_VFSINCL_ENV */
141 #endif /* AFS_SGI_ENV */
142 #ifdef  AFS_AIX_ENV
143 #include <sys/vfs.h>
144 #include <sys/lockf.h>
145 #else
146 #ifdef  AFS_HPUX_ENV
147 #include <unistd.h>
148 #include <checklist.h>
149 #else
150 #if defined(AFS_SGI_ENV)
151 #include <unistd.h>
152 #include <fcntl.h>
153 #include <mntent.h>
154 #else
155 #if     defined(AFS_SUN_ENV) || defined(AFS_SUN5_ENV)
156 #ifdef    AFS_SUN5_ENV
157 #include <unistd.h>
158 #include <sys/mnttab.h>
159 #include <sys/mntent.h>
160 #else
161 #include <mntent.h>
162 #endif
163 #else
164 #endif /* AFS_SGI_ENV */
165 #endif /* AFS_HPUX_ENV */
166 #endif
167 #endif
168 #include <fcntl.h>
169 #ifndef AFS_NT40_ENV
170 #include <afs/osi_inode.h>
171 #endif
172 #include <afs/cmd.h>
173 #include <afs/afsutil.h>
174 #include <afs/fileutil.h>
175 #include <afs/procmgmt.h>  /* signal(), kill(), wait(), etc. */
176 #ifndef AFS_NT40_ENV
177 #include <syslog.h>
178 #endif
179
180 #include "nfs.h"
181 #include "lwp.h"
182 #include "lock.h"
183 #include <afs/afssyscalls.h>
184 #include "ihandle.h"
185 #include "vnode.h"
186 #include "volume.h"
187 #include "partition.h"
188 #include "fssync.h"
189 #include "viceinode.h"
190 #include "salvage.h"
191 #include "volinodes.h"  /* header magic number, etc. stuff */
192 #ifdef AFS_NT40_ENV
193 #include <pthread.h>
194 #endif
195
196 #ifdef  AFS_OSF_ENV
197 extern void *calloc();
198 #endif
199 extern char *vol_DevName();
200 static char *TimeStamp(time_t clock, int precision);
201
202 #define ORPH_IGNORE 0
203 #define ORPH_REMOVE 1
204 #define ORPH_ATTACH 2
205
206
207 int     debug;                  /* -d flag */
208 int     Testing=0;              /* -n flag */
209 int     ListInodeOption;        /* -i flag */
210 int     ShowRootFiles;          /* -r flag */
211 int     RebuildDirs;            /* -sal flag */
212 int     Parallel = 4;           /* -para X flag */
213 int     PartsPerDisk = 8;       /* Salvage up to 8 partitions on same disk sequentially */
214 int     forceR = 0;             /* -b flag */
215 int     ShowLog = 0;            /* -showlog flag */
216 int     ShowSuid = 0;           /* -showsuid flag */
217 int     ShowMounts = 0;         /* -showmounts flag */
218 int     orphans = ORPH_IGNORE;  /* -orphans option */
219 int     Showmode = 0;
220
221 #ifndef AFS_NT40_ENV
222 int useSyslog = 0;      /* -syslog flag */
223 int useSyslogFacility = LOG_DAEMON; /* -syslogfacility option */
224 #endif
225
226 #define MAXPARALLEL     32
227
228 int     OKToZap;                /* -o flag */
229 int     ForceSalvage;           /* If salvage should occur despite the DONT_SALVAGE flag
230                                    in the volume header */
231
232 static FILE *logFile = 0;       /* one of {/usr/afs/logs,/vice/file}/SalvageLog */
233
234 #define ROOTINODE       2       /* Root inode of a 4.2 Unix file system
235                                    partition */
236 Device  fileSysDevice;          /* The device number of the current
237                                    partition being salvaged */
238 #ifdef AFS_NT40_ENV
239 char fileSysPath[8];
240 #else
241 char    *fileSysPath;           /* The path of the mounted partition currently
242                                    being salvaged, i.e. the directory
243                                    containing the volume headers */
244 #endif
245 char    *fileSysPathName;       /* NT needs this to make name pretty in log. */
246 IHandle_t *VGLinkH;             /* Link handle for current volume group. */
247 int VGLinkH_cnt;                /* # of references to lnk handle. */
248 struct DiskPartition *fileSysPartition; /* Partition  being salvaged */
249 #ifndef AFS_NT40_ENV
250 char    *fileSysDeviceName;     /* The block device where the file system
251                                    being salvaged was mounted */
252 char    *filesysfulldev;
253 #endif
254 int     VolumeChanged;          /* Set by any routine which would change the volume in
255                                    a way which would require callback is to be broken if the
256                                    volume was put back on line by an active file server */
257     
258 VolumeDiskData  VolInfo;        /* A copy of the last good or salvaged volume header dealt with */
259
260 struct InodeSummary {           /* Inode summary file--an entry for each
261                                    volume in the inode file for a partition */
262     VolId       volumeId;       /* Volume id */
263     VolId       RWvolumeId;     /* RW volume associated */
264     int         index;          /* index into inode file (0, 1, 2 ...) */
265     int         nInodes;        /* Number of inodes for this volume */
266     int         nSpecialInodes; /* Number of special inodes, i.e.  volume
267                                    header, index, etc.  These are all
268                                    marked (viceinode.h) and will all be sorted
269                                    to the beginning of the information for
270                                    this volume.  Read-only volumes should
271                                    ONLY have special inodes (all the other
272                                    inodes look as if they belong to the
273                                    original RW volume). */
274     Unique      maxUniquifier;  /* The maximum uniquifier found in all the inodes.
275                                    This is only useful for RW volumes and is used
276                                    to compute a new volume uniquifier in the event
277                                    that the header needs to be recreated. The inode
278                                    uniquifier may be a truncated version of vnode
279                                    uniquifier (AFS_3DISPARES). The real maxUniquifer
280                                    is from the vnodes and later calcuated from it */
281     struct VolumeSummary *volSummary;
282                                  /* Either a pointer to the original volume
283                                     header summary, or constructed summary
284                                     information */
285 } *inodeSummary;
286 #define readOnly(isp)   ((isp)->volumeId != (isp)->RWvolumeId)
287 int nVolumesInInodeFile;        /* Number of read-write volumes summarized */
288 int inodeFd;                    /* File descriptor for inode file */
289
290
291 struct VolumeSummary {          /* Volume summary an entry for each
292                                    volume in a volume directory.
293                                    Assumption: one volume directory per
294                                    partition */
295     char        *fileName;      /* File name on the partition for the volume
296                                    header */
297     struct      VolumeHeader header;
298                                 /* volume number, rw volume number, inode
299                                    numbers of each major component of
300                                    the volume */
301     IHandle_t   *volumeInfoHandle;
302     byte        wouldNeedCallback; /* set if the file server should issue
303                                       call backs for all the files in this volume when
304                                       the volume goes back on line */
305 };
306
307 struct VnodeInfo {
308     IHandle_t *handle;      /* Inode containing this index */
309     int nVnodes;            /* Total number of vnodes in index */
310     int nAllocatedVnodes;   /* Total number actually used */
311     int volumeBlockCount;   /* Total number of blocks used by volume */
312     Inode *inodes;          /* Directory only */
313     struct VnodeEssence {
314         short count;        /* Number of references to vnode; MUST BE SIGNED */
315         unsigned claimed:1; /* Set when a parent directory containing an entry
316                               referencing this vnode is found.  The claim
317                               is that the parent in "parent" can point to
318                               this vnode, and no other */
319         unsigned changed:1; /* Set if any parameters (other than the count)
320                               in the vnode change.   It is determined if the
321                               link count has changed by noting whether it is
322                               0 after scanning all directories */
323         unsigned salvaged:1;/* Set if this directory vnode has already been salvaged. */        
324         unsigned todelete:1;/* Set if this vnode is to be deleted (should not be claimed) */
325         afs_uint32 blockCount;
326                             /* Number of blocks (1K) used by this vnode,
327                                approximately */
328         VnodeId parent;     /* parent in vnode */
329         Unique  unique;     /* Must match entry! */
330        char *name;         /* Name of directory entry */
331        int modeBits;      /* File mode bits */
332        Inode InodeNumber; /* file's inode */
333        int type;          /* File type */
334        int author;        /* File author */
335        int owner;         /* File owner */
336        int group;         /* File group */
337     } *vnodes;
338 } vnodeInfo[nVNODECLASSES];
339     
340 struct DirSummary {
341     struct DirHandle dirHandle;
342     VnodeId vnodeNumber;
343     Unique unique;
344     unsigned haveDot, haveDotDot;
345     VolumeId rwVid;
346     int copied; /* If the copy-on-write stuff has been applied */
347     VnodeId parent;
348     char *name;
349     char *vname;
350     IHandle_t *ds_linkH;
351 };
352
353
354 struct VolumeSummary  *volumeSummaryp;  /* Holds all the volumes in a part */
355 int nVolumes;                   /* Number of volumes (read-write and read-only)
356                                    in volume summary */
357
358 #ifdef AFS_NT40_ENV
359 /* For NT, we can fork the per partition salvagers to gain the required
360  * safety against Aborts. But there's too many complex data structures at
361  * the per volume salvager layer to easilty copy the data across.
362  * childJobNumber is resset from -1 to the job number if this is a
363  * per partition child of the main salvager. This information is passed
364  * out-of-band in the extra data area setup for the now unused parent/child
365  * data transfer.
366  */
367 #define SALVAGER_MAGIC 0x00BBaaDD
368 #define NOT_CHILD -1  /* job numbers start at 0 */
369 /* If new options need to be passed to child, add them here. */
370 typedef struct {
371     int cj_magic;
372     int cj_number;
373     char cj_part[32];
374 } childJob_t;
375
376 /* Child job this process is running. */
377 childJob_t myjob = { SALVAGER_MAGIC, NOT_CHILD};
378
379 int nt_SalvagePartition(char *partName, int jobn);
380 int nt_SetupPartitionSalvage(void *datap, int len);
381
382 typedef struct {
383     struct InodeSummary *svgp_inodeSummaryp;
384     int svgp_count;
385 } SVGParms_t;
386 #define canfork 0
387 #else
388 #define canfork 1
389 #endif
390
391
392
393 /* Forward declarations */
394 void Log(), Abort(), Exit();
395 int Fork(void);
396 int Wait(char *prog);
397 char * ToString(char *s);
398 void AskOffline(VolumeId volumeId);
399 void AskOnline(VolumeId volumeId, char *partition);
400 void CheckLogFile(void);
401 void ClearROInUseBit(struct VolumeSummary *summary);
402 void CopyAndSalvage(register struct DirSummary *dir);
403 int CopyInode(Device device, Inode inode1, Inode inode2, int rwvolume);
404 void CopyOnWrite(register struct DirSummary *dir);
405 void CountVolumeInodes(register struct ViceInodeInfo *ip, int maxInodes,
406                        register struct InodeSummary * summary);
407 void DeleteExtraVolumeHeaderFile(register struct VolumeSummary *vsp);
408 void DistilVnodeEssence(VolumeId vid, VnodeClass class, Inode ino,
409                         Unique *maxu);
410 int GetInodeSummary(char *path, VolumeId singleVolumeNumber);
411 void GetVolumeSummary(VolumeId singleVolumeNumber);
412 void JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
413                 Unique unique);
414 void MaybeZapVolume(register struct InodeSummary *isp, char *message,
415                     int deleteMe,
416                     int check);
417 void ObtainSalvageLock(void);
418 void PrintInodeList(void);
419 void PrintInodeSummary(void);
420 void PrintVolumeSummary(void);
421 int QuickCheck(register struct InodeSummary *isp, int nVols);
422 void RemoveTheForce(char *path);
423 void SalvageDir(char *name, VolumeId rwVid, struct VnodeInfo *dirVnodeInfo,
424                 IHandle_t *alinkH, int i, struct DirSummary *rootdir,
425                 int *rootdirfound);
426 void SalvageFileSysParallel(struct DiskPartition *partP);
427 void SalvageFileSys(struct DiskPartition *partP, VolumeId singleVolumeNumber);
428 void SalvageFileSys1(struct DiskPartition *partP, VolumeId singleVolumeNumber);
429 int SalvageHeader(register struct stuff *sp, struct InodeSummary *isp,
430                   int check, int *deleteMe); 
431 int SalvageIndex(Inode ino, VnodeClass class, int RW,
432                  register struct ViceInodeInfo *ip,
433                  int nInodes, struct VolumeSummary *volSummary, int check);
434 int SalvageVnodes(register struct InodeSummary *rwIsp,
435                   register struct InodeSummary * thisIsp,
436                   register struct ViceInodeInfo * inodes, int check);
437 int SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t *alinkH);
438 void DoSalvageVolumeGroup(register struct InodeSummary *isp, int nVols);
439 #ifdef AFS_NT40_ENV
440 void SalvageVolumeGroup(register struct InodeSummary *isp, int nVols);
441 #else
442 #define SalvageVolumeGroup DoSalvageVolumeGroup
443 #endif
444 int SalvageVolumeHeaderFile(register struct InodeSummary *isp,
445                             register struct ViceInodeInfo *inodes,
446                             int RW, int check, int *deleteMe);
447 void showlog(void);
448 int UseTheForceLuke(char *path);
449
450 static int IsVnodeOrphaned(VnodeId vnode);
451
452 /* Uniquifier stored in the Inode */
453 static Unique IUnique(u)
454   Unique u;
455 {
456 #ifdef  AFS_3DISPARES
457   return(u & 0x3fffff);
458 #else
459 #if defined(AFS_SGI_EXMAG)
460   return(u & SGI_UNIQMASK);
461 #else
462   return(u);
463 #endif /* AFS_SGI_EXMAG */
464 #endif
465 }
466
467 static int BadError(aerror)
468 register int aerror; {
469     if (aerror == EPERM || aerror == ENXIO || aerror == ENOENT)
470         return 1;
471     return 0;   /* otherwise may be transient, e.g. EMFILE */
472 }
473
474
475 char *tmpdir = 0;
476 static handleit(as)
477     struct cmd_syndesc *as;
478 {
479     register struct cmd_item *ti;
480     char pname[100], *temp;
481     afs_int32 seenpart = 0, seenvol = 0, vid = 0, seenany = 0, i;
482     struct DiskPartition *partP;
483
484 #ifdef AFS_SGI_VNODE_GLUE
485     if (afs_init_kernel_config(-1) <0) {
486         printf("Can't determine NUMA configuration, not starting salvager.\n");
487         exit(1);
488     }
489 #endif
490
491 #ifdef FAST_RESTART
492     for (i = 0; i < CMD_MAXPARMS; i++) {
493         if (as->parms[i].items) {
494             seenany = 1;
495             break;
496         }
497     }
498     if (!seenany) {
499       printf("Exiting immediately without salvage. Look into the FileLog to find volumes which really need to be salvaged!\n");
500       Exit(0);
501     }
502 #endif /* FAST_RESTART */
503     if (ti = as->parms[0].items) {      /* -partition */
504         seenpart = 1;
505         strncpy(pname, ti->data, 100);
506     }
507     if (ti = as->parms[1].items) {      /* -volumeid */
508         if (!seenpart) {
509             printf("You must also specify '-partition' option with the '-volumeid' option\n");
510             exit(-1);
511         }
512         seenvol = 1;
513         vid = atoi(ti->data);
514     }
515     if (as->parms[2].items)     /* -debug */
516         debug = 1;
517     if (as->parms[3].items)     /* -nowrite */
518         Testing = 1;
519     if (as->parms[4].items)     /* -inodes */
520         ListInodeOption = 1;
521     if (as->parms[5].items)     /* -force */
522         ForceSalvage = 1;
523     if (as->parms[6].items)     /* -oktozap */
524         OKToZap = 1;
525     if (as->parms[7].items)     /* -rootinodes */
526         ShowRootFiles = 1;
527     if (as->parms[8].items)     /* -RebuildDirs */
528         RebuildDirs = 1;
529     if (as->parms[9].items)     /* -ForceReads */
530         forceR = 1;
531     if (ti = as->parms[10].items) {/* -Parallel # */
532         temp = ti->data;
533         if (strncmp(temp,"all",3) == 0) {
534            PartsPerDisk = 1;
535            temp += 3;
536         }
537         if (strlen(temp) != 0) {
538            Parallel = atoi(temp);
539            if (Parallel < 1) Parallel = 1;
540            if (Parallel > MAXPARALLEL) {
541               printf("Setting parallel salvages to maximum of %d \n", MAXPARALLEL);
542               Parallel = MAXPARALLEL;
543            }
544         }
545     }
546     if (ti = as->parms[11].items) {/* -tmpdir */
547         DIR *dirp;
548
549         tmpdir = ti->data;
550         dirp = opendir(tmpdir);
551         if (!dirp) {
552             printf("Can't open temporary placeholder dir %s; using current partition \n", tmpdir);
553             tmpdir = NULL;
554         } else
555             closedir(dirp);
556     }
557     if (ti = as->parms[12].items) /* -showlog */
558         ShowLog = 1;
559     if (ti = as->parms[13].items) { /* -log */
560         Testing = 1;
561         ShowSuid = 1;
562         Showmode = 1;
563     }
564     if (ti = as->parms[14].items) { /* -showmounts */
565         Testing = 1;
566         Showmode = 1;
567         ShowMounts = 1;
568     }
569     if (ti = as->parms[15].items) { /* -orphans */
570        if (Testing)
571           orphans = ORPH_IGNORE;
572        else if (strcmp(ti->data, "remove")==0 || strcmp(ti->data, "r")==0)
573           orphans = ORPH_REMOVE;
574        else if (strcmp(ti->data, "attach")==0 || strcmp(ti->data, "a")==0)
575           orphans = ORPH_ATTACH;
576     }
577
578 #ifndef AFS_NT40_ENV /* ignore options on NT */
579         if ( ti = as->parms[16].items) { /* -syslog */
580                 useSyslog = 1;
581                 ShowLog = 0;
582         }
583         if ( ti = as->parms[17].items) { /* -syslogfacility */
584                 useSyslogFacility = atoi(ti->data);
585         }
586 #endif
587
588
589 #ifdef FAST_RESTART
590     if (ti = as->parms[18].items) {  /* -DontSalvage */
591       printf("Exiting immediately without salvage. Look into the FileLog");
592       printf(" to find volumes which really need to be salvaged!\n");
593       Exit(0);
594     }
595 #endif /* FAST_RESTART */ 
596  
597     /* Note:  if seemvol we initialize this as a standard volume utility:  this has the
598        implication that the file server may be running; negotations have to be made with
599        the file server in this case to take the read write volume and associated read-only
600        volumes off line before salvaging */
601 #ifdef AFS_NT40_ENV
602     if (seenvol) {
603         if (afs_winsockInit()<0) {
604             ReportErrorEventAlt(AFSEVT_SVR_WINSOCK_INIT_FAILED, 0,
605                                 AFSDIR_SALVAGER_FILE, 0);
606             Log("Failed to initailize winsock, exiting.\n");
607             Exit(1);
608         }
609     }
610 #endif
611     VInitVolumePackage(seenvol ? volumeUtility: salvager, 5, 5, DONT_CONNECT_FS, 0);
612     DInit(10);
613 #ifdef AFS_NT40_ENV
614     if (myjob.cj_number != NOT_CHILD) {
615         if (!seenpart) {
616             seenpart = 1;
617             (void) strcpy(pname, myjob.cj_part);
618         }
619     }
620 #endif
621     if (seenpart == 0) {
622        for (partP = DiskPartitionList; partP; partP = partP->next) {
623            SalvageFileSysParallel(partP);
624        }
625        SalvageFileSysParallel(0);
626     }
627     else {
628         partP = VGetPartition(pname, 0);
629         if (!partP) {
630             Log("salvage: Unknown or unmounted partition %s; salvage aborted\n",
631                 pname);
632             Exit(1);
633         }
634         if (!seenvol)
635             SalvageFileSys(partP, 0);
636         else  {
637             /* Salvage individual volume */
638             if (vid <= 0) {
639                 Log("salvage: invalid volume id specified; salvage aborted\n");
640                 Exit(1);
641             }
642             SalvageFileSys (partP, vid);
643         }
644     }
645     return (0);
646 }
647
648
649 #ifndef AFS_NT40_ENV
650 #include "AFS_component_version_number.c"
651 #endif
652 #define MAX_ARGS 128
653 #ifdef AFS_NT40_ENV
654 char *save_args[MAX_ARGS];
655 int n_save_args = 0;
656 pthread_t main_thread;
657 #endif
658
659 main(argc,argv)
660 char **argv;
661 {
662     struct cmd_syndesc *ts;
663     int err = 0;
664     char commandLine[150];
665     
666     int i;
667     extern char cml_version_number[];
668
669 #ifdef  AFS_AIX32_ENV
670     /*
671      * The following signal action for AIX is necessary so that in case of a 
672      * crash (i.e. core is generated) we can include the user's data section 
673      * in the core dump. Unfortunately, by default, only a partial core is
674      * generated which, in many cases, isn't too useful.
675      */
676     struct sigaction nsa;
677     
678     sigemptyset(&nsa.sa_mask);
679     nsa.sa_handler = SIG_DFL;
680     nsa.sa_flags = SA_FULLDUMP;
681     sigaction(SIGABRT, &nsa, NULL);
682     sigaction(SIGSEGV, &nsa, NULL);
683 #endif
684
685     /* Initialize directory paths */
686     if (!(initAFSDirPath() & AFSDIR_SERVER_PATHS_OK)) {
687 #ifdef AFS_NT40_ENV
688         ReportErrorEventAlt(AFSEVT_SVR_NO_INSTALL_DIR, 0, argv[0], 0);
689 #endif
690         fprintf(stderr,"%s: Unable to obtain AFS server directory.\n", argv[0]);
691         exit(2);
692     }
693 #ifdef AFS_NT40_ENV
694     main_thread = pthread_self();
695     if (spawnDatap && spawnDataLen) {
696         /* This is a child per partition salvager. Don't setup log or
697          * try to lock the salvager lock.
698          */
699         if (nt_SetupPartitionSalvage(spawnDatap, spawnDataLen)<0)
700             exit(3);
701     }
702     else {
703 #endif
704         for (commandLine[0] = '\0', i=0; i<argc; i++) {
705             if (i > 0) strcat(commandLine, " ");
706             strcat(commandLine, argv[i]);
707         }
708
709         /* All entries to the log will be appended.  Useful if there are
710          * multiple salvagers appending to the log.
711          */
712         
713         CheckLogFile();
714 #ifndef AFS_NT40_ENV
715 #ifdef AFS_LINUX20_ENV
716         fcntl(fileno(logFile), F_SETFL, O_APPEND); /* Isn't this redundant? */
717 #else
718         fcntl(fileno(logFile), F_SETFL, FAPPEND); /* Isn't this redundant? */
719 #endif
720 #endif
721         setlinebuf(logFile);
722         
723 #ifndef AFS_NT40_ENV
724         if (geteuid() != 0) {
725             printf("Salvager must be run as root.\n");
726             fflush(stdout);
727             Exit(0);
728         }
729 #endif
730         
731         /* bad for normal help flag processing, but can do nada */
732         
733         fprintf(logFile, "%s\n", cml_version_number);
734         Log("STARTING AFS SALVAGER %s (%s)\n", SalvageVersion, commandLine);
735
736         /* Get and hold a lock for the duration of the salvage to make sure
737          * that no other salvage runs at the same time.  The routine
738          * VInitVolumePackage (called below) makes sure that a file server or
739          * other volume utilities don't interfere with the salvage.
740          */
741         ObtainSalvageLock();
742 #ifdef AFS_NT40_ENV
743     }
744 #endif
745
746     ts = cmd_CreateSyntax("initcmd", handleit, 0, "initialize the program");
747     cmd_AddParm(ts, "-partition", CMD_SINGLE,CMD_OPTIONAL, "Name of partition to salvage");
748     cmd_AddParm(ts, "-volumeid", CMD_SINGLE,CMD_OPTIONAL, "Volume Id to salvage");
749     cmd_AddParm(ts, "-debug", CMD_FLAG,CMD_OPTIONAL, "Run in Debugging mode");
750     cmd_AddParm(ts, "-nowrite", CMD_FLAG,CMD_OPTIONAL, "Run readonly/test mode");
751     cmd_AddParm(ts, "-inodes", CMD_FLAG,CMD_OPTIONAL, "Just list affected afs inodes - debugging flag");
752     cmd_AddParm(ts, "-force", CMD_FLAG,CMD_OPTIONAL, "Force full salvaging");
753     cmd_AddParm(ts, "-oktozap", CMD_FLAG,CMD_OPTIONAL, "Give permission to destroy bogus inodes/volumes - debugging flag");
754     cmd_AddParm(ts, "-rootinodes", CMD_FLAG,CMD_OPTIONAL, "Show inodes owned by root - debugging flag");
755     cmd_AddParm(ts, "-salvagedirs", CMD_FLAG,CMD_OPTIONAL, "Force rebuild/salvage of all directories");
756     cmd_AddParm(ts, "-blockreads", CMD_FLAG,CMD_OPTIONAL, "Read smaller blocks to handle IO/bad blocks");
757     cmd_AddParm(ts, "-parallel", CMD_SINGLE,CMD_OPTIONAL, "# of max parallel partition salvaging");
758     cmd_AddParm(ts, "-tmpdir", CMD_SINGLE,CMD_OPTIONAL, "Name of dir to place tmp files ");
759     cmd_AddParm(ts, "-showlog", CMD_FLAG,CMD_OPTIONAL, "Show log file upon completion");
760     cmd_AddParm(ts, "-showsuid", CMD_FLAG,CMD_OPTIONAL, "Report on suid/sgid files");
761     cmd_AddParm(ts, "-showmounts", CMD_FLAG,CMD_OPTIONAL, "Report on mountpoints");
762     cmd_AddParm(ts, "-orphans", CMD_SINGLE, CMD_OPTIONAL, "ignore | remove | attach");
763
764         /* note - syslog isn't avail on NT, but if we make it conditional, have
765                 to deal with screwy offsets for cmd params */
766     cmd_AddParm(ts, "-syslog", CMD_FLAG, CMD_OPTIONAL, "Write salvage log to syslogs");
767     cmd_AddParm(ts, "-syslogfacility", CMD_SINGLE, CMD_OPTIONAL, "Syslog facility number to use");
768
769 #ifdef FAST_RESTART
770     cmd_AddParm(ts, "-DontSalvage", CMD_FLAG, CMD_OPTIONAL, "Don't salvage. This my be set in BosConfig to let the fileserver restart immediately after a crash. Bad volumes will be taken offline");
771 #endif /* FAST_RESTART */
772     err = cmd_Dispatch(argc, argv);
773     Exit(err);
774 }
775
776 /* Get the salvage lock if not already held. Hold until process exits. */
777 void ObtainSalvageLock(void)
778 {
779     int salvageLock;
780    
781 #ifdef AFS_NT40_ENV
782     salvageLock = (int) CreateFile(AFSDIR_SERVER_SLVGLOCK_FILEPATH, 0, 0, NULL,
783                                    OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
784                                    NULL);
785     if (salvageLock == (int)INVALID_HANDLE_VALUE) {
786         fprintf(stderr,
787                 "salvager:  There appears to be another salvager running!  Aborted.\n");
788         Exit(1);
789     }
790 #else
791     salvageLock = open(AFSDIR_SERVER_SLVGLOCK_FILEPATH, O_CREAT|O_RDWR, 0666);
792     assert(salvageLock >= 0);
793 #ifdef AFS_DARWIN_ENV
794     if (flock(salvageLock, LOCK_EX) == -1) {
795 #else
796     if (lockf(salvageLock, F_LOCK, 0) == -1) {
797 #endif
798         fprintf(stderr,
799                 "salvager:  There appears to be another salvager running!  Aborted.\n");
800         Exit(1);
801     }
802 #endif
803 }
804
805
806 #ifdef AFS_SGI_XFS_IOPS_ENV
807 /* Check if the given partition is mounted. For XFS, the root inode is not a
808  * constant. So we check the hard way.
809  */
810 int IsPartitionMounted(char *part)
811 {
812     FILE *mntfp;
813     struct mntent *mntent;
814     
815     assert(mntfp = setmntent(MOUNTED, "r"));
816     while (mntent = getmntent(mntfp)) {
817         if (!strcmp(part, mntent->mnt_dir))
818             break;
819     }
820     endmntent(mntfp);
821     
822     return mntent ? 1 : 1;
823 }
824 #endif
825 /* Check if the given inode is the root of the filesystem. */
826 #ifndef AFS_SGI_XFS_IOPS_ENV
827 int IsRootInode(status)
828 struct stat *status;
829 {
830     /* The root inode is not a fixed value in XFS partitions. So we need to see if
831      * the partition is in the list of mounted partitions. This only affects the
832      * SalvageFileSys path, so we check there.
833      */
834     return (status->st_ino == ROOTINODE);
835 }
836 #endif
837
838 #ifdef AFS_AIX42_ENV
839 #ifndef AFS_NAMEI_ENV
840 /* We don't want to salvage big files filesystems, since we can't put volumes on
841  * them.
842  */
843 int CheckIfBigFilesFS(mountPoint, devName)
844     char *mountPoint;
845     char *devName;
846 {
847     struct superblock fs;
848     char name[128];
849
850     if (strncmp(devName, "/dev/", 5)) {
851         (void) sprintf(name, "/dev/%s", devName);
852     }
853     else {
854         (void) strcpy(name, devName);
855     }
856
857     if (ReadSuper(&fs, name)<0) {
858         Log("Unable to read superblock. Not salvaging partition %s.\n", mountPoint);
859         return 1;
860     }
861     if (IsBigFilesFileSystem(&fs)) {
862         Log("Partition %s is a big files filesystem, not salvaging.\n", mountPoint);
863         return 1;
864     }
865     return 0;
866 }
867 #endif
868 #endif
869
870 #ifdef AFS_NT40_ENV
871 #define HDSTR "\\Device\\Harddisk"
872 #define HDLEN  (sizeof(HDSTR)-1) /* Length of "\Device\Harddisk" */
873 int SameDisk(struct DiskPartition *p1, struct DiskPartition *p2)
874 {
875 #define RES_LEN 256
876     char res[RES_LEN];
877     int d1, d2;
878     static int dowarn=1;
879     
880     if (!QueryDosDevice(p1->devName, res, RES_LEN-1))
881         return 1;
882     if (strncmp(res, HDSTR, HDLEN)) {
883         if (dowarn) {
884             dowarn = 0;
885             Log("WARNING: QueryDosDevice is returning %s, not %s for %s\n",
886                 res, HDSTR, p1->devName);
887         }
888         return 1;
889     }
890     d1 = atoi(&res[HDLEN]);
891
892     if (!QueryDosDevice(p2->devName, res, RES_LEN-1))
893         return 1;
894     if (strncmp(res, HDSTR, HDLEN)) {
895         if (dowarn) {
896             dowarn = 0;
897             Log("WARNING: QueryDosDevice is returning %s, not %s for %s\n",
898                 res, HDSTR, p2->devName);
899         }
900         return 1;
901     }
902     d2 = atoi(&res[HDLEN]);
903     
904     return d1 == d2;
905 }
906 #else
907 #define SameDisk(P1, P2) ((P1)->device/PartsPerDisk == (P2)->device/PartsPerDisk)
908 #endif
909
910 /* This assumes that two partitions with the same device number divided by
911  * PartsPerDisk are on the same disk.
912  */
913 void SalvageFileSysParallel(struct DiskPartition *partP)
914 {
915     struct job {
916        struct DiskPartition *partP;
917        int  pid;              /* Pid for this job */
918        int  jobnumb;          /* Log file job number */
919        struct job *nextjob;   /* Next partition on disk to salvage */
920     };
921     static struct job *jobs[MAXPARALLEL] = {0}; /* Need to zero this */
922     struct job *thisjob = 0;
923     static int numjobs = 0;
924     static int jobcount = 0;
925     char buf[1024];
926     int wstatus;
927     struct job *oldjob;
928     int startjob;
929     FILE *passLog;
930     char logFileName[256];
931     int i, j, pid;
932
933     if (partP) {
934        /* We have a partition to salvage. Copy it into thisjob */
935        thisjob = (struct job *) malloc(sizeof(struct job));
936        if (!thisjob) {
937           Log("Can't salvage '%s'. Not enough memory\n", partP->name);
938           return;
939        }
940        memset(thisjob, 0, sizeof(struct job));
941        thisjob->partP = partP;
942        thisjob->jobnumb = jobcount;
943        jobcount++;
944     }
945     else if (jobcount == 0) {
946        /* We are asking to wait for all jobs (partp == 0), yet we never
947         * started any.
948         */
949        Log("No file system partitions named %s* found; not salvaged\n",
950            VICE_PARTITION_PREFIX);
951        return;
952     }
953
954     if (debug || Parallel == 1) {
955         if (thisjob) {
956            SalvageFileSys(thisjob->partP, 0);
957            free(thisjob);
958         }
959         return;
960     }
961
962     if (thisjob) {
963        /* Check to see if thisjob is for a disk that we are already 
964         * salvaging. If it is, link it in as the next job to do. The
965         * jobs array has 1 entry per disk being salvages. numjobs is 
966         * the total number of disks currently being salvaged. In 
967         * order to keep thejobs array compact, when a disk is
968         * completed, the hightest element in the jobs array is moved 
969         * down to now open slot.
970         */
971        for (j=0; j<numjobs; j++) {
972           if (SameDisk(jobs[j]->partP, thisjob->partP)) {
973              /* On same disk, add it to this list and return */
974              thisjob->nextjob = jobs[j]->nextjob;
975              jobs[j]->nextjob = thisjob;
976              thisjob = 0;
977              break;
978           }
979        }
980     }
981
982     /* Loop until we start thisjob or until all existing jobs are finished */
983     while ( thisjob || (!partP && (numjobs > 0)) ) {
984        startjob = -1;                       /* No new job to start */
985
986        if ( (numjobs >= Parallel) || (!partP && (numjobs > 0)) ) {
987           /* Either the max jobs are running or we have to wait for all
988            * the jobs to finish. In either case, we wait for at least one
989            * job to finish. When it's done, clean up after it.
990            */
991           pid = wait(&wstatus);
992           assert(pid != -1);
993           for (j=0; j<numjobs; j++) {       /* Find which job it is */
994              if (pid == jobs[j]->pid) break;
995           }
996           assert(j < numjobs);
997           if (WCOREDUMP(wstatus)) {         /* Say if the job core dumped */
998              Log("Salvage of %s core dumped!\n", jobs[j]->partP->name);
999           }
1000
1001           numjobs--;                        /* job no longer running */
1002           oldjob  = jobs[j];                /* remember */
1003           jobs[j] = jobs[j]->nextjob;       /* Step to next part on same disk */
1004           free(oldjob);                     /* free the old job */
1005
1006           /* If there is another partition on the disk to salvage, then 
1007            * say we will start it (startjob). If not, then put thisjob there
1008            * and say we will start it.
1009            */
1010           if (jobs[j]) {                    /* Another partitions to salvage */
1011              startjob = j;                  /* Will start it */
1012           } else {                          /* There is not another partition to salvage */
1013              if (thisjob) {                 
1014                 jobs[j] = thisjob;          /* Add thisjob */
1015                 thisjob = 0;
1016                 startjob = j;               /* Will start it */
1017              } else {
1018                 jobs[j] = jobs[numjobs];    /* Move last job up to this slot */
1019                 startjob = -1;              /* Don't start it - already running */
1020              }
1021           }
1022        } else {
1023           /* We don't have to wait for a job to complete */
1024           if (thisjob) {
1025              jobs[numjobs] = thisjob;        /* Add this job */
1026              thisjob = 0;
1027              startjob = numjobs;             /* Will start it */
1028           }
1029        }
1030
1031        /* Start up a new salvage job on a partition in job slot "startjob" */
1032        if (startjob != -1) {
1033           if (!Showmode)
1034              Log("Starting salvage of file system partition %s\n",
1035                  jobs[startjob]->partP->name);
1036 #ifdef AFS_NT40_ENV
1037           /* For NT, we not only fork, but re-exec the salvager. Pass in the
1038            * commands and pass the child job number via the data path.
1039            */
1040           pid = nt_SalvagePartition(jobs[startjob]->partP->name,
1041                                     jobs[startjob]->jobnumb);
1042           jobs[startjob]->pid = pid;
1043           numjobs++;
1044 #else
1045           pid = Fork();
1046           if (pid) {
1047              jobs[startjob]->pid = pid;
1048              numjobs++;
1049           } else {
1050              int fd;
1051              
1052              ShowLog = 0;
1053              for (fd =0; fd < 16; fd++) close(fd);
1054              open("/", 0); dup2(0, 1); dup2(0, 2);
1055 #ifndef AFS_NT40_ENV
1056                  if ( useSyslog ) {
1057                         openlog(NULL, LOG_PID, useSyslogFacility);
1058                  } else
1059 #endif
1060                  {
1061                sprintf(logFileName, "%s.%d", AFSDIR_SERVER_SLVGLOG_FILEPATH, jobs[startjob]->jobnumb);
1062                logFile = fopen(logFileName, "w");
1063                  }
1064              if (!logFile) logFile = stdout;
1065
1066              SalvageFileSys1(jobs[startjob]->partP, 0);
1067              Exit(0);
1068           }
1069 #endif
1070        }
1071     } /* while ( thisjob || (!partP && numjobs > 0) ) */
1072
1073     /* If waited for all jobs to complete, now collect log files and return */
1074 #ifndef AFS_NT40_ENV
1075         if ( ! useSyslog ) /* if syslogging - no need to collect */
1076 #endif
1077     if (!partP) {
1078        for (i=0; i<jobcount; i++) {
1079           sprintf(logFileName, "%s.%d", AFSDIR_SERVER_SLVGLOG_FILEPATH, i);
1080           if (passLog = fopen(logFileName, "r")) {
1081              while(fgets(buf, sizeof(buf), passLog)) {
1082                 fputs(buf, logFile);
1083              }
1084              fclose(passLog);
1085           }
1086           (void)unlink(logFileName);
1087        }
1088        fflush(logFile);
1089     }
1090         return;
1091 }
1092
1093
1094 void SalvageFileSys(struct DiskPartition *partP, VolumeId singleVolumeNumber)
1095 {
1096     if (!canfork || debug || Fork() == 0) {
1097         SalvageFileSys1(partP, singleVolumeNumber);
1098         if (canfork && !debug) {
1099             ShowLog = 0;
1100             Exit(0);
1101         }
1102     }
1103     else
1104         Wait("SalvageFileSys");
1105 }
1106
1107 char *get_DevName(pbuffer, wpath)
1108 char *wpath, *pbuffer;
1109 {
1110     char pbuf[128], *ptr;
1111     strcpy(pbuf, pbuffer);
1112     ptr = (char *)strrchr(pbuf, '/');
1113     if (ptr) {
1114         *ptr = '\0';
1115         strcpy(wpath, pbuf);
1116     } else
1117         return NULL;
1118     ptr = (char *)strrchr(pbuffer, '/');            
1119     if (ptr) {
1120         strcpy(pbuffer, ptr+1);
1121         return pbuffer;
1122     } else
1123         return NULL;
1124 }
1125
1126 void SalvageFileSys1(struct DiskPartition *partP, VolumeId singleVolumeNumber)
1127 {
1128     char *name, *tdir;
1129     char inodeListPath[50];
1130     static char tmpDevName[100];
1131     static char wpath[100];
1132     struct VolumeSummary *vsp, *esp;
1133     int i,j;
1134
1135     fileSysPartition = partP;
1136     fileSysDevice = fileSysPartition->device;
1137     fileSysPathName = VPartitionPath(fileSysPartition);
1138
1139 #ifdef AFS_NT40_ENV
1140     /* Opendir can fail on "C:" but not on "C:\" if C is empty! */
1141     (void) sprintf(fileSysPath, "%s\\", fileSysPathName);
1142     name = partP->devName;
1143 #else
1144     fileSysPath = fileSysPathName;
1145     strcpy(tmpDevName, partP->devName);
1146     name = get_DevName(tmpDevName, wpath);
1147     fileSysDeviceName = name;
1148     filesysfulldev = wpath;
1149 #endif
1150
1151     VLockPartition(partP->name);
1152     if (singleVolumeNumber || ForceSalvage)
1153         ForceSalvage = 1;
1154     else
1155         ForceSalvage = UseTheForceLuke(fileSysPath);
1156
1157     if (singleVolumeNumber) {
1158         if (!VConnectFS()) {
1159             Abort("Couldn't connect to file server\n");
1160         }
1161         AskOffline(singleVolumeNumber);
1162     }
1163     else {
1164         if (!Showmode) Log("SALVAGING FILE SYSTEM PARTITION %s (device=%s%s)\n", partP->name, name, (Testing? "(READONLY mode)":""));
1165         if (ForceSalvage)
1166             Log("***Forced salvage of all volumes on this partition***\n");
1167     }
1168
1169
1170     /*
1171      * Remove any leftover /vicepa/salvage.inodes.* or /vicepa/salvage.temp.* 
1172      * files 
1173      */
1174     {
1175         DIR *dirp;
1176         struct dirent *dp;
1177
1178         assert((dirp = opendir(fileSysPath)) != NULL);
1179         while (dp = readdir(dirp)) {
1180             if (!strncmp(dp->d_name, "salvage.inodes.", 15) ||
1181                 !strncmp(dp->d_name, "salvage.temp.", 13)) {
1182                 char npath[1024];
1183                 Log("Removing old salvager temp files %s\n", dp->d_name);
1184                 strcpy(npath, fileSysPath);
1185                 strcat(npath, "/");
1186                 strcat(npath, dp->d_name);
1187                 unlink(npath);
1188             }
1189         }
1190         closedir(dirp);
1191     }
1192     tdir = (tmpdir ? tmpdir : fileSysPath);
1193 #ifdef AFS_NT40_ENV
1194     (void) _putenv("TMP="); /* If "TMP" is set, then that overrides tdir. */
1195     (void) strcpy(inodeListPath, _tempnam(tdir, "salvage.inodes."));
1196 #else
1197     sprintf(inodeListPath, "%s/salvage.inodes.%s.%d", tdir, name, getpid());
1198 #endif
1199     if (GetInodeSummary(inodeListPath, singleVolumeNumber) < 0) {
1200         unlink(inodeListPath);
1201         return;
1202     }
1203 #ifdef AFS_NT40_ENV
1204     /* Using nt_unlink here since we're really using the delete on close
1205      * semantics of unlink. In most places in the salvager, we really do
1206      * mean to unlink the file at that point. Those places have been
1207      * modified to actually do that so that the NT crt can be used there.
1208      */
1209     inodeFd = _open_osfhandle((long)nt_open(inodeListPath, O_RDWR, 0), O_RDWR);
1210     nt_unlink(inodeListPath); /* NT's crt unlink won't if file is open. */
1211 #else
1212     inodeFd = open(inodeListPath, O_RDONLY);
1213     unlink(inodeListPath);
1214 #endif
1215     if (inodeFd == -1)
1216         Abort("Temporary file %s is missing...\n",
1217                 inodeListPath);
1218     if (ListInodeOption) {
1219         PrintInodeList();
1220         return;
1221     }
1222     /* enumerate volumes in the partition.
1223        figure out sets of read-only + rw volumes.
1224        salvage each set, read-only volumes first, then read-write.
1225        Fix up inodes on last volume in set (whether it is read-write
1226        or read-only).
1227     */
1228     GetVolumeSummary(singleVolumeNumber);
1229
1230     for (i = j = 0,vsp = volumeSummaryp,esp = vsp+nVolumes; i < nVolumesInInodeFile; i = j) {
1231         VolumeId rwvid = inodeSummary[i].RWvolumeId;
1232         for (j=i; j < nVolumesInInodeFile
1233             && inodeSummary[j].RWvolumeId == rwvid; j++) {
1234             VolumeId vid = inodeSummary[j].volumeId;
1235             struct VolumeSummary *tsp;
1236             /* Scan volume list (from partition root directory) looking for the
1237                current rw volume number in the volume list from the inode scan.
1238                If there is one here that is not in the inode volume list,
1239                delete it now. */
1240             for ( ; vsp<esp && (vsp->header.parent < rwvid); vsp++) {
1241                 if (vsp->fileName)
1242                     DeleteExtraVolumeHeaderFile(vsp);
1243             }
1244             /* Now match up the volume summary info from the root directory with the
1245                entry in the volume list obtained from scanning inodes */
1246             inodeSummary[j].volSummary = NULL;
1247             for (tsp = vsp; tsp<esp && (tsp->header.parent == rwvid); tsp++) {
1248                 if (tsp->header.id == vid) {
1249                     inodeSummary[j].volSummary = tsp;
1250                     tsp->fileName = 0;
1251                     break;
1252                 }
1253             }
1254         }
1255         /* Salvage the group of volumes (several read-only + 1 read/write)
1256          * starting with the current read-only volume we're looking at.
1257          */
1258         SalvageVolumeGroup(&inodeSummary[i], j-i);
1259     }
1260
1261     /* Delete any additional volumes that were listed in the partition but which didn't have any corresponding inodes */
1262     for ( ; vsp<esp; vsp++) {   
1263         if (vsp->fileName) 
1264             DeleteExtraVolumeHeaderFile(vsp);
1265     }
1266
1267     if (!singleVolumeNumber)     /* Remove the FORCESALVAGE file */
1268        RemoveTheForce(fileSysPath);
1269
1270     if (!Testing && singleVolumeNumber) {
1271       AskOnline(singleVolumeNumber, fileSysPartition->name);
1272
1273       /* Step through the volumeSummary list and set all volumes on-line.
1274        * The volumes were taken off-line in GetVolumeSummary.
1275        */
1276       for (j=0; j<nVolumes; j++) {
1277          AskOnline(volumeSummaryp[j].header.id, fileSysPartition->name);
1278       }
1279     }
1280     else {
1281        if (!Showmode) 
1282           Log("SALVAGING OF PARTITION %s%s COMPLETED\n",
1283               fileSysPartition->name, (Testing ? " (READONLY mode)":""));
1284     }
1285
1286     close(inodeFd); /* SalvageVolumeGroup was the last which needed it. */
1287 }
1288
1289 void DeleteExtraVolumeHeaderFile(register struct VolumeSummary *vsp)
1290 {
1291     if (!Showmode) Log("The volume header file %s is not associated with any actual data (%sdeleted)\n",
1292         vsp->fileName, (Testing? "would have been ":""));
1293     if (!Testing)
1294         unlink(vsp->fileName);
1295     vsp->fileName = 0;
1296 }
1297
1298 CompareInodes(_p1,_p2)
1299     const void *_p1,*_p2;
1300 {
1301     register const struct ViceInodeInfo *p1 = _p1;
1302     register const struct ViceInodeInfo *p2 = _p2;
1303     if (p1->u.vnode.vnodeNumber == INODESPECIAL ||
1304         p2->u.vnode.vnodeNumber == INODESPECIAL) {
1305         VolumeId p1rwid, p2rwid;
1306         p1rwid = (p1->u.vnode.vnodeNumber==INODESPECIAL
1307             ? p1->u.special.parentId : p1->u.vnode.volumeId);
1308         p2rwid = (p2->u.vnode.vnodeNumber==INODESPECIAL
1309             ? p2->u.special.parentId : p2->u.vnode.volumeId);
1310         if (p1rwid < p2rwid)
1311             return -1;
1312         if (p1rwid > p2rwid)
1313             return 1;
1314         if (p1->u.vnode.vnodeNumber == INODESPECIAL
1315             && p2->u.vnode.vnodeNumber == INODESPECIAL) {
1316             if (p1->u.vnode.volumeId == p2->u.vnode.volumeId)
1317                 return (p1->u.special.type < p2->u.special.type? -1: 1);
1318             if (p1->u.vnode.volumeId == p1rwid)
1319                 return -1;
1320             if (p2->u.vnode.volumeId == p2rwid)
1321                 return 1;
1322             return (p1->u.vnode.volumeId < p2->u.vnode.volumeId? -1: 1);
1323         }
1324         if (p1->u.vnode.vnodeNumber != INODESPECIAL)
1325             return (p2->u.vnode.volumeId == p2rwid? 1: -1);
1326         return (p1->u.vnode.volumeId == p1rwid? -1: 1);
1327     }
1328     if (p1->u.vnode.volumeId<p2->u.vnode.volumeId)
1329         return -1;
1330     if (p1->u.vnode.volumeId>p2->u.vnode.volumeId)
1331         return 1;
1332     if (p1->u.vnode.vnodeNumber < p2->u.vnode.vnodeNumber)
1333         return -1;
1334     if (p1->u.vnode.vnodeNumber > p2->u.vnode.vnodeNumber)
1335         return 1;
1336     /* The following tests are reversed, so that the most desirable
1337        of several similar inodes comes first */
1338     if (p1->u.vnode.vnodeUniquifier > p2->u.vnode.vnodeUniquifier) {
1339 #ifdef  AFS_3DISPARES
1340         if (p1->u.vnode.vnodeUniquifier > 3775414 /* 90% of 4.2M */ &&
1341             p2->u.vnode.vnodeUniquifier < 419490  /* 10% of 4.2M */)
1342             return 1;
1343 #endif
1344 #ifdef  AFS_SGI_EXMAG
1345         if (p1->u.vnode.vnodeUniquifier > 15099494 /* 90% of 16M */ &&
1346             p2->u.vnode.vnodeUniquifier < 1677721  /* 10% of 16M */)
1347             return 1;
1348 #endif
1349         return -1;
1350     }
1351     if (p1->u.vnode.vnodeUniquifier < p2->u.vnode.vnodeUniquifier) {
1352 #ifdef  AFS_3DISPARES
1353         if (p2->u.vnode.vnodeUniquifier > 3775414 /* 90% of 4.2M */ &&
1354             p1->u.vnode.vnodeUniquifier < 419490  /* 10% of 4.2M */)
1355             return -1;
1356 #endif
1357 #ifdef  AFS_SGI_EXMAG
1358         if (p2->u.vnode.vnodeUniquifier > 15099494 /* 90% of 16M */ &&
1359             p1->u.vnode.vnodeUniquifier < 1677721  /* 10% of 16M */)
1360             return 1;
1361 #endif
1362         return 1;
1363     }
1364     if (p1->u.vnode.inodeDataVersion > p2->u.vnode.inodeDataVersion) {
1365 #ifdef  AFS_3DISPARES
1366         if (p1->u.vnode.inodeDataVersion > 1887437 /* 90% of 2.1M */ &&
1367             p2->u.vnode.inodeDataVersion < 209716  /* 10% of 2.1M */)
1368             return 1;
1369 #endif
1370 #ifdef  AFS_SGI_EXMAG
1371         if (p1->u.vnode.inodeDataVersion > 15099494 /* 90% of 16M */ &&
1372             p2->u.vnode.inodeDataVersion < 1677721  /* 10% of 16M */)
1373             return 1;
1374 #endif
1375         return -1;
1376     }
1377     if (p1->u.vnode.inodeDataVersion < p2->u.vnode.inodeDataVersion) {
1378 #ifdef  AFS_3DISPARES
1379         if (p2->u.vnode.inodeDataVersion > 1887437 /* 90% of 2.1M */ &&
1380             p1->u.vnode.inodeDataVersion < 209716  /* 10% of 2.1M */)
1381             return -1;
1382 #endif
1383 #ifdef  AFS_SGI_EXMAG
1384         if (p2->u.vnode.inodeDataVersion > 15099494 /* 90% of 16M */ &&
1385             p1->u.vnode.inodeDataVersion < 1677721  /* 10% of 16M */)
1386             return 1;
1387 #endif
1388         return 1;
1389     }
1390     return 0;
1391 }
1392
1393 void CountVolumeInodes(register struct ViceInodeInfo *ip, int maxInodes,
1394                        register struct InodeSummary * summary)
1395 {
1396     int volume = ip->u.vnode.volumeId;
1397     int rwvolume = volume;
1398     register n, nSpecial;
1399     register Unique maxunique;
1400     n = nSpecial = 0;
1401     maxunique = 0;
1402     while (maxInodes-- && volume == ip->u.vnode.volumeId) {
1403         n++;
1404         if (ip->u.vnode.vnodeNumber == INODESPECIAL) {
1405             nSpecial++;
1406             rwvolume = ip->u.special.parentId;
1407             /* This isn't quite right, as there could (in error) be different
1408                parent inodes in different special vnodes */
1409         }
1410         else {
1411             if (maxunique < ip->u.vnode.vnodeUniquifier)
1412                 maxunique = ip->u.vnode.vnodeUniquifier;
1413         }
1414         ip++;
1415     }
1416     summary->volumeId = volume;
1417     summary->RWvolumeId = rwvolume;
1418     summary->nInodes =n;
1419     summary->nSpecialInodes = nSpecial;
1420     summary->maxUniquifier = maxunique;
1421 }
1422
1423 int OnlyOneVolume(inodeinfo, singleVolumeNumber)
1424     struct ViceInodeInfo *inodeinfo;
1425     VolumeId singleVolumeNumber;
1426 {
1427     if (inodeinfo->u.vnode.vnodeNumber == INODESPECIAL)
1428         return (inodeinfo->u.special.parentId == singleVolumeNumber);
1429     return (inodeinfo->u.vnode.volumeId == singleVolumeNumber);
1430 }
1431
1432 /* GetInodeSummary
1433  *
1434  * Collect list of inodes in file named by path. If a truly fatal error,
1435  * unlink the file and abort. For lessor errors, return -1. The file will
1436  * be unlinked by the caller.
1437  */
1438 int GetInodeSummary(char *path, VolumeId singleVolumeNumber)
1439 {
1440     struct stat status;
1441     int forceSal, err;
1442     struct ViceInodeInfo *ip;
1443     struct InodeSummary summary;
1444     char summaryFileName[50];
1445     FILE *summaryFile;
1446 #ifdef AFS_NT40_ENV
1447     char *dev = fileSysPath;
1448     char *wpath = fileSysPath;
1449 #else
1450     char *dev = fileSysDeviceName;
1451     char *wpath = filesysfulldev;
1452 #endif
1453     char *part = fileSysPath;
1454     char *tdir;
1455
1456     /* This file used to come from vfsck; cobble it up ourselves now... */
1457     if ((err = ListViceInodes(dev, fileSysPath, path, singleVolumeNumber?OnlyOneVolume:0, singleVolumeNumber, &forceSal, forceR, wpath)) < 0) {
1458        if (err == -2) {
1459             Log("*** I/O error %d when writing a tmp inode file %s; Not salvaged %s ***\nIncrease space on partition or use '-tmpdir'\n",
1460                 errno, path, dev);
1461             return -1;
1462         }
1463         unlink(path);
1464         Abort("Unable to get inodes for \"%s\"; not salvaged\n", dev);
1465     }
1466     if (forceSal && !ForceSalvage) {
1467         Log("***Forced salvage of all volumes on this partition***\n");
1468         ForceSalvage = 1;
1469     }
1470     inodeFd = open(path, O_RDWR);
1471     if (inodeFd == -1 || fstat(inodeFd, &status) == -1) {
1472         unlink(path);
1473         Abort("No inode description file for \"%s\"; not salvaged\n", dev);
1474     }
1475     tdir = (tmpdir ? tmpdir : part);
1476 #ifdef AFS_NT40_ENV
1477     (void) _putenv("TMP="); /* If "TMP" is set, then that overrides tdir. */
1478     (void) strcpy(summaryFileName, _tempnam(tdir, "salvage.temp"));
1479 #else
1480     sprintf(summaryFileName, "%s/salvage.temp.%d", tdir, getpid());
1481 #endif
1482     summaryFile = fopen(summaryFileName, "a+");
1483     if (summaryFile == NULL) {
1484         close(inodeFd);
1485         unlink(path);
1486         Abort("Unable to create inode summary file\n");
1487     }
1488     if (!canfork || debug || Fork() == 0) {
1489         int nInodes; 
1490         nInodes = status.st_size / sizeof(struct ViceInodeInfo);
1491         if (nInodes == 0) {
1492             fclose(summaryFile); close(inodeFd);
1493             unlink(summaryFileName);
1494             if (!singleVolumeNumber)     /* Remove the FORCESALVAGE file */
1495                RemoveTheForce(fileSysPath);
1496             Log("%s vice inodes on %s; not salvaged\n",
1497                 singleVolumeNumber? "No applicable": "No", dev);
1498             return -1;
1499         }
1500         ip = (struct ViceInodeInfo *) malloc(status.st_size);
1501         if (ip == NULL) {
1502             fclose(summaryFile); close(inodeFd);
1503             unlink(path);
1504             unlink(summaryFileName);
1505             Abort("Unable to allocate enough space to read inode table; %s not salvaged\n", dev);
1506         }
1507         if (read(inodeFd, ip, status.st_size) != status.st_size) {
1508             fclose(summaryFile); close(inodeFd);
1509             unlink(path);
1510             unlink(summaryFileName);
1511             Abort("Unable to read inode table; %s not salvaged\n", dev);
1512         }
1513         qsort(ip, nInodes, sizeof(struct ViceInodeInfo), CompareInodes);
1514         if (lseek(inodeFd, 0, SEEK_SET) == -1 ||
1515             write(inodeFd, ip, status.st_size) != status.st_size) {
1516             fclose(summaryFile); close(inodeFd);
1517             unlink(path);
1518             unlink(summaryFileName);
1519             Abort("Unable to rewrite inode table; %s not salvaged\n", dev);
1520         }
1521         summary.index = 0;
1522         while (nInodes) {
1523             CountVolumeInodes(ip, nInodes, &summary);
1524             if (fwrite(&summary, sizeof (summary), 1, summaryFile) != 1) {
1525                 Log("Difficulty writing summary file (errno = %d); %s not salvaged\n", errno, dev);
1526                 fclose(summaryFile); close(inodeFd);
1527                 return -1;
1528             }
1529             summary.index += (summary.nInodes);
1530             nInodes -= summary.nInodes;
1531             ip += summary.nInodes;
1532         }
1533         /* Following fflush is not fclose, because if it was debug mode would not work */
1534         if (fflush(summaryFile) == EOF || fsync(fileno(summaryFile)) == -1) {
1535             Log("Unable to write summary file (errno = %d); %s not salvaged\n", errno, dev);
1536             fclose(summaryFile); close(inodeFd);
1537             return -1;
1538         }
1539         if (canfork && !debug) {
1540             ShowLog = 0;
1541             Exit(0);
1542         }
1543     }
1544     else {
1545         if (Wait("Inode summary") == -1) {
1546             fclose(summaryFile); close(inodeFd);
1547             unlink(path);
1548             unlink(summaryFileName);
1549             Exit(1);    /* salvage of this partition aborted */
1550         }
1551     }
1552     assert(fstat(fileno(summaryFile), &status) != -1);
1553     if ( status.st_size != 0 ) {
1554         int ret;
1555         inodeSummary = (struct InodeSummary *) malloc(status.st_size);
1556         assert(inodeSummary != NULL);
1557         /* For GNU we need to do lseek to get the file pointer moved. */
1558         assert(lseek(fileno(summaryFile), 0, SEEK_SET) == 0);
1559         ret = read(fileno(summaryFile), inodeSummary, status.st_size);
1560         assert(ret == status.st_size);
1561     }
1562     nVolumesInInodeFile = status.st_size / sizeof (struct InodeSummary);
1563     fclose(summaryFile);
1564     close(inodeFd);
1565     unlink(summaryFileName);
1566     return 0;
1567 }
1568
1569 /* Comparison routine for volume sort.
1570    This is setup so that a read-write volume comes immediately before
1571    any read-only clones of that volume */
1572 CompareVolumes(_p1,_p2)
1573     const void *_p1,*_p2;
1574 {
1575     register const struct VolumeSummary *p1 = _p1;
1576     register const struct VolumeSummary *p2 = _p2;
1577     if (p1->header.parent != p2->header.parent)
1578         return p1->header.parent < p2->header.parent? -1: 1;
1579     if (p1->header.id == p1->header.parent) /* p1 is rw volume */
1580         return -1;
1581     if (p2->header.id == p2->header.parent) /* p2 is rw volume */
1582         return 1;
1583     return p1->header.id < p2->header.id ? -1: 1; /* Both read-only */
1584 }
1585
1586 void GetVolumeSummary(VolumeId singleVolumeNumber)
1587 {
1588     DIR *dirp;
1589     afs_int32 nvols = 0;
1590     struct VolumeSummary *vsp, vs;
1591     struct VolumeDiskHeader diskHeader;
1592     struct dirent  *dp;
1593
1594     /* Get headers from volume directory */
1595     if (chdir(fileSysPath) == -1 || (dirp = opendir(".")) == NULL)
1596         Abort("Can't read directory %s; not salvaged\n", fileSysPath);
1597     if (!singleVolumeNumber) {
1598         while (dp = readdir(dirp)) {
1599             char *p = dp->d_name;
1600             p = strrchr(dp->d_name, '.');
1601             if (p != NULL && strcmp(p, VHDREXT) == 0) {
1602                 int fd;
1603                 if ((fd = open(dp->d_name, O_RDONLY)) != -1 && 
1604                     read(fd, (char*)&diskHeader, sizeof (diskHeader))
1605                     == sizeof (diskHeader) &&
1606                     diskHeader.stamp.magic == VOLUMEHEADERMAGIC) {
1607                     DiskToVolumeHeader(&vs.header, &diskHeader);
1608                     nvols++;
1609                 }
1610                 close(fd);
1611             } 
1612         }
1613 #ifdef AFS_NT40_ENV
1614         closedir(dirp); dirp = opendir("."); /* No rewinddir for NT */
1615 #else
1616         rewinddir(dirp);
1617 #endif
1618         if (!nvols) nvols = 1;
1619         volumeSummaryp = (struct VolumeSummary *)malloc(nvols * sizeof(struct VolumeSummary));
1620     } else
1621         volumeSummaryp = (struct VolumeSummary *)malloc(20 * sizeof(struct VolumeSummary));
1622     assert(volumeSummaryp != NULL);
1623
1624     nVolumes = 0;
1625     vsp = volumeSummaryp;
1626     while (dp = readdir(dirp)) {
1627         char *p = dp->d_name;
1628         p = strrchr(dp->d_name, '.');
1629         if (p != NULL && strcmp(p, VHDREXT) == 0) {
1630             int error = 0;
1631             int fd;
1632             if ((fd = open(dp->d_name, O_RDONLY)) == -1
1633               || read(fd, &diskHeader, sizeof (diskHeader))
1634                    != sizeof (diskHeader)
1635               || diskHeader.stamp.magic != VOLUMEHEADERMAGIC) {
1636                 error = 1;
1637             }
1638             close(fd);
1639             if (error) {
1640                 if (!singleVolumeNumber) {
1641                     if (!Showmode) Log("%s/%s is not a legitimate volume header file; %sdeleted\n", fileSysPathName, 
1642                         dp->d_name, (Testing? "it would have been ":""));
1643                     if (!Testing)
1644                         unlink(dp->d_name);
1645                 }
1646             }
1647             else {
1648                 char nameShouldBe[64];
1649                 DiskToVolumeHeader(&vsp->header, &diskHeader);
1650                 if (singleVolumeNumber && vsp->header.id==singleVolumeNumber && vsp->header.parent!=singleVolumeNumber) {
1651                     Log("%u is a read-only volume; not salvaged\n", singleVolumeNumber);
1652                     Exit(1);
1653                 }
1654                 if (!singleVolumeNumber || (vsp->header.id==singleVolumeNumber || vsp->header.parent==singleVolumeNumber)) {
1655                     sprintf(nameShouldBe, VFORMAT, vsp->header.id);
1656                     if (singleVolumeNumber)
1657                         AskOffline(vsp->header.id);
1658                     if (strcmp(nameShouldBe, dp->d_name)) {
1659                         if (!Showmode) Log("Volume header file %s is incorrectly named; %sdeleted (it will be recreated later, if necessary)\n", dp->d_name, (Testing ? "it would have been ":""));
1660                         if (!Testing)
1661                             unlink(dp->d_name);
1662                     }
1663                     else {
1664                         vsp->fileName = ToString(dp->d_name);
1665                         nVolumes++;
1666                         vsp++;
1667                     }
1668                 }
1669             }
1670             close(fd);
1671         }
1672     }
1673     closedir(dirp);
1674     qsort(volumeSummaryp,nVolumes,sizeof (struct VolumeSummary),CompareVolumes);
1675 }
1676
1677 /* Find the link table. This should be associated with the RW volume or, if
1678  * a RO only site, then the RO volume. For now, be cautious and hunt carefully.
1679  */
1680 Inode FindLinkHandle(register struct InodeSummary *isp, int nVols,
1681                               struct ViceInodeInfo *allInodes)
1682 {
1683     int i, j;
1684     struct ViceInodeInfo *ip;
1685
1686     for (i=0; i<nVols; i++) {
1687         ip = allInodes + isp[i].index;
1688         for (j=0; j<isp[i].nSpecialInodes; j++) {
1689             if (ip[j].u.special.type == VI_LINKTABLE)
1690                 return ip[j].inodeNumber;
1691         }
1692     }
1693     return (Inode)-1;
1694 }
1695
1696 int CreateLinkTable(register struct InodeSummary *isp, Inode ino)
1697 {
1698     struct versionStamp version;
1699     FdHandle_t *fdP;
1700
1701     if (!VALID_INO(ino))
1702         ino = IH_CREATE(NULL, fileSysDevice, fileSysPath, 0,
1703                         isp->volumeId, INODESPECIAL,
1704                         VI_LINKTABLE, isp->RWvolumeId);
1705     if (!VALID_INO(ino))
1706         Abort("Unable to allocate link table inode for volume %u (error = %d)\n",
1707               isp->RWvolumeId, errno);
1708     IH_INIT(VGLinkH, fileSysDevice, isp->RWvolumeId, ino);
1709     fdP = IH_OPEN(VGLinkH);
1710     if (fdP == NULL)
1711         Abort("Can't open link table for volume %u (error = %d)\n",
1712               isp->RWvolumeId, errno);
1713
1714     if (FDH_TRUNC(fdP, 0)<0)
1715         Abort("Can't truncate link table for volume %u (error = %d)\n",
1716               isp->RWvolumeId, errno);
1717
1718     version.magic = LINKTABLEMAGIC;
1719     version.version = LINKTABLEVERSION;
1720
1721     if (FDH_WRITE(fdP, (char*)&version, sizeof(version))
1722         != sizeof(version))
1723         Abort("Can't truncate link table for volume %u (error = %d)\n",
1724               isp->RWvolumeId, errno);
1725
1726     FDH_REALLYCLOSE(fdP);
1727
1728     /* If the volume summary exits (i.e.,  the V*.vol header file exists),
1729      * then set this inode there as well.
1730      */
1731     if (isp->volSummary)
1732         isp->volSummary->header.linkTable = ino;
1733
1734     return 0;
1735 }
1736
1737 #ifdef AFS_NT40_ENV
1738 void *nt_SVG(void *arg)
1739 {
1740     SVGParms_t *parms = (SVGParms_t*)arg;
1741     DoSalvageVolumeGroup(parms->svgp_inodeSummaryp, parms->svgp_count);
1742     return NULL;
1743 }
1744
1745 void SalvageVolumeGroup(register struct InodeSummary *isp, int nVols)
1746 {
1747     pthread_t tid;
1748     pthread_attr_t tattr;
1749     int code;
1750     SVGParms_t parms;
1751
1752     /* Initialize per volume global variables, even if later code does so */
1753     VolumeChanged = 0;
1754     VGLinkH = NULL;
1755     VGLinkH_cnt = 0;
1756     memset(&VolInfo, 0, sizeof(VolInfo));
1757
1758     parms.svgp_inodeSummaryp = isp;
1759     parms.svgp_count = nVols;
1760     code = pthread_attr_init(&tattr);
1761     if (code) {
1762         Log("Failed to salvage volume group %u: pthread_attr_init()\n",
1763             isp->RWvolumeId);
1764         return;
1765     }
1766     code = pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE);
1767     if (code) {
1768         Log("Failed to salvage volume group %u: pthread_attr_setdetachstate()\n",
1769             isp->RWvolumeId);
1770         return;
1771     }
1772     code = pthread_create(&tid, &tattr, nt_SVG, &parms);
1773     if (code) {
1774         Log("Failed to create thread to salvage volume group %u\n",
1775             isp->RWvolumeId);
1776         return;
1777     }
1778     (void) pthread_join(tid, NULL);
1779 }
1780 #endif /* AFS_NT40_ENV */
1781
1782 void DoSalvageVolumeGroup(register struct InodeSummary *isp, int nVols)
1783 {
1784     struct ViceInodeInfo *inodes,*allInodes,*ip;
1785     int i, totalInodes, size, salvageTo;
1786     int haveRWvolume;
1787     int check;
1788     Inode ino;
1789     int dec_VGLinkH = 0;
1790     int VGLinkH_p1;
1791     FdHandle_t *fdP = NULL;
1792
1793     VGLinkH_cnt = 0;
1794     haveRWvolume = (isp->volumeId==isp->RWvolumeId && isp->nSpecialInodes>0);
1795     if ((!ShowMounts) || (ShowMounts && !haveRWvolume)) {
1796         if (!ForceSalvage && QuickCheck(isp, nVols))
1797             return;
1798     }
1799     if (ShowMounts && !haveRWvolume)
1800         return;
1801     if (canfork && !debug && Fork() != 0) {
1802         (void) Wait("Salvage volume group");
1803         return;
1804     }
1805     for (i = 0, totalInodes = 0; i<nVols; i++)
1806         totalInodes += isp[i].nInodes;
1807     size = totalInodes * sizeof (struct ViceInodeInfo);
1808     inodes = (struct ViceInodeInfo *) malloc(size);
1809     allInodes = inodes - isp->index; /* this would the base of all the inodes
1810                                         for the partition, if all the inodes
1811                                         had been read into memory */
1812     assert(lseek(inodeFd,isp->index*sizeof(struct ViceInodeInfo),SEEK_SET) != -1)
1813     assert(read(inodeFd,inodes,size) == size)
1814
1815     /* Don't try to salvage a read write volume if there isn't one on this
1816        partition */
1817     salvageTo = haveRWvolume? 0:1;
1818
1819 #ifdef AFS_NAMEI_ENV
1820     ino = FindLinkHandle(isp, nVols, allInodes);
1821     if (VALID_INO(ino)) {
1822         IH_INIT(VGLinkH, fileSysDevice, isp->RWvolumeId, ino);
1823         fdP = IH_OPEN(VGLinkH);
1824     }
1825     if (!VALID_INO(ino) || fdP == NULL) {
1826         Log("%s link table for volume %u.\n",
1827             Testing ? "Would have recreated" :"Recreating", isp->RWvolumeId);
1828         if (Testing) {
1829             IH_INIT(VGLinkH, fileSysDevice, -1, -1);
1830         }
1831         else {
1832             CreateLinkTable(isp, ino);
1833         }
1834     }
1835     if (fdP)
1836         FDH_REALLYCLOSE(fdP);
1837 #else
1838     IH_INIT(VGLinkH, fileSysDevice, -1, -1);
1839 #endif
1840
1841     /* Salvage in reverse order--read/write volume last; this way any
1842        Inodes not referenced by the time we salvage the read/write volume
1843        can be picked up by the read/write volume */
1844     /* ACTUALLY, that's not done right now--the inodes just vanish */
1845     for (i = nVols-1; i>=salvageTo; i--) {
1846         int rw = (i == 0);
1847         struct InodeSummary *lisp = &isp[i];
1848 #ifdef AFS_NAMEI_ENV
1849         /* If only the RO is present on this partition, the link table
1850          * shows up as a RW volume special file. Need to make sure the
1851          * salvager doesn't try to salvage the non-existent RW.
1852          */
1853         if (rw && nVols > 1 && isp[i].nSpecialInodes == 1) {
1854             /* If this only special inode is the link table, continue */
1855             if (inodes->u.special.type == VI_LINKTABLE) {
1856                 haveRWvolume = 0;
1857                 continue;
1858             }
1859         }
1860 #endif
1861         if (!Showmode) Log("%s VOLUME %u%s.\n", rw? "SALVAGING": "CHECKING CLONED",
1862           lisp->volumeId, (Testing?"(READONLY mode)":""));
1863         /* Check inodes twice.  The second time do things seriously.  This
1864            way the whole RO volume can be deleted, below, if anything goes wrong */
1865         for (check = 1; check>=0; check--) {
1866             int deleteMe;
1867             if (SalvageVolumeHeaderFile(lisp,allInodes,rw,check, &deleteMe) == -1) {
1868                 MaybeZapVolume(lisp,"Volume header",deleteMe, check);
1869                 if (rw && deleteMe) {
1870                     haveRWvolume = 0; /* This will cause its inodes to be deleted--since salvage
1871                                                           volume won't be called */
1872                     break;
1873                 }
1874                 if (!rw)
1875                     break;
1876             }
1877             if (rw && check == 1)
1878                 continue;
1879             if (SalvageVnodes(isp,lisp,allInodes,check) == -1) {
1880                 MaybeZapVolume(lisp,"Vnode index", 0, check);
1881                 break;
1882             }
1883         }
1884     }
1885
1886     /* Fix actual inode counts */
1887     if (!Showmode) {
1888         for (ip = inodes; totalInodes; ip++,totalInodes--) {
1889             static int TraceBadLinkCounts = 0;
1890 #ifdef AFS_NAMEI_ENV
1891             if (VGLinkH->ih_ino == ip->inodeNumber) {
1892                 dec_VGLinkH = ip->linkCount - VGLinkH_cnt;
1893                 VGLinkH_p1 = ip->u.param[0];
1894                 continue; /* Deal with this last. */
1895             }
1896 #endif
1897             if (ip->linkCount != 0 && TraceBadLinkCounts) {
1898                 TraceBadLinkCounts--; /* Limit reports, per volume */
1899                 Log("#### DEBUG #### Link count incorrect by %d; inode %s, size %u, p=(%u,%u,%u,%u)\n",
1900                     ip->linkCount, PrintInode(NULL, ip->inodeNumber),
1901                     ip->byteCount, ip->u.param[0], ip->u.param[1],
1902                     ip->u.param[2], ip->u.param[3]);
1903             }
1904             while (ip->linkCount > 0) {
1905                 /* below used to assert, not break */
1906                 if (!Testing) {
1907                     if (IH_DEC(VGLinkH, ip->inodeNumber, ip->u.param[0])) {
1908                         Log ("idec failed. inode %s errno %d\n",
1909                              PrintInode(NULL, ip->inodeNumber), errno);
1910                         break;
1911                     }
1912                 }
1913                 ip->linkCount--;
1914             }
1915             while (ip->linkCount < 0) {
1916                 /* these used to be asserts */
1917                 if (!Testing) {
1918                     if (IH_INC(VGLinkH ,ip->inodeNumber, ip->u.param[0])) {
1919                         Log ("iinc failed. inode %s errno %d\n",
1920                              PrintInode(NULL, ip->inodeNumber) ,errno);
1921                         break;
1922                     }
1923                 }
1924                 ip->linkCount++;
1925             }
1926         }
1927 #ifdef AFS_NAMEI_ENV
1928         while (dec_VGLinkH > 0) {
1929             if (IH_DEC(VGLinkH, VGLinkH->ih_ino, VGLinkH_p1)<0) {
1930                 Log("idec failed on link table, errno = %d\n", errno);
1931             }
1932             dec_VGLinkH --;
1933         }
1934         while (dec_VGLinkH < 0) {
1935             if (IH_INC(VGLinkH, VGLinkH->ih_ino, VGLinkH_p1)<0) {
1936                 Log("iinc failed on link table, errno = %d\n", errno);
1937             }
1938             dec_VGLinkH ++;
1939         }
1940 #endif
1941     }
1942     free(inodes);
1943     /* Directory consistency checks on the rw volume */
1944     if (haveRWvolume)
1945         SalvageVolume(isp, VGLinkH);
1946     IH_RELEASE(VGLinkH);
1947
1948     if (canfork && !debug) {
1949         ShowLog = 0;
1950         Exit(0);
1951     }
1952 }
1953
1954 int QuickCheck(register struct InodeSummary *isp, int nVols)
1955 {
1956     /* Check headers BEFORE forking */
1957     register int i;
1958     IHandle_t *h;
1959
1960     for (i = 0; i<nVols; i++) {
1961         struct VolumeSummary *vs = isp[i].volSummary;
1962         VolumeDiskData volHeader;
1963         if (!vs) {
1964             /* Don't salvage just because phantom rw volume is there... */
1965             /* (If a read-only volume exists, read/write inodes must also exist) */
1966             if (i == 0 && isp->nSpecialInodes == 0 && nVols >1)
1967                 continue;
1968             return 0;
1969         }
1970         IH_INIT(h, fileSysDevice, vs->header.parent, vs->header.volumeInfo);
1971         if (IH_IREAD(h, 0, (char*)&volHeader, sizeof(volHeader))
1972             == sizeof(volHeader)
1973             && volHeader.stamp.magic == VOLUMEINFOMAGIC
1974             && volHeader.dontSalvage == DONT_SALVAGE
1975             && volHeader.needsSalvaged == 0
1976             && volHeader.destroyMe == 0) {
1977             if (volHeader.inUse == 1) {
1978                 volHeader.inUse = 0;
1979                 volHeader.inService = 1;
1980                 if (!Testing) {
1981                     if (IH_IWRITE(h, 0, (char*)&volHeader, sizeof(volHeader))
1982                         != sizeof(volHeader)) {
1983                         IH_RELEASE(h);
1984                         return 0;
1985                     }
1986                 }
1987             }
1988             IH_RELEASE(h);
1989         }
1990         else {
1991             IH_RELEASE(h);
1992             return 0;
1993         }
1994     }
1995     return 1;
1996 }
1997
1998
1999 /* SalvageVolumeHeaderFile
2000  *
2001  * Salvage the top level V*.vol header file. Make sure the special files
2002  * exist and that there are no duplicates.
2003  *
2004  * Calls SalvageHeader for each possible type of volume special file.
2005  */
2006
2007 int SalvageVolumeHeaderFile(register struct InodeSummary *isp,
2008                             register struct ViceInodeInfo *inodes,
2009                             int RW, int check, int *deleteMe)
2010 {
2011     int headerFd = 0;
2012     int i;
2013     register struct ViceInodeInfo *ip;
2014     int allinodesobsolete = 1;
2015     struct VolumeDiskHeader diskHeader;
2016
2017     if (deleteMe)
2018         *deleteMe = 0;
2019     memset(&tempHeader, 0, sizeof(tempHeader));
2020     tempHeader.stamp.magic = VOLUMEHEADERMAGIC;
2021     tempHeader.stamp.version = VOLUMEHEADERVERSION;
2022     tempHeader.id = isp->volumeId;
2023     tempHeader.parent = isp->RWvolumeId;
2024     /* Check for duplicates (inodes are sorted by type field) */
2025     for (i = 0; i<isp->nSpecialInodes-1; i++) {
2026         ip = &inodes[isp->index+i];
2027         if (ip->u.special.type == (ip+1)->u.special.type) {
2028             if (!Showmode) Log("Duplicate special inodes in volume header; salvage of volume %u aborted\n", isp->volumeId);
2029             return -1;
2030         }
2031     }
2032     for (i = 0; i<isp->nSpecialInodes; i++) {
2033         ip = &inodes[isp->index+i];
2034         if (ip->u.special.type <= 0 || ip->u.special.type > MAXINODETYPE) {
2035             if (check) {
2036                 Log("Rubbish header inode\n");
2037                 return -1;
2038             }
2039             Log("Rubbish header inode; deleted\n");
2040         }
2041         else if (!stuff[ip->u.special.type-1].obsolete) {
2042             *(stuff[ip->u.special.type-1].inode) = ip->inodeNumber;
2043             if (!check && ip->u.special.type != VI_LINKTABLE)
2044                 ip->linkCount--;        /* Keep the inode around */
2045             allinodesobsolete = 0;
2046         }
2047     }
2048
2049     if (allinodesobsolete) {
2050         if (deleteMe)
2051             *deleteMe = 1;
2052         return -1;
2053     }
2054
2055     if (!check)
2056         VGLinkH_cnt ++; /* one for every header. */
2057
2058     if (!RW && !check && isp->volSummary) {
2059         ClearROInUseBit(isp->volSummary);
2060         return 0;
2061     }
2062
2063     for (i = 0; i< MAXINODETYPE; i++) {
2064         if (stuff[i].inodeType == VI_LINKTABLE) {
2065             /* Gross hack: SalvageHeader does a bcmp on the volume header.
2066              * And we may have recreated the link table earlier, so set the
2067              * RW header as well.
2068              */
2069             if (VALID_INO(VGLinkH->ih_ino)) {
2070                 *stuff[i].inode = VGLinkH->ih_ino;
2071             }
2072             continue;
2073         }
2074         if (SalvageHeader(&stuff[i],isp,check,deleteMe) == -1 && check)
2075             return -1;
2076     }
2077
2078     if (isp->volSummary == NULL) {
2079         char name[64];
2080         sprintf(name, VFORMAT, isp->volumeId);
2081         if (check) {
2082             Log("No header file for volume %u\n", isp->volumeId);
2083             return -1;
2084         }
2085         if (!Showmode) Log("No header file for volume %u; %screating %s/%s\n",
2086             isp->volumeId, (Testing?"it would have been ":""),
2087                            fileSysPathName, name);
2088         headerFd = open(name, O_RDWR|O_CREAT|O_TRUNC, 0644);
2089         assert(headerFd != -1)
2090         isp->volSummary = (struct VolumeSummary *)
2091             malloc(sizeof(struct VolumeSummary));
2092         isp->volSummary->fileName = ToString(name);
2093     }
2094     else {
2095         char name[64];
2096         /* hack: these two fields are obsolete... */
2097         isp->volSummary->header.volumeAcl        = 0;
2098         isp->volSummary->header.volumeMountTable = 0;
2099
2100         if (memcmp(&isp->volSummary->header, &tempHeader, sizeof(struct VolumeHeader))) {
2101             /* We often remove the name before calling us, so we make a fake one up */
2102             if (isp->volSummary->fileName) {
2103                 strcpy(name, isp->volSummary->fileName);
2104             } else {
2105                 sprintf(name, VFORMAT, isp->volumeId);
2106                 isp->volSummary->fileName = ToString(name);
2107             }
2108
2109             Log("Header file %s is damaged or no longer valid%s\n", 
2110                 name, (check ? "" : "; repairing"));
2111             if (check)
2112                 return -1;
2113
2114             headerFd = open(name, O_RDWR|O_TRUNC, 0644);
2115             assert(headerFd != -1)
2116           }
2117     }
2118     if (headerFd) {
2119         memcpy(&isp->volSummary->header, &tempHeader, sizeof(struct VolumeHeader));
2120         if (Testing) {
2121             if (!Showmode) Log("It would have written a new header file for volume %u\n", isp->volumeId);
2122         } else {
2123             VolumeHeaderToDisk(&diskHeader, &tempHeader);
2124             if (write(headerFd, &diskHeader, sizeof(struct VolumeDiskHeader))
2125                 != sizeof(struct VolumeDiskHeader)) {
2126                 Log("Couldn't rewrite volume header file!\n");
2127                 close(headerFd);
2128                 return -1;
2129             }
2130         }
2131         close(headerFd);
2132     }
2133     IH_INIT(isp->volSummary->volumeInfoHandle, fileSysDevice,
2134             isp->RWvolumeId,  isp->volSummary->header.volumeInfo);
2135     return 0;
2136 }
2137
2138 int SalvageHeader(register struct stuff *sp, struct InodeSummary *isp,
2139                   int check, int *deleteMe)
2140 {
2141     union {
2142         VolumeDiskData          volumeInfo;
2143         struct versionStamp     fileHeader;
2144     } header;
2145     IHandle_t *specH;
2146     int recreate = 0;
2147     afs_int32 code;
2148     FdHandle_t *fdP;
2149     
2150     if (sp->obsolete)
2151         return 0;
2152 #ifndef AFS_NAMEI_ENV
2153     if ( sp->inodeType == VI_LINKTABLE)
2154         return 0;
2155 #endif
2156     if (*(sp->inode) == 0) {
2157         if (check) {
2158             Log("Missing inode in volume header (%s)\n", sp->description);
2159             return -1;
2160         }
2161         if (!Showmode) Log("Missing inode in volume header (%s); %s\n",
2162             sp->description, (Testing ? "it would have recreated it": "recreating"));
2163         if (!Testing) {
2164            *(sp->inode) = IH_CREATE(NULL, fileSysDevice, fileSysPath, 0,
2165                                     isp->volumeId, INODESPECIAL,
2166                                     sp->inodeType, isp->RWvolumeId);
2167            if (!VALID_INO(*(sp->inode)))
2168               Abort("Unable to allocate inode (%s) for volume header (error = %d)\n",
2169                     sp->description, errno);
2170         }
2171         recreate = 1;
2172     }
2173
2174     IH_INIT(specH, fileSysDevice, isp->RWvolumeId, *(sp->inode));
2175     fdP = IH_OPEN(specH);
2176     if (OKToZap && (fdP == NULL) && BadError(errno)) {
2177         /* bail out early and destroy the volume */
2178         if (!Showmode) Log("Still can't open volume header inode (%s), destroying volume\n",
2179             sp->description);
2180         if (deleteMe) *deleteMe = 1;
2181         IH_RELEASE(specH);
2182         return -1;
2183     }
2184     if (fdP == NULL)
2185        Abort("Unable to open inode (%s) of volume header (error = %d)\n",
2186              sp->description, errno);
2187     
2188     if (!recreate &&
2189       (FDH_READ(fdP, (char*)&header, sp->size) != sp->size
2190         || header.fileHeader.magic != sp->stamp.magic)) {
2191         if (check) {
2192             Log("Part of the header (%s) is corrupted\n", sp->description);
2193             FDH_REALLYCLOSE(fdP);
2194             IH_RELEASE(specH);
2195             return -1;
2196         }
2197         Log("Part of the header (%s) is corrupted; recreating\n",
2198           sp->description);
2199         recreate = 1;
2200     }
2201     if (sp->inodeType == VI_VOLINFO && header.volumeInfo.destroyMe == DESTROY_ME) {
2202         if (deleteMe)
2203             *deleteMe = 1;
2204         FDH_REALLYCLOSE(fdP);
2205         IH_RELEASE(specH);
2206         return -1;
2207     }
2208     if (recreate && !Testing) {
2209         if (check)
2210            Abort("Internal error: recreating volume header (%s) in check mode\n",
2211                  sp->description);
2212         code = FDH_TRUNC(fdP, 0);
2213         if (code == -1)
2214            Abort("Unable to truncate volume header file (%s) (error = %d)\n",
2215                  sp->description, errno);
2216
2217         /* The following code should be moved into vutil.c */
2218         if (sp->inodeType == VI_VOLINFO) {
2219             struct timeval tp;
2220             memset(&header.volumeInfo, 0, sizeof (header.volumeInfo));
2221             header.volumeInfo.stamp = sp->stamp;
2222             header.volumeInfo.id = isp->volumeId;
2223             header.volumeInfo.parentId = isp->RWvolumeId;
2224             sprintf(header.volumeInfo.name, "bogus.%u",isp->volumeId);
2225             Log("Warning: the name of volume %u is now \"bogus.%u\"\n", isp->volumeId, isp->volumeId);
2226             header.volumeInfo.inService = 0;
2227             header.volumeInfo.blessed = 0;
2228             /* The + 1000 is a hack in case there are any files out in venus caches */
2229             header.volumeInfo.uniquifier = (isp->maxUniquifier+1)+1000;
2230             header.volumeInfo.type =
2231                 (isp->volumeId == isp->RWvolumeId? readwriteVolume:readonlyVolume); /* XXXX */
2232             header.volumeInfo.needsCallback = 0;
2233             gettimeofday(&tp,0);
2234             header.volumeInfo.creationDate = tp.tv_sec;
2235             if(FDH_SEEK(fdP,0,SEEK_SET)<0) {
2236               Abort("Unable to seek to beginning of volume header file (%s) (errno = %d)\n",sp->description,errno);
2237             }
2238             code = FDH_WRITE(fdP, (char*)&header.volumeInfo,
2239                              sizeof(header.volumeInfo));
2240             if (code != sizeof(header.volumeInfo)) {
2241               if (code < 0)
2242                 Abort("Unable to write volume header file (%s) (errno = %d)\n",
2243                       sp->description, errno);
2244               Abort("Unable to write entire volume header file (%s)\n",
2245                     sp->description);
2246             }
2247         } 
2248         else {
2249           if(FDH_SEEK(fdP,0,SEEK_SET)<0) {
2250             Abort("Unable to seek to beginning of volume header file (%s) (errno = %d)\n",sp->description,errno);
2251           }
2252           code = FDH_WRITE(fdP, (char*)&sp->stamp, sizeof(sp->stamp));
2253           if (code != sizeof(sp->stamp)) {
2254             if (code < 0)
2255                   Abort("Unable to write version stamp in volume header file (%s) (errno = %d)\n",
2256                         sp->description, errno);
2257                Abort("Unable to write entire version stamp in volume header file (%s)\n",
2258                         sp->description);
2259             }
2260         }
2261     }
2262     FDH_REALLYCLOSE(fdP);
2263     IH_RELEASE(specH);
2264     if (sp->inodeType == VI_VOLINFO) {
2265         VolInfo = header.volumeInfo;
2266         if (check) {
2267             char update[25];
2268             if (VolInfo.updateDate) {
2269                 strcpy(update, TimeStamp(VolInfo.updateDate, 0));
2270                 if (!Showmode) Log("%s (%u) %supdated %s\n", VolInfo.name, VolInfo.id, 
2271                     (Testing?"it would have been ":""), update);
2272             } else {
2273                 strcpy(update, TimeStamp(VolInfo.creationDate, 0));
2274                 if (!Showmode) Log("%s (%u) not updated (created %s)\n", VolInfo.name, VolInfo.id, update);
2275             }
2276
2277         }
2278     }   
2279
2280     return 0;
2281 }
2282
2283 int SalvageVnodes(register struct InodeSummary *rwIsp,
2284                   register struct InodeSummary * thisIsp,
2285                   register struct ViceInodeInfo * inodes, int check)
2286 {
2287     int ilarge, ismall, ioffset, RW, nInodes;
2288     ioffset = rwIsp->index+rwIsp->nSpecialInodes; /* first inode */
2289     if (Showmode) return 0;
2290     RW = (rwIsp == thisIsp);
2291     nInodes = (rwIsp->nInodes - rwIsp->nSpecialInodes);
2292     ismall = SalvageIndex(thisIsp->volSummary->header.smallVnodeIndex,
2293         vSmall, RW, &inodes[ioffset], nInodes, thisIsp->volSummary, check);
2294     if (check && ismall == -1)
2295         return -1;
2296     ilarge = SalvageIndex(thisIsp->volSummary->header.largeVnodeIndex,
2297         vLarge, RW, &inodes[ioffset], nInodes, thisIsp->volSummary, check);
2298     return (ilarge==0 && ismall==0 ? 0: -1);
2299 }
2300
2301 int SalvageIndex(Inode ino, VnodeClass class, int RW,
2302                  register struct ViceInodeInfo *ip,
2303                  int nInodes, struct VolumeSummary *volSummary, int check)
2304 {
2305     VolumeId volumeNumber;
2306     char buf[SIZEOF_LARGEDISKVNODE];
2307     struct VnodeDiskObject *vnode = (struct VnodeDiskObject *) buf;
2308     int err = 0;
2309     StreamHandle_t *file;
2310     struct VnodeClassInfo *vcp;
2311     int size;
2312     int vnodeIndex, nVnodes;
2313     afs_ino_str_t stmp1, stmp2;
2314     IHandle_t *handle;
2315     FdHandle_t *fdP;
2316
2317     volumeNumber = volSummary->header.id;
2318     IH_INIT(handle, fileSysDevice, volSummary->header.parent, ino);
2319     fdP = IH_OPEN(handle);
2320     assert(fdP != NULL);
2321     file = FDH_FDOPEN(fdP, "r+");
2322     assert(file != NULL)
2323     vcp = &VnodeClassInfo[class];
2324     size = OS_SIZE(fdP->fd_fd);
2325     assert(size != -1);
2326     nVnodes = (size / vcp->diskSize) - 1;
2327     if (nVnodes > 0) {
2328         assert((nVnodes+1) * vcp->diskSize == size)
2329         assert(STREAM_SEEK(file, vcp->diskSize, 0) == 0)
2330     }
2331     else {
2332         nVnodes = 0;
2333     }
2334     for (vnodeIndex = 0;
2335       nVnodes && STREAM_READ(vnode, vcp->diskSize, 1, file) == 1;
2336       nVnodes--, vnodeIndex++) {
2337         if (vnode->type != vNull) {
2338             int vnodeChanged = 0;
2339             int vnodeNumber = bitNumberToVnodeNumber(vnodeIndex, class);
2340             /* Log programs that belong to root (potentially suid root);
2341              don't bother for read-only or backup volumes */
2342 #ifdef  notdef  /* This is done elsewhere */
2343             if (ShowRootFiles && RW && vnode->owner==0 && vnodeNumber != 1)
2344                 Log("OWNER IS ROOT %s %u dir %u vnode %u author %u owner %u mode %o\n",
2345                     VolInfo.name, volumeNumber, vnode->parent, vnodeNumber, vnode->author,
2346                     vnode->owner, vnode->modeBits);
2347 #endif
2348             if (VNDISK_GET_INO(vnode) == 0) {
2349                 if (RW) {
2350                     /* Log("### DEBUG ### Deleted Vnode with 0 inode (vnode %d)\n", vnodeNumber); */
2351                     memset(vnode, 0, vcp->diskSize);
2352                     vnodeChanged = 1;
2353                 }
2354             }
2355             else {
2356                 if (vcp->magic != vnode->vnodeMagic) {
2357                     /* bad magic #, probably partially created vnode */
2358                     Log("Partially allocated vnode %d deleted.\n", vnodeNumber);
2359                     memset(vnode, 0, vcp->diskSize);
2360                     vnodeChanged = 1;
2361                     goto vnodeDone;
2362                 }
2363                 /* ****** Should do a bit more salvage here:  e.g. make sure
2364                    vnode type matches what it should be given the index */
2365                 while (nInodes && ip->u.vnode.vnodeNumber < vnodeNumber) {
2366 /*                  if (vnodeIdToClass(ip->u.vnode.vnodeNumber) == class && RW) {
2367  *                     Log("Inode %d: says it belongs to non-existing vnode %d\n",
2368  *                         ip->inodeNumber, ip->u.vnode.vnodeNumber);
2369  *                  }
2370  */
2371                     ip++;
2372                     nInodes--;
2373                 }
2374                 if (!RW) {
2375                     while (nInodes && ip->u.vnode.vnodeNumber == vnodeNumber) {
2376                         /* The following doesn't work, because the version number
2377                            is not maintained correctly by the file server */
2378                         /*if (vnode->uniquifier == ip->u.vnode.vnodeUniquifier &&
2379                             vnode->dataVersion == ip->u.vnode.inodeDataVersion)
2380                             break;*/
2381                         if (VNDISK_GET_INO(vnode) == ip->inodeNumber)
2382                             break;
2383                         ip++;
2384                         nInodes--;
2385                     }
2386                 }
2387                 else {
2388                     /* For RW volume, look for vnode with matching inode number;
2389                        if no such match, take the first determined by our sort
2390                        order */
2391                     register struct ViceInodeInfo *lip = ip;
2392                     register lnInodes = nInodes;
2393                     while (lnInodes && lip->u.vnode.vnodeNumber == vnodeNumber) {
2394                         if (VNDISK_GET_INO(vnode) == lip->inodeNumber) {
2395                             ip = lip;
2396                             nInodes = lnInodes;
2397                             break;
2398                         }
2399                         lip++;
2400                         lnInodes--;
2401                     }
2402                 }
2403                 if (nInodes && ip->u.vnode.vnodeNumber == vnodeNumber) {
2404                     /* "Matching" inode */
2405                     if (RW) {
2406                         Unique vu,iu;
2407                         FileVersion vd,id;
2408                         vu = vnode->uniquifier;
2409                         iu = ip->u.vnode.vnodeUniquifier;
2410                         vd = vnode->dataVersion;
2411                         id = ip->u.vnode.inodeDataVersion;
2412                         /*
2413                          * Because of the possibility of the uniquifier overflows (> 4M)
2414                          * we compare them modulo the low 22-bits; we shouldn't worry
2415                          * about mismatching since they shouldn't to many old 
2416                          * uniquifiers of the same vnode...
2417                          */
2418                         if (IUnique(vu) != IUnique(iu)) {
2419                             if (!Showmode) {
2420                                Log("Vnode %u: vnode.unique, %u, does not match inode unique, %u; fixed, but status will be wrong\n",
2421                                    vnodeNumber, IUnique(vu), IUnique(iu));
2422                             }
2423                             
2424                             vnode->uniquifier = iu;
2425 #ifdef  AFS_3DISPARES
2426                             vnode->dataVersion = (id >= vd ? 
2427                                 /* 90% of 2.1M */ ((id-vd) > 1887437 ? vd:id):
2428                                 /* 90% of 2.1M */ ((vd-id) > 1887437 ? id:vd));
2429 #else
2430 #if defined(AFS_SGI_EXMAG)
2431                             vnode->dataVersion = (id >= vd ? 
2432                                 /* 90% of 16M */ ((id-vd) > 15099494 ? vd:id):
2433                                 /* 90% of 16M */ ((vd-id) > 15099494 ? id:vd));
2434 #else
2435                             vnode->dataVersion = (id>vd ? id:vd);
2436 #endif /* AFS_SGI_EXMAG */
2437 #endif  /* AFS_3DISPARES */
2438                             vnodeChanged = 1;
2439                         }
2440                         else {
2441                             /* don't bother checking for vd > id any more, since
2442                                 partial file transfers always result in this state,
2443                                 and you can't do much else anyway (you've already
2444                                 found the best data you can) */
2445 #ifdef  AFS_3DISPARES
2446                             if (!vnodeIsDirectory(vnodeNumber) && 
2447                                 ((vd < id && (id-vd) < 1887437) ||
2448                                 ((vd > id && (vd-id) > 1887437)))) {
2449 #else
2450 #if defined(AFS_SGI_EXMAG)
2451                             if (!vnodeIsDirectory(vnodeNumber) && 
2452                                 ((vd < id && (id-vd) < 15099494) ||
2453                                 ((vd > id && (vd-id) > 15099494)))) {
2454 #else
2455                             if (!vnodeIsDirectory(vnodeNumber) && vd < id) {
2456 #endif /* AFS_SGI_EXMAG */
2457 #endif
2458                                 if (!Showmode) Log("Vnode %d: version < inode version; fixed (old status)\n", vnodeNumber);
2459                                 vnode->dataVersion = id;
2460                                 vnodeChanged = 1;
2461                             }
2462                         }
2463                     }
2464                     if (ip->inodeNumber != VNDISK_GET_INO(vnode)) {
2465                         if (check) {
2466                             if (!Showmode) {
2467                                 Log("Vnode %d:  inode number incorrect (is %s should be %s). FileSize=%d\n",
2468                                     vnodeNumber,
2469                                     PrintInode(stmp1, VNDISK_GET_INO(vnode)),
2470                                     PrintInode(stmp2, ip->inodeNumber),
2471                                     ip->byteCount);
2472                             }
2473                             VNDISK_SET_INO(vnode, ip->inodeNumber);
2474                             err = -1;
2475                             goto zooks;
2476                         }
2477                         if (!Showmode) {
2478                             Log("Vnode %d: inode number incorrect; changed from %s to %s. FileSize=%d\n",
2479                                 vnodeNumber,
2480                                 PrintInode(stmp1, VNDISK_GET_INO(vnode)),
2481                                 PrintInode(stmp2, ip->inodeNumber),
2482                                 ip->byteCount);
2483                         }
2484                         VNDISK_SET_INO(vnode, ip->inodeNumber);
2485                         vnodeChanged = 1;
2486                     }
2487                     if (ip->byteCount != vnode->length) {
2488                         if (check) {
2489                             if (!Showmode) Log("Vnode %d: length incorrect; (is %d should be %d)\n",
2490                                                vnodeNumber, vnode->length, ip->byteCount);
2491                             err = -1;
2492                             goto zooks;
2493                         }
2494                         if (!Showmode) Log("Vnode %d: length incorrect; changed from %d to %d\n",
2495                                            vnodeNumber, vnode->length, ip->byteCount);
2496                         vnode->length = ip->byteCount;
2497                         vnodeChanged = 1;
2498                     }
2499                     if (!check)
2500                         ip->linkCount--;        /* Keep the inode around */
2501                     ip++;
2502                     nInodes--;
2503                 }
2504                 else { /* no matching inode */
2505                     if (VNDISK_GET_INO(vnode) != 0 || vnode->type == vDirectory) {
2506                         /* No matching inode--get rid of the vnode */
2507                         if (check) {
2508                             if (VNDISK_GET_INO(vnode)) {
2509                                 if (!Showmode) {
2510                                     Log("Vnode %d (unique %d): corresponding inode %s is missing\n",
2511                                         vnodeNumber, vnode->uniquifier,
2512                                         PrintInode(NULL, VNDISK_GET_INO(vnode)));
2513                                 }
2514                             } else {
2515                                 if (!Showmode) Log("Vnode %d (unique %d): bad directory vnode (no inode number listed)\n", vnodeNumber, vnode->uniquifier);
2516                             }
2517                             err = -1;
2518                             goto zooks;
2519                         }
2520                         if (VNDISK_GET_INO(vnode)) {
2521                             if (!Showmode) {
2522                                 Log("Vnode %d (unique %d): corresponding inode %s is missing; vnode deleted, vnode mod time=%s",
2523                                     vnodeNumber, vnode->uniquifier,
2524                                     PrintInode(NULL, VNDISK_GET_INO(vnode)),
2525                                     ctime((time_t *)&(vnode->serverModifyTime)));
2526                             }
2527                         } else {
2528                             if (!Showmode) Log("Vnode %d (unique %d): bad directory vnode (no inode number listed); vnode deleted, vnode mod time=%s", vnodeNumber, vnode->uniquifier, ctime((time_t *)&(vnode->serverModifyTime)));
2529                         }
2530                         memset(vnode, 0, vcp->diskSize);
2531                         vnodeChanged = 1;
2532                     } else {
2533                        /* Should not reach here becuase we checked for 
2534                         * (inodeNumber == 0) above. And where we zero the vnode,
2535                         * we also goto vnodeDone.
2536                         */
2537                     }
2538                 }
2539                 while (nInodes && ip->u.vnode.vnodeNumber == vnodeNumber) {
2540                     ip++;
2541                     nInodes--;
2542                 }
2543             }   /* VNDISK_GET_INO(vnode) != 0 */
2544           vnodeDone:
2545             assert(!(vnodeChanged && check));
2546             if (vnodeChanged && !Testing) {
2547                 assert(IH_IWRITE(handle, vnodeIndexOffset(vcp,vnodeNumber),
2548                                  (char*)vnode, vcp->diskSize)
2549                   == vcp->diskSize);
2550                 VolumeChanged = 1;      /* For break call back */
2551             }
2552         }
2553     }
2554 zooks:
2555     STREAM_CLOSE(file);
2556     FDH_CLOSE(fdP);
2557     IH_RELEASE(handle);
2558     return err;
2559 }
2560
2561 struct VnodeEssence *CheckVnodeNumber(vnodeNumber)
2562     VnodeId vnodeNumber;
2563 {
2564     VnodeClass class;
2565     struct VnodeInfo *vip;
2566     int offset;
2567
2568     class = vnodeIdToClass(vnodeNumber);
2569     vip = &vnodeInfo[class];
2570     offset = vnodeIdToBitNumber(vnodeNumber);
2571     return (offset >= vip->nVnodes? NULL: &vip->vnodes[offset]);
2572 }
2573
2574
2575 void CopyOnWrite(register struct DirSummary *dir)
2576 {
2577     /* Copy the directory unconditionally if we are going to change it:
2578      * not just if was cloned.
2579      */
2580     struct VnodeDiskObject vnode;
2581     struct VnodeClassInfo *vcp = &VnodeClassInfo[vLarge];
2582     Inode oldinode, newinode;
2583     int code;
2584
2585     if (dir->copied || Testing)
2586         return;
2587     DFlush(); /* Well justified paranoia... */
2588
2589     code = IH_IREAD(vnodeInfo[vLarge].handle,
2590                     vnodeIndexOffset(vcp, dir->vnodeNumber),
2591                      (char*)&vnode, sizeof (vnode));
2592     assert(code == sizeof(vnode));
2593     oldinode = VNDISK_GET_INO(&vnode);
2594     /* Increment the version number by a whole lot to avoid problems with
2595      * clients that were promised new version numbers--but the file server
2596      * crashed before the versions were written to disk.
2597      */
2598     newinode = IH_CREATE(dir->ds_linkH, fileSysDevice, fileSysPath, 0,
2599                          dir->rwVid, dir->vnodeNumber,
2600                          vnode.uniquifier, vnode.dataVersion += 200);
2601     assert(VALID_INO(newinode));
2602     assert(CopyInode(fileSysDevice, oldinode, newinode, dir->rwVid) == 0);
2603     vnode.cloned = 0;
2604     VNDISK_SET_INO(&vnode, newinode);
2605     code = IH_IWRITE(vnodeInfo[vLarge].handle,
2606                      vnodeIndexOffset(vcp, dir->vnodeNumber),
2607                      (char*)&vnode, sizeof (vnode));
2608     assert(code == sizeof (vnode));
2609
2610     SetSalvageDirHandle(&dir->dirHandle, dir->dirHandle.dirh_handle->ih_vid,
2611                         fileSysDevice, newinode);
2612     /* Don't delete the original inode right away, because the directory is
2613      * still being scanned.
2614      */
2615     dir->copied = 1;
2616 }
2617  
2618 /* This function should either successfully create a new dir, or give up and leave
2619  * things the way they were.  In particular, if it fails to write the new dir properly,
2620  * it should return w/o changing the reference to the old dir.
2621  */
2622 void CopyAndSalvage(register struct DirSummary *dir)
2623 {
2624     struct VnodeDiskObject vnode;
2625     struct VnodeClassInfo *vcp = &VnodeClassInfo[vLarge];
2626     Inode oldinode, newinode;
2627     DirHandle newdir;
2628     register afs_int32 code;
2629     afs_int32   parentUnique= 1;
2630     struct VnodeEssence *vnodeEssence;
2631
2632     if (Testing)
2633         return;
2634     Log("Salvaging directory %u...\n", dir->vnodeNumber);
2635     code = IH_IREAD(vnodeInfo[vLarge].handle,
2636                     vnodeIndexOffset(vcp, dir->vnodeNumber),
2637                     (char*)&vnode, sizeof (vnode));
2638     assert(code == sizeof (vnode));
2639     oldinode = VNDISK_GET_INO(&vnode);
2640     /* Increment the version number by a whole lot to avoid problems with
2641      * clients that were promised new version numbers--but the file server
2642      * crashed before the versions were written to disk.
2643      */
2644     newinode = IH_CREATE(dir->ds_linkH, fileSysDevice, fileSysPath, 0,
2645                          dir->rwVid, dir->vnodeNumber,
2646                          vnode.uniquifier,
2647         vnode.dataVersion += 200);
2648     assert(VALID_INO(newinode));
2649     SetSalvageDirHandle(&newdir, dir->rwVid, fileSysDevice, newinode);
2650
2651     /* Assign . and .. vnode numbers from dir and vnode.parent. 
2652      * The uniquifier for . is in the vnode.
2653      * The uniquifier for .. might be set to a bogus value of 1 and 
2654      * the salvager will later clean it up.
2655      */
2656     if ( vnode.parent && (vnodeEssence = CheckVnodeNumber(vnode.parent)) ) {
2657        parentUnique = (vnodeEssence->unique ? vnodeEssence->unique : 1);
2658     }
2659     code = DirSalvage(&dir->dirHandle, &newdir, 
2660                       dir->vnodeNumber, vnode.uniquifier, 
2661                       (vnode.parent?vnode.parent:dir->vnodeNumber), 
2662                       parentUnique);
2663     if (code == 0) code = DFlush();
2664     if (code) {
2665         /* didn't really build the new directory properly, let's just give up. */
2666         code = IH_DEC(dir->ds_linkH, newinode, dir->rwVid);
2667         assert(code == 0);
2668         Log("Directory salvage returned code %d, continuing.\n", code);
2669         assert(1 == 2);
2670     }
2671     Log("Checking the results of the directory salvage...\n");
2672     if (!DirOK(&newdir)) {
2673         Log("Directory salvage failed!!!; restoring old version of the directory.\n");
2674         code = IH_DEC(dir->ds_linkH, newinode, dir->rwVid);
2675         assert(code == 0);
2676         assert(1 == 2);
2677     }
2678     vnode.cloned = 0;
2679     VNDISK_SET_INO(&vnode, newinode);
2680     vnode.length = Length(&newdir);
2681     code = IH_IWRITE(vnodeInfo[vLarge].handle,
2682                     vnodeIndexOffset(vcp, dir->vnodeNumber),
2683                     (char*)&vnode, sizeof (vnode));
2684     assert(code == sizeof (vnode));
2685 #ifdef AFS_NT40_ENV
2686     nt_sync(fileSysDevice);
2687 #else
2688     sync();     /* this is slow, but hopefully rarely called.  We don't have
2689                  * an open FD on the file itself to fsync.
2690                  */
2691 #endif
2692     code = IH_DEC(dir->ds_linkH, oldinode, dir->rwVid);
2693     assert(code == 0);
2694     dir->dirHandle = newdir;
2695 }
2696
2697 void JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
2698                 Unique unique)
2699 {
2700     struct VnodeEssence *vnodeEssence;
2701     afs_int32 dirOrphaned, todelete;
2702
2703     dirOrphaned = IsVnodeOrphaned(dir->vnodeNumber);
2704
2705     vnodeEssence = CheckVnodeNumber(vnodeNumber);
2706     if (vnodeEssence == NULL) {
2707         if (!Showmode) {
2708            Log("dir vnode %d: invalid entry deleted: %s/%s (vnode %d, unique %d)\n", 
2709                dir->vnodeNumber, dir->name?dir->name:"??",
2710                name, vnodeNumber, unique);
2711         }
2712         if (!Testing) {
2713             CopyOnWrite(dir);
2714             assert(Delete(&dir->dirHandle, name) == 0)
2715         }
2716         return;
2717     }
2718  
2719 #ifdef AFS_AIX_ENV
2720     /* On AIX machines, don't allow entries to point to inode 0. That is a special 
2721      * mount inode for the partition. If this inode were deleted, it would crash
2722      * the machine.
2723      */
2724     if (vnodeEssence->InodeNumber == 0) {
2725        Log("dir vnode %d: invalid entry: %s/%s has no inode (vnode %d, unique %d)%s\n",
2726            dir->vnodeNumber, (dir->name?dir->name:"??"),
2727            name, vnodeNumber, unique, 
2728            (Testing?"-- would have deleted":" -- deleted"));
2729        if (!Testing) {
2730          CopyOnWrite(dir);
2731          assert(Delete(&dir->dirHandle, name) == 0);
2732        }
2733        return;
2734     }
2735 #endif
2736
2737     if (!(vnodeNumber & 1) && !Showmode &&
2738         !(vnodeEssence->count || vnodeEssence->unique || vnodeEssence->modeBits)) {
2739        Log("dir vnode %d: invalid entry: %s/%s (vnode %d, unique %d)%s\n",
2740            dir->vnodeNumber, (dir->name?dir->name:"??"),
2741            name, vnodeNumber, unique, 
2742            ((!unique)?(Testing?"-- would have deleted":" -- deleted"):""));
2743        if (!unique) {
2744           if (!Testing) {
2745              CopyOnWrite(dir);
2746              assert(Delete(&dir->dirHandle, name) == 0);
2747           }
2748           return;
2749        }
2750     }
2751
2752     /* Check if the Uniquifiers match. If not, change the directory entry
2753      * so its unique matches the vnode unique. Delete if the unique is zero
2754      * or if the directory is orphaned.
2755      */
2756     if (!IUnique(vnodeEssence->unique) || 
2757         (IUnique(vnodeEssence->unique) != IUnique(unique)) ) {
2758         if ( !IUnique(vnodeEssence->unique) && 
2759              ((strcmp(name,"..")==0) || (strcmp(name,".")==0)) ) {
2760            /* This is an orphaned directory. Don't delete the . or ..
2761             * entry. Otherwise, it will get created in the next 
2762             * salvage and deleted again here. So Just skip it.
2763             */
2764            return;
2765         }
2766     
2767         todelete = ((!vnodeEssence->unique || dirOrphaned) ? 1 : 0);
2768
2769         if (!Showmode) {
2770            Log("dir vnode %d: %s/%s (vnode %d): unique changed from %d to %d %s\n",
2771                dir->vnodeNumber, (dir->name ? dir->name : "??"), 
2772                name, vnodeNumber, unique, vnodeEssence->unique,
2773                (!todelete?"":(Testing?"-- would have deleted":"-- deleted")));
2774         }
2775         if (!Testing) {
2776             ViceFid fid;
2777             fid.Vnode = vnodeNumber;
2778             fid.Unique = vnodeEssence->unique;
2779             CopyOnWrite(dir);
2780             assert(Delete(&dir->dirHandle, name) == 0)
2781             if (!todelete)
2782                assert(Create(&dir->dirHandle, name, &fid) == 0)
2783         }
2784         if (todelete) return; /* no need to continue */
2785     }
2786
2787     if (strcmp(name,".") == 0) {
2788         if (dir->vnodeNumber != vnodeNumber || (IUnique(dir->unique) != IUnique(unique))) {
2789             ViceFid fid;
2790             if (!Showmode) Log("directory vnode %d.%d: bad '.' entry (was %d.%d); fixed\n",
2791                 dir->vnodeNumber, dir->unique, vnodeNumber, unique);
2792             if (!Testing) {
2793                 CopyOnWrite(dir);
2794                 assert(Delete(&dir->dirHandle, ".") == 0)
2795                 fid.Vnode = dir->vnodeNumber;
2796                 fid.Unique = dir->unique;
2797                 assert(Create(&dir->dirHandle, ".", &fid) == 0)
2798             }
2799
2800             vnodeNumber = fid.Vnode;         /* Get the new Essence */
2801             unique = fid.Unique;
2802             vnodeEssence = CheckVnodeNumber(vnodeNumber);
2803         }
2804         dir->haveDot = 1;
2805     }
2806     else if (strcmp(name,"..") == 0) {
2807         ViceFid pa;
2808         if (dir->parent) {
2809             struct VnodeEssence *dotdot;
2810             pa.Vnode = dir->parent;
2811             dotdot = CheckVnodeNumber(pa.Vnode);
2812             assert (dotdot != NULL); /* XXX Should not be assert */
2813             pa.Unique = dotdot->unique;
2814         }
2815         else {
2816             pa.Vnode = dir->vnodeNumber;
2817             pa.Unique = dir->unique;
2818         }
2819         if ((pa.Vnode != vnodeNumber) || (IUnique(pa.Unique) != IUnique(unique))) {
2820             if (!Showmode) Log("directory vnode %d.%d: bad '..' entry (was %d.%d); fixed\n",
2821                 dir->vnodeNumber, IUnique(dir->unique), vnodeNumber, IUnique(unique));
2822             if (!Testing) {
2823                 CopyOnWrite(dir);
2824                 assert(Delete(&dir->dirHandle, "..") == 0);
2825                 assert(Create(&dir->dirHandle, "..", &pa) == 0);
2826             }
2827
2828             vnodeNumber = pa.Vnode;         /* Get the new Essence */
2829             unique = pa.Unique;
2830             vnodeEssence = CheckVnodeNumber(vnodeNumber);
2831         }
2832         dir->haveDotDot = 1;
2833     } else if (strncmp(name,".__afs",6) == 0) {
2834         if (!Showmode) {
2835             Log("dir vnode %d: special old unlink-while-referenced file %s %s deleted (vnode %d)\n",
2836                dir->vnodeNumber, name, (Testing?"would have been":"is"), vnodeNumber);
2837         }
2838         if (!Testing) {
2839             CopyOnWrite(dir);
2840             assert(Delete(&dir->dirHandle, name) == 0)
2841         }
2842         vnodeEssence->claimed  = 0;   /* Not claimed: Orphaned */
2843         vnodeEssence->todelete = 1;   /* Will later delete vnode and decr inode */
2844         return;
2845     }
2846     else {
2847         if (ShowSuid && (vnodeEssence->modeBits & 06000))
2848             Log("FOUND suid/sgid file: %s/%s (%u.%u %05o) author %u (vnode %u dir %u)\n", dir->name?dir->name:"??", name,
2849                 vnodeEssence->owner, vnodeEssence->group, vnodeEssence->modeBits, vnodeEssence->author,vnodeNumber, dir->vnodeNumber);
2850         if (ShowMounts && (vnodeEssence->type == vSymlink) && !(vnodeEssence->modeBits & 0111)) {
2851             int code, size;         
2852             char buf[1024];
2853             IHandle_t *ihP;
2854             FdHandle_t *fdP;
2855
2856             IH_INIT(ihP, fileSysDevice, dir->dirHandle.dirh_handle->ih_vid,
2857                     vnodeEssence->InodeNumber);
2858             fdP = IH_OPEN(ihP);
2859             assert(fdP != NULL);
2860             size = FDH_SIZE(fdP);
2861             assert(size != -1);
2862             memset(buf, 0, 1024);
2863             if (size > 1024) size = 1024;
2864             code = FDH_READ(fdP, buf, size);
2865             assert(code == size);
2866             Log("In volume %u (%s) found mountpoint %s/%s to '%s'\n",
2867                 dir->dirHandle.dirh_handle->ih_vid, dir->vname,
2868                 dir->name?dir->name:"??", name, buf);
2869             FDH_REALLYCLOSE(fdP);
2870             IH_RELEASE(ihP);
2871         }
2872         if (ShowRootFiles && vnodeEssence->owner==0 && vnodeNumber != 1)
2873             Log("FOUND root file: %s/%s (%u.%u %05o) author %u (vnode %u dir %u)\n", dir->name?dir->name:"??", name,
2874                 vnodeEssence->owner, vnodeEssence->group, vnodeEssence->modeBits, vnodeEssence->author, vnodeNumber, dir->vnodeNumber);
2875         if (vnodeIdToClass(vnodeNumber) == vLarge && vnodeEssence->name == NULL) {
2876             char *n;
2877             if (n = (char*)malloc(strlen(name)+1))
2878                 strcpy(n, name);
2879             vnodeEssence->name = n;
2880         }
2881
2882         /* The directory entry points to the vnode. Check to see if the
2883          * vnode points back to the directory. If not, then let the 
2884          * directory claim it (else it might end up orphaned). Vnodes 
2885          * already claimed by another directory are deleted from this
2886          * directory: hardlinks to the same vnode are not allowed
2887          * from different directories.
2888          */
2889         if (vnodeEssence->parent != dir->vnodeNumber) {
2890            if (!vnodeEssence->claimed && !dirOrphaned) {
2891               /* Vnode does not point back to this directory.
2892                * Orphaned dirs cannot claim a file (it may belong to
2893                * another non-orphaned dir).
2894                */
2895               if (!Showmode) {
2896                  Log("dir vnode %d: %s/%s (vnode %d, unique %d) -- parent vnode %schanged from %d to %d\n",
2897                      dir->vnodeNumber, (dir->name ? dir->name : "??"), name,
2898                      vnodeNumber, unique, (Testing?"would have been ":""),
2899                      vnodeEssence->parent, dir->vnodeNumber);
2900               }
2901               vnodeEssence->parent = dir->vnodeNumber;
2902               vnodeEssence->changed = 1;
2903            } else {
2904               /* Vnode was claimed by another directory */
2905               if (!Showmode) {
2906                  if (dirOrphaned) {
2907                    Log("dir vnode %d: %s/%s parent vnode is %d (vnode %d, unique %d) -- %sdeleted\n",
2908                        dir->vnodeNumber, (dir->name ? dir->name : "??"), name,
2909                        vnodeEssence->parent, vnodeNumber, unique,
2910                        (Testing?"would have been ":""));
2911                  } else {
2912                    Log("dir vnode %d: %s/%s already claimed by directory vnode %d (vnode %d, unique %d) -- %sdeleted\n",
2913                        dir->vnodeNumber, (dir->name ? dir->name : "??"), name,
2914                        vnodeEssence->parent, vnodeNumber, unique, 
2915                        (Testing?"would have been ":""));
2916                  }
2917               }
2918               if (!Testing) {
2919                  CopyOnWrite(dir);
2920                  assert(Delete(&dir->dirHandle, name) == 0);
2921               }
2922               return;
2923            }
2924         }
2925         /* This directory claims the vnode */
2926         vnodeEssence->claimed = 1;
2927     }
2928     vnodeEssence->count--;
2929 }
2930
2931 void DistilVnodeEssence(VolumeId rwVId, VnodeClass class, Inode ino,
2932                         Unique *maxu)
2933 {
2934     register struct VnodeInfo *vip = &vnodeInfo[class];
2935     struct VnodeClassInfo *vcp = &VnodeClassInfo[class];
2936     char buf[SIZEOF_LARGEDISKVNODE];
2937     struct VnodeDiskObject *vnode = (struct VnodeDiskObject *) buf;
2938     int size;
2939     StreamHandle_t *file;
2940     int vnodeIndex;
2941     int nVnodes;
2942     FdHandle_t *fdP;
2943
2944     IH_INIT(vip->handle, fileSysDevice, rwVId, ino);
2945     fdP = IH_OPEN(vip->handle);
2946     assert(fdP != NULL)
2947     file = FDH_FDOPEN(fdP, "r+");
2948     assert(file != NULL);
2949     size = OS_SIZE(fdP->fd_fd);
2950     assert(size != -1);
2951     vip->nVnodes = (size / vcp->diskSize) - 1;
2952     if (vip->nVnodes > 0) {
2953         assert((vip->nVnodes+1)*vcp->diskSize == size)
2954         assert(STREAM_SEEK(file, vcp->diskSize, 0) == 0)
2955         assert((vip->vnodes = (struct VnodeEssence *)
2956           calloc(vip->nVnodes, sizeof(struct VnodeEssence))) != NULL)
2957         if (class == vLarge) {
2958             assert((vip->inodes = (Inode *)
2959               calloc(vip->nVnodes, sizeof (Inode))) != NULL)
2960         }
2961         else {
2962             vip->inodes = NULL;
2963         }
2964     }
2965     else {
2966         vip->nVnodes = 0;
2967         vip->vnodes = NULL;
2968         vip->inodes = NULL;
2969     }
2970     vip->volumeBlockCount = vip->nAllocatedVnodes = 0;
2971     for (vnodeIndex = 0, nVnodes = vip->nVnodes;
2972       nVnodes && STREAM_READ(vnode, vcp->diskSize, 1, file) == 1;
2973       nVnodes--, vnodeIndex++) {
2974         if (vnode->type != vNull) {
2975             register struct VnodeEssence *vep = &vip->vnodes[vnodeIndex];
2976             vip->nAllocatedVnodes++;
2977             vep->count = vnode->linkCount;
2978             vep->blockCount = nBlocks(vnode->length);
2979             vip->volumeBlockCount += vep->blockCount;
2980             vep->parent = vnode->parent;
2981             vep->unique = vnode->uniquifier;
2982             if (*maxu < vnode->uniquifier)
2983                *maxu = vnode->uniquifier;
2984             vep->modeBits = vnode->modeBits;
2985             vep->InodeNumber = VNDISK_GET_INO(vnode);
2986             vep->type = vnode->type;
2987             vep->author = vnode->author;
2988             vep->owner = vnode->owner;
2989             vep->group = vnode->group;
2990             if (vnode->type == vDirectory) {
2991                 assert(class == vLarge)
2992                 vip->inodes[vnodeIndex] = VNDISK_GET_INO(vnode); 
2993             }
2994         }
2995       }
2996     STREAM_CLOSE(file);
2997     FDH_CLOSE(fdP);
2998 }
2999
3000 static char *GetDirName(vnode, vp, path)
3001     VnodeId vnode;
3002     struct VnodeEssence *vp;
3003     char *path;
3004 {
3005     struct VnodeEssence *parentvp;
3006     
3007     if (vnode == 1) {
3008         strcpy(path, ".");
3009         return path;
3010     }
3011     if (vp->parent && vp->name && (parentvp = CheckVnodeNumber(vp->parent)) && GetDirName(vp->parent, parentvp, path)) {
3012         strcat(path, "/");
3013         strcat(path, vp->name);
3014         return path;
3015     }
3016     return 0;
3017 }
3018
3019 /* To determine if a vnode is orhpaned or not, the vnode and all its parent
3020  * vnodes must be "claimed". The vep->claimed flag is set in JudgeEntry().
3021  */
3022 static int IsVnodeOrphaned(vnode)
3023     VnodeId vnode;
3024 {
3025     struct VnodeEssence *vep;
3026     
3027     if (vnode == 0) return(1);            /* Vnode zero does not exist */
3028     if (vnode == 1) return(0);            /* The root dir vnode is always claimed */
3029     vep = CheckVnodeNumber(vnode);        /* Get the vnode essence */
3030     if (!vep || !vep->claimed) return(1); /* Vnode is not claimed - it is orphaned */
3031     
3032     return( IsVnodeOrphaned(vep->parent) );
3033 }
3034
3035 void SalvageDir(char *name, VolumeId rwVid, struct VnodeInfo *dirVnodeInfo,
3036                 IHandle_t *alinkH, int i, struct DirSummary *rootdir,
3037                 int *rootdirfound)
3038 {
3039     static struct DirSummary dir;
3040     static struct DirHandle dirHandle;
3041     struct VnodeEssence *parent;
3042     static char path[MAXPATHLEN];
3043     int dirok, code;
3044
3045     if (dirVnodeInfo->vnodes[i].salvaged)
3046         return;         /* already salvaged */
3047
3048     dir.rwVid = rwVid;
3049     dirVnodeInfo->vnodes[i].salvaged = 1;
3050     
3051     if (dirVnodeInfo->inodes[i] == 0)
3052         return; /* Not allocated to a directory */
3053
3054     parent = CheckVnodeNumber(dirVnodeInfo->vnodes[i].parent);
3055     if (parent && parent->salvaged == 0)
3056         SalvageDir(name, rwVid, dirVnodeInfo, alinkH,
3057                    vnodeIdToBitNumber(dirVnodeInfo->vnodes[i].parent),
3058                    rootdir, rootdirfound);
3059     dir.vnodeNumber = bitNumberToVnodeNumber(i, vLarge);
3060     dir.unique = dirVnodeInfo->vnodes[i].unique;
3061     dir.copied = 0;
3062     dir.vname = name;
3063     dir.parent = dirVnodeInfo->vnodes[i].parent;
3064     dir.haveDot = dir.haveDotDot = 0;
3065     dir.ds_linkH = alinkH;
3066     SetSalvageDirHandle(&dir.dirHandle, dir.rwVid, fileSysDevice, dirVnodeInfo->inodes[i]);
3067
3068     dirok = ((RebuildDirs && !Testing) ? 0 : DirOK(&dir.dirHandle));
3069     if (!dirok) {
3070         if (!RebuildDirs) {
3071            Log("Directory bad, vnode %d; %s...\n",
3072                dir.vnodeNumber, (Testing ? "skipping" : "salvaging"));
3073         }
3074         if (!Testing) {
3075            CopyAndSalvage(&dir);
3076            dirok = 1;
3077         }
3078     }
3079     dirHandle = dir.dirHandle;
3080
3081     dir.name = GetDirName(bitNumberToVnodeNumber(i,vLarge), &dirVnodeInfo->vnodes[i], path);
3082     
3083     if (dirok) {
3084        /* If enumeration failed for random reasons, we will probably delete
3085         * too much stuff, so we guard against this instead.
3086         */
3087        assert(EnumerateDir(&dirHandle, JudgeEntry, &dir) == 0);
3088     }
3089     
3090     /* Delete the old directory if it was copied in order to salvage.
3091      * CopyOnWrite has written the new inode # to the disk, but we still
3092      * have the old one in our local structure here.  Thus, we idec the
3093      * local dude.
3094      */
3095     DFlush();
3096     if (dir.copied && !Testing) {
3097         code = IH_DEC(dir.ds_linkH, dirHandle.dirh_handle->ih_ino, rwVid);
3098         assert(code == 0);
3099         dirVnodeInfo->inodes[i] = dir.dirHandle.dirh_inode;
3100     }
3101
3102     /* Remember rootdir DirSummary _after_ it has been judged */
3103     if (dir.vnodeNumber == 1 && dir.unique == 1) {
3104         memcpy(rootdir, &dir, sizeof(struct DirSummary));
3105         *rootdirfound = 1;
3106     }
3107
3108     return;
3109 }
3110
3111 int SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t *alinkH)
3112 {
3113     /* This routine, for now, will only be called for read-write volumes */
3114     int i, j, code;
3115     int BlocksInVolume = 0, FilesInVolume = 0;
3116     register VnodeClass class;
3117     struct DirSummary rootdir, oldrootdir;
3118     struct VnodeInfo *dirVnodeInfo;
3119     struct VnodeDiskObject vnode;
3120     VolumeDiskData volHeader;
3121     VolumeId vid;
3122     int orphaned, rootdirfound = 0;
3123     Unique maxunique = 0; /* the maxUniquifier from the vnodes */
3124     afs_int32 ofiles=0, oblocks=0;  /* Number of orphaned files/blocks */
3125     struct VnodeEssence *vep;
3126     afs_int32 v, pv;
3127     IHandle_t *h;
3128     int nBytes;
3129     ViceFid  pa;
3130     VnodeId LFVnode, ThisVnode;
3131     Unique  LFUnique, ThisUnique;
3132     char npath[128];
3133
3134     vid = rwIsp->volSummary->header.id;
3135     IH_INIT(h, fileSysDevice, vid, rwIsp->volSummary->header.volumeInfo);
3136     nBytes = IH_IREAD(h, 0, (char*)&volHeader, sizeof(volHeader));
3137     assert(nBytes == sizeof(volHeader));
3138     assert(volHeader.stamp.magic == VOLUMEINFOMAGIC);
3139     assert (volHeader.destroyMe != DESTROY_ME);
3140     /* (should not have gotten this far with DESTROY_ME flag still set!) */
3141
3142     DistilVnodeEssence(vid, vLarge,
3143                        rwIsp->volSummary->header.largeVnodeIndex,
3144                        &maxunique);
3145     DistilVnodeEssence(vid, vSmall,
3146                        rwIsp->volSummary->header.smallVnodeIndex,
3147                        &maxunique);
3148
3149     dirVnodeInfo = &vnodeInfo[vLarge];
3150     for (i = 0; i < dirVnodeInfo->nVnodes; i++) {
3151         SalvageDir(volHeader.name, vid, dirVnodeInfo, alinkH, i,
3152                    &rootdir, &rootdirfound);
3153     }
3154     if (Showmode) {
3155         IH_RELEASE(h);
3156         return 0;
3157     }
3158
3159     /* Parse each vnode looking for orphaned vnodes and
3160      * connect them to the tree as orphaned (if requested).
3161      */
3162     oldrootdir = rootdir;
3163     for (class=0; class < nVNODECLASSES; class++) {
3164        for (v=0; v < vnodeInfo[class].nVnodes; v++) {
3165           vep = &(vnodeInfo[class].vnodes[v]);
3166           ThisVnode  = bitNumberToVnodeNumber(v, class);
3167           ThisUnique = vep->unique;
3168
3169           if ((vep->type == 0) || vep->claimed || ThisVnode == 1)
3170              continue;    /* Ignore unused, claimed, and root vnodes */
3171         
3172           /* This vnode is orphaned. If it is a directory vnode, then the '..'
3173            * entry in this vnode had incremented the parent link count (In
3174            * JudgeEntry()). We need to go to the parent and decrement that
3175            * link count. But if the parent's unique is zero, then the parent
3176            * link count was not incremented in JudgeEntry().
3177            */
3178           if (class == vLarge) {          /* directory vnode */
3179              pv = vnodeIdToBitNumber(vep->parent);
3180              if (IUnique(vnodeInfo[vLarge].vnodes[pv].unique) != 0)
3181                 vnodeInfo[vLarge].vnodes[pv].count++;
3182           }
3183
3184           if (!rootdirfound)
3185              continue;  /* If no rootdir, can't attach orphaned files */
3186
3187           /* Here we attach orphaned files and directories into the
3188            * root directory, LVVnode, making sure link counts stay correct.
3189            */
3190           if ((orphans == ORPH_ATTACH) && !vep->todelete && !Testing) {
3191              LFVnode  = rootdir.vnodeNumber;    /* Lost+Found vnode number */
3192              LFUnique = rootdir.unique;         /* Lost+Found uniquifier */
3193
3194              /* Update this orphaned vnode's info. Its parent info and 
3195               * link count (do for orphaned directories and files).
3196               */
3197              vep->parent = LFVnode;  /* Parent is the root dir */
3198              vep->unique = LFUnique;
3199              vep->changed = 1;
3200              vep->claimed = 1;
3201              vep->count--;           /* Inc link count (root dir will pt to it) */
3202
3203              /* If this orphaned vnode is a directory, change '..'. 
3204               * The name of the orphaned dir/file is unknown, so we
3205               * build a unique name. No need to CopyOnWrite the directory
3206               * since it is not connected to tree in BK or RO volume and
3207               * won't be visible there.
3208               */
3209              if (class == vLarge) {
3210                 ViceFid pa;
3211                 DirHandle dh;
3212
3213                 /* Remove and recreate the ".." entry in this orphaned directory */
3214                 SetSalvageDirHandle(&dh,vid,fileSysDevice,vnodeInfo[class].inodes[v]);
3215                 pa.Vnode  = LFVnode;
3216                 pa.Unique = LFUnique;
3217                 assert(Delete(&dh, "..") == 0);
3218                 assert(Create(&dh, "..", &pa) == 0);
3219
3220                 /* The original parent's link count was decremented above.
3221                  * Here we increment the new parent's link count.
3222                  */
3223                 pv = vnodeIdToBitNumber(LFVnode);
3224                 vnodeInfo[vLarge].vnodes[pv].count--;
3225
3226              }
3227
3228              /* Go to the root dir and add this entry. The link count of the
3229               * root dir was incremented when ".." was created. Try 10 times.
3230               */
3231              for (j=0; j<10; j++) {
3232                 pa.Vnode  = ThisVnode;
3233                 pa.Unique = ThisUnique;
3234
3235                 sprintf(npath, "%s.%d.%d", 
3236                         ((class == vLarge)?"__ORPHANDIR__":"__ORPHANFILE__"),
3237                         ThisVnode, ThisUnique);
3238
3239                 CopyOnWrite(&rootdir);
3240                 code = Create(&rootdir.dirHandle, npath, &pa);
3241                 if (!code) break;
3242
3243                 ThisUnique += 50;       /* Try creating a different file */
3244              }
3245              assert(code == 0);
3246              Log("Attaching orphaned %s to volume's root dir as %s\n",
3247                  ((class == vLarge)?"directory":"file"), npath);
3248           }
3249        } /* for each vnode in the class */
3250     } /* for each class of vnode */
3251
3252     /* Delete the old rootinode directory if the rootdir was CopyOnWrite */
3253     DFlush();
3254     if (!oldrootdir.copied && rootdir.copied) {
3255        code = IH_DEC(oldrootdir.ds_linkH, oldrootdir.dirHandle.dirh_inode, oldrootdir.rwVid);
3256        assert(code == 0);
3257        /* dirVnodeInfo->inodes[?] is not updated with new inode number */
3258     }
3259
3260     DFlush(); /* Flush the changes */
3261     if (!rootdirfound && (orphans == ORPH_ATTACH)) {
3262        Log("Cannot attach orphaned files and directories: Root directory not found\n");
3263        orphans = ORPH_IGNORE;
3264     }
3265
3266     /* Write out all changed vnodes. Orphaned files and directories
3267      * will get removed here also (if requested).
3268      */
3269     for (class = 0; class < nVNODECLASSES; class++){
3270         int nVnodes = vnodeInfo[class].nVnodes;
3271         struct VnodeClassInfo *vcp = &VnodeClassInfo[class];
3272         struct VnodeEssence *vnodes = vnodeInfo[class].vnodes;
3273         FilesInVolume += vnodeInfo[class].nAllocatedVnodes;
3274         BlocksInVolume += vnodeInfo[class].volumeBlockCount;
3275         for (i = 0; i<nVnodes; i++) {
3276             register struct VnodeEssence *vnp = &vnodes[i];
3277             VnodeId vnodeNumber = bitNumberToVnodeNumber(i,class);
3278
3279             /* If the vnode is good but is unclaimed (not listed in
3280              * any directory entries), then it is orphaned.
3281              */
3282             orphaned = -1;
3283             if ((vnp->type != 0) && (orphaned=IsVnodeOrphaned(vnodeNumber))) {
3284                vnp->claimed = 0; /* Makes IsVnodeOrphaned calls faster */
3285                vnp->changed = 1;
3286             }
3287
3288             if (vnp->changed || vnp->count) {
3289                 int oldCount;
3290                 int code;
3291                 nBytes = IH_IREAD(vnodeInfo[class].handle,
3292                             vnodeIndexOffset(vcp, vnodeNumber),
3293                              (char*)&vnode, sizeof (vnode));
3294                 assert(nBytes == sizeof(vnode));
3295
3296                 vnode.parent = vnp->parent;
3297                 oldCount = vnode.linkCount;
3298                 vnode.linkCount = vnode.linkCount - vnp->count;
3299
3300                 if (orphaned == -1)
3301                    orphaned = IsVnodeOrphaned(vnodeNumber);
3302                 if (orphaned) {
3303                     if (!vnp->todelete) {
3304                        /* Orphans should have already been attached (if requested) */
3305                        assert(orphans != ORPH_ATTACH);
3306                        oblocks += vnp->blockCount;
3307                        ofiles++;
3308                     }
3309                     if (((orphans == ORPH_REMOVE) || vnp->todelete) && !Testing) {
3310                        BlocksInVolume -= vnp->blockCount;
3311                        FilesInVolume--;
3312                        if (VNDISK_GET_INO(&vnode)) {
3313                            code = IH_DEC(alinkH, VNDISK_GET_INO(&vnode), vid);
3314                            assert(code == 0);
3315                        }
3316                        memset(&vnode, 0, sizeof(vnode));
3317                     }
3318                 } else if (vnp->count) {
3319                     if (!Showmode) {
3320                        Log("Vnode %d: link count incorrect (was %d, %s %d)\n",
3321                            vnodeNumber, oldCount, 
3322                            (Testing?"would have changed to":"now"), vnode.linkCount);
3323                     }
3324                 }
3325
3326                 vnode.dataVersion++;
3327                 if (!Testing) {
3328                     nBytes = IH_IWRITE(vnodeInfo[class].handle,
3329                                       vnodeIndexOffset(vcp, vnodeNumber),
3330                                       (char*)&vnode, sizeof (vnode));
3331                     assert(nBytes == sizeof(vnode));
3332                     }
3333                 VolumeChanged = 1;
3334             }
3335         }
3336     }
3337     if (!Showmode && ofiles) {
3338        Log("%s %d orphaned files and directories (approx. %u KB)\n",
3339            (!Testing && (orphans == ORPH_REMOVE))?"Removed":"Found",
3340            ofiles, oblocks);
3341     }
3342
3343     for (class = 0; class < nVNODECLASSES; class++) {
3344        register struct VnodeInfo *vip = &vnodeInfo[class];
3345        for (i=0; i<vip->nVnodes; i++)
3346           if (vip->vnodes[i].name) free(vip->vnodes[i].name);
3347        if (vip->vnodes) free(vip->vnodes);
3348        if (vip->inodes) free(vip->inodes);
3349     }
3350
3351     /* Set correct resource utilization statistics */
3352     volHeader.filecount = FilesInVolume;
3353     volHeader.diskused = BlocksInVolume;
3354
3355     /* Make sure the uniquifer is big enough: maxunique is the real maxUniquifier */
3356     if (volHeader.uniquifier < (maxunique + 1)) {
3357         if (!Showmode) Log("Volume uniquifier is too low; fixed\n");
3358         /* Plus 2,000 in case there are workstations out there with
3359          * cached vnodes that have since been deleted
3360          */
3361         volHeader.uniquifier = (maxunique + 1 + 2000);
3362     }
3363
3364     /* Turn off the inUse bit; the volume's been salvaged! */
3365     volHeader.inUse = 0;                /* clear flag indicating inUse@last crash */
3366     volHeader.needsSalvaged = 0;        /* clear 'damaged' flag */
3367     volHeader.inService = 1;            /* allow service again */
3368     volHeader.needsCallback = (VolumeChanged != 0);    
3369     volHeader.dontSalvage = DONT_SALVAGE;
3370     VolumeChanged = 0;
3371     if (!Testing) {
3372         nBytes = IH_IWRITE(h, 0, (char*)&volHeader, sizeof(volHeader));
3373         assert(nBytes == sizeof(volHeader));
3374     }
3375     if (!Showmode) {
3376        Log("%sSalvaged %s (%u): %d files, %d blocks\n",
3377            (Testing?"It would have ":""), volHeader.name, 
3378            volHeader.id, FilesInVolume, BlocksInVolume);
3379    }
3380     IH_RELEASE(vnodeInfo[vSmall].handle);
3381     IH_RELEASE(vnodeInfo[vLarge].handle);
3382     IH_RELEASE(h);
3383     return 0;
3384 }
3385
3386 void ClearROInUseBit(struct VolumeSummary *summary)
3387 {
3388     IHandle_t *h = summary->volumeInfoHandle;
3389     int nBytes;
3390
3391     VolumeDiskData volHeader;
3392     
3393     nBytes = IH_IREAD(h, 0, (char*)&volHeader, sizeof(volHeader));
3394     assert(nBytes == sizeof(volHeader));
3395     assert(volHeader.stamp.magic == VOLUMEINFOMAGIC)
3396     volHeader.inUse = 0;
3397     volHeader.needsSalvaged = 0;
3398     volHeader.inService = 1;
3399     volHeader.dontSalvage = DONT_SALVAGE;
3400     if (!Testing) {
3401         nBytes = IH_IREAD(h, 0, (char*)&volHeader, sizeof(volHeader));
3402         assert(nBytes == sizeof(volHeader));
3403     }
3404 }
3405
3406 /* MaybeZapVolume
3407  * Possible delete the volume.
3408  *
3409  * deleteMe - Always do so, only a partial volume.
3410  */
3411 void MaybeZapVolume(register struct InodeSummary *isp, char *message,
3412                     int deleteMe,
3413                     int check)
3414 {
3415     if (readOnly(isp) || deleteMe) {
3416         if (isp->volSummary && isp->volSummary->fileName) {
3417             if (deleteMe) {
3418                 if (!Showmode) Log("Volume %u (is only a partial volume--probably an attempt was made to move/restore it when a machine crash occured.\n", isp->volumeId);
3419                 if (!Showmode) Log("It will be deleted on this server (you may find it elsewhere)\n");
3420             } else {