pass2.c revision 176574
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 176574 2008-02-26 03:05:48Z delphij $");
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 blksort(const void *, const void *);
53static int pass2check(struct inodesc *);
54
55void
56pass2(void)
57{
58	union dinode *dp;
59	struct inoinfo **inpp, *inp;
60	struct inoinfo **inpend;
61	struct inodesc curino;
62	union dinode dino;
63	int i;
64	char pathbuf[MAXPATHLEN + 1];
65
66	switch (inoinfo(ROOTINO)->ino_state) {
67
68	case USTATE:
69		pfatal("ROOT INODE UNALLOCATED");
70		if (reply("ALLOCATE") == 0) {
71			ckfini(0);
72			exit(EEXIT);
73		}
74		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
75			errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
76		break;
77
78	case DCLEAR:
79		pfatal("DUPS/BAD IN ROOT INODE");
80		if (reply("REALLOCATE")) {
81			freeino(ROOTINO);
82			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
83				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
84			break;
85		}
86		if (reply("CONTINUE") == 0) {
87			ckfini(0);
88			exit(EEXIT);
89		}
90		break;
91
92	case FSTATE:
93	case FCLEAR:
94	case FZLINK:
95		pfatal("ROOT INODE NOT DIRECTORY");
96		if (reply("REALLOCATE")) {
97			freeino(ROOTINO);
98			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
99				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
100			break;
101		}
102		if (reply("FIX") == 0) {
103			ckfini(0);
104			exit(EEXIT);
105		}
106		dp = ginode(ROOTINO);
107		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
108		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
109		inodirty();
110		break;
111
112	case DSTATE:
113	case DZLINK:
114		break;
115
116	default:
117		errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
118		    inoinfo(ROOTINO)->ino_state);
119	}
120	inoinfo(ROOTINO)->ino_state = DFOUND;
121	inoinfo(WINO)->ino_state = FSTATE;
122	inoinfo(WINO)->ino_type = DT_WHT;
123	/*
124	 * Sort the directory list into disk block order.
125	 */
126	qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
127	/*
128	 * Check the integrity of each directory.
129	 */
130	memset(&curino, 0, sizeof(struct inodesc));
131	curino.id_type = DATA;
132	curino.id_func = pass2check;
133	inpend = &inpsort[inplast];
134	for (inpp = inpsort; inpp < inpend; inpp++) {
135		if (got_siginfo) {
136			printf("%s: phase 2: dir %td of %d (%d%%)\n", cdevname,
137			    inpp - inpsort, (int)inplast,
138			    (int)((inpp - inpsort) * 100 / inplast));
139			got_siginfo = 0;
140		}
141		if (got_sigalarm) {
142			setproctitle("%s p2 %d%%", cdevname,
143			    (int)((inpp - inpsort) * 100 / inplast));
144			got_sigalarm = 0;
145		}
146		inp = *inpp;
147		if (inp->i_isize == 0)
148			continue;
149		if (inp->i_isize < MINDIRSIZE) {
150			direrror(inp->i_number, "DIRECTORY TOO SHORT");
151			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
152			if (reply("FIX") == 1) {
153				dp = ginode(inp->i_number);
154				DIP_SET(dp, di_size, inp->i_isize);
155				inodirty();
156			}
157		} else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
158			getpathname(pathbuf, inp->i_number, inp->i_number);
159			if (usedsoftdep)
160				pfatal("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
161					"DIRECTORY", pathbuf,
162					(intmax_t)inp->i_isize, DIRBLKSIZ);
163			else
164				pwarn("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
165					"DIRECTORY", pathbuf,
166					(intmax_t)inp->i_isize, DIRBLKSIZ);
167			if (preen)
168				printf(" (ADJUSTED)\n");
169			inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
170			if (preen || reply("ADJUST") == 1) {
171				dp = ginode(inp->i_number);
172				DIP_SET(dp, di_size,
173				    roundup(inp->i_isize, DIRBLKSIZ));
174				inodirty();
175			}
176		}
177		dp = &dino;
178		memset(dp, 0, sizeof(struct ufs2_dinode));
179		DIP_SET(dp, di_mode, IFDIR);
180		DIP_SET(dp, di_size, inp->i_isize);
181		for (i = 0;
182		     i < (inp->i_numblks<NDADDR ? inp->i_numblks : NDADDR);
183		     i++)
184			DIP_SET(dp, di_db[i], inp->i_blks[i]);
185		if (inp->i_numblks > NDADDR)
186			for (i = 0; i < NIADDR; i++)
187				DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
188		curino.id_number = inp->i_number;
189		curino.id_parent = inp->i_parent;
190		(void)ckinode(dp, &curino);
191	}
192	/*
193	 * Now that the parents of all directories have been found,
194	 * make another pass to verify the value of `..'
195	 */
196	for (inpp = inpsort; inpp < inpend; inpp++) {
197		inp = *inpp;
198		if (inp->i_parent == 0 || inp->i_isize == 0)
199			continue;
200		if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
201		    INO_IS_DUNFOUND(inp->i_number))
202			inoinfo(inp->i_number)->ino_state = DFOUND;
203		if (inp->i_dotdot == inp->i_parent ||
204		    inp->i_dotdot == (ino_t)-1)
205			continue;
206		if (inp->i_dotdot == 0) {
207			inp->i_dotdot = inp->i_parent;
208			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
209			if (reply("FIX") == 0)
210				continue;
211			(void)makeentry(inp->i_number, inp->i_parent, "..");
212			inoinfo(inp->i_parent)->ino_linkcnt--;
213			continue;
214		}
215		fileerror(inp->i_parent, inp->i_number,
216		    "BAD INODE NUMBER FOR '..'");
217		if (reply("FIX") == 0)
218			continue;
219		inoinfo(inp->i_dotdot)->ino_linkcnt++;
220		inoinfo(inp->i_parent)->ino_linkcnt--;
221		inp->i_dotdot = inp->i_parent;
222		(void)changeino(inp->i_number, "..", inp->i_parent);
223	}
224	/*
225	 * Mark all the directories that can be found from the root.
226	 */
227	propagate();
228}
229
230static int
231pass2check(struct inodesc *idesc)
232{
233	struct direct *dirp = idesc->id_dirp;
234	struct inoinfo *inp;
235	int n, entrysize, ret = 0;
236	union dinode *dp;
237	const char *errmsg;
238	struct direct proto;
239	char namebuf[MAXPATHLEN + 1];
240	char pathbuf[MAXPATHLEN + 1];
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 (inp->i_parent != 0 && idesc->id_entryno > 2) {
420				getpathname(pathbuf, idesc->id_number,
421				    idesc->id_number);
422				getpathname(namebuf, dirp->d_ino, dirp->d_ino);
423				pwarn("%s%s%s %s %s\n", pathbuf,
424				    (strcmp(pathbuf, "/") == 0 ? "" : "/"),
425				    dirp->d_name,
426				    "IS AN EXTRANEOUS HARD LINK TO DIRECTORY",
427				    namebuf);
428				if (cursnapshot != 0)
429					break;
430				if (preen) {
431					printf(" (REMOVED)\n");
432					n = 1;
433					break;
434				}
435				if ((n = reply("REMOVE")) == 1)
436					break;
437			}
438			if (idesc->id_entryno > 2)
439				inp->i_parent = idesc->id_number;
440			/* FALLTHROUGH */
441
442		case FSTATE:
443		case FZLINK:
444			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
445				fileerror(idesc->id_number, dirp->d_ino,
446				    "BAD TYPE VALUE");
447				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
448				if (reply("FIX") == 1)
449					ret |= ALTERED;
450			}
451			inoinfo(dirp->d_ino)->ino_linkcnt--;
452			break;
453
454		default:
455			errx(EEXIT, "BAD STATE %d FOR INODE I=%d",
456			    inoinfo(dirp->d_ino)->ino_state, dirp->d_ino);
457		}
458	}
459	if (n == 0)
460		return (ret|KEEPON);
461	dirp->d_ino = 0;
462	return (ret|KEEPON|ALTERED);
463}
464
465/*
466 * Routine to sort disk blocks.
467 */
468static int
469blksort(const void *arg1, const void *arg2)
470{
471
472	return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
473		(*(struct inoinfo * const *)arg2)->i_blks[0]);
474}
475