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