xref: /csrg-svn/sbin/fsck/dir.c (revision 39980)
1 /*
2  * Copyright (c) 1980, 1986 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that the above copyright notice and this paragraph are
7  * duplicated in all such forms and that any documentation,
8  * advertising materials, and other materials related to such
9  * distribution and use acknowledge that the software was developed
10  * by the University of California, Berkeley.  The name of the
11  * University may not be used to endorse or promote products derived
12  * from this software without specific prior written permission.
13  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  */
17 
18 #ifndef lint
19 static char sccsid[] = "@(#)dir.c	5.11 (Berkeley) 02/01/90";
20 #endif /* not lint */
21 
22 #include <sys/param.h>
23 #include <ufs/dinode.h>
24 #include <ufs/fs.h>
25 #define KERNEL
26 #include <ufs/dir.h>
27 #undef KERNEL
28 #include "fsck.h"
29 
30 #define MINDIRSIZE	(sizeof (struct dirtemplate))
31 
32 char	*endpathname = &pathname[BUFSIZ - 2];
33 char	*lfname = "lost+found";
34 int	lfmode = 01777;
35 struct	dirtemplate emptydir = { 0, DIRBLKSIZ };
36 struct	dirtemplate dirhead = { 0, 12, 1, ".", 0, DIRBLKSIZ - 12, 2, ".." };
37 
38 struct direct	*fsck_readdir();
39 struct bufarea	*getdirblk();
40 
41 descend(parentino, inumber)
42 	struct inodesc *parentino;
43 	ino_t inumber;
44 {
45 	register struct dinode *dp;
46 	struct inodesc curino;
47 
48 	bzero((char *)&curino, sizeof(struct inodesc));
49 	if (statemap[inumber] != DSTATE)
50 		errexit("BAD INODE %d TO DESCEND", statemap[inumber]);
51 	statemap[inumber] = DFOUND;
52 	dp = getcacheino(inumber);
53 	if (dp->di_size == 0) {
54 		direrror(inumber, "ZERO LENGTH DIRECTORY");
55 		if (reply("REMOVE") == 1)
56 			statemap[inumber] = DCLEAR;
57 		return;
58 	}
59 	if (dp->di_size < MINDIRSIZE) {
60 		direrror(inumber, "DIRECTORY TOO SHORT");
61 		dp->di_size = MINDIRSIZE;
62 		if (reply("FIX") == 1) {
63 			dp = ginode(inumber);
64 			dp->di_size = MINDIRSIZE;
65 			inodirty();
66 		}
67 	}
68 	if ((dp->di_size & (DIRBLKSIZ - 1)) != 0) {
69 		pwarn("DIRECTORY %s: LENGTH %d NOT MULTIPLE OF %d",
70 			pathname, dp->di_size, DIRBLKSIZ);
71 		dp->di_size = roundup(dp->di_size, DIRBLKSIZ);
72 		if (preen)
73 			printf(" (ADJUSTED)\n");
74 		if (preen || reply("ADJUST") == 1) {
75 			dp = ginode(inumber);
76 			dp->di_size = roundup(dp->di_size, DIRBLKSIZ);
77 			inodirty();
78 		}
79 	}
80 	curino.id_type = DATA;
81 	curino.id_func = parentino->id_func;
82 	curino.id_parent = parentino->id_number;
83 	curino.id_number = inumber;
84 	(void)ckinode(dp, &curino);
85 	if (curino.id_entryno < 2) {
86 		direrror(inumber, "NULL DIRECTORY");
87 		if (reply("REMOVE") == 1)
88 			statemap[inumber] = DCLEAR;
89 	}
90 }
91 
92 dirscan(idesc)
93 	register struct inodesc *idesc;
94 {
95 	register struct direct *dp;
96 	register struct bufarea *bp;
97 	int dsize, n;
98 	long blksiz;
99 	char dbuf[DIRBLKSIZ];
100 
101 	if (idesc->id_type != DATA)
102 		errexit("wrong type to dirscan %d\n", idesc->id_type);
103 	if (idesc->id_entryno == 0 &&
104 	    (idesc->id_filesize & (DIRBLKSIZ - 1)) != 0)
105 		idesc->id_filesize = roundup(idesc->id_filesize, DIRBLKSIZ);
106 	blksiz = idesc->id_numfrags * sblock.fs_fsize;
107 	if (chkrange(idesc->id_blkno, idesc->id_numfrags)) {
108 		idesc->id_filesize -= blksiz;
109 		return (SKIP);
110 	}
111 	idesc->id_loc = 0;
112 	for (dp = fsck_readdir(idesc); dp != NULL; dp = fsck_readdir(idesc)) {
113 		dsize = dp->d_reclen;
114 		bcopy((char *)dp, dbuf, dsize);
115 		idesc->id_dirp = (struct direct *)dbuf;
116 		if ((n = (*idesc->id_func)(idesc)) & ALTERED) {
117 			bp = getdirblk(idesc->id_blkno, blksiz);
118 			bcopy(dbuf, (char *)dp, dsize);
119 			dirty(bp);
120 			sbdirty();
121 		}
122 		if (n & STOP)
123 			return (n);
124 	}
125 	return (idesc->id_filesize > 0 ? KEEPON : STOP);
126 }
127 
128 /*
129  * get next entry in a directory.
130  */
131 struct direct *
132 fsck_readdir(idesc)
133 	register struct inodesc *idesc;
134 {
135 	register struct direct *dp, *ndp;
136 	register struct bufarea *bp;
137 	long size, blksiz;
138 
139 	blksiz = idesc->id_numfrags * sblock.fs_fsize;
140 	bp = getdirblk(idesc->id_blkno, blksiz);
141 	if (idesc->id_loc % DIRBLKSIZ == 0 && idesc->id_filesize > 0 &&
142 	    idesc->id_loc < blksiz) {
143 		dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
144 		if (dircheck(idesc, dp))
145 			goto dpok;
146 		idesc->id_loc += DIRBLKSIZ;
147 		idesc->id_filesize -= DIRBLKSIZ;
148 		dp->d_reclen = DIRBLKSIZ;
149 		dp->d_ino = 0;
150 		dp->d_namlen = 0;
151 		dp->d_name[0] = '\0';
152 		if (dofix(idesc, "DIRECTORY CORRUPTED"))
153 			dirty(bp);
154 		return (dp);
155 	}
156 dpok:
157 	if (idesc->id_filesize <= 0 || idesc->id_loc >= blksiz)
158 		return NULL;
159 	dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
160 	idesc->id_loc += dp->d_reclen;
161 	idesc->id_filesize -= dp->d_reclen;
162 	if ((idesc->id_loc % DIRBLKSIZ) == 0)
163 		return (dp);
164 	ndp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
165 	if (idesc->id_loc < blksiz && idesc->id_filesize > 0 &&
166 	    dircheck(idesc, ndp) == 0) {
167 		size = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ);
168 		dp->d_reclen += size;
169 		idesc->id_loc += size;
170 		idesc->id_filesize -= size;
171 		if (dofix(idesc, "DIRECTORY CORRUPTED"))
172 			dirty(bp);
173 	}
174 	return (dp);
175 }
176 
177 /*
178  * Verify that a directory entry is valid.
179  * This is a superset of the checks made in the kernel.
180  */
181 dircheck(idesc, dp)
182 	struct inodesc *idesc;
183 	register struct direct *dp;
184 {
185 	register int size;
186 	register char *cp;
187 	int spaceleft;
188 
189 	size = DIRSIZ(dp);
190 	spaceleft = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ);
191 	if (dp->d_ino < maxino &&
192 	    dp->d_reclen != 0 &&
193 	    dp->d_reclen <= spaceleft &&
194 	    (dp->d_reclen & 0x3) == 0 &&
195 	    dp->d_reclen >= size &&
196 	    idesc->id_filesize >= size &&
197 	    dp->d_namlen <= MAXNAMLEN) {
198 		if (dp->d_ino == 0)
199 			return (1);
200 		for (cp = dp->d_name, size = 0; size < dp->d_namlen; size++)
201 			if (*cp == 0 || (*cp++ & 0200))
202 				return (0);
203 		if (*cp == 0)
204 			return (1);
205 	}
206 	return (0);
207 }
208 
209 direrror(ino, errmesg)
210 	ino_t ino;
211 	char *errmesg;
212 {
213 	register struct dinode *dp;
214 
215 	pwarn("%s ", errmesg);
216 	pinode(ino);
217 	printf("\n");
218 	if (ino < ROOTINO || ino > maxino) {
219 		pfatal("NAME=%s\n", pathname);
220 		return;
221 	}
222 	dp = ginode(ino);
223 	if (ftypeok(dp))
224 		pfatal("%s=%s\n",
225 		    (dp->di_mode & IFMT) == IFDIR ? "DIR" : "FILE", pathname);
226 	else
227 		pfatal("NAME=%s\n", pathname);
228 }
229 
230 adjust(idesc, lcnt)
231 	register struct inodesc *idesc;
232 	short lcnt;
233 {
234 	register struct dinode *dp;
235 
236 	dp = ginode(idesc->id_number);
237 	if (dp->di_nlink == lcnt) {
238 		if (linkup(idesc->id_number, (ino_t)0) == 0)
239 			clri(idesc, "UNREF", 0);
240 	} else {
241 		pwarn("LINK COUNT %s", (lfdir == idesc->id_number) ? lfname :
242 			((dp->di_mode & IFMT) == IFDIR ? "DIR" : "FILE"));
243 		pinode(idesc->id_number);
244 		printf(" COUNT %d SHOULD BE %d",
245 			dp->di_nlink, dp->di_nlink - lcnt);
246 		if (preen) {
247 			if (lcnt < 0) {
248 				printf("\n");
249 				pfatal("LINK COUNT INCREASING");
250 			}
251 			printf(" (ADJUSTED)\n");
252 		}
253 		if (preen || reply("ADJUST") == 1) {
254 			dp->di_nlink -= lcnt;
255 			inodirty();
256 		}
257 	}
258 }
259 
260 mkentry(idesc)
261 	struct inodesc *idesc;
262 {
263 	register struct direct *dirp = idesc->id_dirp;
264 	struct direct newent;
265 	int newlen, oldlen;
266 
267 	newent.d_namlen = 11;
268 	newlen = DIRSIZ(&newent);
269 	if (dirp->d_ino != 0)
270 		oldlen = DIRSIZ(dirp);
271 	else
272 		oldlen = 0;
273 	if (dirp->d_reclen - oldlen < newlen)
274 		return (KEEPON);
275 	newent.d_reclen = dirp->d_reclen - oldlen;
276 	dirp->d_reclen = oldlen;
277 	dirp = (struct direct *)(((char *)dirp) + oldlen);
278 	dirp->d_ino = idesc->id_parent;	/* ino to be entered is in id_parent */
279 	dirp->d_reclen = newent.d_reclen;
280 	dirp->d_namlen = strlen(idesc->id_name);
281 	bcopy(idesc->id_name, dirp->d_name, (int)dirp->d_namlen + 1);
282 	return (ALTERED|STOP);
283 }
284 
285 chgino(idesc)
286 	struct inodesc *idesc;
287 {
288 	register struct direct *dirp = idesc->id_dirp;
289 
290 	if (bcmp(dirp->d_name, idesc->id_name, (int)dirp->d_namlen + 1))
291 		return (KEEPON);
292 	dirp->d_ino = idesc->id_parent;
293 	return (ALTERED|STOP);
294 }
295 
296 linkup(orphan, parentdir)
297 	ino_t orphan;
298 	ino_t parentdir;
299 {
300 	register struct dinode *dp;
301 	int lostdir, len;
302 	ino_t oldlfdir;
303 	struct inodesc idesc;
304 	char tempname[BUFSIZ];
305 	extern int pass4check();
306 
307 	bzero((char *)&idesc, sizeof(struct inodesc));
308 	dp = ginode(orphan);
309 	lostdir = (dp->di_mode & IFMT) == IFDIR;
310 	pwarn("UNREF %s ", lostdir ? "DIR" : "FILE");
311 	pinode(orphan);
312 	if (preen && dp->di_size == 0)
313 		return (0);
314 	if (preen)
315 		printf(" (RECONNECTED)\n");
316 	else
317 		if (reply("RECONNECT") == 0)
318 			return (0);
319 	pathp = pathname;
320 	*pathp++ = '/';
321 	*pathp = '\0';
322 	if (lfdir == 0) {
323 		dp = ginode(ROOTINO);
324 		idesc.id_name = lfname;
325 		idesc.id_type = DATA;
326 		idesc.id_func = findino;
327 		idesc.id_number = ROOTINO;
328 		if ((ckinode(dp, &idesc) & FOUND) != 0) {
329 			lfdir = idesc.id_parent;
330 		} else {
331 			pwarn("NO lost+found DIRECTORY");
332 			if (preen || reply("CREATE")) {
333 				lfdir = allocdir(ROOTINO, (ino_t)0, lfmode);
334 				if (lfdir != 0) {
335 					if (makeentry(ROOTINO, lfdir, lfname) != 0) {
336 						if (preen)
337 							printf(" (CREATED)\n");
338 					} else {
339 						freedir(lfdir, ROOTINO);
340 						lfdir = 0;
341 						if (preen)
342 							printf("\n");
343 					}
344 				}
345 			}
346 		}
347 		if (lfdir == 0) {
348 			pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY");
349 			printf("\n\n");
350 			return (0);
351 		}
352 	}
353 	dp = ginode(lfdir);
354 	if ((dp->di_mode & IFMT) != IFDIR) {
355 		pfatal("lost+found IS NOT A DIRECTORY");
356 		if (reply("REALLOCATE") == 0)
357 			return (0);
358 		oldlfdir = lfdir;
359 		if ((lfdir = allocdir(ROOTINO, (ino_t)0, lfmode)) == 0) {
360 			pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n");
361 			return (0);
362 		}
363 		idesc.id_type = DATA;
364 		idesc.id_func = chgino;
365 		idesc.id_number = ROOTINO;
366 		idesc.id_parent = lfdir;	/* new inumber for lost+found */
367 		idesc.id_name = lfname;
368 		if ((ckinode(ginode(ROOTINO), &idesc) & ALTERED) == 0) {
369 			pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n");
370 			return (0);
371 		}
372 		inodirty();
373 		idesc.id_type = ADDR;
374 		idesc.id_func = pass4check;
375 		idesc.id_number = oldlfdir;
376 		adjust(&idesc, lncntp[oldlfdir] + 1);
377 		lncntp[oldlfdir] = 0;
378 		dp = ginode(lfdir);
379 	}
380 	if (statemap[lfdir] != DFOUND) {
381 		pfatal("SORRY. NO lost+found DIRECTORY\n\n");
382 		return (0);
383 	}
384 	len = strlen(lfname);
385 	bcopy(lfname, pathp, len + 1);
386 	pathp += len;
387 	len = lftempname(tempname, orphan);
388 	if (makeentry(lfdir, orphan, tempname) == 0) {
389 		pfatal("SORRY. NO SPACE IN lost+found DIRECTORY");
390 		printf("\n\n");
391 		return (0);
392 	}
393 	lncntp[orphan]--;
394 	*pathp++ = '/';
395 	bcopy(tempname, pathp, len + 1);
396 	pathp += len;
397 	if (lostdir) {
398 		dp = ginode(orphan);
399 		idesc.id_type = DATA;
400 		idesc.id_func = chgino;
401 		idesc.id_number = orphan;
402 		idesc.id_fix = DONTKNOW;
403 		idesc.id_name = "..";
404 		idesc.id_parent = lfdir;	/* new value for ".." */
405 		(void)ckinode(dp, &idesc);
406 		dp = ginode(lfdir);
407 		dp->di_nlink++;
408 		inodirty();
409 		lncntp[lfdir]++;
410 		pwarn("DIR I=%u CONNECTED. ", orphan);
411 		printf("PARENT WAS I=%u\n", parentdir);
412 		if (preen == 0)
413 			printf("\n");
414 	}
415 	return (1);
416 }
417 
418 /*
419  * make an entry in a directory
420  */
421 makeentry(parent, ino, name)
422 	ino_t parent, ino;
423 	char *name;
424 {
425 	struct dinode *dp;
426 	struct inodesc idesc;
427 
428 	if (parent < ROOTINO || parent >= maxino ||
429 	    ino < ROOTINO || ino >= maxino)
430 		return (0);
431 	bzero((char *)&idesc, sizeof(struct inodesc));
432 	idesc.id_type = DATA;
433 	idesc.id_func = mkentry;
434 	idesc.id_number = parent;
435 	idesc.id_parent = ino;	/* this is the inode to enter */
436 	idesc.id_fix = DONTKNOW;
437 	idesc.id_name = name;
438 	dp = ginode(parent);
439 	if (dp->di_size % DIRBLKSIZ) {
440 		dp->di_size = roundup(dp->di_size, DIRBLKSIZ);
441 		inodirty();
442 	}
443 	if ((ckinode(dp, &idesc) & ALTERED) != 0)
444 		return (1);
445 	if (expanddir(dp) == 0)
446 		return (0);
447 	return (ckinode(dp, &idesc) & ALTERED);
448 }
449 
450 /*
451  * Attempt to expand the size of a directory
452  */
453 expanddir(dp)
454 	register struct dinode *dp;
455 {
456 	daddr_t lastbn, newblk;
457 	register struct bufarea *bp;
458 	char *cp, firstblk[DIRBLKSIZ];
459 
460 	lastbn = lblkno(&sblock, dp->di_size);
461 	if (lastbn >= NDADDR - 1)
462 		return (0);
463 	if ((newblk = allocblk(sblock.fs_frag)) == 0)
464 		return (0);
465 	dp->di_db[lastbn + 1] = dp->di_db[lastbn];
466 	dp->di_db[lastbn] = newblk;
467 	dp->di_size += sblock.fs_bsize;
468 	dp->di_blocks += btodb(sblock.fs_bsize);
469 	bp = getdirblk(dp->di_db[lastbn + 1],
470 		dblksize(&sblock, dp, lastbn + 1));
471 	if (bp->b_errs)
472 		goto bad;
473 	bcopy(bp->b_un.b_buf, firstblk, DIRBLKSIZ);
474 	bp = getdirblk(newblk, sblock.fs_bsize);
475 	if (bp->b_errs)
476 		goto bad;
477 	bcopy(firstblk, bp->b_un.b_buf, DIRBLKSIZ);
478 	for (cp = &bp->b_un.b_buf[DIRBLKSIZ];
479 	     cp < &bp->b_un.b_buf[sblock.fs_bsize];
480 	     cp += DIRBLKSIZ)
481 		bcopy((char *)&emptydir, cp, sizeof emptydir);
482 	dirty(bp);
483 	bp = getdirblk(dp->di_db[lastbn + 1],
484 		dblksize(&sblock, dp, lastbn + 1));
485 	if (bp->b_errs)
486 		goto bad;
487 	bcopy((char *)&emptydir, bp->b_un.b_buf, sizeof emptydir);
488 	pwarn("NO SPACE LEFT IN %s", pathname);
489 	if (preen)
490 		printf(" (EXPANDED)\n");
491 	else if (reply("EXPAND") == 0)
492 		goto bad;
493 	dirty(bp);
494 	inodirty();
495 	return (1);
496 bad:
497 	dp->di_db[lastbn] = dp->di_db[lastbn + 1];
498 	dp->di_db[lastbn + 1] = 0;
499 	dp->di_size -= sblock.fs_bsize;
500 	dp->di_blocks -= btodb(sblock.fs_bsize);
501 	freeblk(newblk, sblock.fs_frag);
502 	return (0);
503 }
504 
505 /*
506  * allocate a new directory
507  */
508 allocdir(parent, request, mode)
509 	ino_t parent, request;
510 	int mode;
511 {
512 	ino_t ino;
513 	char *cp;
514 	struct dinode *dp;
515 	register struct bufarea *bp;
516 
517 	ino = allocino(request, IFDIR|mode);
518 	dirhead.dot_ino = ino;
519 	dirhead.dotdot_ino = parent;
520 	dp = ginode(ino);
521 	bp = getdirblk(dp->di_db[0], sblock.fs_fsize);
522 	if (bp->b_errs) {
523 		freeino(ino);
524 		return (0);
525 	}
526 	bcopy((char *)&dirhead, bp->b_un.b_buf, sizeof dirhead);
527 	for (cp = &bp->b_un.b_buf[DIRBLKSIZ];
528 	     cp < &bp->b_un.b_buf[sblock.fs_fsize];
529 	     cp += DIRBLKSIZ)
530 		bcopy((char *)&emptydir, cp, sizeof emptydir);
531 	dirty(bp);
532 	dp->di_nlink = 2;
533 	inodirty();
534 	if (ino == ROOTINO) {
535 		lncntp[ino] = dp->di_nlink;
536 		return(ino);
537 	}
538 	if (statemap[parent] != DSTATE && statemap[parent] != DFOUND) {
539 		freeino(ino);
540 		return (0);
541 	}
542 	statemap[ino] = statemap[parent];
543 	if (statemap[ino] == DSTATE) {
544 		lncntp[ino] = dp->di_nlink;
545 		lncntp[parent]++;
546 	}
547 	dp = ginode(parent);
548 	dp->di_nlink++;
549 	inodirty();
550 	return (ino);
551 }
552 
553 /*
554  * free a directory inode
555  */
556 freedir(ino, parent)
557 	ino_t ino, parent;
558 {
559 	struct dinode *dp;
560 
561 	if (ino != parent) {
562 		dp = ginode(parent);
563 		dp->di_nlink--;
564 		inodirty();
565 	}
566 	freeino(ino);
567 }
568 
569 /*
570  * generate a temporary name for the lost+found directory.
571  */
572 lftempname(bufp, ino)
573 	char *bufp;
574 	ino_t ino;
575 {
576 	register ino_t in;
577 	register char *cp;
578 	int namlen;
579 
580 	cp = bufp + 2;
581 	for (in = maxino; in > 0; in /= 10)
582 		cp++;
583 	*--cp = 0;
584 	namlen = cp - bufp;
585 	in = ino;
586 	while (cp > bufp) {
587 		*--cp = (in % 10) + '0';
588 		in /= 10;
589 	}
590 	*cp = '#';
591 	return (namlen);
592 }
593 
594 /*
595  * Get a directory block.
596  * Insure that it is held until another is requested.
597  */
598 struct bufarea *
599 getdirblk(blkno, size)
600 	daddr_t blkno;
601 	long size;
602 {
603 	static struct bufarea *pbp = 0;
604 
605 	if (pbp != 0)
606 		pbp->b_flags &= ~B_INUSE;
607 	pbp = getdatablk(blkno, size);
608 	return (pbp);
609 }
610