xref: /openbsd-src/sbin/fsck_ffs/pass2.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: pass2.c,v 1.32 2011/05/08 14:38:40 otto Exp $	*/
2 /*	$NetBSD: pass2.c,v 1.17 1996/09/27 22:45:15 christos Exp $	*/
3 
4 /*
5  * Copyright (c) 1980, 1986, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/param.h>
34 #include <sys/time.h>
35 #include <ufs/ufs/dinode.h>
36 #include <ufs/ufs/dir.h>
37 #include <ufs/ffs/fs.h>
38 
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 
43 #include "fsck.h"
44 #include "fsutil.h"
45 #include "extern.h"
46 
47 #define MINDIRSIZE	(sizeof(struct dirtemplate))
48 
49 static int pass2check(struct inodesc *);
50 static int blksort(const void *, const void *);
51 
52 static int info_max;
53 static int info_pos;
54 
55 static int
56 pass2_info1(char *buf, size_t buflen)
57 {
58 	return (snprintf(buf, buflen, "phase 2, directory %d/%d",
59 	    info_pos, info_max) > 0);
60 }
61 
62 static int
63 pass2_info2(char *buf, size_t buflen)
64 {
65 	if (snprintf(buf, buflen, "phase 2, parent directory %d/%d",
66 	    info_pos, info_max) > 0)
67 		return (strlen(buf));
68 	return (0);
69 }
70 
71 void
72 pass2(void)
73 {
74 	union dinode *dp;
75 	struct inoinfo **inpp, *inp, *pinp;
76 	struct inoinfo **inpend;
77 	struct inodesc curino;
78 	union dinode dino;
79 	char pathbuf[MAXPATHLEN + 1];
80 	int i;
81 
82 	switch (GET_ISTATE(ROOTINO)) {
83 
84 	case USTATE:
85 		pfatal("ROOT INODE UNALLOCATED");
86 		if (reply("ALLOCATE") == 0) {
87 			ckfini(0);
88 			errexit("%s", "");
89 		}
90 		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
91 			errexit("CANNOT ALLOCATE ROOT INODE\n");
92 		break;
93 
94 	case DCLEAR:
95 		pfatal("DUPS/BAD IN ROOT INODE");
96 		if (reply("REALLOCATE")) {
97 			freeino(ROOTINO);
98 			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
99 				errexit("CANNOT ALLOCATE ROOT INODE\n");
100 			break;
101 		}
102 		if (reply("CONTINUE") == 0) {
103 			ckfini(0);
104 			errexit("%s", "");
105 		}
106 		break;
107 
108 	case FSTATE:
109 	case FCLEAR:
110 		pfatal("ROOT INODE NOT DIRECTORY");
111 		if (reply("REALLOCATE")) {
112 			freeino(ROOTINO);
113 			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
114 				errexit("CANNOT ALLOCATE ROOT INODE\n");
115 			break;
116 		}
117 		if (reply("FIX") == 0) {
118 			ckfini(0);
119 			errexit("%s", "");
120 		}
121 		dp = ginode(ROOTINO);
122 		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
123 		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
124 		inodirty();
125 		break;
126 
127 	case DSTATE:
128 		break;
129 
130 	default:
131 		errexit("BAD STATE %d FOR ROOT INODE\n", GET_ISTATE(ROOTINO));
132 	}
133 	SET_ISTATE(ROOTINO, DFOUND);
134 	/*
135 	 * Sort the directory list into disk block order.
136 	 */
137 	qsort(inpsort, (size_t)inplast, sizeof *inpsort, blksort);
138 	/*
139 	 * Check the integrity of each directory.
140 	 */
141 	memset(&curino, 0, sizeof(struct inodesc));
142 	curino.id_type = DATA;
143 	curino.id_func = pass2check;
144 	inpend = &inpsort[inplast];
145 	info_pos = 0;
146 	info_max = inpend - inpsort;
147 	info_fn = pass2_info1;
148 	for (inpp = inpsort; inpp < inpend; inpp++) {
149 		inp = *inpp;
150 		info_pos ++;
151 		if (inp->i_isize == 0)
152 			continue;
153 		if (inp->i_isize < MINDIRSIZE) {
154 			direrror(inp->i_number, "DIRECTORY TOO SHORT");
155 			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
156 			if (reply("FIX") == 1) {
157 				dp = ginode(inp->i_number);
158 				DIP_SET(dp, di_size, inp->i_isize);
159 				inodirty();
160 			}
161 		} else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
162 			getpathname(pathbuf, sizeof pathbuf,
163 			    inp->i_number, inp->i_number);
164 			if (usedsoftdep)
165 			        pfatal("%s %s: LENGTH %ld NOT MULTIPLE of %d",
166 				       "DIRECTORY", pathbuf, (long)inp->i_isize,
167 				       DIRBLKSIZ);
168 			else
169 				pwarn("%s %s: LENGTH %ld NOT MULTIPLE OF %d",
170 				      "DIRECTORY", pathbuf, (long)inp->i_isize,
171 				      DIRBLKSIZ);
172 			if (preen)
173 				printf(" (ADJUSTED)\n");
174 			inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
175 			if (preen || reply("ADJUST") == 1) {
176 				dp = ginode(inp->i_number);
177 				DIP_SET(dp, di_size, inp->i_isize);
178 				inodirty();
179 			}
180 		}
181 		memset(&dino, 0, sizeof(union dinode));
182 		dp = &dino;
183 		DIP_SET(dp, di_mode, IFDIR);
184 		DIP_SET(dp, di_size, inp->i_isize);
185 		for (i = 0;
186 		     i < (inp->i_numblks<NDADDR ? inp->i_numblks : NDADDR);
187 		     i++)
188 			DIP_SET(dp, di_db[i], inp->i_blks[i]);
189 		if (inp->i_numblks > NDADDR)
190 			for (i = 0; i < NIADDR; i++)
191 				DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
192 		curino.id_number = inp->i_number;
193 		curino.id_parent = inp->i_parent;
194 		(void)ckinode(dp, &curino);
195 	}
196 	/*
197 	 * Now that the parents of all directories have been found,
198 	 * make another pass to verify the value of `..'
199 	 */
200 	info_pos = 0;
201 	info_fn = pass2_info2;
202 	for (inpp = inpsort; inpp < inpend; inpp++) {
203 		inp = *inpp;
204 		info_pos++;
205 		if (inp->i_parent == 0 || inp->i_isize == 0)
206 			continue;
207 		if (inp->i_dotdot == inp->i_parent ||
208 		    inp->i_dotdot == (ino_t)-1)
209 			continue;
210 		if (inp->i_dotdot == 0) {
211 			inp->i_dotdot = inp->i_parent;
212 			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
213 			if (reply("FIX") == 0)
214 				continue;
215 			(void)makeentry(inp->i_number, inp->i_parent, "..");
216 			ILNCOUNT(inp->i_parent)--;
217 			continue;
218 		}
219 		fileerror(inp->i_parent, inp->i_number,
220 		    "BAD INODE NUMBER FOR '..'");
221 		if (reply("FIX") == 0)
222 			continue;
223 		ILNCOUNT(inp->i_dotdot)++;
224 		ILNCOUNT(inp->i_parent)--;
225 		inp->i_dotdot = inp->i_parent;
226 		(void)changeino(inp->i_number, "..", inp->i_parent);
227 	}
228 	info_fn = NULL;
229 	/*
230 	 * Create a list of children for each directory.
231 	 */
232 	inpend = &inpsort[inplast];
233 	for (inpp = inpsort; inpp < inpend; inpp++) {
234 		inp = *inpp;
235 		if (inp->i_parent == 0 ||
236 		    inp->i_number == ROOTINO)
237 			continue;
238 		pinp = getinoinfo(inp->i_parent);
239 		inp->i_sibling = pinp->i_child;
240 		pinp->i_child = inp;
241 	}
242 	/*
243 	 * Mark all the directories that can be found from the root.
244 	 */
245 	propagate(ROOTINO);
246 }
247 
248 static int
249 pass2check(struct inodesc *idesc)
250 {
251 	struct direct *dirp = idesc->id_dirp;
252 	struct inoinfo *inp;
253 	int n, entrysize, ret = 0;
254 	union dinode *dp;
255 	char *errmsg;
256 	struct direct proto;
257 	char namebuf[MAXPATHLEN + 1];
258 	char pathbuf[MAXPATHLEN + 1];
259 
260 	/*
261 	 * check for "."
262 	 */
263 	if (idesc->id_entryno != 0)
264 		goto chk1;
265 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
266 		if (dirp->d_ino != idesc->id_number) {
267 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
268 			dirp->d_ino = idesc->id_number;
269 			if (reply("FIX") == 1)
270 				ret |= ALTERED;
271 		}
272 		if (dirp->d_type != DT_DIR) {
273 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
274 			dirp->d_type = DT_DIR;
275 			if (reply("FIX") == 1)
276 				ret |= ALTERED;
277 		}
278 		goto chk1;
279 	}
280 	direrror(idesc->id_number, "MISSING '.'");
281 	proto.d_ino = idesc->id_number;
282 	proto.d_type = DT_DIR;
283 	proto.d_namlen = 1;
284 	(void)strlcpy(proto.d_name, ".", sizeof proto.d_name);
285 	entrysize = DIRSIZ(0, &proto);
286 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
287 		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
288 			dirp->d_name);
289 	} else if (dirp->d_reclen < entrysize) {
290 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
291 	} else if (dirp->d_reclen < 2 * entrysize) {
292 		proto.d_reclen = dirp->d_reclen;
293 		memcpy(dirp, &proto, (size_t)entrysize);
294 		if (reply("FIX") == 1)
295 			ret |= ALTERED;
296 	} else {
297 		n = dirp->d_reclen - entrysize;
298 		proto.d_reclen = entrysize;
299 		memcpy(dirp, &proto, (size_t)entrysize);
300 		idesc->id_entryno++;
301 		ILNCOUNT(dirp->d_ino)--;
302 		dirp = (struct direct *)((char *)(dirp) + entrysize);
303 		memset(dirp, 0, (size_t)n);
304 		dirp->d_reclen = n;
305 		if (reply("FIX") == 1)
306 			ret |= ALTERED;
307 	}
308 chk1:
309 	if (idesc->id_entryno > 1)
310 		goto chk2;
311 	inp = getinoinfo(idesc->id_number);
312 	proto.d_ino = inp->i_parent;
313 	proto.d_type = DT_DIR;
314 	proto.d_namlen = 2;
315 	(void)strlcpy(proto.d_name, "..", sizeof proto.d_name);
316 	entrysize = DIRSIZ(0, &proto);
317 	if (idesc->id_entryno == 0) {
318 		n = DIRSIZ(0, dirp);
319 		if (dirp->d_reclen < n + entrysize)
320 			goto chk2;
321 		proto.d_reclen = dirp->d_reclen - n;
322 		dirp->d_reclen = n;
323 		idesc->id_entryno++;
324 		ILNCOUNT(dirp->d_ino)--;
325 		dirp = (struct direct *)((char *)(dirp) + n);
326 		memset(dirp, 0, (size_t)proto.d_reclen);
327 		dirp->d_reclen = proto.d_reclen;
328 	}
329 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
330 		inp->i_dotdot = dirp->d_ino;
331 		if (dirp->d_type != DT_DIR) {
332 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
333 			dirp->d_type = DT_DIR;
334 			if (reply("FIX") == 1)
335 				ret |= ALTERED;
336 		}
337 		goto chk2;
338 	}
339 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
340 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
341 		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
342 			dirp->d_name);
343 		inp->i_dotdot = -1;
344 	} else if (dirp->d_reclen < entrysize) {
345 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
346 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
347 		inp->i_dotdot = -1;
348 	} else if (inp->i_parent != 0) {
349 		/*
350 		 * We know the parent, so fix now.
351 		 */
352 		inp->i_dotdot = inp->i_parent;
353 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
354 		proto.d_reclen = dirp->d_reclen;
355 		memcpy(dirp, &proto, (size_t)entrysize);
356 		if (reply("FIX") == 1)
357 			ret |= ALTERED;
358 	}
359 	idesc->id_entryno++;
360 	if (dirp->d_ino != 0)
361 		ILNCOUNT(dirp->d_ino)--;
362 	return (ret|KEEPON);
363 chk2:
364 	if (dirp->d_ino == 0)
365 		return (ret|KEEPON);
366 	if (dirp->d_namlen <= 2 &&
367 	    dirp->d_name[0] == '.' &&
368 	    idesc->id_entryno >= 2) {
369 		if (dirp->d_namlen == 1) {
370 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
371 			dirp->d_ino = 0;
372 			if (reply("FIX") == 1)
373 				ret |= ALTERED;
374 			return (KEEPON | ret);
375 		}
376 		if (dirp->d_name[1] == '.') {
377 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
378 			dirp->d_ino = 0;
379 			if (reply("FIX") == 1)
380 				ret |= ALTERED;
381 			return (KEEPON | ret);
382 		}
383 	}
384 	idesc->id_entryno++;
385 	n = 0;
386 	if (dirp->d_ino > maxino) {
387 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
388 		n = reply("REMOVE");
389 	} else {
390 again:
391 		switch (GET_ISTATE(dirp->d_ino)) {
392 		case USTATE:
393 			if (idesc->id_entryno <= 2)
394 				break;
395 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
396 			n = reply("REMOVE");
397 			break;
398 
399 		case DCLEAR:
400 		case FCLEAR:
401 			if (idesc->id_entryno <= 2)
402 				break;
403 			if (GET_ISTATE(dirp->d_ino) == FCLEAR)
404 				errmsg = "DUP/BAD";
405 			else if (!preen && !usedsoftdep)
406 				errmsg = "ZERO LENGTH DIRECTORY";
407 			else {
408 				n = 1;
409 				break;
410 			}
411 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
412 			if ((n = reply("REMOVE")) == 1)
413 				break;
414 			dp = ginode(dirp->d_ino);
415 			SET_ISTATE(dirp->d_ino, (DIP(dp, di_mode) & IFMT) ==
416 			    IFDIR ? DSTATE : FSTATE);
417 			ILNCOUNT(dirp->d_ino) = DIP(dp, di_nlink);
418 			goto again;
419 
420 		case DSTATE:
421 		case DFOUND:
422 			inp = getinoinfo(dirp->d_ino);
423 			if (inp->i_parent != 0 && idesc->id_entryno > 2) {
424 				getpathname(pathbuf, sizeof pathbuf,
425 				    idesc->id_number, idesc->id_number);
426 				getpathname(namebuf, sizeof namebuf,
427 				    dirp->d_ino, dirp->d_ino);
428 				pwarn("%s %s %s\n", pathbuf,
429 				    "IS AN EXTRANEOUS HARD LINK TO DIRECTORY",
430 				    namebuf);
431 				if (preen) {
432 					printf (" (REMOVED)\n");
433 					n = 1;
434 					break;
435 				}
436 				if ((n = reply("REMOVE")) == 1)
437 					break;
438 			}
439 			if (idesc->id_entryno > 2)
440 				inp->i_parent = idesc->id_number;
441 			/* FALLTHROUGH */
442 
443 		case FSTATE:
444 			if (dirp->d_type != GET_ITYPE(dirp->d_ino)) {
445 				fileerror(idesc->id_number, dirp->d_ino,
446 				    "BAD TYPE VALUE");
447 				dirp->d_type = GET_ITYPE(dirp->d_ino);
448 				if (reply("FIX") == 1)
449 					ret |= ALTERED;
450 			}
451 			ILNCOUNT(dirp->d_ino)--;
452 			break;
453 
454 		default:
455 			errexit("BAD STATE %d FOR INODE I=%d\n",
456 			    GET_ISTATE(dirp->d_ino), 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  */
468 static int
469 blksort(const void *inpp1, const void *inpp2)
470 {
471 	daddr64_t d;
472 
473 	d = (* (struct inoinfo **) inpp1)->i_blks[0] -
474 	    (* (struct inoinfo **) inpp2)->i_blks[0];
475 	if (d < 0)
476 		return (-1);
477 	else if (d > 0)
478 		return (1);
479 	else
480 		return (0);
481 }
482