xref: /illumos-gate/usr/src/cmd/backup/restore/dirs.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
7 /*	  All Rights Reserved	*/
8 
9 /*
10  * Copyright (c) 1983 Regents of the University of California.
11  * All rights reserved.  The Berkeley software License Agreement
12  * specifies the terms and conditions for redistribution.
13  */
14 
15 #include "restore.h"
16 #include <byteorder.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <utime.h>
20 
21 /*
22  * Symbol table of directories read from tape.
23  */
24 #define	HASHSIZE	1000
25 #define	INOHASH(val) (val % HASHSIZE)
26 struct inotab {
27 	struct inotab *t_next;
28 	ino_t	t_ino;
29 	offset_t t_seekpt;
30 	offset_t t_size;
31 	struct inotab *t_xattr;
32 };
33 static struct inotab *inotab[HASHSIZE];
34 static struct inotab *xattrlist = NULL;
35 
36 /*
37  * Information retained about directories.
38  */
39 static struct modeinfo {
40 	ino_t	ino;
41 	time_t	timep[2];
42 	mode_t	mode;
43 	uid_t	uid;
44 	gid_t	gid;
45 	size_t	metasize;
46 } node;
47 
48 /*
49  * Global variables for this file.
50  */
51 static off64_t	g_seekpt;		/* some people have a local seekpt */
52 static FILE	*df, *mf;
53 static char	dirfile[MAXPATHLEN] = "#";	/* No file */
54 static char	modefile[MAXPATHLEN] = "#";	/* No file */
55 
56 static RST_DIR	*dirp;
57 
58 #define	INIT_TEMPFILE(name, type) \
59 	if (name[0] == '#') { \
60 		if (tmpdir == (char *)NULL) /* can't happen; be paranoid */ \
61 			tmpdir = "/tmp"; \
62 		(void) snprintf(name, sizeof (name), \
63 		    "%s/rst" type "%ld.XXXXXX", tmpdir, dumpdate); \
64 		(void) mktemp(name); \
65 	}
66 
67 #define	INIT_DIRFILE()	INIT_TEMPFILE(dirfile, "dir")
68 #define	INIT_MODEFILE()	INIT_TEMPFILE(modefile, "mode")
69 
70 /*
71  * Format of old style directories.
72  */
73 #define	ODIRSIZ 14
74 struct odirect {
75 	ushort_t d_ino;
76 	char	d_name[ODIRSIZ];
77 };
78 
79 #ifdef __STDC__
80 static ino_t search(ino_t, char	*);
81 static void putdir(char *, size_t);
82 static void putent(struct direct *);
83 static void skipmetadata(FILE *, size_t);
84 static void flushent(void);
85 static void dcvt(struct odirect *, struct direct *);
86 static RST_DIR *rst_initdirfile(char *);
87 static offset_t rst_telldir(RST_DIR *);
88 static void rst_seekdir(RST_DIR *, offset_t, offset_t);
89 static struct inotab *allocinotab(ino_t, struct dinode *, off64_t);
90 static void nodeflush(void);
91 static struct inotab *inotablookup(ino_t);
92 #else
93 static ino_t search();
94 static void putdir();
95 static void putent();
96 static void skipmetadata();
97 static void flushent();
98 static void dcvt();
99 static RST_DIR *rst_initdirfile();
100 static offset_t rst_telldir();
101 static void rst_seekdir();
102 static struct inotab *allocinotab();
103 static void nodeflush();
104 static struct inotab *inotablookup();
105 #endif
106 
107 /*
108  *	Extract directory contents, building up a directory structure
109  *	on disk for extraction by name.
110  *	If genmode is requested, save mode, owner, and times for all
111  *	directories on the tape.
112  */
113 void
114 extractdirs(int genmode)
115 {
116 	int ts;
117 	struct dinode *ip;
118 	int saverr;
119 	struct inotab *itp;
120 	struct direct nulldir;
121 	static char dotname[] = "."; /* dirlookup/psearch writes to its arg */
122 
123 	vprintf(stdout, gettext("Extract directories from tape\n"));
124 	INIT_DIRFILE();
125 	if ((df = safe_fopen(dirfile, "w", 0600)) == (FILE *)NULL) {
126 		saverr = errno;
127 		(void) fprintf(stderr,
128 		    gettext("%s: %s - cannot create directory temporary\n"),
129 			progname, dirfile);
130 		errno = saverr;
131 		perror("fopen");
132 		done(1);
133 	}
134 	if (genmode != 0) {
135 		INIT_MODEFILE();
136 		if ((mf = safe_fopen(modefile, "w", 0600)) == (FILE *)NULL) {
137 			saverr = errno;
138 			(void) fprintf(stderr,
139 			    gettext("%s: %s - cannot create modefile \n"),
140 				progname, modefile);
141 			errno = saverr;
142 			perror("fopen");
143 			done(1);
144 		}
145 	}
146 	nulldir.d_ino = 0;
147 	nulldir.d_namlen = 1;
148 	(void) strcpy(nulldir.d_name, "/");
149 	/* LINTED DIRSIZ will always fit into a ushort_t */
150 	nulldir.d_reclen = (ushort_t)DIRSIZ(&nulldir);
151 	/* LINTED sign extension ok in assert */
152 	assert(DIRSIZ(&nulldir) == (ulong_t)nulldir.d_reclen);
153 	for (;;) {
154 		curfile.name = gettext("<directory file - name unknown>");
155 		curfile.action = USING;
156 		ip = curfile.dip;
157 		ts = curfile.ts;
158 		if (ts != TS_END && ts != TS_INODE) {
159 			getfile(null, null);
160 			continue;
161 		}
162 		if (ts == TS_INODE && ip == NULL) {
163 			(void) fprintf(stderr, gettext(
164 "%s: extractdirs: Failed internal consistency check, curfile.dip is NULL\n"),
165 			    progname);
166 			done(1);
167 		}
168 		if ((ts == TS_INODE && (ip->di_mode & IFMT) != IFDIR &&
169 		    (ip->di_mode & IFMT) != IFATTRDIR) ||
170 		    (ts == TS_END)) {
171 			(void) fflush(df);
172 			/* XXX Legitimate error, bad complaint string */
173 			if (ferror(df))
174 				panic("%s: %s\n", dirfile, strerror(errno));
175 			(void) fclose(df);
176 			rst_closedir(dirp);
177 			dirp = rst_initdirfile(dirfile);
178 			if (dirp == NULL)
179 				perror("initdirfile");
180 			if (mf != NULL) {
181 				(void) fflush(mf);
182 				/* XXX Legitimate error, bad complaint string */
183 				if (ferror(mf))
184 					panic("%s: %s\n",
185 					    modefile, strerror(errno));
186 				(void) fclose(mf);
187 			}
188 			if (dirlookup(dotname) == 0) {
189 				(void) fprintf(stderr, gettext(
190 				    "Root directory is not on tape\n"));
191 				done(1);
192 			}
193 			return;
194 		}
195 		itp = allocinotab(curfile.ino, ip, g_seekpt);
196 		getfile(putdir, null);
197 		if (mf != NULL)
198 			nodeflush();
199 
200 		putent(&nulldir);
201 		flushent();
202 		itp->t_size = g_seekpt - itp->t_seekpt;
203 	}
204 }
205 
206 /*
207  * skip over all the directories on the tape
208  */
209 void
210 skipdirs()
211 {
212 	while (curfile.dip != NULL &&
213 		((curfile.dip->di_mode & IFMT) == IFDIR ||
214 		(curfile.dip->di_mode & IFMT) == IFATTRDIR)) {
215 		skipfile();
216 	}
217 }
218 
219 /*
220  *	Recursively find names and inumbers of all files in subtree
221  *	pname and pass them off to be processed.
222  */
223 void
224 treescan(char *pname, ino_t ino, long (*todo)())
225 {
226 	struct inotab *itp;
227 	struct direct *dp;
228 	uint_t loclen;
229 	offset_t bpt;
230 	char locname[MAXCOMPLEXLEN];
231 
232 	itp = inotablookup(ino);
233 	if (itp == NULL) {
234 		/*
235 		 * Pname is name of a simple file or an unchanged directory.
236 		 */
237 		(void) (*todo)(pname, ino, LEAF);
238 		return;
239 	}
240 	/*
241 	 * Pname is a dumped directory name.
242 	 */
243 	if ((*todo)(pname, ino, NODE) == FAIL)
244 		return;
245 	/*
246 	 * begin search through the directory
247 	 * skipping over "." and ".."
248 	 */
249 	loclen = complexcpy(locname, pname, MAXCOMPLEXLEN);
250 	locname[loclen-1] = '/';
251 	rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
252 	dp = rst_readdir(dirp); /* "." */
253 
254 	if (dp != NULL && strcmp(dp->d_name, ".") == 0)
255 		dp = rst_readdir(dirp); /* ".." */
256 	else
257 		(void) fprintf(stderr,
258 		    gettext("Warning: `.' missing from directory %s\n"),
259 			pname);
260 	if (dp != NULL && strcmp(dp->d_name, "..") == 0)
261 		dp = rst_readdir(dirp); /* first real entry */
262 	else
263 		(void) fprintf(stderr,
264 		    gettext("Warning: `..' missing from directory %s\n"),
265 			pname);
266 	bpt = rst_telldir(dirp);
267 	/*
268 	 * a zero inode signals end of directory
269 	 */
270 	while (dp != NULL && dp->d_ino != 0) {
271 		locname[loclen] = '\0';
272 		if ((loclen + dp->d_namlen) >= (sizeof (locname) - 2)) {
273 			(void) fprintf(stderr,
274 			    gettext(
275 				"%s%s: ignoring name that exceeds %d char\n"),
276 			    locname, dp->d_name, MAXCOMPLEXLEN);
277 		} else {
278 			/* Always fits by if() condition */
279 			(void) strcpy(locname + loclen, dp->d_name);
280 			/* put a double null on string for lookupname() */
281 			locname[loclen+dp->d_namlen+1] = '\0';
282 			treescan(locname, dp->d_ino, todo);
283 			rst_seekdir(dirp, bpt, itp->t_seekpt);
284 		}
285 		dp = rst_readdir(dirp);
286 		bpt = rst_telldir(dirp);
287 	}
288 	if (dp == NULL)
289 		(void) fprintf(stderr,
290 			gettext("corrupted directory: %s.\n"), locname);
291 }
292 
293 /*
294  * Scan the directory table looking for extended attribute trees.
295  * Recursively find names and inumbers in each tree and pass them
296  * off to be processed.  If the always parameter is not set, only
297  * process the attribute tree if the attribute tree parent is to
298  * be extracted.
299  */
300 void
301 attrscan(int always, long (*todo)())
302 {
303 	struct inotab *itp;
304 	struct entry *ep, *parent;
305 	struct direct *dp;
306 	char name[MAXCOMPLEXLEN];
307 	int len;
308 
309 	for (itp = xattrlist; itp != NULL; itp = itp->t_xattr) {
310 		rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
311 		if ((dp = rst_readdir(dirp)) != NULL &&	/* "." */
312 		    (dp = rst_readdir(dirp)) != NULL &&	/* ".." */
313 		    strcmp(dp->d_name, "..") == 0) {
314 			if ((parent = lookupino(dp->d_ino)) != NULL) {
315 				if (!always &&
316 				    (parent->e_flags & (NEW|EXTRACT)) == 0)
317 					continue;
318 				len = complexcpy(name, myname(parent),
319 							MAXCOMPLEXLEN - 3);
320 				name[len] = '.';
321 				name[len+1] = '\0';
322 				name[len+2] = '\0';
323 				inattrspace = 1;
324 				if ((ep = lookupino(itp->t_ino)) == NULL) {
325 					ep = addentry(name, itp->t_ino,
326 								NODE|ROOT);
327 				}
328 				ep->e_flags |= XATTRROOT;
329 				treescan(name, itp->t_ino, todo);
330 				inattrspace = 0;
331 			} else {
332 				(void) fprintf(stderr,
333 			gettext("Warning: orphaned attribute directory\n"));
334 			}
335 		} else {
336 			(void) fprintf(stderr,
337 	    gettext("Warning: `..' missing from attribute directory\n"));
338 		}
339 	}
340 }
341 
342 /*
343  * Search the directory tree rooted at inode ROOTINO
344  * for the path pointed at by n.  Note that n must be
345  * modifiable, although it is returned in the same
346  * condition it was given to us in.
347  */
348 ino_t
349 psearch(char *n)
350 {
351 	char *cp, *cp1;
352 	ino_t ino;
353 	char c;
354 
355 	ino = ROOTINO;
356 	if (*(cp = n) == '/')
357 		cp++;
358 next:
359 	cp1 = cp + 1;
360 	while (*cp1 != '/' && *cp1)
361 		cp1++;
362 	c = *cp1;
363 	*cp1 = 0;
364 	ino = search(ino, cp);
365 	if (ino == 0) {
366 		*cp1 = c;
367 		return (0);
368 	}
369 	*cp1 = c;
370 	if (c == '/') {
371 		cp = cp1+1;
372 		goto next;
373 	}
374 	return (ino);
375 }
376 
377 /*
378  * search the directory inode ino
379  * looking for entry cp
380  */
381 static ino_t
382 search(ino_t inum, char *cp)
383 {
384 	struct direct *dp;
385 	struct inotab *itp;
386 	uint_t len;
387 
388 	itp = inotablookup(inum);
389 	if (itp == NULL)
390 		return (0);
391 	rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
392 	len = strlen(cp);
393 	do {
394 		dp = rst_readdir(dirp);
395 		if (dp == NULL || dp->d_ino == 0)
396 			return (0);
397 	} while (dp->d_namlen != len || strncmp(dp->d_name, cp, len) != 0);
398 	return (dp->d_ino);
399 }
400 
401 /*
402  * Put the directory entries in the directory file
403  */
404 static void
405 putdir(char *buf, size_t size)
406 {
407 	struct direct cvtbuf;
408 	struct odirect *odp;
409 	struct odirect *eodp;
410 	struct direct *dp;
411 	size_t loc, i;
412 
413 	if (cvtflag) {
414 		/*LINTED [buf is char[] in getfile, size % fs_fsize == 0]*/
415 		eodp = (struct odirect *)&buf[size];
416 		/*LINTED [buf is char[] in getfile]*/
417 		for (odp = (struct odirect *)buf; odp < eodp; odp++)
418 			if (odp->d_ino != 0) {
419 				dcvt(odp, &cvtbuf);
420 				putent(&cvtbuf);
421 			}
422 	} else {
423 		loc = 0;
424 		while (loc < size) {
425 			/*LINTED [buf is char[] in getfile, loc % 4 == 0]*/
426 			dp = (struct direct *)(buf + loc);
427 			normdirect(byteorder, dp);
428 			i = DIRBLKSIZ - (loc & (DIRBLKSIZ - 1));
429 			if (dp->d_reclen == 0 || (long)dp->d_reclen > i) {
430 				loc += i;
431 				continue;
432 			}
433 			loc += dp->d_reclen;
434 			if (dp->d_ino != 0) {
435 				putent(dp);
436 			}
437 		}
438 	}
439 }
440 
441 /*
442  * These variables are "local" to the following two functions.
443  */
444 static char dirbuf[DIRBLKSIZ];
445 static int32_t dirloc = 0;
446 static int32_t prev = 0;
447 
448 /*
449  * add a new directory entry to a file.
450  */
451 static void
452 putent(struct direct *dp)
453 {
454 	/* LINTED DIRSIZ will always fit in a ushort_t */
455 	dp->d_reclen = (ushort_t)DIRSIZ(dp);
456 	/* LINTED sign extension ok in assert */
457 	assert(DIRSIZ(dp) == (ulong_t)dp->d_reclen);
458 	if (dirloc + (long)dp->d_reclen > DIRBLKSIZ) {
459 		/*LINTED [prev += dp->d_reclen, prev % 4 == 0]*/
460 		((struct direct *)(dirbuf + prev))->d_reclen =
461 		    DIRBLKSIZ - prev;
462 		(void) fwrite(dirbuf, 1, DIRBLKSIZ, df);
463 		if (ferror(df))
464 			panic("%s: %s\n", dirfile, strerror(errno));
465 		dirloc = 0;
466 	}
467 	bcopy((char *)dp, dirbuf + dirloc, (size_t)dp->d_reclen);
468 	prev = dirloc;
469 	dirloc += dp->d_reclen;
470 }
471 
472 /*
473  * flush out a directory that is finished.
474  */
475 static void
476 #ifdef __STDC__
477 flushent(void)
478 #else
479 flushent()
480 #endif
481 {
482 
483 	/* LINTED prev += dp->d_reclen, prev % 4 == 0 */
484 	((struct direct *)(dirbuf + prev))->d_reclen = DIRBLKSIZ - prev;
485 	(void) fwrite(dirbuf, (size_t)dirloc, 1, df);
486 	if (ferror(df))
487 		panic("%s: %s\n", dirfile, strerror(errno));
488 	g_seekpt = ftello64(df);
489 	dirloc = 0;
490 }
491 
492 static void
493 dcvt(struct odirect *odp, struct direct *ndp)
494 {
495 
496 	(void) bzero((char *)ndp, sizeof (*ndp));
497 	ndp->d_ino =  odp->d_ino;
498 	/* Note that odp->d_name may not be null-terminated */
499 	/* LINTED assertion always true */
500 	assert(sizeof (ndp->d_name) > sizeof (odp->d_name));
501 	(void) strncpy(ndp->d_name, odp->d_name, sizeof (odp->d_name));
502 	ndp->d_name[sizeof (odp->d_name)] = '\0';
503 	/* LINTED: strlen will fit into d_namlen */
504 	ndp->d_namlen = strlen(ndp->d_name);
505 
506 	/* LINTED sign extension ok in assert */
507 	assert(DIRSIZ(ndp) == (ulong_t)ndp->d_reclen);
508 	/* LINTED DIRSIZ always fits in ushort_t */
509 	ndp->d_reclen = (ushort_t)DIRSIZ(ndp);
510 }
511 
512 /*
513  * Initialize the directory file
514  */
515 static RST_DIR *
516 rst_initdirfile(char *name)
517 {
518 	RST_DIR *dp;
519 	int fd;
520 
521 	if ((fd = open(name, O_RDONLY | O_LARGEFILE)) == -1)
522 		return ((RST_DIR *)0);
523 	if ((dp = (RST_DIR *)malloc(sizeof (*dp))) == NULL) {
524 		(void) close(fd);
525 		return ((RST_DIR *)0);
526 	}
527 	dp->dd_fd = fd;
528 	dp->dd_loc = 0;
529 	dp->dd_refcnt = 1;
530 	return (dp);
531 }
532 
533 /*
534  * Simulate the opening of a directory
535  */
536 RST_DIR *
537 rst_opendir(char *name)
538 {
539 	struct inotab *itp;
540 	ino_t ino;
541 
542 	if ((ino = dirlookup(name)) > 0 &&
543 	    (itp = inotablookup(ino)) != NULL) {
544 		rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
545 		dirp->dd_refcnt++;
546 		return (dirp);
547 	}
548 	return ((RST_DIR *)0);
549 }
550 
551 /*
552  * Releases the hidden state created by rst_opendir().
553  * Specifically, the dirp it provided to the caller is malloc'd.
554  */
555 void
556 rst_closedir(RST_DIR *cdirp)
557 {
558 	if ((cdirp != NULL) && (--(cdirp->dd_refcnt) < 1))
559 		free(cdirp);
560 }
561 
562 /*
563  * return a pointer into a directory
564  */
565 static offset_t
566 rst_telldir(RST_DIR *tdirp)
567 {
568 	offset_t pos = llseek(tdirp->dd_fd, (offset_t)0, SEEK_CUR);
569 
570 	if (pos == (offset_t)-1) {
571 		perror("Could not determine position in directory file");
572 		done(1);
573 	}
574 
575 	return ((pos - tdirp->dd_size) + tdirp->dd_loc);
576 }
577 
578 /*
579  * Seek to an entry in a directory.
580  * Only values returned by ``rst_telldir'' should be passed to rst_seekdir.
581  * This routine handles many directories in a single file.
582  * It takes the base of the directory in the file, plus
583  * the desired seek offset into it.
584  */
585 static void
586 rst_seekdir(RST_DIR *sdirp, offset_t loc, offset_t base)
587 {
588 
589 	if (loc == rst_telldir(sdirp))
590 		return;
591 	loc -= base;
592 	if (loc < 0)
593 		(void) fprintf(stderr,
594 			gettext("bad seek pointer to rst_seekdir %d\n"), loc);
595 	(void) llseek(sdirp->dd_fd, base + (loc & ~(DIRBLKSIZ - 1)), 0);
596 	sdirp->dd_loc = loc & (DIRBLKSIZ - 1);
597 	if (sdirp->dd_loc != 0)
598 		sdirp->dd_size = read(sdirp->dd_fd, sdirp->dd_buf, DIRBLKSIZ);
599 }
600 
601 /*
602  * get next entry in a directory.
603  */
604 struct direct *
605 rst_readdir(RST_DIR *rdirp)
606 {
607 	struct direct *dp;
608 
609 	for (;;) {
610 		if (rdirp->dd_loc == 0) {
611 			rdirp->dd_size = read(rdirp->dd_fd, rdirp->dd_buf,
612 			    DIRBLKSIZ);
613 			if (rdirp->dd_size <= 0) {
614 				dprintf(stderr,
615 					gettext("error reading directory\n"));
616 				return ((struct direct *)0);
617 			}
618 		}
619 		if (rdirp->dd_loc >= rdirp->dd_size) {
620 			rdirp->dd_loc = 0;
621 			continue;
622 		}
623 		/*LINTED [rvalue will be aligned on int boundary]*/
624 		dp = (struct direct *)(rdirp->dd_buf + rdirp->dd_loc);
625 		if (dp->d_reclen == 0 ||
626 		    (long)dp->d_reclen > (DIRBLKSIZ + 1 - rdirp->dd_loc)) {
627 			dprintf(stderr,
628 			    gettext("corrupted directory: bad reclen %d\n"),
629 				dp->d_reclen);
630 			return ((struct direct *)0);
631 		}
632 		rdirp->dd_loc += dp->d_reclen;
633 		if (dp->d_ino == 0 && strcmp(dp->d_name, "/") != 0)
634 			continue;
635 		if ((ino_t)(dp->d_ino) >= maxino) {
636 			dprintf(stderr,
637 				gettext("corrupted directory: bad inum %lu\n"),
638 				dp->d_ino);
639 			continue;
640 		}
641 		return (dp);
642 	}
643 }
644 
645 /*
646  * Set the mode, owner, and times for all new or changed directories
647  */
648 void
649 #ifdef __STDC__
650 setdirmodes(void)
651 #else
652 setdirmodes()
653 #endif
654 {
655 	FILE *smf;
656 	struct entry *ep;
657 	char *cp, *metadata = NULL;
658 	size_t metasize = 0;
659 	int override = -1;
660 	int saverr;
661 	static int complained_chown = 0;
662 	static int complained_chmod = 0;
663 	int dfd;
664 
665 	vprintf(stdout, gettext("Set directory mode, owner, and times.\n"));
666 	/* XXX if modefile[0] == '#', shouldn't we just bail here? */
667 	/* XXX why isn't it set already? */
668 	INIT_MODEFILE();
669 	smf = fopen64(modefile, "r");
670 	if (smf == NULL) {
671 		perror("fopen");
672 		(void) fprintf(stderr,
673 			gettext("cannot open mode file %s\n"), modefile);
674 		(void) fprintf(stderr,
675 			gettext("directory mode, owner, and times not set\n"));
676 		return;
677 	}
678 	clearerr(smf);
679 	for (;;) {
680 		(void) fread((char *)&node, 1, sizeof (node), smf);
681 		if (feof(smf))
682 			break;
683 		ep = lookupino(node.ino);
684 		if (command == 'i' || command == 'x') {
685 			if (ep == NIL) {
686 				skipmetadata(smf, node.metasize);
687 				continue;
688 			}
689 			if (ep->e_flags & EXISTED) {
690 				if (override < 0) {
691 					if (reply(gettext(
692 				"Directories already exist, set modes anyway"))
693 					    == FAIL)
694 						override = 0;
695 					else
696 						override = 1;
697 				}
698 				if (override == 0) {
699 					/* LINTED: result fits into short */
700 					ep->e_flags &= ~NEW;
701 					skipmetadata(smf, node.metasize);
702 					continue;
703 				}
704 			}
705 			if (node.ino == ROOTINO &&
706 			    reply(gettext("set owner/mode for '.'")) == FAIL) {
707 				skipmetadata(smf, node.metasize);
708 				continue;
709 			}
710 		}
711 		if (ep == NIL) {
712 			panic(gettext("cannot find directory inode %d\n"),
713 				node.ino);
714 			skipmetadata(smf, node.metasize);
715 			continue;
716 		}
717 		cp = myname(ep);
718 		resolve(myname(ep), &dfd, &cp);
719 		if (dfd != AT_FDCWD) {
720 			if (fchdir(dfd) < 0) {
721 				saverr = errno;
722 				(void) fprintf(stderr,
723 			    gettext("Can not set attribute context: %s\n"),
724 					strerror(saverr));
725 				(void) close(dfd);
726 				continue;
727 			}
728 		}
729 		if (chmod(cp, node.mode) < 0 && !complained_chmod) {
730 			saverr = errno;
731 			(void) fprintf(stderr,
732 			gettext("Can not set directory permissions: %s\n"),
733 				strerror(saverr));
734 			complained_chmod = 1;
735 		}
736 		if (node.metasize != 0) {
737 			if (node.metasize > metasize)
738 				metadata = realloc(metadata,
739 				    metasize = node.metasize);
740 			if (metadata == NULL) {
741 				(void) fprintf(stderr,
742 					gettext("Cannot malloc metadata\n"));
743 				done(1);
744 			}
745 			(void) fread(metadata, 1, node.metasize, smf);
746 			metaproc(cp, metadata, node.metasize);
747 		}
748 
749 		/*
750 		 * BUG 4302943
751 		 * Since the ACLs must be set before fixing the ownership,
752 		 * chown should be called only after metaproc
753 		 */
754 		if (chown(cp, node.uid, node.gid) < 0 && !complained_chown) {
755 			saverr = errno;
756 			(void) fprintf(stderr,
757 			    gettext("Can not set directory ownership: %s\n"),
758 			    strerror(saverr));
759 			complained_chown = 1;
760 		}
761 		utime(cp, (struct utimbuf *)node.timep);
762 		/* LINTED: result fits into short */
763 		ep->e_flags &= ~NEW;
764 		if (dfd != AT_FDCWD) {
765 			fchdir(savepwd);
766 			(void) close(dfd);
767 		}
768 	}
769 	if (ferror(smf))
770 		panic(gettext("error setting directory modes\n"));
771 	if (metadata != NULL)
772 		(void) free(metadata);
773 	(void) fclose(smf);
774 }
775 
776 void
777 skipmetadata(FILE *f, size_t size)
778 {
779 	/* XXX should we bail if this doesn't work? */
780 	/* LINTED unsigned -> signed conversion ok here */
781 	(void) fseeko(f, (off_t)size, SEEK_CUR);
782 }
783 
784 /*
785  * Generate a literal copy of a directory.
786  */
787 int
788 genliteraldir(char *name, ino_t ino)
789 {
790 	struct inotab *itp;
791 	int ofile, dp;
792 	off64_t i;
793 	size_t size;
794 	char buf[BUFSIZ];
795 
796 	itp = inotablookup(ino);
797 	if (itp == NULL) {
798 		(void) fprintf(stderr,
799 		    gettext("Cannot find directory inode %d named %s\n"),
800 		    ino, name);
801 		return (FAIL);
802 	}
803 	if ((ofile = creat(name, 0666)) < 0) {
804 		(void) fprintf(stderr, "%s: ", name);
805 		(void) fflush(stderr);
806 		perror(gettext("cannot create file"));
807 		return (FAIL);
808 	}
809 	rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt);
810 	dp = dup(dirp->dd_fd);
811 	if (dp < 0) {
812 		perror(gettext("dup(2) failed"));
813 		(void) close(ofile);
814 		(void) unlink(name);
815 		return (FAIL);
816 	}
817 	for (i = itp->t_size; i != 0; i -= size) {
818 		/* LINTED cast is safe due to comparison */
819 		size = i < BUFSIZ ? (size_t)i : BUFSIZ;
820 		/* XXX instead of done(), clean up and return FAIL? */
821 		if (read(dp, buf, size) == -1) {
822 			(void) fprintf(stderr, gettext(
823 				"read error extracting inode %d, name %s\n"),
824 				curfile.ino, curfile.name);
825 			perror("read");
826 			done(1);
827 		}
828 		if (write(ofile, buf, size) == -1) {
829 			(void) fprintf(stderr, gettext(
830 				"write error extracting inode %d, name %s\n"),
831 				curfile.ino, curfile.name);
832 			perror("write");
833 			done(1);
834 		}
835 	}
836 	(void) close(dp);
837 	(void) close(ofile);
838 	return (GOOD);
839 }
840 
841 /*
842  * Determine the type of an inode
843  */
844 int
845 inodetype(ino_t ino)
846 {
847 	struct inotab *itp;
848 
849 	itp = inotablookup(ino);
850 	if (itp == NULL)
851 		return (LEAF);
852 	return (NODE);
853 }
854 
855 /*
856  * Allocate and initialize a directory inode entry.
857  * If requested, save its pertinent mode, owner, and time info.
858  */
859 static struct inotab *
860 allocinotab(ino_t ino, struct dinode *dip, off64_t seekpt)
861 {
862 	struct inotab	*itp;
863 
864 	itp = (struct inotab *)calloc(1, sizeof (*itp));
865 	if (itp == 0) {
866 		(void) fprintf(stderr,
867 		    gettext("no memory for directory table\n"));
868 		done(1);
869 	}
870 	itp->t_next = inotab[INOHASH(ino)];
871 	inotab[INOHASH(ino)] = itp;
872 	itp->t_ino = ino;
873 	itp->t_seekpt = seekpt;
874 	if ((dip->di_mode & IFMT) == IFATTRDIR) {
875 		itp->t_xattr = xattrlist;
876 		xattrlist = itp;
877 	}
878 	if (mf == NULL)
879 		return (itp);
880 	node.ino = ino;
881 	node.timep[0] = dip->di_atime;
882 	node.timep[1] = dip->di_mtime;
883 	node.mode = dip->di_mode;
884 	node.uid =
885 		dip->di_suid == UID_LONG ? dip->di_uid : (uid_t)dip->di_suid;
886 	node.gid =
887 		dip->di_sgid == GID_LONG ? dip->di_gid : (gid_t)dip->di_sgid;
888 	return (itp);
889 }
890 
891 void
892 nodeflush()
893 {
894 	char *metadata;
895 
896 	if (mf == NULL) {
897 		(void) fprintf(stderr, gettext(
898 		    "Inconsistency detected: modefile pointer is NULL\n"));
899 		done(1);
900 	}
901 	metaget(&metadata, &(node.metasize));
902 	(void) fwrite((char *)&node, 1, sizeof (node), mf);
903 	if (node.metasize != 0)
904 		(void) fwrite(metadata, 1, node.metasize, mf);
905 	if (ferror(mf))
906 		panic("%s: %s\n", modefile, strerror(errno));
907 }
908 
909 /*
910  * Look up an inode in the table of directories
911  */
912 static struct inotab *
913 inotablookup(ino_t ino)
914 {
915 	struct inotab *itp;
916 
917 	for (itp = inotab[INOHASH(ino)]; itp != NULL; itp = itp->t_next)
918 		if (itp->t_ino == ino)
919 			return (itp);
920 	return ((struct inotab *)0);
921 }
922 
923 /*
924  * Clean up and exit
925  */
926 void
927 done(int exitcode)
928 {
929 	closemt(ALLOW_OFFLINE);		/* don't force offline on exit */
930 	if (modefile[0] != '#')
931 		(void) unlink(modefile);
932 	if (dirfile[0] != '#')
933 		(void) unlink(dirfile);
934 	exit(exitcode);
935 }
936