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