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