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