xref: /openbsd/sbin/fsck_ffs/pass2.c (revision d415bd75)
1 /*	$OpenBSD: pass2.c,v 1.37 2015/01/20 18:22:21 deraadt 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>	/* DEV_BSIZE roundup */
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 #include <limits.h>
43 
44 #include "fsck.h"
45 #include "fsutil.h"
46 #include "extern.h"
47 
48 #define MINDIRSIZE	(sizeof(struct dirtemplate))
49 
50 static int pass2check(struct inodesc *);
51 static int blksort(const void *, const void *);
52 
53 static int info_max;
54 static int info_pos;
55 
56 static int
57 pass2_info1(char *buf, size_t buflen)
58 {
59 	return (snprintf(buf, buflen, "phase 2, directory %d/%d",
60 	    info_pos, info_max) > 0);
61 }
62 
63 static int
64 pass2_info2(char *buf, size_t buflen)
65 {
66 	if (snprintf(buf, buflen, "phase 2, parent directory %d/%d",
67 	    info_pos, info_max) > 0)
68 		return (strlen(buf));
69 	return (0);
70 }
71 
72 void
73 pass2(void)
74 {
75 	union dinode *dp;
76 	struct inoinfo **inpp, *inp, *pinp;
77 	struct inoinfo **inpend;
78 	struct inodesc curino;
79 	union dinode dino;
80 	char pathbuf[PATH_MAX + 1];
81 	int i;
82 
83 	switch (GET_ISTATE(ROOTINO)) {
84 
85 	case USTATE:
86 		pfatal("ROOT INODE UNALLOCATED");
87 		if (reply("ALLOCATE") == 0) {
88 			ckfini(0);
89 			errexit("%s", "");
90 		}
91 		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
92 			errexit("CANNOT ALLOCATE ROOT INODE\n");
93 		break;
94 
95 	case DCLEAR:
96 		pfatal("DUPS/BAD IN ROOT INODE");
97 		if (reply("REALLOCATE")) {
98 			freeino(ROOTINO);
99 			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
100 				errexit("CANNOT ALLOCATE ROOT INODE\n");
101 			break;
102 		}
103 		if (reply("CONTINUE") == 0) {
104 			ckfini(0);
105 			errexit("%s", "");
106 		}
107 		break;
108 
109 	case FSTATE:
110 	case FCLEAR:
111 		pfatal("ROOT INODE NOT DIRECTORY");
112 		if (reply("REALLOCATE")) {
113 			freeino(ROOTINO);
114 			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
115 				errexit("CANNOT ALLOCATE ROOT INODE\n");
116 			break;
117 		}
118 		if (reply("FIX") == 0) {
119 			ckfini(0);
120 			errexit("%s", "");
121 		}
122 		dp = ginode(ROOTINO);
123 		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
124 		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
125 		inodirty();
126 		break;
127 
128 	case DSTATE:
129 		break;
130 
131 	default:
132 		errexit("BAD STATE %d FOR ROOT INODE\n", GET_ISTATE(ROOTINO));
133 	}
134 	SET_ISTATE(ROOTINO, DFOUND);
135 	/*
136 	 * Sort the directory list into disk block order.
137 	 */
138 	qsort(inpsort, (size_t)inplast, sizeof *inpsort, blksort);
139 	/*
140 	 * Check the integrity of each directory.
141 	 */
142 	memset(&curino, 0, sizeof(struct inodesc));
143 	curino.id_type = DATA;
144 	curino.id_func = pass2check;
145 	inpend = &inpsort[inplast];
146 	info_pos = 0;
147 	info_max = inpend - inpsort;
148 	info_fn = pass2_info1;
149 	for (inpp = inpsort; inpp < inpend; inpp++) {
150 		inp = *inpp;
151 		info_pos ++;
152 		if (inp->i_isize == 0)
153 			continue;
154 		if (inp->i_isize < MINDIRSIZE) {
155 			direrror(inp->i_number, "DIRECTORY TOO SHORT");
156 			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
157 			if (reply("FIX") == 1) {
158 				dp = ginode(inp->i_number);
159 				DIP_SET(dp, di_size, inp->i_isize);
160 				inodirty();
161 			}
162 		} else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
163 			getpathname(pathbuf, sizeof pathbuf,
164 			    inp->i_number, inp->i_number);
165 			if (usedsoftdep)
166 			        pfatal("%s %s: LENGTH %zu NOT MULTIPLE of %d",
167 				       "DIRECTORY", pathbuf, inp->i_isize,
168 				       DIRBLKSIZ);
169 			else
170 				pwarn("%s %s: LENGTH %zu NOT MULTIPLE OF %d",
171 				      "DIRECTORY", pathbuf, inp->i_isize,
172 				      DIRBLKSIZ);
173 			if (preen)
174 				printf(" (ADJUSTED)\n");
175 			inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
176 			if (preen || reply("ADJUST") == 1) {
177 				dp = ginode(inp->i_number);
178 				DIP_SET(dp, di_size, inp->i_isize);
179 				inodirty();
180 			}
181 		}
182 		memset(&dino, 0, sizeof(union dinode));
183 		dp = &dino;
184 		DIP_SET(dp, di_mode, IFDIR);
185 		DIP_SET(dp, di_size, inp->i_isize);
186 		for (i = 0;
187 		     i < (inp->i_numblks<NDADDR ? inp->i_numblks : NDADDR);
188 		     i++)
189 			DIP_SET(dp, di_db[i], inp->i_blks[i]);
190 		if (inp->i_numblks > NDADDR)
191 			for (i = 0; i < NIADDR; i++)
192 				DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
193 		curino.id_number = inp->i_number;
194 		curino.id_parent = inp->i_parent;
195 		(void)ckinode(dp, &curino);
196 	}
197 	/*
198 	 * Now that the parents of all directories have been found,
199 	 * make another pass to verify the value of `..'
200 	 */
201 	info_pos = 0;
202 	info_fn = pass2_info2;
203 	for (inpp = inpsort; inpp < inpend; inpp++) {
204 		inp = *inpp;
205 		info_pos++;
206 		if (inp->i_parent == 0 || inp->i_isize == 0)
207 			continue;
208 		if (inp->i_dotdot == inp->i_parent ||
209 		    inp->i_dotdot == (ino_t)-1)
210 			continue;
211 		if (inp->i_dotdot == 0) {
212 			inp->i_dotdot = inp->i_parent;
213 			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
214 			if (reply("FIX") == 0)
215 				continue;
216 			(void)makeentry(inp->i_number, inp->i_parent, "..");
217 			ILNCOUNT(inp->i_parent)--;
218 			continue;
219 		}
220 		fileerror(inp->i_parent, inp->i_number,
221 		    "BAD INODE NUMBER FOR '..'");
222 		if (reply("FIX") == 0)
223 			continue;
224 		ILNCOUNT(inp->i_dotdot)++;
225 		ILNCOUNT(inp->i_parent)--;
226 		inp->i_dotdot = inp->i_parent;
227 		(void)changeino(inp->i_number, "..", inp->i_parent);
228 	}
229 	info_fn = NULL;
230 	/*
231 	 * Create a list of children for each directory.
232 	 */
233 	inpend = &inpsort[inplast];
234 	for (inpp = inpsort; inpp < inpend; inpp++) {
235 		inp = *inpp;
236 		if (inp->i_parent == 0 ||
237 		    inp->i_number == ROOTINO)
238 			continue;
239 		pinp = getinoinfo(inp->i_parent);
240 		inp->i_sibling = pinp->i_child;
241 		pinp->i_child = inp;
242 	}
243 	/*
244 	 * Mark all the directories that can be found from the root.
245 	 */
246 	propagate(ROOTINO);
247 }
248 
249 static int
250 pass2check(struct inodesc *idesc)
251 {
252 	struct direct *dirp = idesc->id_dirp;
253 	struct inoinfo *inp;
254 	int n, entrysize, ret = 0;
255 	union dinode *dp;
256 	char *errmsg;
257 	struct direct proto;
258 	char namebuf[PATH_MAX + 1];
259 	char pathbuf[PATH_MAX + 1];
260 
261 	/*
262 	 * check for "."
263 	 */
264 	if (idesc->id_entryno != 0)
265 		goto chk1;
266 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
267 		if (dirp->d_ino != idesc->id_number) {
268 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
269 			dirp->d_ino = idesc->id_number;
270 			if (reply("FIX") == 1)
271 				ret |= ALTERED;
272 		}
273 		if (dirp->d_type != DT_DIR) {
274 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
275 			dirp->d_type = DT_DIR;
276 			if (reply("FIX") == 1)
277 				ret |= ALTERED;
278 		}
279 		goto chk1;
280 	}
281 	direrror(idesc->id_number, "MISSING '.'");
282 	proto.d_ino = idesc->id_number;
283 	proto.d_type = DT_DIR;
284 	proto.d_namlen = 1;
285 	(void)strlcpy(proto.d_name, ".", sizeof proto.d_name);
286 	entrysize = DIRSIZ(0, &proto);
287 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
288 		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
289 			dirp->d_name);
290 	} else if (dirp->d_reclen < entrysize) {
291 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
292 	} else if (dirp->d_reclen < 2 * entrysize) {
293 		proto.d_reclen = dirp->d_reclen;
294 		memcpy(dirp, &proto, (size_t)entrysize);
295 		if (reply("FIX") == 1)
296 			ret |= ALTERED;
297 	} else {
298 		n = dirp->d_reclen - entrysize;
299 		proto.d_reclen = entrysize;
300 		memcpy(dirp, &proto, (size_t)entrysize);
301 		idesc->id_entryno++;
302 		ILNCOUNT(dirp->d_ino)--;
303 		dirp = (struct direct *)((char *)(dirp) + entrysize);
304 		memset(dirp, 0, (size_t)n);
305 		dirp->d_reclen = n;
306 		if (reply("FIX") == 1)
307 			ret |= ALTERED;
308 	}
309 chk1:
310 	if (idesc->id_entryno > 1)
311 		goto chk2;
312 	inp = getinoinfo(idesc->id_number);
313 	proto.d_ino = inp->i_parent;
314 	proto.d_type = DT_DIR;
315 	proto.d_namlen = 2;
316 	(void)strlcpy(proto.d_name, "..", sizeof proto.d_name);
317 	entrysize = DIRSIZ(0, &proto);
318 	if (idesc->id_entryno == 0) {
319 		n = DIRSIZ(0, dirp);
320 		if (dirp->d_reclen < n + entrysize)
321 			goto chk2;
322 		proto.d_reclen = dirp->d_reclen - n;
323 		dirp->d_reclen = n;
324 		idesc->id_entryno++;
325 		ILNCOUNT(dirp->d_ino)--;
326 		dirp = (struct direct *)((char *)(dirp) + n);
327 		memset(dirp, 0, (size_t)proto.d_reclen);
328 		dirp->d_reclen = proto.d_reclen;
329 	}
330 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
331 		inp->i_dotdot = dirp->d_ino;
332 		if (dirp->d_type != DT_DIR) {
333 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
334 			dirp->d_type = DT_DIR;
335 			if (reply("FIX") == 1)
336 				ret |= ALTERED;
337 		}
338 		goto chk2;
339 	}
340 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
341 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
342 		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
343 			dirp->d_name);
344 		inp->i_dotdot = -1;
345 	} else if (dirp->d_reclen < entrysize) {
346 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
347 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
348 		inp->i_dotdot = -1;
349 	} else if (inp->i_parent != 0) {
350 		/*
351 		 * We know the parent, so fix now.
352 		 */
353 		inp->i_dotdot = inp->i_parent;
354 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
355 		proto.d_reclen = dirp->d_reclen;
356 		memcpy(dirp, &proto, (size_t)entrysize);
357 		if (reply("FIX") == 1)
358 			ret |= ALTERED;
359 	}
360 	idesc->id_entryno++;
361 	if (dirp->d_ino != 0)
362 		ILNCOUNT(dirp->d_ino)--;
363 	return (ret|KEEPON);
364 chk2:
365 	if (dirp->d_ino == 0)
366 		return (ret|KEEPON);
367 	if (dirp->d_namlen <= 2 &&
368 	    dirp->d_name[0] == '.' &&
369 	    idesc->id_entryno >= 2) {
370 		if (dirp->d_namlen == 1) {
371 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
372 			dirp->d_ino = 0;
373 			if (reply("FIX") == 1)
374 				ret |= ALTERED;
375 			return (KEEPON | ret);
376 		}
377 		if (dirp->d_name[1] == '.') {
378 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
379 			dirp->d_ino = 0;
380 			if (reply("FIX") == 1)
381 				ret |= ALTERED;
382 			return (KEEPON | ret);
383 		}
384 	}
385 	idesc->id_entryno++;
386 	n = 0;
387 	if (dirp->d_ino > maxino) {
388 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
389 		n = reply("REMOVE");
390 	} else {
391 again:
392 		switch (GET_ISTATE(dirp->d_ino)) {
393 		case USTATE:
394 			if (idesc->id_entryno <= 2)
395 				break;
396 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
397 			n = reply("REMOVE");
398 			break;
399 
400 		case DCLEAR:
401 		case FCLEAR:
402 			if (idesc->id_entryno <= 2)
403 				break;
404 			if (GET_ISTATE(dirp->d_ino) == FCLEAR)
405 				errmsg = "DUP/BAD";
406 			else if (!preen && !usedsoftdep)
407 				errmsg = "ZERO LENGTH DIRECTORY";
408 			else {
409 				n = 1;
410 				break;
411 			}
412 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
413 			if ((n = reply("REMOVE")) == 1)
414 				break;
415 			dp = ginode(dirp->d_ino);
416 			SET_ISTATE(dirp->d_ino, (DIP(dp, di_mode) & IFMT) ==
417 			    IFDIR ? DSTATE : FSTATE);
418 			ILNCOUNT(dirp->d_ino) = DIP(dp, di_nlink);
419 			goto again;
420 
421 		case DSTATE:
422 		case DFOUND:
423 			inp = getinoinfo(dirp->d_ino);
424 			if (inp->i_parent != 0 && idesc->id_entryno > 2) {
425 				getpathname(pathbuf, sizeof pathbuf,
426 				    idesc->id_number, idesc->id_number);
427 				getpathname(namebuf, sizeof namebuf,
428 				    dirp->d_ino, dirp->d_ino);
429 				pwarn("%s %s %s\n", pathbuf,
430 				    "IS AN EXTRANEOUS HARD LINK TO DIRECTORY",
431 				    namebuf);
432 				if (preen) {
433 					printf (" (REMOVED)\n");
434 					n = 1;
435 					break;
436 				}
437 				if ((n = reply("REMOVE")) == 1)
438 					break;
439 			}
440 			if (idesc->id_entryno > 2)
441 				inp->i_parent = idesc->id_number;
442 			/* FALLTHROUGH */
443 
444 		case FSTATE:
445 			if (dirp->d_type != GET_ITYPE(dirp->d_ino)) {
446 				fileerror(idesc->id_number, dirp->d_ino,
447 				    "BAD TYPE VALUE");
448 				dirp->d_type = GET_ITYPE(dirp->d_ino);
449 				if (reply("FIX") == 1)
450 					ret |= ALTERED;
451 			}
452 			ILNCOUNT(dirp->d_ino)--;
453 			break;
454 
455 		default:
456 			errexit("BAD STATE %d FOR INODE I=%llu\n",
457 			    GET_ISTATE(dirp->d_ino),
458 			    (unsigned long long)dirp->d_ino);
459 		}
460 	}
461 	if (n == 0)
462 		return (ret|KEEPON);
463 	dirp->d_ino = 0;
464 	return (ret|KEEPON|ALTERED);
465 }
466 
467 /*
468  * Routine to sort disk blocks.
469  */
470 static int
471 blksort(const void *inpp1, const void *inpp2)
472 {
473 	daddr_t d;
474 
475 	d = (* (struct inoinfo **) inpp1)->i_blks[0] -
476 	    (* (struct inoinfo **) inpp2)->i_blks[0];
477 	if (d < 0)
478 		return (-1);
479 	else if (d > 0)
480 		return (1);
481 	else
482 		return (0);
483 }
484