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