2 * Copyright 2000, International Business Machines Corporation and others.
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
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.
16 #include <afsconfig.h>
17 #include <afs/param.h>
22 /* Defined in vol/vol-salvage.c */
23 extern void Log(const char *format, ...)
24 AFS_ATTRIBUTE_FORMAT(__printf__, 1, 2);
26 #define printf Log /* To make it work with volume salvager */
28 /* This routine is called with one parameter, the id (the same thing that is
29 * passed to physio or the buffer package) of a directory to check. It
30 * returns 1 if the directory looks good, and 0 otherwise. */
34 /* figure out how many pages in use in a directory, given ptr to its (locked)
37 ComputeUsedPages(struct DirHeader *dhp)
39 afs_int32 usedPages, i;
41 if (dhp->header.pgcount != 0) {
43 usedPages = ntohs(dhp->header.pgcount);
47 for (i = 0; i < MAXPAGES; i++) {
48 if (dhp->alloMap[i] == EPP) {
60 * check whether a directory object is ok.
62 * @param[in] file opaque pointer to directory object fid
64 * @return operation status
65 * @retval 1 dir is fine, or something went wrong checking
66 * @retval 0 we *know* that the dir is bad
71 struct DirHeader *dhp;
72 struct PageHeader *pp;
74 struct DirBuffer headerbuf, pagebuf, entrybuf;
76 int havedot = 0, havedotdot = 0;
77 int usedPages, count, entry;
78 char eaMap[BIGMAXPAGES * EPP / 8]; /* Change eaSize initialization below, too. */
80 afs_int32 entcount, maxents;
85 eaSize = BIGMAXPAGES * EPP / 8;
87 /* Read the directory header */
88 code = DReadWithErrno(file, 0, &headerbuf, &physerr);
90 /* if physerr is 0, then we know that the read worked, but was short,
91 * and the damage is permanent. Otherwise, we got an I/O or programming
92 * error. Claim the dir is OK, but log something.
95 printf("Could not read first page in directory (%d)\n", physerr);
97 AFS_UNREACHED(return 1);
99 printf("First page in directory does not exist.\n");
102 dhp = (struct DirHeader *)headerbuf.data;
104 /* Check magic number for first page */
105 if (dhp->header.tag != htons(1234)) {
106 printf("Bad first pageheader magic number.\n");
107 DRelease(&headerbuf, 0);
111 /* Verify that the number of free entries in each directory page
112 * is within range (0-EPP). Also ensure directory is contiguous:
113 * Once the first alloMap entry with EPP free entries is found,
114 * the rest should match.
116 up = 0; /* count of used pages if total pages < MAXPAGES */
117 k = 0; /* found last page */
118 for (i = 0; i < MAXPAGES; i++) {
121 /* Check if in range */
123 if ((j < 0) || (j > EPP - (13 + 2))) {
124 /* First page's dirheader uses 13 entries and at least
125 * two must exist for "." and ".."
127 printf("The dir header alloc map for page %d is bad.\n", i);
128 DRelease(&headerbuf, 0);
132 if ((j < 0) || (j > EPP)) {
133 printf("The dir header alloc map for page %d is bad.\n", i);
134 DRelease(&headerbuf, 0);
139 /* Check if contiguous */
140 if (k) { /* last page found */
141 if (j != EPP) { /* remaining entries must be EPP */
143 ("A partially-full page occurs in slot %d, after the dir end.\n",
145 DRelease(&headerbuf, 0);
148 } else if (j == EPP) { /* is this the last page */
150 } else { /* a used page */
151 up++; /* keep count */
155 /* Compute number of used directory pages and max entries in all
156 ** those pages, the value of 'up' must be less than pgcount. The above
157 ** loop only checks the first MAXPAGES in a directory. An alloMap does
158 ** not exists for pages between MAXPAGES and BIGMAXPAGES */
159 usedPages = ComputeUsedPages(dhp);
160 if (usedPages < up) {
162 ("Count of used directory pages does not match count in directory header\n");
163 DRelease(&headerbuf, 0);
167 /* For each directory page, check the magic number in each page
168 * header, and check that number of free entries (from freebitmap)
169 * matches the count in the alloMap from directory header.
171 for (i = 0; i < usedPages; i++) {
172 /* Read the page header */
173 code = DReadWithErrno(file, i, &pagebuf, &physerr);
175 DRelease(&headerbuf, 0);
177 /* couldn't read page, but not because it wasn't there permanently */
178 printf("Failed to read dir page %d (errno %d)\n", i, physerr);
180 AFS_UNREACHED(return 1);
182 printf("Directory shorter than alloMap indicates (page %d)\n", i);
185 pp = (struct PageHeader *)pagebuf.data;
187 /* check the tag field */
188 if (pp->tag != htons(1234)) {
189 printf("Directory page %d has a bad magic number.\n", i);
190 DRelease(&pagebuf, 0);
191 DRelease(&headerbuf, 0);
195 /* Count the number of entries allocated in this single
196 * directory page using the freebitmap in the page header.
199 for (j = 0; j < EPP / 8; j++) {
200 k = pp->freebitmap[j];
218 count = EPP - count; /* Change to count of free entries */
220 /* Now check that the count of free entries matches the count in the alloMap */
221 if ((i < MAXPAGES) && ((count & 0xff) != (dhp->alloMap[i] & 0xff))) {
223 ("Header alloMap count doesn't match count in freebitmap for page %d.\n",
225 DRelease(&pagebuf, 0);
226 DRelease(&headerbuf, 0);
230 DRelease(&pagebuf, 0);
233 /* Initialize the in-memory freebit map for all pages. */
234 for (i = 0; i < eaSize; i++) {
236 if (i < usedPages * (EPP / 8)) {
238 eaMap[i] = 0xff; /* A dir header uses first 13 entries */
240 eaMap[i] = 0x1f; /* A dir header uses first 13 entries */
241 } else if ((i % 8) == 0) {
242 eaMap[i] = 0x01; /* A page header uses only first entry */
246 maxents = usedPages * EPP;
248 /* Walk down all the hash lists, ensuring that each flag field has FFIRST
249 * in it. Mark the appropriate bits in the in-memory freebit map.
250 * Check that the name is in the right hash bucket.
251 * Also check for loops in the hash chain by counting the entries.
253 for (entcount = 0, i = 0; i < NHASHENT; i++) {
254 for (entry = ntohs(dhp->hashTable[i]); entry; entry = ne) {
255 /* Verify that the entry is within range */
256 if (entry < 0 || entry >= maxents) {
257 printf("Out-of-range hash id %d in chain %d.\n", entry, i);
258 DRelease(&headerbuf, 0);
262 /* Read the directory entry */
263 code = afs_dir_GetBlobWithErrno(file, entry, &entrybuf, &physerr);
266 /* something went wrong reading the page, but it wasn't
267 * really something wrong with the dir that we can fix.
269 printf("Could not get dir blob %d (errno %d)\n", entry,
271 DRelease(&headerbuf, 0);
274 printf("Invalid hash id %d in chain %d.\n", entry, i);
275 DRelease(&headerbuf, 0);
278 ep = (struct DirEntry *)entrybuf.data;
280 ne = ntohs(ep->next);
282 /* There can't be more than maxents entries */
283 if (++entcount >= maxents) {
284 printf("Directory's hash chain %d is circular.\n", i);
285 DRelease(&entrybuf, 0);
286 DRelease(&headerbuf, 0);
290 /* A null name is no good */
291 if (ep->name[0] == '\000') {
292 printf("Dir entry %p in chain %d has bogus (null) name.\n",
294 DRelease(&entrybuf, 0);
295 DRelease(&headerbuf, 0);
299 /* The entry flag better be FFIRST */
300 if (ep->flag != FFIRST) {
301 printf("Dir entry %p in chain %d has bogus flag field.\n",
303 DRelease(&entrybuf, 0);
304 DRelease(&headerbuf, 0);
308 /* Check the size of the name */
309 j = strlen(ep->name);
310 if (j >= MAXENAME) { /* MAXENAME counts the null */
311 printf("Dir entry %p in chain %d has too-long name.\n", ep, i);
312 DRelease(&entrybuf, 0);
313 DRelease(&headerbuf, 0);
317 /* The name used up k directory entries, set the bit in our in-memory
318 * freebitmap for each entry used by the name.
320 k = afs_dir_NameBlobs(ep->name);
321 for (j = 0; j < k; j++) {
322 eaMap[(entry + j) >> 3] |= (1 << ((entry + j) & 7));
325 /* Hash the name and make sure it is in the correct name hash */
326 if ((j = afs_dir_DirHash(ep->name)) != i) {
327 printf("Dir entry %p should be in hash bucket %d but IS in %d.\n",
329 DRelease(&entrybuf, 0);
330 DRelease(&headerbuf, 0);
334 /* Check that if this is entry 13 (the 1st entry), then name must be "." */
336 if (strcmp(ep->name, ".") == 0) {
340 ("Dir entry %p, index 13 has name '%s' should be '.'\n",
342 DRelease(&entrybuf, 0);
343 DRelease(&headerbuf, 0);
348 /* Check that if this is entry 14 (the 2nd entry), then name must be ".." */
350 if (strcmp(ep->name, "..") == 0) {
354 ("Dir entry %p, index 14 has name '%s' should be '..'\n",
356 DRelease(&entrybuf, 0);
357 DRelease(&headerbuf, 0);
362 /* CHECK FOR DUPLICATE NAMES? */
364 DRelease(&entrybuf, 0);
368 /* Verify that we found '.' and '..' in the correct place */
369 if (!havedot || !havedotdot) {
371 ("Directory entry '.' or '..' does not exist or is in the wrong index.\n");
372 DRelease(&headerbuf, 0);
376 /* The in-memory freebit map has been computed. Check that it
377 * matches the one in the page header.
378 * Note that if this matches, alloMap has already been checked against it.
380 for (i = 0; i < usedPages; i++) {
381 code = DReadWithErrno(file, i, &pagebuf, &physerr);
384 ("Failed on second attempt to read dir page %d (errno %d)\n",
386 DRelease(&headerbuf, 0);
387 /* if physerr is 0, then the dir is really bad, and we return dir
388 * *not* OK. Otherwise, we Die instead of returning true (1),
389 * becauase the dir isn't known to be bad (we can't tell, since
395 return 0; /* dir is really shorter */
397 pp = (struct PageHeader *)pagebuf.data;
399 count = i * (EPP / 8);
400 for (j = 0; j < EPP / 8; j++) {
401 if (eaMap[count + j] != pp->freebitmap[j]) {
403 ("Entry freebitmap error, page %d, map offset %d, %x should be %x.\n",
404 i, j, pp->freebitmap[j], eaMap[count + j]);
405 DRelease(&pagebuf, 0);
406 DRelease(&headerbuf, 0);
411 DRelease(&pagebuf, 0);
414 /* Finally cleanup and return. */
415 DRelease(&headerbuf, 0);
420 * Salvage a directory object.
422 * @param[in] fromFile fid of original, currently suspect directory object
423 * @param[in] toFile fid where salvager will place new, fixed directory
424 * @param[in] vn vnode of currently suspect directory
425 * @param[in] vu uniquifier of currently suspect directory
426 * @param[in] pvn vnode of parent directory
427 * @param[in] pvu uniquifier of parent directory
429 * @return operation status
433 DirSalvage(void *fromFile, void *toFile, afs_int32 vn, afs_int32 vu,
434 afs_int32 pvn, afs_int32 pvu)
436 /* First do a MakeDir on the target. */
437 afs_int32 dot[3], dotdot[3], lfid[3], code, usedPages;
441 struct DirBuffer headerbuf, entrybuf;
442 struct DirHeader *dhp;
447 memset(dot, 0, sizeof(dot));
448 memset(dotdot, 0, sizeof(dotdot));
454 afs_dir_MakeDir(toFile, dot, dotdot); /* Returns no error code. */
456 /* Find out how many pages are valid. */
457 code = DReadWithErrno(fromFile, 0, &headerbuf, &physerr);
459 printf("Failed to read first page of fromDir!\n");
460 /* if physerr != 0, then our call failed and we should let our
461 * caller know that there's something wrong with the new dir. If not,
462 * then we return here anyway, with an empty, but at least good, directory.
466 dhp = (struct DirHeader *)headerbuf.data;
468 usedPages = ComputeUsedPages(dhp);
470 /* Finally, enumerate all the entries, doing a create on them. */
471 for (i = 0; i < NHASHENT; i++) {
472 entry = ntohs(dhp->hashTable[i]);
476 if (entry < 0 || entry >= usedPages * EPP) {
478 ("Warning: bogus hash table entry encountered, ignoring.\n");
482 code = afs_dir_GetBlobWithErrno(fromFile, entry, &entrybuf, &physerr);
486 ("can't continue down hash chain (entry %d, errno %d)\n",
488 DRelease(&headerbuf, 0);
492 ("Warning: bogus hash chain encountered, switching to next.\n");
495 ep = (struct DirEntry *)entrybuf.data;
497 strncpy(tname, ep->name, MAXENAME);
498 tname[MAXENAME - 1] = '\000'; /* just in case */
501 entry = ntohs(ep->next);
503 if ((strcmp(tp, ".") != 0) && (strcmp(tp, "..") != 0)) {
504 lfid[1] = ntohl(ep->fid.vnode);
505 lfid[2] = ntohl(ep->fid.vunique);
506 code = afs_dir_Create(toFile, tname, lfid);
509 ("Create of %s returned code %d, skipping to next hash chain.\n",
511 DRelease(&entrybuf, 0);
515 DRelease(&entrybuf, 0);
519 /* Clean up things. */
520 DRelease(&headerbuf, 0);