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