inode.c revision 88413
154994Simp/*
254994Simp * Copyright (c) 1980, 1986, 1993
354994Simp *	The Regents of the University of California.  All rights reserved.
454994Simp *
554994Simp * Redistribution and use in source and binary forms, with or without
654994Simp * modification, are permitted provided that the following conditions
754994Simp * are met:
854994Simp * 1. Redistributions of source code must retain the above copyright
954994Simp *    notice, this list of conditions and the following disclaimer.
1054994Simp * 2. Redistributions in binary form must reproduce the above copyright
1154994Simp *    notice, this list of conditions and the following disclaimer in the
1254994Simp *    documentation and/or other materials provided with the distribution.
1354994Simp * 3. All advertising materials mentioning features or use of this software
1454994Simp *    must display the following acknowledgement:
1554994Simp *	This product includes software developed by the University of
1654994Simp *	California, Berkeley and its contributors.
1754994Simp * 4. Neither the name of the University nor the names of its contributors
1854994Simp *    may be used to endorse or promote products derived from this software
1954994Simp *    without specific prior written permission.
2054994Simp *
2154994Simp * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2254994Simp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2354994Simp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2454994Simp * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2554994Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2654994Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2754994Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2854994Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2954994Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3054994Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3154994Simp * SUCH DAMAGE.
32119419Sobrien */
33119419Sobrien
34119419Sobrien#ifndef lint
3554994Simp#if 0
3654994Simpstatic const char sccsid[] = "@(#)inode.c	8.8 (Berkeley) 4/28/95";
3754994Simp#endif
3854994Simpstatic const char rcsid[] =
3954994Simp  "$FreeBSD: head/sbin/fsck_ffs/inode.c 88413 2001-12-22 12:35:03Z alfred $";
4054994Simp#endif /* not lint */
4154994Simp
4254994Simp#include <sys/param.h>
4354994Simp#include <sys/time.h>
4454994Simp#include <sys/sysctl.h>
4569955Simp
4654994Simp#include <ufs/ufs/dinode.h>
4754994Simp#include <ufs/ufs/dir.h>
4854994Simp#include <ufs/ffs/fs.h>
4954994Simp
5054994Simp#include <err.h>
5154994Simp#include <pwd.h>
5254994Simp#include <string.h>
5354994Simp
5454994Simp#include "fsck.h"
5554994Simp
5654994Simpstatic ino_t startinum;
5754994Simp
5854994Simpstatic int iblock __P((struct inodesc *, long ilevel, quad_t isize));
5959099Simp
6059099Simpint
6154994Simpckinode(dp, idesc)
6260173Simp	struct dinode *dp;
6360173Simp	register struct inodesc *idesc;
6454994Simp{
6554994Simp	ufs_daddr_t *ap;
6654994Simp	int ret;
6754994Simp	long n, ndb, offset;
6854994Simp	struct dinode dino;
6969955Simp	quad_t remsize, sizepb;
7056397Shosokawa	mode_t mode;
7169955Simp	char pathbuf[MAXPATHLEN + 1];
7269955Simp
7354994Simp	if (idesc->id_fix != IGNORE)
7454994Simp		idesc->id_fix = DONTKNOW;
7554994Simp	idesc->id_lbn = -1;
7654994Simp	idesc->id_entryno = 0;
7754994Simp	idesc->id_filesize = dp->di_size;
7854994Simp	mode = dp->di_mode & IFMT;
7969955Simp	if (mode == IFBLK || mode == IFCHR || (mode == IFLNK &&
8054994Simp	    dp->di_size < (unsigned)sblock.fs_maxsymlinklen))
8154994Simp		return (KEEPON);
8254994Simp	dino = *dp;
8354994Simp	ndb = howmany(dino.di_size, sblock.fs_bsize);
8454994Simp	for (ap = &dino.di_db[0]; ap < &dino.di_db[NDADDR]; ap++) {
8554994Simp		idesc->id_lbn++;
8654994Simp		if (--ndb == 0 && (offset = blkoff(&sblock, dino.di_size)) != 0)
8754994Simp			idesc->id_numfrags =
8854994Simp				numfrags(&sblock, fragroundup(&sblock, offset));
8954994Simp		else
9054994Simp			idesc->id_numfrags = sblock.fs_frag;
9154994Simp		if (*ap == 0) {
92113506Smdodd			if (idesc->id_type == DATA && ndb >= 0) {
93113506Smdodd				/* An empty block in a directory XXX */
94113506Smdodd				getpathname(pathbuf, idesc->id_number,
95						idesc->id_number);
96                        	pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
97					pathbuf);
98                        	if (reply("ADJUST LENGTH") == 1) {
99					dp = ginode(idesc->id_number);
100                                	dp->di_size = (ap - &dino.di_db[0]) *
101					    sblock.fs_bsize;
102					printf(
103					    "YOU MUST RERUN FSCK AFTERWARDS\n");
104					rerun = 1;
105                                	inodirty();
106
107                        	}
108			}
109			continue;
110		}
111		idesc->id_blkno = *ap;
112		if (idesc->id_type != DATA)
113			ret = (*idesc->id_func)(idesc);
114		else
115			ret = dirscan(idesc);
116		if (ret & STOP)
117			return (ret);
118	}
119	idesc->id_numfrags = sblock.fs_frag;
120	remsize = dino.di_size - sblock.fs_bsize * NDADDR;
121	sizepb = sblock.fs_bsize;
122	for (ap = &dino.di_ib[0], n = 1; n <= NIADDR; ap++, n++) {
123		sizepb *= NINDIR(&sblock);
124		if (*ap) {
125			idesc->id_blkno = *ap;
126			ret = iblock(idesc, n, remsize);
127			if (ret & STOP)
128				return (ret);
129		} else {
130			idesc->id_lbn += sizepb / sblock.fs_bsize;
131			if (idesc->id_type == DATA && remsize > 0) {
132				/* An empty block in a directory XXX */
133				getpathname(pathbuf, idesc->id_number,
134						idesc->id_number);
135                        	pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
136					pathbuf);
137                        	if (reply("ADJUST LENGTH") == 1) {
138					dp = ginode(idesc->id_number);
139                                	dp->di_size -= remsize;
140					remsize = 0;
141					printf(
142					    "YOU MUST RERUN FSCK AFTERWARDS\n");
143					rerun = 1;
144                                	inodirty();
145					break;
146                        	}
147			}
148		}
149		remsize -= sizepb;
150	}
151	return (KEEPON);
152}
153
154static int
155iblock(idesc, ilevel, isize)
156	struct inodesc *idesc;
157	long ilevel;
158	quad_t isize;
159{
160	ufs_daddr_t *ap;
161	ufs_daddr_t *aplim;
162	struct bufarea *bp;
163	int i, n, (*func)(), nif;
164	quad_t sizepb;
165	char buf[BUFSIZ];
166	char pathbuf[MAXPATHLEN + 1];
167	struct dinode *dp;
168
169	if (idesc->id_type != DATA) {
170		func = idesc->id_func;
171		if (((n = (*func)(idesc)) & KEEPON) == 0)
172			return (n);
173	} else
174		func = dirscan;
175	if (chkrange(idesc->id_blkno, idesc->id_numfrags))
176		return (SKIP);
177	bp = getdatablk(idesc->id_blkno, sblock.fs_bsize);
178	ilevel--;
179	for (sizepb = sblock.fs_bsize, i = 0; i < ilevel; i++)
180		sizepb *= NINDIR(&sblock);
181	nif = howmany(isize , sizepb);
182	if (nif > NINDIR(&sblock))
183		nif = NINDIR(&sblock);
184	if (idesc->id_func == pass1check && nif < NINDIR(&sblock)) {
185		aplim = &bp->b_un.b_indir[NINDIR(&sblock)];
186		for (ap = &bp->b_un.b_indir[nif]; ap < aplim; ap++) {
187			if (*ap == 0)
188				continue;
189			(void)sprintf(buf, "PARTIALLY TRUNCATED INODE I=%lu",
190			    (u_long)idesc->id_number);
191			if (preen) {
192				pfatal("%s", buf);
193			} else if (dofix(idesc, buf)) {
194				*ap = 0;
195				dirty(bp);
196			}
197		}
198		flush(fswritefd, bp);
199	}
200	aplim = &bp->b_un.b_indir[nif];
201	for (ap = bp->b_un.b_indir; ap < aplim; ap++) {
202		if (ilevel == 0)
203			idesc->id_lbn++;
204		if (*ap) {
205			idesc->id_blkno = *ap;
206			if (ilevel == 0)
207				n = (*func)(idesc);
208			else
209				n = iblock(idesc, ilevel, isize);
210			if (n & STOP) {
211				bp->b_flags &= ~B_INUSE;
212				return (n);
213			}
214		} else {
215			if (idesc->id_type == DATA && isize > 0) {
216				/* An empty block in a directory XXX */
217				getpathname(pathbuf, idesc->id_number,
218						idesc->id_number);
219                        	pfatal("DIRECTORY %s: CONTAINS EMPTY BLOCKS",
220					pathbuf);
221                        	if (reply("ADJUST LENGTH") == 1) {
222					dp = ginode(idesc->id_number);
223                                	dp->di_size -= isize;
224					isize = 0;
225					printf(
226					    "YOU MUST RERUN FSCK AFTERWARDS\n");
227					rerun = 1;
228                                	inodirty();
229					bp->b_flags &= ~B_INUSE;
230					return(STOP);
231                        	}
232			}
233		}
234		isize -= sizepb;
235	}
236	bp->b_flags &= ~B_INUSE;
237	return (KEEPON);
238}
239
240/*
241 * Check that a block in a legal block number.
242 * Return 0 if in range, 1 if out of range.
243 */
244int
245chkrange(blk, cnt)
246	ufs_daddr_t blk;
247	int cnt;
248{
249	register int c;
250
251	if (cnt <= 0 || blk <= 0 || blk > maxfsblock ||
252	    cnt - 1 > maxfsblock - blk)
253		return (1);
254	if (cnt > sblock.fs_frag ||
255	    fragnum(&sblock, blk) + cnt > sblock.fs_frag) {
256		if (debug)
257			printf("bad size: blk %ld, offset %i, size %d\n",
258			    (long)blk, (int)fragnum(&sblock, blk), cnt);
259		return (1);
260	}
261	c = dtog(&sblock, blk);
262	if (blk < cgdmin(&sblock, c)) {
263		if ((blk + cnt) > cgsblock(&sblock, c)) {
264			if (debug) {
265				printf("blk %ld < cgdmin %ld;",
266				    (long)blk, (long)cgdmin(&sblock, c));
267				printf(" blk + cnt %ld > cgsbase %ld\n",
268				    (long)(blk + cnt),
269				    (long)cgsblock(&sblock, c));
270			}
271			return (1);
272		}
273	} else {
274		if ((blk + cnt) > cgbase(&sblock, c+1)) {
275			if (debug)  {
276				printf("blk %ld >= cgdmin %ld;",
277				    (long)blk, (long)cgdmin(&sblock, c));
278				printf(" blk + cnt %ld > sblock.fs_fpg %ld\n",
279				    (long)(blk + cnt), (long)sblock.fs_fpg);
280			}
281			return (1);
282		}
283	}
284	return (0);
285}
286
287/*
288 * General purpose interface for reading inodes.
289 */
290struct dinode *
291ginode(inumber)
292	ino_t inumber;
293{
294	ufs_daddr_t iblk;
295
296	if (inumber < ROOTINO || inumber > maxino)
297		errx(EEXIT, "bad inode number %d to ginode", inumber);
298	if (startinum == 0 ||
299	    inumber < startinum || inumber >= startinum + INOPB(&sblock)) {
300		iblk = ino_to_fsba(&sblock, inumber);
301		if (pbp != 0)
302			pbp->b_flags &= ~B_INUSE;
303		pbp = getdatablk(iblk, sblock.fs_bsize);
304		startinum = (inumber / INOPB(&sblock)) * INOPB(&sblock);
305	}
306	return (&pbp->b_un.b_dinode[inumber % INOPB(&sblock)]);
307}
308
309/*
310 * Special purpose version of ginode used to optimize first pass
311 * over all the inodes in numerical order.
312 */
313static ino_t nextino, lastinum, lastvalidinum;
314static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize;
315static struct dinode *inodebuf;
316
317struct dinode *
318getnextinode(inumber)
319	ino_t inumber;
320{
321	long size;
322	ufs_daddr_t dblk;
323	static struct dinode *dp;
324
325	if (inumber != nextino++ || inumber > lastvalidinum)
326		errx(EEXIT, "bad inode number %d to nextinode", inumber);
327	if (inumber >= lastinum) {
328		readcnt++;
329		dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum));
330		if (readcnt % readpercg == 0) {
331			size = partialsize;
332			lastinum += partialcnt;
333		} else {
334			size = inobufsize;
335			lastinum += fullcnt;
336		}
337		/*
338		 * If bread returns an error, it will already have zeroed
339		 * out the buffer, so we do not need to do so here.
340		 */
341		(void)bread(fsreadfd, (char *)inodebuf, dblk, size);
342		dp = inodebuf;
343	}
344	return (dp++);
345}
346
347void
348setinodebuf(inum)
349	ino_t inum;
350{
351
352	if (inum % sblock.fs_ipg != 0)
353		errx(EEXIT, "bad inode number %d to setinodebuf", inum);
354	lastvalidinum = inum + sblock.fs_ipg - 1;
355	startinum = 0;
356	nextino = inum;
357	lastinum = inum;
358	readcnt = 0;
359	if (inodebuf != NULL)
360		return;
361	inobufsize = blkroundup(&sblock, INOBUFSIZE);
362	fullcnt = inobufsize / sizeof(struct dinode);
363	readpercg = sblock.fs_ipg / fullcnt;
364	partialcnt = sblock.fs_ipg % fullcnt;
365	partialsize = partialcnt * sizeof(struct dinode);
366	if (partialcnt != 0) {
367		readpercg++;
368	} else {
369		partialcnt = fullcnt;
370		partialsize = inobufsize;
371	}
372	if ((inodebuf = (struct dinode *)malloc((unsigned)inobufsize)) == NULL)
373		errx(EEXIT, "cannot allocate space for inode buffer");
374}
375
376void
377freeinodebuf()
378{
379
380	if (inodebuf != NULL)
381		free((char *)inodebuf);
382	inodebuf = NULL;
383}
384
385/*
386 * Routines to maintain information about directory inodes.
387 * This is built during the first pass and used during the
388 * second and third passes.
389 *
390 * Enter inodes into the cache.
391 */
392void
393cacheino(dp, inumber)
394	register struct dinode *dp;
395	ino_t inumber;
396{
397	register struct inoinfo *inp;
398	struct inoinfo **inpp;
399	int blks;
400
401	blks = howmany(dp->di_size, sblock.fs_bsize);
402	if (blks > NDADDR)
403		blks = NDADDR + NIADDR;
404	inp = (struct inoinfo *)
405		malloc(sizeof(*inp) + (blks - 1) * sizeof(ufs_daddr_t));
406	if (inp == NULL)
407		errx(EEXIT, "cannot increase directory list");
408	inpp = &inphead[inumber % dirhash];
409	inp->i_nexthash = *inpp;
410	*inpp = inp;
411	inp->i_parent = inumber == ROOTINO ? ROOTINO : (ino_t)0;
412	inp->i_dotdot = (ino_t)0;
413	inp->i_number = inumber;
414	inp->i_isize = dp->di_size;
415	inp->i_numblks = blks * sizeof(ufs_daddr_t);
416	memmove(&inp->i_blks[0], &dp->di_db[0], (size_t)inp->i_numblks);
417	if (inplast == listmax) {
418		listmax += 100;
419		inpsort = (struct inoinfo **)realloc((char *)inpsort,
420		    (unsigned)listmax * sizeof(struct inoinfo *));
421		if (inpsort == NULL)
422			errx(EEXIT, "cannot increase directory list");
423	}
424	inpsort[inplast++] = inp;
425}
426
427/*
428 * Look up an inode cache structure.
429 */
430struct inoinfo *
431getinoinfo(inumber)
432	ino_t inumber;
433{
434	register struct inoinfo *inp;
435
436	for (inp = inphead[inumber % dirhash]; inp; inp = inp->i_nexthash) {
437		if (inp->i_number != inumber)
438			continue;
439		return (inp);
440	}
441	errx(EEXIT, "cannot find inode %d", inumber);
442	return ((struct inoinfo *)0);
443}
444
445/*
446 * Clean up all the inode cache structure.
447 */
448void
449inocleanup()
450{
451	register struct inoinfo **inpp;
452
453	if (inphead == NULL)
454		return;
455	for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--)
456		free((char *)(*inpp));
457	free((char *)inphead);
458	free((char *)inpsort);
459	inphead = inpsort = NULL;
460}
461
462void
463inodirty()
464{
465
466	dirty(pbp);
467}
468
469void
470clri(idesc, type, flag)
471	register struct inodesc *idesc;
472	char *type;
473	int flag;
474{
475	register struct dinode *dp;
476
477	dp = ginode(idesc->id_number);
478	if (flag == 1) {
479		pwarn("%s %s", type,
480		    (dp->di_mode & IFMT) == IFDIR ? "DIR" : "FILE");
481		pinode(idesc->id_number);
482	}
483	if (preen || reply("CLEAR") == 1) {
484		if (preen)
485			printf(" (CLEARED)\n");
486		n_files--;
487		if (bkgrdflag == 0) {
488			(void)ckinode(dp, idesc);
489			inoinfo(idesc->id_number)->ino_state = USTATE;
490			clearinode(dp);
491			inodirty();
492		} else {
493			cmd.value = idesc->id_number;
494			cmd.size = -dp->di_nlink;
495			if (debug)
496				printf("adjrefcnt ino %ld amt %ld\n",
497				    (long)cmd.value, cmd.size);
498			if (sysctl(adjrefcnt, MIBSIZE, 0, 0,
499			    &cmd, sizeof cmd) == -1)
500				rwerror("ADJUST INODE", cmd.value);
501		}
502	}
503}
504
505int
506findname(idesc)
507	struct inodesc *idesc;
508{
509	register struct direct *dirp = idesc->id_dirp;
510
511	if (dirp->d_ino != idesc->id_parent || idesc->id_entryno < 2) {
512		idesc->id_entryno++;
513		return (KEEPON);
514	}
515	memmove(idesc->id_name, dirp->d_name, (size_t)dirp->d_namlen + 1);
516	return (STOP|FOUND);
517}
518
519int
520findino(idesc)
521	struct inodesc *idesc;
522{
523	register struct direct *dirp = idesc->id_dirp;
524
525	if (dirp->d_ino == 0)
526		return (KEEPON);
527	if (strcmp(dirp->d_name, idesc->id_name) == 0 &&
528	    dirp->d_ino >= ROOTINO && dirp->d_ino <= maxino) {
529		idesc->id_parent = dirp->d_ino;
530		return (STOP|FOUND);
531	}
532	return (KEEPON);
533}
534
535int
536clearentry(idesc)
537	struct inodesc *idesc;
538{
539	register struct direct *dirp = idesc->id_dirp;
540
541	if (dirp->d_ino != idesc->id_parent || idesc->id_entryno < 2) {
542		idesc->id_entryno++;
543		return (KEEPON);
544	}
545	dirp->d_ino = 0;
546	return (STOP|FOUND|ALTERED);
547}
548
549void
550pinode(ino)
551	ino_t ino;
552{
553	register struct dinode *dp;
554	register char *p;
555	struct passwd *pw;
556	time_t t;
557
558	printf(" I=%lu ", (u_long)ino);
559	if (ino < ROOTINO || ino > maxino)
560		return;
561	dp = ginode(ino);
562	printf(" OWNER=");
563	if ((pw = getpwuid((int)dp->di_uid)) != 0)
564		printf("%s ", pw->pw_name);
565	else
566		printf("%u ", (unsigned)dp->di_uid);
567	printf("MODE=%o\n", dp->di_mode);
568	if (preen)
569		printf("%s: ", cdevname);
570	printf("SIZE=%qu ", dp->di_size);
571	t = dp->di_mtime;
572	p = ctime(&t);
573	printf("MTIME=%12.12s %4.4s ", &p[4], &p[20]);
574}
575
576void
577blkerror(ino, type, blk)
578	ino_t ino;
579	char *type;
580	ufs_daddr_t blk;
581{
582
583	pfatal("%ld %s I=%lu", (long)blk, type, (u_long)ino);
584	printf("\n");
585	switch (inoinfo(ino)->ino_state) {
586
587	case FSTATE:
588		inoinfo(ino)->ino_state = FCLEAR;
589		return;
590
591	case DSTATE:
592		inoinfo(ino)->ino_state = DCLEAR;
593		return;
594
595	case FCLEAR:
596	case DCLEAR:
597		return;
598
599	default:
600		errx(EEXIT, "BAD STATE %d TO BLKERR", inoinfo(ino)->ino_state);
601		/* NOTREACHED */
602	}
603}
604
605/*
606 * allocate an unused inode
607 */
608ino_t
609allocino(request, type)
610	ino_t request;
611	int type;
612{
613	register ino_t ino;
614	register struct dinode *dp;
615	struct cg *cgp = &cgrp;
616	int cg;
617
618	if (request == 0)
619		request = ROOTINO;
620	else if (inoinfo(request)->ino_state != USTATE)
621		return (0);
622	for (ino = request; ino < maxino; ino++)
623		if (inoinfo(ino)->ino_state == USTATE)
624			break;
625	if (ino == maxino)
626		return (0);
627	cg = ino_to_cg(&sblock, ino);
628	getblk(&cgblk, cgtod(&sblock, cg), sblock.fs_cgsize);
629	if (!cg_chkmagic(cgp))
630		pfatal("CG %d: BAD MAGIC NUMBER\n", cg);
631	setbit(cg_inosused(cgp), ino % sblock.fs_ipg);
632	cgp->cg_cs.cs_nifree--;
633	switch (type & IFMT) {
634	case IFDIR:
635		inoinfo(ino)->ino_state = DSTATE;
636		cgp->cg_cs.cs_ndir++;
637		break;
638	case IFREG:
639	case IFLNK:
640		inoinfo(ino)->ino_state = FSTATE;
641		break;
642	default:
643		return (0);
644	}
645	cgdirty();
646	dp = ginode(ino);
647	dp->di_db[0] = allocblk((long)1);
648	if (dp->di_db[0] == 0) {
649		inoinfo(ino)->ino_state = USTATE;
650		return (0);
651	}
652	dp->di_mode = type;
653	dp->di_flags = 0;
654	dp->di_atime = time(NULL);
655	dp->di_mtime = dp->di_ctime = dp->di_atime;
656	dp->di_mtimensec = dp->di_ctimensec = dp->di_atimensec = 0;
657	dp->di_size = sblock.fs_fsize;
658	dp->di_blocks = btodb(sblock.fs_fsize);
659	n_files++;
660	inodirty();
661	if (newinofmt)
662		inoinfo(ino)->ino_type = IFTODT(type);
663	return (ino);
664}
665
666/*
667 * deallocate an inode
668 */
669void
670freeino(ino)
671	ino_t ino;
672{
673	struct inodesc idesc;
674	struct dinode *dp;
675
676	memset(&idesc, 0, sizeof(struct inodesc));
677	idesc.id_type = ADDR;
678	idesc.id_func = pass4check;
679	idesc.id_number = ino;
680	dp = ginode(ino);
681	(void)ckinode(dp, &idesc);
682	clearinode(dp);
683	inodirty();
684	inoinfo(ino)->ino_state = USTATE;
685	n_files--;
686}
687