xref: /freebsd/sbin/fsck_ffs/pass2.c (revision 3ff369fed2a08f32dda232c10470b949bef9489f)
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  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifndef lint
35 #if 0
36 static const char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
37 #endif
38 static const char rcsid[] =
39   "$FreeBSD$";
40 #endif /* not lint */
41 
42 #include <sys/param.h>
43 
44 #include <ufs/ufs/dinode.h>
45 #include <ufs/ufs/dir.h>
46 #include <ufs/ffs/fs.h>
47 
48 #include <err.h>
49 #include <string.h>
50 
51 #include "fsck.h"
52 
53 #define MINDIRSIZE	(sizeof (struct dirtemplate))
54 
55 static int blksort(const void *, const void *);
56 static int pass2check(struct inodesc *);
57 
58 void
59 pass2(void)
60 {
61 	struct dinode *dp;
62 	struct inoinfo **inpp, *inp;
63 	struct inoinfo **inpend;
64 	struct inodesc curino;
65 	struct dinode dino;
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 		pfatal("ROOT INODE NOT DIRECTORY");
97 		if (reply("REALLOCATE")) {
98 			freeino(ROOTINO);
99 			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
100 				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
101 			break;
102 		}
103 		if (reply("FIX") == 0) {
104 			ckfini(0);
105 			exit(EEXIT);
106 		}
107 		dp = ginode(ROOTINO);
108 		dp->di_mode &= ~IFMT;
109 		dp->di_mode |= IFDIR;
110 		inodirty();
111 		break;
112 
113 	case DSTATE:
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 	dp = &dino;
134 	inpend = &inpsort[inplast];
135 	for (inpp = inpsort; inpp < inpend; inpp++) {
136 		if (got_siginfo) {
137 			printf("%s: phase 2: dir %d of %d (%d%%)\n", cdevname,
138 			    inpp - inpsort, (int)inplast,
139 			    (int)((inpp - inpsort) * 100 / inplast));
140 			got_siginfo = 0;
141 		}
142 		inp = *inpp;
143 		if (inp->i_isize == 0)
144 			continue;
145 		if (inp->i_isize < MINDIRSIZE) {
146 			direrror(inp->i_number, "DIRECTORY TOO SHORT");
147 			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
148 			if (reply("FIX") == 1) {
149 				dp = ginode(inp->i_number);
150 				dp->di_size = inp->i_isize;
151 				inodirty();
152 				dp = &dino;
153 			}
154 		} else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
155 			getpathname(pathbuf, inp->i_number, inp->i_number);
156 			if (usedsoftdep)
157 				pfatal("%s %s: LENGTH %d NOT MULTIPLE OF %d",
158 					"DIRECTORY", pathbuf, inp->i_isize,
159 					DIRBLKSIZ);
160 			else
161 				pwarn("%s %s: LENGTH %d NOT MULTIPLE OF %d",
162 					"DIRECTORY", pathbuf, inp->i_isize,
163 					DIRBLKSIZ);
164 			if (preen)
165 				printf(" (ADJUSTED)\n");
166 			inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
167 			if (preen || reply("ADJUST") == 1) {
168 				dp = ginode(inp->i_number);
169 				dp->di_size = roundup(inp->i_isize, DIRBLKSIZ);
170 				inodirty();
171 				dp = &dino;
172 			}
173 		}
174 		memset(&dino, 0, sizeof(struct dinode));
175 		dino.di_mode = IFDIR;
176 		dp->di_size = inp->i_isize;
177 		memmove(&dp->di_db[0], &inp->i_blks[0], (size_t)inp->i_numblks);
178 		curino.id_number = inp->i_number;
179 		curino.id_parent = inp->i_parent;
180 		(void)ckinode(dp, &curino);
181 	}
182 	/*
183 	 * Now that the parents of all directories have been found,
184 	 * make another pass to verify the value of `..'
185 	 */
186 	for (inpp = inpsort; inpp < inpend; inpp++) {
187 		inp = *inpp;
188 		if (inp->i_parent == 0 || inp->i_isize == 0)
189 			continue;
190 		if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
191 		    inoinfo(inp->i_number)->ino_state == DSTATE)
192 			inoinfo(inp->i_number)->ino_state = DFOUND;
193 		if (inp->i_dotdot == inp->i_parent ||
194 		    inp->i_dotdot == (ino_t)-1)
195 			continue;
196 		if (inp->i_dotdot == 0) {
197 			inp->i_dotdot = inp->i_parent;
198 			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
199 			if (reply("FIX") == 0)
200 				continue;
201 			(void)makeentry(inp->i_number, inp->i_parent, "..");
202 			inoinfo(inp->i_parent)->ino_linkcnt--;
203 			continue;
204 		}
205 		fileerror(inp->i_parent, inp->i_number,
206 		    "BAD INODE NUMBER FOR '..'");
207 		if (reply("FIX") == 0)
208 			continue;
209 		inoinfo(inp->i_dotdot)->ino_linkcnt++;
210 		inoinfo(inp->i_parent)->ino_linkcnt--;
211 		inp->i_dotdot = inp->i_parent;
212 		(void)changeino(inp->i_number, "..", inp->i_parent);
213 	}
214 	/*
215 	 * Mark all the directories that can be found from the root.
216 	 */
217 	propagate();
218 }
219 
220 static int
221 pass2check(struct inodesc *idesc)
222 {
223 	struct direct *dirp = idesc->id_dirp;
224 	struct inoinfo *inp;
225 	int n, entrysize, ret = 0;
226 	struct dinode *dp;
227 	char *errmsg;
228 	struct direct proto;
229 	char namebuf[MAXPATHLEN + 1];
230 	char pathbuf[MAXPATHLEN + 1];
231 
232 	/*
233 	 * check for "."
234 	 */
235 	if (idesc->id_entryno != 0)
236 		goto chk1;
237 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
238 		if (dirp->d_ino != idesc->id_number) {
239 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
240 			dirp->d_ino = idesc->id_number;
241 			if (reply("FIX") == 1)
242 				ret |= ALTERED;
243 		}
244 		if (dirp->d_type != DT_DIR) {
245 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
246 			dirp->d_type = DT_DIR;
247 			if (reply("FIX") == 1)
248 				ret |= ALTERED;
249 		}
250 		goto chk1;
251 	}
252 	direrror(idesc->id_number, "MISSING '.'");
253 	proto.d_ino = idesc->id_number;
254 	proto.d_type = DT_DIR;
255 	proto.d_namlen = 1;
256 	(void)strcpy(proto.d_name, ".");
257 	entrysize = DIRSIZ(0, &proto);
258 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
259 		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
260 			dirp->d_name);
261 	} else if (dirp->d_reclen < entrysize) {
262 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
263 	} else if (dirp->d_reclen < 2 * entrysize) {
264 		proto.d_reclen = dirp->d_reclen;
265 		memmove(dirp, &proto, (size_t)entrysize);
266 		if (reply("FIX") == 1)
267 			ret |= ALTERED;
268 	} else {
269 		n = dirp->d_reclen - entrysize;
270 		proto.d_reclen = entrysize;
271 		memmove(dirp, &proto, (size_t)entrysize);
272 		idesc->id_entryno++;
273 		inoinfo(dirp->d_ino)->ino_linkcnt--;
274 		dirp = (struct direct *)((char *)(dirp) + entrysize);
275 		memset(dirp, 0, (size_t)n);
276 		dirp->d_reclen = n;
277 		if (reply("FIX") == 1)
278 			ret |= ALTERED;
279 	}
280 chk1:
281 	if (idesc->id_entryno > 1)
282 		goto chk2;
283 	inp = getinoinfo(idesc->id_number);
284 	proto.d_ino = inp->i_parent;
285 	proto.d_type = DT_DIR;
286 	proto.d_namlen = 2;
287 	(void)strcpy(proto.d_name, "..");
288 	entrysize = DIRSIZ(0, &proto);
289 	if (idesc->id_entryno == 0) {
290 		n = DIRSIZ(0, dirp);
291 		if (dirp->d_reclen < n + entrysize)
292 			goto chk2;
293 		proto.d_reclen = dirp->d_reclen - n;
294 		dirp->d_reclen = n;
295 		idesc->id_entryno++;
296 		inoinfo(dirp->d_ino)->ino_linkcnt--;
297 		dirp = (struct direct *)((char *)(dirp) + n);
298 		memset(dirp, 0, (size_t)proto.d_reclen);
299 		dirp->d_reclen = proto.d_reclen;
300 	}
301 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
302 		inp->i_dotdot = dirp->d_ino;
303 		if (dirp->d_type != DT_DIR) {
304 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
305 			dirp->d_type = DT_DIR;
306 			if (reply("FIX") == 1)
307 				ret |= ALTERED;
308 		}
309 		goto chk2;
310 	}
311 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
312 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
313 		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
314 			dirp->d_name);
315 		inp->i_dotdot = (ino_t)-1;
316 	} else if (dirp->d_reclen < entrysize) {
317 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
318 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
319 		inp->i_dotdot = (ino_t)-1;
320 	} else if (inp->i_parent != 0) {
321 		/*
322 		 * We know the parent, so fix now.
323 		 */
324 		inp->i_dotdot = inp->i_parent;
325 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
326 		proto.d_reclen = dirp->d_reclen;
327 		memmove(dirp, &proto, (size_t)entrysize);
328 		if (reply("FIX") == 1)
329 			ret |= ALTERED;
330 	}
331 	idesc->id_entryno++;
332 	if (dirp->d_ino != 0)
333 		inoinfo(dirp->d_ino)->ino_linkcnt--;
334 	return (ret|KEEPON);
335 chk2:
336 	if (dirp->d_ino == 0)
337 		return (ret|KEEPON);
338 	if (dirp->d_namlen <= 2 &&
339 	    dirp->d_name[0] == '.' &&
340 	    idesc->id_entryno >= 2) {
341 		if (dirp->d_namlen == 1) {
342 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
343 			dirp->d_ino = 0;
344 			if (reply("FIX") == 1)
345 				ret |= ALTERED;
346 			return (KEEPON | ret);
347 		}
348 		if (dirp->d_name[1] == '.') {
349 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
350 			dirp->d_ino = 0;
351 			if (reply("FIX") == 1)
352 				ret |= ALTERED;
353 			return (KEEPON | ret);
354 		}
355 	}
356 	idesc->id_entryno++;
357 	n = 0;
358 	if (dirp->d_ino > maxino) {
359 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
360 		n = reply("REMOVE");
361 	} else if (((dirp->d_ino == WINO && dirp->d_type != DT_WHT) ||
362 		    (dirp->d_ino != WINO && dirp->d_type == DT_WHT))) {
363 		fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
364 		dirp->d_ino = WINO;
365 		dirp->d_type = DT_WHT;
366 		if (reply("FIX") == 1)
367 			ret |= ALTERED;
368 	} else {
369 again:
370 		switch (inoinfo(dirp->d_ino)->ino_state) {
371 		case USTATE:
372 			if (idesc->id_entryno <= 2)
373 				break;
374 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
375 			n = reply("REMOVE");
376 			break;
377 
378 		case DCLEAR:
379 		case FCLEAR:
380 			if (idesc->id_entryno <= 2)
381 				break;
382 			if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
383 				errmsg = "DUP/BAD";
384 			else if (!preen && !usedsoftdep)
385 				errmsg = "ZERO LENGTH DIRECTORY";
386 			else {
387 				n = 1;
388 				break;
389 			}
390 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
391 			if ((n = reply("REMOVE")) == 1)
392 				break;
393 			dp = ginode(dirp->d_ino);
394 			inoinfo(dirp->d_ino)->ino_state =
395 			    (dp->di_mode & IFMT) == IFDIR ? DSTATE : FSTATE;
396 			inoinfo(dirp->d_ino)->ino_linkcnt = dp->di_nlink;
397 			goto again;
398 
399 		case DSTATE:
400 			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
401 				inoinfo(dirp->d_ino)->ino_state = DFOUND;
402 			/* fall through */
403 
404 		case DFOUND:
405 			inp = getinoinfo(dirp->d_ino);
406 			if (inp->i_parent != 0 && idesc->id_entryno > 2) {
407 				getpathname(pathbuf, idesc->id_number,
408 				    idesc->id_number);
409 				getpathname(namebuf, dirp->d_ino, dirp->d_ino);
410 				pwarn("%s%s%s %s %s\n", pathbuf,
411 				    (strcmp(pathbuf, "/") == 0 ? "" : "/"),
412 				    dirp->d_name,
413 				    "IS AN EXTRANEOUS HARD LINK TO DIRECTORY",
414 				    namebuf);
415 				if (cursnapshot != 0)
416 					break;
417 				if (preen) {
418 					printf(" (REMOVED)\n");
419 					n = 1;
420 					break;
421 				}
422 				if ((n = reply("REMOVE")) == 1)
423 					break;
424 			}
425 			if (idesc->id_entryno > 2)
426 				inp->i_parent = idesc->id_number;
427 			/* fall through */
428 
429 		case FSTATE:
430 			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
431 				fileerror(idesc->id_number, dirp->d_ino,
432 				    "BAD TYPE VALUE");
433 				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
434 				if (reply("FIX") == 1)
435 					ret |= ALTERED;
436 			}
437 			inoinfo(dirp->d_ino)->ino_linkcnt--;
438 			break;
439 
440 		default:
441 			errx(EEXIT, "BAD STATE %d FOR INODE I=%d",
442 			    inoinfo(dirp->d_ino)->ino_state, dirp->d_ino);
443 		}
444 	}
445 	if (n == 0)
446 		return (ret|KEEPON);
447 	dirp->d_ino = 0;
448 	return (ret|KEEPON|ALTERED);
449 }
450 
451 /*
452  * Routine to sort disk blocks.
453  */
454 static int
455 blksort(const void *arg1, const void *arg2)
456 {
457 
458 	return ((*(struct inoinfo **)arg1)->i_blks[0] -
459 		(*(struct inoinfo **)arg2)->i_blks[0]);
460 }
461