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 extern afs_int32 DErrno
;
36 /* figure out how many pages in use in a directory, given ptr to its (locked)
39 ComputeUsedPages(struct DirHeader
*dhp
)
41 afs_int32 usedPages
, i
;
43 if (dhp
->header
.pgcount
!= 0) {
45 usedPages
= ntohs(dhp
->header
.pgcount
);
49 for (i
= 0; i
< MAXPAGES
; i
++) {
50 if (dhp
->alloMap
[i
] == EPP
) {
62 * check whether a directory object is ok.
64 * @param[in] file opaque pointer to directory object fid
66 * @return operation status
67 * @retval 1 dir is fine, or something went wrong checking
68 * @retval 0 we *know* that the dir is bad
73 struct DirHeader
*dhp
;
74 struct PageHeader
*pp
;
76 struct DirBuffer headerbuf
, pagebuf
, entrybuf
;
78 int havedot
= 0, havedotdot
= 0;
79 int usedPages
, count
, entry
;
80 char eaMap
[BIGMAXPAGES
* EPP
/ 8]; /* Change eaSize initialization below, too. */
82 afs_int32 entcount
, maxents
;
86 eaSize
= BIGMAXPAGES
* EPP
/ 8;
88 /* Read the directory header */
89 code
= DRead(file
,0, &headerbuf
);
91 /* if DErrno is 0, then we know that the read worked, but was short,
92 * and the damage is permanent. Otherwise, we got an I/O or programming
93 * error. Claim the dir is OK, but log something.
96 printf("Could not read first page in directory (%d)\n", DErrno
);
100 printf("First page in directory does not exist.\n");
103 dhp
= (struct DirHeader
*)headerbuf
.data
;
105 /* Check magic number for first page */
106 if (dhp
->header
.tag
!= htons(1234)) {
107 printf("Bad first pageheader magic number.\n");
108 DRelease(&headerbuf
, 0);
112 /* Verify that the number of free entries in each directory page
113 * is within range (0-EPP). Also ensure directory is contiguous:
114 * Once the first alloMap entry with EPP free entries is found,
115 * the rest should match.
117 up
= 0; /* count of used pages if total pages < MAXPAGES */
118 k
= 0; /* found last page */
119 for (i
= 0; i
< MAXPAGES
; i
++) {
122 /* Check if in range */
124 if ((j
< 0) || (j
> EPP
- (13 + 2))) {
125 /* First page's dirheader uses 13 entries and at least
126 * two must exist for "." and ".."
128 printf("The dir header alloc map for page %d is bad.\n", i
);
129 DRelease(&headerbuf
, 0);
133 if ((j
< 0) || (j
> EPP
)) {
134 printf("The dir header alloc map for page %d is bad.\n", i
);
135 DRelease(&headerbuf
, 0);
140 /* Check if contiguous */
141 if (k
) { /* last page found */
142 if (j
!= EPP
) { /* remaining entries must be EPP */
144 ("A partially-full page occurs in slot %d, after the dir end.\n",
146 DRelease(&headerbuf
, 0);
149 } else if (j
== EPP
) { /* is this the last page */
151 } else { /* a used page */
152 up
++; /* keep count */
156 /* Compute number of used directory pages and max entries in all
157 ** those pages, the value of 'up' must be less than pgcount. The above
158 ** loop only checks the first MAXPAGES in a directory. An alloMap does
159 ** not exists for pages between MAXPAGES and BIGMAXPAGES */
160 usedPages
= ComputeUsedPages(dhp
);
161 if (usedPages
< up
) {
163 ("Count of used directory pages does not match count in directory header\n");
164 DRelease(&headerbuf
, 0);
168 /* For each directory page, check the magic number in each page
169 * header, and check that number of free entries (from freebitmap)
170 * matches the count in the alloMap from directory header.
172 for (i
= 0; i
< usedPages
; i
++) {
173 /* Read the page header */
174 code
= DRead(file
, i
, &pagebuf
);
176 DRelease(&headerbuf
, 0);
178 /* couldn't read page, but not because it wasn't there permanently */
179 printf("Failed to read dir page %d (errno %d)\n", i
, DErrno
);
183 printf("Directory shorter than alloMap indicates (page %d)\n", i
);
186 pp
= (struct PageHeader
*)pagebuf
.data
;
188 /* check the tag field */
189 if (pp
->tag
!= htons(1234)) {
190 printf("Directory page %d has a bad magic number.\n", i
);
191 DRelease(&pagebuf
, 0);
192 DRelease(&headerbuf
, 0);
196 /* Count the number of entries allocated in this single
197 * directory page using the freebitmap in the page header.
200 for (j
= 0; j
< EPP
/ 8; j
++) {
201 k
= pp
->freebitmap
[j
];
219 count
= EPP
- count
; /* Change to count of free entries */
221 /* Now check that the count of free entries matches the count in the alloMap */
222 if ((i
< MAXPAGES
) && ((count
& 0xff) != (dhp
->alloMap
[i
] & 0xff))) {
224 ("Header alloMap count doesn't match count in freebitmap for page %d.\n",
226 DRelease(&pagebuf
, 0);
227 DRelease(&headerbuf
, 0);
231 DRelease(&pagebuf
, 0);
234 /* Initialize the in-memory freebit map for all pages. */
235 for (i
= 0; i
< eaSize
; i
++) {
237 if (i
< usedPages
* (EPP
/ 8)) {
239 eaMap
[i
] = 0xff; /* A dir header uses first 13 entries */
241 eaMap
[i
] = 0x1f; /* A dir header uses first 13 entries */
242 } else if ((i
% 8) == 0) {
243 eaMap
[i
] = 0x01; /* A page header uses only first entry */
247 maxents
= usedPages
* EPP
;
249 /* Walk down all the hash lists, ensuring that each flag field has FFIRST
250 * in it. Mark the appropriate bits in the in-memory freebit map.
251 * Check that the name is in the right hash bucket.
252 * Also check for loops in the hash chain by counting the entries.
254 for (entcount
= 0, i
= 0; i
< NHASHENT
; i
++) {
255 for (entry
= ntohs(dhp
->hashTable
[i
]); entry
; entry
= ne
) {
256 /* Verify that the entry is within range */
257 if (entry
< 0 || entry
>= maxents
) {
258 printf("Out-of-range hash id %d in chain %d.\n", entry
, i
);
259 DRelease(&headerbuf
, 0);
263 /* Read the directory entry */
265 code
= afs_dir_GetBlob(file
, entry
, &entrybuf
);
268 /* something went wrong reading the page, but it wasn't
269 * really something wrong with the dir that we can fix.
271 printf("Could not get dir blob %d (errno %d)\n", entry
,
273 DRelease(&headerbuf
, 0);
276 printf("Invalid hash id %d in chain %d.\n", entry
, i
);
277 DRelease(&headerbuf
, 0);
280 ep
= (struct DirEntry
*)entrybuf
.data
;
282 ne
= ntohs(ep
->next
);
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(&entrybuf
, 0);
288 DRelease(&headerbuf
, 0);
292 /* A null name is no good */
293 if (ep
->name
[0] == '\000') {
294 printf("Dir entry %"AFS_PTR_FMT
295 " in chain %d has bogus (null) name.\n", ep
, i
);
296 DRelease(&entrybuf
, 0);
297 DRelease(&headerbuf
, 0);
301 /* The entry flag better be FFIRST */
302 if (ep
->flag
!= FFIRST
) {
303 printf("Dir entry %"AFS_PTR_FMT
304 " in chain %d has bogus flag field.\n", ep
, i
);
305 DRelease(&entrybuf
, 0);
306 DRelease(&headerbuf
, 0);
310 /* Check the size of the name */
311 j
= strlen(ep
->name
);
312 if (j
>= MAXENAME
) { /* MAXENAME counts the null */
313 printf("Dir entry %"AFS_PTR_FMT
314 " in chain %d has too-long name.\n", ep
, i
);
315 DRelease(&entrybuf
, 0);
316 DRelease(&headerbuf
, 0);
320 /* The name used up k directory entries, set the bit in our in-memory
321 * freebitmap for each entry used by the name.
323 k
= afs_dir_NameBlobs(ep
->name
);
324 for (j
= 0; j
< k
; j
++) {
325 eaMap
[(entry
+ j
) >> 3] |= (1 << ((entry
+ j
) & 7));
328 /* Hash the name and make sure it is in the correct name hash */
329 if ((j
= afs_dir_DirHash(ep
->name
)) != i
) {
330 printf("Dir entry %"AFS_PTR_FMT
331 " should be in hash bucket %d but IS in %d.\n",
333 DRelease(&entrybuf
, 0);
334 DRelease(&headerbuf
, 0);
338 /* Check that if this is entry 13 (the 1st entry), then name must be "." */
340 if (strcmp(ep
->name
, ".") == 0) {
344 ("Dir entry %"AFS_PTR_FMT
345 ", index 13 has name '%s' should be '.'\n",
347 DRelease(&entrybuf
, 0);
348 DRelease(&headerbuf
, 0);
353 /* Check that if this is entry 14 (the 2nd entry), then name must be ".." */
355 if (strcmp(ep
->name
, "..") == 0) {
359 ("Dir entry %"AFS_PTR_FMT
360 ", index 14 has name '%s' should be '..'\n",
362 DRelease(&entrybuf
, 0);
363 DRelease(&headerbuf
, 0);
368 /* CHECK FOR DUPLICATE NAMES? */
370 DRelease(&entrybuf
, 0);
374 /* Verify that we found '.' and '..' in the correct place */
375 if (!havedot
|| !havedotdot
) {
377 ("Directory entry '.' or '..' does not exist or is in the wrong index.\n");
378 DRelease(&headerbuf
, 0);
382 /* The in-memory freebit map has been computed. Check that it
383 * matches the one in the page header.
384 * Note that if this matches, alloMap has already been checked against it.
386 for (i
= 0; i
< usedPages
; i
++) {
387 code
= DRead(file
, i
, &pagebuf
);
390 ("Failed on second attempt to read dir page %d (errno %d)\n",
392 DRelease(&headerbuf
, 0);
393 /* if DErrno is 0, then the dir is really bad, and we return dir *not* OK.
394 * otherwise, we want to return true (1), meaning the dir isn't known
395 * to be bad (we can't tell, since I/Os are failing.
400 return 0; /* dir is really shorter */
402 pp
= (struct PageHeader
*)pagebuf
.data
;
404 count
= i
* (EPP
/ 8);
405 for (j
= 0; j
< EPP
/ 8; j
++) {
406 if (eaMap
[count
+ j
] != pp
->freebitmap
[j
]) {
408 ("Entry freebitmap error, page %d, map offset %d, %x should be %x.\n",
409 i
, j
, pp
->freebitmap
[j
], eaMap
[count
+ j
]);
410 DRelease(&pagebuf
, 0);
411 DRelease(&headerbuf
, 0);
416 DRelease(&pagebuf
, 0);
419 /* Finally cleanup and return. */
420 DRelease(&headerbuf
, 0);
425 * Salvage a directory object.
427 * @param[in] fromFile fid of original, currently suspect directory object
428 * @param[in] toFile fid where salvager will place new, fixed directory
429 * @param[in] vn vnode of currently suspect directory
430 * @param[in] vu uniquifier of currently suspect directory
431 * @param[in] pvn vnode of parent directory
432 * @param[in] pvu uniquifier of parent directory
434 * @return operation status
438 DirSalvage(void *fromFile
, void *toFile
, afs_int32 vn
, afs_int32 vu
,
439 afs_int32 pvn
, afs_int32 pvu
)
441 /* First do a MakeDir on the target. */
442 afs_int32 dot
[3], dotdot
[3], lfid
[3], code
, usedPages
;
446 struct DirBuffer headerbuf
, entrybuf
;
447 struct DirHeader
*dhp
;
451 memset(dot
, 0, sizeof(dot
));
452 memset(dotdot
, 0, sizeof(dotdot
));
458 afs_dir_MakeDir(toFile
, dot
, dotdot
); /* Returns no error code. */
460 /* Find out how many pages are valid, using stupid heuristic since DRead
461 * never returns null.
463 code
= DRead(fromFile
, 0, &headerbuf
);
465 printf("Failed to read first page of fromDir!\n");
466 /* if DErrno != 0, then our call failed and we should let our
467 * caller know that there's something wrong with the new dir. If not,
468 * then we return here anyway, with an empty, but at least good, directory.
472 dhp
= (struct DirHeader
*)headerbuf
.data
;
474 usedPages
= ComputeUsedPages(dhp
);
476 /* Finally, enumerate all the entries, doing a create on them. */
477 for (i
= 0; i
< NHASHENT
; i
++) {
478 entry
= ntohs(dhp
->hashTable
[i
]);
482 if (entry
< 0 || entry
>= usedPages
* EPP
) {
484 ("Warning: bogus hash table entry encountered, ignoring.\n");
489 code
= afs_dir_GetBlob(fromFile
, entry
, &entrybuf
);
493 ("can't continue down hash chain (entry %d, errno %d)\n",
495 DRelease(&headerbuf
, 0);
499 ("Warning: bogus hash chain encountered, switching to next.\n");
502 ep
= (struct DirEntry
*)entrybuf
.data
;
504 strncpy(tname
, ep
->name
, MAXENAME
);
505 tname
[MAXENAME
- 1] = '\000'; /* just in case */
508 entry
= ntohs(ep
->next
);
510 if ((strcmp(tp
, ".") != 0) && (strcmp(tp
, "..") != 0)) {
511 lfid
[1] = ntohl(ep
->fid
.vnode
);
512 lfid
[2] = ntohl(ep
->fid
.vunique
);
513 code
= afs_dir_Create(toFile
, tname
, lfid
);
516 ("Create of %s returned code %d, skipping to next hash chain.\n",
518 DRelease(&entrybuf
, 0);
522 DRelease(&entrybuf
, 0);
526 /* Clean up things. */
527 DRelease(&headerbuf
, 0);