1/*	$NetBSD: pass2.c,v 1.47 2011/06/09 19:57:52 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/cdefs.h>
33#ifndef lint
34#if 0
35static char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
36#else
37__RCSID("$NetBSD: pass2.c,v 1.47 2011/06/09 19:57:52 christos Exp $");
38#endif
39#endif /* not lint */
40
41#include <sys/param.h>
42#include <sys/time.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 <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52
53#include "fsck.h"
54#include "fsutil.h"
55#include "extern.h"
56#include "exitvalues.h"
57
58#define MINDIRSIZE	(sizeof (struct dirtemplate))
59
60static int blksort(const void *, const void *);
61static int pass2check(struct inodesc *);
62
63void
64pass2(void)
65{
66	union dinode *dp;
67	struct inoinfo **inpp, *inp, *pinp;
68	struct inoinfo **inpend;
69	struct inostat *rinfo, *info;
70	struct inodesc curino;
71	union dinode dino;
72	int i, maxblk;
73	unsigned ii;
74	char pathbuf[MAXPATHLEN + 1];
75
76	rinfo = inoinfo(ROOTINO);
77	switch (rinfo->ino_state) {
78
79	case USTATE:
80		pfatal("ROOT INODE UNALLOCATED");
81		if (reply("ALLOCATE") == 0) {
82			markclean = 0;
83			ckfini(1);
84			exit(FSCK_EXIT_CHECK_FAILED);
85		}
86		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
87			errexit("CANNOT ALLOCATE ROOT INODE");
88		break;
89
90	case DCLEAR:
91		pfatal("DUPS/BAD IN ROOT INODE");
92		if (reply("REALLOCATE")) {
93			freeino(ROOTINO);
94			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
95				errexit("CANNOT ALLOCATE ROOT INODE");
96			break;
97		}
98		markclean = 0;
99		if (reply("CONTINUE") == 0) {
100			ckfini(1);
101			exit(FSCK_EXIT_CHECK_FAILED);
102		}
103		break;
104
105	case FSTATE:
106	case FCLEAR:
107		pfatal("ROOT INODE NOT DIRECTORY");
108		if (reply("REALLOCATE")) {
109			freeino(ROOTINO);
110			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
111				errexit("CANNOT ALLOCATE ROOT INODE");
112			break;
113		}
114		if (reply("FIX") == 0) {
115			markclean = 0;
116			ckfini(1);
117			exit(FSCK_EXIT_CHECK_FAILED);
118		}
119		dp = ginode(ROOTINO);
120		DIP_SET(dp, mode,
121		    iswap16((iswap16(DIP(dp, mode)) & ~IFMT) | IFDIR));
122		inodirty();
123		break;
124
125	case DSTATE:
126		break;
127
128	default:
129		errexit("BAD STATE %d FOR ROOT INODE", rinfo->ino_state);
130	}
131	if (newinofmt) {
132		info = inoinfo(WINO);
133		info->ino_state = FSTATE;
134		info->ino_type = DT_WHT;
135	}
136	/*
137	 * Sort the directory list into disk block order.
138	 */
139	qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
140	/*
141	 * Check the integrity of each directory.
142	 */
143	memset(&curino, 0, sizeof(struct inodesc));
144	curino.id_type = DATA;
145	curino.id_func = pass2check;
146	inpend = &inpsort[inplast];
147	for (inpp = inpsort; inpp < inpend; inpp++) {
148		if (got_siginfo) {
149			fprintf(stderr,
150			    "%s: phase 2: dir %ld of %d (%d%%)\n", cdevname(),
151			    (long)(inpp - inpsort), (int)inplast,
152			    (int)((inpp - inpsort) * 100 / inplast));
153			got_siginfo = 0;
154		}
155#ifdef PROGRESS
156		progress_bar(cdevname(), preen ? NULL : "phase 2",
157			    (inpp - inpsort), inplast);
158#endif /* PROGRESS */
159		inp = *inpp;
160		if (inp->i_isize == 0)
161			continue;
162		if (inp->i_isize < MINDIRSIZE) {
163			direrror(inp->i_number, "DIRECTORY TOO SHORT");
164			inp->i_isize = roundup(MINDIRSIZE, dirblksiz);
165			if (reply("FIX") == 1) {
166				dp = ginode(inp->i_number);
167				DIP_SET(dp, size, iswap64(inp->i_isize));
168				inodirty();
169			} else
170				markclean = 0;
171		} else if ((inp->i_isize & (dirblksiz - 1)) != 0) {
172			getpathname(pathbuf, sizeof(pathbuf), inp->i_number,
173			    inp->i_number);
174			if (usedsoftdep)
175				pfatal("%s %s: LENGTH %lld NOT MULTIPLE OF %d",
176					"DIRECTORY", pathbuf,
177					(long long)inp->i_isize, dirblksiz);
178			else
179				pwarn("%s %s: LENGTH %lld NOT MULTIPLE OF %d",
180					"DIRECTORY", pathbuf,
181					(long long)inp->i_isize, dirblksiz);
182			if (preen)
183				printf(" (ADJUSTED)\n");
184			inp->i_isize = roundup(inp->i_isize, dirblksiz);
185			if (preen || reply("ADJUST") == 1) {
186				dp = ginode(inp->i_number);
187				DIP_SET(dp, size, iswap64(inp->i_isize));
188				inodirty();
189			} else
190				markclean = 0;
191		}
192		memset(&dino, 0, sizeof dino);
193		dp = &dino;
194		if (!is_ufs2) {
195			dp->dp1.di_mode = iswap16(IFDIR);
196			dp->dp1.di_size = iswap64(inp->i_isize);
197			maxblk = inp->i_numblks < NDADDR ? inp->i_numblks :
198			    NDADDR;
199			for (i = 0; i < maxblk; i++)
200				dp->dp1.di_db[i] = inp->i_blks[i];
201			if (inp->i_numblks > NDADDR) {
202				for (i = 0; i < NIADDR; i++)
203					dp->dp1.di_ib[i] =
204					    inp->i_blks[NDADDR + i];
205			}
206		} else {
207			dp->dp2.di_mode = iswap16(IFDIR);
208			dp->dp2.di_size = iswap64(inp->i_isize);
209			maxblk = inp->i_numblks < NDADDR ? inp->i_numblks :
210			    NDADDR;
211			for (i = 0; i < maxblk; i++)
212				dp->dp2.di_db[i] = inp->i_blks[i];
213			if (inp->i_numblks > NDADDR) {
214				for (i = 0; i < NIADDR; i++)
215					dp->dp2.di_ib[i] =
216					    inp->i_blks[NDADDR + i];
217			}
218		}
219		curino.id_number = inp->i_number;
220		curino.id_parent = inp->i_parent;
221		curino.id_uid = iswap32(DIP(dp, uid));
222		curino.id_gid = iswap32(DIP(dp, gid));
223		(void)ckinode(&dino, &curino);
224	}
225
226	/*
227	 * Byte swapping in directory entries, if needed, has been done.
228	 * Now rescan dirs for pass2check()
229	 */
230	if (do_dirswap) {
231		do_dirswap = 0;
232		for (inpp = inpsort; inpp < inpend; inpp++) {
233			inp = *inpp;
234			if (inp->i_isize == 0)
235				continue;
236			memset(&dino, 0, sizeof dino);
237			if (!is_ufs2) {
238				dino.dp1.di_mode = iswap16(IFDIR);
239				dino.dp1.di_size = iswap64(inp->i_isize);
240				for (ii = 0; ii < inp->i_numblks; ii++)
241					dino.dp1.di_db[ii] = inp->i_blks[ii];
242			} else {
243				dino.dp2.di_mode = iswap16(IFDIR);
244				dino.dp2.di_size = iswap64(inp->i_isize);
245				for (ii = 0; ii < inp->i_numblks; ii++)
246					dino.dp2.di_db[ii] = inp->i_blks[ii];
247			}
248			curino.id_number = inp->i_number;
249			curino.id_parent = inp->i_parent;
250			curino.id_uid = iswap32(DIP(&dino, uid));
251			curino.id_gid = iswap32(DIP(&dino, gid));
252			(void)ckinode(&dino, &curino);
253		}
254	}
255
256	/*
257	 * Now that the parents of all directories have been found,
258	 * make another pass to verify the value of `..'
259	 */
260	for (inpp = inpsort; inpp < inpend; inpp++) {
261		inp = *inpp;
262		if (inp->i_parent == 0 || inp->i_isize == 0)
263			continue;
264		if (inp->i_dotdot == inp->i_parent ||
265		    inp->i_dotdot == (ino_t)-1)
266			continue;
267		info = inoinfo(inp->i_parent);
268		if (inp->i_dotdot == 0) {
269			inp->i_dotdot = inp->i_parent;
270			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
271			if (reply("FIX") == 0) {
272				markclean = 0;
273				continue;
274			}
275			(void)makeentry(inp->i_number, inp->i_parent, "..");
276			info->ino_linkcnt--;
277			continue;
278		}
279		fileerror(inp->i_parent, inp->i_number,
280		    "BAD INODE NUMBER FOR '..'");
281		if (reply("FIX") == 0) {
282			markclean = 0;
283			continue;
284		}
285		inoinfo(inp->i_dotdot)->ino_linkcnt++;
286		info->ino_linkcnt--;
287		inp->i_dotdot = inp->i_parent;
288		(void)changeino(inp->i_number, "..", inp->i_parent);
289	}
290	/*
291	 * Create a list of children for each directory.
292	 */
293	inpend = &inpsort[inplast];
294	for (inpp = inpsort; inpp < inpend; inpp++) {
295		inp = *inpp;
296		info = inoinfo(inp->i_number);
297		inp->i_child = inp->i_sibling = 0;
298		if (info->ino_state == DFOUND)
299			info->ino_state = DSTATE;
300	}
301	for (inpp = inpsort; inpp < inpend; inpp++) {
302		inp = *inpp;
303		if (inp->i_parent == 0 ||
304		    inp->i_number == ROOTINO)
305			continue;
306		pinp = getinoinfo(inp->i_parent);
307		inp->i_sibling = pinp->i_child;
308		pinp->i_child = inp;
309	}
310	/*
311	 * Mark all the directories that can be found from the root.
312	 */
313	propagate(ROOTINO);
314
315#ifdef PROGRESS
316	if (!preen)
317		progress_done();
318#endif /* PROGRESS */
319}
320
321static int
322pass2check(struct inodesc *idesc)
323{
324	struct direct *dirp = idesc->id_dirp;
325	struct inoinfo *inp;
326	struct inostat *info;
327	int n, entrysize, ret = 0;
328	union dinode *dp;
329	const char *errmsg;
330	struct direct proto;
331	char namebuf[MAXPATHLEN + 1];
332	char pathbuf[MAXPATHLEN + 1];
333
334	/*
335	 * If converting, set directory entry type.
336	 */
337	if (!is_ufs2 && doinglevel2 && iswap32(dirp->d_ino) > 0 &&
338	    iswap32(dirp->d_ino) < maxino) {
339		dirp->d_type = inoinfo(iswap32(dirp->d_ino))->ino_type;
340		ret |= ALTERED;
341	}
342	/*
343	 * check for "."
344	 */
345	if (idesc->id_entryno != 0)
346		goto chk1;
347	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
348		if (iswap32(dirp->d_ino) != idesc->id_number) {
349			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
350			dirp->d_ino = iswap32(idesc->id_number);
351			if (reply("FIX") == 1)
352				ret |= ALTERED;
353			else
354				markclean = 0;
355		}
356		if (newinofmt && dirp->d_type != DT_DIR) {
357			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
358			dirp->d_type = DT_DIR;
359			if (reply("FIX") == 1)
360				ret |= ALTERED;
361			else
362				markclean = 0;
363		}
364		goto chk1;
365	}
366	direrror(idesc->id_number, "MISSING '.'");
367	proto.d_ino = iswap32(idesc->id_number);
368	if (newinofmt)
369		proto.d_type = DT_DIR;
370	else
371		proto.d_type = 0;
372	proto.d_namlen = 1;
373	(void)strlcpy(proto.d_name, ".", sizeof(proto.d_name));
374#	if BYTE_ORDER == LITTLE_ENDIAN
375		if (!newinofmt && !needswap) {
376#	else
377		if (!newinofmt && needswap) {
378#	endif
379			u_char tmp;
380
381			tmp = proto.d_type;
382			proto.d_type = proto.d_namlen;
383			proto.d_namlen = tmp;
384		}
385	entrysize = DIRSIZ(0, &proto, 0);
386	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
387		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
388			dirp->d_name);
389		markclean = 0;
390	} else if (iswap16(dirp->d_reclen) < entrysize) {
391		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
392		markclean = 0;
393	} else if (iswap16(dirp->d_reclen) < 2 * entrysize) {
394		proto.d_reclen = dirp->d_reclen;
395		memmove(dirp, &proto, (size_t)entrysize);
396		if (reply("FIX") == 1)
397			ret |= ALTERED;
398		else
399			markclean = 0;
400	} else {
401		n = iswap16(dirp->d_reclen) - entrysize;
402		proto.d_reclen = iswap16(entrysize);
403		memmove(dirp, &proto, (size_t)entrysize);
404		idesc->id_entryno++;
405		inoinfo(iswap32(dirp->d_ino))->ino_linkcnt--;
406		dirp = (struct direct *)((char *)(dirp) + entrysize);
407		memset(dirp, 0, (size_t)n);
408		dirp->d_reclen = iswap16(n);
409		if (reply("FIX") == 1)
410			ret |= ALTERED;
411		else
412			markclean = 0;
413	}
414chk1:
415	if (idesc->id_entryno > 1)
416		goto chk2;
417	inp = getinoinfo(idesc->id_number);
418	proto.d_ino = iswap32(inp->i_parent);
419	if (newinofmt)
420		proto.d_type = DT_DIR;
421	else
422		proto.d_type = 0;
423	proto.d_namlen = 2;
424	(void)strlcpy(proto.d_name, "..", sizeof(proto.d_name));
425#if BYTE_ORDER == LITTLE_ENDIAN
426	if (!newinofmt && !needswap) {
427#else
428	if (!newinofmt && needswap) {
429#endif
430		u_char tmp;
431
432		tmp = proto.d_type;
433		proto.d_type = proto.d_namlen;
434		proto.d_namlen = tmp;
435	}
436	entrysize = DIRSIZ(0, &proto, 0);
437	if (idesc->id_entryno == 0) {
438		n = DIRSIZ(0, dirp, 0);
439		if (iswap16(dirp->d_reclen) < n + entrysize)
440			goto chk2;
441		proto.d_reclen = iswap16(iswap16(dirp->d_reclen) - n);
442		dirp->d_reclen = iswap16(n);
443		idesc->id_entryno++;
444		inoinfo(iswap32(dirp->d_ino))->ino_linkcnt--;
445		dirp = (struct direct *)((char *)(dirp) + n);
446		memset(dirp, 0, (size_t)iswap16(proto.d_reclen));
447		dirp->d_reclen = proto.d_reclen;
448	}
449	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
450		inp->i_dotdot = iswap32(dirp->d_ino);
451		if (newinofmt && dirp->d_type != DT_DIR) {
452			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
453			dirp->d_type = DT_DIR;
454			if (reply("FIX") == 1)
455				ret |= ALTERED;
456			else
457				markclean = 0;
458		}
459		goto chk2;
460	}
461	if (iswap32(dirp->d_ino) != 0 && strcmp(dirp->d_name, ".") != 0) {
462		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
463		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
464			dirp->d_name);
465		inp->i_dotdot = (ino_t)-1;
466		markclean = 0;
467	} else if (iswap16(dirp->d_reclen) < entrysize) {
468		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
469		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
470		inp->i_dotdot = (ino_t)-1;
471		markclean = 0;
472	} else if (inp->i_parent != 0) {
473		/*
474		 * We know the parent, so fix now.
475		 */
476		inp->i_dotdot = inp->i_parent;
477		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
478		proto.d_reclen = dirp->d_reclen;
479		memmove(dirp, &proto, (size_t)entrysize);
480		if (reply("FIX") == 1)
481			ret |= ALTERED;
482		else
483			markclean = 0;
484	}
485	idesc->id_entryno++;
486	if (dirp->d_ino != 0)
487		inoinfo(iswap32(dirp->d_ino))->ino_linkcnt--;
488	return (ret|KEEPON);
489chk2:
490	if (dirp->d_ino == 0)
491		return (ret|KEEPON);
492	if (dirp->d_namlen <= 2 &&
493	    dirp->d_name[0] == '.' &&
494	    idesc->id_entryno >= 2) {
495		if (dirp->d_namlen == 1) {
496			direrror(idesc->id_number, "EXTRA '.' ENTRY");
497			dirp->d_ino = 0;
498			if (reply("FIX") == 1)
499				ret |= ALTERED;
500			else
501				markclean = 0;
502			return (KEEPON | ret);
503		}
504		if (dirp->d_name[1] == '.') {
505			direrror(idesc->id_number, "EXTRA '..' ENTRY");
506			dirp->d_ino = 0;
507			if (reply("FIX") == 1)
508				ret |= ALTERED;
509			else
510				markclean = 0;
511			return (KEEPON | ret);
512		}
513	}
514	idesc->id_entryno++;
515	n = 0;
516	if (iswap32(dirp->d_ino) > maxino) {
517		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
518		n = reply("REMOVE");
519		if (n == 0)
520			markclean = 0;
521	} else if (newinofmt &&
522		   ((iswap32(dirp->d_ino) == WINO && dirp->d_type != DT_WHT) ||
523		    (iswap32(dirp->d_ino) != WINO && dirp->d_type == DT_WHT))) {
524		fileerror(idesc->id_number, iswap32(dirp->d_ino), "BAD WHITEOUT ENTRY");
525		dirp->d_ino = iswap32(WINO);
526		dirp->d_type = DT_WHT;
527		if (reply("FIX") == 1)
528			ret |= ALTERED;
529		else
530			markclean = 0;
531	} else {
532again:
533		info = inoinfo(iswap32(dirp->d_ino));
534		switch (info->ino_state) {
535		case USTATE:
536			if (idesc->id_entryno <= 2)
537				break;
538			fileerror(idesc->id_number, iswap32(dirp->d_ino), "UNALLOCATED");
539			n = reply("REMOVE");
540			if (n == 0)
541				markclean = 0;
542			break;
543
544		case DCLEAR:
545		case FCLEAR:
546			if (idesc->id_entryno <= 2)
547				break;
548			if (info->ino_state == FCLEAR)
549				errmsg = "DUP/BAD";
550			else if (!preen && !usedsoftdep)
551				errmsg = "ZERO LENGTH DIRECTORY";
552			else {
553				n = 1;
554				break;
555			}
556			fileerror(idesc->id_number, iswap32(dirp->d_ino), errmsg);
557			if ((n = reply("REMOVE")) == 1)
558				break;
559			dp = ginode(iswap32(dirp->d_ino));
560			info->ino_state =
561			    (iswap16(DIP(dp, mode)) & IFMT) == IFDIR ? DSTATE : FSTATE;
562			info->ino_linkcnt = iswap16(DIP(dp, nlink));
563			goto again;
564
565		case DSTATE:
566		case DFOUND:
567			inp = getinoinfo(iswap32(dirp->d_ino));
568			if (inp->i_parent != 0 && idesc->id_entryno > 2) {
569				getpathname(pathbuf, sizeof(pathbuf),
570				    idesc->id_number, idesc->id_number);
571				getpathname(namebuf, sizeof(namebuf),
572				    iswap32(dirp->d_ino), iswap32(dirp->d_ino));
573				pwarn("%s %s %s\n", pathbuf,
574				    "IS AN EXTRANEOUS HARD LINK TO DIRECTORY",
575				    namebuf);
576				if (preen)
577					printf(" (IGNORED)\n");
578				else if ((n = reply("REMOVE")) == 1)
579					break;
580			}
581			if (idesc->id_entryno > 2)
582				inp->i_parent = idesc->id_number;
583			/* fall through */
584
585		case FSTATE:
586			if (newinofmt && dirp->d_type != info->ino_type) {
587				fileerror(idesc->id_number, iswap32(dirp->d_ino),
588				    "BAD TYPE VALUE");
589				dirp->d_type = info->ino_type;
590				if (reply("FIX") == 1)
591					ret |= ALTERED;
592				else
593					markclean = 0;
594			}
595			info->ino_linkcnt--;
596			break;
597
598		default:
599			errexit("BAD STATE %d FOR INODE I=%d",
600			    info->ino_state, iswap32(dirp->d_ino));
601		}
602	}
603	if (n == 0)
604		return (ret|KEEPON);
605	dirp->d_ino = 0;
606	return (ret|KEEPON|ALTERED);
607}
608
609/*
610 * Routine to sort disk blocks.
611 */
612static int
613blksort(const void *arg1, const void *arg2)
614{
615
616	return ((*(const struct inoinfo *const *)arg1)->i_blks[0] -
617		(*(const struct inoinfo *const *)arg2)->i_blks[0]);
618}
619