xref: /onnv-gate/usr/src/cmd/fs.d/ufs/fsck/pass3.c (revision 504:d6a0968b144c)
10Sstevel@tonic-gate /*
2392Sswilcox  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
30Sstevel@tonic-gate  * Use is subject to license terms.
40Sstevel@tonic-gate  */
50Sstevel@tonic-gate 
60Sstevel@tonic-gate /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
70Sstevel@tonic-gate /*	  All Rights Reserved  	*/
80Sstevel@tonic-gate 
90Sstevel@tonic-gate /*
100Sstevel@tonic-gate  * Copyright (c) 1980, 1986, 1990 The Regents of the University of California.
110Sstevel@tonic-gate  * All rights reserved.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * Redistribution and use in source and binary forms are permitted
140Sstevel@tonic-gate  * provided that: (1) source distributions retain this entire copyright
150Sstevel@tonic-gate  * notice and comment, and (2) distributions including binaries display
160Sstevel@tonic-gate  * the following acknowledgement:  ``This product includes software
170Sstevel@tonic-gate  * developed by the University of California, Berkeley and its contributors''
180Sstevel@tonic-gate  * in the documentation or other materials provided with the distribution
190Sstevel@tonic-gate  * and in all advertising materials mentioning features or use of this
200Sstevel@tonic-gate  * software. Neither the name of the University nor the names of its
210Sstevel@tonic-gate  * contributors may be used to endorse or promote products derived
220Sstevel@tonic-gate  * from this software without specific prior written permission.
230Sstevel@tonic-gate  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
240Sstevel@tonic-gate  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
250Sstevel@tonic-gate  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
260Sstevel@tonic-gate  */
270Sstevel@tonic-gate 
280Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
290Sstevel@tonic-gate 
30392Sswilcox #include <stdio.h>
31392Sswilcox #include <stdlib.h>
32392Sswilcox #include <unistd.h>
33392Sswilcox #include <string.h>
340Sstevel@tonic-gate #include <sys/param.h>
350Sstevel@tonic-gate #include <sys/types.h>
360Sstevel@tonic-gate #include <sys/mntent.h>
370Sstevel@tonic-gate #include <sys/fs/ufs_fs.h>
380Sstevel@tonic-gate #include <sys/vnode.h>
390Sstevel@tonic-gate #include <sys/fs/ufs_inode.h>
400Sstevel@tonic-gate #define	_KERNEL
410Sstevel@tonic-gate #include <sys/fs/ufs_fsdir.h>
420Sstevel@tonic-gate #undef _KERNEL
430Sstevel@tonic-gate #include "fsck.h"
440Sstevel@tonic-gate 
45392Sswilcox static int pass3acheck(struct inodesc *);
460Sstevel@tonic-gate static void setcurino(struct inodesc *, struct dinode *, struct inoinfo *);
470Sstevel@tonic-gate 
48392Sswilcox void
pass3a(void)49392Sswilcox pass3a(void)
500Sstevel@tonic-gate {
51392Sswilcox 	caddr_t flow;
520Sstevel@tonic-gate 	struct inoinfo **inpp, *inp;
53392Sswilcox 	fsck_ino_t orphan;
540Sstevel@tonic-gate 	int loopcnt;
55392Sswilcox 	int state;
56392Sswilcox 	struct shadowclientinfo *sci, *sci_victim, *sci_prev, **sci_rootp;
570Sstevel@tonic-gate 	struct inodesc curino;
580Sstevel@tonic-gate 	struct dinode *dp;
59392Sswilcox 	struct inodesc idesc;
60392Sswilcox 	char namebuf[MAXNAMLEN + 1];
610Sstevel@tonic-gate 
620Sstevel@tonic-gate 	for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--) {
630Sstevel@tonic-gate 		inp = *inpp;
64392Sswilcox 		state = statemap[inp->i_number];
650Sstevel@tonic-gate 		if (inp->i_number == UFSROOTINO ||
66392Sswilcox 		    (inp->i_parent != 0 && !S_IS_DUNFOUND(state)))
67392Sswilcox 			continue;
68392Sswilcox 		if (state == DCLEAR || state == USTATE || (state & INORPHAN))
690Sstevel@tonic-gate 			continue;
70392Sswilcox 		/*
71392Sswilcox 		 * If we are running with logging and we come
72392Sswilcox 		 * across unreferenced directories, we just leave
73392Sswilcox 		 * them in DSTATE which will cause them to be pitched
74392Sswilcox 		 * in pass 4.
75392Sswilcox 		 */
76392Sswilcox 		if (preen && !iscorrupt && islog && S_IS_DUNFOUND(state)) {
77392Sswilcox 			if (inp->i_dotdot >= UFSROOTINO) {
78392Sswilcox 				LINK_RANGE(flow, lncntp[inp->i_dotdot], 1);
79392Sswilcox 				if (flow != NULL) {
80392Sswilcox 					dp = ginode(inp->i_dotdot);
81392Sswilcox 					LINK_CLEAR(flow, inp->i_dotdot,
82392Sswilcox 					    dp->di_mode, &idesc);
83392Sswilcox 					if (statemap[inp->i_dotdot] == USTATE)
84392Sswilcox 						continue;
85392Sswilcox 				}
86392Sswilcox 				TRACK_LNCNTP(inp->i_dotdot,
87392Sswilcox 				    lncntp[inp->i_dotdot]++);
88392Sswilcox 			}
890Sstevel@tonic-gate 			continue;
90392Sswilcox 		}
91392Sswilcox 
920Sstevel@tonic-gate 		for (loopcnt = 0; ; loopcnt++) {
930Sstevel@tonic-gate 			orphan = inp->i_number;
94392Sswilcox 			/*
95392Sswilcox 			 * Skip out if we aren't connected to the name
96392Sswilcox 			 * space, or our parent is connected, or we've
97392Sswilcox 			 * looked at too many directories.  Our parent
98392Sswilcox 			 * being connected means that orphan is the
99392Sswilcox 			 * first ancestor of *inpp with questionable
100392Sswilcox 			 * antecedents.
101392Sswilcox 			 */
1020Sstevel@tonic-gate 			if (inp->i_parent == 0 ||
103392Sswilcox 			    !INO_IS_DUNFOUND(inp->i_parent) ||
1040Sstevel@tonic-gate 			    loopcnt > numdirs)
1050Sstevel@tonic-gate 				break;
1060Sstevel@tonic-gate 			inp = getinoinfo(inp->i_parent);
107392Sswilcox 			/*
108392Sswilcox 			 * Can't happen, because a non-zero parent's already
109392Sswilcox 			 * been seen and therefore cached.
110392Sswilcox 			 */
111392Sswilcox 			if (inp == NULL)
112392Sswilcox 				errexit("pass3 could not find cached "
113392Sswilcox 					"inode I=%d\n",
114392Sswilcox 					inp->i_parent);
1150Sstevel@tonic-gate 		}
116392Sswilcox 
117392Sswilcox 		/*
118392Sswilcox 		 * Already did this one.  Don't bother the user
119392Sswilcox 		 * with redundant questions.
120392Sswilcox 		 */
121392Sswilcox 		if (statemap[orphan] & INORPHAN)
122392Sswilcox 			continue;
123392Sswilcox 
1240Sstevel@tonic-gate 		/*
1250Sstevel@tonic-gate 		 * A link count of 0 with parent and .. inodes of 0
1260Sstevel@tonic-gate 		 * indicates a partly deleted directory.
1270Sstevel@tonic-gate 		 * Clear it.
1280Sstevel@tonic-gate 		 */
129392Sswilcox 		dp = ginode(orphan);
1300Sstevel@tonic-gate 		if (dp->di_nlink == 0 && inp->i_dotdot == 0 &&
1310Sstevel@tonic-gate 		    inp->i_parent == 0) {
132392Sswilcox 			/*
133392Sswilcox 			 * clri() just uses curino.id_number; in other
134392Sswilcox 			 * words, it won't use the callback that setcurino()
135392Sswilcox 			 * puts in.
136392Sswilcox 			 */
1370Sstevel@tonic-gate 			setcurino(&curino, dp, inp);
138392Sswilcox 			clri(&curino, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK);
139392Sswilcox 
140392Sswilcox 			/*
141392Sswilcox 			 * If we didn't clear it, at least mark it so
142392Sswilcox 			 * we don't waste time on it again.
143392Sswilcox 			 */
144392Sswilcox 			if (statemap[orphan] != USTATE) {
145392Sswilcox 				statemap[orphan] |= INORPHAN;
146392Sswilcox 			}
147392Sswilcox 			continue;
148392Sswilcox 		}
149392Sswilcox 
150392Sswilcox 		/*
151392Sswilcox 		 * We can call linkup() multiple times on the same directory
152392Sswilcox 		 * inode, if we were told not to reconnect it the first time.
153392Sswilcox 		 * This is because we find it as a disconnected parent of
154392Sswilcox 		 * of its children (and mark it found), and then finally get
155392Sswilcox 		 * to it in the inpsort array.  This is better than in the
156392Sswilcox 		 * past, where we'd call it every time we found it as a
157392Sswilcox 		 * child's parent.  Ideally, we'd suppress even the second
158392Sswilcox 		 * query, but that confuses pass 4's interpretation of
159392Sswilcox 		 * the state flags.
160392Sswilcox 		 */
161392Sswilcox 		if (loopcnt <= countdirs) {
162392Sswilcox 			if (linkup(orphan, inp->i_dotdot, NULL)) {
163392Sswilcox 				/*
164392Sswilcox 				 * Bookkeeping for any sort of relinked
165392Sswilcox 				 * directory.
166392Sswilcox 				 */
167392Sswilcox 				inp->i_dotdot = lfdir;
168392Sswilcox 				inp->i_parent = inp->i_dotdot;
169392Sswilcox 				statemap[orphan] &= ~(INORPHAN);
170392Sswilcox 			} else {
171392Sswilcox 				statemap[orphan] |= INORPHAN;
172392Sswilcox 			}
173392Sswilcox 			propagate();
1740Sstevel@tonic-gate 			continue;
1750Sstevel@tonic-gate 		}
1760Sstevel@tonic-gate 
177392Sswilcox 		/*
178392Sswilcox 		 * We visited more directories than exist in the
179392Sswilcox 		 * filesystem.  The only way to do that is if there's
180392Sswilcox 		 * a loop.
181392Sswilcox 		 */
182392Sswilcox 		pfatal("ORPHANED DIRECTORY LOOP DETECTED I=%d\n", orphan);
183392Sswilcox 
184392Sswilcox 		/*
185392Sswilcox 		 * Can never get here with inp->i_parent zero, because
186392Sswilcox 		 * of the interactions between the for() and the
187392Sswilcox 		 * if (loopcnt <= countdirs) above.
188392Sswilcox 		 */
189392Sswilcox 		init_inodesc(&idesc);
190392Sswilcox 		idesc.id_type = DATA;
191392Sswilcox 		idesc.id_number = inp->i_parent;
192392Sswilcox 		idesc.id_parent = orphan;
193392Sswilcox 		idesc.id_func = findname;
194392Sswilcox 		idesc.id_name = namebuf;
195392Sswilcox 		namebuf[0] = '\0';
196392Sswilcox 
197392Sswilcox 		/*
198392Sswilcox 		 * Theoretically, this lookup via ckinode can't fail
199392Sswilcox 		 * (if orphan doesn't exist in i_parent, then i_parent
200*504Sswilcox 		 * would not have been filled in by pass2check()).
201392Sswilcox 		 * However, if we're interactive, we want to at least
202392Sswilcox 		 * attempt to continue.  The worst case is that it
203392Sswilcox 		 * gets reconnected as #nnn into lost+found instead of
204392Sswilcox 		 * to its old parent with its old name.
205392Sswilcox 		 */
206392Sswilcox 		if ((ckinode(ginode(inp->i_parent),
207392Sswilcox 		    &idesc, CKI_TRAVERSE) & FOUND) == 0)
208392Sswilcox 			pfatal("COULD NOT FIND NAME IN PARENT DIRECTORY");
209392Sswilcox 
210392Sswilcox 		if (linkup(orphan, inp->i_parent, namebuf)) {
211392Sswilcox 			if (cleardirentry(inp->i_parent, orphan) & FOUND) {
212392Sswilcox 				LFDIR_LINK_RANGE_NORVAL(flow, lncntp[lfdir], 1,
213392Sswilcox 				    &idesc);
214392Sswilcox 				TRACK_LNCNTP(orphan, lncntp[orphan]++);
2150Sstevel@tonic-gate 			}
2160Sstevel@tonic-gate 			inp->i_parent = inp->i_dotdot = lfdir;
217392Sswilcox 			LFDIR_LINK_RANGE_NORVAL(flow, lncntp[lfdir], -1,
218392Sswilcox 			    &idesc);
219392Sswilcox 			TRACK_LNCNTP(lfdir, lncntp[lfdir]--);
2200Sstevel@tonic-gate 			statemap[orphan] = DFOUND;
221392Sswilcox 		} else {
222392Sswilcox 			/*
223392Sswilcox 			 * Represents a on-disk leak, not an inconsistency,
224392Sswilcox 			 * so don't set iscorrupt.  Such leaks are harmless
225392Sswilcox 			 * in the context of discrepancies that the kernel
226392Sswilcox 			 * will panic over.
227392Sswilcox 			 *
228392Sswilcox 			 * We don't care if tsearch() returns non-NULL
229392Sswilcox 			 * != orphan, since there's no dynamic memory
230392Sswilcox 			 * to free here.
231392Sswilcox 			 */
232392Sswilcox 			if (tsearch((void *)orphan, &limbo_dirs,
233392Sswilcox 				    ino_t_cmp) == NULL)
234392Sswilcox 				errexit("out of memory");
235392Sswilcox 			statemap[orphan] |= INORPHAN;
236392Sswilcox 			continue;
2370Sstevel@tonic-gate 		}
238392Sswilcox 		propagate();
2390Sstevel@tonic-gate 	}
2400Sstevel@tonic-gate 
241392Sswilcox 	/*
242392Sswilcox 	 * The essence of the inner loop is to update the inode of
243392Sswilcox 	 * every shadow or attribute inode's lncntp[] by the number of
244392Sswilcox 	 * links we've found to them in pass 2 and above.  Logically,
245392Sswilcox 	 * all that is needed is just the one line:
246392Sswilcox 	 *
247392Sswilcox 	 * lncntp[sci->shadow] -= sci->totalclients;
248392Sswilcox 	 *
249392Sswilcox 	 * However, there's the possibility of wrapping the link count
250392Sswilcox 	 * (this is especially true for shadows, which are expected to
251392Sswilcox 	 * be shared amongst many files).  This means that we have to
252392Sswilcox 	 * range-check before changing anything, and if the check
253392Sswilcox 	 * fails, offer to clear the shadow or attribute.  If we do
254392Sswilcox 	 * clear it, then we have to remove it from the linked list of
255392Sswilcox 	 * all of the type of inodes that we're going through.
256392Sswilcox 	 *
257392Sswilcox 	 * Just to make things a little more complicated, these are
258392Sswilcox 	 * singly-linked lists, so we have to do all the extra
259392Sswilcox 	 * bookkeeping that goes along with that as well.
260392Sswilcox 	 *
261392Sswilcox 	 * The only connection between the shadowclientinfo and
262392Sswilcox 	 * attrclientinfo lists is that they use the same underlying
263392Sswilcox 	 * struct.  Both need this scan, so the outer loop is just to
264392Sswilcox 	 * pick which one we're working on at the moment.  There is no
265392Sswilcox 	 * requirement as to which of these lists is scanned first.
266392Sswilcox 	 */
267392Sswilcox 	for (loopcnt = 0; loopcnt < 2; loopcnt++) {
268392Sswilcox 		if (loopcnt == 0)
269392Sswilcox 			sci_rootp = &shadowclientinfo;
270392Sswilcox 		else
271392Sswilcox 			sci_rootp = &attrclientinfo;
2720Sstevel@tonic-gate 
273392Sswilcox 		sci = *sci_rootp;
274392Sswilcox 		sci_prev = NULL;
275392Sswilcox 		while (sci != NULL) {
276392Sswilcox 			sci_victim = NULL;
277392Sswilcox 			LINK_RANGE(flow, lncntp[sci->shadow],
278392Sswilcox 			    -(sci->totalClients));
279392Sswilcox 			if (flow != NULL) {
280392Sswilcox 				/*
281392Sswilcox 				 * Overflowed the link count.
282392Sswilcox 				 */
283392Sswilcox 				dp = ginode(sci->shadow);
284392Sswilcox 				LINK_CLEAR(flow, sci->shadow, dp->di_mode,
285392Sswilcox 				    &idesc);
286392Sswilcox 				if (statemap[sci->shadow] == USTATE) {
287392Sswilcox 					/*
288392Sswilcox 					 * It's been cleared, fix the
289392Sswilcox 					 * lists.
290392Sswilcox 					 */
291392Sswilcox 					if (sci_prev == NULL) {
292392Sswilcox 						*sci_rootp = sci->next;
293392Sswilcox 					} else {
294392Sswilcox 						sci_prev->next = sci->next;
295392Sswilcox 					}
296392Sswilcox 					sci_victim = sci;
297392Sswilcox 				}
298392Sswilcox 			}
299392Sswilcox 
300392Sswilcox 			/*
301392Sswilcox 			 * If we did not clear the shadow, then we
302392Sswilcox 			 * need to update the count and advance the
303392Sswilcox 			 * previous pointer.  Otherwise, finish the
304392Sswilcox 			 * clean up once we're done with the struct.
305392Sswilcox 			 */
306392Sswilcox 			if (sci_victim == NULL) {
307392Sswilcox 				TRACK_LNCNTP(sci->shadow,
308392Sswilcox 				    lncntp[sci->shadow] -= sci->totalClients);
309392Sswilcox 				sci_prev = sci;
310392Sswilcox 			}
311392Sswilcox 			sci = sci->next;
312392Sswilcox 			if (sci_victim != NULL)
313392Sswilcox 				deshadow(sci_victim, NULL);
314392Sswilcox 		}
3150Sstevel@tonic-gate 	}
3160Sstevel@tonic-gate }
3170Sstevel@tonic-gate 
3180Sstevel@tonic-gate 
3190Sstevel@tonic-gate /*
3200Sstevel@tonic-gate  * This is used to verify the cflags of files
321392Sswilcox  * under a directory that used to be an attrdir.
3220Sstevel@tonic-gate  */
3230Sstevel@tonic-gate 
324392Sswilcox static int
pass3acheck(struct inodesc * idesc)325392Sswilcox pass3acheck(struct inodesc *idesc)
3260Sstevel@tonic-gate {
3270Sstevel@tonic-gate 	struct direct *dirp = idesc->id_dirp;
328392Sswilcox 	int n = 0, ret = 0;
3290Sstevel@tonic-gate 	struct dinode *dp, *pdirp;
330392Sswilcox 	int isattr;
331392Sswilcox 	int dirtype;
332392Sswilcox 	int inotype;
3330Sstevel@tonic-gate 
3340Sstevel@tonic-gate 	if (dirp->d_ino == 0)
3350Sstevel@tonic-gate 		return (KEEPON);
3360Sstevel@tonic-gate 
3370Sstevel@tonic-gate 	idesc->id_entryno++;
3380Sstevel@tonic-gate 	if ((strcmp(dirp->d_name, ".") == 0) ||
3390Sstevel@tonic-gate 	    (strcmp(dirp->d_name, "..") == 0)) {
3400Sstevel@tonic-gate 		return (KEEPON);
3410Sstevel@tonic-gate 	}
3420Sstevel@tonic-gate 
343392Sswilcox 	switch (statemap[dirp->d_ino] & ~(INDELAYD)) {
3440Sstevel@tonic-gate 	case DSTATE:
3450Sstevel@tonic-gate 	case DFOUND:
3460Sstevel@tonic-gate 	case FSTATE:
3470Sstevel@tonic-gate 		/*
348392Sswilcox 		 * Accept DSTATE and DFOUND so we can handle normal
349392Sswilcox 		 * directories as well as xattr directories.
350392Sswilcox 		 *
3510Sstevel@tonic-gate 		 * For extended attribute directories .. may point
3520Sstevel@tonic-gate 		 * to a file.  In this situation we don't want
3530Sstevel@tonic-gate 		 * to decrement link count as it was already
354392Sswilcox 		 * decremented when the entry was seen and decremented
3550Sstevel@tonic-gate 		 * in the directory it actually lives in.
3560Sstevel@tonic-gate 		 */
3570Sstevel@tonic-gate 		dp = ginode(dirp->d_ino);
3580Sstevel@tonic-gate 		isattr = (dp->di_cflags & IXATTR);
359392Sswilcox 		inotype = (dp->di_mode & IFMT);
3600Sstevel@tonic-gate 		pdirp = ginode(idesc->id_number);
3610Sstevel@tonic-gate 		dirtype = (pdirp->di_mode & IFMT);
362392Sswilcox 		/*
363392Sswilcox 		 * IXATTR indicates that an object is itself an extended
364392Sswilcox 		 * attribute.  An IFMT of IFATTRDIR means we are looking
365392Sswilcox 		 * at a directory which contains files which should all
366392Sswilcox 		 * have IXATTR set.  The IFATTRDIR case was handled in
367392Sswilcox 		 * pass 2b.
368392Sswilcox 		 *
369392Sswilcox 		 * Note that the following code actually handles
370392Sswilcox 		 * anything that's marked as an extended attribute but
371392Sswilcox 		 * in a regular directory, not just files.
372392Sswilcox 		 */
3730Sstevel@tonic-gate 		if ((dirtype == IFDIR) && isattr) {
3740Sstevel@tonic-gate 			fileerror(idesc->id_number, dirp->d_ino,
375392Sswilcox 		    "%s I=%d should NOT be marked as extended attribute\n",
376392Sswilcox 			    (inotype == IFDIR) ? "Directory" : "File",
377392Sswilcox 			    dirp->d_ino);
3780Sstevel@tonic-gate 			dp = ginode(dirp->d_ino);
3790Sstevel@tonic-gate 			dp->di_cflags &= ~IXATTR;
3800Sstevel@tonic-gate 			if ((n = reply("FIX")) == 1) {
3810Sstevel@tonic-gate 				inodirty();
382392Sswilcox 			} else {
383392Sswilcox 				iscorrupt = 1;
3840Sstevel@tonic-gate 			}
3850Sstevel@tonic-gate 			if (n != 0)
3860Sstevel@tonic-gate 				return (KEEPON | ALTERED);
3870Sstevel@tonic-gate 		}
3880Sstevel@tonic-gate 		break;
3890Sstevel@tonic-gate 	default:
3900Sstevel@tonic-gate 		errexit("PASS3: BAD STATE %d FOR INODE I=%d",
3910Sstevel@tonic-gate 		    statemap[dirp->d_ino], dirp->d_ino);
392392Sswilcox 		/* NOTREACHED */
3930Sstevel@tonic-gate 	}
3940Sstevel@tonic-gate 	if (n == 0)
3950Sstevel@tonic-gate 		return (ret|KEEPON);
3960Sstevel@tonic-gate 	return (ret|KEEPON|ALTERED);
3970Sstevel@tonic-gate }
3980Sstevel@tonic-gate 
3990Sstevel@tonic-gate static void
setcurino(struct inodesc * idesc,struct dinode * dp,struct inoinfo * inp)400392Sswilcox setcurino(struct inodesc *idesc, struct dinode *dp, struct inoinfo *inp)
401392Sswilcox {
402392Sswilcox 	(void) memmove((void *)&dp->di_db[0], (void *)&inp->i_blks[0],
403392Sswilcox 		inp->i_blkssize);
404392Sswilcox 
405392Sswilcox 	init_inodesc(idesc);
406392Sswilcox 	idesc->id_number = inp->i_number;
407392Sswilcox 	idesc->id_parent = inp->i_parent;
408392Sswilcox 	idesc->id_fix = DONTKNOW;
409392Sswilcox 	idesc->id_type = DATA;
410392Sswilcox 	idesc->id_func = pass3acheck;
411392Sswilcox }
412392Sswilcox 
413392Sswilcox void
maybe_convert_attrdir_to_dir(fsck_ino_t orphan)414392Sswilcox maybe_convert_attrdir_to_dir(fsck_ino_t orphan)
4150Sstevel@tonic-gate {
416392Sswilcox 	struct dinode *dp = ginode(orphan);
417392Sswilcox 	struct inoinfo *inp = getinoinfo(orphan);
418392Sswilcox 	struct inodesc idesc;
419392Sswilcox 
420392Sswilcox 	if (dp->di_cflags & IXATTR) {
421392Sswilcox 		dp->di_cflags &= ~IXATTR;
422392Sswilcox 		inodirty();
423392Sswilcox 	}
424392Sswilcox 
425392Sswilcox 	if ((dp->di_mode & IFMT) == IFATTRDIR) {
426392Sswilcox 		dp->di_mode &= ~IFATTRDIR;
427392Sswilcox 		dp->di_mode |= IFDIR;
428392Sswilcox 		inodirty();
429392Sswilcox 
430392Sswilcox 		setcurino(&idesc, dp, inp);
431392Sswilcox 		idesc.id_fix = FIX;
432392Sswilcox 		idesc.id_filesize = dp->di_size;
433392Sswilcox 		(void) ckinode(dp, &idesc, CKI_TRAVERSE);
434392Sswilcox 	}
4350Sstevel@tonic-gate }
436