# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= fsck ATTMK= $(LIBPROG) include ../../Makefile.fstype include ../Makefile.roll FSCKOBJS= main.o dir.o dup_avl.o inode.o pass1.o pass1b.o \ pass2.o \ pass3.o pass3b.o pass4.o pass5.o setup.o \ utilities.o FSCKSRCS= $(FSCKOBJS:%.o=%.c) UFSDIR= ../../../../uts/common/fs/ufs UFSOBJS= ufs_subr.o ufs_tables.o UFSSRCS= $(UFSOBJS:%.o=$(UFSDIR)/%.c) ROLLDIR= ../roll_log OBJS= $(FSCKOBJS) $(UFSOBJS) $(ROLLOBJS) $(FSLIB) SRCS= $(FSCKSRCS) $(UFSSRCS) $(ROLLSRCS) $(FSLIBSRC) CPPFLAGS += -D_LARGEFILE64_SOURCE -I../../ -I../../../../lib/libadm/inc LDLIBS += -lefi -lavl CERRWARN += -Wno-parentheses CERRWARN += -Wno-implicit-function-declaration CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(CTFMERGE_HOOK) $(POST_PROCESS) %.o: $(UFSDIR)/%.c $(COMPILE.c) $< $(CTFCONVERT_HOOK) clean: $(RM) $(FSCKOBJS) $(UFSOBJS) $(FSLIB) include ../../../../Makefile.xref XREFFLAGS= -f -x /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include "fsck.h" struct rc_queue { struct rc_queue *rc_next; fsck_ino_t rc_orphan; fsck_ino_t rc_parent; caddr_t rc_name; }; caddr_t lfname = "lost+found"; /* name to use for l+f dir */ static int lfmode = 01700; /* mode to use when creating l+f dir */ static struct dirtemplate emptydir = { 0, DIRBLKSIZ }; static struct dirtemplate dirhead = { 0, 12, 1, ".", 0, DIRBLKSIZ - 12, 2, ".." }; static void lftempname(char *, fsck_ino_t); static int do_reconnect(fsck_ino_t, fsck_ino_t, caddr_t); static caddr_t mkuniqname(caddr_t, caddr_t, fsck_ino_t, fsck_ino_t); static int chgino(struct inodesc *); static int dircheck(struct inodesc *, struct direct *); static int expanddir(fsck_ino_t, char *); static void freedir(fsck_ino_t, fsck_ino_t); static struct direct *fsck_readdir(struct inodesc *); static struct bufarea *getdirblk(daddr32_t, size_t); static int mkentry(struct inodesc *); static fsck_ino_t newdir(fsck_ino_t, fsck_ino_t, int, caddr_t); static fsck_ino_t reallocdir(fsck_ino_t, fsck_ino_t, int, caddr_t); /* * Propagate connected state through the tree. */ void propagate(void) { struct inoinfo **inpp, *inp; struct inoinfo **inpend; int change, inorphan; inpend = &inpsort[inplast]; do { change = 0; for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_parent == 0) continue; if (statemap[inp->i_parent] == DFOUND && INO_IS_DUNFOUND(inp->i_number)) { inorphan = statemap[inp->i_number] & INORPHAN; statemap[inp->i_number] = DFOUND | inorphan; change++; } } } while (change > 0); } /* * Scan each entry in a directory block. */ int dirscan(struct inodesc *idesc) { struct direct *dp; struct bufarea *bp; uint_t dsize, n; size_t blksiz; union { /* keep lint happy about alignment */ char dbuf[DIRBLKSIZ]; struct direct dir; } u; if (idesc->id_type != DATA) errexit("wrong type to dirscan %d\n", idesc->id_type); if (idesc->id_entryno == 0 && (idesc->id_filesize & (DIRBLKSIZ - 1)) != 0) idesc->id_filesize = roundup(idesc->id_filesize, DIRBLKSIZ); blksiz = idesc->id_numfrags * sblock.fs_fsize; if (chkrange(idesc->id_blkno, idesc->id_numfrags)) { idesc->id_filesize -= (offset_t)blksiz; return (SKIP); } idesc->id_loc = 0; for (dp = fsck_readdir(idesc); dp != NULL; dp = fsck_readdir(idesc)) { /* * If we were just passed a corrupt directory entry with * d_reclen > DIRBLKSIZ, we don't want to memmove() all over * our stack. This directory gets cleaned up later. */ dsize = MIN(dp->d_reclen, sizeof (u.dbuf)); (void) memmove((void *)u.dbuf, (void *)dp, (size_t)dsize); idesc->id_dirp = &u.dir; if ((n = (*idesc->id_func)(idesc)) & ALTERED) { /* * We can ignore errors from getdirblk() here, * as the block is still in memory thanks to * buffering and fsck_readdir(). If there was * an error reading it before, then all decisions * leading to getting us here were based on the * resulting zeros. As such, we have nothing * to worry about at this point. */ bp = getdirblk(idesc->id_blkno, blksiz); (void) memmove((void *)(bp->b_un.b_buf + idesc->id_loc - dsize), (void *)u.dbuf, (size_t)dsize); dirty(bp); sbdirty(); } if (n & STOP) return (n); } return (idesc->id_filesize > 0 ? KEEPON : STOP); } /* * Get current entry in a directory (and peek at the next entry). */ static struct direct * fsck_readdir(struct inodesc *idesc) { struct direct *dp, *ndp = 0; struct bufarea *bp; ushort_t size; /* of directory entry */ size_t blksiz; int dofixret; int salvaged; /* when to report SALVAGED in preen mode */ int origloc = idesc->id_loc; blksiz = idesc->id_numfrags * sblock.fs_fsize; /* * Sanity check id_filesize and id_loc fields. The latter * has to be within the block we're looking at, as well as * aligned to a four-byte boundary. The alignment is due to * a struct direct containing four-byte integers. It's * unfortunate that the four is a magic number, but there's * really no good way to derive it from the ufs header files. */ if ((idesc->id_filesize <= 0) || (idesc->id_loc >= blksiz) || ((idesc->id_loc & 3) != 0)) return (NULL); /* * We don't have to worry about holes in the directory's * block list, because that was checked for when the * inode was first encountered during pass1. We never * scan a directory until after we've vetted its block list. */ /* * We can ignore errors from getdirblk() here, as dircheck() * will reject any entries that would have been in the bad * sectors (fsck_bread() fills in zeros on failures). The main * reject keys are that d_reclen would be zero and/or that it * is less than the minimal size of a directory entry. Since * entries can't span sectors, there's no worry about having * a good beginning in one sector and the rest in the next, * where that second sector was unreadable and therefore * replaced with zeros. */ bp = getdirblk(idesc->id_blkno, blksiz); /* LINTED b_buf is aligned and id_loc was verified above */ dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc); /* * Check the current entry in the directory. */ if (dircheck(idesc, dp) == 0) { /* * If we are in here, then either the current directory * entry is bad or the next directory entry is bad. */ next_is_bad: /* * Find the amount of space left to the end of the * directory block for either directory entry. */ size = DIRBLKSIZ - (idesc->id_loc & (DIRBLKSIZ - 1)); /* * Advance to the end of the directory block. */ idesc->id_loc += size; idesc->id_filesize -= (offset_t)size; /* * Ask the question before we fix the in-core directory * block because dofix() may reuse the buffer. */ salvaged = (idesc->id_fix == DONTKNOW); dofixret = dofix(idesc, "DIRECTORY CORRUPTED"); /* * If there was an error reading the block, then that * same error can reasonably be expected to have occurred * when it was read previously. As such, the decision * to come here was based on the results of that partially- * zerod block, and so anything we change should be * based on it as well. Upshot: no need to check for * errors here. */ bp = getdirblk(idesc->id_blkno, blksiz); /* LINTED b_buf is aligned and id_loc/origloc was verified */ dp = (struct direct *)(bp->b_un.b_buf + origloc); /* * This is the current directory entry and since it is * corrupt we cannot trust the rest of the directory * block so change the current directory entry to * contain nothing and encompass the rest of the block. */ if (ndp == NULL) { dp->d_reclen = size; dp->d_ino = 0; dp->d_namlen = 0; dp->d_name[0] = '\0'; } /* * This is the next directory entry, i.e., we got here * via a "goto next_is_bad". That directory entry is * corrupt. However, the current directory entry is okay * so if we are in fix mode, just extend its record size * to encompass the rest of the block. */ else if (dofixret) { dp->d_reclen += size; } /* * If the user said to fix the directory corruption, then * mark the block as dirty. Otherwise, our "repairs" only * apply to the in-core copy so we don't hand back trash * to the caller. * * Note: It is possible that saying "no" to a change in * one part of the I/O buffer and "yes" to a later change * in the same I/O buffer may still flush the change to * which we said "no". This is the pathological case and * no fix is planned at this time. */ if (dofixret) { dirty(bp); if (preen && salvaged) (void) printf(" (SALVAGED)\n"); if (idesc->id_number == lfdir) lfdir = 0; } /* * dp points into bp, which will get re-used at some * arbitrary time in the future. We rely on the fact * that we're singled-threaded, and that we'll be done * with this directory entry by the time the next one * is needed. */ return (dp); } /* * The current directory entry checked out so advance past it. */ idesc->id_loc += dp->d_reclen; idesc->id_filesize -= (offset_t)dp->d_reclen; /* * If we are not at the directory block boundary, then peek * at the next directory entry and if it is bad we can add * its space to the current directory entry (compression). * Again, we sanity check the id_loc and id_filesize fields * since we modified them above. */ if ((idesc->id_loc & (DIRBLKSIZ - 1)) && /* not at start */ (idesc->id_loc < blksiz) && /* within block */ ((idesc->id_loc & 3) == 0) && /* properly aligned */ (idesc->id_filesize > 0)) { /* data follows */ /* LINTED b_buf is aligned and id_loc verified to be ok */ ndp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc); if (dircheck(idesc, ndp) == 0) goto next_is_bad; } /* * See comment above about dp pointing into bp. */ return (dp); } /* * Verify that a directory entry is valid. * This is a superset of the checks made in the kernel. */ static int dircheck(struct inodesc *idesc, struct direct *dp) { size_t size; char *cp; int spaceleft; /* * Recall that id_filesize is the number of bytes left to * process in the directory. We check id_filesize >= size * instead of id_filesize >= d_reclen because all that the * directory is actually required to contain is the entry * itself (and it's how the kernel does the allocation). * * We indirectly check for d_reclen going past the end of * the allocated space by comparing it against spaceleft. */ size = DIRSIZ(dp); spaceleft = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ); if (dp->d_ino < maxino && dp->d_reclen != 0 && (int)dp->d_reclen <= spaceleft && (dp->d_reclen & 0x3) == 0 && (int)dp->d_reclen >= size && idesc->id_filesize >= (offset_t)size && dp->d_namlen <= MAXNAMLEN) { if (dp->d_ino == 0) return (1); for (cp = dp->d_name, size = 0; size < (size_t)dp->d_namlen; size++, cp++) if ((*cp == '\0') || (*cp == '/')) goto bad; if (*cp == '\0') return (1); } bad: if (debug) { (void) printf("Bad dir in inode %d at lbn %d, loc %d:\n", idesc->id_number, idesc->id_lbn, idesc->id_loc); (void) printf(" ino %d reclen %d namlen %d name `%s'\n", dp->d_ino, dp->d_reclen, dp->d_namlen, dp->d_name); } return (0); } void adjust(struct inodesc *idesc, int lcnt) { struct dinode *dp; caddr_t flow; int saveiscorrupt; struct inodesc lcidesc; dp = ginode(idesc->id_number); if (dp->di_nlink == lcnt) { /* * If we have not hit any unresolved problems, are running * in preen mode, and are on a file system using logging, * then just toss any partially allocated files, as they are * an expected occurrence. */ if (!iscorrupt && preen && islog) { clri(idesc, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); return; } else { /* * The file system can be considered clean even if * a file is not linked up, but is cleared. In * other words, the kernel won't panic over it. * Hence, iscorrupt should not be set when * linkup is answered no, but clri is answered yes. * * If neither is answered yes, then we have a * non-panic-inducing known corruption that the * user needs to be reminded of when we exit. */ saveiscorrupt = iscorrupt; if (linkup(idesc->id_number, (fsck_ino_t)0, NULL) == 0) { iscorrupt = saveiscorrupt; clri(idesc, "UNREF", CLRI_QUIET, CLRI_NOP_OK); if (statemap[idesc->id_number] != USTATE) iscorrupt = 1; return; } dp = ginode(idesc->id_number); } lcnt = lncntp[idesc->id_number]; } /* * It doesn't happen often, but it's possible to get a true * excess of links (especially if a lot of directories got * orphaned and reattached to lost+found). Instead of wrapping * around, do something semi-useful (i.e., give progress towards * a less-broken filesystem) when this happens. */ LINK_RANGE(flow, dp->di_nlink, -lcnt); if (flow != NULL) { LINK_CLEAR(flow, idesc->id_number, dp->di_mode, &lcidesc); if (statemap[idesc->id_number] == USTATE) return; } dp = ginode(idesc->id_number); if (lcnt && dp->di_nlink != lcnt) { pwarn("LINK COUNT %s", file_id(idesc->id_number, dp->di_mode)); pinode(idesc->id_number); dp = ginode(idesc->id_number); (void) printf(" COUNT %d SHOULD BE %d", dp->di_nlink, dp->di_nlink - lcnt); /* * Even lost+found is subject to this, as whenever * we modify it, we update both the in-memory and * on-disk counts. Thus, they should still be in * sync. */ if (preen) { if (lcnt < 0) { (void) printf("\n"); if ((dp->di_mode & IFMT) == IFSHAD) pwarn("LINK COUNT INCREASING"); else pfatal("LINK COUNT INCREASING"); } } if (preen || reply("ADJUST") == 1) { dp->di_nlink -= lcnt; inodirty(); if (preen) (void) printf(" (ADJUSTED)\n"); } else if (((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR)) { /* * File counts can be off relatively harmlessly, * but a bad directory count can cause the * kernel to lose its mind. */ iscorrupt = 1; } } } static int mkentry(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; struct direct newent; int newlen, oldlen; newent.d_namlen = strlen(idesc->id_name); newlen = DIRSIZ(&newent); if (dirp->d_ino != 0) oldlen = DIRSIZ(dirp); else oldlen = 0; if ((int)dirp->d_reclen - oldlen < newlen) return (KEEPON); newent.d_reclen = dirp->d_reclen - (ushort_t)oldlen; dirp->d_reclen = (ushort_t)oldlen; /* LINTED dirp is aligned and DIRSIZ() forces oldlen to be aligned */ dirp = (struct direct *)(((char *)dirp) + oldlen); dirp->d_ino = idesc->id_parent; /* ino to be entered is in id_parent */ dirp->d_reclen = newent.d_reclen; dirp->d_namlen = newent.d_namlen; (void) memmove(dirp->d_name, idesc->id_name, (size_t)newent.d_namlen + 1); return (ALTERED|STOP); } static int chgino(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (memcmp(dirp->d_name, idesc->id_name, (size_t)dirp->d_namlen + 1) != 0) return (KEEPON); dirp->d_ino = idesc->id_parent; return (ALTERED|STOP); } int linkup(fsck_ino_t orphan, fsck_ino_t parentdir, caddr_t name) { int rval; struct dinode *dp; int lostdir; int lostshadow; fsck_ino_t oldlfdir; fsck_ino_t *intree; struct inodesc idesc; init_inodesc(&idesc); dp = ginode(orphan); lostdir = (((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR)); if (debug && lostdir && dp->di_nlink <= 0 && lncntp[orphan] == -1) (void) printf( "old fsck would have left inode %d for reclaim thread\n", orphan); lostshadow = (dp->di_mode & IFMT) == IFSHAD; pwarn("UNREF %s ", file_id(orphan, dp->di_mode)); pinode(orphan); if (lostshadow || (dp->di_size == 0 && dp->di_oeftflag == 0)) return (0); if (!preen && (reply("RECONNECT") == 0)) goto noconnect; if (lfdir == 0) { dp = ginode(UFSROOTINO); idesc.id_name = lfname; idesc.id_type = DATA; idesc.id_func = findino; idesc.id_number = UFSROOTINO; idesc.id_fix = DONTKNOW; if ((ckinode(dp, &idesc, CKI_TRAVERSE) & FOUND) != 0) { lfdir = idesc.id_parent; } else { pwarn("NO %s DIRECTORY", lfname); if (preen || reply("CREATE") == 1) { lfdir = newdir(UFSROOTINO, (fsck_ino_t)0, lfmode, lfname); if (lfdir != 0) { if (preen) (void) printf(" (CREATED)\n"); else (void) printf("\n"); statemap[lfdir] |= INFOUND; /* * XXX What if we allocate an inode * that's already been scanned? Then * we need to leave lnctnp[] alone. */ TRACK_LNCNTP(UFSROOTINO, lncntp[UFSROOTINO]++); } } } if (lfdir == 0) { pfatal("SORRY. CANNOT CREATE %s DIRECTORY\n", lfname); pwarn("Could not reconnect inode %d\n", orphan); goto noconnect; } else { /* * We searched for it via the namespace, so by * definition it's been found. We have to do this * because it is possible that we're called before * the full namespace mapping is complete (especially * from pass 1, if it encounters a corrupt directory * that has to be cleared). */ statemap[lfdir] |= INFOUND; } } dp = ginode(lfdir); if ((dp->di_mode & IFMT) != IFDIR) { pfatal("%s IS NOT A DIRECTORY", lfname); if (reply("REALLOCATE") == 0) { iscorrupt = 1; goto noconnect; } oldlfdir = lfdir; lfdir = reallocdir(UFSROOTINO, (fsck_ino_t)0, lfmode, lfname); if (lfdir == 0) { iscorrupt = 1; pfatal("SORRY. CANNOT CREATE %s DIRECTORY\n\n", lfname); goto noconnect; } inodirty(); statemap[lfdir] |= INFOUND; freeino(oldlfdir, TI_PARENT); } if (statemap[lfdir] != DFOUND) { /* * Not a consistency problem of the sort that'll * cause the kernel heartburn, so don't set iscorrupt. */ if (debug) (void) printf("lfdir %d is in state 0x%x\n", lfdir, (int)statemap[lfdir]); lfdir = 0; pfatal("SORRY. %s DIRECTORY DISAPPEARED\n\n", lfname); pwarn("Could not reconnect inode %d\n", orphan); goto noconnect; } rval = do_reconnect(orphan, parentdir, name); return (rval); /* * Leaving things unconnected is harmless as far as trying to * use the filesystem later, so don't set iscorrupt yet (it's * just lost blocks and inodes, after all). * * Lost directories get noted for reporting after all checks * are done - they may get cleared later. */ noconnect: if (lostdir) { intree = tsearch((void *)orphan, &limbo_dirs, ino_t_cmp); if (intree == NULL) errexit("linkup: out of memory"); } return (0); } /* * Connect an orphaned inode to lost+found. * * Returns non-zero for success, zero for failure. */ static int do_reconnect(fsck_ino_t orphan, fsck_ino_t parentdir, caddr_t name) { caddr_t flow_msg; struct dinode *dp; int lostdir; mode_t mode; fsck_ino_t *intree; struct inodesc idesc; dp = ginode(orphan); mode = dp->di_mode & IFMT; lostdir = (mode == IFDIR) || (mode == IFATTRDIR); name = mkuniqname(name, lfname, lfdir, orphan); if (name == NULL) goto noconnect; if (makeentry(lfdir, orphan, name) == 0) { pfatal("SORRY. NO SPACE IN %s DIRECTORY\n", lfname); pwarn("Could not reconnect inode %d\n", orphan); goto noconnect; } dp = ginode(orphan); LINK_RANGE(flow_msg, lncntp[orphan], -1); if (flow_msg != NULL) { LINK_CLEAR(flow_msg, orphan, dp->di_mode, &idesc); if (statemap[orphan] == USTATE) goto noconnect; } TRACK_LNCNTP(orphan, lncntp[orphan]--); /* * Make sure that anything we put into the normal namespace * looks like it belongs there. Attributes can only be in * attribute directories, not the normal directory lost+found. */ maybe_convert_attrdir_to_dir(orphan); if (lostdir) { /* * Can't be creating a duplicate entry with makeentry(), * because changeino() will succeed if ".." already * exists. */ if ((changeino(orphan, "..", lfdir) & ALTERED) == 0 && parentdir != (fsck_ino_t)-1) (void) makeentry(orphan, lfdir, ".."); /* * If we were half-detached, don't try to get * inode 0 later on. */ if (parentdir == 0) parentdir = -1; /* * Fix up link counts. * * XXX This section is getting pretty byzantine, espcially * when combined with changeino()/chgino()'s link manipulation. */ LFDIR_LINK_RANGE_RVAL(flow_msg, lncntp[lfdir], 1, &idesc, 0); TRACK_LNCNTP(lfdir, lncntp[lfdir]--); pwarn("DIR I=%lu CONNECTED. ", (long)orphan); reattached_dir = 1; if (parentdir != (fsck_ino_t)-1) { /* * Have to clear the parent's reference. Otherwise, * if it's an orphan, then we may clear this orphan * in pass 4 even though we've reconnected it. * * We already have the reference count * allowing for a parent link, so undo the * adjustment done above. Otherwise we come * out high by one. */ (void) printf("PARENT WAS I=%lu\n", (long)parentdir); (void) cleardirentry(parentdir, orphan); } if (!preen) (void) printf("\n"); } else if (preen) { (void) printf(" (RECONNECTED)\n"); } statemap[orphan] &= ~INDELAYD; return (1); /* * Leaving things unconnected is harmless as far as trying to * use the filesystem later, so don't set iscorrupt yet (it's * just lost blocks and inodes, after all). * * Lost directories get noted for reporting after all checks * are done - they may get cleared later. */ noconnect: if (lostdir) { intree = tsearch((void *)orphan, &limbo_dirs, ino_t_cmp); if (intree == NULL) errexit("linkup: out of memory"); } return (0); } /* * fix an entry in a directory. */ int changeino(fsck_ino_t dir, char *name, fsck_ino_t newnum) { struct inodesc idesc; init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = chgino; idesc.id_number = dir; idesc.id_fix = DONTKNOW; idesc.id_name = name; idesc.id_parent = newnum; /* new value for name */ return (ckinode(ginode(dir), &idesc, CKI_TRAVERSE)); } /* * make an entry in a directory */ int makeentry(fsck_ino_t parent, fsck_ino_t ino, char *name) { int repeat; struct dinode *dp; struct inoinfo *iip; struct inodesc idesc; char pathbuf[MAXPATHLEN + 1]; if (parent < UFSROOTINO || parent >= maxino || ino < UFSROOTINO || ino >= maxino) return (0); init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = mkentry; idesc.id_number = parent; idesc.id_parent = ino; /* this is the inode to enter */ idesc.id_fix = DONTKNOW; idesc.id_name = name; repeat = 0; again: dp = ginode(parent); if ((dp->di_size % DIRBLKSIZ) != 0) { dp->di_size = roundup(dp->di_size, DIRBLKSIZ); inodirty(); iip = getinoinfo(ino); if (iip != NULL) iip->i_isize = dp->di_size; } if ((ckinode(dp, &idesc, CKI_TRAVERSE) & ALTERED) != 0) { iip = getinoinfo(ino); if (iip != NULL) iip->i_isize = dp->di_size; return (1); } if (repeat == 0) { getpathname(pathbuf, parent, parent); if (expanddir(parent, pathbuf) == 0) return (0); repeat = 1; goto again; } return (0); } /* * Attempt to expand the size of a directory */ static int expanddir(fsck_ino_t ino, char *name) { struct bufarea *bpback, *bp[2]; daddr32_t nxtibn, nxtbn; daddr32_t newblk[2]; struct dinode *dp; char *cp; int bc, f; int n; int allocIndir; int frag2blks; int lffragsz = 0; int c = 0; int retval = 0; bp[0] = bp[1] = NULL; dp = ginode(ino); if (dp->di_size == 0) { goto bail; } nxtbn = lblkno(&sblock, dp->di_size - 1) + 1; /* * Check that none of the nominally in-use direct block * addresses for the directory are bogus. */ for (bc = 0; ((nxtbn > 0) && (bc < nxtbn) && (bc < NDADDR)); bc++) { if (dp->di_db[bc] == 0) { goto bail; } } /* * Determine our data block allocation needs. We always need to * allocate at least one data block. We may need a second, the * indirect block itself. */ allocIndir = 0; nxtibn = -1; n = 0; if (nxtbn <= NDADDR) { /* * Still in direct blocks. Check for the unlikely * case where the last block is a frag rather than * a full block. This would only happen if someone had * created a file in lost+found, and then that caused * the dynamic directory shrinking capabilities of ufs * to kick in. * * Note that we test nxtbn <= NDADDR, as it's the * next block (i.e., one greater than the current/ * actual block being examined). */ lffragsz = dp->di_size % sblock.fs_bsize; } if (nxtbn >= NDADDR && !lffragsz) { n = sblock.fs_bsize / sizeof (daddr32_t); nxtibn = nxtbn - NDADDR; /* * Only go one level of indirection */ if (nxtibn >= n) { goto bail; } /* * First indirect block means we need to pick up * the actual indirect pointer block as well. */ if (nxtibn == 0) allocIndir++; } /* * Allocate all the new blocks we need. */ if ((newblk[0] = allocblk(sblock.fs_frag)) == 0) { goto bail; } c++; if (allocIndir) { if ((newblk[1] = allocblk(sblock.fs_frag)) == 0) { goto bail; } c++; } /* * Take care of the block that will hold new directory entries. * This one is always allocated. */ bp[0] = getdirblk(newblk[0], (size_t)sblock.fs_bsize); if (bp[0]->b_errs) { goto bail; } if (lffragsz) { /* * Preserve the partially-populated existing directory. */ bpback = getdirblk(dp->di_db[nxtbn - 1], (size_t)dblksize(&sblock, dp, nxtbn - 1)); if (!bpback->b_errs) { (void) memmove(bp[0]->b_un.b_buf, bpback->b_un.b_buf, (size_t)lffragsz); } } /* * Initialize the new fragments. lffragsz is zero if this * is a completely-new block. */ for (cp = &(bp[0]->b_un.b_buf[lffragsz]); cp < &(bp[0]->b_un.b_buf[sblock.fs_bsize]); cp += DIRBLKSIZ) { (void) memmove(cp, (char *)&emptydir, sizeof (emptydir)); } dirty(bp[0]); /* * If we allocated the indirect block, zero it out. Otherwise * read it in if we're using one. */ if (allocIndir) { bp[1] = getdatablk(newblk[1], (size_t)sblock.fs_bsize); if (bp[1]->b_errs) { goto bail; } (void) memset(bp[1]->b_un.b_buf, 0, sblock.fs_bsize); dirty(bp[1]); } else if (nxtibn >= 0) { /* Check that the indirect block pointer looks okay */ if (dp->di_ib[0] == 0) { goto bail; } bp[1] = getdatablk(dp->di_ib[0], (size_t)sblock.fs_bsize); if (bp[1]->b_errs) { goto bail; } for (bc = 0; ((bc < nxtibn) && (bc < n)); bc++) { /* LINTED pointer cast alignment */ if (((daddr32_t *)bp[1]->b_un.b_buf)[bc] == 0) { goto bail; } } } /* * Since the filesystem's consistency isn't affected by * whether or not we actually do the expansion, iscorrupt * is left alone for any of the approval paths. */ pwarn("NO SPACE LEFT IN %s", name); if (!preen && (reply("EXPAND") == 0)) goto bail; /* * Now that everything we need is gathered up and the * necessary approvals acquired, we can make our provisional * changes permanent. */ if (lffragsz) { /* * We've saved the data from the old end fragment(s) in * our new block, so we can just swap the new one in. * Make sure the size reflects the expansion of the * final fragments/block. */ frag2blks = roundup(lffragsz, sblock.fs_fsize); freeblk(ino, dp->di_db[nxtbn - 1], frag2blks / sblock.fs_fsize); frag2blks = btodb(frag2blks); dp->di_size -= (u_offset_t)lffragsz; dp->di_blocks = dp->di_blocks - frag2blks; dp->di_db[nxtbn - 1] = newblk[0]; dp->di_size += (u_offset_t)sblock.fs_bsize; dp->di_blocks += btodb(sblock.fs_bsize); inodirty(); retval = 1; goto done; } /* * Full-block addition's much easier. It's just an append. */ dp->di_size += (u_offset_t)sblock.fs_bsize; dp->di_blocks += btodb(sblock.fs_bsize); if (allocIndir) { dp->di_blocks += btodb(sblock.fs_bsize); } inodirty(); if (nxtibn < 0) { /* * Still in direct blocks */ dp->di_db[nxtbn] = newblk[0]; } else { /* * Last indirect is always going to point at the * new directory buffer */ if (allocIndir) dp->di_ib[0] = newblk[1]; /* LINTED pointer case alignment */ ((daddr32_t *)bp[1]->b_un.b_buf)[nxtibn] = newblk[0]; dirty(bp[1]); } if (preen) (void) printf(" (EXPANDED)\n"); retval = 1; goto done; bail: for (f = 0; f < c; f++) freeblk(ino, newblk[f], sblock.fs_frag); done: /* * bp[0] is handled by the directory cache's auto-release. */ if (bp[1] != NULL) brelse(bp[1]); return (retval); } static fsck_ino_t newdir(fsck_ino_t parent, fsck_ino_t request, int mode, caddr_t name) { fsck_ino_t dino; char pname[BUFSIZ]; /* * This function creates a new directory and populates it with * "." and "..", then links to it as NAME in PARENT. */ dino = allocdir(parent, request, mode, 1); if (dino != 0) { getpathname(pname, parent, parent); name = mkuniqname(name, pname, parent, dino); /* * We don't touch numdirs, because it's just a cache of * what the filesystem claimed originally and is used * to calculate hash keys. */ if (makeentry(parent, dino, name) == 0) { freedir(dino, parent); dino = 0; } } return (dino); } /* * Replace whatever NAME refers to in PARENT with a new directory. * Note that if the old inode REQUEST is a directory, all of its * contents will be freed and reaped. */ static fsck_ino_t reallocdir(fsck_ino_t parent, fsck_ino_t request, int mode, caddr_t name) { int retval; fsck_ino_t newino; if ((request != 0) && (statemap[request] != USTATE)) freeino(request, TI_PARENT); newino = allocdir(parent, request, mode, 0); if (newino != 0) { retval = changeino(parent, name, newino); if ((retval & ALTERED) == 0) { /* * No change made, so name doesn't exist, so * unwind allocation rather than leak it. */ freedir(newino, parent); newino = 0; } } return (newino); } /* * allocate a new directory */ fsck_ino_t allocdir(fsck_ino_t parent, fsck_ino_t request, int mode, int update_parent) { fsck_ino_t ino; caddr_t cp; caddr_t flow; struct dinode *dp; struct bufarea *bp; struct inoinfo *inp; struct inodesc idesc; struct dirtemplate *dirp; ino = allocino(request, IFDIR|mode); if (ino == 0) return (0); dirp = &dirhead; dirp->dot_ino = ino; dirp->dotdot_ino = parent; dp = ginode(ino); bp = getdirblk(dp->di_db[0], (size_t)sblock.fs_fsize); if (bp->b_errs) { freeino(ino, TI_PARENT); return (0); } (void) memmove(bp->b_un.b_buf, (void *)dirp, sizeof (struct dirtemplate)); for (cp = &bp->b_un.b_buf[DIRBLKSIZ]; cp < &bp->b_un.b_buf[sblock.fs_fsize]; cp += DIRBLKSIZ) (void) memmove(cp, (void *)&emptydir, sizeof (emptydir)); dirty(bp); dp->di_nlink = 2; inodirty(); if (!inocached(ino)) { cacheino(dp, ino); } else { /* * re-using an old directory inode */ inp = getinoinfo(ino); if (inp == NULL) { if (debug) errexit("allocdir got NULL from getinoinfo " "for existing entry I=%d\n", ino); cacheino(dp, ino); } else { init_inoinfo(inp, dp, ino); inp->i_parent = parent; inp->i_dotdot = parent; } } /* * Short-circuit all the dancing around below if it's the * root inode. The net effect's the same. */ if (ino == UFSROOTINO) { TRACK_LNCNTP(ino, lncntp[ino] = dp->di_nlink); return (ino); } if (!update_parent) return (ino); /* * We never create attribute directories, which can have * non-directory parents. So, the parent of the directory * we're creating must itself be a directory. */ if (!INO_IS_DVALID(parent)) { freeino(ino, TI_PARENT); return (0); } /* * Make sure the parent can handle another link. * Since we might only update one version of the * count (disk versus in-memory), we have to check both. */ LINK_RANGE(flow, lncntp[parent], -1); if (flow == NULL) LINK_RANGE(flow, (int)dp->di_nlink, 1); if (flow != NULL) { LINK_CLEAR(flow, parent, dp->di_mode, &idesc); if (statemap[parent] == USTATE) { /* * No parent any more, so bail out. Callers * are expected to handle this possibility. * Since most just throw up their hands if * we return 0, this just happens to work. */ freeino(ino, TI_PARENT); return (0); } } /* * We've created a directory with two entries, "." and "..", * and a link count of two ("." and one from its parent). If * the parent's not been scanned yet, which means this inode * will get scanned later as well, then make our in-core count * match what we pushed out to disk. Similarly, update the * parent. On the other hand, if the parent's already been * looked at (statemap[ino] == DFOUND), the discrepancy * between lncntp[] and di_nlink will be noted later, with * appropriate reporting and propagation, in pass2. * * We're explicitly skipping where the parent was DZLINK or * DFOUND. If it has zero links, it can't be gotten to, so * we want a discrepancy set up that will be caught in pass2. * DFOUND was discussed above. * * Regarding the claim of a link from the parent: we've not * done anything to create such a link here. We depend on the * semantics of our callers attaching the inode we return to * an existing entry in the directory or creating the entry * themselves, but in either case, not modifying the link * count. * * Note that setting lncntp[ino] to zero means that both claimed * links have been ``found''. */ statemap[ino] = statemap[parent]; if (INO_IS_DVALID(parent)) { TRACK_LNCNTP(ino, lncntp[ino] = 0); TRACK_LNCNTP(parent, lncntp[parent]--); } dp = ginode(parent); dp->di_nlink++; inodirty(); return (ino); } /* * free a directory inode */ static void freedir(fsck_ino_t ino, fsck_ino_t parent) { struct inoinfo *iip; if (ino != parent) { /* * Make sure that the desired parent gets a link * count update from freeino()/truncino(). If * we can't look it up, then it's not really a * directory, so there's nothing to worry about. */ iip = getinoinfo(ino); if (iip != NULL) iip->i_parent = parent; } freeino(ino, TI_PARENT); } /* * generate a temporary name for use in the lost+found directory. */ static void lftempname(char *bufp, fsck_ino_t ino) { fsck_ino_t in; caddr_t cp; int namlen; cp = bufp + 2; for (in = maxino; in > 0; in /= 10) cp++; *--cp = '\0'; /* LINTED difference will not overflow an int */ namlen = cp - bufp; if ((namlen > BUFSIZ) || (namlen > MAXPATHLEN)) { errexit("buffer overflow in lftempname()\n"); } in = ino; while (cp > bufp) { *--cp = (in % 10) + '0'; in /= 10; } *cp = '#'; } /* * Get a directory block. * Insure that it is held until another is requested. * * Our callers are expected to check for errors and/or be * prepared to handle blocks of zeros in the middle of a * directory. */ static struct bufarea * getdirblk(daddr32_t blkno, size_t size) { if (pdirbp != 0) { brelse(pdirbp); } pdirbp = getdatablk(blkno, size); return (pdirbp); } /* * Create a unique name for INODE to be created in directory PARENT. * Use NAME if it is provided (non-NULL) and doesn't already exist. * Returning NULL indicates no unique name could be generated. * * If we were given a name, and it conflicts with an existing * entry, use our usual temp name instead. Without this check, * we could end up creating duplicate entries for multiple * orphaned directories in lost+found with the same name (but * different parents). Of course, our usual name might already * be in use as well, so be paranoid. * * We could do something like keep tacking something onto the * end of tempname until we come up with something that's not * in use, but that has liabilities as well. This is a * sufficiently rare case that it's not worth going that * overboard for. */ static caddr_t mkuniqname(caddr_t name, caddr_t pname, fsck_ino_t parent, fsck_ino_t inode) { fsck_ino_t oldino; struct dinode *dp; caddr_t flow_msg; struct inodesc idesc; static char tempname[BUFSIZ]; lftempname(tempname, inode); if ((name != NULL) && (lookup_named_ino(parent, name) != 0)) { name = NULL; } if (name == NULL) { /* * No name given, or it wasn't unique. */ name = tempname; if ((oldino = lookup_named_ino(parent, name)) != 0) { pfatal( "Name ``%s'' for inode %d already exists in %s \n", name, oldino, pname); if (reply("REMOVE OLD ENTRY") == 0) { if (parent == lfdir) pwarn( "Could not reconnect inode %d\n\n", inode); else pwarn( "Could not create entry for %d\n\n", inode); name = NULL; goto noconnect; } (void) changeino(parent, name, inode); LINK_RANGE(flow_msg, lncntp[oldino], 1); if (flow_msg != NULL) { /* * Do a best-effort, but if we're not * allowed to do the clear, the fs is * corrupt in any case, so just carry on. */ dp = ginode(oldino); LINK_CLEAR(flow_msg, oldino, dp->di_mode, &idesc); if (statemap[oldino] != USTATE) iscorrupt = 1; } else { TRACK_LNCNTP(oldino, lncntp[oldino]++); } } } noconnect: return (name); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Keep track of duplicate fragment references (elsewhere called * blocks for ancient historical reasons). * * The duplicates are kept in a binary tree to attempt to minimize * search times when checking the block lists of all active inodes * for multiple uses. This is opposed to using a simple linear list * that is traversed for every block, as is used in the traditional * fsck. It can be very time-expensive if there's more than just a * very few duplicates, and typically there are either none or lots. * * For each multiply-claimed fragment, we note all of the claiming * inodes and their corresponding logical block numbers. This allows * reporting exactly which parts of which files were damaged, which * provides at least a chance of recovering the bulk of the data on * a seriously-corrupted filesystem. */ #include #include #include #include #define _KERNEL #include /* for struct direct */ #undef _KERNEL #include #include "fsck.h" #define OFFSETOF(type, elt) ((size_t)(&((type *)NULL)->elt)) /* * For each physical fragment with multiple claimants, the specifics * of each claim are recorded. This means there are N+1 AVL trees in * use: one for each fragment's claimant table, plus one that orders * the fragments themselves. * * The table of fragments simply has the physical fragment number * (pfn) and has the root of the tree of the associated claimants. It * is keyed by the pfn and called dup_frags. * * The subsidiary trees list inodes and logical fragment number (lfn) * for each claimant. They are keyed first by inode number and then * by lfn. Both are needed, as it is possible for one inode to have * multiple claims on the same fragment. */ typedef struct claimant { fsck_ino_t cl_inode; daddr32_t cl_lfn; avl_node_t cl_avl; } claimant_t; typedef struct fragment { daddr32_t fr_pfn; avl_tree_t fr_claimants; avl_node_t fr_avl; } fragment_t; typedef struct reference { daddr32_t ref_lfn; daddr32_t ref_pfn; avl_node_t ref_avl; } reference_t; typedef struct inode_dup { fsck_ino_t id_ino; avl_tree_t id_fragments; avl_node_t id_avl; } inode_dup_t; static avl_tree_t dup_frags; static void free_invert_frags(avl_tree_t *); static void report_dup_lfn_pfn(daddr32_t, daddr32_t, daddr32_t, daddr32_t); static inode_dup_t *new_inode_dup(fsck_ino_t); static void invert_frags(avl_tree_t *, avl_tree_t *); static void report_inode_dups(inode_dup_t *); static int by_ino_cmp(const void *, const void *); static int by_lfn_cmp(const void *, const void *); static claimant_t *alloc_claimant(fsck_ino_t, daddr32_t); static fragment_t *alloc_dup(daddr32_t); static int claimant_cmp(const void *, const void *); static int fragment_cmp(const void *, const void *); static int decrement_claimant(fragment_t *, fsck_ino_t, daddr32_t); static int increment_claimant(fragment_t *, fsck_ino_t, daddr32_t); /* * Simple accessor function for the outside world so only we need to * see and interpret our data structures. */ int have_dups(void) { return (avl_numnodes(&dup_frags) > 0); } /* * Locates, creates, and deletes a record of a duplicate reference. * * For DB_INCR, returns true if the dup was added to the tree. * For DB_DECR, returns true if the dup was in the tree. */ int find_dup_ref(daddr32_t fragno, fsck_ino_t ino, daddr32_t lfn, int flags) { fragment_t key; fragment_t *dup; avl_index_t where; int added = 0; int removed = 0; if (avl_first(&dup_frags) == NULL) { if (flags & DB_CREATE) avl_create(&dup_frags, fragment_cmp, sizeof (fragment_t), OFFSETOF(fragment_t, fr_avl)); else return (0); } key.fr_pfn = fragno; dup = avl_find(&dup_frags, (void *)&key, &where); if ((dup == NULL) & (flags & DB_CREATE)) { dup = alloc_dup(fragno); avl_insert(&dup_frags, (void *)dup, where); } if (dup != NULL) { if (flags & DB_INCR) { if (debug) (void) printf( "adding claim by ino %d as lfn %d\n", ino, lfn); added = increment_claimant(dup, ino, lfn); } else if (flags & DB_DECR) { /* * Note that dup may be invalidated by this call. */ removed = decrement_claimant(dup, ino, lfn); if (debug) (void) printf( "check for claimant ino %d lfn %d returned %d\n", ino, lfn, removed); } } return (added || removed || (dup != NULL)); } /* * Dump the duplicates table in a relatively user-friendly form. * The idea is that the output can be useful when trying to manually * work out which block belongs to which of the claiming inodes. * * What we have is a tree of duplicates indexed by physical * fragment number. What we want to report is: * * Inode %d: * Logical Offset 0x%08llx, Physical Fragment %d * Logical Offsets 0x%08llx - 0x%08llx, Physical Fragments %d - %d * ... * Inode %d: * Logical Offsets 0x%08llx - 0x%08llx, Physical Fragments %d - %d * ... */ int report_dups(int quiet) { int overlaps; inode_dup_t *inode; fragment_t *dup; avl_tree_t inode_frags; overlaps = 0; ASSERT(have_dups()); /* * Figure out how many actual dups are still around. * This tells us whether or not we can mark the * filesystem clean. */ dup = avl_first(&dup_frags); while (dup != NULL) { if (avl_numnodes(&dup->fr_claimants) > 1) { overlaps++; break; } dup = AVL_NEXT(&dup_frags, dup); } /* * Now report on every object that still exists that * had *any* dups associated with it. */ if (!quiet) { (void) puts("\nSome blocks that were found to be in " "multiple files are still\nassigned to " "file(s).\nFragments sorted by inode and " "logical offsets:"); invert_frags(&dup_frags, &inode_frags); inode = avl_first(&inode_frags); while (inode != NULL) { report_inode_dups(inode); inode = AVL_NEXT(&inode_frags, inode); } (void) printf("\n"); free_invert_frags(&inode_frags); } return (overlaps); } static void report_inode_dups(inode_dup_t *inode) { reference_t *dup; daddr32_t first_lfn, last_lfn, first_pfn, last_pfn; (void) printf("Inode %d:\n", inode->id_ino); dup = avl_first(&inode->id_fragments); first_lfn = last_lfn = dup->ref_lfn; first_pfn = last_pfn = dup->ref_pfn; while ((dup = AVL_NEXT(&inode->id_fragments, dup)) != NULL) { if (((last_lfn + 1) != dup->ref_lfn) || ((last_pfn + 1) != dup->ref_pfn)) { report_dup_lfn_pfn(first_lfn, last_lfn, first_pfn, last_pfn); first_lfn = last_lfn = dup->ref_lfn; first_pfn = last_pfn = dup->ref_pfn; } } report_dup_lfn_pfn(first_lfn, last_lfn, first_pfn, last_pfn); } static void report_dup_lfn_pfn(daddr32_t first_lfn, daddr32_t last_lfn, daddr32_t first_pfn, daddr32_t last_pfn) { if ((first_lfn == last_lfn) && (first_pfn == last_pfn)) { (void) printf( " Logical Offset 0x%08llx Physical Fragment %d\n", (longlong_t)first_lfn * sblock.fs_fsize, first_pfn); } else { (void) printf( " Logical Offsets 0x%08llx - 0x%08llx, " "Physical Fragments %d - %d\n", (longlong_t)first_lfn * sblock.fs_fsize, (longlong_t)last_lfn * sblock.fs_fsize, first_pfn, last_pfn); } } /* * Given a tree of fragment_ts, each element of which has an integral * sub-tree of claimant_ts, produce a tree of inode_dup_ts, each element * of which has an integral sub-tree of reference_ts. */ static void invert_frags(avl_tree_t *source, avl_tree_t *target) { fragment_t *src_frag; claimant_t *src_claim; inode_dup_t *tgt_inode; inode_dup_t tgt_inode_key; reference_t *tgt_ref; reference_t tgt_ref_key; avl_index_t where; avl_create(target, by_ino_cmp, sizeof (inode_dup_t), OFFSETOF(inode_dup_t, id_avl)); src_frag = avl_first(source); while (src_frag != NULL) { src_claim = avl_first(&src_frag->fr_claimants); while (src_claim != NULL) { /* * Have we seen this inode before? */ tgt_inode_key.id_ino = src_claim->cl_inode; tgt_inode = avl_find(target, (void *)&tgt_inode_key, &where); if (tgt_inode == NULL) { /* * No, so set up a record for it. */ tgt_inode = new_inode_dup(src_claim->cl_inode); avl_insert(target, (void *)tgt_inode, where); } /* * Now, how about this logical fragment? In * theory, we should never see a duplicate, since * a given lfn only exists once for a given inode. * As such, we ignore duplicate hits. */ tgt_ref_key.ref_lfn = src_claim->cl_lfn; tgt_ref = avl_find(&tgt_inode->id_fragments, (void *)&tgt_ref_key, &where); if (tgt_ref == NULL) { /* * Haven't seen it, add it. */ tgt_ref = (reference_t *)malloc( sizeof (reference_t)); if (tgt_ref == NULL) errexit("Out of memory in " "invert_frags\n"); tgt_ref->ref_lfn = src_claim->cl_lfn; tgt_ref->ref_pfn = src_frag->fr_pfn; avl_insert(&tgt_inode->id_fragments, (void *)tgt_ref, where); } src_claim = AVL_NEXT(&src_frag->fr_claimants, src_claim); } src_frag = AVL_NEXT(source, src_frag); } } /* * Discard memory associated with the inverted fragments tree created * by report_dups() via invert_frags(). */ static void free_invert_frags(avl_tree_t *tree) { void *outer = NULL; /* traversal cookie */ void *inner; /* traversal cookie */ inode_dup_t *inode_dup; reference_t *ref_dup; while ((inode_dup = avl_destroy_nodes(tree, &outer)) != NULL) { inner = NULL; while ((ref_dup = avl_destroy_nodes(&inode_dup->id_fragments, &inner)) != NULL) { free((void *)ref_dup); } avl_destroy(&inode_dup->id_fragments); free((void *)inode_dup); } avl_destroy(tree); } /* * Discard all memory allocations associated with the current duplicates * table. */ void free_dup_state(void) { void *dup_cookie = NULL; void *claim_cookie; fragment_t *fragv; claimant_t *claimv; while ((fragv = avl_destroy_nodes(&dup_frags, &dup_cookie)) != NULL) { claim_cookie = NULL; while ((claimv = avl_destroy_nodes(&fragv->fr_claimants, &claim_cookie)) != NULL) { free((void *)claimv); } avl_destroy(&fragv->fr_claimants); free((void *)fragv); } avl_destroy(&dup_frags); } /* * If the given claimant has not been seen before, add it to DUP's * list of them. It's not fatal for the same PFN/INODE/LFN to get * added twice, because pass1b() will add the same dups that pass1() * did, plus one. */ static int increment_claimant(fragment_t *dup, fsck_ino_t ino, daddr32_t lfn) { avl_index_t where; claimant_t *claimant; claimant_t key; int added = 0; key.cl_inode = ino; key.cl_lfn = lfn; claimant = avl_find(&dup->fr_claimants, &key, &where); if (claimant == NULL) { if (debug) (void) printf("inserting claimant\n"); claimant = alloc_claimant(ino, lfn); avl_insert(&dup->fr_claimants, (void *)claimant, where); statemap[ino] |= INCLEAR; /* * If the inode is to be cleared and has zero links then remove * the zero link bit as it will be cleared anyway. If INZLINK * is being removed and it's a directory inode then add the * inode to the orphan directory list. */ if (statemap[ino] & INZLINK) { statemap[ino] &= ~INZLINK; if (statemap[ino] & DSTATE) { add_orphan_dir(ino); } } added = 1; } return (added); } /* * If the given claimant is on DUP's list, remove it. It is not * an error for the claimant to not be on the list. */ static int decrement_claimant(fragment_t *dup, fsck_ino_t ino, daddr32_t lfn) { avl_index_t where; claimant_t *claimant; claimant_t key; int busy = 0; key.cl_inode = ino; key.cl_lfn = lfn; claimant = avl_find(&dup->fr_claimants, &key, &where); if (claimant != NULL) { avl_remove(&dup->fr_claimants, claimant); if (avl_numnodes(&dup->fr_claimants) == 0) { avl_destroy(&dup->fr_claimants); avl_remove(&dup_frags, (void *)dup); free((void *)dup); } else { busy = 1; } } return (busy); } static claimant_t * alloc_claimant(fsck_ino_t inode, daddr32_t lfn) { claimant_t *new = (claimant_t *)malloc(sizeof (claimant_t)); if (new == NULL) errexit("Out of memory in alloc_claimant()\n"); new->cl_inode = inode; new->cl_lfn = lfn; return (new); } static fragment_t * alloc_dup(daddr32_t pfn) { fragment_t *new = (fragment_t *)malloc(sizeof (fragment_t)); if (new == NULL) errexit("Out of memory in alloc_dup()\n"); new->fr_pfn = pfn; avl_create(&new->fr_claimants, claimant_cmp, sizeof (fragment_t), OFFSETOF(claimant_t, cl_avl)); return (new); } /* * Compare two fragment_t instances for avl_find(). It requires a * return value of -1/0/1, so we can't just hand back left - right. */ static int fragment_cmp(const void *vlp, const void *vrp) { const fragment_t *lp = (const fragment_t *)vlp; const fragment_t *rp = (const fragment_t *)vrp; int cmp = lp->fr_pfn - rp->fr_pfn; if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } /* * Compare two claimant_t instances for avl_find(). It requires a * return value of -1/0/1, so we can't just hand back left - right. */ static int claimant_cmp(const void *vlp, const void *vrp) { const claimant_t *lp = (const claimant_t *)vlp; const claimant_t *rp = (const claimant_t *)vrp; int cmp; cmp = lp->cl_inode - rp->cl_inode; if (cmp == 0) { /* * lfn < 0 is a wildcard lfn match. */ if ((lp->cl_lfn >= 0) && (rp->cl_lfn >= 0)) cmp = lp->cl_lfn - rp->cl_lfn; } if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } static int by_ino_cmp(const void *vlp, const void *vrp) { const inode_dup_t *lp = (const inode_dup_t *)vlp; const inode_dup_t *rp = (const inode_dup_t *)vrp; int cmp; cmp = lp->id_ino - rp->id_ino; if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } static int by_lfn_cmp(const void *vlp, const void *vrp) { const reference_t *lp = (const reference_t *)vlp; const reference_t *rp = (const reference_t *)vrp; int cmp; cmp = lp->ref_lfn - rp->ref_lfn; if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } static inode_dup_t * new_inode_dup(fsck_ino_t inode) { inode_dup_t *new; new = (inode_dup_t *)malloc(sizeof (inode_dup_t)); if (new == NULL) errexit("Out of memory in new_inode_dup\n"); new->id_ino = inode; avl_create(&new->id_fragments, by_lfn_cmp, sizeof (reference_t), OFFSETOF(reference_t, ref_avl)); return (new); } /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _FSCK_FSCK_H #define _FSCK_FSCK_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #define MAXDUP 10 /* limit on dup blks (per inode) */ #define MAXBAD 10 /* limit on bad blks (per inode) */ #define MAXBUFSPACE 40*1024 /* initial space to allocate to buffers */ #define INOBUFSIZE 56*1024 /* size of buffer to read inodes in pass1 */ #ifndef BUFSIZ #define BUFSIZ MAXPATHLEN #endif /* * Inode states in statemap[]. */ #define USTATE 0x01 /* inode not allocated */ #define FSTATE 0x02 /* inode is file */ #define DSTATE 0x04 /* inode is directory */ #define SSTATE 0x08 /* inode is a shadow/acl */ #define STMASK 0x0f /* pick off the basic state/type */ /* flags OR'd into the above */ #define INZLINK 0x0010 /* inode has zero links */ #define INFOUND 0x0020 /* inode was found during descent */ #define INCLEAR 0x0040 /* inode is to be cleared */ #define INORPHAN 0x0080 /* inode is a known orphan (pass3 only) */ #define INDELAYD 0x0200 /* link count update delayed */ #define INMASK 0xfff0 /* pick off the modifiers */ #define FZLINK (FSTATE | INZLINK) #define DZLINK (DSTATE | INZLINK) #define SZLINK (SSTATE | INZLINK) #define DFOUND (DSTATE | INFOUND) #define DCLEAR (DSTATE | INCLEAR) #define FCLEAR (FSTATE | INCLEAR) #define SCLEAR (SSTATE | INCLEAR) /* * These tests depend on the state/type defines above not overlapping bits. * * DUNFOUND === (state == DSTATE || state == DZLINK) * INCLEAR is irrelevant to the determination of * connectedness, so it's not included in this test. * * DVALID === (state == DSTATE || state == DZLINK || state == DFOUND) */ #define S_IS_DUNFOUND(state) (((state) & (DSTATE | INZLINK)) \ == (state)) #define S_IS_DVALID(state) (((state) & (DSTATE | INZLINK | INFOUND | \ INORPHAN)) == (state)) #define S_IS_ZLINK(state) (((state) & INZLINK) != 0) #define INO_IS_DUNFOUND(ino) S_IS_DUNFOUND(statemap[ino]) #define INO_IS_DVALID(ino) S_IS_DVALID(statemap[ino]) /* * buffer cache structure. */ struct bufarea { struct bufarea *b_next; /* free list queue */ struct bufarea *b_prev; /* free list queue */ diskaddr_t b_bno; /* physical sector number */ int b_size; int b_errs; int b_flags; int b_cnt; /* reference cnt */ union { char *b_buf; /* buffer space */ daddr32_t *b_indir; /* indirect block */ struct fs *b_fs; /* super block */ struct cg *b_cg; /* cylinder group */ struct dinode *b_dinode; /* inode block */ } b_un; char b_dirty; }; #define B_INUSE 1 #define MINBUFS 5 /* minimum number of buffers required */ extern struct bufarea sblk; /* file system superblock */ extern struct bufarea cgblk; /* cylinder group blocks */ extern struct bufarea *pbp; /* pointer to inode data in buffer pool */ extern struct bufarea *pdirbp; /* pointer to directory data in buffer pool */ #define sbdirty() dirty(&sblk) #define cgdirty() dirty(&cgblk) #define sblock (*sblk.b_un.b_fs) #define cgrp (*cgblk.b_un.b_cg) /* * inodesc.id_fix values. See inode.c for a description of their usage. */ enum fixstate { DONTKNOW, NOFIX, FIX, IGNORE }; /* * Tells truncino() whether or not to attempt to update the parent * directory's link count. Also, TI_NODUP flags when we're discarding * fragments that are beyond the original end of the file, and so * should not be considered duplicate-claim candidates. */ #define TI_NOPARENT 0x0001 /* leave parent's di_nlink alone */ #define TI_PARENT 0x0002 /* update parent's di_nlink */ #define TI_NODUP 0x0004 /* not a dup candidate */ /* * Modes for ckinode() and ckinode_common(). * * CKI_TRAVERSE is the common case, and requests a traditional * traversal of blocks or directory entries. * * CKI_TRUNCATE indicates that we're truncating the file, and that any * block indices beyond the end of the target length should be cleared * after the callback has returned (i.e., this is a superset of * CKI_TRAVERSE). idesc->id_truncto is the first logical block number * to clear. If it is less than zero, then the traversal will be * equivalent to a simple CKI_TRAVERSE. */ enum cki_action { CKI_TRAVERSE, CKI_TRUNCATE }; /* * The general definition of an ino_t is an unsigned quantity. * However, the on-disk version is an int32_t, which is signed. * Since we really want to be able to detect wrapped-around * inode numbers and such, we'll use something that's compatible * with what's on disk since that's the only context that really * matters. If an int32_t is found not to be sufficiently large, * this will make it much easier to change later. * * Note that there is one unsigned inode field in the on-disk * inode, ic_oeftflag. Since all other inode fields are signed, * no legitimate inode number can be put into ic_oeftflag that * would overflow into the high bit. Essentially, it should * actually be declared as int32_t just like all the others, and * we're going to pretend that it was. * * None of the routines that we use in ufs_subr.c do anything with * inode numbers. If that changes, then great care will be needed * to deal with the differences in definition of ino_t and fsck_ino_t. * Lint is your friend. */ typedef int32_t fsck_ino_t; /* * See the full discussion of the interactions between struct inodesc * and ckinode() in inode.c */ struct inodesc { enum fixstate id_fix; /* policy on fixing errors */ int (*id_func)(struct inodesc *); /* function to be applied to blocks of inode */ fsck_ino_t id_number; /* inode number described */ fsck_ino_t id_parent; /* for DATA nodes, their parent */ /* also used for extra (*id_func) parameter */ /* and return values */ daddr32_t id_lbn; /* logical fragment number of current block */ daddr32_t id_blkno; /* physical fragment number being examined */ int id_numfrags; /* number of frags contained in block */ daddr32_t id_truncto; /* # blocks to truncate to, -1 for no trunc. */ offset_t id_filesize; /* for DATA nodes, the size of the directory */ uint_t id_loc; /* for DATA nodes, current location in dir */ daddr32_t id_entryno; /* for DATA nodes, current dir entry number */ daddr32_t id_firsthole; /* for DATA inode, logical block that is */ /* zero but shouldn't be, -1 for no holes */ struct direct *id_dirp; /* for DATA nodes, ptr to current entry */ caddr_t id_name; /* for DATA nodes, name to find or enter */ char id_type; /* type of descriptor, DATA or ADDR */ }; /* file types (0 is reserved for catching bugs) */ #define DATA 1 /* a directory */ #define ACL 2 /* an acl/shadow */ #define ADDR 3 /* anything but a directory or an acl/shadow */ /* * OR'd flags for find_dup_ref()'s mode argument */ #define DB_CREATE 0x01 /* if dup record found, make one */ #define DB_INCR 0x02 /* increment block's reference count */ #define DB_DECR 0x04 /* decrement block's reference count */ /* * Cache data structures */ struct inoinfo { struct inoinfo *i_nextlist; /* next inode/acl cache entry */ fsck_ino_t i_number; /* inode number of this entry */ fsck_ino_t i_parent; /* inode number of parent */ fsck_ino_t i_dotdot; /* inode number of .. */ fsck_ino_t i_extattr; /* inode of hidden attr dir */ offset_t i_isize; /* size of inode */ size_t i_blkssize; /* size of block array in bytes */ daddr32_t i_blks[1]; /* actually longer */ }; /* * Inode cache */ extern struct inoinfo **inphead, **inpsort; extern int64_t numdirs, listmax, inplast; /* * ACL cache */ extern struct inoinfo **aclphead, **aclpsort; extern int64_t numacls, aclmax, aclplast; /* * Tree of directories we haven't reconnected or cleared. Any * dir inode that linkup() fails on gets added, any that clri() * succeeds on gets removed. If there are any left at the end of * pass four, then we have a user-forced corrupt filesystem, and * need to set iscorrupt. * * Elements are fsck_ino_t instances (not pointers). */ extern void *limbo_dirs; /* * Number of directories we actually found in the filesystem, * as opposed to how many the superblock claims there are. */ extern fsck_ino_t countdirs; /* * shadowclients and shadowclientinfo are structures for keeping track of * shadow inodes that exist, and which regular inodes use them (i.e. are * their clients). */ struct shadowclients { fsck_ino_t *client; /* an array of inode numbers */ int nclients; /* how many inodes in the array are in use (valid) */ struct shadowclients *next; /* link to more client inode numbers */ }; struct shadowclientinfo { fsck_ino_t shadow; /* the shadow inode that this info is for */ int totalClients; /* how many inodes total refer to this */ struct shadowclients *clients; /* a linked list of wads of clients */ struct shadowclientinfo *next; /* link to the next shadow inode */ }; /* global pointer to this shadow/client information */ extern struct shadowclientinfo *shadowclientinfo; extern struct shadowclientinfo *attrclientinfo; /* * In ufs_inode.h ifdef _KERNEL, this is defined as `/@/'. However, * to avoid all sorts of potential confusion (you can't actually use * `foo/@/bar' to get to an attribute), we use something that doesn't * look quite so much like a simple pathname. */ #define XATTR_DIR_NAME " " /* * granularity -- how many client inodes do we make space for at a time * initialized in setup.c; */ extern int maxshadowclients; /* * Initialized global variables. */ extern caddr_t lfname; /* * Unitialized globals. */ extern char *devname; /* name of device being checked */ extern size_t dev_bsize; /* computed value of DEV_BSIZE */ extern int secsize; /* actual disk sector size */ extern char nflag; /* assume a no response */ extern char yflag; /* assume a yes response */ extern daddr32_t bflag; /* location of alternate super block */ extern int debug; /* output debugging info */ extern int rflag; /* check raw file systems */ extern int fflag; /* check regardless of clean flag (force) */ extern int mflag; /* sanity check only */ extern int verbose; /* be chatty */ extern char preen; /* just fix normal inconsistencies */ extern char mountedfs; /* checking mounted device */ extern int exitstat; /* exit status (see EX* defines below) */ extern char hotroot; /* checking root device */ extern char rerun; /* rerun fsck. Only used in non-preen mode */ extern int interrupted; /* 1 => exit EXSIGNAL on exit */ extern char havesb; /* superblock has been read */ extern int fsmodified; /* 1 => write done to file system */ extern int fsreadfd; /* file descriptor for reading file system */ extern int fswritefd; /* file descriptor for writing file system */ extern int iscorrupt; /* known to be corrupt/inconsistent */ /* -1 means mark clean so user can mount+fix */ extern int isdirty; /* 1 => write pending to file system */ extern int islog; /* logging file system */ extern int islogok; /* log is okay */ extern int errorlocked; /* set => mounted fs has been error-locked */ /* implies fflag "force check flag" */ extern char *elock_combuf; /* error lock comment buffer */ extern char *elock_mountp; /* mount point; used to unlock error-lock */ extern int pid; /* fsck's process id (put in lockfs comment) */ extern int mountfd; /* fd of mount point */ extern daddr32_t maxfsblock; /* number of blocks in the file system */ extern uint_t largefile_count; /* global largefile counter */ extern char *mount_point; /* if mounted, this is where */ extern char *blockmap; /* ptr to primary blk allocation map */ extern fsck_ino_t maxino; /* number of inodes in file system */ extern fsck_ino_t lastino; /* last inode in use */ extern ushort_t *statemap; /* ptr to inode state table */ extern short *lncntp; /* ptr to link count table */ extern fsck_ino_t lfdir; /* lost & found directory inode number */ extern int overflowed_lf; /* tried to wrap lost & found's link count */ extern int reattached_dir; /* reconnected at least one directory */ extern int broke_dir_link; /* broke at least one directory hardlink */ extern daddr32_t n_blks; /* number of blocks in use */ extern fsck_ino_t n_files; /* number of files in use */ #define clearinode(dp) { \ *(dp) = zino; \ } extern struct dinode zino; #define testbmap(blkno) isset(blockmap, blkno) #define setbmap(blkno) setbit(blockmap, blkno) #define clrbmap(blkno) clrbit(blockmap, blkno) #define STOP 0x01 #define SKIP 0x02 #define KEEPON 0x04 #define ALTERED 0x08 #define FOUND 0x10 /* * Support relatively easy debugging of lncntp[] updates. This can't * be a function, because of the (_op) step. Normally, we just do that. */ #define TRACK_LNCNTP(_ino, _op) (_op) /* * See if the net link count for an inode has gone outside * what can be represented on disk. Returning text as NULL * indicates no. * * Remember that link counts are effectively inverted, so * underflow and overflow are reversed as well. * * This check should be done before modifying the actual link * count. */ #define LINK_RANGE(text, current, offset) { \ int net = ((int)(current)) + ((int)(offset)); \ text = NULL; \ if (net > (MAXLINK)) \ text = "UNDERFLOW"; \ else if (net < -(MAXLINK)) \ text = "OVERFLOW"; \ } /* * If LINK_RANGE() indicated a problem, this is the boiler-plate * for dealing with it. Usage is: * * LINK_RANGE(text, current, offset); * if (text != NULL) { * LINK_CLEAR(text, ino, mode, idp); * if (statemap[ino] == USTATE) * ...inode was cleared... * } * * Note that clri() will set iscorrupt if the user elects not to * clear the problem inode, so the filesystem won't get reported * as clean when it shouldn't be. */ #define LINK_CLEAR(text, ino, mode, idp) { \ pwarn("%s LINK COUNT %s", file_id((ino), (mode)), (text)); \ pinode((ino)); \ pfatal(""); \ init_inodesc((idp)); \ (idp)->id_type = ADDR; \ (idp)->id_func = pass4check; \ (idp)->id_number = ino; \ (idp)->id_fix = DONTKNOW; \ clri((idp), (text), CLRI_QUIET, CLRI_NOP_CORRUPT); \ } /* * Used for checking link count under/overflow specifically on * the lost+found directory. If the user decides not to do the * clri(), then flag that we've hit this problem and refuse to do * the reconnect. */ #define LFDIR_LINK_RANGE_RVAL(text, current, offset, idp, rval) { \ LINK_RANGE(text, current, offset); \ if (text != NULL) { \ LINK_CLEAR(text, lfdir, IFDIR, idp); \ if (statemap[lfdir] == USTATE) { \ lfdir = 0; \ return (rval); \ } else { \ overflowed_lf++; \ } \ } \ } #define LFDIR_LINK_RANGE_NORVAL(text, current, offset, idp) { \ LINK_RANGE(text, current, offset); \ if (text != NULL) { \ LINK_CLEAR(text, lfdir, IFDIR, idp); \ if (statemap[lfdir] == USTATE) { \ lfdir = 0; \ return; \ } else { \ overflowed_lf++; \ } \ } \ } /* * Values for mounted() and mountedfs. */ #define M_NOMNT 0 /* filesystem is not mounted */ #define M_RO 1 /* filesystem is mounted read-only */ #define M_RW 2 /* filesystem is mounted read-write */ #define EXOKAY 0 /* file system is unmounted and ok */ #define EXBADPARM 1 /* bad parameter(s) given */ #define EXUMNTCHK 32 /* fsck -m: unmounted, needs checking */ #define EXMOUNTED 33 /* file system already mounted, not magic, */ /* or it is magic and mounted read/write */ #define EXNOSTAT 34 /* cannot stat device */ #define EXREBOOTNOW 35 /* modified root or something equally scary */ #define EXFNDERRS 36 /* uncorrectable errors, terminate normally */ #define EXSIGNAL 37 /* a signal was caught during processing */ #define EXERRFATAL 39 /* uncorrectable errors, exit immediately */ #define EXROOTOKAY 40 /* for root, same as 0 */ /* * Values for clri()'s `verbose' and `corrupting' arguments (third * and fourth, respectively). */ #define CLRI_QUIET 1 #define CLRI_VERBOSE 2 #define CLRI_NOP_OK 1 #define CLRI_NOP_CORRUPT 2 /* * Filesystems that are `magical' - if they exist in vfstab, * then they have to be mounted for the system to have gotten * far enough to be able to run fsck. Thus, don't get all * bent out of shape if we're asked to check it and it is mounted. * Actual initialization of the array is in main.c */ enum magic { MAGIC_NONE = 0, MAGIC_ROOT = 1, MAGIC_USR = 2, MAGIC_LIMIT = 3 }; extern char *magic_fs[]; /* * Paths needed by calcsb(). */ #define MKFS_PATH "/usr/lib/fs/ufs/mkfs" #define NEWFS_PATH "/usr/lib/fs/ufs/newfs" int acltypeok(struct dinode *); void add_orphan_dir(fsck_ino_t); void adjust(struct inodesc *, int); daddr32_t allocblk(int); fsck_ino_t allocdir(fsck_ino_t, fsck_ino_t, int, int); fsck_ino_t allocino(fsck_ino_t, int); void blkerror(fsck_ino_t, caddr_t, daddr32_t, daddr32_t); void brelse(struct bufarea *); void bufinit(void); void bwrite(int, caddr_t, diskaddr_t, int64_t); void cacheacl(struct dinode *, fsck_ino_t); void cacheino(struct dinode *, fsck_ino_t); void catch(int); void catchquit(int); caddr_t cg_sanity(struct cg *, int); void cgflush(void); int cgisdirty(void); int changeino(fsck_ino_t, caddr_t, fsck_ino_t); int check_mnttab(caddr_t, caddr_t, size_t); int check_vfstab(caddr_t, caddr_t, size_t); int chkrange(daddr32_t, int); void ckfini(void); int ckinode(struct dinode *, struct inodesc *, enum cki_action); void clearattrref(fsck_ino_t); int cleardirentry(fsck_ino_t, fsck_ino_t); void clearshadow(fsck_ino_t, struct shadowclientinfo **); void clri(struct inodesc *, caddr_t, int, int); void deshadow(struct shadowclientinfo *, void (*)(fsck_ino_t)); void direrror(fsck_ino_t, caddr_t, ...); int dirscan(struct inodesc *); void dirty(struct bufarea *); int do_errorlock(int); int dofix(struct inodesc *, caddr_t, ...); void examinelog(void (*)(daddr32_t)); void errexit(caddr_t, ...); void fileerror(fsck_ino_t, fsck_ino_t, caddr_t, ...); caddr_t file_id(fsck_ino_t, mode_t); int find_dup_ref(daddr32_t, fsck_ino_t, daddr32_t, int); int findino(struct inodesc *); int findname(struct inodesc *); void fix_cg(struct cg *, int); void flush(int, struct bufarea *); void free_dup_state(void); void freeblk(fsck_ino_t, daddr32_t, int); void freeino(fsck_ino_t, int); void freeinodebuf(void); int fsck_asprintf(caddr_t *, caddr_t, ...); int fsck_bread(int, caddr_t, diskaddr_t, size_t); int ftypeok(struct dinode *); struct bufarea *getblk(struct bufarea *, daddr32_t, size_t); struct bufarea *getdatablk(daddr32_t, size_t size); diskaddr_t getdisksize(caddr_t, int); struct inoinfo *getinoinfo(fsck_ino_t); struct dinode *getnextinode(fsck_ino_t); struct dinode *getnextrefresh(void); void getpathname(caddr_t, fsck_ino_t, fsck_ino_t); struct dinode *ginode(fsck_ino_t); caddr_t hasvfsopt(struct vfstab *, caddr_t); int have_dups(void); void init_inodesc(struct inodesc *); void init_inoinfo(struct inoinfo *, struct dinode *, fsck_ino_t); void initbarea(struct bufarea *); int ino_t_cmp(const void *, const void *); int inocached(fsck_ino_t); void inocleanup(void); void inodirty(void); int is_errorlocked(caddr_t); int linkup(fsck_ino_t, fsck_ino_t, caddr_t); int lookup_named_ino(fsck_ino_t, caddr_t); int makeentry(fsck_ino_t, fsck_ino_t, caddr_t); void maybe_convert_attrdir_to_dir(fsck_ino_t); int mounted(caddr_t, caddr_t, size_t); void pass1(void); void pass1b(void); int pass1check(struct inodesc *); void pass2(void); void pass3a(void); void pass3b(void); int pass3bcheck(struct inodesc *); void pass4(void); int pass4check(struct inodesc *); void pass5(void); void pfatal(caddr_t, ...); void pinode(fsck_ino_t); void printclean(void); void propagate(void); void pwarn(caddr_t, ...); caddr_t rawname(caddr_t); void registershadowclient(fsck_ino_t, fsck_ino_t, struct shadowclientinfo **); void remove_orphan_dir(fsck_ino_t); int reply(caddr_t, ...); int report_dups(int); void resetinodebuf(void); char *setup(caddr_t); void truncino(fsck_ino_t, offset_t, int); void unbufinit(void); caddr_t unrawname(caddr_t); void unregistershadow(fsck_ino_t, struct shadowclientinfo **); int updateclean(void); int writable(caddr_t); void write_altsb(int); /* * Functions from the kernel sources (ufs_subr.c, etc). */ extern void fragacct(struct fs *, int, int32_t *, int); #ifdef __cplusplus } #endif #endif /* _FSCK_FSCK_H */ #!/bin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # find_files "s.*"\ usr/src/uts/common/fs/ufs /* * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include "fsck.h" uint_t largefile_count = 0; fsck_ino_t lastino; struct bufarea cgblk; struct inoinfo **aclphead, **aclpsort; struct dinode zino; static int get_indir_offsets(int, daddr_t, int *, int *); static int clearanentry(struct inodesc *); static void pdinode(struct dinode *); static void inoflush(void); static void mark_delayed_inodes(fsck_ino_t, daddr32_t); static int iblock(struct inodesc *, int, u_offset_t, enum cki_action); static struct inoinfo *search_cache(struct inoinfo *, fsck_ino_t); static int ckinode_common(struct dinode *, struct inodesc *, enum cki_action); static int lookup_dotdot_ino(fsck_ino_t); /* * ckinode() essentially traverses the blocklist of the provided * inode. For each block either the caller-supplied callback (id_func * in the provided struct inodesc) or dirscan() is invoked. Which is * chosen is controlled by what type of traversal was requested * (id_type) - if it was for an ADDR or ACL, use the callback, * otherwise it is assumed to be DATA (i.e., a directory) whose * contents need to be scanned. * * Note that a directory inode can get passed in with a type of ADDR; * the type field is orthogonal to the IFMT value. This is so that * the file aspects (no duplicate blocks, etc) of a directory can be * verified just like is done for any other file, or the actual * contents can be scanned so that connectivity and such can be * investigated. * * The traversal is controlled by flags in the return value of * dirscan() or the callback. Five flags are defined, STOP, SKIP, * KEEPON, ALTERED, and FOUND. Their semantics are: * * STOP - no further processing of this inode is desired/possible/ * feasible/etc. This can mean that whatever the scan * was searching for was found, or a serious * inconsistency was encountered, or anything else * appropriate. * * SKIP - something that made it impossible to continue was * encountered, and the caller should go on to the next * inode. This is more for i/o failures than for * logical inconsistencies. Nothing actually looks for * this. * * KEEPON - no more blocks of this inode need to be scanned, but * nothing's wrong, so keep on going with the next * inode. It is similar to STOP, except that * ckinode()'s caller will typically advance to the next * inode for KEEPON, whereas it ceases scanning through * the inodes completely for STOP. * * ALTERED - a change was made to the inode. If the caller sees * this set, it should make sure to flush out the * changes. Note that any data blocks read in by the * function need to be marked dirty by it directly; * flushing of those will happen automatically later. * * FOUND - whatever was being searched for was located. * Typically combined with STOP to avoid wasting time * doing additional looking. * * During a traversal, some state needs to be carried around. At the * least, the callback functions need to know what inode they're * working on, which logical block, and whether or not fixing problems * when they're encountered is desired. Rather than try to guess what * else might be needed (and thus end up passing way more arguments * than is reasonable), all the possibilities have been bundled in * struct inodesc. About half of the fields are specific to directory * traversals, and the rest are pretty much generic to any traversal. * * The general fields are: * * id_fix What to do when an error is found. Generally, this * is set to DONTKNOW before a traversal. If a * problem is encountered, it is changed to either FIX * or NOFIX by the dofix() query function. If id_fix * has already been set to FIX when dofix() is called, then * it includes the ALTERED flag (see above) in its return * value; the net effect is that the inode's buffer * will get marked dirty and written to disk at some * point. If id_fix is DONTKNOW, then dofix() will * query the user. If it is NOFIX, then dofix() * essentially does nothing. A few routines set NOFIX * as the initial value, as they are performing a best- * effort informational task, rather than an actual * repair operation. * * id_func This is the function that will be called for every * logical block in the file (assuming id_type is not * DATA). The logical block may represent a hole, so * the callback needs to be prepared to handle that * case. Its return value is a combination of the flags * described above (SKIP, ALTERED, etc). * * id_number The inode number whose block list or data is being * scanned. * * id_parent When id_type is DATA, this is the inode number for * the parent of id_number. Otherwise, it is * available for use as an extra parameter or return * value between the callback and ckinode()'s caller. * Which, if either, of those is left completely up to * the two routines involved, so nothing can generally * be assumed about the id_parent value for non-DATA * traversals. * * id_lbn This is the current logical block (not fragment) * number being visited by the traversal. * * id_blkno This is the physical block corresponding to id_lbn. * * id_numfrags This defines how large a block is being processed in * this particular invocation of the callback. * Usually, it will be the same as sblock.fs_frag. * However, if a direct block is being processed and * it is less than a full filesystem block, * id_numfrags will indicate just how many fragments * (starting from id_lbn) are actually part of the * file. * * id_truncto The pass 4 callback is used in several places to * free the blocks of a file (the `FILE HAS PROBLEM * FOO; CLEAR?' scenario). This has been generalized * to allow truncating a file to a particular length * rather than always completely discarding it. If * id_truncto is -1, then the entire file is released, * otherwise it is logical block number to truncate * to. This generalized interface was motivated by a * desire to be able to discard everything after a * hole in a directory, rather than the entire * directory. * * id_type Selects the type of traversal. DATA for dirscan(), * ADDR or ACL for using the provided callback. * * There are several more fields used just for dirscan() traversals: * * id_filesize The number of bytes in the overall directory left to * process. * * id_loc Byte position within the directory block. Should always * point to the start of a directory entry. * * id_entryno Which logical directory entry is being processed (0 * is `.', 1 is `..', 2 and on are normal entries). * This field is primarily used to enable special * checks when looking at the first two entries. * * The exception (there's always an exception in fsck) * is that in pass 1, it tracks how many fragments are * being used by a particular inode. * * id_firsthole The first logical block number that was found to * be zero. As directories are not supposed to have * holes, this marks where a directory should be * truncated down to. A value of -1 indicates that * no holes were found. * * id_dirp A pointer to the in-memory copy of the current * directory entry (as identified by id_loc). * * id_name This is a directory entry name to either create * (callback is mkentry) or locate (callback is * chgino, findino, or findname). */ int ckinode(struct dinode *dp, struct inodesc *idesc, enum cki_action action) { struct inodesc cleardesc; mode_t mode; if (idesc->id_filesize == 0) idesc->id_filesize = (offset_t)dp->di_size; /* * Our caller should be filtering out completely-free inodes * (mode == zero), so we'll work on the assumption that what * we're given has some basic validity. * * The kernel is inconsistent about MAXPATHLEN including the * trailing \0, so allow the more-generous length for symlinks. */ mode = dp->di_mode & IFMT; if (mode == IFBLK || mode == IFCHR) return (KEEPON); if (mode == IFLNK && dp->di_size > MAXPATHLEN) { pwarn("I=%d Symlink longer than supported maximum\n", idesc->id_number); init_inodesc(&cleardesc); cleardesc.id_type = ADDR; cleardesc.id_number = idesc->id_number; cleardesc.id_fix = DONTKNOW; clri(&cleardesc, "BAD", CLRI_VERBOSE, CLRI_NOP_CORRUPT); return (STOP); } return (ckinode_common(dp, idesc, action)); } /* * This was split out from ckinode() to allow it to be used * without having to pass in kludge flags to suppress the * wrong-for-deletion initialization and irrelevant checks. * This feature is no longer needed, but is being kept in case * the need comes back. */ static int ckinode_common(struct dinode *dp, struct inodesc *idesc, enum cki_action action) { offset_t offset; struct dinode dino; daddr_t ndb; int indir_data_blks, last_indir_blk; int ret, i, frags; (void) memmove(&dino, dp, sizeof (struct dinode)); ndb = howmany(dino.di_size, (u_offset_t)sblock.fs_bsize); for (i = 0; i < NDADDR; i++) { idesc->id_lbn++; offset = blkoff(&sblock, dino.di_size); if ((--ndb == 0) && (offset != 0)) { idesc->id_numfrags = numfrags(&sblock, fragroundup(&sblock, offset)); } else { idesc->id_numfrags = sblock.fs_frag; } if (dino.di_db[i] == 0) { if ((ndb > 0) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = i; } continue; } idesc->id_blkno = dino.di_db[i]; if (idesc->id_type == ADDR || idesc->id_type == ACL) ret = (*idesc->id_func)(idesc); else ret = dirscan(idesc); /* * Need to clear the entry, now that we're done with * it. We depend on freeblk() ignoring a request to * free already-free fragments to handle the problem of * a partial block. */ if ((action == CKI_TRUNCATE) && (idesc->id_truncto >= 0) && (idesc->id_lbn >= idesc->id_truncto)) { dp = ginode(idesc->id_number); /* * The (int) cast is safe, in that if di_size won't * fit, it'll be a multiple of any legal fs_frag, * thus giving a zero result. That value, in turn * means we're doing an entire block. */ frags = howmany((int)dp->di_size, sblock.fs_fsize) % sblock.fs_frag; if (frags == 0) frags = sblock.fs_frag; freeblk(idesc->id_number, dp->di_db[i], frags); dp = ginode(idesc->id_number); dp->di_db[i] = 0; inodirty(); ret |= ALTERED; } if (ret & STOP) return (ret); } #ifdef lint /* * Cure a lint complaint of ``possible use before set''. * Apparently it can't quite figure out the switch statement. */ indir_data_blks = 0; #endif /* * indir_data_blks contains the number of data blocks in all * the previous levels for this iteration. E.g., for the * single indirect case (i = 0, di_ib[i] != 0), NDADDR's worth * of blocks have already been covered by the direct blocks * (di_db[]). At the triple indirect level (i = NIADDR - 1), * it is all of the number of data blocks that were covered * by the second indirect, single indirect, and direct block * levels. */ idesc->id_numfrags = sblock.fs_frag; ndb = howmany(dino.di_size, (u_offset_t)sblock.fs_bsize); for (i = 0; i < NIADDR; i++) { (void) get_indir_offsets(i, ndb, &indir_data_blks, &last_indir_blk); if (dino.di_ib[i] != 0) { /* * We'll only clear di_ib[i] if the first entry (and * therefore all of them) is to be cleared, since we * only go through this code on the first entry of * each level of indirection. The +1 is to account * for the fact that we don't modify id_lbn until * we actually start processing on a data block. */ idesc->id_blkno = dino.di_ib[i]; ret = iblock(idesc, i + 1, (u_offset_t)howmany(dino.di_size, (u_offset_t)sblock.fs_bsize) - indir_data_blks, action); if ((action == CKI_TRUNCATE) && (idesc->id_truncto <= indir_data_blks) && ((idesc->id_lbn + 1) >= indir_data_blks) && ((idesc->id_lbn + 1) <= last_indir_blk)) { dp = ginode(idesc->id_number); if (dp->di_ib[i] != 0) { freeblk(idesc->id_number, dp->di_ib[i], sblock.fs_frag); } } if (ret & STOP) return (ret); } else { /* * Need to know which of the file's logical blocks * reside in the missing indirect block. However, the * precise location is only needed for truncating * directories, and level-of-indirection precision is * sufficient for that. */ if ((indir_data_blks < ndb) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = indir_data_blks; } } } return (KEEPON); } static int get_indir_offsets(int ilevel_wanted, daddr_t ndb, int *data_blks, int *last_blk) { int ndb_ilevel = -1; int ilevel; int dblks, lblk; for (ilevel = 0; ilevel < NIADDR; ilevel++) { switch (ilevel) { case 0: /* SINGLE */ dblks = NDADDR; lblk = dblks + NINDIR(&sblock) - 1; break; case 1: /* DOUBLE */ dblks = NDADDR + NINDIR(&sblock); lblk = dblks + (NINDIR(&sblock) * NINDIR(&sblock)) - 1; break; case 2: /* TRIPLE */ dblks = NDADDR + NINDIR(&sblock) + (NINDIR(&sblock) * NINDIR(&sblock)); lblk = dblks + (NINDIR(&sblock) * NINDIR(&sblock) * NINDIR(&sblock)) - 1; break; default: exitstat = EXERRFATAL; /* * Translate from zero-based array to * one-based human-style counting. */ errexit("panic: indirection level %d not 1, 2, or 3", ilevel + 1); /* NOTREACHED */ } if (dblks < ndb && ndb <= lblk) ndb_ilevel = ilevel; if (ilevel == ilevel_wanted) { if (data_blks != NULL) *data_blks = dblks; if (last_blk != NULL) *last_blk = lblk; } } return (ndb_ilevel); } static int iblock(struct inodesc *idesc, int ilevel, u_offset_t iblks, enum cki_action action) { struct bufarea *bp; int i, n; int (*func)(struct inodesc *) = NULL; u_offset_t fsbperindirb; daddr32_t last_lbn; int nif; char buf[BUFSIZ]; n = KEEPON; switch (idesc->id_type) { case ADDR: func = idesc->id_func; if (((n = (*func)(idesc)) & KEEPON) == 0) return (n); break; case ACL: func = idesc->id_func; break; case DATA: func = dirscan; break; default: errexit("unknown inodesc type %d in iblock()", idesc->id_type); /* NOTREACHED */ } if (chkrange(idesc->id_blkno, idesc->id_numfrags)) { return ((idesc->id_type == ACL) ? STOP : SKIP); } bp = getdatablk(idesc->id_blkno, (size_t)sblock.fs_bsize); if (bp->b_errs != 0) { brelse(bp); return (SKIP); } ilevel--; /* * Trivia note: the BSD fsck has the number of bytes remaining * as the third argument to iblock(), so the equivalent of * fsbperindirb starts at fs_bsize instead of one. We're * working in units of filesystem blocks here, not bytes or * fragments. */ for (fsbperindirb = 1, i = 0; i < ilevel; i++) { fsbperindirb *= (u_offset_t)NINDIR(&sblock); } /* * nif indicates the next "free" pointer (as an array index) in this * indirect block, based on counting the blocks remaining in the * file after subtracting all previously processed blocks. * This figure is based on the size field of the inode. * * Note that in normal operation, nif may initially be calculated * as larger than the number of pointers in this block (as when * there are more indirect blocks following); if that is * the case, nif is limited to the max number of pointers per * indirect block. * * Also note that if an inode is inconsistent (has more blocks * allocated to it than the size field would indicate), the sweep * through any indirect blocks directly pointed at by the inode * continues. Since the block offset of any data blocks referenced * by these indirect blocks is greater than the size of the file, * the index nif may be computed as a negative value. * In this case, we reset nif to indicate that all pointers in * this retrieval block should be zeroed and the resulting * unreferenced data and/or retrieval blocks will be recovered * through garbage collection later. */ nif = (offset_t)howmany(iblks, fsbperindirb); if (nif > NINDIR(&sblock)) nif = NINDIR(&sblock); else if (nif < 0) nif = 0; /* * first pass: all "free" retrieval pointers (from [nif] thru * the end of the indirect block) should be zero. (This * assertion does not hold for directories, which may be * truncated without releasing their allocated space) */ if (nif < NINDIR(&sblock) && (idesc->id_func == pass1check || idesc->id_func == pass3bcheck)) { for (i = nif; i < NINDIR(&sblock); i++) { if (bp->b_un.b_indir[i] == 0) continue; (void) sprintf(buf, "PARTIALLY TRUNCATED INODE I=%lu", (ulong_t)idesc->id_number); if (preen) { pfatal(buf); } else if (dofix(idesc, buf)) { freeblk(idesc->id_number, bp->b_un.b_indir[i], sblock.fs_frag); bp->b_un.b_indir[i] = 0; dirty(bp); } } flush(fswritefd, bp); } /* * second pass: all retrieval pointers referring to blocks within * a valid range [0..filesize] (both indirect and data blocks) * are examined in the same manner as ckinode() checks the * direct blocks in the inode. Sweep through from * the first pointer in this retrieval block to [nif-1]. */ last_lbn = howmany(idesc->id_filesize, sblock.fs_bsize); for (i = 0; i < nif; i++) { if (ilevel == 0) idesc->id_lbn++; if (bp->b_un.b_indir[i] != 0) { idesc->id_blkno = bp->b_un.b_indir[i]; if (ilevel > 0) { n = iblock(idesc, ilevel, iblks, action); /* * Each iteration decreases "remaining block * count" by the number of blocks accessible * by a pointer at this indirect block level. */ iblks -= fsbperindirb; } else { /* * If we're truncating, func will discard * the data block for us. */ n = (*func)(idesc); } if ((action == CKI_TRUNCATE) && (idesc->id_truncto >= 0) && (idesc->id_lbn >= idesc->id_truncto)) { freeblk(idesc->id_number, bp->b_un.b_indir[i], sblock.fs_frag); } /* * Note that truncation never gets STOP back * under normal circumstances. Abnormal would * be a bad acl short-circuit in iblock() or * an out-of-range failure in pass4check(). * We still want to keep going when truncating * under those circumstances, since the whole * point of truncating is to get rid of all * that. */ if ((n & STOP) && (action != CKI_TRUNCATE)) { brelse(bp); return (n); } } else { if ((idesc->id_lbn < last_lbn) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = idesc->id_lbn; } if (idesc->id_type == DATA) { /* * No point in continuing in the indirect * blocks of a directory, since they'll just * get freed anyway. */ brelse(bp); return ((n & ~KEEPON) | STOP); } } } brelse(bp); return (KEEPON); } /* * Check that a block is a legal block number. * Return 0 if in range, 1 if out of range. */ int chkrange(daddr32_t blk, int cnt) { int c; if (cnt <= 0 || blk <= 0 || ((unsigned)blk >= (unsigned)maxfsblock) || ((cnt - 1) > (maxfsblock - blk))) { if (debug) (void) printf( "Bad fragment range: should be 1 <= %d..%d < %d\n", blk, blk + cnt, maxfsblock); return (1); } if ((cnt > sblock.fs_frag) || ((fragnum(&sblock, blk) + cnt) > sblock.fs_frag)) { if (debug) (void) printf("Bad fragment size: size %d\n", cnt); return (1); } c = dtog(&sblock, blk); if (blk < cgdmin(&sblock, c)) { if ((unsigned)(blk + cnt) > (unsigned)cgsblock(&sblock, c)) { if (debug) (void) printf( "Bad fragment position: %d..%d spans start of cg metadata\n", blk, blk + cnt); return (1); } } else { if ((unsigned)(blk + cnt) > (unsigned)cgbase(&sblock, c+1)) { if (debug) (void) printf( "Bad frag pos: %d..%d crosses end of cg\n", blk, blk + cnt); return (1); } } return (0); } /* * General purpose interface for reading inodes. */ /* * Note that any call to ginode() can potentially invalidate any * dinode pointers previously acquired from it. To avoid pain, * make sure to always call inodirty() immediately after modifying * an inode, if there's any chance of ginode() being called after * that. Also, always call ginode() right before you need to access * an inode, so that there won't be any surprises from functions * called between the previous ginode() invocation and the dinode * use. * * Despite all that, we aren't doing the amount of i/o that's implied, * as we use the buffer cache that getdatablk() and friends maintain. */ static fsck_ino_t startinum = -1; struct dinode * ginode(fsck_ino_t inum) { daddr32_t iblk; struct dinode *dp; if (inum < UFSROOTINO || inum > maxino) { errexit("bad inode number %d to ginode\n", inum); } if (startinum == -1 || pbp == NULL || inum < startinum || inum >= (fsck_ino_t)(startinum + (fsck_ino_t)INOPB(&sblock))) { iblk = itod(&sblock, inum); if (pbp != NULL) { brelse(pbp); } /* * We don't check for errors here, because we can't * tell our caller about it, and the zeros that will * be in the buffer are just as good as anything we * could fake. */ pbp = getdatablk(iblk, (size_t)sblock.fs_bsize); startinum = (fsck_ino_t)((inum / INOPB(&sblock)) * INOPB(&sblock)); } dp = &pbp->b_un.b_dinode[inum % INOPB(&sblock)]; if (dp->di_suid != UID_LONG) dp->di_uid = dp->di_suid; if (dp->di_sgid != GID_LONG) dp->di_gid = dp->di_sgid; return (dp); } /* * Special purpose version of ginode used to optimize first pass * over all the inodes in numerical order. It bypasses the buffer * system used by ginode(), etc in favour of reading the bulk of a * cg's inodes at one time. */ static fsck_ino_t nextino, lastinum; static int64_t readcnt, readpercg, fullcnt, inobufsize; static int64_t partialcnt, partialsize; static size_t lastsize; static struct dinode *inodebuf; static diskaddr_t currentdblk; static struct dinode *currentinode; struct dinode * getnextinode(fsck_ino_t inum) { size_t size; diskaddr_t dblk; static struct dinode *dp; if (inum != nextino++ || inum > maxino) errexit("bad inode number %d to nextinode\n", inum); /* * Will always go into the if() the first time we're called, * so dp will always be valid. */ if (inum >= lastinum) { readcnt++; dblk = fsbtodb(&sblock, itod(&sblock, lastinum)); currentdblk = dblk; if (readcnt % readpercg == 0) { if (partialsize > SIZE_MAX) errexit( "Internal error: partialsize overflow"); size = (size_t)partialsize; lastinum += partialcnt; } else { if (inobufsize > SIZE_MAX) errexit("Internal error: inobufsize overflow"); size = (size_t)inobufsize; lastinum += fullcnt; } /* * If fsck_bread() returns an error, it will already have * zeroed out the buffer, so we do not need to do so here. */ (void) fsck_bread(fsreadfd, (caddr_t)inodebuf, dblk, size); lastsize = size; dp = inodebuf; } currentinode = dp; return (dp++); } /* * Reread the current getnext() buffer. This allows for changing inodes * other than the current one via ginode()/inodirty()/inoflush(). * * Just reuses all the interesting variables that getnextinode() set up * last time it was called. This shouldn't get called often, so we don't * try to figure out if the caller's actually touched an inode in the * range we have cached. There could have been an arbitrary number of * them, after all. */ struct dinode * getnextrefresh(void) { if (inodebuf == NULL) { return (NULL); } inoflush(); (void) fsck_bread(fsreadfd, (caddr_t)inodebuf, currentdblk, lastsize); return (currentinode); } void resetinodebuf(void) { startinum = 0; nextino = 0; lastinum = 0; readcnt = 0; inobufsize = blkroundup(&sblock, INOBUFSIZE); fullcnt = inobufsize / sizeof (struct dinode); readpercg = sblock.fs_ipg / fullcnt; partialcnt = sblock.fs_ipg % fullcnt; partialsize = partialcnt * sizeof (struct dinode); if (partialcnt != 0) { readpercg++; } else { partialcnt = fullcnt; partialsize = inobufsize; } if (inodebuf == NULL && (inodebuf = (struct dinode *)malloc((unsigned)inobufsize)) == NULL) errexit("Cannot allocate space for inode buffer\n"); while (nextino < UFSROOTINO) (void) getnextinode(nextino); } void freeinodebuf(void) { if (inodebuf != NULL) { free((void *)inodebuf); } inodebuf = NULL; } /* * Routines to maintain information about directory inodes. * This is built during the first pass and used during the * second and third passes. * * Enter inodes into the cache. */ void cacheino(struct dinode *dp, fsck_ino_t inum) { struct inoinfo *inp; struct inoinfo **inpp; uint_t blks; blks = NDADDR + NIADDR; inp = (struct inoinfo *) malloc(sizeof (*inp) + (blks - 1) * sizeof (daddr32_t)); if (inp == NULL) errexit("Cannot increase directory list\n"); init_inoinfo(inp, dp, inum); /* doesn't touch i_nextlist or i_number */ inpp = &inphead[inum % numdirs]; inp->i_nextlist = *inpp; *inpp = inp; inp->i_number = inum; if (inplast == listmax) { listmax += 100; inpsort = (struct inoinfo **)realloc((void *)inpsort, (unsigned)listmax * sizeof (struct inoinfo *)); if (inpsort == NULL) errexit("cannot increase directory list"); } inpsort[inplast++] = inp; } /* * Look up an inode cache structure. */ struct inoinfo * getinoinfo(fsck_ino_t inum) { struct inoinfo *inp; inp = search_cache(inphead[inum % numdirs], inum); return (inp); } /* * Determine whether inode is in cache. */ int inocached(fsck_ino_t inum) { return (search_cache(inphead[inum % numdirs], inum) != NULL); } /* * Clean up all the inode cache structure. */ void inocleanup(void) { struct inoinfo **inpp; if (inphead == NULL) return; for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--) { free((void *)(*inpp)); } free((void *)inphead); free((void *)inpsort); inphead = inpsort = NULL; } /* * Routines to maintain information about acl inodes. * This is built during the first pass and used during the * second and third passes. * * Enter acl inodes into the cache. */ void cacheacl(struct dinode *dp, fsck_ino_t inum) { struct inoinfo *aclp; struct inoinfo **aclpp; uint_t blks; blks = NDADDR + NIADDR; aclp = (struct inoinfo *) malloc(sizeof (*aclp) + (blks - 1) * sizeof (daddr32_t)); if (aclp == NULL) return; aclpp = &aclphead[inum % numacls]; aclp->i_nextlist = *aclpp; *aclpp = aclp; aclp->i_number = inum; aclp->i_isize = (offset_t)dp->di_size; aclp->i_blkssize = (size_t)(blks * sizeof (daddr32_t)); (void) memmove(&aclp->i_blks[0], &dp->di_db[0], aclp->i_blkssize); if (aclplast == aclmax) { aclmax += 100; aclpsort = (struct inoinfo **)realloc((char *)aclpsort, (unsigned)aclmax * sizeof (struct inoinfo *)); if (aclpsort == NULL) errexit("cannot increase acl list"); } aclpsort[aclplast++] = aclp; } /* * Generic cache search function. * ROOT is the first entry in a hash chain (the caller is expected * to have done the initial bucket lookup). KEY is what's being * searched for. * * Returns a pointer to the entry if it is found, NULL otherwise. */ static struct inoinfo * search_cache(struct inoinfo *element, fsck_ino_t key) { while (element != NULL) { if (element->i_number == key) break; element = element->i_nextlist; } return (element); } void inodirty(void) { dirty(pbp); } static void inoflush(void) { if (pbp != NULL) flush(fswritefd, pbp); } /* * Interactive wrapper for freeino(), for those times when we're * not sure if we should throw something away. */ void clri(struct inodesc *idesc, char *type, int verbose, int corrupting) { int need_parent; struct dinode *dp; if (statemap[idesc->id_number] == USTATE) return; dp = ginode(idesc->id_number); if (verbose == CLRI_VERBOSE) { pwarn("%s %s", type, file_id(idesc->id_number, dp->di_mode)); pinode(idesc->id_number); } if (preen || (reply("CLEAR") == 1)) { need_parent = (corrupting == CLRI_NOP_OK) ? TI_NOPARENT : TI_PARENT; freeino(idesc->id_number, need_parent); if (preen) (void) printf(" (CLEARED)\n"); remove_orphan_dir(idesc->id_number); } else if (corrupting == CLRI_NOP_CORRUPT) { iscorrupt = 1; } (void) printf("\n"); } /* * Find the directory entry for the inode noted in id_parent (which is * not necessarily the parent of anything, we're just using a convenient * field. */ int findname(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (dirp->d_ino != idesc->id_parent) return (KEEPON); (void) memmove(idesc->id_name, dirp->d_name, MIN(dirp->d_namlen, MAXNAMLEN) + 1); return (STOP|FOUND); } /* * Find the inode number associated with the given name. */ int findino(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (dirp->d_ino == 0) return (KEEPON); if (strcmp(dirp->d_name, idesc->id_name) == 0 && dirp->d_ino >= UFSROOTINO && dirp->d_ino <= maxino) { idesc->id_parent = dirp->d_ino; return (STOP|FOUND); } return (KEEPON); } int cleardirentry(fsck_ino_t parentdir, fsck_ino_t target) { struct inodesc idesc; struct dinode *dp; dp = ginode(parentdir); init_inodesc(&idesc); idesc.id_func = clearanentry; idesc.id_parent = target; idesc.id_type = DATA; idesc.id_fix = NOFIX; return (ckinode(dp, &idesc, CKI_TRAVERSE)); } static int clearanentry(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (dirp->d_ino != idesc->id_parent || idesc->id_entryno < 2) { idesc->id_entryno++; return (KEEPON); } dirp->d_ino = 0; return (STOP|FOUND|ALTERED); } void pinode(fsck_ino_t ino) { struct dinode *dp; (void) printf(" I=%lu ", (ulong_t)ino); if (ino < UFSROOTINO || ino > maxino) return; dp = ginode(ino); pdinode(dp); } static void pdinode(struct dinode *dp) { char *p; struct passwd *pw; time_t t; (void) printf(" OWNER="); if ((pw = getpwuid((int)dp->di_uid)) != 0) (void) printf("%s ", pw->pw_name); else (void) printf("%lu ", (ulong_t)dp->di_uid); (void) printf("MODE=%o\n", dp->di_mode); if (preen) (void) printf("%s: ", devname); (void) printf("SIZE=%lld ", (longlong_t)dp->di_size); /* ctime() ignores LOCALE, so this is safe */ t = (time_t)dp->di_mtime; p = ctime(&t); (void) printf("MTIME=%12.12s %4.4s ", p + 4, p + 20); } void blkerror(fsck_ino_t ino, char *type, daddr32_t blk, daddr32_t lbn) { pfatal("FRAGMENT %d %s I=%u LFN %d", blk, type, ino, lbn); (void) printf("\n"); switch (statemap[ino] & ~INDELAYD) { case FSTATE: case FZLINK: statemap[ino] = FCLEAR; return; case DFOUND: case DSTATE: case DZLINK: statemap[ino] = DCLEAR; add_orphan_dir(ino); return; case SSTATE: statemap[ino] = SCLEAR; return; case FCLEAR: case DCLEAR: case SCLEAR: return; default: errexit("BAD STATE 0x%x TO BLKERR\n", statemap[ino]); /* NOTREACHED */ } } /* * allocate an unused inode */ fsck_ino_t allocino(fsck_ino_t request, int type) { fsck_ino_t ino; struct dinode *dp; struct cg *cgp = &cgrp; int cg; time_t t; caddr_t err; if (debug && (request != 0) && (request != UFSROOTINO)) errexit("assertion failed: allocino() asked for " "inode %d instead of 0 or %d", (int)request, (int)UFSROOTINO); /* * We know that we're only going to get requests for UFSROOTINO * or 0. If UFSROOTINO is wanted, then it better be available * because our caller is trying to recreate the root directory. * If we're asked for 0, then which one we return doesn't matter. * We know that inodes 0 and 1 are never valid to return, so we * the start at the lowest-legal inode number. * * If we got a request for UFSROOTINO, then request != 0, and * this pair of conditionals is the only place that treats * UFSROOTINO specially. */ if (request == 0) request = UFSROOTINO; else if (statemap[request] != USTATE) return (0); /* * Doesn't do wrapping, since we know we started at * the smallest inode. */ for (ino = request; ino < maxino; ino++) if (statemap[ino] == USTATE) break; if (ino == maxino) return (0); /* * In pass5, we'll calculate the bitmaps and counts all again from * scratch and do a comparison, but for that to work the cg has * to know what in-memory changes we've made to it. If we have * trouble reading the cg, cg_sanity() should kick it out so * we can skip explicit i/o error checking here. */ cg = itog(&sblock, ino); (void) getblk(&cgblk, cgtod(&sblock, cg), (size_t)sblock.fs_cgsize); err = cg_sanity(cgp, cg); if (err != NULL) { pfatal("CG %d: %s\n", cg, err); free((void *)err); if (reply("REPAIR") == 0) errexit("Program terminated."); fix_cg(cgp, cg); } setbit(cg_inosused(cgp), ino % sblock.fs_ipg); cgp->cg_cs.cs_nifree--; cgdirty(); if (lastino < ino) lastino = ino; /* * Don't currently support IFATTRDIR or any of the other * types, as they aren't needed. */ switch (type & IFMT) { case IFDIR: statemap[ino] = DSTATE; cgp->cg_cs.cs_ndir++; break; case IFREG: case IFLNK: statemap[ino] = FSTATE; break; default: /* * Pretend nothing ever happened. This clears the * dirty flag, among other things. */ initbarea(&cgblk); if (debug) (void) printf("allocino: unknown type 0%o\n", type & IFMT); return (0); } /* * We're allocating what should be a completely-unused inode, * so make sure we don't inherit anything from any previous * incarnations. */ dp = ginode(ino); (void) memset((void *)dp, 0, sizeof (struct dinode)); dp->di_db[0] = allocblk(1); if (dp->di_db[0] == 0) { statemap[ino] = USTATE; return (0); } dp->di_mode = (mode_t)type; (void) time(&t); dp->di_atime = (time32_t)t; dp->di_ctime = dp->di_atime; dp->di_mtime = dp->di_ctime; dp->di_size = (u_offset_t)sblock.fs_fsize; dp->di_blocks = btodb(sblock.fs_fsize); n_files++; inodirty(); return (ino); } /* * Release some or all of the blocks of an inode. * Only truncates down. Assumes new_length is appropriately aligned * to a block boundary (or a directory block boundary, if it's a * directory). * * If this is a directory, discard all of its contents first, so * we don't create a bunch of orphans that would need another fsck * run to clean up. * * Even if truncating to zero length, the inode remains allocated. */ void truncino(fsck_ino_t ino, offset_t new_length, int update) { struct inodesc idesc; struct inoinfo *iip; struct dinode *dp; fsck_ino_t parent; mode_t mode; caddr_t message; int isdir, islink; int ilevel, dblk; dp = ginode(ino); mode = (dp->di_mode & IFMT); isdir = (mode == IFDIR) || (mode == IFATTRDIR); islink = (mode == IFLNK); if (isdir) { /* * Go with the parent we found by chasing references, * if we've gotten that far. Otherwise, use what the * directory itself claims. If there's no ``..'' entry * in it, give up trying to get the link counts right. */ if (update == TI_NOPARENT) { parent = -1; } else { iip = getinoinfo(ino); if (iip != NULL) { parent = iip->i_parent; } else { parent = lookup_dotdot_ino(ino); if (parent != 0) { /* * Make sure that the claimed * parent actually has a * reference to us. */ dp = ginode(parent); idesc.id_name = lfname; idesc.id_type = DATA; idesc.id_func = findino; idesc.id_number = ino; idesc.id_fix = DONTKNOW; if ((ckinode(dp, &idesc, CKI_TRAVERSE) & FOUND) == 0) parent = 0; } } } mark_delayed_inodes(ino, numfrags(&sblock, new_length)); if (parent > 0) { dp = ginode(parent); LINK_RANGE(message, dp->di_nlink, -1); if (message != NULL) { LINK_CLEAR(message, parent, dp->di_mode, &idesc); if (statemap[parent] == USTATE) goto no_parent_update; } TRACK_LNCNTP(parent, lncntp[parent]--); } else if ((mode == IFDIR) && (parent == 0)) { /* * Currently don't have a good way to * handle this, so throw up our hands. * However, we know that we can still * do some good if we continue, so * don't actually exit yet. * * We don't do it for attrdirs, * because there aren't link counts * between them and their parents. */ pwarn("Could not determine former parent of " "inode %d, link counts are possibly\n" "incorrect. Please rerun fsck(8) to " "correct this.\n", ino); iscorrupt = 1; } /* * ...else if it's a directory with parent == -1, then * we've not gotten far enough to know connectivity, * and it'll get handled automatically later. */ } no_parent_update: init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass4check; idesc.id_number = ino; idesc.id_fix = DONTKNOW; idesc.id_truncto = howmany(new_length, sblock.fs_bsize); dp = ginode(ino); if (!islink && ckinode(dp, &idesc, CKI_TRUNCATE) & ALTERED) inodirty(); /* * This has to be done after ckinode(), so that all of * the fragments get visited. Note that we assume we're * always truncating to a block boundary, rather than a * fragment boundary. */ dp = ginode(ino); dp->di_size = new_length; /* * Clear now-obsolete pointers. */ for (dblk = idesc.id_truncto + 1; dblk < NDADDR; dblk++) { dp->di_db[dblk] = 0; } ilevel = get_indir_offsets(-1, idesc.id_truncto, NULL, NULL); for (ilevel++; ilevel < NIADDR; ilevel++) { dp->di_ib[ilevel] = 0; } inodirty(); } /* * Release an inode's resources, then release the inode itself. */ void freeino(fsck_ino_t ino, int update_parent) { int cg; struct dinode *dp; struct cg *cgp; n_files--; dp = ginode(ino); /* * We need to make sure that the file is really a large file. * Everything bigger than UFS_MAXOFFSET_T is treated as a file with * negative size, which shall be cleared. (see verify_inode() in * pass1.c) */ if (dp->di_size > (u_offset_t)MAXOFF_T && dp->di_size <= (u_offset_t)UFS_MAXOFFSET_T && ftypeok(dp) && (dp->di_mode & IFMT) != IFBLK && (dp->di_mode & IFMT) != IFCHR) { largefile_count--; } truncino(ino, 0, update_parent); dp = ginode(ino); if ((dp->di_mode & IFMT) == IFATTRDIR) { clearshadow(ino, &attrclientinfo); dp = ginode(ino); } clearinode(dp); inodirty(); statemap[ino] = USTATE; /* * Keep the disk in sync with us so that pass5 doesn't get * upset about spurious inconsistencies. */ cg = itog(&sblock, ino); (void) getblk(&cgblk, (diskaddr_t)cgtod(&sblock, cg), (size_t)sblock.fs_cgsize); cgp = cgblk.b_un.b_cg; clrbit(cg_inosused(cgp), ino % sblock.fs_ipg); cgp->cg_cs.cs_nifree += 1; cgdirty(); sblock.fs_cstotal.cs_nifree += 1; sbdirty(); } void init_inoinfo(struct inoinfo *inp, struct dinode *dp, fsck_ino_t inum) { inp->i_parent = ((inum == UFSROOTINO) ? UFSROOTINO : (fsck_ino_t)0); inp->i_dotdot = (fsck_ino_t)0; inp->i_isize = (offset_t)dp->di_size; inp->i_blkssize = (NDADDR + NIADDR) * sizeof (daddr32_t); inp->i_extattr = dp->di_oeftflag; (void) memmove((void *)&inp->i_blks[0], (void *)&dp->di_db[0], inp->i_blkssize); } /* * Return the inode number in the ".." entry of the provided * directory inode. */ static int lookup_dotdot_ino(fsck_ino_t ino) { struct inodesc idesc; init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = findino; idesc.id_name = ".."; idesc.id_number = ino; idesc.id_fix = NOFIX; if ((ckinode(ginode(ino), &idesc, CKI_TRAVERSE) & FOUND) != 0) { return (idesc.id_parent); } return (0); } /* * Convenience wrapper around ckinode(findino()). */ int lookup_named_ino(fsck_ino_t dir, caddr_t name) { struct inodesc idesc; init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = findino; idesc.id_name = name; idesc.id_number = dir; idesc.id_fix = NOFIX; if ((ckinode(ginode(dir), &idesc, CKI_TRAVERSE) & FOUND) != 0) { return (idesc.id_parent); } return (0); } /* * Marks inodes that are being orphaned and might need to be reconnected * by pass4(). The inode we're traversing is the directory whose * contents will be reconnected later. id_parent is the lfn at which * to start looking at said contents. */ static int mark_a_delayed_inode(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (idesc->id_lbn < idesc->id_parent) { return (KEEPON); } if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0 && strcmp(dirp->d_name, "..") != 0) { statemap[dirp->d_ino] &= ~INFOUND; statemap[dirp->d_ino] |= INDELAYD; } return (KEEPON); } static void mark_delayed_inodes(fsck_ino_t ino, daddr32_t first_lfn) { struct dinode *dp; struct inodesc idelayed; init_inodesc(&idelayed); idelayed.id_number = ino; idelayed.id_type = DATA; idelayed.id_fix = NOFIX; idelayed.id_func = mark_a_delayed_inode; idelayed.id_parent = first_lfn; idelayed.id_entryno = 2; dp = ginode(ino); (void) ckinode(dp, &idelayed, CKI_TRAVERSE); } /* * Clear the i_oeftflag/extended attribute pointer from INO. */ void clearattrref(fsck_ino_t ino) { struct dinode *dp; dp = ginode(ino); if (debug) { if (dp->di_oeftflag == 0) (void) printf("clearattref: no attr to clear on %d\n", ino); } dp->di_oeftflag = 0; inodirty(); } /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED '`AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * In-core structures: * blockmap[] * A bitmap of block usage very similar to what's on disk, but * for the entire filesystem rather than just a cylinder group. * Zero indicates free, one indicates allocated. Note that this * is opposite the interpretation of a cylinder group's free block * bitmap. * * statemap[] * Tracks what is known about each inode in the filesystem. * The fundamental state value is one of USTATE, FSTATE, DSTATE, * or SSTATE (unallocated, file, directory, shadow/acl). * * There are optional modifying attributes as well: INZLINK, * INFOUND, INCLEAR, INORPHAN, and INDELAYD. The IN prefix * stands for inode. INZLINK declares that no links (di_nlink == * 0) to the inode have been found. It is used instead of * examining di_nlink because we've always got the statemap[] in * memory, and on average the odds are against having any given * inode in the cache. INFOUND flags that an inode was * encountered during the descent of the filesystem. In other * words, it's reachable, either by name or by being an acl or * attribute. INCLEAR declares an intent to call clri() on an * inode. The INCLEAR and INZLINK attributes are treated in a * mutually exclusive manner with INCLEAR taking higher precedence * as the intent is to clear the inode. * * INORPHAN indicates that the inode has already been seen once * in pass3 and determined to be an orphan, so any additional * encounters don't need to waste cycles redetermining that status. * It also means we don't ask the user about doing something to the * inode N times. * * INDELAYD marks inodes that pass1 determined needed to be truncated. * They can't be truncated during that pass, because it depends on * having a stable world for building the block and inode tables from. * * The IN flags rarely used directly, but instead are * pre-combined through the {D,F,S}ZLINK, DFOUND, and * {D,F,S}CLEAR convenience macros. This mainly matters when * trying to use grep on the source. * * Three state-test macros are provided: S_IS_DUNFOUND(), * S_IS_DVALID(), and S_IS_ZLINK(). The first is true when an * inode's state indicates that it is either a simple directory * (DSTATE without the INFOUND or INCLEAR modifiers) or a * directory with the INZLINK modifier set. By definition, if a * directory has zero links, then it can't be found. As for * S_IS_DVALID(), it decides if a directory inode is alive. * Effectively, this translates to whether or not it's been * flagged for clearing. If not, then it's valid for current * purposes. This is true even if INZLINK is set, as we may find * a reference to it later. Finally, S_IS_ZLINK() just picks out * the INZLINK flag from the state. * * The S_*() macros all work on a state value. To simplify a * bit, the INO_IS_{DUNFOUND,DVALID}() macros take an inode * number argument. The inode is looked up in the statemap[] and * the result handed off to the corresponding S_*() macro. This * is partly a holdover from working with different data * structures (with the same net intent) in the BSD fsck. * * lncntp * Each entry is initialized to the di_link from the on-disk * inode. Each time we find one of those links, we decrement it. * Once all the traversing is done, we should have a zero. If we * have a positive value, then some reference disappeared * (probably from a directory that got nuked); deal with it by * fixing the count. If we have a negative value, then we found * an extra reference. This is a can't-happen, except in the * special case of when we reconnect a directory to its parent or * to lost+found. An exact match between lncntp[] and the on-disk * inode means it's completely unreferenced. * * aclphead * This is a hash table of the acl inodes in the filesystem. * * aclpsort * The same acls as in aclphead, but as a simple linear array. * It is used to hold the acl pointers for sorting and scanning * in pass3b. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" static void usage(void) __NORETURN; static long argtol(int, char *, char *, int); static void checkfilesys(char *); static void check_sanity(char *); static void report_limbo(const void *, VISIT, int); #define QUICK_CHECK 'm' /* are things ok according to superblock? */ #define ALL_no 'n' /* auto-answer interactive questions `no' */ #define ALL_NO 'N' /* auto-answer interactive questions `no' */ #define UFS_OPTS 'o' /* ufs-specific options, see subopts[] */ #define ECHO_CMD 'V' /* echo the command line */ #define ALL_yes 'y' /* auto-answer interactive questions `yes' */ #define ALL_YES 'Y' /* auto-answer interactive questions `yes' */ #define VERBOSE 'v' /* be chatty */ static char *subopts[] = { #define PREEN 0 /* non-interactive mode (parent is parallel) */ "p", #define BLOCK 1 /* alternate superblock */ "b", #define DEBUG 2 /* yammer */ "d", #define ONLY_WRITES 3 /* check all writable filesystems */ "w", #define FORCE 4 /* force checking, even if clean */ "f", NULL }; /* * Filesystems that are `magical' - if they exist in vfstab, * then they have to be mounted for the system to have gotten * far enough to be able to run fsck. Thus, don't get all * bent out of shape if we're asked to check it and it is mounted. */ char *magic_fs[] = { "", /* MAGIC_NONE, for normal filesystems */ "/", /* MAGIC_ROOT */ "/usr", /* MAGIC_USR */ NULL /* MAGIC_LIMIT */ }; daddr32_t bflag; daddr32_t n_blks; daddr32_t maxfsblock; int debug; int errorlocked; int exitstat; int fflag; int fsmodified; int fswritefd; int iscorrupt; int islog; int islogok; int interrupted; int mflag; int mountfd; int overflowed_lf; int rflag; int reattached_dir; int broke_dir_link; int verbose; char hotroot; char mountedfs; char nflag; char preen; char rerun; char *blockmap; char *devname; char yflag; short *lncntp; ushort_t *statemap; fsck_ino_t maxino; fsck_ino_t countdirs; fsck_ino_t n_files; void *limbo_dirs; int main(int argc, char *argv[]) { int c; int wflag = 0; char *suboptions, *value; struct rlimit rlimit; extern int optind; extern char *optarg; while ((c = getopt(argc, argv, "mnNo:VvyY")) != EOF) { switch (c) { case QUICK_CHECK: mflag++; break; case ALL_no: case ALL_NO: nflag++; yflag = 0; break; case VERBOSE: verbose++; break; case UFS_OPTS: /* * ufs specific options. */ if (optarg == NULL) { usage(); } suboptions = optarg; while (*suboptions != '\0') { switch (getsubopt(&suboptions, subopts, &value)) { case PREEN: preen++; break; case BLOCK: bflag = argtol(BLOCK, "block", value, 10); (void) printf("Alternate super block " "location: %ld.\n", (long)bflag); break; case DEBUG: debug++; verbose++; break; case ONLY_WRITES: /* check only writable filesystems */ wflag++; break; case FORCE: fflag++; break; default: usage(); } } break; case ECHO_CMD: { int opt_count; char *opt_text; (void) printf("fsck -F ufs "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) printf("%s ", opt_text); } (void) printf("\n"); } break; case ALL_yes: case ALL_YES: yflag++; nflag = 0; break; default: usage(); } } argc -= optind; argv += optind; if (argc == 0) usage(); rflag++; /* check raw devices where we can */ if (signal(SIGINT, SIG_IGN) != SIG_IGN) (void) signal(SIGINT, catch); if (preen) (void) signal(SIGQUIT, catchquit); /* * Push up our allowed memory limit so we can cope * with huge file systems. */ if (getrlimit(RLIMIT_DATA, &rlimit) == 0) { rlimit.rlim_cur = rlimit.rlim_max; (void) setrlimit(RLIMIT_DATA, &rlimit); } /* * There are a lot of places where we just exit if a problem is * found. This means that we won't necessarily check everything * we were asked to. It would be nice to do everything, and * then provide a summary when we're done. However, the * interface doesn't really allow us to do that in any useful * way. So, we'll just bail on the first unrecoverable * problem encountered. If we've been run by the generic * wrapper, we were only given one filesystem to check, so the * multi-fs case implies being run manually; that means the * user can rerun us on the remaining filesystems when it's * convenient for them. */ while (argc-- > 0) { if (wflag && !writable(*argv)) { (void) fprintf(stderr, "not writeable '%s'\n", *argv); argv++; if (exitstat == 0) exitstat = EXBADPARM; } else { checkfilesys(*argv++); } } if (interrupted) exitstat = EXSIGNAL; exit(exitstat); } /* * A relatively intelligent strtol(). Note that if str is NULL, we'll * exit, so ret does not actually need to be pre-initialized. Lint * doesn't believe this, and it's harmless enough to make lint happy here. */ static long argtol(int flag, char *req, char *str, int base) { char *cp = str; long ret = -1; errno = 0; if (str != NULL) ret = strtol(str, &cp, base); if (cp == str || *cp) { (void) fprintf(stderr, "-%c flag requires a %s\n", flag, req); exit(EXBADPARM); } if (errno != 0) { (void) fprintf(stderr, "-%c %s value out of range\n", flag, req); } return (ret); } /* * Check the specified file system. */ static void checkfilesys(char *filesys) { daddr32_t n_ffree, n_bfree; char *devstr; fsck_ino_t files; daddr32_t blks; fsck_ino_t inumber; int zlinks_printed; fsck_ino_t limbo_victim; double dbl_nffree, dbl_dsize; int quiet_dups; mountfd = -1; hotroot = 0; mountedfs = M_NOMNT; reattached_dir = 0; broke_dir_link = 0; iscorrupt = 1; /* assume failure in setup() */ islog = 0; islogok = 0; overflowed_lf = 0; errorlocked = is_errorlocked(filesys); limbo_dirs = NULL; if ((devstr = setup(filesys)) == NULL) { if (!iscorrupt) { return; } if (preen) pfatal("CAN'T CHECK FILE SYSTEM."); if (exitstat == 0) exitstat = mflag ? EXUMNTCHK : EXERRFATAL; exit(exitstat); } else { devname = devstr; } if (mflag) { check_sanity(filesys); /* NOTREACHED */ } if (debug) printclean(); iscorrupt = 0; /* setup() succeeded, assume good filesystem */ /* * 1: scan inodes tallying blocks used */ if (!preen) { /* hotroot is reported as such in setup() if debug is on */ if (mountedfs != M_NOMNT) (void) printf("** Currently Mounted on %s\n", sblock.fs_fsmnt); else (void) printf("** Last Mounted on %s\n", sblock.fs_fsmnt); (void) printf("** Phase 1 - Check Blocks and Sizes\n"); } pass1(); /* * 1b: locate first references to duplicates, if any */ if (have_dups()) { if (preen) pfatal("INTERNAL ERROR: dups with -o p"); (void) printf("** Phase 1b - Rescan For More DUPS\n"); pass1b(); } /* * 2: traverse directories from root to mark all connected directories */ if (!preen) (void) printf("** Phase 2 - Check Pathnames\n"); pass2(); /* * 3a: scan inodes looking for disconnected directories. */ if (!preen) (void) printf("** Phase 3a - Check Connectivity\n"); pass3a(); /* * 3b: check acls */ if (!preen) (void) printf("** Phase 3b - Verify Shadows/ACLs\n"); pass3b(); /* * 4: scan inodes looking for disconnected files; check reference counts */ if (!preen) (void) printf("** Phase 4 - Check Reference Counts\n"); pass4(); /* * 5: check and repair resource counts in cylinder groups */ if (!preen) (void) printf("** Phase 5 - Check Cylinder Groups\n"); recount: pass5(); if (overflowed_lf) { iscorrupt = 1; } if (!nflag && mountedfs == M_RW) { (void) printf("FILESYSTEM MAY STILL BE INCONSISTENT.\n"); rerun = 1; } if (have_dups()) { quiet_dups = (reply("LIST REMAINING DUPS") == 0); if (report_dups(quiet_dups) > 0) iscorrupt = 1; (void) printf("WARNING: DATA LOSS MAY HAVE OCCURRED DUE TO " "DUP BLOCKS.\nVERIFY FILE CONTENTS BEFORE USING.\n"); } if (limbo_dirs != NULL) { /* * Don't force iscorrupt, as this is sufficiently * harmless that the filesystem can be mounted and * used. We just leak some inodes and/or blocks. */ pwarn("Orphan directories not cleared or reconnected:\n"); twalk(limbo_dirs, report_limbo); while (limbo_dirs != NULL) { limbo_victim = *(fsck_ino_t *)limbo_dirs; if (limbo_victim != 0) { (void) tdelete((void *)limbo_victim, &limbo_dirs, ino_t_cmp); } } rerun = 1; } if (iscorrupt) { if (mountedfs == M_RW) (void) printf("FS IS MOUNTED R/W AND" " FSCK DID ITS BEST TO FIX" " INCONSISTENCIES.\n"); else (void) printf("FILESYSTEM MAY STILL BE" " INCONSISTENT.\n"); rerun = 1; } /* * iscorrupt must be stable at this point. * updateclean() returns true when it had to discard the log. * This can only happen once, since sblock.fs_logbno gets * cleared as part of that operation. */ if (updateclean()) { if (!preen) (void) printf( "Log was discarded, updating cyl groups\n"); goto recount; } if (debug) printclean(); ckfini(); /* * print out summary statistics */ n_ffree = sblock.fs_cstotal.cs_nffree; n_bfree = sblock.fs_cstotal.cs_nbfree; files = maxino - UFSROOTINO - sblock.fs_cstotal.cs_nifree - n_files; blks = n_blks + sblock.fs_ncg * (cgdmin(&sblock, 0) - cgsblock(&sblock, 0)); blks += cgsblock(&sblock, 0) - cgbase(&sblock, 0); blks += howmany(sblock.fs_cssize, sblock.fs_fsize); blks = maxfsblock - (n_ffree + sblock.fs_frag * n_bfree) - blks; if (debug && (files > 0 || blks > 0)) { countdirs = sblock.fs_cstotal.cs_ndir - countdirs; pwarn("Reclaimed: %d directories, %d files, %lld fragments\n", countdirs, files - countdirs, (longlong_t)blks); } dbl_nffree = (double)n_ffree; dbl_dsize = (double)sblock.fs_dsize; if (!verbose) { /* * Done as one big string to try for a single write, * so the output doesn't get interleaved with other * preening fscks. */ pwarn("%ld files, %lld used, %lld free " "(%lld frags, %lld blocks, %.1f%% fragmentation)\n", (long)n_files, (longlong_t)n_blks, (longlong_t)n_ffree + sblock.fs_frag * n_bfree, (longlong_t)n_ffree, (longlong_t)n_bfree, (dbl_nffree * 100.0) / dbl_dsize); } else { pwarn("\nFilesystem summary:\n"); pwarn("Inodes in use: %ld\n", (long)n_files); pwarn("Blocks in use: %lld\n", (longlong_t)n_blks); pwarn("Total free fragments: %lld\n", (longlong_t)n_ffree + sblock.fs_frag * n_bfree); pwarn("Free fragments not in blocks: %lld\n", (longlong_t)n_ffree); pwarn("Total free blocks: %lld\n", (longlong_t)n_bfree); pwarn("Fragment/block fragmentation: %.1f%%\n", (dbl_nffree * 100.0) / dbl_dsize); pwarn(""); if (files < 0) pwarn("%d inodes missing\n", -files); if (blks < 0) pwarn("%lld blocks missing\n", -(longlong_t)blks); zlinks_printed = 0; for (inumber = UFSROOTINO; inumber < maxino; inumber++) { if (S_IS_ZLINK(statemap[inumber])) { if (zlinks_printed == 0) { pwarn("The following zero " "link count inodes remain:"); } if (zlinks_printed) { if ((zlinks_printed % 9) == 0) (void) puts(",\n"); else (void) puts(", "); } (void) printf("%u", inumber); zlinks_printed++; } } if ((zlinks_printed != 0) && ((zlinks_printed % 9) != 0)) (void) putchar('\n'); } /* * Clean up after ourselves, so we can do the next filesystem. */ free_dup_state(); inocleanup(); free(blockmap); free(statemap); free((void *)lncntp); lncntp = NULL; blockmap = NULL; statemap = NULL; if (iscorrupt && exitstat == 0) exitstat = EXFNDERRS; if (fsmodified) (void) printf("\n***** FILE SYSTEM WAS MODIFIED *****\n"); if (overflowed_lf) (void) printf("\n***** %s FULL, MUST REMOVE ENTRIES *****\n", lfname); if (reattached_dir) { (void) printf("ORPHANED DIRECTORIES REATTACHED; DIR LINK " "COUNTS MAY NOT BE CORRECT.\n"); rerun = 1; } if (broke_dir_link) { (void) printf( "DIRECTORY HARDLINK BROKEN; LOOPS MAY STILL EXIST.\n"); rerun = 1; } if (iscorrupt) (void) printf("***** FILE SYSTEM IS BAD *****\n"); if (rerun) { if (mountedfs == M_RW) (void) printf("\n***** PLEASE RERUN FSCK ON UNMOUNTED" " FILE SYSTEM *****\n"); else (void) printf("\n***** PLEASE RERUN FSCK *****\n"); } if ((exitstat == 0) && (((mountedfs != M_NOMNT) && !errorlocked) || hotroot)) { exitstat = EXROOTOKAY; } if ((exitstat == 0) && rerun) exitstat = EXFNDERRS; if (mountedfs != M_NOMNT) { if (!fsmodified) return; /* * _FIOFFS is much more effective than a simple sync(). * Note that the original fswritefd was discarded in * ckfini(). */ fswritefd = open(devstr, O_RDWR, 0); if (fswritefd != -1) { (void) ioctl(fswritefd, _FIOFFS, NULL); (void) close(fswritefd); } if (!preen) (void) printf("\n***** REBOOT NOW *****\n"); exitstat = EXREBOOTNOW; } } /* * fsck -m: does the filesystem pass cursory examination * * XXX This is very redundant with setup(). The right thing would be * for setup() to modify its behaviour when mflag is set (less * chatty, exit instead of return, etc). */ void check_sanity(char *filename) { struct stat64 stbd, stbr; char *devname; struct ustat usb; char vfsfilename[MAXPATHLEN]; struct vfstab vfsbuf; FILE *vfstab; struct statvfs vfs_stat; int found_magic[MAGIC_LIMIT]; int magic_cnt; int is_magic = 0; int is_block = 0; int is_file = 0; (void) memset((void *)found_magic, 0, sizeof (found_magic)); if (stat64(filename, &stbd) < 0) { (void) fprintf(stderr, "ufs fsck: sanity check failed : cannot stat %s\n", filename); exit(EXNOSTAT); } if (S_ISBLK(stbd.st_mode)) { is_block = 1; } else if (S_ISCHR(stbd.st_mode)) { is_block = 0; } else if (S_ISREG(stbd.st_mode)) { is_file = 1; } /* * Determine if this is the root file system via vfstab. Give up * silently on failures. The whole point of this is to be tolerant * of the magic file systems being already mounted. */ if (!is_file && (vfstab = fopen(VFSTAB, "r")) != NULL) { for (magic_cnt = 0; magic_cnt < MAGIC_LIMIT; magic_cnt++) { if (magic_cnt == MAGIC_NONE) continue; if (getvfsfile(vfstab, &vfsbuf, magic_fs[magic_cnt]) == 0) { if (is_block) devname = vfsbuf.vfs_special; else devname = vfsbuf.vfs_fsckdev; if (stat64(devname, &stbr) == 0) { if (stbr.st_rdev == stbd.st_rdev) { found_magic[magic_cnt] = 1; is_magic = magic_cnt; break; } } } } } /* * Only works if filename is a block device or if * character and block device has the same dev_t value. * This is currently true, but nothing really forces it. */ if (!is_magic && (ustat(stbd.st_rdev, &usb) == 0)) { (void) fprintf(stderr, "ufs fsck: sanity check: %s already mounted\n", filename); exit(EXMOUNTED); } if (is_magic) { (void) strcpy(vfsfilename, magic_fs[is_magic]); if (statvfs(vfsfilename, &vfs_stat) != 0) { (void) fprintf(stderr, "ufs fsck: Cannot stat %s\n", vfsfilename); exit(EXNOSTAT); } if (!(vfs_stat.f_flag & ST_RDONLY)) { /* * The file system is mounted read/write * We need to exit saying this. If it's only * mounted readonly, we can continue. */ (void) fprintf(stderr, "ufs fsck: sanity check:" "%s already mounted read/write\n", filename); exit(EXMOUNTED); } } /* * We know that at boot, the ufs root file system is mounted * read-only first. After fsck runs, it is remounted as * read-write. Therefore, we do not need to check for different * values for fs_state between the root file system and the * rest of the file systems. */ if (islog && !islogok) { (void) fprintf(stderr, "ufs fsck: sanity check: %s needs checking\n", filename); exit(EXUMNTCHK); } if ((sblock.fs_state + (long)sblock.fs_time == FSOKAY) && (sblock.fs_clean == FSCLEAN || sblock.fs_clean == FSSTABLE || (sblock.fs_clean == FSLOG && islog))) { (void) fprintf(stderr, "ufs fsck: sanity check: %s okay\n", filename); } else { (void) fprintf(stderr, "ufs fsck: sanity check: %s needs checking\n", filename); exit(EXUMNTCHK); } exit(EXOKAY); } caddr_t hasvfsopt(struct vfstab *vfs, char *opt) { struct mnttab mtab; if (vfs->vfs_mntopts == NULL) return (NULL); mtab.mnt_mntopts = vfs->vfs_mntopts; return (hasmntopt(&mtab, opt)); } static void __NORETURN usage(void) { (void) fprintf(stderr, "ufs usage: fsck [-F ufs] [-m] [-n] [-V] [-v] [-y] " "[-o p,b=#,w,f] [special ....]\n"); exit(EXBADPARM); } /*ARGSUSED*/ static void report_limbo(const void *node, VISIT order, int level) { fsck_ino_t ino = *(fsck_ino_t *)node; if ((order == postorder) || (order == leaf)) { (void) printf(" Inode %d\n", ino); } } /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include "fsck.h" /* * for each large file (size > MAXOFF_T), the global largefile_count * gets incremented during this pass. */ static uint32_t badblk; /* number seen for the current inode */ static uint32_t dupblk; /* number seen for the current inode */ static void clear_attr_acl(fsck_ino_t, fsck_ino_t, char *); static void verify_inode(fsck_ino_t, struct inodesc *, fsck_ino_t); static void check_dirholes(fsck_ino_t, struct inodesc *); static void collapse_dirhole(fsck_ino_t, struct inodesc *); static void note_used(daddr32_t); void pass1(void) { uint_t c, i; daddr32_t cgd; struct inodesc idesc; fsck_ino_t inumber; fsck_ino_t maxinumber; /* * Set file system reserved blocks in used block map. */ for (c = 0; c < sblock.fs_ncg; c++) { cgd = cgdmin(&sblock, c); if (c == 0) { /* * Doing the first cylinder group, account for * the cg summaries as well. */ i = cgbase(&sblock, c); cgd += howmany(sblock.fs_cssize, sblock.fs_fsize); } else { i = cgsblock(&sblock, c); } for (; i < cgd; i++) { note_used(i); } } /* * Note blocks being used by the log, so we don't declare * them as available and some time in the future we get a * freeing free block panic. */ if (islog && islogok && sblock.fs_logbno) examinelog(¬e_used); /* * Find all allocated blocks. This must be completed before * we read the contents of any directories, as dirscan() et al * don't want to know about block allocation holes. So, part * of this pass is to truncate any directories with holes to * just before those holes, so dirscan() can remain blissfully * ignorant. */ inumber = 0; n_files = n_blks = 0; resetinodebuf(); maxinumber = sblock.fs_ncg * sblock.fs_ipg; for (c = 0; c < sblock.fs_ncg; c++) { for (i = 0; i < sblock.fs_ipg; i++, inumber++) { if (inumber < UFSROOTINO) continue; init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass1check; verify_inode(inumber, &idesc, maxinumber); } } freeinodebuf(); } /* * Perform checks on an inode and setup/track the state of the inode * in maps (statemap[], lncntp[]) for future reference and validation. * Initiate the calls to ckinode and in turn pass1check() to handle * further validation. */ static void verify_inode(fsck_ino_t inumber, struct inodesc *idesc, fsck_ino_t maxinumber) { int j, clear, flags; int isdir; char *err; fsck_ino_t shadow, attrinode; daddr32_t ndb; struct dinode *dp; struct inoinfo *iip; dp = getnextinode(inumber); if ((dp->di_mode & IFMT) == 0) { /* mode and type of file is not set */ if ((memcmp((void *)dp->di_db, (void *)zino.di_db, NDADDR * sizeof (daddr32_t)) != 0) || (memcmp((void *)dp->di_ib, (void *)zino.di_ib, NIADDR * sizeof (daddr32_t)) != 0) || (dp->di_mode != 0) || (dp->di_size != 0)) { pfatal("PARTIALLY ALLOCATED INODE I=%u", inumber); if (reply("CLEAR") == 1) { dp = ginode(inumber); clearinode(dp); inodirty(); } else { iscorrupt = 1; } } statemap[inumber] = USTATE; return; } isdir = ((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR); lastino = inumber; if (dp->di_size > (u_offset_t)UFS_MAXOFFSET_T) { pfatal("NEGATIVE SIZE %lld I=%d", (longlong_t)dp->di_size, inumber); goto bogus; } /* * A more precise test of the type is done later on. Just get * rid of the blatantly-wrong ones before we do any * significant work. */ if ((dp->di_mode & IFMT) == IFMT) { pfatal("BAD MODE 0%o I=%d", dp->di_mode & IFMT, inumber); if (reply("BAD MODE: MAKE IT A FILE") == 1) { statemap[inumber] = FSTATE; dp = ginode(inumber); dp->di_mode = IFREG | 0600; inodirty(); truncino(inumber, sblock.fs_fsize, TI_NOPARENT); dp = getnextrefresh(); } else { iscorrupt = 1; } } ndb = howmany(dp->di_size, (u_offset_t)sblock.fs_bsize); if (ndb < 0) { /* extra space to distinguish from previous pfatal() */ pfatal("NEGATIVE SIZE %lld I=%d", (longlong_t)dp->di_size, inumber); goto bogus; } if ((dp->di_mode & IFMT) == IFBLK || (dp->di_mode & IFMT) == IFCHR) { if (dp->di_size != 0) { pfatal("SPECIAL FILE WITH NON-ZERO LENGTH %lld I=%d", (longlong_t)dp->di_size, inumber); goto bogus; } for (j = 0; j < NDADDR; j++) { /* * It's a device, so all the block pointers * should be zero except for di_ordev. * di_ordev is overlayed on the block array, * but where varies between big and little * endian, so make sure that the only non-zero * element is the correct one. There can be * a device whose ordev is zero, so we can't * check for the reverse. */ if (dp->di_db[j] != 0 && &dp->di_db[j] != &dp->di_ordev) { if (debug) { (void) printf( "spec file di_db[%d] has %d\n", j, dp->di_db[j]); } pfatal( "SPECIAL FILE WITH NON-ZERO FRAGMENT LIST I=%d", inumber); goto bogus; } } for (j = 0; j < NIADDR; j++) { if (dp->di_ib[j] != 0) { if (debug) (void) printf( "special has %d at ib[%d]\n", dp->di_ib[j], j); pfatal( "SPECIAL FILE WITH NON-ZERO FRAGMENT LIST I=%d", inumber); goto bogus; } } } else { /* * This assignment is mostly here to appease lint, but * doesn't hurt. */ err = "Internal error: unexpected variant of having " "blocks past end of file I=%d"; clear = 0; /* * If it's not a device, it has to follow the * rules for files. In particular, no blocks after * the last one that di_size says is in use. */ for (j = ndb; j < NDADDR; j++) { if (dp->di_db[j] != 0) { if (debug) { (void) printf("bad file direct " "addr[%d]: block 0x%x " "format: 0%o\n", j, dp->di_db[j], dp->di_mode & IFMT); } err = "FILE WITH FRAGMENTS PAST END I=%d"; clear = 1; break; } } /* * Find last indirect pointer that should be in use, * and make sure any after it are clear. */ if (!clear) { for (j = 0, ndb -= NDADDR; ndb > 0; j++) { ndb /= NINDIR(&sblock); } for (; j < NIADDR; j++) { if (dp->di_ib[j] != 0) { if (debug) { (void) printf("bad file " "indirect addr: block %d\n", dp->di_ib[j]); } err = "FILE WITH FRAGMENTS PAST END I=%d"; clear = 2; break; } } } if (clear) { /* * The discarded blocks will be garbage- * collected in pass5. If we're told not to * discard them, it's just lost blocks, which * isn't worth setting iscorrupt for. */ pwarn(err, inumber); if (preen || reply("DISCARD EXCESS FRAGMENTS") == 1) { dp = ginode(inumber); if (clear == 1) { for (; j < NDADDR; j++) dp->di_db[j] = 0; j = 0; } for (; j < NIADDR; j++) dp->di_ib[j] = 0; inodirty(); dp = getnextrefresh(); if (preen) (void) printf(" (TRUNCATED)"); } } } if (ftypeok(dp) == 0) { pfatal("UNKNOWN FILE TYPE 0%o I=%d", dp->di_mode, inumber); goto bogus; } n_files++; TRACK_LNCNTP(inumber, lncntp[inumber] = dp->di_nlink); /* * We can't do anything about it right now, so note that its * processing is being delayed. Otherwise, we'd be changing * the block allocations out from under ourselves, which causes * no end of confusion. */ flags = statemap[inumber] & INDELAYD; /* * if errorlocked or logging, then open deleted files will * manifest as di_nlink <= 0 and di_mode != 0 * so skip them; they're ok. * Also skip anything already marked to be cleared. */ if (dp->di_nlink <= 0 && !((errorlocked || islog) && dp->di_mode == 0) && !(flags & INCLEAR)) { flags |= INZLINK; if (debug) (void) printf( "marking i=%d INZLINK; nlink %d, mode 0%o, islog %d\n", inumber, dp->di_nlink, dp->di_mode, islog); } switch (dp->di_mode & IFMT) { case IFDIR: case IFATTRDIR: if (dp->di_size == 0) { /* * INCLEAR means it will be ignored by passes 2 & 3. */ if ((dp->di_mode & IFMT) == IFDIR) (void) printf("ZERO-LENGTH DIR I=%d\n", inumber); else (void) printf("ZERO-LENGTH ATTRDIR I=%d\n", inumber); add_orphan_dir(inumber); flags |= INCLEAR; flags &= ~INZLINK; /* It will be cleared anyway */ } statemap[inumber] = DSTATE | flags; cacheino(dp, inumber); countdirs++; break; case IFSHAD: if (dp->di_size == 0) { (void) printf("ZERO-LENGTH SHADOW I=%d\n", inumber); flags |= INCLEAR; flags &= ~INZLINK; /* It will be cleared anyway */ } statemap[inumber] = SSTATE | flags; cacheacl(dp, inumber); break; default: statemap[inumber] = FSTATE | flags; } badblk = 0; dupblk = 0; idesc->id_number = inumber; idesc->id_fix = DONTKNOW; if (dp->di_size > (u_offset_t)MAXOFF_T) { largefile_count++; } (void) ckinode(dp, idesc, CKI_TRAVERSE); if (isdir && (idesc->id_firsthole >= 0)) check_dirholes(inumber, idesc); if (dp->di_blocks != idesc->id_entryno) { /* * The kernel releases any blocks it finds in the lists, * ignoring the block count itself. So, a bad count is * not grounds for setting iscorrupt. */ pwarn("INCORRECT DISK BLOCK COUNT I=%u (%d should be %d)", inumber, (uint32_t)dp->di_blocks, idesc->id_entryno); if (!preen && (reply("CORRECT") == 0)) return; dp = ginode(inumber); dp->di_blocks = idesc->id_entryno; iip = getinoinfo(inumber); if (iip != NULL) iip->i_isize = dp->di_size; inodirty(); if (preen) (void) printf(" (CORRECTED)\n"); } if (isdir && (dp->di_blocks == 0)) { /* * INCLEAR will cause passes 2 and 3 to skip it. */ (void) printf("DIR WITH ZERO BLOCKS I=%d\n", inumber); statemap[inumber] = DCLEAR; add_orphan_dir(inumber); } /* * Check that the ACL is on a valid file type */ shadow = dp->di_shadow; if (shadow != 0) { if (acltypeok(dp) == 0) { clear_attr_acl(inumber, -1, "NON-ZERO ACL REFERENCE, I=%d\n"); } else if ((shadow <= UFSROOTINO) || (shadow > maxinumber)) { clear_attr_acl(inumber, -1, "BAD ACL REFERENCE I=%d\n"); } else { registershadowclient(shadow, inumber, &shadowclientinfo); } } attrinode = dp->di_oeftflag; if (attrinode != 0) { if ((attrinode <= UFSROOTINO) || (attrinode > maxinumber)) { clear_attr_acl(attrinode, inumber, "BAD ATTRIBUTE REFERENCE TO I=%d FROM I=%d\n"); } else { dp = ginode(attrinode); if ((dp->di_mode & IFMT) != IFATTRDIR) { clear_attr_acl(attrinode, inumber, "BAD ATTRIBUTE DIR REF TO I=%d FROM I=%d\n"); } else if (dp->di_size == 0) { clear_attr_acl(attrinode, inumber, "REFERENCE TO ZERO-LENGTH ATTRIBUTE DIR I=%d from I=%d\n"); } else { registershadowclient(attrinode, inumber, &attrclientinfo); } } } return; /* * If we got here, we've not had the chance to see if a * directory has holes, but we know the directory's bad, * so it's safe to always return false (no holes found). * * Also, a pfatal() is always done before jumping here, so * we know we're not in preen mode. */ bogus: if (isdir) { /* * INCLEAR makes passes 2 & 3 skip it. */ statemap[inumber] = DCLEAR; add_orphan_dir(inumber); cacheino(dp, inumber); } else { statemap[inumber] = FCLEAR; } if (reply("CLEAR") == 1) { (void) tdelete((void *)inumber, &limbo_dirs, ino_t_cmp); freeino(inumber, TI_PARENT); inodirty(); } else { iscorrupt = 1; } } /* * Do fixup for bad acl/attr references. If PARENT is -1, then * we assume we're working on a shadow, otherwise an extended attribute. * FMT must be a printf format string, with one %d directive for * the inode number. */ static void clear_attr_acl(fsck_ino_t inumber, fsck_ino_t parent, char *fmt) { fsck_ino_t victim = inumber; struct dinode *dp; if (parent != -1) victim = parent; if (fmt != NULL) { if (parent == -1) pwarn(fmt, (int)inumber); else pwarn(fmt, (int)inumber, (int)parent); } if (debug) (void) printf("parent file/dir I=%d\nvictim I=%d", (int)parent, (int)victim); if (!preen && (reply("REMOVE REFERENCE") == 0)) { iscorrupt = 1; return; } dp = ginode(victim); if (parent == -1) { /* * The file had a bad shadow/acl, so lock it down * until someone can protect it the way they need it * to be (i.e., be conservatively paranoid). */ dp->di_shadow = 0; dp->di_mode &= IFMT; } else { dp->di_oeftflag = 0; } inodirty(); if (preen) (void) printf(" (CORRECTED)\n"); } /* * Check if we have holes in the directory's indirect * blocks. If there are, get rid of everything after * the first hole. */ static void check_dirholes(fsck_ino_t inumber, struct inodesc *idesc) { char pathbuf[MAXPATHLEN + 1]; getpathname(pathbuf, idesc->id_number, idesc->id_number); pfatal("I=%d DIRECTORY %s: CONTAINS EMPTY BLOCKS", idesc->id_number, pathbuf); if (reply("TRUNCATE AT FIRST EMPTY BLOCK") == 1) { /* * We found a hole, so get rid of it. */ collapse_dirhole(inumber, idesc); if (preen) (void) printf(" (TRUNCATED)\n"); } else { iscorrupt = 1; } } /* * Truncate a directory to its first hole. If there are non-holes * in the direct blocks after the problem block, move them down so * that there's somewhat less lossage. Doing this for indirect blocks * is left as an exercise for the reader. */ static void collapse_dirhole(fsck_ino_t inumber, struct inodesc *idesc) { offset_t new_size; int blocks; if (idesc->id_firsthole < 0) { return; } /* * Since truncino() adjusts the size, we don't need to do that here, * but we have to tell it what final size we want. * * We need to count from block zero up through the last block * before the hole. If the hole is in the indirect blocks, chop at * the start of the nearest level of indirection. Orphans will * get reconnected, so we're not actually losing anything by doing * it this way, and we're simplifying truncation significantly. */ new_size = idesc->id_firsthole * (offset_t)sblock.fs_bsize; blocks = howmany(new_size, sblock.fs_bsize); if (blocks > NDADDR) { if (blocks < (NDADDR + NINDIR(&sblock))) blocks = NDADDR; else if (blocks < (NDADDR + NINDIR(&sblock) + (NINDIR(&sblock) * NINDIR(&sblock)))) blocks = NDADDR + NINDIR(&sblock); else blocks = NDADDR + NINDIR(&sblock) + (NINDIR(&sblock) * NINDIR(&sblock)); new_size = blocks * sblock.fs_bsize; if (debug) (void) printf("to %lld (blocks %d)\n", (longlong_t)new_size, blocks); } truncino(inumber, new_size, TI_NOPARENT); /* * Technically, there are still the original number of fragments * associated with the object. However, that number is not used * to control anything, so we can do the in-memory truncation of * it without bad things happening. */ idesc->id_entryno = btodb(new_size); } int pass1check(struct inodesc *idesc) { int res = KEEPON; int anyout; int nfrags; daddr32_t lbn; daddr32_t fragno = idesc->id_blkno; struct dinode *dp; /* * If this is a fallocate'd file, block numbers may be stored * as negative. In that case negate the negative numbers. */ dp = ginode(idesc->id_number); if (dp->di_cflags & IFALLOCATE && fragno < 0) fragno = -fragno; if ((anyout = chkrange(fragno, idesc->id_numfrags)) != 0) { /* * Note that blkerror() exits when preening. */ blkerror(idesc->id_number, "OUT OF RANGE", fragno, idesc->id_lbn * sblock.fs_frag); dp = ginode(idesc->id_number); if ((((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR)) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = idesc->id_lbn; } if (++badblk >= MAXBAD) { pwarn("EXCESSIVE BAD FRAGMENTS I=%u", idesc->id_number); if (reply("CONTINUE") == 0) errexit("Program terminated."); /* * See discussion below as to why we don't * want to short-circuit the processing of * this inode. However, we know that this * particular block is bad, so we don't need * to go through the dup check loop. */ return (SKIP | STOP); } } /* * For each fragment, verify that it is a legal one (either * by having already found the entire run to be legal, or by * individual inspection), and if it is legal, see if we've * seen it before or not. If we haven't, note that we've seen * it and continue on. If we have (our in-core bitmap shows * it as already being busy), then this must be a duplicate * allocation. Whine and moan accordingly. * * Note that for full-block allocations, this will produce * a complaint for each fragment making up the block (i.e., * fs_frags' worth). Among other things, this could be * considered artificially inflating the dup-block count. * However, since it is possible that one file has a full * fs block allocated, but another is only claiming a frag * or two out of the middle, we'll just live it. */ for (nfrags = 0; nfrags < idesc->id_numfrags; fragno++, nfrags++) { if (anyout && chkrange(fragno, 1)) { /* bad fragment number */ res = SKIP; } else if (!testbmap(fragno)) { /* no other claims seen as yet */ note_used(fragno); } else { /* * We have a duplicate claim for the same fragment. * * blkerror() exits when preening. * * We want to report all the dups up until * hitting MAXDUP. Fortunately, blkerror()'s * side-effects on statemap[] are idempotent, * so the ``extra'' calls are harmless. */ lbn = idesc->id_lbn * sblock.fs_frag + nfrags; if (dupblk < MAXDUP) blkerror(idesc->id_number, "DUP", fragno, lbn); /* * Use ==, so we only complain once, no matter * how far over the limit we end up going. */ if (++dupblk == MAXDUP) { pwarn("EXCESSIVE DUPLICATE FRAGMENTS I=%u", idesc->id_number); if (reply("CONTINUE") == 0) errexit("Program terminated."); /* * If we stop the traversal here, then * there may be more dups in the * inode's block list that don't get * flagged. Later, if we're told to * clear one of the files claiming * these blocks, but not the other, we * will release blocks that are * actually still in use. An additional * fsck run would be necessary to undo * the damage. So, instead of the * traditional return (STOP) when told * to continue, we really do just continue. */ } (void) find_dup_ref(fragno, idesc->id_number, lbn, DB_CREATE | DB_INCR); } /* * id_entryno counts the number of disk blocks found. */ idesc->id_entryno += btodb(sblock.fs_fsize); } return (res); } static void note_used(daddr32_t frag) { n_blks++; setbmap(frag); } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include "fsck.h" static int pass1bcheck(struct inodesc *); void pass1b(void) { struct dinode *dp; struct inodesc idesc; fsck_ino_t inumber; /* * We can get STOP failures from ckinode() that * are completely independent of our dup checks. * If that were not the case, then we could track * when we've seen all of the dups and short- * circuit our search. As it is, we need to * keep going, so there's no point in looking * at what ckinode() returns to us. */ for (inumber = UFSROOTINO; inumber < maxino; inumber++) { init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass1bcheck; idesc.id_number = inumber; idesc.id_fix = DONTKNOW; dp = ginode(inumber); if (statemap[inumber] != USTATE) (void) ckinode(dp, &idesc, CKI_TRAVERSE); } } static int pass1bcheck(struct inodesc *idesc) { int res = KEEPON; int nfrags; daddr32_t lbn; daddr32_t blkno = idesc->id_blkno; for (nfrags = 0; nfrags < idesc->id_numfrags; blkno++, nfrags++) { if (chkrange(blkno, 1)) { res = SKIP; } else { /* * Note that we only report additional dup claimants * in this pass, as the first claimant found was * listed during pass 1. */ lbn = idesc->id_lbn * sblock.fs_frag + nfrags; if (find_dup_ref(blkno, idesc->id_number, lbn, DB_INCR)) blkerror(idesc->id_number, "DUP", blkno, lbn); } } return (res); } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include "fsck.h" #define MINDIRSIZE (sizeof (struct dirtemplate)) static int blksort(const void *, const void *); static int pass2check(struct inodesc *); void pass2(void) { struct dinode *dp, *dp2, *dpattr; struct inoinfo **inpp, *inp; struct inoinfo **inpend; struct inodesc curino; struct inodesc ldesc; struct dinode dino; char pathbuf[MAXPATHLEN + 1]; int found; int dirtype; caddr_t errmsg; struct shadowclientinfo *sci; switch (statemap[UFSROOTINO] & ~INDELAYD) { case USTATE: pfatal("ROOT INODE UNALLOCATED"); if (reply("ALLOCATE") == 0) { errexit("Program terminated."); } if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO) errexit("CANNOT ALLOCATE ROOT INODE\n"); break; case DCLEAR: pfatal("DUPS/BAD IN ROOT INODE"); if (reply("REALLOCATE") == 1) { freeino(UFSROOTINO, TI_NOPARENT); if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO) errexit("CANNOT ALLOCATE ROOT INODE\n"); break; } if (reply("CONTINUE") == 0) { errexit("Program terminated."); } break; case FSTATE: case FCLEAR: case FZLINK: case SSTATE: case SCLEAR: pfatal("ROOT INODE NOT DIRECTORY"); if (reply("REALLOCATE") == 1) { freeino(UFSROOTINO, TI_NOPARENT); if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO) errexit("CANNOT ALLOCATE ROOT INODE\n"); break; } if (reply("FIX") == 0) { ckfini(); errexit("Program terminated."); } dp = ginode(UFSROOTINO); dp->di_mode &= ~IFMT; dp->di_mode |= IFDIR; inodirty(); break; case DSTATE: case DZLINK: break; default: errexit("BAD STATE 0x%x FOR ROOT INODE\n", statemap[UFSROOTINO]); } statemap[UFSROOTINO] = DFOUND; /* * Technically, we do know who the parent is. However, * if this is set, then we'll get confused during the * second-dir-entry-is-dotdot test for the root inode. */ inp = getinoinfo(UFSROOTINO); if (inp != NULL && inp->i_dotdot != 0) inp->i_dotdot = 0; /* * Sort the directory list into disk block order. There's no * requirement to do this, but it may help improve our i/o times * somewhat. */ qsort((void *)inpsort, (size_t)inplast, sizeof (*inpsort), blksort); /* * Check the integrity of each directory. In general, we treat * attribute directories just like normal ones. Only the handling * of .. is really different. */ (void) memset(&dino, 0, sizeof (struct dinode)); dino.di_mode = IFDIR; inpend = &inpsort[inplast]; for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_isize == 0) continue; /* != DSTATE also covers case of == USTATE */ if (((statemap[inp->i_number] & STMASK) != DSTATE) || ((statemap[inp->i_number] & INCLEAR) == INCLEAR)) continue; if (inp->i_isize < (offset_t)MINDIRSIZE) { direrror(inp->i_number, "DIRECTORY TOO SHORT"); inp->i_isize = (offset_t)roundup(MINDIRSIZE, DIRBLKSIZ); if (reply("FIX") == 1) { dp = ginode(inp->i_number); dp->di_size = (u_offset_t)inp->i_isize; inodirty(); } else { iscorrupt = 1; } } if ((inp->i_isize & (offset_t)(DIRBLKSIZ - 1)) != 0) { getpathname(pathbuf, inp->i_number, inp->i_number); pwarn("DIRECTORY %s: LENGTH %lld NOT MULTIPLE OF %d", pathbuf, (longlong_t)inp->i_isize, DIRBLKSIZ); inp->i_isize = roundup(inp->i_isize, (offset_t)DIRBLKSIZ); if (preen || reply("ADJUST") == 1) { dp = ginode(inp->i_number); dp->di_size = (u_offset_t)roundup(inp->i_isize, (offset_t)DIRBLKSIZ); inodirty(); if (preen) (void) printf(" (ADJUSTED)\n"); } else { iscorrupt = 1; } } dp = ginode(inp->i_number); if ((dp->di_mode & IFMT) == IFATTRDIR && (dp->di_cflags & IXATTR) == 0) { pwarn("ATTRIBUTE DIRECTORY I=%d MISSING IXATTR FLAG", inp->i_number); if (preen || reply("CORRECT") == 1) { dp->di_cflags |= IXATTR; inodirty(); if (preen) (void) printf(" (CORRECTED)\n"); } } dp = &dino; dp->di_size = (u_offset_t)inp->i_isize; (void) memmove((void *)&dp->di_db[0], (void *)&inp->i_blks[0], inp->i_blkssize); init_inodesc(&curino); curino.id_type = DATA; curino.id_func = pass2check; curino.id_number = inp->i_number; curino.id_parent = inp->i_parent; curino.id_fix = DONTKNOW; (void) ckinode(dp, &curino, CKI_TRAVERSE); /* * Make sure we mark attrdirs as DFOUND, since they won't * be located during normal scan of standard directories. */ if (curino.id_parent == 0) { dpattr = ginode(inp->i_number); if ((dpattr->di_mode & IFMT) == IFATTRDIR) { for (sci = attrclientinfo; sci != NULL; sci = sci->next) { if (sci->shadow == inp->i_number) { curino.id_parent = sci->clients->client[0]; statemap[inp->i_number] = DFOUND; inp->i_parent = curino.id_parent; } } } } } /* * Now that the parents of all directories have been found, * make another pass to verify the value of .. */ for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_parent == 0 || inp->i_isize == 0) continue; /* * There are only directories in inpsort[], so only * directory-related states need to be checked. There * should never be any flags associated with USTATE. */ if ((statemap[inp->i_number] & (STMASK | INCLEAR)) == DCLEAR || statemap[inp->i_number] == USTATE) { continue; } if (statemap[inp->i_parent] == DFOUND && S_IS_DUNFOUND(statemap[inp->i_number])) { statemap[inp->i_number] = DFOUND | (statemap[inp->i_number] & INCLEAR); } if (inp->i_dotdot == inp->i_parent || inp->i_dotdot == (fsck_ino_t)-1) { continue; } if (inp->i_dotdot == 0) { inp->i_dotdot = inp->i_parent; fileerror(inp->i_parent, inp->i_number, "MISSING '..'"); if (reply("FIX") == 0) { iscorrupt = 1; continue; } dp = ginode(inp->i_number); found = 0; dirtype = (dp->di_mode & IFMT); /* * See if this is an attrdir that we located in pass1. * i.e. it was on an i_oeftflag of some other inode. * if it isn't found then we have an orphaned attrdir * that needs to be tossed into lost+found. */ if (dirtype == IFATTRDIR) { for (sci = attrclientinfo; sci != NULL; sci = sci->next) { if (sci->shadow == inp->i_number) { inp->i_parent = sci->clients->client[0]; found = 1; } } } /* * We've already proven there's no "..", so this * can't create a duplicate. */ if (makeentry(inp->i_number, inp->i_parent, "..")) { /* * is it an orphaned attrdir? */ if (dirtype == IFATTRDIR && found == 0) { /* * Throw it into lost+found */ if (linkup(inp->i_number, lfdir, NULL) == 0) { pwarn( "Unable to move attrdir I=%d to lost+found\n", inp->i_number); iscorrupt = 1; } maybe_convert_attrdir_to_dir( inp->i_number); } if (dirtype == IFDIR) { LINK_RANGE(errmsg, lncntp[inp->i_parent], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, inp->i_parent, IFDIR, &ldesc); if (statemap[inp->i_parent] != USTATE) { /* * iscorrupt is * already set */ continue; } } TRACK_LNCNTP(inp->i_parent, lncntp[inp->i_parent]--); } continue; } pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n"); iscorrupt = 1; inp->i_dotdot = (fsck_ino_t)-1; continue; } dp2 = ginode(inp->i_parent); if ((dp2->di_mode & IFMT) == IFATTRDIR) { continue; } fileerror(inp->i_parent, inp->i_number, "BAD INODE NUMBER FOR '..'"); if (reply("FIX") == 0) { iscorrupt = 1; continue; } LINK_RANGE(errmsg, lncntp[inp->i_dotdot], 1); if (errmsg != NULL) { LINK_CLEAR(errmsg, inp->i_dotdot, IFDIR, &ldesc); if (statemap[inp->i_dotdot] != USTATE) { /* iscorrupt is already set */ continue; } } TRACK_LNCNTP(inp->i_dotdot, lncntp[inp->i_dotdot]++); LINK_RANGE(errmsg, lncntp[inp->i_parent], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, inp->i_parent, IFDIR, &ldesc); if (statemap[inp->i_parent] != USTATE) { /* iscorrupt is already set */ continue; } } TRACK_LNCNTP(inp->i_parent, lncntp[inp->i_parent]--); inp->i_dotdot = inp->i_parent; (void) changeino(inp->i_number, "..", inp->i_parent); } /* * Mark all the directories that can be found from the root. */ propagate(); } /* * Sanity-check a single directory entry. Which entry is being * examined is tracked via idesc->id_entryno. There are two * special ones, 0 (.) and 1 (..). Those have to exist in order * in the first two locations in the directory, and have the usual * properties. All other entries have to not be for either of * the special two, and the inode they reference has to be * reasonable. * * This is only called from dirscan(), which looks for the * ALTERED flag after each invocation. If it finds it, the * relevant buffer gets pushed out, so we don't have to worry * about it here. */ #define PASS2B_PROMPT "REMOVE DIRECTORY ENTRY FROM I=%d" static int pass2check(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; struct inodesc ldesc; struct inoinfo *inp; short reclen, entrysize; int ret = 0; int act, update_lncntp; struct dinode *dp, *pdirp, *attrdirp; caddr_t errmsg; struct direct proto; char namebuf[MAXPATHLEN + 1]; char pathbuf[MAXPATHLEN + 1]; int isattr; int pdirtype; int breakout = 0; int dontreconnect; if (idesc->id_entryno != 0) goto chk1; /* * check for "." */ if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) { if (dirp->d_ino != idesc->id_number) { direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'"); dirp->d_ino = idesc->id_number; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } goto chk1; } /* * Build up a new one, and make sure there's room to put * it where it belongs. */ direrror(idesc->id_number, "MISSING '.'"); proto.d_ino = idesc->id_number; proto.d_namlen = 1; (void) strcpy(proto.d_name, "."); entrysize = DIRSIZ(&proto); if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) { pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n", dirp->d_name); iscorrupt = 1; } else if ((int)dirp->d_reclen < entrysize) { pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n"); iscorrupt = 1; } else if ((int)dirp->d_reclen < 2 * entrysize) { /* * No room for another entry after us ("." is the * smallest entry you can have), so just put all * of the old entry's space into the new entry. * * Because we don't touch id_entryno, we end up going * through the chk2 tests as well. */ proto.d_reclen = dirp->d_reclen; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } else { /* * There's enough room for an entire additional entry * after this, so create the "." entry and follow it * with an empty entry that covers the rest of the * space. * * The increment of id_entryno means we'll skip the * "." case of chk1, doing the ".." tests instead. * Since we know that there's not a ".." where it * should be (because we just created an empty entry * there), that's the best way of getting it recreated * as well. */ reclen = dirp->d_reclen - entrysize; proto.d_reclen = entrysize; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); idesc->id_entryno++; /* * Make sure the link count is in range before updating * it. This makes the assumption that the link count * for this inode included one for ".", even though * there wasn't a "." entry. Even if that's not true, * it's a reasonable working hypothesis, and the link * count verification done in pass4 will fix it for * us anyway. */ LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc); if (statemap[dirp->d_ino] == USTATE) { /* * The inode got zapped, so reset the * directory entry. Extend it to also * cover the space we were going to make * into a new entry. */ dirp->d_ino = 0; dirp->d_reclen += reclen; ret |= ALTERED; return (ret); } } /* * Create the new empty entry. */ /* LINTED pointer cast alignment (entrysize is valid) */ dirp = (struct direct *)((char *)(dirp) + entrysize); (void) memset((void *)dirp, 0, (size_t)reclen); dirp->d_reclen = reclen; /* * Did the user want us to create a new "."? This * query assumes that the direrror(MISSING) was the * last thing printed, so if the LINK_RANGE() check * fails, it can't pass through here. */ if (reply("FIX") == 1) { TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]--); ret |= ALTERED; } else { iscorrupt = 1; } } /* * XXX The next few lines are needed whether we're processing "." * or "..". However, there are some extra steps still needed * for the former, hence the big block of code for * id_entryno == 0. Alternatively, there could be a label just * before this comment, and everything through the end of that * block moved there. In some ways, that might make the * control flow more logical (factoring out to separate functions * would be even better). */ chk1: if (idesc->id_entryno > 1) goto chk2; inp = getinoinfo(idesc->id_number); if (inp == NULL) { /* * This is a can't-happen, since inodes get cached before * we get called on them. */ errexit("pass2check got NULL from getinoinfo at chk1 I=%d\n", idesc->id_number); } proto.d_ino = inp->i_parent; proto.d_namlen = 2; (void) strcpy(proto.d_name, ".."); entrysize = DIRSIZ(&proto); if (idesc->id_entryno == 0) { /* * We may not actually need to split things up, but if * there's room to do so, we should, as that implies * that the "." entry is larger than it is supposed * to be, and therefore there's something wrong, albeit * possibly harmlessly so. */ reclen = DIRSIZ(dirp); if ((int)dirp->d_reclen < reclen + entrysize) { /* * Not enough room for inserting a ".." after * the "." entry. */ goto chk2; } /* * There's enough room for an entire additional entry * after "."'s, so split it up. There's no reason "." * should be bigger than the minimum, so shrink it to * fit, too. Since by the time we're done with this * part, dirp will be pointing at where ".." should be, * update id_entryno to show that that's the entry * we're on. */ proto.d_reclen = dirp->d_reclen - reclen; dirp->d_reclen = reclen; idesc->id_entryno++; if (dirp->d_ino > 0 && dirp->d_ino <= maxino) { /* * Account for the link to ourselves. */ LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc); if (statemap[dirp->d_ino] == USTATE) { /* * We were going to split the entry * up, but the link count overflowed. * Since we got rid of the inode, * we need to also zap the directory * entry, and restoring the original * state of things is the least-bad * result. */ dirp->d_ino = 0; dirp->d_reclen += proto.d_reclen; ret |= ALTERED; return (ret); } } TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--); /* * Make sure the new entry doesn't get interpreted * as having actual content. */ /* LINTED pointer cast alignment (reclen is valid) */ dirp = (struct direct *)((char *)(dirp) + reclen); (void) memset((void *)dirp, 0, (size_t)proto.d_reclen); dirp->d_reclen = proto.d_reclen; } else { /* * Everything was fine, up until we realized that * the indicated inode was impossible. By clearing * d_ino here, we'll trigger the recreation of it * down below, using i_parent. Unlike the other * half of this if(), we're everything so it shows * that we're still on the "." entry. */ fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE"); dirp->d_ino = 0; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } } /* * Record this ".." inode, but only if we haven't seen one before. * If this isn't the first, it'll get cleared below, and so we * want to remember the entry that'll still be around later. */ if (dirp->d_ino != 0 && inp->i_dotdot == 0 && strcmp(dirp->d_name, "..") == 0) { inp->i_dotdot = dirp->d_ino; goto chk2; } if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) { fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n", dirp->d_name); iscorrupt = 1; inp->i_dotdot = (fsck_ino_t)-1; } else if ((int)dirp->d_reclen < entrysize) { fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n"); /* XXX Same consideration as immediately above. */ iscorrupt = 1; inp->i_dotdot = (fsck_ino_t)-1; } else if (inp->i_parent != 0) { /* * We know the parent, so fix now. */ proto.d_ino = inp->i_dotdot = inp->i_parent; fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); /* * Lint won't be quiet about d_reclen being set but not * used. It apparently doesn't understand the implications * of calling memmove(), and won't believe us that it's ok. */ proto.d_reclen = dirp->d_reclen; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } else if (inp->i_number == UFSROOTINO) { /* * Always know parent of root inode, so fix now. */ proto.d_ino = inp->i_dotdot = inp->i_parent = UFSROOTINO; fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); /* * Lint won't be quiet about d_reclen being set but not * used. It apparently doesn't understand the implications * of calling memmove(), and won't believe us that it's ok. */ proto.d_reclen = dirp->d_reclen; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } idesc->id_entryno++; if (dirp->d_ino != 0) { LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc); if (statemap[dirp->d_ino] == USTATE) { dirp->d_ino = 0; ret |= ALTERED; } } TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--); } return (ret|KEEPON); chk2: if (dirp->d_ino == 0) return (ret|KEEPON); if (dirp->d_namlen <= 2 && dirp->d_name[0] == '.' && idesc->id_entryno >= 2) { if (dirp->d_namlen == 1) { direrror(idesc->id_number, "EXTRA '.' ENTRY"); dirp->d_ino = 0; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } return (KEEPON | ret); } if (dirp->d_name[1] == '.') { direrror(idesc->id_number, "EXTRA '..' ENTRY"); dirp->d_ino = 0; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } return (KEEPON | ret); } } /* * Because of this increment, all tests for skipping . and .. * below are ``> 2'', not ``> 1'' as would logically be expected. */ idesc->id_entryno++; act = -1; /* * The obvious check would be for d_ino < UFSROOTINO. However, * 1 is a valid inode number. Although it isn't currently used, * as it was once the bad block list, there's nothing to prevent * it from acquiring a new purpose in the future. So, don't * arbitrarily disallow it. We don't test for <= zero, because * d_ino is unsigned. */ update_lncntp = 0; if (dirp->d_ino > maxino || dirp->d_ino == 0) { fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE"); act = (reply(PASS2B_PROMPT, idesc->id_number) == 1); } else { again: update_lncntp = 0; switch (statemap[dirp->d_ino] & ~(INDELAYD)) { case USTATE: if (idesc->id_entryno <= 2) break; fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED"); act = (reply(PASS2B_PROMPT, idesc->id_number) == 1); break; case DCLEAR: case FCLEAR: case SCLEAR: if (idesc->id_entryno <= 2) break; dp = ginode(dirp->d_ino); if (statemap[dirp->d_ino] == DCLEAR) { errmsg = ((dp->di_mode & IFMT) == IFATTRDIR) ? "REFERENCE TO ZERO LENGTH ATTRIBUTE DIRECTORY" : "REFERENCE TO ZERO LENGTH DIRECTORY"; inp = getinoinfo(dirp->d_ino); if (inp == NULL) { /* * The inode doesn't exist, as all * should be cached by now. This * gets caught by the range check * above, and so it is a can't-happen * at this point. */ errexit("pass2check found a zero-len " "reference to bad I=%d\n", dirp->d_ino); } if (inp->i_parent != 0) { (void) printf( "Multiple links to I=%d, link counts wrong, rerun fsck\n", inp->i_number); iscorrupt = 1; } } else if (statemap[dirp->d_ino] == SCLEAR) { /* * In theory, this is a can't-happen, * because shadows don't appear in directory * entries. However, an inode might've * been reused without a stale directory * entry having been cleared, so check * for it just in case. We'll check for * the no-dir-entry shadows in pass3b(). */ errmsg = "ZERO LENGTH SHADOW"; } else { errmsg = "DUP/BAD"; } fileerror(idesc->id_number, dirp->d_ino, errmsg); if ((act = reply(PASS2B_PROMPT, idesc->id_number)) == 1) break; /* * Not doing anything about it, so just try * again as whatever the base type was. * * fileerror() invalidated dp. Lint thinks this * is unnecessary, but we know better. */ dp = ginode(dirp->d_ino); statemap[dirp->d_ino] &= STMASK; TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino] = 0); goto again; case DSTATE: case DZLINK: if (statemap[idesc->id_number] == DFOUND) { statemap[dirp->d_ino] = DFOUND; } /* FALLTHROUGH */ case DFOUND: /* * This is encouraging the best-practice of not * hard-linking directories. It's legal (see POSIX), * but not a good idea. So, don't consider it an * instance of corruption, but offer to nuke it. */ inp = getinoinfo(dirp->d_ino); if (inp == NULL) { /* * Same can't-happen argument as in the * zero-len case above. */ errexit("pass2check found bad reference to " "hard-linked directory I=%d\n", dirp->d_ino); } dp = ginode(idesc->id_number); if (inp->i_parent != 0 && idesc->id_entryno > 2 && ((dp->di_mode & IFMT) != IFATTRDIR)) { /* * XXX For nested dirs, this can report * the same name for both paths. */ getpathname(pathbuf, idesc->id_number, dirp->d_ino); getpathname(namebuf, dirp->d_ino, dirp->d_ino); pwarn( "%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s\n", pathbuf, namebuf); if (preen) { (void) printf(" (IGNORED)\n"); } else { act = reply(PASS2B_PROMPT, idesc->id_number); if (act == 1) { update_lncntp = 1; broke_dir_link = 1; break; } } } if ((idesc->id_entryno > 2) && (inp->i_extattr != idesc->id_number)) { inp->i_parent = idesc->id_number; } /* FALLTHROUGH */ case FSTATE: case FZLINK: /* * There's nothing to do for normal file-like * things. Extended attributes come through * here as well, though, and for them, .. may point * to a file. In this situation we don't want * to decrement link count as it was already * decremented when the entry was seen in the * directory it actually lives in. */ pdirp = ginode(idesc->id_number); pdirtype = (pdirp->di_mode & IFMT); dp = ginode(dirp->d_ino); isattr = (dp->di_cflags & IXATTR); act = -1; if (pdirtype == IFATTRDIR && (strcmp(dirp->d_name, "..") == 0)) { dontreconnect = 0; if (dp->di_oeftflag != 0) { attrdirp = ginode(dp->di_oeftflag); /* * is it really an attrdir? * if so, then don't do anything. */ if ((attrdirp->di_mode & IFMT) == IFATTRDIR) dontreconnect = 1; dp = ginode(dirp->d_ino); } /* * Rare corner case - the attrdir's .. * points to the attrdir itself. */ if (dirp->d_ino == idesc->id_number) { dontreconnect = 1; TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]--); } /* * Lets see if we have an orphaned attrdir * that thinks it belongs to this file. * Only re-connect it if the current * attrdir is 0 or not an attrdir. */ if ((dp->di_oeftflag != idesc->id_number) && (dontreconnect == 0)) { fileerror(idesc->id_number, dirp->d_ino, "Attribute directory I=%d not " "attached to file I=%d\n", idesc->id_number, dirp->d_ino); if ((act = reply("FIX")) == 1) { dp = ginode(dirp->d_ino); if (debug) (void) printf( "debug: changing i=%d's oeft from %d ", dirp->d_ino, dp->di_oeftflag); dp->di_oeftflag = idesc->id_number; if (debug) (void) printf("to %d\n", dp->di_oeftflag); inodirty(); registershadowclient( idesc->id_number, dirp->d_ino, &attrclientinfo); } dp = ginode(dirp->d_ino); } /* * This can only be true if we've modified * an inode/xattr connection, and we * don't keep track of those in the link * counts. So, skipping the checks just * after this is not a problem. */ if (act > 0) return (KEEPON | ALTERED); /* * Don't screw up link counts for directories. * If we aren't careful we can perform * an extra decrement, since the .. of * an attrdir could be either a file or a * directory. If it's a file then its link * should be correct after it is seen when the * directory it lives in scanned. */ if ((pdirtype == IFATTRDIR) && ((dp->di_mode & IFMT) == IFDIR)) breakout = 1; if ((dp->di_mode & IFMT) != IFDIR) breakout = 1; } else if ((pdirtype != IFATTRDIR) || (strcmp(dirp->d_name, ".") != 0)) { if ((pdirtype == IFDIR) && isattr) { fileerror(idesc->id_number, dirp->d_ino, "File should NOT be marked as " "extended attribute\n"); if ((act = reply("FIX")) == 1) { dp = ginode(dirp->d_ino); if (debug) (void) printf( "changing i=%d's cflags from 0x%x to ", dirp->d_ino, dp->di_cflags); dp->di_cflags &= ~IXATTR; if (debug) (void) printf("0x%x\n", dp->di_cflags); inodirty(); if ((dp->di_mode & IFMT) == IFATTRDIR) { dp->di_mode &= ~IFATTRDIR; dp->di_mode |= IFDIR; inodirty(); pdirp = ginode( idesc->id_number); if (pdirp->di_oeftflag != 0) { pdirp->di_oeftflag = 0; inodirty(); } } } } else { if (pdirtype == IFATTRDIR && (isattr == 0)) { fileerror(idesc->id_number, dirp->d_ino, "File should BE marked as " "extended attribute\n"); if ((act = reply("FIX")) == 1) { dp = ginode( dirp->d_ino); dp->di_cflags |= IXATTR; /* * Make sure it's a file * while we're at it. */ dp->di_mode &= ~IFMT; dp->di_mode |= IFREG; inodirty(); } } } } if (breakout == 0 || dontreconnect == 0) { TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--); if (act > 0) return (KEEPON | ALTERED); } break; case SSTATE: errmsg = "ACL IN DIRECTORY"; fileerror(idesc->id_number, dirp->d_ino, errmsg); act = (reply(PASS2B_PROMPT, idesc->id_number) == 1); break; default: errexit("BAD STATE 0x%x FOR INODE I=%d", statemap[dirp->d_ino], dirp->d_ino); } } if (act == 0) { iscorrupt = 1; } if (act <= 0) return (ret|KEEPON); if (update_lncntp) { LINK_RANGE(errmsg, lncntp[idesc->id_number], 1); if (errmsg != NULL) { LINK_CLEAR(errmsg, idesc->id_number, IFDIR, &ldesc); if (statemap[idesc->id_number] == USTATE) { idesc->id_number = 0; ret |= ALTERED; } } TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]++); } dirp->d_ino = 0; return (ret|KEEPON|ALTERED); } #undef PASS2B_PROMPT /* * Routine to sort disk blocks. */ static int blksort(const void *arg1, const void *arg2) { const struct inoinfo **inpp1 = (const struct inoinfo **)arg1; const struct inoinfo **inpp2 = (const struct inoinfo **)arg2; return ((*inpp1)->i_blks[0] - (*inpp2)->i_blks[0]); } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include "fsck.h" static int pass3acheck(struct inodesc *); static void setcurino(struct inodesc *, struct dinode *, struct inoinfo *); void pass3a(void) { caddr_t flow; struct inoinfo **inpp, *inp; fsck_ino_t orphan; int loopcnt; int state; struct shadowclientinfo *sci, *sci_victim, *sci_prev, **sci_rootp; struct inodesc curino; struct dinode *dp; struct inodesc idesc; char namebuf[MAXNAMLEN + 1]; for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--) { inp = *inpp; state = statemap[inp->i_number]; if (inp->i_number == UFSROOTINO || (inp->i_parent != 0 && !S_IS_DUNFOUND(state))) continue; if (state == DCLEAR || state == USTATE || (state & INORPHAN)) continue; /* * If we are running with logging and we come * across unreferenced directories, we just leave * them in DSTATE which will cause them to be pitched * in pass 4. */ if (preen && !iscorrupt && islog && S_IS_DUNFOUND(state)) { if (inp->i_dotdot >= UFSROOTINO) { LINK_RANGE(flow, lncntp[inp->i_dotdot], 1); if (flow != NULL) { dp = ginode(inp->i_dotdot); LINK_CLEAR(flow, inp->i_dotdot, dp->di_mode, &idesc); if (statemap[inp->i_dotdot] == USTATE) continue; } TRACK_LNCNTP(inp->i_dotdot, lncntp[inp->i_dotdot]++); } continue; } for (loopcnt = 0; ; loopcnt++) { orphan = inp->i_number; /* * Skip out if we aren't connected to the name * space, or our parent is connected, or we've * looked at too many directories. Our parent * being connected means that orphan is the * first ancestor of *inpp with questionable * antecedents. */ if (inp->i_parent == 0 || !INO_IS_DUNFOUND(inp->i_parent) || loopcnt > numdirs) break; inp = getinoinfo(inp->i_parent); /* * Can't happen, because a non-zero parent's already * been seen and therefore cached. */ if (inp == NULL) errexit("pass3 could not find cached " "inode I=%d\n", inp->i_parent); } /* * Already did this one. Don't bother the user * with redundant questions. */ if (statemap[orphan] & INORPHAN) continue; /* * A link count of 0 with parent and .. inodes of 0 * indicates a partly deleted directory. * Clear it. */ dp = ginode(orphan); if (dp->di_nlink == 0 && inp->i_dotdot == 0 && inp->i_parent == 0) { /* * clri() just uses curino.id_number; in other * words, it won't use the callback that setcurino() * puts in. */ setcurino(&curino, dp, inp); clri(&curino, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); /* * If we didn't clear it, at least mark it so * we don't waste time on it again. */ if (statemap[orphan] != USTATE) { statemap[orphan] |= INORPHAN; } continue; } /* * We can call linkup() multiple times on the same directory * inode, if we were told not to reconnect it the first time. * This is because we find it as a disconnected parent of * of its children (and mark it found), and then finally get * to it in the inpsort array. This is better than in the * past, where we'd call it every time we found it as a * child's parent. Ideally, we'd suppress even the second * query, but that confuses pass 4's interpretation of * the state flags. */ if (loopcnt <= countdirs) { if (linkup(orphan, inp->i_dotdot, NULL)) { /* * Bookkeeping for any sort of relinked * directory. */ inp->i_dotdot = lfdir; inp->i_parent = inp->i_dotdot; statemap[orphan] &= ~(INORPHAN); } else { statemap[orphan] |= INORPHAN; } propagate(); continue; } /* * We visited more directories than exist in the * filesystem. The only way to do that is if there's * a loop. */ pfatal("ORPHANED DIRECTORY LOOP DETECTED I=%d\n", orphan); /* * Can never get here with inp->i_parent zero, because * of the interactions between the for() and the * if (loopcnt <= countdirs) above. */ init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_number = inp->i_parent; idesc.id_parent = orphan; idesc.id_func = findname; idesc.id_name = namebuf; namebuf[0] = '\0'; /* * Theoretically, this lookup via ckinode can't fail * (if orphan doesn't exist in i_parent, then i_parent * would not have been filled in by pass2check()). * However, if we're interactive, we want to at least * attempt to continue. The worst case is that it * gets reconnected as #nnn into lost+found instead of * to its old parent with its old name. */ if ((ckinode(ginode(inp->i_parent), &idesc, CKI_TRAVERSE) & FOUND) == 0) pfatal("COULD NOT FIND NAME IN PARENT DIRECTORY"); if (linkup(orphan, inp->i_parent, namebuf)) { if (cleardirentry(inp->i_parent, orphan) & FOUND) { LFDIR_LINK_RANGE_NORVAL(flow, lncntp[lfdir], 1, &idesc); TRACK_LNCNTP(orphan, lncntp[orphan]++); } inp->i_parent = inp->i_dotdot = lfdir; LFDIR_LINK_RANGE_NORVAL(flow, lncntp[lfdir], -1, &idesc); TRACK_LNCNTP(lfdir, lncntp[lfdir]--); statemap[orphan] = DFOUND; } else { /* * Represents a on-disk leak, not an inconsistency, * so don't set iscorrupt. Such leaks are harmless * in the context of discrepancies that the kernel * will panic over. * * We don't care if tsearch() returns non-NULL * != orphan, since there's no dynamic memory * to free here. */ if (tsearch((void *)orphan, &limbo_dirs, ino_t_cmp) == NULL) errexit("out of memory"); statemap[orphan] |= INORPHAN; continue; } propagate(); } /* * The essence of the inner loop is to update the inode of * every shadow or attribute inode's lncntp[] by the number of * links we've found to them in pass 2 and above. Logically, * all that is needed is just the one line: * * lncntp[sci->shadow] -= sci->totalclients; * * However, there's the possibility of wrapping the link count * (this is especially true for shadows, which are expected to * be shared amongst many files). This means that we have to * range-check before changing anything, and if the check * fails, offer to clear the shadow or attribute. If we do * clear it, then we have to remove it from the linked list of * all of the type of inodes that we're going through. * * Just to make things a little more complicated, these are * singly-linked lists, so we have to do all the extra * bookkeeping that goes along with that as well. * * The only connection between the shadowclientinfo and * attrclientinfo lists is that they use the same underlying * struct. Both need this scan, so the outer loop is just to * pick which one we're working on at the moment. There is no * requirement as to which of these lists is scanned first. */ for (loopcnt = 0; loopcnt < 2; loopcnt++) { if (loopcnt == 0) sci_rootp = &shadowclientinfo; else sci_rootp = &attrclientinfo; sci = *sci_rootp; sci_prev = NULL; while (sci != NULL) { sci_victim = NULL; LINK_RANGE(flow, lncntp[sci->shadow], -(sci->totalClients)); if (flow != NULL) { /* * Overflowed the link count. */ dp = ginode(sci->shadow); LINK_CLEAR(flow, sci->shadow, dp->di_mode, &idesc); if (statemap[sci->shadow] == USTATE) { /* * It's been cleared, fix the * lists. */ if (sci_prev == NULL) { *sci_rootp = sci->next; } else { sci_prev->next = sci->next; } sci_victim = sci; } } /* * If we did not clear the shadow, then we * need to update the count and advance the * previous pointer. Otherwise, finish the * clean up once we're done with the struct. */ if (sci_victim == NULL) { TRACK_LNCNTP(sci->shadow, lncntp[sci->shadow] -= sci->totalClients); sci_prev = sci; } sci = sci->next; if (sci_victim != NULL) deshadow(sci_victim, NULL); } } } /* * This is used to verify the cflags of files * under a directory that used to be an attrdir. */ static int pass3acheck(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; int n = 0, ret = 0; struct dinode *dp, *pdirp; int isattr; int dirtype; int inotype; if (dirp->d_ino == 0) return (KEEPON); idesc->id_entryno++; if ((strcmp(dirp->d_name, ".") == 0) || (strcmp(dirp->d_name, "..") == 0)) { return (KEEPON); } switch (statemap[dirp->d_ino] & ~(INDELAYD)) { case DSTATE: case DFOUND: case FSTATE: /* * Accept DSTATE and DFOUND so we can handle normal * directories as well as xattr directories. * * For extended attribute directories .. may point * to a file. In this situation we don't want * to decrement link count as it was already * decremented when the entry was seen and decremented * in the directory it actually lives in. */ dp = ginode(dirp->d_ino); isattr = (dp->di_cflags & IXATTR); inotype = (dp->di_mode & IFMT); pdirp = ginode(idesc->id_number); dirtype = (pdirp->di_mode & IFMT); /* * IXATTR indicates that an object is itself an extended * attribute. An IFMT of IFATTRDIR means we are looking * at a directory which contains files which should all * have IXATTR set. The IFATTRDIR case was handled in * pass 2b. * * Note that the following code actually handles * anything that's marked as an extended attribute but * in a regular directory, not just files. */ if ((dirtype == IFDIR) && isattr) { fileerror(idesc->id_number, dirp->d_ino, "%s I=%d should NOT be marked as extended attribute\n", (inotype == IFDIR) ? "Directory" : "File", dirp->d_ino); dp = ginode(dirp->d_ino); dp->di_cflags &= ~IXATTR; if ((n = reply("FIX")) == 1) { inodirty(); } else { iscorrupt = 1; } if (n != 0) return (KEEPON | ALTERED); } break; default: errexit("PASS3: BAD STATE %d FOR INODE I=%d", statemap[dirp->d_ino], dirp->d_ino); /* NOTREACHED */ } if (n == 0) return (ret|KEEPON); return (ret|KEEPON|ALTERED); } static void setcurino(struct inodesc *idesc, struct dinode *dp, struct inoinfo *inp) { (void) memmove((void *)&dp->di_db[0], (void *)&inp->i_blks[0], inp->i_blkssize); init_inodesc(idesc); idesc->id_number = inp->i_number; idesc->id_parent = inp->i_parent; idesc->id_fix = DONTKNOW; idesc->id_type = DATA; idesc->id_func = pass3acheck; } void maybe_convert_attrdir_to_dir(fsck_ino_t orphan) { struct dinode *dp = ginode(orphan); struct inoinfo *inp = getinoinfo(orphan); struct inodesc idesc; if (dp->di_cflags & IXATTR) { dp->di_cflags &= ~IXATTR; inodirty(); } if ((dp->di_mode & IFMT) == IFATTRDIR) { dp->di_mode &= ~IFATTRDIR; dp->di_mode |= IFDIR; inodirty(); setcurino(&idesc, dp, inp); idesc.id_fix = FIX; idesc.id_filesize = dp->di_size; (void) ckinode(dp, &idesc, CKI_TRAVERSE); } } /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" /* * We can be run on multiple filesystems (processed serially), so * these need to be re-initialized each time we start the pass. */ static caddr_t aclbuf; /* hold acl's for parsing */ static int64_t aclbufoff; /* offset into aclbuf */ static int64_t maxaclsize; /* how big aclbuf is */ static int aclblksort(const void *, const void *); static int bufchk(char *, int64_t, fsck_ino_t); static void clear_shadow_client(struct shadowclientinfo *, struct shadowclients *, int); void pass3b(void) { fsck_ino_t inumber; struct dinode *dp; struct inoinfo *aclp; struct inodesc curino; struct shadowclientinfo *sci; struct shadowclients *scc; int64_t acl_size_limit; int i; /* * Sort the acl list into disk block order. */ qsort((char *)aclpsort, (int)aclplast, sizeof (*aclpsort), aclblksort); /* * Scan all the acl inodes, finding the largest acl file. * * The largest legal size is (4 * MAX_ACL_ENTRIES + 8) entries. * The four are the categories of specific users, specific * groups, default specific users, and default specific groups. * The eight are the entries for the owning user/group/other/class * plus the equivalent defaults. * * We double this to allow for a truly worst-case but legal * situation of every single acl having its own fsd_t wrapper. * Doubling is a bit pessimistic (sizeof (acl_t) > sizeof (fsd_t)). */ acl_size_limit = sizeof (ufs_acl_t) * (4 * MAX_ACL_ENTRIES + 8); acl_size_limit *= 2; maxaclsize = 0; for (inumber = 0; inumber < aclplast; inumber++) { aclp = aclpsort[inumber]; if ((int64_t)aclp->i_isize > acl_size_limit) { (void) printf( "ACL I=%d is excessively large (%lld > %lld)", inumber, (longlong_t)aclp->i_isize, (longlong_t)acl_size_limit); if (preen) { (void) printf(" (IGNORING)\n"); } else if (reply("CLEAR") == 1) { freeino(inumber, TI_PARENT); } else { iscorrupt = 1; (void) printf("IGNORING SHADOW I=%d\n", inumber); } continue; } if ((int64_t)aclp->i_isize > maxaclsize) maxaclsize = (int64_t)aclp->i_isize; } maxaclsize = ((maxaclsize / sblock.fs_bsize) + 1) * sblock.fs_bsize; if (maxaclsize == 0) goto noacls; if (aclbuf != NULL) { free((void *)aclbuf); } if ((aclbuf = malloc(maxaclsize)) == NULL) { errexit("cannot alloc %lld bytes for aclbuf\n", (longlong_t)maxaclsize); } /* * Scan all the acl inodes, checking contents */ for (inumber = 0; inumber < aclplast; inumber++) { aclp = aclpsort[inumber]; if ((int64_t)aclp->i_isize > acl_size_limit) { continue; } if ((statemap[aclp->i_number] & STMASK) != SSTATE) { continue; } dp = ginode(aclp->i_number); init_inodesc(&curino); curino.id_fix = FIX; curino.id_type = ACL; curino.id_func = pass3bcheck; curino.id_number = aclp->i_number; curino.id_filesize = aclp->i_isize; aclbufoff = 0; (void) memset(aclbuf, 0, (size_t)maxaclsize); if ((ckinode(dp, &curino, CKI_TRAVERSE) & KEEPON) == 0 || bufchk(aclbuf, (int64_t)aclp->i_isize, aclp->i_number)) { dp = ginode(aclp->i_number); /* defensive no-op */ if (dp->di_nlink <= 0) { statemap[aclp->i_number] = FSTATE; continue; } (void) printf("ACL I=%d BAD/CORRUPT", aclp->i_number); if (preen || reply("CLEAR") == 1) { if (preen) (void) printf("\n"); freeino(aclp->i_number, TI_PARENT); } else { iscorrupt = 1; } } } /* * Now scan all shadow inodes, checking that any inodes that previously * had an acl still have an acl. */ noacls: for (sci = shadowclientinfo; sci; sci = sci->next) { if ((statemap[sci->shadow] & STMASK) != SSTATE) { for (scc = sci->clients; scc; scc = scc->next) { for (i = 0; i < scc->nclients; i++) { clear_shadow_client(sci, scc, i); } } } } free((void *)aclbuf); aclbuf = NULL; } static void clear_shadow_client(struct shadowclientinfo *sci, struct shadowclients *scc, int client) { int suppress_update = 0; caddr_t flow; struct inodesc ldesc; struct dinode *dp; (void) printf("I=%d HAS BAD/CLEARED ACL I=%d", scc->client[client], sci->shadow); if (preen || reply("FIX") == 1) { if (preen) (void) printf("\n"); /* * If we clear the ACL, then the permissions should * be as restrictive as possible until the user can * set it to something reasonable. If we keep the * ACL, then the permissions are pretty much * irrelevant. So, just always clear the permission * bits. */ dp = ginode(scc->client[client]); dp->di_mode &= IFMT; dp->di_shadow = 0; inodirty(); /* * Decrement in-memory link count - pass1 made sure * the shadow inode # is a valid inode number. But * first, see if we're going to overflow our sixteen * bits. */ LINK_RANGE(flow, lncntp[dp->di_shadow], 1); if (flow != NULL) { LINK_CLEAR(flow, scc->client[client], dp->di_mode, &ldesc); if (statemap[scc->client[client]] == USTATE) suppress_update = 1; } /* * We don't touch the shadow's on-disk link count, * because we've already cleared its state in pass3b(). * Here we're just trying to keep lncntp[] in sync, so * we can detect spurious links. */ if (!suppress_update) TRACK_LNCNTP(sci->shadow, lncntp[sci->shadow]++); } else { iscorrupt = 1; } } /* * Collect all the (data) blocks of an acl file into a buffer. * Later we'll scan the buffer and validate the acl data. */ int pass3bcheck(struct inodesc *idesc) { struct bufarea *bp; size_t size, bsize; if (aclbufoff == idesc->id_filesize) { return (STOP); } bsize = size = sblock.fs_fsize * idesc->id_numfrags; if ((size + aclbufoff) > idesc->id_filesize) size = idesc->id_filesize - aclbufoff; if (aclbufoff + size > maxaclsize) errexit("acl size %lld exceeds maximum calculated " "size of %lld bytes", (longlong_t)aclbufoff + size, (longlong_t)maxaclsize); bp = getdatablk(idesc->id_blkno, bsize); if (bp->b_errs != 0) { brelse(bp); return (STOP); } (void) memmove((void *)(aclbuf + aclbufoff), (void *)bp->b_un.b_buf, (size_t)size); aclbufoff += size; brelse(bp); return (KEEPON); } /* * Routine to sort disk blocks. */ static int aclblksort(const void *pp1, const void *pp2) { const struct inoinfo **aclpp1 = (const struct inoinfo **)pp1; const struct inoinfo **aclpp2 = (const struct inoinfo **)pp2; return ((*aclpp1)->i_blks[0] - (*aclpp2)->i_blks[0]); } /* * Scan a chunk of a shadow file. Return zero if no ACLs were found, * or when all that were found were valid. */ static int bufchk(char *buf, int64_t len, fsck_ino_t inum) { ufs_fsd_t *fsdp; ufs_acl_t *ufsaclp = NULL; int numacls; int curacl; struct type_counts_s { int nuser_objs; int ngroup_objs; int nother_objs; int nclass_objs; int ndef_user_objs; int ndef_group_objs; int ndef_other_objs; int ndef_class_objs; int nusers; int ngroups; int ndef_users; int ndef_groups; } type_counts[3]; /* indexed by FSD_ACL and FSD_DFACL */ struct type_counts_s *tcp, *tcp_all, *tcp_def, *tcp_norm; int numdefs; caddr_t bad; caddr_t end = buf + len; int64_t recsz = 0; int64_t min_recsz = FSD_RECSZ(fsdp, sizeof (*fsdp)); struct shadowclientinfo *sci; struct shadowclients *scc; fsck_ino_t target; int numtargets = 0; /* * check we have a non-zero length for this shadow inode */ if (len == 0) { pwarn("ACL I=%d HAS ZERO LENGTH\n", inum); return (1); } (void) memset(type_counts, 0, sizeof (type_counts)); /* LINTED pointer cast alignment (aligned buffer always passed in) */ for (fsdp = (ufs_fsd_t *)buf; (caddr_t)fsdp < end; /* LINTED as per the above */ fsdp = (ufs_fsd_t *)((caddr_t)fsdp + recsz)) { recsz = FSD_RECSZ(fsdp, fsdp->fsd_size); if ((recsz < min_recsz) || (((caddr_t)fsdp + recsz) > (buf + len))) { pwarn("Bad FSD entry size %lld in shadow inode %d", recsz, inum); if (reply("CLEAR SHADOW INODE") == 1) { freeino(inum, TI_PARENT); } else { /* * Bad size can cause the kernel to * go traipsing off into never-never land. */ iscorrupt = 1; } return (0); } switch (fsdp->fsd_type) { case FSD_FREE: /* ignore empty slots */ break; case FSD_ACL: case FSD_DFACL: /* * Subtract out the two ints in the fsd_type, * leaving us just the size of fsd_data[]. */ numacls = (fsdp->fsd_size - 2 * sizeof (int)) / sizeof (ufs_acl_t); tcp = &type_counts[fsdp->fsd_type]; curacl = 0; /* LINTED pointer cast alignment */ for (ufsaclp = (ufs_acl_t *)fsdp->fsd_data; numacls; ufsaclp++, curacl++) { switch (ufsaclp->acl_tag) { case USER_OBJ: /* Owner */ tcp->nuser_objs++; break; case GROUP_OBJ: /* Group */ tcp->ngroup_objs++; break; case OTHER_OBJ: /* Other */ tcp->nother_objs++; break; case CLASS_OBJ: /* Mask */ tcp->nclass_objs++; break; case DEF_USER_OBJ: /* Default Owner */ tcp->ndef_user_objs++; break; case DEF_GROUP_OBJ: /* Default Group */ tcp->ndef_group_objs++; break; case DEF_OTHER_OBJ: /* Default Other */ tcp->ndef_other_objs++; break; case DEF_CLASS_OBJ: /* Default Mask */ tcp->ndef_class_objs++; break; case USER: /* Users */ tcp->nusers++; break; case GROUP: /* Groups */ tcp->ngroups++; break; case DEF_USER: /* Default Users */ tcp->ndef_users++; break; case DEF_GROUP: /* Default Groups */ tcp->ndef_groups++; break; default: return (1); } if ((ufsaclp->acl_perm & ~07) != 0) { /* * Caller will report inode, etc */ pwarn("Bad permission 0%o in ACL\n", ufsaclp->acl_perm); return (1); } numacls--; } break; default: if (fsdp->fsd_type >= FSD_RESERVED3 && fsdp->fsd_type <= FSD_RESERVED7) bad = "Unexpected"; else bad = "Unknown"; pwarn("%s FSD type %d in shadow inode %d", bad, fsdp->fsd_type, inum); /* * This is relatively harmless, since the * kernel will ignore any entries it doesn't * recognize. Don't bother with iscorrupt. */ if (preen) { (void) printf(" (IGNORED)\n"); } else if (reply("IGNORE") == 0) { if (reply("CLEAR SHADOW INODE") == 1) { freeino(inum, TI_PARENT); } return (0); } break; } } if ((caddr_t)fsdp != (buf + len)) { return (1); } /* If we didn't find any acls, ignore the unknown attribute */ if (ufsaclp == NULL) return (0); /* * Should only have default ACLs in FSD_DFACL records. * However, the kernel can handle it, so just report that * something odd might be going on. */ tcp = &type_counts[FSD_DFACL]; if (verbose && (tcp->nuser_objs != 0 || tcp->ngroup_objs != 0 || tcp->nother_objs != 0 || tcp->nclass_objs != 0 || tcp->nusers != 0 || tcp->ngroups != 0)) { (void) printf("NOTE: ACL I=%d has miscategorized ACLs. ", inum); (void) printf("This is harmless, but not normal.\n"); } /* * Similarly for default ACLs in FSD_ACL records. */ tcp = &type_counts[FSD_ACL]; if (verbose && (tcp->ndef_user_objs != 0 || tcp->ndef_group_objs != 0 || tcp->ndef_other_objs != 0 || tcp->ndef_class_objs != 0 || tcp->ndef_users != 0 || tcp->ndef_groups != 0)) { (void) printf("NOTE: ACL I=%d has miscategorized ACLs.", inum); (void) printf(" This is harmless, but not normal.\n"); } /* * Get consolidated totals, now that we're done with checking * the segregation above. Assumes that neither FSD_ACL nor * FSD_DFACL are zero. */ tcp_all = &type_counts[0]; tcp_norm = &type_counts[FSD_ACL]; tcp_def = &type_counts[FSD_DFACL]; tcp_all->nuser_objs = tcp_def->nuser_objs + tcp_norm->nuser_objs; tcp_all->ngroup_objs = tcp_def->ngroup_objs + tcp_norm->ngroup_objs; tcp_all->nother_objs = tcp_def->nother_objs + tcp_norm->nother_objs; tcp_all->nclass_objs = tcp_def->nclass_objs + tcp_norm->nclass_objs; tcp_all->ndef_user_objs = tcp_def->ndef_user_objs + tcp_norm->ndef_user_objs; tcp_all->ndef_group_objs = tcp_def->ndef_group_objs + tcp_norm->ndef_group_objs; tcp_all->ndef_other_objs = tcp_def->ndef_other_objs + tcp_norm->ndef_other_objs; tcp_all->ndef_class_objs = tcp_def->ndef_class_objs + tcp_norm->ndef_class_objs; tcp_all->nusers = tcp_def->nusers + tcp_norm->nusers; tcp_all->ngroups = tcp_def->ngroups + tcp_norm->ngroups; tcp_all->ndef_users = tcp_def->ndef_users + tcp_norm->ndef_users; tcp_all->ndef_groups = tcp_def->ndef_groups + tcp_norm->ndef_groups; /* * Check relationships among acls */ if (tcp_all->nuser_objs != 1 || tcp_all->ngroup_objs != 1 || tcp_all->nother_objs != 1 || tcp_all->nclass_objs > 1) { return (1); } if (tcp_all->ngroups && !tcp_all->nclass_objs) { return (1); } if (tcp_all->ndef_user_objs > 1 || tcp_all->ndef_group_objs > 1 || tcp_all->ndef_other_objs > 1 || tcp_all->ndef_class_objs > 1) { return (1); } /* * Check relationships among default acls */ numdefs = tcp_all->ndef_other_objs + tcp_all->ndef_user_objs + tcp_all->ndef_group_objs; if (numdefs != 0 && numdefs != 3) { return (1); } /* * If there are default acls, then the shadow inode's clients * must be a directory or an xattr directory. */ if (numdefs != 0) { /* This is an ACL so find it's clients */ for (sci = shadowclientinfo; sci != NULL; sci = sci->next) if (sci->shadow == inum) break; if ((sci == NULL) || (sci->clients == NULL)) return (1); /* Got shadow info, now look at clients */ for (scc = sci->clients; scc != NULL; scc = scc->next) { for (numtargets = 0; numtargets < scc->nclients; numtargets++) { target = scc->client[numtargets]; if (!INO_IS_DVALID(target)) return (1); } } } if (tcp_all->ndef_groups && !tcp_all->ndef_class_objs) { return (1); } if ((tcp_all->ndef_users || tcp_all->ndef_groups) && ((numdefs != 3) && !tcp_all->ndef_class_objs)) { return (1); } return (0); } /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include "fsck.h" void pass4(void) { fsck_ino_t inumber; struct dinode *dp; struct inodesc idesc; int n, was_dir; int need_rescan; int scan_pass = 0; /* * If we clear a directory, it may have produced orphans which * we need to go pick up. So, do this until done. It can be * proven that the loop terminates because at most there can * be lastino directories, and we only rescan if we clear a * directory. */ do { if (debug) (void) printf("pass4 scan %d\n", scan_pass++); need_rescan = 0; for (inumber = UFSROOTINO; inumber <= lastino; inumber++) { init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass4check; idesc.id_number = inumber; was_dir = (statemap[inumber] & DSTATE) == DSTATE; switch (statemap[inumber] & ~(INORPHAN | INDELAYD | INZLINK)) { case FZLINK: case DZLINK: /* * INZLINK gets set if the inode claimed zero * links when we first looked at it in pass 1. * If lncntp[] also claims it has zero links, * it really is unreferenced. However, we * could have found a link to it during one of * the other passes, so we have to check the * final count in lncntp[]. */ if (lncntp[inumber] == 0) { clri(&idesc, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); if (was_dir && (statemap[inumber] == USTATE)) need_rescan = 1; break; } /* FALLTHROUGH */ case FSTATE: case DFOUND: case SSTATE: n = lncntp[inumber]; if (n || (statemap[inumber] & (INDELAYD | INZLINK))) { /* * adjust() will clear the inode if * the link count goes to zero. If * it isn't cleared, we need to note * that we've adjusted the count * already, so we don't do it again * on a rescan. */ adjust(&idesc, n); if (was_dir && (statemap[inumber] == USTATE)) { need_rescan = 1; } else { TRACK_LNCNTP(inumber, lncntp[inumber] = 0); } } break; case DSTATE: clri(&idesc, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); if (was_dir && (statemap[inumber] == USTATE)) need_rescan = 1; break; case DCLEAR: dp = ginode(inumber); if (dp->di_size == 0) { clri(&idesc, "ZERO LENGTH", CLRI_VERBOSE, CLRI_NOP_CORRUPT); break; } /* FALLTHROUGH */ case FCLEAR: clri(&idesc, "BAD/DUP", CLRI_VERBOSE, CLRI_NOP_CORRUPT); break; case SCLEAR: clri(&idesc, "BAD", CLRI_VERBOSE, CLRI_NOP_CORRUPT); break; case USTATE: break; default: errexit("BAD STATE 0x%x FOR INODE I=%d", (int)statemap[inumber], inumber); } } } while (need_rescan); } int pass4check(struct inodesc *idesc) { int fragnum, cg_frag; int res = KEEPON; daddr32_t blkno = idesc->id_blkno; int cylno; struct cg *cgp = &cgrp; caddr_t err; if ((idesc->id_truncto >= 0) && (idesc->id_lbn < idesc->id_truncto)) { if (debug) (void) printf( "pass4check: skipping inode %d lbn %d with truncto %d\n", idesc->id_number, idesc->id_lbn, idesc->id_truncto); return (KEEPON); } for (fragnum = 0; fragnum < idesc->id_numfrags; fragnum++) { if (chkrange(blkno + fragnum, 1)) { res = SKIP; } else if (testbmap(blkno + fragnum)) { /* * The block's in use. Remove our reference * from it. * * If it wasn't a dup, or everybody's done with * it, then this is the last reference and it's * safe to actually deallocate the on-disk block. * * We depend on pass 5 resolving the on-disk bitmap * effects. */ cg_frag = blkno + fragnum; if (!find_dup_ref(cg_frag, idesc->id_number, idesc->id_lbn * sblock.fs_frag + fragnum, DB_DECR)) { if (debug) (void) printf("p4c marking %d avail\n", cg_frag); clrbmap(cg_frag); n_blks--; /* * Do the same for the on-disk bitmap, so * that we don't need another pass to figure * out what's really being used. We'll let * pass5() work out the fragment/block * accounting. */ cylno = dtog(&sblock, cg_frag); (void) getblk(&cgblk, cgtod(&sblock, cylno), (size_t)sblock.fs_cgsize); err = cg_sanity(cgp, cylno); if (err != NULL) { pfatal("CG %d: %s\n", cylno, err); free((void *)err); if (reply("REPAIR") == 0) errexit("Program terminated."); fix_cg(cgp, cylno); } clrbit(cg_blksfree(cgp), dtogd(&sblock, cg_frag)); cgdirty(); res |= ALTERED; } } } return (res); } /* * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include "fsck.h" static int check_maps(uchar_t *, uchar_t *, int, int, char *, int, int); void pass5(void) { caddr_t err; int32_t c, blk, frags; size_t basesize, sumsize, mapsize; int excessdirs; int inomapsize, blkmapsize; int update_csums, update_bitmaps; int bad_csum_sb, bad_csum_cg, bad_cgblks_cg, bad_cgblktot_cg; struct fs *fs = &sblock; struct cg *cg = &cgrp; diskaddr_t dbase, dmax; diskaddr_t d; uint64_t i, j; struct csum *cs; struct csum backup_cs; time_t now; struct csum cstotal; struct inodesc idesc; union { /* keep lint happy about alignment */ struct cg cg; /* the rest of buf has the bitmaps */ char buf[MAXBSIZE]; } u; caddr_t buf = u.buf; struct cg *newcg = &u.cg; (void) memset((void *)buf, 0, sizeof (u.buf)); newcg->cg_niblk = fs->fs_ipg; if (fs->fs_postblformat != FS_DYNAMICPOSTBLFMT) { pfatal("UNSUPPORTED ROTATIONAL TABLE FORMAT %d\n", fs->fs_postblformat); errexit("Program terminated."); /* NOTREACHED */ } /* LINTED this subtraction can't overflow and is int32-aligned */ basesize = &newcg->cg_space[0] - (uchar_t *)newcg; /* * We reserve the space for the old rotation summary * tables for the benefit of old kernels, but do not * maintain them in modern kernels. In time, they could * theoretically go away, if we wanted to deal with * changing the on-disk format. */ /* * Note that we don't use any of the cg_*() macros until * after cg_sanity() has approved of what we've got. */ newcg->cg_btotoff = basesize; newcg->cg_boff = newcg->cg_btotoff + fs->fs_cpg * sizeof (daddr32_t); newcg->cg_iusedoff = newcg->cg_boff + fs->fs_cpg * fs->fs_nrpos * sizeof (uint16_t); (void) memset(&newcg->cg_space[0], 0, newcg->cg_iusedoff - basesize); inomapsize = howmany(fs->fs_ipg, NBBY); newcg->cg_freeoff = newcg->cg_iusedoff + inomapsize; blkmapsize = howmany(fs->fs_fpg, NBBY); newcg->cg_nextfreeoff = newcg->cg_freeoff + blkmapsize; newcg->cg_magic = CG_MAGIC; sumsize = newcg->cg_iusedoff - newcg->cg_btotoff; mapsize = newcg->cg_nextfreeoff - newcg->cg_iusedoff; init_inodesc(&idesc); idesc.id_type = ADDR; (void) memset((void *)&cstotal, 0, sizeof (struct csum)); now = time(NULL); /* * If the last fragments in the file system don't make up a * full file system block, mark the bits in the blockmap * that correspond to those missing fragments as "allocated", * so that the last block doesn't get counted as a free block * and those missing fragments don't get counted as free frags. */ j = blknum(fs, (uint64_t)fs->fs_size + fs->fs_frag - 1); for (i = fs->fs_size; i < j; i++) setbmap(i); /* * The cg summaries are not always updated when using * logging. Since we're really concerned with getting a * sane filesystem, rather than in trying to debug UFS * corner cases, logically we would just always recompute * them. However, it is disconcerting to users to be asked * about updating the summaries when, from their point of * view, there's been no indication of a problem up to this * point. So, only do it if we find a discrepancy. */ update_csums = -1; update_bitmaps = 0; for (c = 0; c < fs->fs_ncg; c++) { backup_cs = cstotal; /* * cg_sanity() will catch i/o errors for us. */ (void) getblk(&cgblk, (diskaddr_t)cgtod(fs, c), (size_t)fs->fs_cgsize); err = cg_sanity(cg, c); if (err != NULL) { pfatal("CG %d: %s\n", c, err); free((void *)err); if (reply("REPAIR") == 0) errexit("Program terminated."); fix_cg(cg, c); } /* * If the on-disk timestamp is in the future, then it * by definition is wrong. Otherwise, if it's in * the past, then use that value so that we don't * declare a spurious mismatch. */ if (now > cg->cg_time) newcg->cg_time = cg->cg_time; else newcg->cg_time = now; newcg->cg_cgx = c; dbase = cgbase(fs, c); dmax = dbase + fs->fs_fpg; if (dmax > fs->fs_size) dmax = fs->fs_size; newcg->cg_ndblk = dmax - dbase; if (c == fs->fs_ncg - 1) newcg->cg_ncyl = fs->fs_ncyl - (fs->fs_cpg * c); else newcg->cg_ncyl = fs->fs_cpg; newcg->cg_niblk = sblock.fs_ipg; newcg->cg_cs.cs_ndir = 0; newcg->cg_cs.cs_nffree = 0; newcg->cg_cs.cs_nbfree = 0; newcg->cg_cs.cs_nifree = fs->fs_ipg; if ((cg->cg_rotor >= 0) && (cg->cg_rotor < newcg->cg_ndblk)) newcg->cg_rotor = cg->cg_rotor; else newcg->cg_rotor = 0; if ((cg->cg_frotor >= 0) && (cg->cg_frotor < newcg->cg_ndblk)) newcg->cg_frotor = cg->cg_frotor; else newcg->cg_frotor = 0; if ((cg->cg_irotor >= 0) && (cg->cg_irotor < newcg->cg_niblk)) newcg->cg_irotor = cg->cg_irotor; else newcg->cg_irotor = 0; (void) memset((void *)&newcg->cg_frsum[0], 0, sizeof (newcg->cg_frsum)); (void) memset((void *)cg_inosused(newcg), 0, (size_t)mapsize); /* LINTED macro is int32-aligned per newcg->cg_btotoff above */ (void) memset((void *)&cg_blktot(newcg)[0], 0, sumsize + mapsize); j = fs->fs_ipg * c; for (i = 0; i < fs->fs_ipg; j++, i++) { switch (statemap[j] & ~(INORPHAN | INDELAYD)) { case USTATE: break; case DSTATE: case DCLEAR: case DFOUND: case DZLINK: newcg->cg_cs.cs_ndir++; /* FALLTHROUGH */ case FSTATE: case FCLEAR: case FZLINK: case SSTATE: case SCLEAR: newcg->cg_cs.cs_nifree--; setbit(cg_inosused(newcg), i); break; default: if (j < UFSROOTINO) break; errexit("BAD STATE 0x%x FOR INODE I=%d", statemap[j], (int)j); } } if (c == 0) { for (i = 0; i < UFSROOTINO; i++) { setbit(cg_inosused(newcg), i); newcg->cg_cs.cs_nifree--; } } /* * Count up what fragments and blocks are free, and * reflect the relevant section of blockmap[] into * newcg's map. */ for (i = 0, d = dbase; d < dmax; d += fs->fs_frag, i += fs->fs_frag) { frags = 0; for (j = 0; j < fs->fs_frag; j++) { if (testbmap(d + j)) continue; setbit(cg_blksfree(newcg), i + j); frags++; } if (frags == fs->fs_frag) { newcg->cg_cs.cs_nbfree++; j = cbtocylno(fs, i); /* LINTED macro is int32-aligned per above */ cg_blktot(newcg)[j]++; /* LINTED cg_blks(newcg) is aligned */ cg_blks(fs, newcg, j)[cbtorpos(fs, i)]++; } else if (frags > 0) { newcg->cg_cs.cs_nffree += frags; blk = blkmap(fs, cg_blksfree(newcg), i); fragacct(fs, blk, newcg->cg_frsum, 1); } } cstotal.cs_nffree += newcg->cg_cs.cs_nffree; cstotal.cs_nbfree += newcg->cg_cs.cs_nbfree; cstotal.cs_nifree += newcg->cg_cs.cs_nifree; cstotal.cs_ndir += newcg->cg_cs.cs_ndir; /* * Note that, just like the kernel, we dynamically * allocated an array to hold the csums and stuffed * the pointer into the in-core superblock's fs_u.fs_csp * field. This means that the fs_u field contains a * random value when the disk version is examined, but * fs_cs() gives us a valid pointer nonetheless. * We need to compare the recalculated summaries to * both the superblock version and the on disk version. * If either is bad, copy the calculated version over * the corrupt values. */ cs = &fs->fs_cs(fs, c); bad_csum_sb = (memcmp((void *)cs, (void *)&newcg->cg_cs, sizeof (*cs)) != 0); bad_csum_cg = (memcmp((void *)&cg->cg_cs, (void *)&newcg->cg_cs, sizeof (struct csum)) != 0); /* * Has the user told us what to do yet? If not, find out. */ if ((bad_csum_sb || bad_csum_cg) && (update_csums == -1)) { if (preen) { update_csums = 1; (void) printf("CORRECTING BAD CG SUMMARIES" " FOR CG %d\n", c); } else if (update_csums == -1) { update_csums = (reply( "CORRECT BAD CG SUMMARIES FOR CG %d", c) == 1); } } if (bad_csum_sb && (update_csums == 1)) { (void) memmove((void *)cs, (void *)&newcg->cg_cs, sizeof (*cs)); sbdirty(); (void) printf("CORRECTED SUPERBLOCK SUMMARIES FOR" " CG %d\n", c); } if (bad_csum_cg && (update_csums == 1)) { (void) memmove((void *)cg, (void *)newcg, (size_t)basesize); /* LINTED per cg_sanity() */ (void) memmove((void *)&cg_blktot(cg)[0], /* LINTED macro aligned as above */ (void *)&cg_blktot(newcg)[0], sumsize); cgdirty(); (void) printf("CORRECTED SUMMARIES FOR CG %d\n", c); } excessdirs = cg->cg_cs.cs_ndir - newcg->cg_cs.cs_ndir; if (excessdirs < 0) { pfatal("LOST %d DIRECTORIES IN CG %d\n", -excessdirs, c); excessdirs = 0; } if (excessdirs > 0) { if (check_maps((uchar_t *)cg_inosused(newcg), (uchar_t *)cg_inosused(cg), inomapsize, cg->cg_cgx * fs->fs_ipg, "DIR", 0, excessdirs)) { if (!verbose) (void) printf("DIR BITMAP WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)cg_inosused(cg), (void *)cg_inosused(newcg), inomapsize); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } } if (check_maps((uchar_t *)cg_inosused(newcg), (uchar_t *)cg_inosused(cg), inomapsize, cg->cg_cgx * fs->fs_ipg, "FILE", excessdirs, fs->fs_ipg)) { if (!verbose) (void) printf("FILE BITMAP WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)cg_inosused(cg), (void *)cg_inosused(newcg), inomapsize); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } if (check_maps((uchar_t *)cg_blksfree(cg), (uchar_t *)cg_blksfree(newcg), blkmapsize, cg->cg_cgx * fs->fs_fpg, "FRAG", 0, fs->fs_fpg)) { if (!verbose) (void) printf("FRAG BITMAP WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)cg_blksfree(cg), (void *)cg_blksfree(newcg), blkmapsize); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } bad_cgblks_cg = (memcmp((void *)&cg_blks(fs, cg, 0)[0], (void *)&cg_blks(fs, newcg, 0)[0], fs->fs_cpg * fs->fs_nrpos * sizeof (int16_t)) != 0); if (bad_cgblks_cg) { if (!verbose) (void) printf("ROTATIONAL POSITIONS " "BLOCK COUNT WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)&cg_blks(fs, cg, 0)[0], (void *)&cg_blks(fs, newcg, 0)[0], fs->fs_cpg * fs->fs_nrpos * sizeof (int16_t)); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } bad_cgblktot_cg = (memcmp((void *)&cg_blktot(cg)[0], (void *)&cg_blktot(newcg)[0], fs->fs_cpg * sizeof (int32_t)) != 0); if (bad_cgblktot_cg) { if (!verbose) (void) printf("ROTATIONAL POSITIONS " "BLOCK TOTAL WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)&cg_blktot(cg)[0], (void *)&cg_blktot(newcg)[0], fs->fs_cpg * sizeof (int32_t)); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } /* * Fixing one set of problems often shows up more in the * same cg. Just to make sure, go back and check it * again if we found something this time through. */ if (cgisdirty()) { cgflush(); cstotal = backup_cs; c--; } } if ((fflag || !(islog && islogok)) && (memcmp((void *)&cstotal, (void *)&fs->fs_cstotal, sizeof (struct csum)) != 0)) { if (dofix(&idesc, "CORRECT GLOBAL SUMMARY")) { (void) memmove((void *)&fs->fs_cstotal, (void *)&cstotal, sizeof (struct csum)); fs->fs_ronly = 0; fs->fs_fmod = 0; sbdirty(); } else { iscorrupt = 1; } } } /* * Compare two allocation bitmaps, reporting any discrepancies. * * If a mismatch is found, if the bit is set in map1, it's considered * to be an indication that the corresponding resource is supposed * to be free, but isn't. Otherwise, it's considered marked as allocated * but not found to be so. In other words, if the two maps being compared * use a set bit to indicate something is free, pass the on-disk map * first. Otherwise, pass the calculated map first. */ static int check_maps( uchar_t *map1, /* map of claimed allocations */ uchar_t *map2, /* map of determined allocations */ int mapsize, /* size of above two maps */ int startvalue, /* resource value for first element in map */ char *name, /* name of resource found in maps */ int skip, /* number of entries to skip before starting to free */ int limit) /* limit on number of entries to free */ { long i, j, k, l, m, n, size; int astart, aend, ustart, uend; int mismatch; mismatch = 0; astart = ustart = aend = uend = -1; for (i = 0; i < mapsize; i++) { j = *map1++; k = *map2++; if (j == k) continue; for (m = 0, l = 1; m < NBBY; m++, l <<= 1) { if ((j & l) == (k & l)) continue; n = startvalue + i * NBBY + m; if ((j & l) != 0) { if (astart == -1) { astart = aend = n; continue; } if (aend + 1 == n) { aend = n; continue; } if (verbose) { if (astart == aend) pwarn( "ALLOCATED %s %d WAS MARKED FREE ON DISK\n", name, astart); else pwarn( "ALLOCATED %sS %d-%d WERE MARKED FREE ON DISK\n", name, astart, aend); } mismatch = 1; astart = aend = n; } else { if (ustart == -1) { ustart = uend = n; continue; } if (uend + 1 == n) { uend = n; continue; } size = uend - ustart + 1; if (size <= skip) { skip -= size; ustart = uend = n; continue; } if (skip > 0) { ustart += skip; size -= skip; skip = 0; } if (size > limit) size = limit; if (verbose) { if (size == 1) pwarn( "UNALLOCATED %s %d WAS MARKED USED ON DISK\n", name, ustart); else pwarn( "UNALLOCATED %sS %d-%ld WERE MARKED USED ON DISK\n", name, ustart, ustart + size - 1); } mismatch = 1; limit -= size; if (limit <= 0) return (mismatch); ustart = uend = n; } } } if (astart != -1) { if (verbose) { if (astart == aend) pwarn( "ALLOCATED %s %d WAS MARKED FREE ON DISK\n", name, astart); else pwarn( "ALLOCATED %sS %d-%d WERE MARKED FREE ON DISK\n", name, astart, aend); } mismatch = 1; } if (ustart != -1) { size = uend - ustart + 1; if (size <= skip) return (mismatch); if (skip > 0) { ustart += skip; size -= skip; } if (size > limit) size = limit; if (verbose) { if (size == 1) pwarn( "UNALLOCATED %s %d WAS MARKED USED ON DISK\n", name, ustart); else pwarn( "UNALLOCATED %sS %d-%ld WERE MARKED USED ON DISK\n", name, ustart, ustart + size - 1); } mismatch = 1; } return (mismatch); } /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #define DKTYPENAMES #include #include #include #include #include #include #include #include #include #include #include #include #include /* for ENDIAN defines */ #include #include #include #include #include #include #include #include #include #include #include #include #include "roll_log.h" #include "fsck.h" /* * The size of a cylinder group is calculated by CGSIZE. The maximum size * is limited by the fact that cylinder groups are at most one block. * Its size is derived from the size of the maps maintained in the * cylinder group and the (struct cg) size. */ #define CGSIZE(fs) \ /* base cg */ (sizeof (struct cg) + \ /* blktot size */ (fs)->fs_cpg * sizeof (int32_t) + \ /* blks size */ (fs)->fs_cpg * (fs)->fs_nrpos * sizeof (short) + \ /* inode map */ howmany((fs)->fs_ipg, NBBY) + \ /* block map */ howmany((fs)->fs_cpg * (fs)->fs_spc / NSPF(fs), NBBY)) #define altsblock (*asblk.b_un.b_fs) #define POWEROF2(num) (((num) & ((num) - 1)) == 0) /* * Methods of determining where alternate superblocks should * be. MAX_SB_STYLES must be the last one, and the others need * to be positive. */ typedef enum { MKFS_STYLE = 1, NEWFS_STYLE, MAX_SB_STYLES } calcsb_t; static caddr_t calcsb_names[] = { "", "MKFS", "NEWFS", "" }; fsck_ino_t lfdir; int64_t numacls, aclmax, aclplast; int64_t numdirs, listmax, inplast; char havesb; int fsreadfd; int isdirty; int pid; int secsize; size_t dev_bsize; struct bufarea sblk; static struct bufarea asblk; /* alternate superblock */ struct inoinfo **inphead, **inpsort; struct shadowclientinfo *shadowclientinfo = NULL; struct shadowclientinfo *attrclientinfo = NULL; int maxshadowclients = 1024; /* allocation size, not limit */ static void badsb(int, caddr_t); static int calcsb(calcsb_t, caddr_t, int, struct fs *); static int checksb(int); static void flush_fs(void); static void sblock_init(void); static void uncreate_maps(void); static int read_super_block(int listerr) { int fd; caddr_t err; if (mount_point != NULL) { fd = open(mount_point, O_RDONLY); if (fd == -1) { errexit("fsck: open mount point error: %s", strerror(errno)); /* NOTREACHED */ } /* get the latest super block */ if (ioctl(fd, _FIOGETSUPERBLOCK, &sblock)) { errexit("fsck: ioctl _FIOGETSUPERBLOCK error: %s", strerror(errno)); /* NOTREACHED */ } (void) close(fd); } else { (void) fsck_bread(fsreadfd, (caddr_t)&sblock, bflag != 0 ? (diskaddr_t)bflag : (diskaddr_t)SBLOCK, SBSIZE); } /* * Don't let trash from the disk trip us up later * in ungetsummaryinfo(). */ sblock.fs_u.fs_csp = NULL; /* * Rudimentary consistency checks. Can't really call * checksb() here, because there may be outstanding * deltas that still need to be applied. */ if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { err = "MAGIC NUMBER WRONG"; goto fail; } if (sblock.fs_magic == FS_MAGIC && (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sblock.fs_version != UFS_VERSION_MIN)) { err = "UNRECOGNIZED VERSION"; goto fail; } if (sblock.fs_magic == MTB_UFS_MAGIC && (sblock.fs_version > MTB_UFS_VERSION_1 || sblock.fs_version < MTB_UFS_VERSION_MIN)) { err = "UNRECOGNIZED VERSION"; goto fail; } if (sblock.fs_ncg < 1) { err = "NCG OUT OF RANGE"; goto fail; } if (sblock.fs_cpg < 1) { err = "CPG OUT OF RANGE"; goto fail; } if (sblock.fs_ncg * sblock.fs_cpg < sblock.fs_ncyl || (sblock.fs_ncg - 1) * sblock.fs_cpg >= sblock.fs_ncyl) { err = "NCYL IS INCONSISTENT WITH NCG*CPG"; goto fail; } if (sblock.fs_sbsize < 0 || sblock.fs_sbsize > SBSIZE) { err = "SIZE OUT OF RANGE"; goto fail; } return (1); fail: badsb(listerr, err); return (0); } static void flush_fs() { int fd; if (mount_point != NULL) { fd = open(mount_point, O_RDONLY); if (fd == -1) { errexit("fsck: open mount point error: %s", strerror(errno)); /* NOTREACHED */ } if (ioctl(fd, _FIOFFS, NULL)) { /* flush file system */ errexit("fsck: ioctl _FIOFFS error: %s", strerror(errno)); /* NOTREACHED */ } (void) close(fd); } } /* * Roll the embedded log, if any, and set up the global variables * islog and islogok. */ static int logsetup(caddr_t devstr) { void *buf; extent_block_t *ebp; ml_unit_t *ul; ml_odunit_t *ud; void *ud_buf; int badlog; islog = islogok = 0; if (bflag != 0) return (1); /* can't roll log while alternate sb specified */ /* * Roll the log, if any. A bad sb implies we'll be using * an alternate sb as far as logging goes, so just fail back * to the caller if we can't read the default sb. Suppress * complaints, because the caller will be reading the same * superblock again and running full verification on it, so * whatever is bad will be reported then. */ sblock.fs_logbno = 0; badlog = 0; if (!read_super_block(0)) return (1); /* * Roll the log in 3 cases: * 1. If it's unmounted (mount_point == NULL) and it's not marked * as fully rolled (sblock.fs_rolled != FS_ALL_ROLLED) * 2. If it's mounted and anything other than a sanity * check fsck (mflag) is being done, as we have the current * super block. Note, only a sanity check is done for * root/usr at boot. If a roll were done then the expensive * ufs_flush() gets called, leading to a slower boot. * 3. If anything other then a sanity check (mflag) is being done * to a mounted filesystem while it is in read-only state * (e.g. root during early boot stages) we have to detect this * and have to roll the log as well. NB. the read-only mount * will flip fs_clean from FSLOG to FSSTABLE and marks the * log as FS_NEED_ROLL. */ if (sblock.fs_logbno && (((mount_point == NULL) && (sblock.fs_rolled != FS_ALL_ROLLED)) || ((mount_point != NULL) && !mflag))) { int roll_log_err = 0; if (sblock.fs_ronly && (sblock.fs_clean == FSSTABLE) && (sblock.fs_state + sblock.fs_time == FSOKAY)) { /* * roll the log without a mount */ flush_fs(); } if (sblock.fs_clean == FSLOG && (sblock.fs_state + sblock.fs_time == FSOKAY)) { if (rl_roll_log(devstr) != RL_SUCCESS) roll_log_err = 1; } if (roll_log_err) { (void) printf("Can't roll the log for %s.\n", devstr); /* * There are two cases where we want to set * an error code and return: * - We're preening * - We're not on a live root and the user * chose *not* to ignore the log * Otherwise, we want to mark the log as bad * and continue to check the filesystem. This * has the side effect of destroying the log. */ if (preen || (!hotroot && reply( "DISCARDING THE LOG MAY DISCARD PENDING TRANSACTIONS.\n" "DISCARD THE LOG AND CONTINUE") == 0)) { exitstat = EXERRFATAL; return (0); } ++badlog; } } /* Logging UFS may be enabled */ if (sblock.fs_logbno) { ++islog; /* log is not okay; check the fs */ if (FSOKAY != (sblock.fs_state + sblock.fs_time)) return (1); /* * If logging or (stable and mounted) then continue */ if (!((sblock.fs_clean == FSLOG) || (sblock.fs_clean == FSSTABLE) && (mount_point != NULL))) return (1); /* get the log allocation block */ buf = malloc(dev_bsize); if (buf == NULL) { return (1); } ud_buf = malloc(dev_bsize); if (ud_buf == NULL) { free(buf); return (1); } (void) fsck_bread(fsreadfd, buf, logbtodb(&sblock, sblock.fs_logbno), dev_bsize); ebp = (extent_block_t *)buf; /* log allocation block is not okay; check the fs */ if (ebp->type != LUFS_EXTENTS) { free(buf); free(ud_buf); return (1); } /* get the log state block(s) */ if (fsck_bread(fsreadfd, ud_buf, (logbtodb(&sblock, ebp->extents[0].pbno)), dev_bsize)) { (void) fsck_bread(fsreadfd, ud_buf, (logbtodb(&sblock, ebp->extents[0].pbno)) + 1, dev_bsize); } ud = (ml_odunit_t *)ud_buf; ul = (ml_unit_t *)malloc(sizeof (*ul)); if (ul == NULL) { free(buf); free(ud_buf); return (1); } ul->un_ondisk = *ud; /* log state is okay; don't need to check the fs */ if ((ul->un_chksum == ul->un_head_ident + ul->un_tail_ident) && (ul->un_version == LUFS_VERSION_LATEST) && (ul->un_badlog == 0) && (!badlog)) { ++islogok; } free(ud_buf); free(buf); free(ul); } return (1); } /* * - given a pathname, determine the pathname to actually check * - if a directory * - if it is in mnttab, set devstr to the special (block) name * - if it is in vfstab, set devstr to the special (block) name * - if it has not been found, bail * - a file is used as-is, clear rflag * - a device is converted to block version (so can search mnttab) */ static void derive_devstr(const caddr_t dev, caddr_t devstr, size_t str_size) { mode_t mode; struct stat statb; if (stat(dev, &statb) < 0) { exitstat = EXNOSTAT; errexit("fsck: could not stat %s: %s", dev, strerror(errno)); } mode = statb.st_mode & S_IFMT; switch (mode) { case S_IFDIR: /* * The check_*() routines update devstr with the name. */ devstr[0] = '\0'; if (!(check_mnttab(dev, devstr, str_size) || check_vfstab(dev, devstr, str_size))) { exitstat = EXBADPARM; errexit( "fsck: could not find mountpoint %s in mnttab nor vfstab", dev); } break; case S_IFREG: rflag = 0; (void) strlcpy(devstr, dev, str_size); break; case S_IFCHR: case S_IFBLK: (void) strlcpy(devstr, unrawname(dev), str_size); break; default: exitstat = EXBADPARM; errexit("fsck: %s must be a mountpoint, device, or file", dev); /* NOTREACHED */ } } /* * Reports the index of the magic filesystem that mntp names. * If it does not correspond any of them, returns zero (hence * the backwards loop). */ static int which_corefs(const caddr_t mntp) { int corefs; for (corefs = MAGIC_LIMIT - 1; corefs > 0; corefs--) if (strcmp(mntp, magic_fs[corefs]) == 0) break; return (corefs); } /* * - set mount_point to NULL * - if name is mounted (search mnttab) * - if it is a device, clear rflag * - if mounted on /, /usr, or /var, set corefs * - if corefs and read-only, set hotroot and continue * - if errorlocked, continue * - if preening, bail * - ask user whether to continue, bail if not * - if it is a device and not mounted and rflag, convert * name to raw version */ static int check_mount_state(caddr_t devstr, size_t str_size) { int corefs = 0; int is_dev = 0; struct stat statb; if (stat(devstr, &statb) < 0) { exitstat = EXNOSTAT; errexit("fsck: could not stat %s: %s", devstr, strerror(errno)); } if (S_ISCHR(statb.st_mode) || S_ISBLK(statb.st_mode)) is_dev = 1; /* * mounted() will update mount_point when returning true. */ mount_point = NULL; if ((mountedfs = mounted(devstr, devstr, str_size)) != M_NOMNT) { if (is_dev) rflag = 0; corefs = which_corefs(mount_point); if (corefs && (mountedfs == M_RO)) { hotroot++; } else if (errorlocked) { goto carry_on; } else if (preen) { exitstat = EXMOUNTED; pfatal("%s IS CURRENTLY MOUNTED%s.", devstr, mountedfs == M_RW ? " READ/WRITE" : ""); } else { if (!nflag && !mflag) { pwarn("%s IS CURRENTLY MOUNTED READ/%s.", devstr, mountedfs == M_RW ? "WRITE" : "ONLY"); if (reply("CONTINUE") == 0) { exitstat = EXMOUNTED; errexit("Program terminated"); } } } } else if (is_dev && rflag) { (void) strlcpy(devstr, rawname(devstr), str_size); } carry_on: return (corefs); } static int open_and_intro(caddr_t devstr, int corefs) { int retval = 0; if ((fsreadfd = open64(devstr, O_RDONLY)) < 0) { (void) printf("Can't open %s: %s\n", devstr, strerror(errno)); exitstat = EXNOSTAT; retval = -1; goto finish; } if (!preen || debug != 0) (void) printf("** %s", devstr); if (errorlocked) { if (debug && elock_combuf != NULL) (void) printf(" error-lock comment: \"%s\" ", elock_combuf); fflag = 1; } pid = getpid(); if (nflag || (fswritefd = open64(devstr, O_WRONLY)) < 0) { fswritefd = -1; if (preen && !debug) pfatal("(NO WRITE ACCESS)\n"); (void) printf(" (NO WRITE)"); } if (!preen) (void) printf("\n"); else if (debug) (void) printf(" pid %d\n", pid); if (debug && (hotroot || (mountedfs != M_NOMNT))) { (void) printf("** %s", devstr); if (hotroot) (void) printf(" is %s fs", magic_fs[corefs]); if (mountedfs != M_NOMNT) (void) printf(" and is mounted read-%s", (mountedfs == M_RO) ? "only" : "write"); if (errorlocked) (void) printf(" and is error-locked"); (void) printf(".\n"); } finish: return (retval); } static int find_superblock(caddr_t devstr) { int cg = 0; int retval = 0; int first; int found; calcsb_t style; struct fs proto; /* * Check the superblock, looking for alternates if necessary. * In more-recent times, some UFS instances get created with * only the first ten and last ten superblock backups. Since * if we can't get the necessary information from any of those, * the odds are also against us for the ones in between, we'll * just look at those twenty to save time. */ if (!read_super_block(1) || !checksb(1)) { if (bflag || preen) { retval = -1; goto finish; } for (style = MKFS_STYLE; style < MAX_SB_STYLES; style++) { if (reply("LOOK FOR ALTERNATE SUPERBLOCKS WITH %s", calcsb_names[style]) == 0) continue; first = 1; found = 0; if (!calcsb(style, devstr, fsreadfd, &proto)) { cg = proto.fs_ncg; continue; } if (debug) { (void) printf( "debug: calcsb(%s) gave fpg %d, cgoffset %d, ", calcsb_names[style], proto.fs_fpg, proto.fs_cgoffset); (void) printf("cgmask 0x%x, sblk %d, ncg %d\n", proto.fs_cgmask, proto.fs_sblkno, proto.fs_ncg); } for (cg = 0; cg < proto.fs_ncg; cg++) { bflag = fsbtodb(&proto, cgsblock(&proto, cg)); if (debug) (void) printf( "debug: trying block %lld\n", (longlong_t)bflag); if (read_super_block(0) && checksb(0)) { (void) printf( "FOUND ALTERNATE SUPERBLOCK %d WITH %s\n", bflag, calcsb_names[style]); if (reply( "USE ALTERNATE SUPERBLOCK") == 1) { found = 1; break; } } if (first && (cg >= 9)) { first = 0; if (proto.fs_ncg <= 9) cg = proto.fs_ncg; else if (proto.fs_ncg <= 19) cg = 9; else cg = proto.fs_ncg - 10; } } if (found) break; } /* * Didn't find one? Try to fake it. */ if (style >= MAX_SB_STYLES) { pwarn("SEARCH FOR ALTERNATE SUPERBLOCKS FAILED.\n"); for (style = MKFS_STYLE; style < MAX_SB_STYLES; style++) { if (reply("USE GENERIC SUPERBLOCK FROM %s", calcsb_names[style]) == 1 && calcsb(style, devstr, fsreadfd, &sblock)) { break; } } /* * We got something from mkfs/newfs, so use it. */ if (style < MAX_SB_STYLES) { proto.fs_ncg = sblock.fs_ncg; bflag = 0; } } /* * Still no luck? Tell the user they're on their own. */ if (style >= MAX_SB_STYLES) { pwarn("SEARCH FOR ALTERNATE SUPERBLOCKS FAILED. " "YOU MUST USE THE -o b OPTION\n" "TO FSCK TO SPECIFY THE LOCATION OF A VALID " "ALTERNATE SUPERBLOCK TO\n" "SUPPLY NEEDED INFORMATION; SEE fsck(8).\n"); bflag = 0; retval = -1; goto finish; } /* * Need to make sure a human really wants us to use * this. -y mode could've gotten us this far, so * we need to ask something that has to be answered * in the negative. * * Note that we can't get here when preening. */ if (!found) { pwarn("CALCULATED GENERIC SUPERBLOCK WITH %s\n", calcsb_names[style]); } else { pwarn("FOUND ALTERNATE SUPERBLOCK AT %d USING %s\n", bflag, calcsb_names[style]); } pwarn("If filesystem was created with manually-specified "); pwarn("geometry, using\nauto-discovered superblock may "); pwarn("result in irrecoverable damage to\nfilesystem and "); pwarn("user data.\n"); if (reply("CANCEL FILESYSTEM CHECK") == 1) { if (cg >= 0) { pwarn("Please verify that the indicated block " "contains a proper\nsuperblock for the " "filesystem (see fsdb(8)).\n"); if (yflag) pwarn("\nFSCK was running in YES " "mode. If you wish to run in " "that mode using\nthe alternate " "superblock, run " "`fsck -y -o b=%d %s'.\n", bflag, devstr); } retval = -1; goto finish; } /* * Pretend we found it as an alternate, so everything * gets updated when we clean up at the end. */ if (!found) { havesb = 1; sblk.b_bno = fsbtodb(&sblock, cgsblock(&sblock, 0)); bwrite(fswritefd, (caddr_t)&sblock, SBLOCK, SBSIZE); write_altsb(fswritefd); } } finish: return (retval); } /* * Check and potentially fix certain fields in the super block. */ static void fixup_superblock(void) { /* * Kernel looks for FS_OPTTIME, and assumes that if that's not * what's there, it must be FS_OPTSPACE, so not fixing does not * require setting iscorrupt. */ if (sblock.fs_optim != FS_OPTTIME && sblock.fs_optim != FS_OPTSPACE) { pfatal("UNDEFINED OPTIMIZATION IN SUPERBLOCK"); if (reply("SET TO DEFAULT") == 1) { sblock.fs_optim = FS_OPTTIME; sbdirty(); } } if ((sblock.fs_minfree < 0 || sblock.fs_minfree > 99)) { pfatal("IMPOSSIBLE MINFREE=%d IN SUPERBLOCK", sblock.fs_minfree); if (reply("SET TO DEFAULT") == 1) { sblock.fs_minfree = 10; sbdirty(); } else if (sblock.fs_minfree < 0) { /* * Kernel uses minfree without verification, * and a negative value would do bad things. */ iscorrupt = 1; } } } static int initial_error_state_adjust(void) { int retval = 0; /* do this right away to prevent any other fscks on this fs */ switch (sblock.fs_clean) { case FSBAD: break; case FSFIX: if (preen) errexit("ERROR-LOCKED; MARKED \"FSFIX\"\n"); if (reply("marked FSFIX, CONTINUE") == 0) { retval = -1; goto finish; } break; case FSCLEAN: if (preen) errexit("ERROR-LOCKED; MARKED \"FSCLEAN\"\n"); if (reply("marked FSCLEAN, CONTINUE") == 0) { retval = -1; goto finish; } break; default: if (preen) { if (debug) pwarn("ERRORLOCKED; NOT MARKED \"FSBAD\"\n"); else errexit("ERRORLOCKED; NOT MARKED \"FSBAD\"\n"); } else { (void) printf("error-locked but not marked \"FSBAD\";"); if (reply(" CONTINUE") == 0) { retval = -1; goto finish; } } break; } if (!do_errorlock(LOCKFS_ELOCK)) { if (preen) { retval = -1; goto finish; } if (reply("error-lock reset failed; CONTINUE") == 0) { retval = -1; goto finish; } } sblock.fs_state = FSOKAY - (long)sblock.fs_time; sblock.fs_clean = FSFIX; sbdirty(); write_altsb(fswritefd); finish: return (retval); } static void getsummaryinfo(void) { size_t size; int failed; int asked; int i, j; caddr_t sip; /* * read in the summary info. */ sblock.fs_u.fs_csp = calloc(1, sblock.fs_cssize); if (sblock.fs_u.fs_csp == NULL) errexit( "cannot allocate %u bytes for cylinder group summary info\n", (unsigned)sblock.fs_cssize); sip = (caddr_t)sblock.fs_u.fs_csp; asked = 0; for (i = 0, j = 0; i < sblock.fs_cssize; i += sblock.fs_bsize, j++) { size = sblock.fs_cssize - i < sblock.fs_bsize ? sblock.fs_cssize - i : sblock.fs_bsize; failed = fsck_bread(fsreadfd, sip, fsbtodb(&sblock, sblock.fs_csaddr + j * sblock.fs_frag), size); if (failed && !asked) { pfatal("BAD SUMMARY INFORMATION"); if (reply("CONTINUE") == 0) { ckfini(); exit(EXFNDERRS); } asked = 1; } sip += size; } } /* * Reverses the effects of getsummaryinfo(). */ static void ungetsummaryinfo(void) { if ((sblk.b_un.b_fs != NULL) && (sblk.b_un.b_fs->fs_u.fs_csp != NULL)) { free(sblk.b_un.b_fs->fs_u.fs_csp); sblk.b_un.b_fs->fs_u.fs_csp = NULL; } } /* * Allocate and initialize the global tables. * It is the responsibility of the caller to clean up and allocations * if an error is returned. */ static int create_and_init_maps(void) { int64_t bmapsize; int retval = 0; maxfsblock = sblock.fs_size; maxino = sblock.fs_ncg * sblock.fs_ipg; bmapsize = roundup(howmany((uint64_t)maxfsblock, NBBY), sizeof (short)); blockmap = calloc((size_t)bmapsize, sizeof (char)); if (blockmap == NULL) { (void) printf("cannot alloc %lld bytes for blockmap\n", (longlong_t)bmapsize); retval = -1; goto finish; } statemap = calloc((size_t)(maxino + 1), sizeof (*statemap)); if (statemap == NULL) { (void) printf("cannot alloc %lld bytes for statemap\n", (longlong_t)(maxino + 1) * sizeof (*statemap)); retval = -1; goto finish; } lncntp = (short *)calloc((size_t)(maxino + 1), sizeof (short)); if (lncntp == NULL) { (void) printf("cannot alloc %lld bytes for lncntp\n", (longlong_t)(maxino + 1) * sizeof (short)); retval = -1; goto finish; } /* * If we had to fake up a superblock, it won't show that there * are any directories at all. This causes problems when we * use numdirs to calculate hash keys, so use something at least * vaguely plausible. */ numdirs = sblock.fs_cstotal.cs_ndir; if (numdirs == 0) numdirs = sblock.fs_ipg * sblock.fs_ncg / 2; listmax = numdirs + 10; inpsort = (struct inoinfo **)calloc((unsigned)listmax, sizeof (struct inoinfo *)); inphead = (struct inoinfo **)calloc((unsigned)numdirs, sizeof (struct inoinfo *)); if (inpsort == NULL || inphead == NULL) { (void) printf("cannot alloc %lld bytes for inphead\n", (longlong_t)numdirs * sizeof (struct inoinfo *)); retval = -1; goto finish; } if (debug) { if (listmax > ULONG_MAX) errexit("create_and_init_maps: listmax overflowed\n"); if (numdirs > ULONG_MAX) errexit("create_and_init_maps: numdirs overflowed\n"); } numacls = numdirs; aclmax = numdirs + 10; aclpsort = (struct inoinfo **)calloc((unsigned)aclmax, sizeof (struct inoinfo *)); aclphead = (struct inoinfo **)calloc((unsigned)numacls, sizeof (struct inoinfo *)); if (aclpsort == NULL || aclphead == NULL) { (void) printf("cannot alloc %lld bytes for aclphead\n", (longlong_t)numacls * sizeof (struct inoinfo *)); retval = -1; goto finish; } if (debug) { if (aclmax > ULONG_MAX) errexit("create_and_init_maps: aclmax overflowed\n"); if (numacls > ULONG_MAX) errexit("create_and_init_maps: numacls overflowed\n"); } aclplast = 0L; inplast = 0L; finish: return (retval); } caddr_t setup(caddr_t dev) { int corefs; static char devstr[MAXPATHLEN + 1]; havesb = 0; devname = devstr; derive_devstr(dev, devstr, sizeof (devstr)); errorlocked = is_errorlocked(devstr); corefs = check_mount_state(devstr, sizeof (devstr)); sblock_init(); if (open_and_intro(devstr, corefs) == -1) goto cleanup; if (mflag && mounted(devstr, devstr, sizeof (devstr)) == M_RW) return (devstr); /* * Check log state */ if (!logsetup(devstr)) goto cleanup; /* * Flush fs if we're going to do anything other than a sanity check. * Note, if logging then the fs was already flushed in logsetup(). */ if (!islog && !mflag) flush_fs(); if (find_superblock(devstr) == -1) goto cleanup; fixup_superblock(); if (errorlocked && (initial_error_state_adjust() == -1)) goto cleanup; /* * asblk could be dirty because we found a mismatch between * the primary superblock and one of its backups in checksb(). */ if (asblk.b_dirty && !bflag) { (void) memmove(&altsblock, &sblock, (size_t)sblock.fs_sbsize); flush(fswritefd, &asblk); } getsummaryinfo(); /* * if not error-locked, using the standard superblock, * not bad log, not forced, preening, and is clean; * stop checking */ if (!errorlocked && (bflag == 0) && ((!islog || islogok) && (fflag == 0) && preen && (FSOKAY == (sblock.fs_state + sblock.fs_time)) && ((sblock.fs_clean == FSLOG && islog) || ((sblock.fs_clean == FSCLEAN) || (sblock.fs_clean == FSSTABLE))))) { iscorrupt = 0; printclean(); goto cleanup; } if (create_and_init_maps() == -1) goto nomaps; bufinit(); return (devstr); nomaps: ckfini(); exitstat = EXERRFATAL; /* FALLTHROUGH */ cleanup: unbufinit(); uncreate_maps(); ungetsummaryinfo(); /* * Can't get rid of the superblock buffer, because our * caller references it to generate the summary statistics. */ return (NULL); } /* * Undoes the allocations in create_and_init_maps() */ static void uncreate_maps(void) { /* * No ordering dependency amongst these, so they are here in * the same order they were calculated. */ if (blockmap != NULL) free(blockmap); if (statemap != NULL) free(statemap); if (lncntp != NULL) free(lncntp); if (inpsort != NULL) free(inpsort); if (inphead != NULL) free(inphead); if (aclpsort != NULL) free(aclpsort); if (aclphead != NULL) free(aclphead); } /* * mkfs limits the size of the inode map to be no more than a third of * the cylinder group space. We'll use that value for sanity checking * the superblock's inode per group value. */ #define MAXIpG (roundup(sblock.fs_bsize * NBBY / 3, sblock.fs_inopb)) /* * Check the super block and its summary info. */ static int checksb(int listerr) { caddr_t err; /* * When the fs check is successfully completed, the alternate super * block at sblk.b_bno will be overwritten by ckfini() with the * repaired super block. */ sblk.b_bno = bflag ? bflag : (SBOFF / dev_bsize); sblk.b_size = SBSIZE; /* * Sanity-check some of the values we are going to use later * in allocation requests. */ if (sblock.fs_cstotal.cs_ndir < 1 || sblock.fs_cstotal.cs_ndir > sblock.fs_ncg * sblock.fs_ipg) { if (verbose) (void) printf( "Found %d directories, should be between 1 and %d inclusive.\n", sblock.fs_cstotal.cs_ndir, sblock.fs_ncg * sblock.fs_ipg); err = "NUMBER OF DIRECTORIES OUT OF RANGE"; goto failedsb; } if (sblock.fs_nrpos <= 0 || sblock.fs_postbloff < 0 || sblock.fs_cpc < 0 || (sblock.fs_postbloff + (sblock.fs_nrpos * sblock.fs_cpc * sizeof (short))) > sblock.fs_sbsize) { err = "ROTATIONAL POSITION TABLE SIZE OUT OF RANGE"; goto failedsb; } if (sblock.fs_cssize != fragroundup(&sblock, sblock.fs_ncg * sizeof (struct csum))) { err = "SIZE OF CYLINDER GROUP SUMMARY AREA WRONG"; goto failedsb; } if (sblock.fs_inopb != (sblock.fs_bsize / sizeof (struct dinode))) { err = "INOPB NONSENSICAL RELATIVE TO BSIZE"; goto failedsb; } if (sblock.fs_bsize > MAXBSIZE) { err = "BLOCK SIZE LARGER THAN MAXIMUM SUPPORTED"; goto failedsb; } if (sblock.fs_bsize != (sblock.fs_frag * sblock.fs_fsize)) { err = "FRAGS PER BLOCK OR FRAG SIZE WRONG"; goto failedsb; } if (sblock.fs_dsize >= sblock.fs_size) { err = "NUMBER OF DATA BLOCKS OUT OF RANGE"; goto failedsb; } #if 0 if (sblock.fs_size > (sblock.fs_nsect * sblock.fs_ntrak * sblock.fs_ncyl)) { err = "FILESYSTEM SIZE LARGER THAN DEVICE"; goto failedsb; } #endif /* * Check that the number of inodes per group isn't less than or * equal to zero. Also makes sure it isn't more than the * maximum number mkfs enforces. */ if (sblock.fs_ipg <= 0 || sblock.fs_ipg > MAXIpG) { err = "INODES PER GROUP OUT OF RANGE"; goto failedsb; } if (sblock.fs_cgsize > sblock.fs_bsize) { err = "CG HEADER LARGER THAN ONE BLOCK"; goto failedsb; } /* * Set all possible fields that could differ, then do check * of whole super block against an alternate super block. * When an alternate super-block is specified this check is skipped. */ (void) getblk(&asblk, cgsblock(&sblock, sblock.fs_ncg - 1), (size_t)sblock.fs_sbsize); if (asblk.b_errs != 0) { brelse(&asblk); return (0); } if (bflag != 0) { /* * Invalidate clean flag and state information. * Note that we couldn't return until after the * above getblk(), because we're going to want to * update asblk when everything's done. */ sblock.fs_clean = FSACTIVE; sblock.fs_state = (long)sblock.fs_time; sblock.fs_reclaim = 0; sbdirty(); havesb = 1; return (1); } altsblock.fs_link = sblock.fs_link; altsblock.fs_rolled = sblock.fs_rolled; altsblock.fs_time = sblock.fs_time; altsblock.fs_state = sblock.fs_state; altsblock.fs_cstotal = sblock.fs_cstotal; altsblock.fs_cgrotor = sblock.fs_cgrotor; altsblock.fs_fmod = sblock.fs_fmod; altsblock.fs_clean = sblock.fs_clean; altsblock.fs_ronly = sblock.fs_ronly; altsblock.fs_flags = sblock.fs_flags; altsblock.fs_maxcontig = sblock.fs_maxcontig; altsblock.fs_minfree = sblock.fs_minfree; altsblock.fs_optim = sblock.fs_optim; altsblock.fs_rotdelay = sblock.fs_rotdelay; altsblock.fs_maxbpg = sblock.fs_maxbpg; altsblock.fs_logbno = sblock.fs_logbno; altsblock.fs_reclaim = sblock.fs_reclaim; altsblock.fs_si = sblock.fs_si; (void) memmove((void *)altsblock.fs_fsmnt, (void *)sblock.fs_fsmnt, sizeof (sblock.fs_fsmnt)); /* * The following should not have to be copied. */ (void) memmove((void *)altsblock.fs_u.fs_csp_pad, (void *)sblock.fs_u.fs_csp_pad, sizeof (sblock.fs_u.fs_csp_pad)); altsblock.fs_fsbtodb = sblock.fs_fsbtodb; altsblock.fs_npsect = sblock.fs_npsect; altsblock.fs_nrpos = sblock.fs_nrpos; if (memcmp((void *)&sblock, (void *)&altsblock, (size_t)sblock.fs_sbsize) != 0) { err = "BAD VALUES IN SUPER BLOCK"; goto failedsb; } havesb = 1; return (1); failedsb: badsb(listerr, err); return (0); } static void badsb(int listerr, caddr_t s) { if (!listerr) return; if (preen) (void) printf("%s: ", devname); (void) printf("BAD SUPERBLOCK AT BLOCK %d: %s\n", bflag != 0 ? bflag : SBLOCK, s); if (preen) { pwarn( "USE AN ALTERNATE SUPERBLOCK TO SUPPLY NEEDED INFORMATION;\n"); pwarn("e.g. fsck [-F ufs] -o b=# [special ...] \n"); exitstat = EXERRFATAL; pfatal( "where # is the alternate super block. SEE fsck_ufs(8). \n"); } /* we're expected to return if not preening */ } /* * Write out the super block into each of the alternate super blocks. */ void write_altsb(int fd) { int cylno; for (cylno = 0; cylno < sblock.fs_ncg; cylno++) bwrite(fd, (caddr_t)&sblock, fsbtodb(&sblock, cgsblock(&sblock, cylno)), sblock.fs_sbsize); } static void sblock_init(void) { fsmodified = 0; if (errorlocked) isdirty = 1; lfdir = 0; initbarea(&sblk); initbarea(&asblk); /* * May have buffer left over from previous filesystem check. */ if (sblk.b_un.b_buf == NULL) sblk.b_un.b_buf = calloc(1, SBSIZE); if (asblk.b_un.b_buf == NULL) asblk.b_un.b_buf = calloc(1, SBSIZE); if (sblk.b_un.b_buf == NULL || asblk.b_un.b_buf == NULL) errexit("cannot allocate space for superblock\n"); /* * Could get the actual sector size from the device here, * but considering how much would need to change in the rest * of the system before it'd be a problem for us, it's not * worth worrying about right now. */ dev_bsize = secsize = DEV_BSIZE; } /* * Calculate a prototype superblock based on information in the disk label. * When done the cgsblock macro can be calculated and the fs_ncg field * can be used. Do NOT attempt to use other macros without verifying that * their needed information is available! * * In BSD, the disk label includes all sorts of useful information, * like cpg. Solaris doesn't have that, and deriving it (as well as * some other parameters) is difficult. Rather than duplicate the * code, just ask mkfs what it would've come up with by default. * Ideally, we'd just link in the code, but given the source base * involved, it's more practical to just get a binary dump. * * The one minor drawback to the above approach is that newfs and mkfs * will produce vastly different layouts for the same partition if * they're allowed to default everything. So, if the superblock that * mkfs gives us doesn't work for guessing where the alternates are, * we need to try newfs. */ static int calcsb(calcsb_t style, caddr_t dev, int devfd, struct fs *fs) { #define FROM_CHILD 0 #define TO_FSCK 1 #define CMD_IDX 0 #define DEV_IDX 3 #define SIZE_IDX 4 int child_pipe[2]; caddr_t mkfsline[] = { "", /* CMD_IDX */ "-o", "calcbinsb,N", NULL, /* DEV_IDX */ NULL, /* SIZE_IDX */ NULL }; caddr_t newfsline[] = { "", /* CMD_IDX */ "-B", "-N", NULL, /* DEV_IDX */ NULL }; int pending, transferred; caddr_t *cmdline; caddr_t target; caddr_t sizestr = NULL; caddr_t path_old, path_new, mkfs_dir, mkfs_path, newfs_path; caddr_t slash; diskaddr_t size; int devnull; switch (style) { case MKFS_STYLE: if (debug) (void) printf("calcsb() going with style MKFS\n"); cmdline = mkfsline; break; case NEWFS_STYLE: if (debug) (void) printf("calcsb() going with style NEWFS\n"); cmdline = newfsline; break; default: if (debug) (void) printf("calcsb() doesn't undestand style %d\n", style); return (0); } cmdline[DEV_IDX] = dev; /* * Normally, only use the stock versions of the utilities. * However, if we're debugging, the odds are that we're * using experimental versions of them as well, so allow * some flexibility. */ mkfs_path = getenv("MKFS_PATH"); if (!debug || (mkfs_path == NULL)) mkfs_path = MKFS_PATH; newfs_path = getenv("NEWFS_PATH"); if (!debug || (newfs_path == NULL)) newfs_path = NEWFS_PATH; if (style == MKFS_STYLE) { cmdline[CMD_IDX] = mkfs_path; size = getdisksize(dev, devfd); if (size == 0) return (0); (void) fsck_asprintf(&sizestr, "%lld", (longlong_t)size); cmdline[SIZE_IDX] = sizestr; } else if (style == NEWFS_STYLE) { /* * Make sure that newfs will find the right version of mkfs. */ cmdline[CMD_IDX] = newfs_path; path_old = getenv("PATH"); /* mkfs_path is always initialized, despite lint's concerns */ mkfs_dir = strdup(mkfs_path); if (mkfs_dir == NULL) return (0); /* * If no location data for mkfs, don't need to do * anything about PATH. */ slash = strrchr(mkfs_dir, '/'); if (slash != NULL) { /* * Just want the dir, so discard the executable name. */ *slash = '\0'; /* * newfs uses system() to find mkfs, so make sure * that the one we want to use is first on the * list. Don't free path_new upon success, as it * has become part of the environment. */ (void) fsck_asprintf(&path_new, "PATH=%s:%s", mkfs_dir, path_old); if (putenv(path_new) != 0) { free(mkfs_dir); free(path_new); return (0); } } free(mkfs_dir); } else { /* * Bad search style, quietly return failure. */ if (debug) { (void) printf("calcsb: got bad style number %d\n", (int)style); } return (0); } if (pipe(child_pipe) < 0) { pfatal("calcsb: could not create pipe: %s\n", strerror(errno)); if (sizestr != NULL) free(sizestr); return (0); } switch (fork()) { case -1: pfatal("calcsb: fork failed: %s\n", strerror(errno)); if (sizestr != NULL) free(sizestr); return (0); case 0: if (dup2(child_pipe[TO_FSCK], fileno(stdout)) < 0) { (void) printf( "calcsb: could not rename file descriptor: %s\n", strerror(errno)); exit(EXBADPARM); } devnull = open("/dev/null", O_WRONLY); if (devnull == -1) { (void) printf("calcsb: could not open /dev/null: %s\n", strerror(errno)); exit(EXBADPARM); } if (dup2(devnull, fileno(stderr)) < 0) { (void) printf( "calcsb: could not rename file descriptor: %s\n", strerror(errno)); exit(EXBADPARM); } (void) close(child_pipe[FROM_CHILD]); (void) execv(cmdline[CMD_IDX], cmdline); (void) printf("calcsb: could not exec %s: %s\n", cmdline[CMD_IDX], strerror(errno)); exit(EXBADPARM); /* NOTREACHED */ default: break; } (void) close(child_pipe[TO_FSCK]); if (sizestr != NULL) free(sizestr); pending = sizeof (struct fs); target = (caddr_t)fs; do { transferred = read(child_pipe[FROM_CHILD], target, pending); pending -= transferred; target += transferred; } while ((pending > 0) && (transferred > 0)); if (pending > 0) { if (transferred < 0) pfatal( "calcsb: binary read of superblock from %s failed: %s\n", (style == MKFS_STYLE) ? "mkfs" : "newfs", (transferred < 0) ? strerror(errno) : ""); else pfatal( "calcsb: short read of superblock from %s\n", (style == MKFS_STYLE) ? "mkfs" : "newfs"); return (0); } (void) close(child_pipe[FROM_CHILD]); (void) wait(NULL); if ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC)) return (0); return (1); } /* * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. Neither the name of the University nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" struct bufarea *pbp; struct bufarea *pdirbp; caddr_t mount_point = NULL; static struct bufarea bufhead; /* head of list of other blks in filesys */ char *elock_combuf; char *elock_mountp; static struct lockfs *lfp; /* current lockfs status */ static int64_t diskreads, totalreads; /* Disk cache statistics */ static int log_checksum(int32_t *, int32_t *, int); static void vdirerror(fsck_ino_t, caddr_t, va_list); static struct mnttab *search_mnttab(caddr_t, caddr_t, caddr_t, size_t); static struct vfstab *search_vfstab(caddr_t, caddr_t, caddr_t, size_t); static void vpwarn(caddr_t, va_list); static int getaline(FILE *, caddr_t, int); static struct bufarea *alloc_bufarea(void); static void rwerror(caddr_t, diskaddr_t, int rval); static void debugclean(void); static void report_io_prob(caddr_t, diskaddr_t, size_t, ssize_t); static void freelogblk(daddr32_t); static void verrexit(caddr_t, va_list); static void vpfatal(caddr_t, va_list); static diskaddr_t get_device_size(int, caddr_t); static diskaddr_t brute_force_get_device_size(int); static void cg_constants(int, daddr32_t *, daddr32_t *, daddr32_t *, daddr32_t *, daddr32_t *, daddr32_t *); int ftypeok(struct dinode *dp) { switch (dp->di_mode & IFMT) { case IFDIR: case IFREG: case IFBLK: case IFCHR: case IFLNK: case IFSOCK: case IFIFO: case IFSHAD: case IFATTRDIR: return (1); default: if (debug) (void) printf("bad file type 0%o\n", dp->di_mode); return (0); } } int acltypeok(struct dinode *dp) { if (CHECK_ACL_ALLOWED(dp->di_mode & IFMT)) return (1); if (debug) (void) printf("bad file type for acl I=%d: 0%o\n", dp->di_shadow, dp->di_mode); return (0); } NOTE(PRINTFLIKE(1)) int reply(caddr_t fmt, ...) { va_list ap; char line[80]; if (preen) pfatal("INTERNAL ERROR: GOT TO reply() in preen mode"); if (mflag) { /* * We don't know what's going on, so don't potentially * make things worse by having errexit() write stuff * out to disk. */ (void) printf( "\n%s: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY.\n", devname); exit(EXERRFATAL); } va_start(ap, fmt); (void) putchar('\n'); (void) vprintf(fmt, ap); (void) putchar('?'); (void) putchar(' '); va_end(ap); if (nflag || fswritefd < 0) { (void) printf(" no\n\n"); return (0); } if (yflag) { (void) printf(" yes\n\n"); return (1); } (void) fflush(stdout); if (getaline(stdin, line, sizeof (line)) == EOF) errexit("\n"); (void) printf("\n"); if (line[0] == 'y' || line[0] == 'Y') { return (1); } else { return (0); } } int getaline(FILE *fp, caddr_t loc, int maxlen) { int n; caddr_t p, lastloc; p = loc; lastloc = &p[maxlen-1]; while ((n = getc(fp)) != '\n') { if (n == EOF) return (EOF); if (!isspace(n) && p < lastloc) *p++ = (char)n; } *p = '\0'; /* LINTED pointer difference won't overflow */ return (p - loc); } /* * Malloc buffers and set up cache. */ void bufinit(void) { struct bufarea *bp; int bufcnt, i; caddr_t bufp; bufp = malloc((size_t)sblock.fs_bsize); if (bufp == NULL) goto nomem; initbarea(&cgblk); cgblk.b_un.b_buf = bufp; bufhead.b_next = bufhead.b_prev = &bufhead; bufcnt = MAXBUFSPACE / sblock.fs_bsize; if (bufcnt < MINBUFS) bufcnt = MINBUFS; for (i = 0; i < bufcnt; i++) { bp = (struct bufarea *)malloc(sizeof (struct bufarea)); if (bp == NULL) { if (i >= MINBUFS) goto noalloc; goto nomem; } bufp = malloc((size_t)sblock.fs_bsize); if (bufp == NULL) { free((void *)bp); if (i >= MINBUFS) goto noalloc; goto nomem; } initbarea(bp); bp->b_un.b_buf = bufp; bp->b_prev = &bufhead; bp->b_next = bufhead.b_next; bufhead.b_next->b_prev = bp; bufhead.b_next = bp; } noalloc: bufhead.b_size = i; /* save number of buffers */ pbp = pdirbp = NULL; return; nomem: errexit("cannot allocate buffer pool\n"); /* NOTREACHED */ } /* * Undo a bufinit(). */ void unbufinit(void) { int cnt; struct bufarea *bp, *nbp; cnt = 0; for (bp = bufhead.b_prev; bp != NULL && bp != &bufhead; bp = nbp) { cnt++; flush(fswritefd, bp); nbp = bp->b_prev; /* * We're discarding the entire chain, so this isn't * technically necessary. However, it doesn't hurt * and lint's data flow analysis is much happier * (this prevents it from thinking there's a chance * of our using memory elsewhere after it's been released). */ nbp->b_next = bp->b_next; bp->b_next->b_prev = nbp; free((void *)bp->b_un.b_buf); free((void *)bp); } if (bufhead.b_size != cnt) errexit("Panic: cache lost %d buffers\n", bufhead.b_size - cnt); } /* * Manage a cache of directory blocks. */ struct bufarea * getdatablk(daddr32_t blkno, size_t size) { struct bufarea *bp; for (bp = bufhead.b_next; bp != &bufhead; bp = bp->b_next) if (bp->b_bno == fsbtodb(&sblock, blkno)) { goto foundit; } for (bp = bufhead.b_prev; bp != &bufhead; bp = bp->b_prev) if ((bp->b_flags & B_INUSE) == 0) break; if (bp == &bufhead) { bp = alloc_bufarea(); if (bp == NULL) { errexit("deadlocked buffer pool\n"); /* NOTREACHED */ } } /* * We're at the same logical level as getblk(), so if there * are any errors, we'll let our caller handle them. */ diskreads++; (void) getblk(bp, blkno, size); foundit: totalreads++; bp->b_cnt++; /* * Move the buffer to head of linked list if it isn't * already there. */ if (bufhead.b_next != bp) { bp->b_prev->b_next = bp->b_next; bp->b_next->b_prev = bp->b_prev; bp->b_prev = &bufhead; bp->b_next = bufhead.b_next; bufhead.b_next->b_prev = bp; bufhead.b_next = bp; } bp->b_flags |= B_INUSE; return (bp); } void brelse(struct bufarea *bp) { bp->b_cnt--; if (bp->b_cnt == 0) { bp->b_flags &= ~B_INUSE; } } struct bufarea * getblk(struct bufarea *bp, daddr32_t blk, size_t size) { diskaddr_t dblk; dblk = fsbtodb(&sblock, blk); if (bp->b_bno == dblk) return (bp); flush(fswritefd, bp); bp->b_errs = fsck_bread(fsreadfd, bp->b_un.b_buf, dblk, size); bp->b_bno = dblk; bp->b_size = size; return (bp); } void flush(int fd, struct bufarea *bp) { int i, j; caddr_t sip; long size; if (!bp->b_dirty) return; /* * It's not our buf, so if there are errors, let whoever * acquired it deal with the actual problem. */ if (bp->b_errs != 0) pfatal("WRITING ZERO'ED BLOCK %lld TO DISK\n", bp->b_bno); bp->b_dirty = 0; bp->b_errs = 0; bwrite(fd, bp->b_un.b_buf, bp->b_bno, (long)bp->b_size); if (bp != &sblk) { return; } /* * We're flushing the superblock, so make sure all the * ancillary bits go out as well. */ sip = (caddr_t)sblock.fs_u.fs_csp; for (i = 0, j = 0; i < sblock.fs_cssize; i += sblock.fs_bsize, j++) { size = sblock.fs_cssize - i < sblock.fs_bsize ? sblock.fs_cssize - i : sblock.fs_bsize; bwrite(fswritefd, sip, fsbtodb(&sblock, sblock.fs_csaddr + j * sblock.fs_frag), size); sip += size; } } static void rwerror(caddr_t mesg, diskaddr_t blk, int rval) { int olderr = errno; if (!preen) (void) printf("\n"); if (rval == -1) pfatal("CANNOT %s: DISK BLOCK %lld: %s", mesg, blk, strerror(olderr)); else pfatal("CANNOT %s: DISK BLOCK %lld", mesg, blk); if (reply("CONTINUE") == 0) { exitstat = EXERRFATAL; errexit("Program terminated\n"); } } void ckfini(void) { int64_t percentage; if (fswritefd < 0) return; flush(fswritefd, &sblk); /* * Were we using a backup superblock? */ if (havesb && sblk.b_bno != SBOFF / dev_bsize) { if (preen || reply("UPDATE STANDARD SUPERBLOCK") == 1) { sblk.b_bno = SBOFF / dev_bsize; sbdirty(); flush(fswritefd, &sblk); } } flush(fswritefd, &cgblk); if (cgblk.b_un.b_buf != NULL) { free((void *)cgblk.b_un.b_buf); cgblk.b_un.b_buf = NULL; } unbufinit(); pbp = NULL; pdirbp = NULL; if (debug) { /* * Note that we only count cache-related reads. * Anything that called fsck_bread() or getblk() * directly are explicitly not cached, so they're not * included here. */ if (totalreads != 0) percentage = diskreads * 100 / totalreads; else percentage = 0; (void) printf("cache missed %lld of %lld reads (%lld%%)\n", (longlong_t)diskreads, (longlong_t)totalreads, (longlong_t)percentage); } (void) close(fsreadfd); (void) close(fswritefd); fsreadfd = -1; fswritefd = -1; } int fsck_bread(int fd, caddr_t buf, diskaddr_t blk, size_t size) { caddr_t cp; int i; int errs; offset_t offset = ldbtob(blk); offset_t addr; /* * In our universe, nothing exists before the superblock, so * just pretend it's always zeros. This is the complement of * bwrite()'s ignoring write requests into that space. */ if (blk < SBLOCK) { if (debug) (void) printf( "WARNING: fsck_bread() passed blkno < %d (%lld)\n", SBLOCK, (longlong_t)blk); (void) memset(buf, 0, (size_t)size); return (1); } if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } if ((i = read(fd, buf, size)) == size) { return (0); } rwerror("READ", blk, i); if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } errs = 0; (void) memset(buf, 0, (size_t)size); pwarn("THE FOLLOWING SECTORS COULD NOT BE READ:"); for (cp = buf, i = 0; i < btodb(size); i++, cp += DEV_BSIZE) { addr = ldbtob(blk + i); if (llseek(fd, addr, SEEK_SET) < 0 || read(fd, cp, (int)secsize) < 0) { iscorrupt = 1; (void) printf(" %llu", blk + (u_longlong_t)i); errs++; } } (void) printf("\n"); return (errs); } void bwrite(int fd, caddr_t buf, diskaddr_t blk, int64_t size) { int i; int n; caddr_t cp; offset_t offset = ldbtob(blk); offset_t addr; if (fd < 0) return; if (blk < SBLOCK) { if (debug) (void) printf( "WARNING: Attempt to write illegal blkno %lld on %s\n", (longlong_t)blk, devname); return; } if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } if ((i = write(fd, buf, (int)size)) == size) { fsmodified = 1; return; } rwerror("WRITE", blk, i); if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } pwarn("THE FOLLOWING SECTORS COULD NOT BE WRITTEN:"); for (cp = buf, i = 0; i < btodb(size); i++, cp += DEV_BSIZE) { n = 0; addr = ldbtob(blk + i); if (llseek(fd, addr, SEEK_SET) < 0 || (n = write(fd, cp, DEV_BSIZE)) < 0) { iscorrupt = 1; (void) printf(" %llu", blk + (u_longlong_t)i); } else if (n > 0) { fsmodified = 1; } } (void) printf("\n"); } /* * Allocates the specified number of contiguous fragments. */ daddr32_t allocblk(int wantedfrags) { int block, leadfrag, tailfrag; daddr32_t selected; size_t size; struct bufarea *bp; /* * It's arguable whether we should just fail, or instead * error out here. Since we should only ever be asked for * a single fragment or an entire block (i.e., sblock.fs_frag), * we'll fail out because anything else means somebody * changed code without considering all of the ramifications. */ if (wantedfrags <= 0 || wantedfrags > sblock.fs_frag) { exitstat = EXERRFATAL; errexit("allocblk() asked for %d frags. " "Legal range is 1 to %d", wantedfrags, sblock.fs_frag); } /* * For each filesystem block, look at every possible starting * offset within the block such that we can get the number of * contiguous fragments that we need. This is a drastically * simplified version of the kernel's mapsearch() and alloc*(). * It's also correspondingly slower. */ for (block = 0; block < maxfsblock - sblock.fs_frag; block += sblock.fs_frag) { for (leadfrag = 0; leadfrag <= sblock.fs_frag - wantedfrags; leadfrag++) { /* * Is first fragment of candidate run available? */ if (testbmap(block + leadfrag)) continue; /* * Are the rest of them available? */ for (tailfrag = 1; tailfrag < wantedfrags; tailfrag++) if (testbmap(block + leadfrag + tailfrag)) break; if (tailfrag < wantedfrags) { /* * No, skip the known-unusable run. */ leadfrag += tailfrag; continue; } /* * Found what we need, so claim them. */ for (tailfrag = 0; tailfrag < wantedfrags; tailfrag++) setbmap(block + leadfrag + tailfrag); n_blks += wantedfrags; size = wantedfrags * sblock.fs_fsize; selected = block + leadfrag; bp = getdatablk(selected, size); (void) memset((void *)bp->b_un.b_buf, 0, size); dirty(bp); brelse(bp); if (debug) (void) printf( "allocblk: selected %d (in block %d), frags %d, size %d\n", selected, selected % sblock.fs_bsize, wantedfrags, (int)size); return (selected); } } return (0); } /* * Free a previously allocated block */ void freeblk(fsck_ino_t ino, daddr32_t blkno, int frags) { struct inodesc idesc; if (debug) (void) printf("debug: freeing %d fragments starting at %d\n", frags, blkno); init_inodesc(&idesc); idesc.id_number = ino; idesc.id_blkno = blkno; idesc.id_numfrags = frags; idesc.id_truncto = -1; /* * Nothing in the return status has any relevance to how * we're using pass4check(), so just ignore it. */ (void) pass4check(&idesc); } /* * Fill NAMEBUF with a path starting in CURDIR for INO. Assumes * that the given buffer is at least MAXPATHLEN + 1 characters. */ void getpathname(caddr_t namebuf, fsck_ino_t curdir, fsck_ino_t ino) { int len; caddr_t cp; struct dinode *dp; struct inodesc idesc; struct inoinfo *inp; if (debug) (void) printf("debug: getpathname(curdir %d, ino %d)\n", curdir, ino); if ((curdir == 0) || (!INO_IS_DVALID(curdir))) { (void) strcpy(namebuf, "?"); return; } if ((curdir == UFSROOTINO) && (ino == UFSROOTINO)) { (void) strcpy(namebuf, "/"); return; } init_inodesc(&idesc); idesc.id_type = DATA; cp = &namebuf[MAXPATHLEN - 1]; *cp = '\0'; /* * In the case of extended attributes, our * parent won't necessarily be a directory, so just * return what we've found with a prefix indicating * that it's an XATTR. Presumably our caller will * know what's going on and do something useful, like * work out the path of the parent and then combine * the two names. * * Can't use strcpy(), etc, because we've probably * already got some name information in the buffer and * the usual trailing \0 would lose it. */ dp = ginode(curdir); if ((dp->di_mode & IFMT) == IFATTRDIR) { idesc.id_number = curdir; idesc.id_parent = ino; idesc.id_func = findname; idesc.id_name = namebuf; idesc.id_fix = NOFIX; if ((ckinode(dp, &idesc, CKI_TRAVERSE) & FOUND) == 0) { *cp-- = '?'; } len = sizeof (XATTR_DIR_NAME) - 1; cp -= len; (void) memmove(cp, XATTR_DIR_NAME, len); goto attrname; } /* * If curdir == ino, need to get a handle on .. so we * can search it for ino's name. Otherwise, just search * the given directory for ino. Repeat until out of space * or a full path has been built. */ if (curdir != ino) { idesc.id_parent = curdir; goto namelookup; } while (ino != UFSROOTINO && ino != 0) { idesc.id_number = ino; idesc.id_func = findino; idesc.id_name = ".."; idesc.id_fix = NOFIX; if ((ckinode(ginode(ino), &idesc, CKI_TRAVERSE) & FOUND) == 0) { inp = getinoinfo(ino); if ((inp == NULL) || (inp->i_parent == 0)) { break; } idesc.id_parent = inp->i_parent; } /* * To get this far, id_parent must have the inode * number for `..' in it. By definition, that's got * to be a directory, so search it for the inode of * interest. */ namelookup: idesc.id_number = idesc.id_parent; idesc.id_parent = ino; idesc.id_func = findname; idesc.id_name = namebuf; idesc.id_fix = NOFIX; if ((ckinode(ginode(idesc.id_number), &idesc, CKI_TRAVERSE) & FOUND) == 0) { break; } /* * Prepend to what we've accumulated so far. If * there's not enough room for even one more path element * (of the worst-case length), then bail out. */ len = strlen(namebuf); cp -= len; if (cp < &namebuf[MAXNAMLEN]) break; (void) memmove(cp, namebuf, len); *--cp = '/'; /* * Corner case for a looped-to-itself directory. */ if (ino == idesc.id_number) break; /* * Climb one level of the hierarchy. In other words, * the current .. becomes the inode to search for and * its parent becomes the directory to search in. */ ino = idesc.id_number; } /* * If we hit a discontinuity in the hierarchy, indicate it by * prefixing the path so far with `?'. Otherwise, the first * character will be `/' as a side-effect of the *--cp above. * * The special case is to handle the situation where we're * trying to look something up in UFSROOTINO, but didn't find * it. */ if (ino != UFSROOTINO || cp == &namebuf[MAXPATHLEN - 1]) { if (cp > namebuf) cp--; *cp = '?'; } /* * The invariants being used for buffer integrity are: * - namebuf[] is terminated with \0 before anything else * - cp is always <= the last element of namebuf[] * - the new path element is always stored at the * beginning of namebuf[], and is no more than MAXNAMLEN-1 * characters * - cp is is decremented by the number of characters in * the new path element * - if, after the above accounting for the new element's * size, there is no longer enough room at the beginning of * namebuf[] for a full-sized path element and a slash, * terminate the loop. cp is in the range * &namebuf[0]..&namebuf[MAXNAMLEN - 1] */ attrname: /* LINTED per the above discussion */ (void) memmove(namebuf, cp, &namebuf[MAXPATHLEN] - cp); } /* ARGSUSED */ void catch(int dummy) { ckfini(); exit(EXSIGNAL); } /* * When preening, allow a single quit to signal * a special exit after filesystem checks complete * so that reboot sequence may be interrupted. */ /* ARGSUSED */ void catchquit(int dummy) { (void) printf("returning to single-user after filesystem check\n"); interrupted = 1; (void) signal(SIGQUIT, SIG_DFL); } /* * determine whether an inode should be fixed. */ NOTE(PRINTFLIKE(2)) int dofix(struct inodesc *idesc, caddr_t msg, ...) { int rval = 0; va_list ap; va_start(ap, msg); switch (idesc->id_fix) { case DONTKNOW: if (idesc->id_type == DATA) vdirerror(idesc->id_number, msg, ap); else vpwarn(msg, ap); if (preen) { idesc->id_fix = FIX; rval = ALTERED; break; } if (reply("SALVAGE") == 0) { idesc->id_fix = NOFIX; break; } idesc->id_fix = FIX; rval = ALTERED; break; case FIX: rval = ALTERED; break; case NOFIX: break; default: errexit("UNKNOWN INODESC FIX MODE %d\n", (int)idesc->id_fix); } va_end(ap); return (rval); } NOTE(PRINTFLIKE(1)) void errexit(caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); verrexit(fmt, ap); /* NOTREACHED */ } NOTE(PRINTFLIKE(1)) static void verrexit(caddr_t fmt, va_list ap) { static int recursing = 0; if (!recursing) { recursing = 1; if (errorlocked || iscorrupt) { if (havesb && fswritefd >= 0) { sblock.fs_clean = FSBAD; sblock.fs_state = FSOKAY - (long)sblock.fs_time; sblock.fs_state = -sblock.fs_state; sbdirty(); write_altsb(fswritefd); flush(fswritefd, &sblk); } } ckfini(); recursing = 0; } (void) vprintf(fmt, ap); if (fmt[strlen(fmt) - 1] != '\n') (void) putchar('\n'); exit((exitstat != 0) ? exitstat : EXERRFATAL); } /* * An unexpected inconsistency occured. * Die if preening, otherwise just print message and continue. */ NOTE(PRINTFLIKE(1)) void pfatal(caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vpfatal(fmt, ap); va_end(ap); } NOTE(PRINTFLIKE(1)) static void vpfatal(caddr_t fmt, va_list ap) { if (preen) { if (*fmt != '\0') { (void) printf("%s: ", devname); (void) vprintf(fmt, ap); (void) printf("\n"); } (void) printf( "%s: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY.\n", devname); if (havesb && fswritefd >= 0) { sblock.fs_clean = FSBAD; sblock.fs_state = -(FSOKAY - (long)sblock.fs_time); sbdirty(); flush(fswritefd, &sblk); } /* * We're exiting, it doesn't really matter that our * caller doesn't get to call va_end(). */ if (exitstat == 0) exitstat = EXFNDERRS; exit(exitstat); } if (*fmt != '\0') { (void) vprintf(fmt, ap); } } /* * Pwarn just prints a message when not preening, * or a warning (preceded by filename) when preening. */ NOTE(PRINTFLIKE(1)) void pwarn(caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vpwarn(fmt, ap); va_end(ap); } NOTE(PRINTFLIKE(1)) static void vpwarn(caddr_t fmt, va_list ap) { if (*fmt != '\0') { if (preen) (void) printf("%s: ", devname); (void) vprintf(fmt, ap); } } /* * Like sprintf(), except the buffer is dynamically allocated * and returned, instead of being passed in. A pointer to the * buffer is stored in *RET, and FMT is the usual format string. * The number of characters in *RET (excluding the trailing \0, * to be consistent with the other *printf() routines) is returned. * * Solaris doesn't have asprintf(3C) yet, unfortunately. */ NOTE(PRINTFLIKE(2)) int fsck_asprintf(caddr_t *ret, caddr_t fmt, ...) { int len; caddr_t buffer; va_list ap; va_start(ap, fmt); len = vsnprintf(NULL, 0, fmt, ap); va_end(ap); buffer = malloc((len + 1) * sizeof (char)); if (buffer == NULL) { errexit("Out of memory in asprintf\n"); /* NOTREACHED */ } va_start(ap, fmt); (void) vsnprintf(buffer, len + 1, fmt, ap); va_end(ap); *ret = buffer; return (len); } /* * So we can take advantage of kernel routines in ufs_subr.c. */ /* PRINTFLIKE2 */ void cmn_err(int level, caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); if (level == CE_PANIC) { (void) printf("INTERNAL INCONSISTENCY:"); verrexit(fmt, ap); } else { (void) vprintf(fmt, ap); } va_end(ap); } /* * Check to see if unraw version of name is already mounted. * Updates devstr with the device name if devstr is not NULL * and str_size is positive. */ int mounted(caddr_t name, caddr_t devstr, size_t str_size) { int found; struct mnttab *mntent; mntent = search_mnttab(NULL, unrawname(name), devstr, str_size); if (mntent == NULL) return (M_NOMNT); /* * It's mounted. With or without write access? */ if (hasmntopt(mntent, MNTOPT_RO) != 0) found = M_RO; /* mounted as RO */ else found = M_RW; /* mounted as R/W */ if (mount_point == NULL) { mount_point = strdup(mntent->mnt_mountp); if (mount_point == NULL) { errexit("fsck: memory allocation failure: %s", strerror(errno)); /* NOTREACHED */ } if (devstr != NULL && str_size > 0) (void) strlcpy(devstr, mntent->mnt_special, str_size); } return (found); } /* * Check to see if name corresponds to an entry in vfstab, and that the entry * does not have option ro. */ int writable(caddr_t name) { int rw = 1; struct vfstab vfsbuf, vfskey; FILE *vfstab; vfstab = fopen(VFSTAB, "r"); if (vfstab == NULL) { (void) printf("can't open %s\n", VFSTAB); return (1); } (void) memset((void *)&vfskey, 0, sizeof (vfskey)); vfsnull(&vfskey); vfskey.vfs_special = unrawname(name); vfskey.vfs_fstype = MNTTYPE_UFS; if ((getvfsany(vfstab, &vfsbuf, &vfskey) == 0) && (hasvfsopt(&vfsbuf, MNTOPT_RO))) { rw = 0; } (void) fclose(vfstab); return (rw); } /* * debugclean */ static void debugclean(void) { if (!debug) return; if ((iscorrupt == 0) && (isdirty == 0)) return; if ((sblock.fs_clean == FSSTABLE) || (sblock.fs_clean == FSCLEAN) || (sblock.fs_clean == FSLOG && islog && islogok) || ((FSOKAY == (sblock.fs_state + sblock.fs_time)) && !errorlocked)) return; (void) printf("WARNING: inconsistencies detected on %s filesystem %s\n", sblock.fs_clean == FSSTABLE ? "stable" : sblock.fs_clean == FSLOG ? "logging" : sblock.fs_clean == FSFIX ? "being fixed" : "clean", devname); } /* * updateclean * Carefully and transparently update the clean flag. * * `iscorrupt' has to be in its final state before this is called. */ int updateclean(void) { int freedlog = 0; struct bufarea cleanbuf; size_t size; ssize_t io_res; diskaddr_t bno; char fsclean; int fsreclaim; char fsflags; int flags_ok = 1; daddr32_t fslogbno; offset_t sblkoff; time_t t; /* * debug stuff */ debugclean(); /* * set fsclean to its appropriate value */ fslogbno = sblock.fs_logbno; fsclean = sblock.fs_clean; fsreclaim = sblock.fs_reclaim; fsflags = sblock.fs_flags; if (FSOKAY != (sblock.fs_state + sblock.fs_time) && !errorlocked) { fsclean = FSACTIVE; } /* * If ufs log is not okay, note that we need to clear it. */ examinelog(NULL); if (fslogbno && !(islog && islogok)) { fsclean = FSACTIVE; fslogbno = 0; } /* * if necessary, update fs_clean and fs_state */ switch (fsclean) { case FSACTIVE: if (!iscorrupt) { fsclean = FSSTABLE; fsreclaim = 0; } break; case FSCLEAN: case FSSTABLE: if (iscorrupt) { fsclean = FSACTIVE; } else { fsreclaim = 0; } break; case FSLOG: if (iscorrupt) { fsclean = FSACTIVE; } else if (!islog || fslogbno == 0) { fsclean = FSSTABLE; fsreclaim = 0; } else if (fflag) { fsreclaim = 0; } break; case FSFIX: fsclean = FSBAD; if (errorlocked && !iscorrupt) { fsclean = islog ? FSLOG : FSCLEAN; } break; default: if (iscorrupt) { fsclean = FSACTIVE; } else { fsclean = FSSTABLE; fsreclaim = 0; } } if (largefile_count > 0) fsflags |= FSLARGEFILES; else fsflags &= ~FSLARGEFILES; /* * There can be two discrepencies here. A) The superblock * shows no largefiles but we found some while scanning. * B) The superblock indicates the presence of largefiles, * but none are present. Note that if preening, the superblock * is silently corrected. */ if ((fsflags == FSLARGEFILES && sblock.fs_flags != FSLARGEFILES) || (fsflags != FSLARGEFILES && sblock.fs_flags == FSLARGEFILES)) flags_ok = 0; if (debug) (void) printf( "** largefile count=%d, fs.fs_flags=%x, flags_ok %d\n", largefile_count, sblock.fs_flags, flags_ok); /* * If fs is unchanged, do nothing. */ if ((!isdirty) && (flags_ok) && (fslogbno == sblock.fs_logbno) && (sblock.fs_clean == fsclean) && (sblock.fs_reclaim == fsreclaim) && (FSOKAY == (sblock.fs_state + sblock.fs_time))) { if (errorlocked) { if (!do_errorlock(LOCKFS_ULOCK)) pwarn( "updateclean(unchanged): unlock(LOCKFS_ULOCK) failed\n"); } return (freedlog); } /* * if user allows, update superblock state */ if (debug) { (void) printf( "superblock: flags 0x%x logbno %d clean %d reclaim %d state 0x%x\n", sblock.fs_flags, sblock.fs_logbno, sblock.fs_clean, sblock.fs_reclaim, sblock.fs_state + sblock.fs_time); (void) printf( "calculated: flags 0x%x logbno %d clean %d reclaim %d state 0x%x\n", fsflags, fslogbno, fsclean, fsreclaim, FSOKAY); } if (!isdirty && !preen && !rerun && (reply("FILE SYSTEM STATE IN SUPERBLOCK IS WRONG; FIX") == 0)) return (freedlog); (void) time(&t); sblock.fs_time = (time32_t)t; if (debug) printclean(); if (sblock.fs_logbno != fslogbno) { examinelog(&freelogblk); freedlog++; } sblock.fs_logbno = fslogbno; sblock.fs_clean = fsclean; sblock.fs_state = FSOKAY - (long)sblock.fs_time; sblock.fs_reclaim = fsreclaim; sblock.fs_flags = fsflags; /* * if superblock can't be written, return */ if (fswritefd < 0) return (freedlog); /* * Read private copy of superblock, update clean flag, and write it. */ bno = sblk.b_bno; size = sblk.b_size; sblkoff = ldbtob(bno); if ((cleanbuf.b_un.b_buf = malloc(size)) == NULL) errexit("out of memory"); if (llseek(fsreadfd, sblkoff, SEEK_SET) == -1) { (void) printf("COULD NOT SEEK TO SUPERBLOCK AT %lld: %s\n", (longlong_t)bno, strerror(errno)); goto out; } if ((io_res = read(fsreadfd, cleanbuf.b_un.b_buf, size)) != size) { report_io_prob("READ FROM", bno, size, io_res); goto out; } cleanbuf.b_un.b_fs->fs_logbno = sblock.fs_logbno; cleanbuf.b_un.b_fs->fs_clean = sblock.fs_clean; cleanbuf.b_un.b_fs->fs_state = sblock.fs_state; cleanbuf.b_un.b_fs->fs_time = sblock.fs_time; cleanbuf.b_un.b_fs->fs_reclaim = sblock.fs_reclaim; cleanbuf.b_un.b_fs->fs_flags = sblock.fs_flags; if (llseek(fswritefd, sblkoff, SEEK_SET) == -1) { (void) printf("COULD NOT SEEK TO SUPERBLOCK AT %lld: %s\n", (longlong_t)bno, strerror(errno)); goto out; } if ((io_res = write(fswritefd, cleanbuf.b_un.b_buf, size)) != size) { report_io_prob("WRITE TO", bno, size, io_res); goto out; } /* * 1208040 * If we had to use -b to grab an alternate superblock, then we * likely had to do so because of unacceptable differences between * the main and alternate superblocks. So, we had better update * the alternate superblock as well, or we'll just fail again * the next time we attempt to run fsck! */ if (bflag != 0) { write_altsb(fswritefd); } if (errorlocked) { if (!do_errorlock(LOCKFS_ULOCK)) pwarn( "updateclean(changed): unlock(LOCKFS_ULOCK) failed\n"); } out: if (cleanbuf.b_un.b_buf != NULL) { free((void *)cleanbuf.b_un.b_buf); } return (freedlog); } static void report_io_prob(caddr_t what, diskaddr_t bno, size_t expected, ssize_t failure) { if (failure < 0) (void) printf("COULD NOT %s SUPERBLOCK AT %d: %s\n", what, (int)bno, strerror(errno)); else if (failure == 0) (void) printf("COULD NOT %s SUPERBLOCK AT %d: EOF\n", what, (int)bno); else (void) printf("SHORT %s SUPERBLOCK AT %d: %u out of %u bytes\n", what, (int)bno, (unsigned)failure, (unsigned)expected); } /* * print out clean info */ void printclean(void) { caddr_t s; if (FSOKAY != (sblock.fs_state + sblock.fs_time) && !errorlocked) s = "unknown"; else switch (sblock.fs_clean) { case FSACTIVE: s = "active"; break; case FSCLEAN: s = "clean"; break; case FSSTABLE: s = "stable"; break; case FSLOG: s = "logging"; break; case FSBAD: s = "is bad"; break; case FSFIX: s = "being fixed"; break; default: s = "unknown"; } if (preen) pwarn("is %s.\n", s); else (void) printf("** %s is %s.\n", devname, s); } int is_errorlocked(caddr_t fs) { int retval; struct stat64 statb; caddr_t mountp; struct mnttab *mntent; retval = 0; if (!fs) return (0); if (stat64(fs, &statb) < 0) return (0); if (S_ISDIR(statb.st_mode)) { mountp = fs; } else if (S_ISBLK(statb.st_mode) || S_ISCHR(statb.st_mode)) { mntent = search_mnttab(NULL, fs, NULL, 0); if (mntent == NULL) return (0); mountp = mntent->mnt_mountp; if (mountp == NULL) /* theoretically a can't-happen */ return (0); } else { return (0); } /* * From here on, must `goto out' to avoid memory leakage. */ if (elock_combuf == NULL) elock_combuf = (caddr_t)calloc(LOCKFS_MAXCOMMENTLEN, sizeof (char)); else elock_combuf = (caddr_t)realloc(elock_combuf, LOCKFS_MAXCOMMENTLEN); if (elock_combuf == NULL) goto out; (void) memset((void *)elock_combuf, 0, LOCKFS_MAXCOMMENTLEN); if (elock_mountp != NULL) { free(elock_mountp); } elock_mountp = strdup(mountp); if (elock_mountp == NULL) goto out; if (mountfd < 0) { if ((mountfd = open64(mountp, O_RDONLY)) == -1) goto out; } if (lfp == NULL) { lfp = (struct lockfs *)malloc(sizeof (struct lockfs)); if (lfp == NULL) goto out; (void) memset((void *)lfp, 0, sizeof (struct lockfs)); } lfp->lf_comlen = LOCKFS_MAXCOMMENTLEN; lfp->lf_comment = elock_combuf; if (ioctl(mountfd, _FIOLFSS, lfp) == -1) goto out; /* * lint believes that the ioctl() (or any other function * taking lfp as an arg) could free lfp. This is not the * case, however. */ retval = LOCKFS_IS_ELOCK(lfp); out: return (retval); } /* * Given a name which is known to be a directory, see if it appears * in the vfstab. If so, return the entry's block (special) device * field via devstr. */ int check_vfstab(caddr_t name, caddr_t devstr, size_t str_size) { return (NULL != search_vfstab(name, NULL, devstr, str_size)); } /* * Given a name which is known to be a directory, see if it appears * in the mnttab. If so, return the entry's block (special) device * field via devstr. */ int check_mnttab(caddr_t name, caddr_t devstr, size_t str_size) { return (NULL != search_mnttab(name, NULL, devstr, str_size)); } /* * Search for mount point and/or special device in the given file. * The first matching entry is returned. * * If an entry is found and str_size is greater than zero, then * up to size_str bytes of the special device name from the entry * are copied to devstr. */ #define SEARCH_TAB_BODY(st_type, st_file, st_mount, st_special, \ st_nuller, st_init, st_searcher) \ { \ FILE *fp; \ struct st_type *retval = NULL; \ struct st_type key; \ static struct st_type buffer; \ \ /* LINTED ``assigned value never used'' */ \ st_nuller(&key); \ key.st_mount = mountp; \ key.st_special = special; \ st_init; \ \ if ((fp = fopen(st_file, "r")) == NULL) \ return (NULL); \ \ if (st_searcher(fp, &buffer, &key) == 0) { \ retval = &buffer; \ if (devstr != NULL && str_size > 0 && \ buffer.st_special != NULL) { \ (void) strlcpy(devstr, buffer.st_special, \ str_size); \ } \ } \ (void) fclose(fp); \ return (retval); \ } static struct vfstab * search_vfstab(caddr_t mountp, caddr_t special, caddr_t devstr, size_t str_size) SEARCH_TAB_BODY(vfstab, VFSTAB, vfs_mountp, vfs_special, vfsnull, (retval = retval), getvfsany) static struct mnttab * search_mnttab(caddr_t mountp, caddr_t special, caddr_t devstr, size_t str_size) SEARCH_TAB_BODY(mnttab, MNTTAB, mnt_mountp, mnt_special, mntnull, (key.mnt_fstype = MNTTYPE_UFS), getmntany) int do_errorlock(int lock_type) { caddr_t buf; time_t now; struct tm *local; int rc; if (elock_combuf == NULL) errexit("do_errorlock(%s, %d): unallocated elock_combuf\n", elock_mountp ? elock_mountp : "", lock_type); if ((buf = (caddr_t)calloc(LOCKFS_MAXCOMMENTLEN, sizeof (char))) == NULL) { errexit("Couldn't alloc memory for temp. lock status buffer\n"); } if (lfp == NULL) { errexit("do_errorlock(%s, %d): lockfs status unallocated\n", elock_mountp, lock_type); } (void) memmove((void *)buf, (void *)elock_combuf, LOCKFS_MAXCOMMENTLEN-1); switch (lock_type) { case LOCKFS_ELOCK: /* * Note that if it is error-locked, we won't get an * error back if we try to error-lock it again. */ if (time(&now) != (time_t)-1) { if ((local = localtime(&now)) != NULL) (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s [pid:%d fsck start:%02d/%02d/%02d %02d:%02d:%02d", elock_combuf, (int)pid, local->tm_mon + 1, local->tm_mday, (local->tm_year % 100), local->tm_hour, local->tm_min, local->tm_sec); else (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s [fsck pid %d", elock_combuf, pid); } else { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s [fsck pid %d", elock_combuf, pid); } break; case LOCKFS_ULOCK: if (time(&now) != (time_t)-1) { if ((local = localtime(&now)) != NULL) { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s, done:%02d/%02d/%02d %02d:%02d:%02d]", elock_combuf, local->tm_mon + 1, local->tm_mday, (local->tm_year % 100), local->tm_hour, local->tm_min, local->tm_sec); } else { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s]", elock_combuf); } } else { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s]", elock_combuf); } if ((rc = ioctl(mountfd, _FIOLFSS, lfp)) == -1) { pwarn("do_errorlock: unlock failed: %s\n", strerror(errno)); goto out; } break; default: break; } (void) memmove((void *)elock_combuf, (void *)buf, LOCKFS_MAXCOMMENTLEN - 1); lfp->lf_lock = lock_type; lfp->lf_comlen = LOCKFS_MAXCOMMENTLEN; lfp->lf_comment = elock_combuf; lfp->lf_flags = 0; errno = 0; if ((rc = ioctl(mountfd, _FIOLFS, lfp)) == -1) { if (errno == EINVAL) { pwarn("Another fsck active?\n"); iscorrupt = 0; /* don't go away mad, just go away */ } else { pwarn("do_errorlock(lock_type:%d, %s) failed: %s\n", lock_type, elock_combuf, strerror(errno)); } } out: if (buf != NULL) { free((void *)buf); } return (rc != -1); } /* * Shadow inode support. To register a shadow with a client is to note * that an inode (the client) refers to the shadow. */ static struct shadowclients * newshadowclient(struct shadowclients *prev) { struct shadowclients *rc; rc = (struct shadowclients *)malloc(sizeof (*rc)); if (rc == NULL) errexit("newshadowclient: cannot malloc shadow client"); rc->next = prev; rc->nclients = 0; rc->client = (fsck_ino_t *)malloc(sizeof (fsck_ino_t) * maxshadowclients); if (rc->client == NULL) errexit("newshadowclient: cannot malloc client array"); return (rc); } void registershadowclient(fsck_ino_t shadow, fsck_ino_t client, struct shadowclientinfo **info) { struct shadowclientinfo *sci; struct shadowclients *scc; /* * Already have a record for this shadow? */ for (sci = *info; sci != NULL; sci = sci->next) if (sci->shadow == shadow) break; if (sci == NULL) { /* * It's a new shadow, add it to the list */ sci = (struct shadowclientinfo *)malloc(sizeof (*sci)); if (sci == NULL) errexit("registershadowclient: cannot malloc"); sci->next = *info; *info = sci; sci->shadow = shadow; sci->totalClients = 0; sci->clients = newshadowclient(NULL); } sci->totalClients++; scc = sci->clients; if (scc->nclients >= maxshadowclients) { scc = newshadowclient(sci->clients); sci->clients = scc; } scc->client[scc->nclients++] = client; } /* * Locate and discard a shadow. */ void clearshadow(fsck_ino_t shadow, struct shadowclientinfo **info) { struct shadowclientinfo *sci, *prev; /* * Do we have a record for this shadow? */ prev = NULL; for (sci = *info; sci != NULL; sci = sci->next) { if (sci->shadow == shadow) break; prev = sci; } if (sci != NULL) { /* * First, pull it off the list, since we know there * shouldn't be any future references to this one. */ if (prev == NULL) *info = sci->next; else prev->next = sci->next; deshadow(sci, clearattrref); } } /* * Discard all memory used to track clients of a shadow. */ void deshadow(struct shadowclientinfo *sci, void (*cb)(fsck_ino_t)) { struct shadowclients *clients, *discard; int idx; clients = sci->clients; while (clients != NULL) { discard = clients; clients = clients->next; if (discard->client != NULL) { if (cb != NULL) { for (idx = 0; idx < discard->nclients; idx++) (*cb)(discard->client[idx]); } free((void *)discard->client); } free((void *)discard); } free((void *)sci); } /* * Allocate more buffer as need arises but allocate one at a time. * This is done to make sure that fsck does not exit with error if it * needs more buffer to complete its task. */ static struct bufarea * alloc_bufarea(void) { struct bufarea *newbp; caddr_t bufp; bufp = malloc((unsigned int)sblock.fs_bsize); if (bufp == NULL) return (NULL); newbp = (struct bufarea *)malloc(sizeof (struct bufarea)); if (newbp == NULL) { free((void *)bufp); return (NULL); } initbarea(newbp); newbp->b_un.b_buf = bufp; newbp->b_prev = &bufhead; newbp->b_next = bufhead.b_next; bufhead.b_next->b_prev = newbp; bufhead.b_next = newbp; bufhead.b_size++; return (newbp); } /* * We length-limit in both unrawname() and rawname() to avoid * overflowing our arrays or those of our naive, trusting callers. */ caddr_t unrawname(caddr_t name) { caddr_t dp; static char fullname[MAXPATHLEN + 1]; if ((dp = getfullblkname(name)) == NULL) return (""); (void) strlcpy(fullname, dp, sizeof (fullname)); /* * Not reporting under debug, as the allocation isn't * reported by getfullblkname. The idea is that we * produce balanced alloc/free instances. */ free(dp); return (fullname); } caddr_t rawname(caddr_t name) { caddr_t dp; static char fullname[MAXPATHLEN + 1]; if ((dp = getfullrawname(name)) == NULL) return (""); (void) strlcpy(fullname, dp, sizeof (fullname)); /* * Not reporting under debug, as the allocation isn't * reported by getfullblkname. The idea is that we * produce balanced alloc/free instances. */ free(dp); return (fullname); } /* * Make sure that a cg header looks at least moderately reasonable. * We want to be able to trust the contents enough to be able to use * the standard accessor macros. So, besides looking at the obvious * such as the magic number, we verify that the offset field values * are properly aligned and not too big or small. * * Returns a NULL pointer if the cg is sane enough for our needs, else * a dynamically-allocated string describing all of its faults. */ #define Append_Error(full, full_len, addition, addition_len) \ if (full == NULL) { \ full = addition; \ full_len = addition_len; \ } else { \ /* lint doesn't think realloc() understands NULLs */ \ full = realloc(full, full_len + addition_len + 1); \ if (full == NULL) { \ errexit("Out of memory in cg_sanity"); \ /* NOTREACHED */ \ } \ (void) strcpy(full + full_len, addition); \ full_len += addition_len; \ free(addition); \ } caddr_t cg_sanity(struct cg *cgp, int cgno) { caddr_t full_err; caddr_t this_err = NULL; int full_len, this_len; daddr32_t ndblk; daddr32_t exp_btotoff, exp_boff, exp_iusedoff; daddr32_t exp_freeoff, exp_nextfreeoff; cg_constants(cgno, &exp_btotoff, &exp_boff, &exp_iusedoff, &exp_freeoff, &exp_nextfreeoff, &ndblk); full_err = NULL; full_len = 0; if (!cg_chkmagic(cgp)) { this_len = fsck_asprintf(&this_err, "BAD CG MAGIC NUMBER (0x%x should be 0x%x)\n", cgp->cg_magic, CG_MAGIC); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_cgx != cgno) { this_len = fsck_asprintf(&this_err, "WRONG CG NUMBER (%d should be %d)\n", cgp->cg_cgx, cgno); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_btotoff & 3) != 0) { this_len = fsck_asprintf(&this_err, "BLOCK TOTALS OFFSET %d NOT FOUR-BYTE ALIGNED\n", cgp->cg_btotoff); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_boff & 1) != 0) { this_len = fsck_asprintf(&this_err, "FREE BLOCK POSITIONS TABLE OFFSET %d NOT TWO-BYTE ALIGNED\n", cgp->cg_boff); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_ncyl < 1) || (cgp->cg_ncyl > sblock.fs_cpg)) { if (cgp->cg_ncyl < 1) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE NUMBER OF CYLINDERS IN GROUP (%d is less than 1)\n", cgp->cg_ncyl); } else { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE NUMBER OF CYLINDERS IN GROUP (%d is greater than %d)\n", cgp->cg_ncyl, sblock.fs_cpg); } Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_niblk != sblock.fs_ipg) { this_len = fsck_asprintf(&this_err, "INCORRECT NUMBER OF INODES IN GROUP (%d should be %d)\n", cgp->cg_niblk, sblock.fs_ipg); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_ndblk != ndblk) { this_len = fsck_asprintf(&this_err, "INCORRECT NUMBER OF DATA BLOCKS IN GROUP (%d should be %d)\n", cgp->cg_ndblk, ndblk); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_rotor < 0) || (cgp->cg_rotor >= ndblk)) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE BLOCK ALLOCATION ROTOR POSITION " "(%d should be at least 0 and less than %d)\n", cgp->cg_rotor, ndblk); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_frotor < 0) || (cgp->cg_frotor >= ndblk)) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE FRAGMENT ALLOCATION ROTOR POSITION " "(%d should be at least 0 and less than %d)\n", cgp->cg_frotor, ndblk); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_irotor < 0) || (cgp->cg_irotor >= sblock.fs_ipg)) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE INODE ALLOCATION ROTOR POSITION " "(%d should be at least 0 and less than %d)\n", cgp->cg_irotor, sblock.fs_ipg); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_btotoff != exp_btotoff) { this_len = fsck_asprintf(&this_err, "INCORRECT BLOCK TOTALS OFFSET (%d should be %d)\n", cgp->cg_btotoff, exp_btotoff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_boff != exp_boff) { this_len = fsck_asprintf(&this_err, "BAD FREE BLOCK POSITIONS TABLE OFFSET (%d should %d)\n", cgp->cg_boff, exp_boff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_iusedoff != exp_iusedoff) { this_len = fsck_asprintf(&this_err, "INCORRECT USED INODE MAP OFFSET (%d should be %d)\n", cgp->cg_iusedoff, exp_iusedoff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_freeoff != exp_freeoff) { this_len = fsck_asprintf(&this_err, "INCORRECT FREE FRAGMENT MAP OFFSET (%d should be %d)\n", cgp->cg_freeoff, exp_freeoff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_nextfreeoff != exp_nextfreeoff) { this_len = fsck_asprintf(&this_err, "END OF HEADER POSITION INCORRECT (%d should be %d)\n", cgp->cg_nextfreeoff, exp_nextfreeoff); Append_Error(full_err, full_len, this_err, this_len); } return (full_err); } #undef Append_Error /* * This is taken from mkfs, and is what is used to come up with the * original values for a struct cg. This implies that, since these * are all constants, recalculating them now should give us the same * thing as what's on disk. */ static void cg_constants(int cgno, daddr32_t *btotoff, daddr32_t *boff, daddr32_t *iusedoff, daddr32_t *freeoff, daddr32_t *nextfreeoff, daddr32_t *ndblk) { daddr32_t cbase, dmax; struct cg *cgp; (void) getblk(&cgblk, (diskaddr_t)cgtod(&sblock, cgno), (size_t)sblock.fs_cgsize); cgp = cgblk.b_un.b_cg; cbase = cgbase(&sblock, cgno); dmax = cbase + sblock.fs_fpg; if (dmax > sblock.fs_size) dmax = sblock.fs_size; /* LINTED pointer difference won't overflow */ *btotoff = &cgp->cg_space[0] - (uchar_t *)(&cgp->cg_link); *boff = *btotoff + sblock.fs_cpg * sizeof (daddr32_t); *iusedoff = *boff + sblock.fs_cpg * sblock.fs_nrpos * sizeof (int16_t); *freeoff = *iusedoff + howmany(sblock.fs_ipg, NBBY); *nextfreeoff = *freeoff + howmany(sblock.fs_cpg * sblock.fs_spc / NSPF(&sblock), NBBY); *ndblk = dmax - cbase; } /* * Corrects all fields in the cg that can be done with the available * redundant data. */ void fix_cg(struct cg *cgp, int cgno) { daddr32_t exp_btotoff, exp_boff, exp_iusedoff; daddr32_t exp_freeoff, exp_nextfreeoff; daddr32_t ndblk; cg_constants(cgno, &exp_btotoff, &exp_boff, &exp_iusedoff, &exp_freeoff, &exp_nextfreeoff, &ndblk); if (cgp->cg_cgx != cgno) { cgp->cg_cgx = cgno; } if ((cgp->cg_ncyl < 1) || (cgp->cg_ncyl > sblock.fs_cpg)) { if (cgno == (sblock.fs_ncg - 1)) { cgp->cg_ncyl = sblock.fs_ncyl - (sblock.fs_cpg * cgno); } else { cgp->cg_ncyl = sblock.fs_cpg; } } if (cgp->cg_niblk != sblock.fs_ipg) { /* * This is not used by the kernel, so it's pretty * harmless if it's wrong. */ cgp->cg_niblk = sblock.fs_ipg; } if (cgp->cg_ndblk != ndblk) { cgp->cg_ndblk = ndblk; } /* * For the rotors, any position's valid, so pick the one we know * will always exist. */ if ((cgp->cg_rotor < 0) || (cgp->cg_rotor >= cgp->cg_ndblk)) { cgp->cg_rotor = 0; } if ((cgp->cg_frotor < 0) || (cgp->cg_frotor >= cgp->cg_ndblk)) { cgp->cg_frotor = 0; } if ((cgp->cg_irotor < 0) || (cgp->cg_irotor >= sblock.fs_ipg)) { cgp->cg_irotor = 0; } /* * For btotoff and boff, if they're misaligned they won't * match the expected values, so we're catching both cases * here. Of course, if any of these are off, it seems likely * that the tables really won't be where we calculate they * should be anyway. */ if (cgp->cg_btotoff != exp_btotoff) { cgp->cg_btotoff = exp_btotoff; } if (cgp->cg_boff != exp_boff) { cgp->cg_boff = exp_boff; } if (cgp->cg_iusedoff != exp_iusedoff) { cgp->cg_iusedoff = exp_iusedoff; } if (cgp->cg_freeoff != exp_freeoff) { cgp->cg_freeoff = exp_freeoff; } if (cgp->cg_nextfreeoff != exp_nextfreeoff) { cgp->cg_nextfreeoff = exp_nextfreeoff; } /* * Reset the magic, as we've recreated this cg, also * update the cg_time, as we're writing out the cg */ cgp->cg_magic = CG_MAGIC; cgp->cg_time = time(NULL); /* * We know there was at least one correctable problem, * or else we wouldn't have been called. So instead of * marking the buffer dirty N times above, just do it * once here. */ cgdirty(); } void examinelog(void (*cb)(daddr32_t)) { struct bufarea *bp; extent_block_t *ebp; extent_t *ep; daddr32_t nfno, fno; int i; int j; /* * Since ufs stores fs_logbno as blocks and MTBufs stores it as frags * we need to translate accordingly using logbtodb() */ if (logbtodb(&sblock, sblock.fs_logbno) < SBLOCK) { if (debug) { (void) printf("fs_logbno < SBLOCK: %ld < %ld\n" \ "Aborting log examination\n", \ logbtodb(&sblock, sblock.fs_logbno), SBLOCK); } return; } /* * Read errors will return zeros, which will cause us * to do nothing harmful, so don't need to handle it. */ bp = getdatablk(logbtofrag(&sblock, sblock.fs_logbno), (size_t)sblock.fs_bsize); ebp = (void *)bp->b_un.b_buf; /* * Does it look like a log allocation table? */ /* LINTED pointer cast is aligned */ if (!log_checksum(&ebp->chksum, (int32_t *)bp->b_un.b_buf, sblock.fs_bsize)) return; if (ebp->type != LUFS_EXTENTS || ebp->nextents == 0) return; ep = &ebp->extents[0]; for (i = 0; i < ebp->nextents; ++i, ++ep) { fno = logbtofrag(&sblock, ep->pbno); nfno = dbtofsb(&sblock, ep->nbno); for (j = 0; j < nfno; ++j, ++fno) { /* * Invoke the callback first, so that pass1 can * mark the log blocks in-use. Then, if any * subsequent pass over the log shows us that a * block got freed (say, it was also claimed by * an inode that we cleared), we can safely declare * the log bad. */ if (cb != NULL) (*cb)(fno); if (!testbmap(fno)) islogok = 0; } } brelse(bp); if (cb != NULL) { fno = logbtofrag(&sblock, sblock.fs_logbno); for (j = 0; j < sblock.fs_frag; ++j, ++fno) (*cb)(fno); } } static void freelogblk(daddr32_t frag) { freeblk(sblock.fs_logbno, frag, 1); } caddr_t file_id(fsck_ino_t inum, mode_t mode) { static char name[MAXPATHLEN + 1]; if (lfdir == inum) { return (lfname); } if ((mode & IFMT) == IFDIR) { (void) strcpy(name, "DIR"); } else if ((mode & IFMT) == IFATTRDIR) { (void) strcpy(name, "ATTR DIR"); } else if ((mode & IFMT) == IFSHAD) { (void) strcpy(name, "ACL"); } else { (void) strcpy(name, "FILE"); } return (name); } /* * Simple initializer for inodesc structures, so users of only a few * fields don't have to worry about getting the right defaults for * everything out. */ void init_inodesc(struct inodesc *idesc) { /* * Most fields should be zero, just hit the special cases. */ (void) memset((void *)idesc, 0, sizeof (struct inodesc)); idesc->id_fix = DONTKNOW; idesc->id_lbn = -1; idesc->id_truncto = -1; idesc->id_firsthole = -1; } /* * Compare routine for tsearch(C) to use on ino_t instances. */ int ino_t_cmp(const void *left, const void *right) { const fsck_ino_t lino = (const fsck_ino_t)left; const fsck_ino_t rino = (const fsck_ino_t)right; return (lino - rino); } int cgisdirty(void) { return (cgblk.b_dirty); } void cgflush(void) { flush(fswritefd, &cgblk); } void dirty(struct bufarea *bp) { if (fswritefd < 0) { /* * No one should call dirty() in read only mode. * But if one does, it's not fatal issue. Just warn them. */ pwarn("WON'T SET DIRTY FLAG IN READ_ONLY MODE\n"); } else { (bp)->b_dirty = 1; isdirty = 1; } } void initbarea(struct bufarea *bp) { (bp)->b_dirty = 0; (bp)->b_bno = (diskaddr_t)-1LL; (bp)->b_flags = 0; (bp)->b_cnt = 0; (bp)->b_errs = 0; } /* * Partition-sizing routines adapted from ../newfs/newfs.c. * Needed because calcsb() needs to use mkfs to work out what the * superblock should be, and mkfs insists on being told how many * sectors to use. * * Error handling assumes we're never called while preening. * * XXX This should be extracted into a ../ufslib.{c,h}, * in the same spirit to ../../fslib.{c,h}. Once that is * done, both fsck and newfs should be modified to link * against it. */ static int label_type; #define LABEL_TYPE_VTOC 1 #define LABEL_TYPE_EFI 2 #define LABEL_TYPE_OTHER 3 #define MB (1024 * 1024) #define SECTORS_PER_TERABYTE (1LL << 31) #define FS_SIZE_UPPER_LIMIT 0x100000000000LL diskaddr_t getdisksize(caddr_t disk, int fd) { int rpm; struct dk_geom g; struct dk_cinfo ci; diskaddr_t actual_size; /* * get_device_size() determines the actual size of the * device, and also the disk's attributes, such as geometry. */ actual_size = get_device_size(fd, disk); if (label_type == LABEL_TYPE_VTOC) { if (ioctl(fd, DKIOCGGEOM, &g)) { pwarn("%s: Unable to read Disk geometry", disk); return (0); } if (sblock.fs_nsect == 0) sblock.fs_nsect = g.dkg_nsect; if (sblock.fs_ntrak == 0) sblock.fs_ntrak = g.dkg_nhead; if (sblock.fs_rps == 0) { rpm = ((int)g.dkg_rpm <= 0) ? 3600: g.dkg_rpm; sblock.fs_rps = rpm / 60; } } if (sblock.fs_bsize == 0) sblock.fs_bsize = MAXBSIZE; /* * Adjust maxcontig by the device's maxtransfer. If maxtransfer * information is not available, default to the min of a MB and * maxphys. */ if (sblock.fs_maxcontig == -1 && ioctl(fd, DKIOCINFO, &ci) == 0) { sblock.fs_maxcontig = ci.dki_maxtransfer * DEV_BSIZE; if (sblock.fs_maxcontig < 0) { int gotit, maxphys; gotit = fsgetmaxphys(&maxphys, NULL); /* * If we cannot get the maxphys value, default * to ufs_maxmaxphys (MB). */ if (gotit) { sblock.fs_maxcontig = MIN(maxphys, MB); } else { sblock.fs_maxcontig = MB; } } sblock.fs_maxcontig /= sblock.fs_bsize; } return (actual_size); } /* * Figure out how big the partition we're dealing with is. */ static diskaddr_t get_device_size(int fd, caddr_t name) { struct extvtoc vtoc; struct dk_gpt *efi_vtoc; diskaddr_t slicesize = 0; int index = read_extvtoc(fd, &vtoc); if (index >= 0) { label_type = LABEL_TYPE_VTOC; } else { if (index == VT_ENOTSUP || index == VT_ERROR) { /* it might be an EFI label */ index = efi_alloc_and_read(fd, &efi_vtoc); if (index >= 0) label_type = LABEL_TYPE_EFI; } } if (index < 0) { /* * Since both attempts to read the label failed, we're * going to fall back to a brute force approach to * determining the device's size: see how far out we can * perform reads on the device. */ slicesize = brute_force_get_device_size(fd); if (slicesize == 0) { switch (index) { case VT_ERROR: pwarn("%s: %s\n", name, strerror(errno)); break; case VT_EIO: pwarn("%s: I/O error accessing VTOC", name); break; case VT_EINVAL: pwarn("%s: Invalid field in VTOC", name); break; default: pwarn("%s: unknown error %d accessing VTOC", name, index); break; } return (0); } else { label_type = LABEL_TYPE_OTHER; } } if (label_type == LABEL_TYPE_EFI) { slicesize = efi_vtoc->efi_parts[index].p_size; efi_free(efi_vtoc); } else if (label_type == LABEL_TYPE_VTOC) { slicesize = vtoc.v_part[index].p_size; } return (slicesize); } /* * brute_force_get_device_size * * Determine the size of the device by seeing how far we can * read. Doing an llseek( , , SEEK_END) would probably work * in most cases, but we've seen at least one third-party driver * which doesn't correctly support the SEEK_END option when the * the device is greater than a terabyte. */ static diskaddr_t brute_force_get_device_size(int fd) { diskaddr_t min_fail = 0; diskaddr_t max_succeed = 0; diskaddr_t cur_db_off; char buf[DEV_BSIZE]; /* * First, see if we can read the device at all, just to * eliminate errors that have nothing to do with the * device's size. */ if (((llseek(fd, (offset_t)0, SEEK_SET)) == -1) || ((read(fd, buf, DEV_BSIZE)) == -1)) return (0); /* can't determine size */ /* * Now, go sequentially through the multiples of 4TB * to find the first read that fails (this isn't strictly * the most efficient way to find the actual size if the * size really could be anything between 0 and 2**64 bytes. * We expect the sizes to be less than 16 TB for some time, * so why do a bunch of reads that are larger than that? * However, this algorithm *will* work for sizes of greater * than 16 TB. We're just not optimizing for those sizes.) */ /* * XXX lint uses 32-bit arithmetic for doing flow analysis. * We're using > 32-bit constants here. Therefore, its flow * analysis is wrong. For the time being, ignore complaints * from it about the body of the for() being unreached. */ for (cur_db_off = SECTORS_PER_TERABYTE * 4; (min_fail == 0) && (cur_db_off < FS_SIZE_UPPER_LIMIT); cur_db_off += 4 * SECTORS_PER_TERABYTE) { if ((llseek(fd, (offset_t)(cur_db_off * DEV_BSIZE), SEEK_SET) == -1) || (read(fd, buf, DEV_BSIZE) != DEV_BSIZE)) min_fail = cur_db_off; else max_succeed = cur_db_off; } /* * XXX Same lint flow analysis problem as above. */ if (min_fail == 0) return (0); /* * We now know that the size of the device is less than * min_fail and greater than or equal to max_succeed. Now * keep splitting the difference until the actual size in * sectors in known. We also know that the difference * between max_succeed and min_fail at this time is * 4 * SECTORS_PER_TERABYTE, which is a power of two, which * simplifies the math below. */ while (min_fail - max_succeed > 1) { cur_db_off = max_succeed + (min_fail - max_succeed)/2; if (((llseek(fd, (offset_t)(cur_db_off * DEV_BSIZE), SEEK_SET)) == -1) || ((read(fd, buf, DEV_BSIZE)) != DEV_BSIZE)) min_fail = cur_db_off; else max_succeed = cur_db_off; } /* the size is the last successfully read sector offset plus one */ return (max_succeed + 1); } static void vfileerror(fsck_ino_t cwd, fsck_ino_t ino, caddr_t fmt, va_list ap) { struct dinode *dp; char pathbuf[MAXPATHLEN + 1]; vpwarn(fmt, ap); (void) putchar(' '); pinode(ino); (void) printf("\n"); getpathname(pathbuf, cwd, ino); if (ino < UFSROOTINO || ino > maxino) { pfatal("NAME=%s\n", pathbuf); return; } dp = ginode(ino); if (ftypeok(dp)) pfatal("%s=%s\n", file_id(ino, dp->di_mode), pathbuf); else pfatal("NAME=%s\n", pathbuf); } void direrror(fsck_ino_t ino, caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vfileerror(ino, ino, fmt, ap); va_end(ap); } static void vdirerror(fsck_ino_t ino, caddr_t fmt, va_list ap) { vfileerror(ino, ino, fmt, ap); } void fileerror(fsck_ino_t cwd, fsck_ino_t ino, caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vfileerror(cwd, ino, fmt, ap); va_end(ap); } /* * Adds the given inode to the orphaned-directories list, limbo_dirs. * Assumes that the caller has set INCLEAR in the inode's statemap[] * entry. * * With INCLEAR set, the inode will get ignored by passes 2 and 3, * meaning it's effectively an orphan. It needs to be noted now, so * it will be remembered in pass 4. */ void add_orphan_dir(fsck_ino_t ino) { if (tsearch((void *)ino, &limbo_dirs, ino_t_cmp) == NULL) errexit("add_orphan_dir: out of memory"); } /* * Remove an inode from the orphaned-directories list, presumably * because it's been cleared. */ void remove_orphan_dir(fsck_ino_t ino) { (void) tdelete((void *)ino, &limbo_dirs, ino_t_cmp); } /* * log_setsum() and log_checksum() are equivalent to lufs.c:setsum() * and lufs.c:checksum(). */ static void log_setsum(int32_t *sp, int32_t *lp, int nb) { int32_t csum = 0; *sp = 0; nb /= sizeof (int32_t); while (nb--) csum += *lp++; *sp = csum; } static int log_checksum(int32_t *sp, int32_t *lp, int nb) { int32_t ssum = *sp; log_setsum(sp, lp, nb); if (ssum != *sp) { *sp = ssum; return (0); } return (1); }