1/*	$NetBSD: dir.c,v 1.62 2023/07/04 20:40:53 riastradh Exp $	*/
2
3/*
4 * Copyright (c) 1980, 1986, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34#if 0
35static char sccsid[] = "@(#)dir.c	8.8 (Berkeley) 4/28/95";
36#else
37__RCSID("$NetBSD: dir.c,v 1.62 2023/07/04 20:40:53 riastradh Exp $");
38#endif
39#endif /* not lint */
40
41#include <sys/param.h>
42#include <sys/time.h>
43
44#include <ufs/ufs/dinode.h>
45#include <ufs/ufs/dir.h>
46#include <ufs/ffs/fs.h>
47#include <ufs/ffs/ffs_extern.h>
48
49#include <err.h>
50#include <stdio.h>
51#include <string.h>
52
53#include "fsck.h"
54#include "fsutil.h"
55#include "extern.h"
56
57const char	*lfname = "lost+found";
58int	lfmode = 01700;
59ino_t	lfdir;
60struct	dirtemplate emptydir = {
61	.dot_ino = 0,
62	.dot_reclen = UFS_DIRBLKSIZ,
63};
64struct	dirtemplate dirhead = {
65	.dot_ino = 0,
66	.dot_reclen = 12,
67	.dot_type = DT_DIR,
68	.dot_namlen = 1,
69	.dot_name = ".",
70	.dotdot_ino = 0,
71	.dotdot_reclen = UFS_DIRBLKSIZ - 12,
72	.dotdot_type = DT_DIR,
73	.dotdot_namlen = 2,
74	.dotdot_name = "..",
75};
76struct	odirtemplate odirhead = {
77	.dot_ino = 0,
78	.dot_reclen = 12,
79	.dot_namlen = 1,
80	.dot_name = ".",
81	.dotdot_ino = 0,
82	.dotdot_reclen = UFS_DIRBLKSIZ - 12,
83	.dotdot_namlen = 2,
84	.dotdot_name = "..",
85};
86
87static int chgino(struct  inodesc *);
88static int dircheck(struct inodesc *, struct direct *, struct bufarea *);
89static int expanddir(union dinode *, char *);
90static void freedir(ino_t, ino_t);
91static struct direct *fsck_readdir(struct inodesc *);
92static struct bufarea *getdirblk(daddr_t, long);
93static int lftempname(char *, ino_t);
94static int mkentry(struct inodesc *);
95void reparent(ino_t, ino_t);
96
97/*
98 * Propagate connected state through the tree.
99 */
100void
101propagate(ino_t inumber)
102{
103	struct inoinfo *inp;
104
105	inp = getinoinfo(inumber);
106
107	for (;;) {
108		inoinfo(inp->i_number)->ino_state = DMARK;
109		if (inp->i_child &&
110		    inoinfo(inp->i_child->i_number)->ino_state != DMARK)
111			inp = inp->i_child;
112		else if (inp->i_number == inumber)
113			break;
114		else if (inp->i_sibling)
115			inp = inp->i_sibling;
116		else
117			inp = getinoinfo(inp->i_parent);
118	}
119
120	for (;;) {
121		inoinfo(inp->i_number)->ino_state = DFOUND;
122		if (inp->i_child &&
123		    inoinfo(inp->i_child->i_number)->ino_state != DFOUND)
124			inp = inp->i_child;
125		else if (inp->i_number == inumber)
126			break;
127		else if (inp->i_sibling)
128			inp = inp->i_sibling;
129		else
130			inp = getinoinfo(inp->i_parent);
131	}
132}
133
134void
135reparent(ino_t inumber, ino_t parent)
136{
137	struct inoinfo *inp, *pinp;
138
139	inp = getinoinfo(inumber);
140	inp->i_parent = inp->i_dotdot = parent;
141	pinp = getinoinfo(parent);
142	inp->i_sibling = pinp->i_child;
143	pinp->i_child = inp;
144	propagate(inumber);
145}
146
147#if (BYTE_ORDER == LITTLE_ENDIAN)
148# define NEEDSWAP	(!needswap)
149#else
150# define NEEDSWAP	(needswap)
151#endif
152
153static void
154dirswap(void *dbuf)
155{
156	struct direct *tdp = (struct direct *)dbuf;
157	u_char tmp;
158
159	tmp = tdp->d_namlen;
160	tdp->d_namlen = tdp->d_type;
161	tdp->d_type = tmp;
162}
163
164/*
165 * Scan each entry in a directory block.
166 */
167int
168dirscan(struct inodesc *idesc)
169{
170	struct direct *dp;
171	struct bufarea *bp;
172	int dsize, n;
173	long blksiz;
174#if !defined(NO_APPLE_UFS) && UFS_DIRBLKSIZ < APPLEUFS_DIRBLKSIZ
175	char dbuf[APPLEUFS_DIRBLKSIZ];
176#else
177	char dbuf[UFS_DIRBLKSIZ];
178#endif
179
180	if (idesc->id_type != DATA)
181		errexit("wrong type to dirscan %d", idesc->id_type);
182	if (idesc->id_entryno == 0 &&
183	    (idesc->id_filesize & (dirblksiz - 1)) != 0)
184		idesc->id_filesize = roundup(idesc->id_filesize, dirblksiz);
185	blksiz = idesc->id_numfrags * sblock->fs_fsize;
186	if (chkrange(idesc->id_blkno, idesc->id_numfrags)) {
187		idesc->id_filesize -= blksiz;
188		return (SKIP);
189	}
190
191	/*
192	 * If we are swapping byte order in directory entries, just swap
193	 * this block and return.
194	 */
195	if (do_dirswap) {
196		int off;
197		bp = getdirblk(idesc->id_blkno, blksiz);
198		for (off = 0; off < blksiz; off += iswap16(dp->d_reclen)) {
199			dp = (struct direct *)(bp->b_un.b_buf + off);
200			dp->d_ino = bswap32(dp->d_ino);
201			dp->d_reclen = bswap16(dp->d_reclen);
202			if (!newinofmt) {
203				u_int8_t tmp = dp->d_namlen;
204				dp->d_namlen = dp->d_type;
205				dp->d_type = tmp;
206			}
207			if (dp->d_reclen == 0)
208				break;
209		}
210		dirty(bp);
211		idesc->id_filesize -= blksiz;
212		return (idesc->id_filesize > 0 ? KEEPON : STOP);
213	}
214
215	idesc->id_loc = 0;
216	for (dp = fsck_readdir(idesc); dp != NULL; dp = fsck_readdir(idesc)) {
217		dsize = iswap16(dp->d_reclen);
218		if (dsize > (int)sizeof dbuf)
219			dsize = sizeof dbuf;
220		memmove(dbuf, dp, (size_t)dsize);
221		if (!newinofmt && NEEDSWAP)
222			dirswap(dbuf);
223		idesc->id_dirp = (struct direct *)dbuf;
224		if ((n = (*idesc->id_func)(idesc)) & ALTERED) {
225			if (!newinofmt && !doinglevel2 && NEEDSWAP)
226				dirswap(dbuf);
227			bp = getdirblk(idesc->id_blkno, blksiz);
228			memmove(bp->b_un.b_buf + idesc->id_loc - dsize, dbuf,
229			    (size_t)dsize);
230			dirty(bp);
231			sbdirty();
232		}
233		if (n & STOP)
234			return (n);
235	}
236	return (idesc->id_filesize > 0 ? KEEPON : STOP);
237}
238
239/*
240 * get next entry in a directory.
241 */
242static struct direct *
243fsck_readdir(struct inodesc *idesc)
244{
245	struct direct *dp, *ndp;
246	struct bufarea *bp;
247	long size, blksiz, fix, dploc;
248
249	blksiz = idesc->id_numfrags * sblock->fs_fsize;
250	bp = getdirblk(idesc->id_blkno, blksiz);
251	if (idesc->id_loc % dirblksiz == 0 && idesc->id_filesize > 0 &&
252	    idesc->id_loc < blksiz) {
253		dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
254		if (dircheck(idesc, dp, bp))
255			goto dpok;
256		if (idesc->id_fix == IGNORE)
257			return (0);
258		fix = dofix(idesc, "DIRECTORY CORRUPTED");
259		bp = getdirblk(idesc->id_blkno, blksiz);
260		dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
261		dp->d_reclen = iswap16(dirblksiz);
262		dp->d_ino = 0;
263		dp->d_type = 0;
264		dp->d_namlen = 0;
265		dp->d_name[0] = '\0';
266		if (fix)
267			dirty(bp);
268		else
269			markclean = 0;
270		idesc->id_loc += dirblksiz;
271		idesc->id_filesize -= dirblksiz;
272		return (dp);
273	}
274dpok:
275	if (idesc->id_filesize <= 0 || idesc->id_loc >= blksiz)
276		return NULL;
277	dploc = idesc->id_loc;
278	dp = (struct direct *)(bp->b_un.b_buf + dploc);
279	idesc->id_loc += iswap16(dp->d_reclen);
280	idesc->id_filesize -= iswap16(dp->d_reclen);
281	if ((idesc->id_loc % dirblksiz) == 0)
282		return (dp);
283	ndp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc);
284	if (idesc->id_loc < blksiz && idesc->id_filesize > 0 &&
285	    dircheck(idesc, ndp, bp) == 0) {
286		size = dirblksiz - (idesc->id_loc % dirblksiz);
287		idesc->id_loc += size;
288		idesc->id_filesize -= size;
289		if (idesc->id_fix == IGNORE)
290			return (0);
291		fix = dofix(idesc, "DIRECTORY CORRUPTED");
292		bp = getdirblk(idesc->id_blkno, blksiz);
293		dp = (struct direct *)(bp->b_un.b_buf + dploc);
294		dp->d_reclen = iswap16(iswap16(dp->d_reclen) + size);
295		if (fix)
296			dirty(bp);
297		else
298			markclean = 0;
299	}
300	return (dp);
301}
302
303/*
304 * Verify that a directory entry is valid.
305 * This is a superset of the checks made in the kernel.
306 * Returns:
307 *	1: good
308 *	0: bad
309 */
310static int
311dircheck(struct inodesc *idesc, struct direct *dp, struct bufarea *bp)
312{
313	uint8_t namlen, type;
314	uint16_t reclen;
315	uint32_t ino;
316	char *cp;
317	int size, spaceleft, modified, unused, i;
318
319	modified = 0;
320	spaceleft = dirblksiz - (idesc->id_loc % dirblksiz);
321
322	/* fill in the correct info for our fields */
323	ino = iswap32(dp->d_ino);
324	reclen = iswap16(dp->d_reclen);
325	if (!newinofmt && NEEDSWAP) {
326		type = dp->d_namlen;
327		namlen = dp->d_type;
328	} else {
329		namlen = dp->d_namlen;
330		type = dp->d_type;
331	}
332
333	if (ino >= maxino ||
334	    reclen == 0 || reclen > spaceleft || (reclen & 0x3) != 0)
335		goto bad;
336
337	size = UFS_DIRSIZ(!newinofmt, dp, needswap);
338	if (ino == 0) {
339		/*
340		 * Special case of an unused directory entry. Normally
341		 * the kernel would coalesce unused space with the previous
342		 * entry by extending its d_reclen, but there are situations
343		 * (e.g. fsck) where that doesn't occur.
344		 * If we're clearing out directory cruft (-z flag), then make
345		 * sure this entry gets fully cleared as well.
346		 */
347		if (!zflag || fswritefd < 0)
348			return 1;
349
350		if (dp->d_type != 0) {
351			dp->d_type = 0;
352			modified = 1;
353		}
354		if (dp->d_namlen != 0) {
355			dp->d_namlen = 0;
356			modified = 1;
357		}
358		if (dp->d_name[0] != '\0') {
359			dp->d_name[0] = '\0';
360			modified = 1;
361		}
362		goto good;
363	}
364
365	if (reclen < size || idesc->id_filesize < size ||
366	    /* namlen > MAXNAMLEN || */
367	    type > 15)
368		goto bad;
369
370	for (cp = dp->d_name, i = 0; i < namlen; i++)
371		if (*cp == '\0' || (*cp++ == '/'))
372			goto bad;
373
374	if (*cp != '\0')
375		goto bad;
376
377	if (!zflag || fswritefd < 0)
378		return 1;
379good:
380	/*
381	 * Clear unused directory entry space, including the d_name
382	 * padding.
383	 */
384	/* First figure the number of pad bytes. */
385	unused = UFS_NAMEPAD(namlen);
386
387	/* Add in the free space to the end of the record. */
388	unused += iswap16(dp->d_reclen) - size;
389
390	/*
391	 * Now clear out the unused space, keeping track if we actually
392	 * changed anything.
393	 */
394	for (cp = &dp->d_name[namlen]; unused > 0; unused--, cp++) {
395		if (*cp == '\0')
396			continue;
397		*cp = '\0';
398		modified = 1;
399	}
400
401	/* mark dirty so we update the zeroed space */
402	if (modified)
403		dirty(bp);
404	return 1;
405bad:
406	if (debug)
407		printf("Bad dir: ino %d reclen %d namlen %d type %d name %s\n",
408		    ino, reclen, namlen, type, dp->d_name);
409	return 0;
410}
411
412void
413direrror(ino_t ino, const char *errmesg)
414{
415
416	fileerror(ino, ino, errmesg);
417}
418
419void
420fileerror(ino_t cwd, ino_t ino, const char *errmesg)
421{
422	union dinode *dp;
423	char pathbuf[MAXPATHLEN + 1];
424	uint16_t mode;
425
426	pwarn("%s ", errmesg);
427	pinode(ino);
428	printf("\n");
429	getpathname(pathbuf, sizeof(pathbuf), cwd, ino);
430	if (ino < UFS_ROOTINO || ino > maxino) {
431		pfatal("NAME=%s\n", pathbuf);
432		return;
433	}
434	dp = ginode(ino);
435	if (ftypeok(dp)) {
436		mode = DIP(dp, mode);
437		pfatal("%s=%s\n",
438		    (iswap16(mode) & IFMT) == IFDIR ? "DIR" : "FILE", pathbuf);
439	}
440	else
441		pfatal("NAME=%s\n", pathbuf);
442}
443
444void
445adjust(struct inodesc *idesc, int lcnt)
446{
447	union dinode *dp;
448	int16_t nlink;
449	int saveresolved;
450
451	dp = ginode(idesc->id_number);
452	nlink = iswap16(DIP(dp, nlink));
453	if (nlink == lcnt) {
454		/*
455		 * If we have not hit any unresolved problems, are running
456		 * in preen mode, and are on a file system using soft updates,
457		 * then just toss any partially allocated files.
458		 */
459		if (resolved && preen && usedsoftdep) {
460			clri(idesc, "UNREF", 1);
461			return;
462		} else {
463			/*
464			 * The file system can be marked clean even if
465			 * a file is not linked up, but is cleared.
466			 * Hence, resolved should not be cleared when
467			 * linkup is answered no, but clri is answered yes.
468			 */
469			saveresolved = resolved;
470			if (linkup(idesc->id_number, (ino_t)0, NULL) == 0) {
471				resolved = saveresolved;
472				clri(idesc, "UNREF", 0);
473				return;
474			}
475			/*
476			 * Account for the new reference created by linkup().
477			 */
478			dp = ginode(idesc->id_number);
479			lcnt--;
480		}
481	}
482	if (lcnt != 0) {
483		pwarn("LINK COUNT %s", (lfdir == idesc->id_number) ? lfname :
484			((iswap16(DIP(dp, mode)) & IFMT) == IFDIR ?
485			"DIR" : "FILE"));
486		pinode(idesc->id_number);
487		printf(" COUNT %d SHOULD BE %d",
488			nlink, nlink - lcnt);
489		if (preen || usedsoftdep) {
490			if (lcnt < 0) {
491				printf("\n");
492				pfatal("LINK COUNT INCREASING");
493			}
494			if (preen)
495				printf(" (ADJUSTED)\n");
496		}
497		if (preen || reply("ADJUST") == 1) {
498			DIP_SET(dp, nlink, iswap16(nlink - lcnt));
499			inodirty();
500		} else
501			markclean = 0;
502	}
503}
504
505static int
506mkentry(struct inodesc *idesc)
507{
508	struct direct *dirp = idesc->id_dirp;
509	struct direct newent;
510	int newlen, oldlen;
511
512	newent.d_namlen = strlen(idesc->id_name);
513	newlen = UFS_DIRSIZ(0, &newent, 0);
514	if (dirp->d_ino != 0)
515		oldlen = UFS_DIRSIZ(0, dirp, 0);
516	else
517		oldlen = 0;
518	if (iswap16(dirp->d_reclen) - oldlen < newlen)
519		return (KEEPON);
520	newent.d_reclen = iswap16(iswap16(dirp->d_reclen) - oldlen);
521	dirp->d_reclen = iswap16(oldlen);
522	dirp = (struct direct *)(((char *)dirp) + oldlen);
523	/* ino to be entered is in id_parent */
524	dirp->d_ino = iswap32(idesc->id_parent);
525	dirp->d_reclen = newent.d_reclen;
526	if (newinofmt)
527		dirp->d_type = inoinfo(idesc->id_parent)->ino_type;
528	else
529		dirp->d_type = 0;
530	dirp->d_namlen = newent.d_namlen;
531	memmove(dirp->d_name, idesc->id_name, (size_t)newent.d_namlen + 1);
532	/*
533	 * If the entry was split, dirscan() will only reverse the byte
534	 * order of the original entry, and not the new one, before
535	 * writing it back out.  So, we reverse the byte order here if
536	 * necessary.
537	 */
538	if (oldlen != 0 && !newinofmt && !doinglevel2 && NEEDSWAP)
539		dirswap(dirp);
540	return (ALTERED|STOP);
541}
542
543static int
544chgino(struct inodesc *idesc)
545{
546	struct direct *dirp = idesc->id_dirp;
547
548	if (memcmp(dirp->d_name, idesc->id_name, (int)dirp->d_namlen + 1))
549		return (KEEPON);
550	dirp->d_ino = iswap32(idesc->id_parent);
551	if (newinofmt)
552		dirp->d_type = inoinfo(idesc->id_parent)->ino_type;
553	else
554		dirp->d_type = 0;
555	return (ALTERED|STOP);
556}
557
558int
559linkup(ino_t orphan, ino_t parentdir, char *name)
560{
561	union dinode *dp;
562	int lostdir;
563	ino_t oldlfdir;
564	struct inodesc idesc;
565	char tempname[BUFSIZ];
566	int16_t nlink;
567	uint16_t mode;
568
569	memset(&idesc, 0, sizeof(struct inodesc));
570	dp = ginode(orphan);
571	mode = iswap16(DIP(dp, mode));
572	nlink = iswap16(DIP(dp, nlink));
573	lostdir = (mode & IFMT) == IFDIR;
574	pwarn("UNREF %s ", lostdir ? "DIR" : "FILE");
575	pinode(orphan);
576	if (preen  && DIP(dp, size) == 0)
577		return (0);
578	if (preen)
579		printf(" (RECONNECTED)\n");
580	else
581		if (reply("RECONNECT") == 0) {
582			markclean = 0;
583			return (0);
584		}
585	if (parentdir != 0)
586		inoinfo(parentdir)->ino_linkcnt++;
587	if (lfdir == 0) {
588		dp = ginode(UFS_ROOTINO);
589		idesc.id_name = lfname;
590		idesc.id_type = DATA;
591		idesc.id_func = findino;
592		idesc.id_number = UFS_ROOTINO;
593		idesc.id_uid = iswap32(DIP(dp, uid));
594		idesc.id_gid = iswap32(DIP(dp, gid));
595		if ((ckinode(dp, &idesc) & FOUND) != 0) {
596			lfdir = idesc.id_parent;
597		} else {
598			pwarn("NO lost+found DIRECTORY");
599			if (preen || reply("CREATE")) {
600				lfdir = allocdir(UFS_ROOTINO, (ino_t)0, lfmode);
601				if (lfdir != 0) {
602					if (makeentry(UFS_ROOTINO, lfdir, lfname) != 0) {
603						numdirs++;
604						if (preen)
605							printf(" (CREATED)\n");
606					} else {
607						freedir(lfdir, UFS_ROOTINO);
608						lfdir = 0;
609						if (preen)
610							printf("\n");
611					}
612				}
613				if (lfdir != 0) {
614					reparent(lfdir, UFS_ROOTINO);
615				}
616			}
617		}
618		if (lfdir == 0) {
619			pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n");
620			markclean = 0;
621			return (0);
622		}
623	}
624	dp = ginode(lfdir);
625	mode = DIP(dp, mode);
626	mode = iswap16(mode);
627	if ((mode & IFMT) != IFDIR) {
628		pfatal("lost+found IS NOT A DIRECTORY");
629		if (reply("REALLOCATE") == 0) {
630			markclean = 0;
631			return (0);
632		}
633		oldlfdir = lfdir;
634		lfdir = allocdir(UFS_ROOTINO, (ino_t)0, lfmode);
635		if (lfdir == 0) {
636			pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n");
637			markclean = 0;
638			return (0);
639		}
640		if ((changeino(UFS_ROOTINO, lfname, lfdir) & ALTERED) == 0) {
641			pfatal("SORRY. CANNOT CREATE lost+found DIRECTORY\n\n");
642			markclean = 0;
643			return (0);
644		}
645		inodirty();
646		reparent(lfdir, UFS_ROOTINO);
647		idesc.id_type = ADDR;
648		idesc.id_func = pass4check;
649		idesc.id_number = oldlfdir;
650		adjust(&idesc, inoinfo(oldlfdir)->ino_linkcnt + 1);
651		inoinfo(oldlfdir)->ino_linkcnt = 0;
652		dp = ginode(lfdir);
653	}
654	if (inoinfo(lfdir)->ino_state != DFOUND) {
655		pfatal("SORRY. NO lost+found DIRECTORY\n\n");
656		markclean = 0;
657		return (0);
658	}
659	(void)lftempname(tempname, orphan);
660	if (makeentry(lfdir, orphan, (name ? name : tempname)) == 0) {
661		pfatal("SORRY. NO SPACE IN lost+found DIRECTORY");
662		printf("\n\n");
663		markclean = 0;
664		return (0);
665	}
666	inoinfo(orphan)->ino_linkcnt--;
667	if (lostdir) {
668		if ((changeino(orphan, "..", lfdir) & ALTERED) == 0 &&
669		    parentdir != (ino_t)-1)
670			(void)makeentry(orphan, lfdir, "..");
671		dp = ginode(lfdir);
672		nlink = DIP(dp, nlink);
673		DIP_SET(dp, nlink, iswap16(iswap16(nlink) + 1));
674		inodirty();
675		inoinfo(lfdir)->ino_linkcnt++;
676		reparent(orphan, lfdir);
677		pwarn("DIR I=%llu CONNECTED. ", (unsigned long long)orphan);
678		if (parentdir != (ino_t)-1)
679			printf("PARENT WAS I=%llu\n",
680			    (unsigned long long)parentdir);
681		if (preen == 0)
682			printf("\n");
683	}
684	return (1);
685}
686
687/*
688 * fix an entry in a directory.
689 */
690int
691changeino(ino_t dir, const char *name, ino_t newnum)
692{
693	struct inodesc idesc;
694	union dinode *dp;
695
696	dp = ginode(dir);
697	memset(&idesc, 0, sizeof(struct inodesc));
698	idesc.id_type = DATA;
699	idesc.id_func = chgino;
700	idesc.id_number = dir;
701	idesc.id_fix = DONTKNOW;
702	idesc.id_name = name;
703	idesc.id_parent = newnum;	/* new value for name */
704	idesc.id_uid = iswap32(DIP(dp, uid));
705	idesc.id_gid = iswap32(DIP(dp, gid));
706	return (ckinode(dp, &idesc));
707}
708
709/*
710 * make an entry in a directory
711 */
712int
713makeentry(ino_t parent, ino_t ino, const char *name)
714{
715	union dinode *dp;
716	struct inodesc idesc;
717	char pathbuf[MAXPATHLEN + 1];
718
719	if (parent < UFS_ROOTINO || parent >= maxino ||
720	    ino < UFS_ROOTINO || ino >= maxino)
721		return (0);
722	dp = ginode(parent);
723	memset(&idesc, 0, sizeof(struct inodesc));
724	idesc.id_type = DATA;
725	idesc.id_func = mkentry;
726	idesc.id_number = parent;
727	idesc.id_parent = ino;	/* this is the inode to enter */
728	idesc.id_fix = DONTKNOW;
729	idesc.id_name = name;
730	idesc.id_uid = iswap32(DIP(dp, uid));
731	idesc.id_gid = iswap32(DIP(dp, gid));
732	if (iswap64(DIP(dp, size)) % dirblksiz) {
733		DIP_SET(dp, size,
734		    iswap64(roundup(iswap64(DIP(dp, size)), dirblksiz)));
735		inodirty();
736	}
737	if ((ckinode(dp, &idesc) & ALTERED) != 0)
738		return (1);
739	getpathname(pathbuf, sizeof(pathbuf), parent, parent);
740	dp = ginode(parent);
741	if (expanddir(dp, pathbuf) == 0)
742		return (0);
743	update_uquot(idesc.id_number, idesc.id_uid, idesc.id_gid,
744	    btodb(sblock->fs_bsize), 0);
745	return (ckinode(dp, &idesc) & ALTERED);
746}
747
748/*
749 * Attempt to expand the size of a directory
750 */
751static int
752expanddir(union dinode *dp, char *name)
753{
754	daddr_t lastbn, newblk, dirblk;
755	struct bufarea *bp;
756	char *cp;
757#if !defined(NO_APPLE_UFS) && UFS_DIRBLKSIZ < APPLEUFS_DIRBLKSIZ
758	char firstblk[APPLEUFS_DIRBLKSIZ];
759#else
760	char firstblk[UFS_DIRBLKSIZ];
761#endif
762	struct ufs1_dinode *dp1 = NULL;
763	struct ufs2_dinode *dp2 = NULL;
764
765	if (is_ufs2)
766		dp2 = &dp->dp2;
767	else
768		dp1 = &dp->dp1;
769
770	lastbn = ffs_lblkno(sblock, iswap64(DIP(dp, size)));
771	if (lastbn >= UFS_NDADDR - 1 || DIP(dp, db[lastbn]) == 0 ||
772	    DIP(dp, size) == 0)
773		return (0);
774	if ((newblk = allocblk(sblock->fs_frag)) == 0)
775		return (0);
776	if (is_ufs2) {
777		dp2->di_db[lastbn + 1] = dp2->di_db[lastbn];
778		dp2->di_db[lastbn] = iswap64(newblk);
779		dp2->di_size = iswap64(iswap64(dp2->di_size)+sblock->fs_bsize);
780		dp2->di_blocks = iswap64(iswap64(dp2->di_blocks) +
781		    btodb(sblock->fs_bsize));
782		dirblk = iswap64(dp2->di_db[lastbn + 1]);
783	} else {
784		dp1->di_db[lastbn + 1] = dp1->di_db[lastbn];
785		dp1->di_db[lastbn] = iswap32((int32_t)newblk);
786		dp1->di_size = iswap64(iswap64(dp1->di_size)+sblock->fs_bsize);
787		dp1->di_blocks = iswap32(iswap32(dp1->di_blocks) +
788		    btodb(sblock->fs_bsize));
789		dirblk = iswap32(dp1->di_db[lastbn + 1]);
790	}
791	bp = getdirblk(dirblk, ffs_sblksize(sblock, (daddr_t)DIP(dp, size), lastbn + 1));
792	if (bp->b_errs)
793		goto bad;
794	memmove(firstblk, bp->b_un.b_buf, dirblksiz);
795	bp = getdirblk(newblk, sblock->fs_bsize);
796	if (bp->b_errs)
797		goto bad;
798	memmove(bp->b_un.b_buf, firstblk, dirblksiz);
799	emptydir.dot_reclen = iswap16(dirblksiz);
800	for (cp = &bp->b_un.b_buf[dirblksiz];
801	     cp < &bp->b_un.b_buf[sblock->fs_bsize];
802	     cp += dirblksiz)
803		memmove(cp, &emptydir, sizeof emptydir);
804	dirty(bp);
805	bp = getdirblk(dirblk, ffs_sblksize(sblock, (daddr_t)DIP(dp, size), lastbn + 1));
806	if (bp->b_errs)
807		goto bad;
808	memmove(bp->b_un.b_buf, &emptydir, sizeof emptydir);
809	pwarn("NO SPACE LEFT IN %s", name);
810	if (preen)
811		printf(" (EXPANDED)\n");
812	else if (reply("EXPAND") == 0)
813		goto bad;
814	dirty(bp);
815	inodirty();
816	return (1);
817bad:
818	if (is_ufs2) {
819		dp2->di_db[lastbn] = dp2->di_db[lastbn + 1];
820		dp2->di_db[lastbn + 1] = 0;
821		dp2->di_size = iswap64(iswap64(dp2->di_size)-sblock->fs_bsize);
822		dp2->di_blocks = iswap64(iswap64(dp2->di_blocks) -
823		    btodb(sblock->fs_bsize));
824	} else {
825		dp1->di_db[lastbn] = dp1->di_db[lastbn + 1];
826		dp1->di_db[lastbn + 1] = 0;
827		dp1->di_size = iswap64(iswap64(dp1->di_size)-sblock->fs_bsize);
828		dp1->di_blocks = iswap32(iswap32(dp1->di_blocks) -
829		    btodb(sblock->fs_bsize));
830	}
831	freeblk(newblk, sblock->fs_frag);
832	markclean = 0;
833	return (0);
834}
835
836/*
837 * allocate a new directory
838 */
839ino_t
840allocdir(ino_t parent, ino_t request, int mode)
841{
842	ino_t ino;
843	char *cp;
844	union dinode *dp;
845	struct bufarea *bp;
846	struct inoinfo *inp;
847	struct dirtemplate *dirp;
848	daddr_t dirblk;
849
850	ino = allocino(request, IFDIR|mode);
851	if (ino < UFS_ROOTINO)
852		return 0;
853	update_uquot(ino, 0, 0, btodb(sblock->fs_fsize), 1);
854	dirhead.dot_reclen = iswap16(12);
855	dirhead.dotdot_reclen = iswap16(dirblksiz - 12);
856	odirhead.dot_reclen = iswap16(12);
857	odirhead.dotdot_reclen = iswap16(dirblksiz - 12);
858	odirhead.dot_namlen = iswap16(1);
859	odirhead.dotdot_namlen = iswap16(2);
860	if (newinofmt)
861		dirp = &dirhead;
862	else
863		dirp = (struct dirtemplate *)&odirhead;
864	dirp->dot_ino = iswap32(ino);
865	dirp->dotdot_ino = iswap32(parent);
866	dp = ginode(ino);
867	dirblk = is_ufs2 ? iswap64(dp->dp2.di_db[0])
868		    : iswap32(dp->dp1.di_db[0]);
869	bp = getdirblk(dirblk, sblock->fs_fsize);
870	if (bp->b_errs) {
871		freeino(ino);
872		return (0);
873	}
874	memmove(bp->b_un.b_buf, dirp, sizeof(struct dirtemplate));
875	emptydir.dot_reclen = iswap16(dirblksiz);
876	for (cp = &bp->b_un.b_buf[dirblksiz];
877	     cp < &bp->b_un.b_buf[sblock->fs_fsize];
878	     cp += dirblksiz)
879		memmove(cp, &emptydir, sizeof emptydir);
880	dirty(bp);
881	DIP_SET(dp, nlink, iswap16(2));
882	inodirty();
883	if (ino == UFS_ROOTINO) {
884		inoinfo(ino)->ino_linkcnt = iswap16(DIP(dp, nlink));
885		cacheino(dp, ino);
886		return(ino);
887	}
888	if (inoinfo(parent)->ino_state != DSTATE &&
889	    inoinfo(parent)->ino_state != DFOUND) {
890		freeino(ino);
891		return (0);
892	}
893	cacheino(dp, ino);
894	inp = getinoinfo(ino);
895	inp->i_parent = parent;
896	inp->i_dotdot = parent;
897	inoinfo(ino)->ino_state = inoinfo(parent)->ino_state;
898	if (inoinfo(ino)->ino_state == DSTATE) {
899		inoinfo(ino)->ino_linkcnt = iswap16(DIP(dp, nlink));
900		inoinfo(parent)->ino_linkcnt++;
901	}
902	dp = ginode(parent);
903	DIP_SET(dp, nlink, iswap16(iswap16(DIP(dp, nlink)) + 1));
904	inodirty();
905	return (ino);
906}
907
908/*
909 * free a directory inode
910 */
911static void
912freedir(ino_t ino, ino_t parent)
913{
914	union dinode *dp;
915
916	if (ino != parent) {
917		dp = ginode(parent);
918		DIP_SET(dp, nlink, iswap16(iswap16(DIP(dp, nlink)) - 1));
919		inodirty();
920	}
921	freeino(ino);
922}
923
924/*
925 * generate a temporary name for the lost+found directory.
926 */
927static int
928lftempname(char *bufp, ino_t ino)
929{
930	ino_t in;
931	char *cp;
932	int namlen;
933
934	cp = bufp + 2;
935	for (in = maxino; in > 0; in /= 10)
936		cp++;
937	*--cp = 0;
938	namlen = cp - bufp;
939	in = ino;
940	while (cp > bufp) {
941		*--cp = (in % 10) + '0';
942		in /= 10;
943	}
944	*cp = '#';
945	return (namlen);
946}
947
948/*
949 * Get a directory block.
950 * Insure that it is held until another is requested.
951 */
952static struct bufarea *
953getdirblk(daddr_t blkno, long size)
954{
955
956	if (pdirbp != 0)
957		pdirbp->b_flags &= ~B_INUSE;
958	pdirbp = getdatablk(blkno, size);
959	return (pdirbp);
960}
961