pass2.c revision 202107
1/*
2 * Copyright (c) 1980, 1986, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#if 0
31#ifndef lint
32static const char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
33#endif /* not lint */
34#endif
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD: head/sbin/fsck_ffs/pass2.c 202107 2010-01-11 19:52:40Z mckusick $");
37
38#include <sys/param.h>
39
40#include <ufs/ufs/dinode.h>
41#include <ufs/ufs/dir.h>
42#include <ufs/ffs/fs.h>
43
44#include <err.h>
45#include <stdint.h>
46#include <string.h>
47
48#include "fsck.h"
49
50#define MINDIRSIZE	(sizeof (struct dirtemplate))
51
52static int fix_extraneous(struct inoinfo *, struct inodesc *);
53static int deleteentry(struct inodesc *);
54static int blksort(const void *, const void *);
55static int pass2check(struct inodesc *);
56
57void
58pass2(void)
59{
60	union dinode *dp;
61	struct inoinfo **inpp, *inp;
62	struct inoinfo **inpend;
63	struct inodesc curino;
64	union dinode dino;
65	int i;
66	char pathbuf[MAXPATHLEN + 1];
67
68	switch (inoinfo(ROOTINO)->ino_state) {
69
70	case USTATE:
71		pfatal("ROOT INODE UNALLOCATED");
72		if (reply("ALLOCATE") == 0) {
73			ckfini(0);
74			exit(EEXIT);
75		}
76		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
77			errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
78		break;
79
80	case DCLEAR:
81		pfatal("DUPS/BAD IN ROOT INODE");
82		if (reply("REALLOCATE")) {
83			freeino(ROOTINO);
84			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
85				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
86			break;
87		}
88		if (reply("CONTINUE") == 0) {
89			ckfini(0);
90			exit(EEXIT);
91		}
92		break;
93
94	case FSTATE:
95	case FCLEAR:
96	case FZLINK:
97		pfatal("ROOT INODE NOT DIRECTORY");
98		if (reply("REALLOCATE")) {
99			freeino(ROOTINO);
100			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
101				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
102			break;
103		}
104		if (reply("FIX") == 0) {
105			ckfini(0);
106			exit(EEXIT);
107		}
108		dp = ginode(ROOTINO);
109		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
110		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
111		inodirty();
112		break;
113
114	case DSTATE:
115	case DZLINK:
116		break;
117
118	default:
119		errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
120		    inoinfo(ROOTINO)->ino_state);
121	}
122	inoinfo(ROOTINO)->ino_state = DFOUND;
123	inoinfo(WINO)->ino_state = FSTATE;
124	inoinfo(WINO)->ino_type = DT_WHT;
125	/*
126	 * Sort the directory list into disk block order.
127	 */
128	qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
129	/*
130	 * Check the integrity of each directory.
131	 */
132	memset(&curino, 0, sizeof(struct inodesc));
133	curino.id_type = DATA;
134	curino.id_func = pass2check;
135	inpend = &inpsort[inplast];
136	for (inpp = inpsort; inpp < inpend; inpp++) {
137		if (got_siginfo) {
138			printf("%s: phase 2: dir %td of %d (%d%%)\n", cdevname,
139			    inpp - inpsort, (int)inplast,
140			    (int)((inpp - inpsort) * 100 / inplast));
141			got_siginfo = 0;
142		}
143		if (got_sigalarm) {
144			setproctitle("%s p2 %d%%", cdevname,
145			    (int)((inpp - inpsort) * 100 / inplast));
146			got_sigalarm = 0;
147		}
148		inp = *inpp;
149		if (inp->i_isize == 0)
150			continue;
151		if (inp->i_isize < MINDIRSIZE) {
152			direrror(inp->i_number, "DIRECTORY TOO SHORT");
153			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
154			if (reply("FIX") == 1) {
155				dp = ginode(inp->i_number);
156				DIP_SET(dp, di_size, inp->i_isize);
157				inodirty();
158			}
159		} else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
160			getpathname(pathbuf, inp->i_number, inp->i_number);
161			if (usedsoftdep)
162				pfatal("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
163					"DIRECTORY", pathbuf,
164					(intmax_t)inp->i_isize, DIRBLKSIZ);
165			else
166				pwarn("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
167					"DIRECTORY", pathbuf,
168					(intmax_t)inp->i_isize, DIRBLKSIZ);
169			if (preen)
170				printf(" (ADJUSTED)\n");
171			inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
172			if (preen || reply("ADJUST") == 1) {
173				dp = ginode(inp->i_number);
174				DIP_SET(dp, di_size,
175				    roundup(inp->i_isize, DIRBLKSIZ));
176				inodirty();
177			}
178		}
179		dp = &dino;
180		memset(dp, 0, sizeof(struct ufs2_dinode));
181		DIP_SET(dp, di_mode, IFDIR);
182		DIP_SET(dp, di_size, inp->i_isize);
183		for (i = 0;
184		     i < (inp->i_numblks<NDADDR ? inp->i_numblks : NDADDR);
185		     i++)
186			DIP_SET(dp, di_db[i], inp->i_blks[i]);
187		if (inp->i_numblks > NDADDR)
188			for (i = 0; i < NIADDR; i++)
189				DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
190		curino.id_number = inp->i_number;
191		curino.id_parent = inp->i_parent;
192		(void)ckinode(dp, &curino);
193	}
194	/*
195	 * Now that the parents of all directories have been found,
196	 * make another pass to verify the value of `..'
197	 */
198	for (inpp = inpsort; inpp < inpend; inpp++) {
199		inp = *inpp;
200		if (inp->i_parent == 0 || inp->i_isize == 0)
201			continue;
202		if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
203		    INO_IS_DUNFOUND(inp->i_number))
204			inoinfo(inp->i_number)->ino_state = DFOUND;
205		if (inp->i_dotdot == inp->i_parent ||
206		    inp->i_dotdot == (ino_t)-1)
207			continue;
208		if (inp->i_dotdot == 0) {
209			inp->i_dotdot = inp->i_parent;
210			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
211			if (reply("FIX") == 0)
212				continue;
213			(void)makeentry(inp->i_number, inp->i_parent, "..");
214			inoinfo(inp->i_parent)->ino_linkcnt--;
215			continue;
216		}
217		fileerror(inp->i_parent, inp->i_number,
218		    "BAD INODE NUMBER FOR '..'");
219		if (reply("FIX") == 0)
220			continue;
221		inoinfo(inp->i_dotdot)->ino_linkcnt++;
222		inoinfo(inp->i_parent)->ino_linkcnt--;
223		inp->i_dotdot = inp->i_parent;
224		(void)changeino(inp->i_number, "..", inp->i_parent);
225	}
226	/*
227	 * Mark all the directories that can be found from the root.
228	 */
229	propagate();
230}
231
232static int
233pass2check(struct inodesc *idesc)
234{
235	struct direct *dirp = idesc->id_dirp;
236	struct inoinfo *inp;
237	int n, entrysize, ret = 0;
238	union dinode *dp;
239	const char *errmsg;
240	struct direct proto;
241
242	/*
243	 * check for "."
244	 */
245	if (dirp->d_ino > maxino)
246		goto chk2;
247	if (idesc->id_entryno != 0)
248		goto chk1;
249	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
250		if (dirp->d_ino != idesc->id_number) {
251			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
252			dirp->d_ino = idesc->id_number;
253			if (reply("FIX") == 1)
254				ret |= ALTERED;
255		}
256		if (dirp->d_type != DT_DIR) {
257			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
258			dirp->d_type = DT_DIR;
259			if (reply("FIX") == 1)
260				ret |= ALTERED;
261		}
262		goto chk1;
263	}
264	direrror(idesc->id_number, "MISSING '.'");
265	proto.d_ino = idesc->id_number;
266	proto.d_type = DT_DIR;
267	proto.d_namlen = 1;
268	(void)strcpy(proto.d_name, ".");
269	entrysize = DIRSIZ(0, &proto);
270	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
271		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
272			dirp->d_name);
273	} else if (dirp->d_reclen < entrysize) {
274		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
275	} else if (dirp->d_reclen < 2 * entrysize) {
276		proto.d_reclen = dirp->d_reclen;
277		memmove(dirp, &proto, (size_t)entrysize);
278		if (reply("FIX") == 1)
279			ret |= ALTERED;
280	} else {
281		n = dirp->d_reclen - entrysize;
282		proto.d_reclen = entrysize;
283		memmove(dirp, &proto, (size_t)entrysize);
284		idesc->id_entryno++;
285		inoinfo(dirp->d_ino)->ino_linkcnt--;
286		dirp = (struct direct *)((char *)(dirp) + entrysize);
287		memset(dirp, 0, (size_t)n);
288		dirp->d_reclen = n;
289		if (reply("FIX") == 1)
290			ret |= ALTERED;
291	}
292chk1:
293	if (idesc->id_entryno > 1)
294		goto chk2;
295	inp = getinoinfo(idesc->id_number);
296	proto.d_ino = inp->i_parent;
297	proto.d_type = DT_DIR;
298	proto.d_namlen = 2;
299	(void)strcpy(proto.d_name, "..");
300	entrysize = DIRSIZ(0, &proto);
301	if (idesc->id_entryno == 0) {
302		n = DIRSIZ(0, dirp);
303		if (dirp->d_reclen < n + entrysize)
304			goto chk2;
305		proto.d_reclen = dirp->d_reclen - n;
306		dirp->d_reclen = n;
307		idesc->id_entryno++;
308		inoinfo(dirp->d_ino)->ino_linkcnt--;
309		dirp = (struct direct *)((char *)(dirp) + n);
310		memset(dirp, 0, (size_t)proto.d_reclen);
311		dirp->d_reclen = proto.d_reclen;
312	}
313	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
314		inp->i_dotdot = dirp->d_ino;
315		if (dirp->d_type != DT_DIR) {
316			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
317			dirp->d_type = DT_DIR;
318			if (reply("FIX") == 1)
319				ret |= ALTERED;
320		}
321		goto chk2;
322	}
323	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
324		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
325		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
326			dirp->d_name);
327		inp->i_dotdot = (ino_t)-1;
328	} else if (dirp->d_reclen < entrysize) {
329		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
330		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
331		inp->i_dotdot = (ino_t)-1;
332	} else if (inp->i_parent != 0) {
333		/*
334		 * We know the parent, so fix now.
335		 */
336		inp->i_dotdot = inp->i_parent;
337		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
338		proto.d_reclen = dirp->d_reclen;
339		memmove(dirp, &proto, (size_t)entrysize);
340		if (reply("FIX") == 1)
341			ret |= ALTERED;
342	}
343	idesc->id_entryno++;
344	if (dirp->d_ino != 0)
345		inoinfo(dirp->d_ino)->ino_linkcnt--;
346	return (ret|KEEPON);
347chk2:
348	if (dirp->d_ino == 0)
349		return (ret|KEEPON);
350	if (dirp->d_namlen <= 2 &&
351	    dirp->d_name[0] == '.' &&
352	    idesc->id_entryno >= 2) {
353		if (dirp->d_namlen == 1) {
354			direrror(idesc->id_number, "EXTRA '.' ENTRY");
355			dirp->d_ino = 0;
356			if (reply("FIX") == 1)
357				ret |= ALTERED;
358			return (KEEPON | ret);
359		}
360		if (dirp->d_name[1] == '.') {
361			direrror(idesc->id_number, "EXTRA '..' ENTRY");
362			dirp->d_ino = 0;
363			if (reply("FIX") == 1)
364				ret |= ALTERED;
365			return (KEEPON | ret);
366		}
367	}
368	idesc->id_entryno++;
369	n = 0;
370	if (dirp->d_ino > maxino) {
371		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
372		n = reply("REMOVE");
373	} else if (((dirp->d_ino == WINO && dirp->d_type != DT_WHT) ||
374		    (dirp->d_ino != WINO && dirp->d_type == DT_WHT))) {
375		fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
376		dirp->d_ino = WINO;
377		dirp->d_type = DT_WHT;
378		if (reply("FIX") == 1)
379			ret |= ALTERED;
380	} else {
381again:
382		switch (inoinfo(dirp->d_ino)->ino_state) {
383		case USTATE:
384			if (idesc->id_entryno <= 2)
385				break;
386			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
387			n = reply("REMOVE");
388			break;
389
390		case DCLEAR:
391		case FCLEAR:
392			if (idesc->id_entryno <= 2)
393				break;
394			if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
395				errmsg = "DUP/BAD";
396			else if (!preen && !usedsoftdep)
397				errmsg = "ZERO LENGTH DIRECTORY";
398			else {
399				n = 1;
400				break;
401			}
402			fileerror(idesc->id_number, dirp->d_ino, errmsg);
403			if ((n = reply("REMOVE")) == 1)
404				break;
405			dp = ginode(dirp->d_ino);
406			inoinfo(dirp->d_ino)->ino_state =
407			   (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
408			inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
409			goto again;
410
411		case DSTATE:
412		case DZLINK:
413			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
414				inoinfo(dirp->d_ino)->ino_state = DFOUND;
415			/* FALLTHROUGH */
416
417		case DFOUND:
418			inp = getinoinfo(dirp->d_ino);
419			if (idesc->id_entryno > 2) {
420				if (inp->i_parent == 0)
421					inp->i_parent = idesc->id_number;
422				else if ((n = fix_extraneous(inp, idesc)) == 1)
423					break;
424			}
425			/* FALLTHROUGH */
426
427		case FSTATE:
428		case FZLINK:
429			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
430				fileerror(idesc->id_number, dirp->d_ino,
431				    "BAD TYPE VALUE");
432				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
433				if (reply("FIX") == 1)
434					ret |= ALTERED;
435			}
436			inoinfo(dirp->d_ino)->ino_linkcnt--;
437			break;
438
439		default:
440			errx(EEXIT, "BAD STATE %d FOR INODE I=%d",
441			    inoinfo(dirp->d_ino)->ino_state, dirp->d_ino);
442		}
443	}
444	if (n == 0)
445		return (ret|KEEPON);
446	dirp->d_ino = 0;
447	return (ret|KEEPON|ALTERED);
448}
449
450static int
451fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
452{
453	struct inodesc dotdesc;
454	char oldname[MAXPATHLEN + 1];
455	char newname[MAXPATHLEN + 1];
456
457	/*
458	 * If we have not yet found "..", look it up now so we know
459	 * which inode the directory itself believes is its parent.
460	 */
461	if (inp->i_dotdot == 0) {
462		memset(&dotdesc, 0, sizeof(struct inodesc));
463		dotdesc.id_type = DATA;
464		dotdesc.id_number = idesc->id_dirp->d_ino;
465		dotdesc.id_func = findino;
466		dotdesc.id_name = strdup("..");
467		if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND))
468			inp->i_dotdot = dotdesc.id_parent;
469	}
470	/*
471	 * We have the previously found old name (inp->i_parent) and the
472	 * just found new name (idesc->id_number). We have five cases:
473	 * 1)  ".." is missing - can remove either name, choose to delete
474	 *     new one and let fsck create ".." pointing to old name.
475	 * 2) Both new and old are in same directory, choose to delete
476	 *    the new name and let fsck fix ".." if it is wrong.
477	 * 3) ".." does not point to the new name, so delete it and let
478	 *    fsck fix ".." to point to the old one if it is wrong.
479	 * 4) ".." points to the old name only, so delete the new one.
480	 * 5) ".." points to the new name only, so delete the old one.
481	 *
482	 * For cases 1-4 we eliminate the new name;
483	 * for case 5 we eliminate the old name.
484	 */
485	if (inp->i_dotdot == 0 ||		    /* Case 1 */
486	    idesc->id_number == inp->i_parent ||    /* Case 2 */
487	    inp->i_dotdot != idesc->id_number ||    /* Case 3 */
488	    inp->i_dotdot == inp->i_parent) {	    /* Case 4 */
489		getpathname(newname, idesc->id_number, idesc->id_number);
490		if (strcmp(newname, "/") != 0)
491			strcat (newname, "/");
492		strcat(newname, idesc->id_dirp->d_name);
493		getpathname(oldname, inp->i_number, inp->i_number);
494		pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s\n",
495		    newname, oldname);
496		if (cursnapshot != 0) {
497			/*
498			 * We need to
499			 *    setcwd(idesc->id_number);
500			 *    unlink(idesc->id_dirp->d_name);
501			 */
502			printf(" (IGNORED)\n");
503			return (0);
504		}
505		if (preen) {
506			printf(" (REMOVED)\n");
507			return (1);
508		}
509		return (reply("REMOVE"));
510	}
511	/*
512	 * None of the first four cases above, so must be case (5).
513	 * Eliminate the old name and make the new the name the parent.
514	 */
515	getpathname(oldname, inp->i_parent, inp->i_number);
516	getpathname(newname, inp->i_number, inp->i_number);
517	pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s\n", oldname,
518	    newname);
519	if (cursnapshot != 0) {
520		/*
521		 * We need to
522		 *    setcwd(inp->i_parent);
523		 *    unlink(last component of oldname pathname);
524		 */
525		printf(" (IGNORED)\n");
526		return (0);
527	}
528	if (!preen && !reply("REMOVE"))
529		return (0);
530	memset(&dotdesc, 0, sizeof(struct inodesc));
531	dotdesc.id_type = DATA;
532	dotdesc.id_number = inp->i_parent; /* directory in which name appears */
533	dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
534	dotdesc.id_func = deleteentry;
535	if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND) && preen)
536		printf(" (REMOVED)\n");
537	inp->i_parent = idesc->id_number;  /* reparent to correct directory */
538	inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
539	return (0);
540}
541
542static int
543deleteentry(struct inodesc *idesc)
544{
545	struct direct *dirp = idesc->id_dirp;
546
547	if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
548		return (KEEPON);
549	dirp->d_ino = 0;
550	return (ALTERED|STOP|FOUND);
551}
552
553/*
554 * Routine to sort disk blocks.
555 */
556static int
557blksort(const void *arg1, const void *arg2)
558{
559
560	return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
561		(*(struct inoinfo * const *)arg2)->i_blks[0]);
562}
563