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