dir-prototypes-20090316
[openafs.git] / src / dir / 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 /* This is the directory salvager.  It consists of two routines.  The first, 
11  * DirOK, checks to see if the directory looks good.  If the directory does 
12  * NOT look good, the approved procedure is to then call Salvage, which 
13  * copies all the good entries from the damaged dir into a new directory. 
14  */
15
16 #include <afsconfig.h>
17 #include <afs/param.h>
18
19 RCSID
20     ("$Header$");
21
22 #include <sys/types.h>
23 #include <errno.h>
24 #ifdef AFS_NT40_ENV
25 #include <winsock2.h>
26 #else
27 #include <netinet/in.h>
28 #endif
29
30 #include <string.h>
31
32 #include "dir.h"
33 /* Defined in vol/vol-salvage.c */
34 extern void Log(const char *format, ...);
35 /* Defined in vol/physio.c */
36 extern void Die(char *);
37     
38 #define printf  Log             /* To make it work with volume salvager */
39
40 /* This routine is called with one parameter, the id (the same thing that is 
41  * passed to physio or the buffer package) of a directory to check.  It 
42  * returns 1 if the directory looks good, and 0 otherwise. */
43
44 #define MAXENAME 256
45
46 extern afs_int32 DErrno;
47
48 /* figure out how many pages in use in a directory, given ptr to its (locked) 
49  * header */
50 static int
51 ComputeUsedPages(register struct DirHeader *dhp)
52 {
53     register afs_int32 usedPages, i;
54
55     if (dhp->header.pgcount != 0) {
56         /* new style */
57         usedPages = ntohs(dhp->header.pgcount);
58     } else {
59         /* old style */
60         usedPages = 0;
61         for (i = 0; i < MAXPAGES; i++) {
62             if (dhp->alloMap[i] == EPP) {
63                 usedPages = i;
64                 break;
65             }
66         }
67         if (usedPages == 0)
68             usedPages = MAXPAGES;
69     }
70     return usedPages;
71 }
72
73 /* returns true if something went wrong checking, or if dir is fine.  Returns
74  * false if we *know* that the dir is bad.
75  */
76 int
77 DirOK(void *file)
78 {
79     struct DirHeader *dhp;
80     struct PageHeader *pp;
81     struct DirEntry *ep;
82     int i, j, k, up;
83     int havedot = 0, havedotdot = 0;
84     int usedPages, count, entry;
85     char eaMap[BIGMAXPAGES * EPP / 8];  /* Change eaSize initialization below, too. */
86     int eaSize;
87     afs_int32 entcount, maxents;
88     unsigned short ne;
89
90     eaSize = BIGMAXPAGES * EPP / 8;
91
92     /* Read the directory header */
93     dhp = (struct DirHeader *)DRead(file, 0);
94     if (!dhp) {
95         /* if DErrno is 0, then we know that the read worked, but was short,
96          * and the damage is permanent.  Otherwise, we got an I/O or programming
97          * error.  Claim the dir is OK, but log something.
98          */
99         if (DErrno != 0) {
100             printf("Could not read first page in directory (%d)\n", DErrno);
101             Die("dirok1");
102             return 1;
103         }
104         printf("First page in directory does not exist.\n");
105         return 0;
106     }
107
108     /* Check magic number for first page */
109     if (dhp->header.tag != htons(1234)) {
110         printf("Bad first pageheader magic number.\n");
111         DRelease((struct buffer *)dhp, 0);
112         return 0;
113     }
114
115     /* Verify that the number of free entries in each directory page
116      * is within range (0-EPP). Also ensure directory is contiguous:
117      * Once the first alloMap entry with EPP free entries is found,
118      * the rest should match.
119      */
120     up = 0;                     /* count of used pages if total pages < MAXPAGES */
121     k = 0;                      /* found last page */
122     for (i = 0; i < MAXPAGES; i++) {
123         j = dhp->alloMap[i];
124
125         /* Check if in range */
126         if (i == 0) {
127             if ((j < 0) || (j > EPP - (13 + 2))) {
128                 /* First page's dirheader uses 13 entries and at least
129                  * two must exist for "." and ".."
130                  */
131                 printf("The dir header alloc map for page %d is bad.\n", i);
132                 DRelease((struct buffer *)dhp, 0);
133                 return 0;
134             }
135         } else {
136             if ((j < 0) || (j > EPP)) {
137                 printf("The dir header alloc map for page %d is bad.\n", i);
138                 DRelease((struct buffer *)dhp, 0);
139                 return 0;
140             }
141         }
142
143         /* Check if contiguous */
144         if (k) {                /* last page found */
145             if (j != EPP) {     /* remaining entries must be EPP */
146                 printf
147                     ("A partially-full page occurs in slot %d, after the dir end.\n",
148                      i);
149                 DRelease((struct buffer *)dhp, 0);
150                 return 0;
151             }
152         } else if (j == EPP) {  /* is this the last page */
153             k = 1;              /* yes */
154         } else {                /* a used page */
155             up++;               /* keep count */
156         }
157     }
158
159     /* Compute number of used directory pages and max entries in all 
160      ** those pages, the value of 'up' must be less than pgcount. The above
161      ** loop only checks the first MAXPAGES in a directory. An alloMap does
162      ** not exists for pages between MAXPAGES and BIGMAXPAGES */
163     usedPages = ComputeUsedPages(dhp);
164     if (usedPages < up) {
165         printf
166             ("Count of used directory pages does not match count in directory header\n");
167         DRelease((struct buffer *)dhp, 0);
168         return 0;
169     }
170
171     /* For each directory page, check the magic number in each page
172      * header, and check that number of free entries (from freebitmap)
173      * matches the count in the alloMap from directory header.
174      */
175     for (i = 0; i < usedPages; i++) {
176         /* Read the page header */
177         pp = (struct PageHeader *)DRead(file, i);
178         if (!pp) {
179             DRelease((struct buffer *)dhp, 0);
180             if (DErrno != 0) {
181                 /* couldn't read page, but not because it wasn't there permanently */
182                 printf("Failed to read dir page %d (errno %d)\n", i, DErrno);
183                 Die("dirok2");
184                 return 1;
185             }
186             printf("Directory shorter than alloMap indicates (page %d)\n", i);
187             return 0;
188         }
189
190         /* check the tag field */
191         if (pp->tag != htons(1234)) {
192             printf("Directory page %d has a bad magic number.\n", i);
193             DRelease((struct buffer *)pp, 0);
194             DRelease((struct buffer *)dhp, 0);
195             return 0;
196         }
197
198         /* Count the number of entries allocated in this single
199          * directory page using the freebitmap in the page header.
200          */
201         count = 0;
202         for (j = 0; j < EPP / 8; j++) {
203             k = pp->freebitmap[j];
204             if (k & 0x80)
205                 count++;
206             if (k & 0x40)
207                 count++;
208             if (k & 0x20)
209                 count++;
210             if (k & 0x10)
211                 count++;
212             if (k & 0x08)
213                 count++;
214             if (k & 0x04)
215                 count++;
216             if (k & 0x02)
217                 count++;
218             if (k & 0x01)
219                 count++;
220         }
221         count = EPP - count;    /* Change to count of free entries */
222
223         /* Now check that the count of free entries matches the count in the alloMap */
224         if ((i < MAXPAGES) && ((count & 0xff) != (dhp->alloMap[i] & 0xff))) {
225             printf
226                 ("Header alloMap count doesn't match count in freebitmap for page %d.\n",
227                  i);
228             DRelease((struct buffer *)pp, 0);
229             DRelease((struct buffer *)dhp, 0);
230             return 0;
231         }
232
233         DRelease((struct buffer *)pp, 0);
234     }
235
236     /* Initialize the in-memory freebit map for all pages. */
237     for (i = 0; i < eaSize; i++) {
238         eaMap[i] = 0;
239         if (i < usedPages * (EPP / 8)) {
240             if (i == 0) {
241                 eaMap[i] = 0xff;        /* A dir header uses first 13 entries */
242             } else if (i == 1) {
243                 eaMap[i] = 0x1f;        /* A dir header uses first 13 entries */
244             } else if ((i % 8) == 0) {
245                 eaMap[i] = 0x01;        /* A page header uses only first entry */
246             }
247         }
248     }
249     maxents = usedPages * EPP;
250
251     /* Walk down all the hash lists, ensuring that each flag field has FFIRST
252      * in it.  Mark the appropriate bits in the in-memory freebit map.
253      * Check that the name is in the right hash bucket.
254      * Also check for loops in the hash chain by counting the entries.
255      */
256     for (entcount = 0, i = 0; i < NHASHENT; i++) {
257         for (entry = ntohs(dhp->hashTable[i]); entry; entry = ne) {
258             /* Verify that the entry is within range */
259             if (entry < 0 || entry >= maxents) {
260                 printf("Out-of-range hash id %d in chain %d.\n", entry, i);
261                 DRelease((struct buffer *)dhp, 0);
262                 return 0;
263             }
264
265             /* Read the directory entry */
266             DErrno = 0;
267             ep = GetBlob(file, entry);
268             if (!ep) {
269                 if (DErrno != 0) {
270                     /* something went wrong reading the page, but it wasn't
271                      * really something wrong with the dir that we can fix.
272                      */
273                     printf("Could not get dir blob %d (errno %d)\n", entry,
274                            DErrno);
275                     DRelease((struct buffer *)dhp, 0);
276                     Die("dirok3");
277                 }
278                 printf("Invalid hash id %d in chain %d.\n", entry, i);
279                 DRelease((struct buffer *)dhp, 0);
280                 return 0;
281             }
282             ne = ntohs(ep->next);
283
284             /* There can't be more than maxents entries */
285             if (++entcount >= maxents) {
286                 printf("Directory's hash chain %d is circular.\n", i);
287                 DRelease((struct buffer *)ep, 0);
288                 DRelease((struct buffer *)dhp, 0);
289                 return 0;
290             }
291
292             /* A null name is no good */
293             if (ep->name[0] == '\000') {
294                 printf("Dir entry %x in chain %d has bogus (null) name.\n",
295                        (int)ep, i);
296                 DRelease((struct buffer *)ep, 0);
297                 DRelease((struct buffer *)dhp, 0);
298                 return 0;
299             }
300
301             /* The entry flag better be FFIRST */
302             if (ep->flag != FFIRST) {
303                 printf("Dir entry %x in chain %d has bogus flag field.\n", (int)ep,
304                        i);
305                 DRelease((struct buffer *)ep, 0);
306                 DRelease((struct buffer *)dhp, 0);
307                 return 0;
308             }
309
310             /* Check the size of the name */
311             j = strlen(ep->name);
312             if (j >= MAXENAME) {        /* MAXENAME counts the null */
313                 printf("Dir entry %x in chain %d has too-long name.\n", (int)ep,
314                        i);
315                 DRelease((struct buffer *)ep, 0);
316                 DRelease((struct buffer *)dhp, 0);
317                 return 0;
318             }
319
320             /* The name used up k directory entries, set the bit in our in-memory
321              * freebitmap for each entry used by the name.
322              */
323             k = NameBlobs(ep->name);
324             for (j = 0; j < k; j++) {
325                 eaMap[(entry + j) >> 3] |= (1 << ((entry + j) & 7));
326             }
327
328             /* Hash the name and make sure it is in the correct name hash */
329             if ((j = DirHash(ep->name)) != i) {
330                 printf
331                     ("Dir entry %x should be in hash bucket %d but IS in %d.\n",
332                      (int)ep, j, i);
333                 DRelease((struct buffer *)ep, 0);
334                 DRelease((struct buffer *)dhp, 0);
335                 return 0;
336             }
337
338             /* Check that if this is entry 13 (the 1st entry), then name must be "." */
339             if (entry == 13) {
340                 if (strcmp(ep->name, ".") == 0) {
341                     havedot = 1;
342                 } else {
343                     printf
344                         ("Dir entry %x, index 13 has name '%s' should be '.'\n",
345                          (int)ep, ep->name);
346                     DRelease((struct buffer *)ep, 0);
347                     DRelease((struct buffer *)dhp, 0);
348                     return 0;
349                 }
350             }
351
352             /* Check that if this is entry 14 (the 2nd entry), then name must be ".." */
353             if (entry == 14) {
354                 if (strcmp(ep->name, "..") == 0) {
355                     havedotdot = 1;
356                 } else {
357                     printf
358                         ("Dir entry %x, index 14 has name '%s' should be '..'\n",
359                          (int)ep, ep->name);
360                     DRelease((struct buffer *)ep, 0);
361                     DRelease((struct buffer *)dhp, 0);
362                     return 0;
363                 }
364             }
365
366             /* CHECK FOR DUPLICATE NAMES? */
367
368             DRelease((struct buffer *)ep, 0);
369         }
370     }
371
372     /* Verify that we found '.' and '..' in the correct place */
373     if (!havedot || !havedotdot) {
374         printf
375             ("Directory entry '.' or '..' does not exist or is in the wrong index.\n");
376         DRelease((struct buffer *)dhp, 0);
377         return 0;
378     }
379
380     /* The in-memory freebit map has been computed.  Check that it
381      * matches the one in the page header.
382      * Note that if this matches, alloMap has already been checked against it.
383      */
384     for (i = 0; i < usedPages; i++) {
385         pp = DRead(file, i);
386         if (!pp) {
387             printf
388                 ("Failed on second attempt to read dir page %d (errno %d)\n",
389                  i, DErrno);
390             DRelease((struct buffer *)dhp, 0);
391             /* if DErrno is 0, then the dir is really bad, and we return dir *not* OK.
392              * otherwise, we want to return true (1), meaning the dir isn't known
393              * to be bad (we can't tell, since I/Os are failing.
394              */
395             if (DErrno != 0)
396                 Die("dirok4");
397             else
398                 return 0;       /* dir is really shorter */
399         }
400
401         count = i * (EPP / 8);
402         for (j = 0; j < EPP / 8; j++) {
403             if (eaMap[count + j] != pp->freebitmap[j]) {
404                 printf
405                     ("Entry freebitmap error, page %d, map offset %d, %x should be %x.\n",
406                      i, j, pp->freebitmap[j], eaMap[count + j]);
407                 DRelease((struct buffer *)pp, 0);
408                 DRelease((struct buffer *)dhp, 0);
409                 return 0;
410             }
411         }
412
413         DRelease((struct buffer *)pp, 0);
414     }
415
416     /* Finally cleanup and return. */
417     DRelease((struct buffer *)dhp, 0);
418     return 1;
419 }
420
421 /* This routine is called with six parameters.  The first is the id of 
422  * the original, currently suspect, directory.  The second is the file 
423  * id of the place the salvager should place the new, fixed, directory. 
424  * The third and the fourth parameters are the vnode number and the
425  * uniquifier of the currently suspect directory. The fifth and the
426  * sixth parameters are the vnode number and the uniquifier of the
427  * parent directory.
428  */
429 int
430 DirSalvage(char *fromFile, char *toFile, afs_int32 vn, afs_int32 vu, 
431            afs_int32 pvn, afs_int32 pvu)
432 {
433     /* First do a MakeDir on the target. */
434     afs_int32 dot[3], dotdot[3], lfid[3], code, usedPages;
435     char tname[256];
436     register int i;
437     register char *tp;
438     struct DirHeader *dhp;
439     struct DirEntry *ep;
440     int entry;
441
442     memset(dot, 0, sizeof(dot));
443     memset(dotdot, 0, sizeof(dotdot));
444     dot[1] = vn;
445     dot[2] = vu;
446     dotdot[1] = pvn;
447     dotdot[2] = pvu;
448
449     MakeDir(toFile, dot, dotdot);       /* Returns no error code. */
450
451     /* Find out how many pages are valid, using stupid heuristic since DRead
452      * never returns null.
453      */
454     dhp = (struct DirHeader *)DRead(fromFile, 0);
455     if (!dhp) {
456         printf("Failed to read first page of fromDir!\n");
457         /* if DErrno != 0, then our call failed and we should let our
458          * caller know that there's something wrong with the new dir.  If not,
459          * then we return here anyway, with an empty, but at least good, directory.
460          */
461         return DErrno;
462     }
463
464     usedPages = ComputeUsedPages(dhp);
465
466     /* Finally, enumerate all the entries, doing a create on them. */
467     for (i = 0; i < NHASHENT; i++) {
468         entry = ntohs(dhp->hashTable[i]);
469         while (1) {
470             if (!entry)
471                 break;
472             if (entry < 0 || entry >= usedPages * EPP) {
473                 printf
474                     ("Warning: bogus hash table entry encountered, ignoring.\n");
475                 break;
476             }
477             DErrno = 0;
478             ep = GetBlob(fromFile, entry);
479             if (!ep) {
480                 if (DErrno) {
481                     printf
482                         ("can't continue down hash chain (entry %d, errno %d)\n",
483                          entry, DErrno);
484                     DRelease((struct buffer *)dhp, 0);
485                     return DErrno;
486                 }
487                 printf
488                     ("Warning: bogus hash chain encountered, switching to next.\n");
489                 break;
490             }
491             strncpy(tname, ep->name, MAXENAME);
492             tname[MAXENAME - 1] = '\000';       /* just in case */
493             tp = tname;
494
495             entry = ntohs(ep->next);
496
497             if ((strcmp(tp, ".") != 0) && (strcmp(tp, "..") != 0)) {
498                 lfid[1] = ntohl(ep->fid.vnode);
499                 lfid[2] = ntohl(ep->fid.vunique);
500                 code = Create(toFile, tname, lfid);
501                 if (code) {
502                     printf
503                         ("Create of %s returned code %d, skipping to next hash chain.\n",
504                          tname, code);
505                     DRelease((struct buffer *)ep, 0);
506                     break;
507                 }
508             }
509             DRelease((struct buffer *)ep, 0);
510         }
511     }
512
513     /* Clean up things. */
514     DRelease((struct buffer *)dhp, 0);
515     return 0;
516 }