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