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