xref: /illumos-gate/usr/src/cmd/backup/restore/utilities.c (revision 3d393ee6c37fa10ac512ed6d36109ad616dc7c1a)
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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
16 
17 #include "restore.h"
18 #include <ctype.h>
19 #include <errno.h>
20 #include <syslog.h>
21 #include <limits.h>
22 /* LINTED: this file really is necessary */
23 #include <euc.h>
24 #include <widec.h>
25 
26 /*
27  * Insure that all the components of a pathname exist. Note that
28  * lookupname() and addentry() both expect complex names as
29  * input arguments, so a double NULL needs to be added to each name.
30  */
31 void
32 pathcheck(char *name)
33 {
34 	char *cp, save;
35 	struct entry *ep;
36 	char *start;
37 
38 	start = strchr(name, '/');
39 	if (start == 0)
40 		return;
41 	for (cp = start; *cp != '\0'; cp++) {
42 		if (*cp != '/')
43 			continue;
44 		*cp = '\0';
45 		save = *(cp+1);
46 		*(cp+1) = '\0';
47 		ep = lookupname(name);
48 		if (ep == NIL) {
49 			ep = addentry(name, psearch(name), NODE);
50 			newnode(ep);
51 		}
52 		/* LINTED: result fits in a short */
53 		ep->e_flags |= NEW|KEEP;
54 		*cp = '/';
55 		*(cp+1) = save;
56 	}
57 }
58 
59 /*
60  * Change a name to a unique temporary name.
61  */
62 void
63 mktempname(struct entry *ep)
64 {
65 	char *newname;
66 
67 	if (ep->e_flags & TMPNAME)
68 		badentry(ep, gettext("mktempname: called with TMPNAME"));
69 	/* LINTED: result fits in a short */
70 	ep->e_flags |= TMPNAME;
71 	newname = savename(gentempname(ep));
72 	renameit(myname(ep), newname);
73 	freename(ep->e_name);
74 	ep->e_name = newname;
75 	/* LINTED: savename guarantees strlen will fit */
76 	ep->e_namlen = strlen(ep->e_name);
77 }
78 
79 /*
80  * Generate a temporary name for an entry.
81  */
82 char *
83 gentempname(struct entry *ep)
84 {
85 	static char name[MAXPATHLEN];
86 	struct entry *np;
87 	long i = 0;
88 
89 	for (np = lookupino(ep->e_ino); np != NIL && np != ep; np = np->e_links)
90 		i++;
91 	if (np == NIL)
92 		badentry(ep, gettext("not on ino list"));
93 	(void) snprintf(name, sizeof (name), "%s%ld%lu", TMPHDR, i, ep->e_ino);
94 	return (name);
95 }
96 
97 /*
98  * Rename a file or directory.
99  */
100 void
101 renameit(char *fp, char *tp)
102 {
103 	int fromfd, tofd;
104 	char *from, *to;
105 	char tobuf[MAXPATHLEN];
106 	char *pathend;
107 
108 	resolve(fp, &fromfd, &from);
109 	/*
110 	 * The to pointer argument is assumed to be either a fully
111 	 * specified path (starting with "./") or a simple temporary
112 	 * file name (starting with TMPHDR).  If passed a simple temp
113 	 * file name, we need to set up the descriptors explicitly.
114 	 */
115 	if (strncmp(tp, TMPHDR, sizeof (TMPHDR) - 1) == 0) {
116 		tofd = fromfd;
117 		if ((pathend = strrchr(from, '/')) != NULL) {
118 			strncpy(tobuf, from, pathend - from + 1);
119 			tobuf[pathend - from + 1] = NULL;
120 			strlcat(tobuf, tp, sizeof (tobuf));
121 			to = tobuf;
122 		} else {
123 			to = tp;
124 		}
125 	} else
126 		resolve(tp, &tofd, &to);
127 	if (renameat(fromfd, from, tofd, to) < 0) {
128 		int saverr = errno;
129 		(void) fprintf(stderr,
130 		    gettext("Warning: cannot rename %s to %s: %s\n"),
131 		    from, to, strerror(saverr));
132 		(void) fflush(stderr);
133 	} else {
134 		vprintf(stdout, gettext("rename %s to %s\n"), from, to);
135 	}
136 	if (fromfd != AT_FDCWD) (void) close(fromfd);
137 	if (tofd != AT_FDCWD) (void) close(tofd);
138 }
139 
140 /*
141  * Create a new node (directory). Note that, because we have no
142  * mkdirat() function, fchdir() must be used set up the appropriate
143  * name space context prior to the call to mkdir() if we are
144  * operating in attribute space.
145  */
146 void
147 newnode(struct entry *np)
148 {
149 	char *cp;
150 	int dfd;
151 
152 	if (np->e_type != NODE)
153 		badentry(np, gettext("newnode: not a node"));
154 	resolve(myname(np), &dfd, &cp);
155 	if (dfd != AT_FDCWD) {
156 		if (fchdir(dfd) < 0) {
157 			int saverr = errno;
158 			(void) fprintf(stderr,
159 				gettext("Warning: cannot create %s: %s"),
160 				cp, strerror(saverr));
161 			(void) fflush(stderr);
162 			(void) close(dfd);
163 			return;
164 		}
165 	}
166 	if (mkdir(cp, 0777) < 0) {
167 		int saverr = errno;
168 		/* LINTED: result fits in a short */
169 		np->e_flags |= EXISTED;
170 		(void) fprintf(stderr, gettext("Warning: "));
171 		(void) fflush(stderr);
172 		(void) fprintf(stderr, "%s: %s\n", cp, strerror(saverr));
173 	} else {
174 		vprintf(stdout, gettext("Make node %s\n"), cp);
175 	}
176 	if (dfd != AT_FDCWD) {
177 		fchdir(savepwd);
178 		(void) close(dfd);
179 	}
180 }
181 
182 /*
183  * Remove an old node (directory). See comment above on newnode()
184  * for explanation of fchdir() use below.
185  */
186 void
187 removenode(struct entry *ep)
188 {
189 	char *cp;
190 	int dfd;
191 
192 	if (ep->e_type != NODE)
193 		badentry(ep, gettext("removenode: not a node"));
194 	if (ep->e_entries != NIL)
195 		badentry(ep, gettext("removenode: non-empty directory"));
196 	/* LINTED: result fits in a short */
197 	ep->e_flags |= REMOVED;
198 	/* LINTED: result fits in a short */
199 	ep->e_flags &= ~TMPNAME;
200 	resolve(myname(ep), &dfd, &cp);
201 	if (dfd != AT_FDCWD) {
202 		if (fchdir(dfd) < 0) {
203 			int saverr = errno;
204 			(void) fprintf(stderr,
205 				gettext("Warning: cannot remove %s: %s"),
206 				cp, strerror(saverr));
207 			(void) fflush(stderr);
208 			(void) close(dfd);
209 			return;
210 		}
211 	}
212 	if (rmdir(cp) < 0) {	/* NOTE: could use unlinkat (..,REMOVEDIR) */
213 		int saverr = errno;
214 		(void) fprintf(stderr, gettext("Warning: %s: %s\n"),
215 			cp, strerror(saverr));
216 		(void) fflush(stderr);
217 	} else {
218 		vprintf(stdout, gettext("Remove node %s\n"), cp);
219 	}
220 	if (dfd != AT_FDCWD) {
221 		(void) fchdir(savepwd);
222 		(void) close(dfd);
223 	}
224 }
225 
226 /*
227  * Remove a leaf.
228  */
229 void
230 removeleaf(struct entry *ep)
231 {
232 	char *cp;
233 	int dfd;
234 
235 	if (ep->e_type != LEAF)
236 		badentry(ep, gettext("removeleaf: not a leaf"));
237 	/* LINTED: result fits in a short */
238 	ep->e_flags |= REMOVED;
239 	/* LINTED: result fits in a short */
240 	ep->e_flags &= ~TMPNAME;
241 	resolve(myname(ep), &dfd, &cp);
242 	if (unlinkat(dfd, cp, 0) < 0) {
243 		int saverr = errno;
244 		(void) fprintf(stderr, gettext("Warning: %s: %s\n"),
245 			cp, strerror(saverr));
246 		(void) fflush(stderr);
247 	} else {
248 		vprintf(stdout, gettext("Remove leaf %s\n"), cp);
249 	}
250 	if (dfd != AT_FDCWD)
251 		(void) close(dfd);
252 }
253 
254 /*
255  * Create a link.
256  *	This function assumes that the context has already been set
257  *	for the link file to be created (i.e., we have "fchdir-ed"
258  *	into attribute space already if this is an attribute link).
259  */
260 int
261 lf_linkit(char *existing, char *new, int type)
262 {
263 	char linkbuf[MAXPATHLEN];
264 	struct stat64 s1[1], s2[1];
265 	char *name;
266 	int dfd, l, result;
267 
268 	resolve(existing, &dfd, &name);
269 	if (dfd == -1) {
270 		(void) fprintf(stderr, gettext(
271 			"Warning: cannot restore %s link %s->%s\n"),
272 			(type == SYMLINK ? "symbolic" : "hard"), new, existing);
273 		result = FAIL;
274 		goto out;
275 	}
276 	if (type == SYMLINK) {
277 		if (symlink(name, new) < 0) {
278 			/* No trailing \0 from readlink(2) */
279 			if (((l = readlink(new, linkbuf, sizeof (linkbuf)))
280 			    > 0) &&
281 			    (l == strlen(name)) &&
282 			    (strncmp(linkbuf, name, l) == 0)) {
283 				vprintf(stdout,
284 				    gettext("Symbolic link %s->%s ok\n"),
285 				    new, name);
286 				result = GOOD;
287 				goto out;
288 			} else {
289 				int saverr = errno;
290 				(void) fprintf(stderr, gettext(
291 			    "Warning: cannot create symbolic link %s->%s: %s"),
292 				    new, name, strerror(saverr));
293 				(void) fflush(stderr);
294 				result = FAIL;
295 				goto out;
296 			}
297 		}
298 	} else if (type == HARDLINK) {
299 		if (link(name, new) < 0) {
300 			int saverr = errno;
301 			if ((stat64(name, s1) == 0) &&
302 			    (stat64(new, s2) == 0) &&
303 			    (s1->st_dev == s2->st_dev) &&
304 			    (s1->st_ino == s2->st_ino)) {
305 				vprintf(stdout,
306 				    gettext("Hard link %s->%s ok\n"),
307 				    new, name);
308 				result = GOOD;
309 				goto out;
310 			} else {
311 				(void) fprintf(stderr, gettext(
312 			    "Warning: cannot create hard link %s->%s: %s\n"),
313 				    new, name, strerror(saverr));
314 				(void) fflush(stderr);
315 				result = FAIL;
316 				goto out;
317 			}
318 		}
319 	} else {
320 		panic(gettext("%s: unknown type %d\n"), "linkit", type);
321 		result = FAIL;
322 		goto out;
323 	}
324 	result = GOOD;
325 	if (type == SYMLINK)
326 		vprintf(stdout, gettext("Create symbolic link %s->%s\n"),
327 		    new, name);
328 	else
329 		vprintf(stdout, gettext("Create hard link %s->%s\n"),
330 		    new, name);
331 out:
332 	if (dfd != AT_FDCWD) {
333 		(void) close(dfd);
334 	}
335 	return (result);
336 }
337 
338 /*
339  * Find lowest-numbered inode (above "start") that needs to be extracted.
340  * Caller knows that a return value of maxino means there's nothing left.
341  */
342 ino_t
343 lowerbnd(ino_t start)
344 {
345 	struct entry *ep;
346 
347 	for (; start < maxino; start++) {
348 		ep = lookupino(start);
349 		if (ep == NIL || ep->e_type == NODE)
350 			continue;
351 		if (ep->e_flags & (NEW|EXTRACT))
352 			return (start);
353 	}
354 	return (start);
355 }
356 
357 /*
358  * Find highest-numbered inode (below "start") that needs to be extracted.
359  */
360 ino_t
361 upperbnd(ino_t start)
362 {
363 	struct entry *ep;
364 
365 	for (; start > ROOTINO; start--) {
366 		ep = lookupino(start);
367 		if (ep == NIL || ep->e_type == NODE)
368 			continue;
369 		if (ep->e_flags & (NEW|EXTRACT))
370 			return (start);
371 	}
372 	return (start);
373 }
374 
375 /*
376  * report on a badly formed entry
377  */
378 void
379 badentry(struct entry *ep, char *msg)
380 {
381 
382 	(void) fprintf(stderr, gettext("bad entry: %s\n"), msg);
383 	(void) fprintf(stderr, gettext("name: %s\n"), myname(ep));
384 	(void) fprintf(stderr, gettext("parent name %s\n"),
385 		myname(ep->e_parent));
386 	if (ep->e_sibling != NIL)
387 		(void) fprintf(stderr, gettext("sibling name: %s\n"),
388 		    myname(ep->e_sibling));
389 	if (ep->e_entries != NIL)
390 		(void) fprintf(stderr, gettext("next entry name: %s\n"),
391 		    myname(ep->e_entries));
392 	if (ep->e_links != NIL)
393 		(void) fprintf(stderr, gettext("next link name: %s\n"),
394 		    myname(ep->e_links));
395 	if (ep->e_xattrs != NIL)
396 		(void) fprintf(stderr, gettext("attribute root name: %s\n"),
397 		    myname(ep->e_xattrs));
398 	if (ep->e_next != NIL)
399 		(void) fprintf(stderr, gettext("next hashchain name: %s\n"),
400 		    myname(ep->e_next));
401 	(void) fprintf(stderr, gettext("entry type: %s\n"),
402 	    ep->e_type == NODE ? gettext("NODE") : gettext("LEAF"));
403 	(void) fprintf(stderr, gettext("inode number: %lu\n"), ep->e_ino);
404 	panic(gettext("flags: %s\n"), flagvalues(ep));
405 	/* Our callers are expected to handle our returning. */
406 }
407 
408 /*
409  * Construct a string indicating the active flag bits of an entry.
410  */
411 char *
412 flagvalues(struct entry *ep)
413 {
414 	static char flagbuf[BUFSIZ];
415 
416 	(void) strlcpy(flagbuf, gettext("|NIL"), sizeof (flagbuf));
417 	flagbuf[0] = '\0';
418 	if (ep->e_flags & REMOVED)
419 		(void) strlcat(flagbuf, gettext("|REMOVED"), sizeof (flagbuf));
420 	if (ep->e_flags & TMPNAME)
421 		(void) strlcat(flagbuf, gettext("|TMPNAME"), sizeof (flagbuf));
422 	if (ep->e_flags & EXTRACT)
423 		(void) strlcat(flagbuf, gettext("|EXTRACT"), sizeof (flagbuf));
424 	if (ep->e_flags & NEW)
425 		(void) strlcat(flagbuf, gettext("|NEW"), sizeof (flagbuf));
426 	if (ep->e_flags & KEEP)
427 		(void) strlcat(flagbuf, gettext("|KEEP"), sizeof (flagbuf));
428 	if (ep->e_flags & EXISTED)
429 		(void) strlcat(flagbuf, gettext("|EXISTED"), sizeof (flagbuf));
430 	if (ep->e_flags & XATTR)
431 		(void) strlcat(flagbuf, gettext("|XATTR"), sizeof (flagbuf));
432 	if (ep->e_flags & XATTRROOT)
433 		(void) strlcat(flagbuf, gettext("|XATTRROOT"),
434 						sizeof (flagbuf));
435 	return (&flagbuf[1]);
436 }
437 
438 /*
439  * Check to see if a name is on a dump tape.
440  */
441 ino_t
442 dirlookup(char *name)
443 {
444 	ino_t ino;
445 
446 	ino = psearch(name);
447 	if (ino == 0 || BIT(ino, dumpmap) == 0)
448 		(void) fprintf(stderr, gettext("%s is not on volume\n"), name);
449 	return (ino);
450 }
451 
452 /*
453  * Elicit a reply.
454  */
455 int
456 reply(char *question)
457 {
458 	char *yesorno = gettext("yn"); /* must be two characters, "yes" first */
459 	int c;
460 
461 	do	{
462 		(void) fprintf(stderr, "%s? [%s] ", question, yesorno);
463 		(void) fflush(stderr);
464 		c = getc(terminal);
465 		while (c != '\n' && getc(terminal) != '\n') {
466 			if (ferror(terminal)) {
467 				(void) fprintf(stderr, gettext(
468 					"Error reading response\n"));
469 				(void) fflush(stderr);
470 				return (FAIL);
471 			}
472 			if (feof(terminal))
473 				return (FAIL);
474 		}
475 		if (isupper(c))
476 			c = tolower(c);
477 	} while (c != yesorno[0] && c != yesorno[1]);
478 	if (c == yesorno[0])
479 		return (GOOD);
480 	return (FAIL);
481 }
482 
483 /*
484  * handle unexpected inconsistencies
485  */
486 /*
487  * Note that a panic w/ EOF on the tty means all panics will return...
488  */
489 #ifdef __STDC__
490 #include <stdarg.h>
491 
492 /* VARARGS1 */
493 void
494 panic(const char *msg, ...)
495 {
496 	va_list	args;
497 
498 	va_start(args, msg);
499 	(void) vfprintf(stderr, msg, args);
500 	va_end(args);
501 	if (reply(gettext("abort")) == GOOD) {
502 		if (reply(gettext("dump core")) == GOOD)
503 			abort();
504 		done(1);
505 	}
506 }
507 #else
508 #include <varargs.h>
509 
510 /* VARARGS1 */
511 void
512 panic(va_dcl)
513 {
514 	va_list	args;
515 	char	*msg;
516 
517 	va_start(args);
518 	msg = va_arg(args, char *);
519 	(void) vfprintf(stderr, msg, args);
520 	va_end(args);
521 	if (reply(gettext("abort")) == GOOD) {
522 		if (reply(gettext("dump core")) == GOOD)
523 			abort();
524 		done(1);
525 	}
526 #endif
527 
528 /*
529  * Locale-specific version of ctime
530  */
531 char *
532 lctime(time_t *tp)
533 {
534 	static char buf[256];
535 	struct tm *tm;
536 
537 	tm = localtime(tp);
538 	(void) strftime(buf, sizeof (buf), "%c\n", tm);
539 	return (buf);
540 }
541 
542 static int
543 statcmp(const struct stat *left, const struct stat *right)
544 {
545 	int result = 1;
546 
547 	if ((left->st_dev == right->st_dev) &&
548 	    (left->st_ino == right->st_ino) &&
549 	    (left->st_mode == right->st_mode) &&
550 	    (left->st_nlink == right->st_nlink) &&
551 	    (left->st_uid == right->st_uid) &&
552 	    (left->st_gid == right->st_gid) &&
553 	    (left->st_rdev == right->st_rdev) &&
554 	    (left->st_ctim.tv_sec == right->st_ctim.tv_sec) &&
555 	    (left->st_ctim.tv_nsec == right->st_ctim.tv_nsec) &&
556 	    (left->st_mtim.tv_sec == right->st_mtim.tv_sec) &&
557 	    (left->st_mtim.tv_nsec == right->st_mtim.tv_nsec) &&
558 	    (left->st_blksize == right->st_blksize) &&
559 	    (left->st_blocks == right->st_blocks)) {
560 		result = 0;
561 	}
562 
563 	return (result);
564 }
565 
566 /*
567  * Safely open a file.
568  */
569 int
570 safe_open(int dfd, const char *filename, int mode, int perms)
571 {
572 	static int init_syslog = 1;
573 	int fd;
574 	int working_mode;
575 	int saverr;
576 	char *errtext;
577 	struct stat pre_stat, pre_lstat;
578 	struct stat post_stat, post_lstat;
579 
580 	if (init_syslog) {
581 		openlog(progname, LOG_CONS, LOG_DAEMON);
582 		init_syslog = 0;
583 	}
584 
585 	/*
586 	 * Don't want to be spoofed into trashing something we
587 	 * shouldn't, thus the following rigamarole.  If it doesn't
588 	 * exist, we create it and proceed.  Otherwise, require that
589 	 * what's there be a real file with no extraneous links and
590 	 * owned by whoever ran us.
591 	 *
592 	 * The silliness with using both lstat() and fstat() is to avoid
593 	 * race-condition games with someone replacing the file with a
594 	 * symlink after we've opened it.  If there was an flstat(),
595 	 * we wouldn't need the fstat().
596 	 *
597 	 * The initial open with the hard-coded flags is ok even if we
598 	 * are intending to open only for reading.  If it succeeds,
599 	 * then the file did not exist, and we'll synthesize an appropriate
600 	 * complaint below.  Otherwise, it does exist, so we won't be
601 	 * truncating it with the open.
602 	 */
603 	if ((fd = openat(dfd, filename,
604 	    O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_LARGEFILE, perms)) < 0) {
605 		if (errno == EEXIST) {
606 			if (fstatat(dfd, filename, &pre_lstat,
607 						AT_SYMLINK_NOFOLLOW) < 0) {
608 				saverr = errno;
609 				(void) close(fd);
610 				errno = saverr;
611 				return (-1);
612 			}
613 
614 			if (fstatat(dfd, filename, &pre_stat, 0) < 0) {
615 				saverr = errno;
616 				(void) close(fd);
617 				errno = saverr;
618 				return (-1);
619 			}
620 
621 			working_mode = mode & (O_WRONLY|O_RDWR|O_RDONLY);
622 			working_mode |= O_LARGEFILE;
623 
624 			if ((fd = openat(dfd, filename, working_mode)) < 0) {
625 				if (errno == ENOENT) {
626 					errtext = gettext(
627 "Unexpected condition detected: %s used to exist, but doesn't any longer\n");
628 					(void) fprintf(stderr, errtext,
629 					    filename);
630 					syslog(LOG_WARNING, errtext, filename);
631 					errno = ENOENT;
632 				}
633 				return (-1);
634 			}
635 
636 			if (fstatat(fd, NULL, &post_lstat,
637 						AT_SYMLINK_NOFOLLOW) < 0) {
638 				saverr = errno;
639 				(void) close(fd);
640 				errno = saverr;
641 				return (-1);
642 			}
643 
644 			if (fstatat(fd, NULL, &post_stat, 0) < 0) {
645 				saverr = errno;
646 				(void) close(fd);
647 				errno = saverr;
648 				return (-1);
649 			}
650 
651 			if (statcmp(&pre_lstat, &post_lstat) != 0) {
652 				errtext = gettext(
653 "Unexpected condition detected: %s's lstat(2) information changed\n");
654 				(void) fprintf(stderr, errtext, filename);
655 				syslog(LOG_WARNING, errtext, filename);
656 				errno = EPERM;
657 				return (-1);
658 			}
659 
660 			if (statcmp(&pre_stat, &post_stat) != 0) {
661 				errtext = gettext(
662 "Unexpected condition detected: %s's stat(2) information changed\n");
663 				(void) fprintf(stderr, errtext, filename);
664 				syslog(LOG_WARNING, errtext, filename);
665 				errno = EPERM;
666 				return (-1);
667 			}
668 
669 			/*
670 			 * If inode, device, or type are wrong, bail out.
671 			 */
672 			if ((!S_ISREG(post_lstat.st_mode) ||
673 			    (post_stat.st_ino != post_lstat.st_ino) ||
674 			    (post_stat.st_dev != post_lstat.st_dev))) {
675 				errtext = gettext(
676 	    "Unexpected condition detected: %s is not a regular file\n");
677 				(void) fprintf(stderr, errtext, filename);
678 				syslog(LOG_WARNING, errtext, filename);
679 				(void) close(fd);
680 				errno = EPERM;
681 				return (-1);
682 			}
683 
684 			/*
685 			 * Bad link count implies someone's linked our
686 			 * target to something else, which we probably
687 			 * shouldn't step on.
688 			 */
689 			if (post_lstat.st_nlink != 1) {
690 				errtext = gettext(
691 	    "Unexpected condition detected: %s must have exactly one link\n");
692 				(void) fprintf(stderr, errtext, filename);
693 				syslog(LOG_WARNING, errtext, filename);
694 				(void) close(fd);
695 				errno = EPERM;
696 				return (-1);
697 			}
698 			/*
699 			 * Root might make a file, but non-root might
700 			 * need to open it.  If the permissions let us
701 			 * get this far, then let it through.
702 			 */
703 			if (post_lstat.st_uid != getuid() &&
704 			    post_lstat.st_uid != 0) {
705 				errtext = gettext(
706 "Unsupported condition detected: %s must be owned by uid %ld or 0\n");
707 				(void) fprintf(stderr, errtext, filename,
708 				    (long)getuid());
709 				syslog(LOG_WARNING, errtext, filename,
710 				    (long)getuid());
711 				(void) close(fd);
712 				errno = EPERM;
713 				return (-1);
714 			}
715 			if (mode & (O_WRONLY|O_TRUNC)) {
716 				if (ftruncate(fd, (off_t)0) < 0) {
717 					(void) fprintf(stderr,
718 					    "ftruncate(%s): %s\n",
719 					    filename, strerror(errno));
720 					(void) close(fd);
721 					return (-1);
722 				}
723 			}
724 		} else {
725 			/*
726 			 * Didn't exist, but couldn't open it.
727 			 */
728 			return (-1);
729 		}
730 	} else {
731 		/*
732 		 * If truncating open succeeded for a read-only open,
733 		 * bail out, as we really shouldn't have succeeded.
734 		 */
735 		if (mode & O_RDONLY) {
736 			/* Undo the O_CREAT */
737 			(void) unlinkat(dfd, filename, 0);
738 			(void) fprintf(stderr, "open(%s): %s\n",
739 			    filename, strerror(ENOENT));
740 			(void) close(fd);
741 			errno = ENOENT;
742 			return (-1);
743 		}
744 	}
745 
746 	return (fd);
747 }
748 
749 /*
750  * STDIO version of safe_open.  Equivalent to fopen64(...).
751  */
752 FILE *
753 safe_fopen(const char *filename, const char *smode, int perms)
754 {
755 	int fd;
756 	int bmode;
757 
758 	/*
759 	 * accepts only modes  "r", "r+", and "w"
760 	 */
761 	if (smode[0] == 'r') {
762 		if (smode[1] == '\0') {
763 			bmode = O_RDONLY;
764 		} else if ((smode[1] == '+') && (smode[2] == '\0')) {
765 			bmode = O_RDWR;
766 		}
767 	} else if ((smode[0] == 'w') && (smode[1] == '\0')) {
768 		bmode = O_WRONLY;
769 	} else {
770 		(void) fprintf(stderr,
771 		    gettext("internal error: safe_fopen: invalid mode `%s'\n"),
772 		    smode);
773 		return (NULL);
774 	}
775 
776 	fd = safe_open(AT_FDCWD, filename, bmode, perms);
777 
778 	/*
779 	 * caller is expected to report error.
780 	 */
781 	if (fd >= 0)
782 		return (fdopen(fd, smode));
783 
784 	return ((FILE *)NULL);
785 }
786 
787 /*
788  * Read the contents of a directory.
789  */
790 int
791 mkentry(char *name, ino_t ino, struct arglist *ap)
792 {
793 	struct afile *fp;
794 
795 	if (ap->base == NULL) {
796 		ap->nent = 20;
797 		ap->base = (struct afile *)calloc((unsigned)ap->nent,
798 			sizeof (*(ap->base)));
799 		if (ap->base == NULL) {
800 			(void) fprintf(stderr,
801 				gettext("%s: out of memory\n"), ap->cmd);
802 			return (FAIL);
803 		}
804 	}
805 	if (ap->head == NULL)
806 		ap->head = ap->last = ap->base;
807 	fp = ap->last;
808 	fp->fnum = ino;
809 	fp->fname = savename(name);
810 	fp++;
811 	if (fp == ap->head + ap->nent) {
812 		ap->base = (struct afile *)realloc((char *)ap->base,
813 		    (size_t)(2 * ap->nent * (size_t)sizeof (*(ap->base))));
814 		if (ap->base == NULL) {
815 			(void) fprintf(stderr,
816 				gettext("%s: out of memory\n"), ap->cmd);
817 			return (FAIL);
818 		}
819 		ap->head = ap->base;
820 		fp = ap->head + ap->nent;
821 		ap->nent *= 2;
822 	}
823 	ap->last = fp;
824 	return (GOOD);
825 }
826 
827 #ifdef __STDC__
828 static int gmatch(wchar_t *, wchar_t *);
829 static int addg(struct direct *, char *, char *, struct arglist *);
830 #else
831 static int gmatch();
832 static int addg();
833 #endif
834 
835 /*
836  * XXX  This value is ASCII (but not language) dependent.  In
837  * ASCII, it is the DEL character (unlikely to appear in paths).
838  * If you are compiling on an EBCDIC-based machine, re-define
839  * this (0x7f is '"') to be something like 0x7 (DEL).  It's
840  * either this hack or re-write the expand() algorithm...
841  */
842 #define	DELIMCHAR	((char)0x7f)
843 
844 /*
845  * Expand a file name.
846  * "as" is the pattern to expand.
847  * "rflg" non-zero indicates that we're recursing.
848  * "ap" is where to put the results of the expansion.
849  *
850  * Our caller guarantees that "as" is at least the string ".".
851  */
852 int
853 expand(char *as, int rflg, struct arglist *ap)
854 {
855 	int		count, size;
856 	char		dir = 0;
857 	char		*rescan = 0;
858 	RST_DIR		*dirp;
859 	char		*s, *cs;
860 	int		sindex, rindexa, lindex;
861 	struct direct	*dp;
862 	char		slash;
863 	char		*rs;
864 	char		c;
865 	wchar_t 	w_fname[PATH_MAX+1];
866 	wchar_t		w_pname[PATH_MAX+1];
867 
868 	/*
869 	 * check for meta chars
870 	 */
871 	s = cs = as;
872 	slash = 0;
873 	while (*cs != '*' && *cs != '?' && *cs != '[') {
874 		if (*cs++ == 0) {
875 			if (rflg && slash)
876 				break;
877 			else
878 				return (0);
879 		} else if (*cs == '/') {
880 			slash++;
881 		}
882 	}
883 	for (;;) {
884 		if (cs == s) {
885 			s = "";
886 			break;
887 		} else if (*--cs == '/') {
888 			*cs = 0;
889 			if (s == cs)
890 				s = "/";
891 			break;
892 		}
893 	}
894 	if ((dirp = rst_opendir(s)) != NULL)
895 		dir++;
896 	count = 0;
897 	if (*cs == 0)
898 		*cs++ = DELIMCHAR;
899 	if (dir) {
900 		/*
901 		 * check for rescan
902 		 */
903 		rs = cs;
904 		do {
905 			if (*rs == '/') {
906 				rescan = rs;
907 				*rs = 0;
908 			}
909 		} while (*rs++);
910 		/* LINTED: result fits into an int */
911 		sindex = (int)(ap->last - ap->head);
912 		(void) mbstowcs(w_pname, cs, PATH_MAX);
913 		w_pname[PATH_MAX - 1] = 0;
914 		while ((dp = rst_readdir(dirp)) != NULL && dp->d_ino != 0) {
915 			if (!dflag && BIT(dp->d_ino, dumpmap) == 0)
916 				continue;
917 			if ((*dp->d_name == '.' && *cs != '.'))
918 				continue;
919 			(void) mbstowcs(w_fname, dp->d_name, PATH_MAX);
920 			w_fname[PATH_MAX - 1] = 0;
921 			if (gmatch(w_fname, w_pname)) {
922 				if (addg(dp, s, rescan, ap) < 0) {
923 					rst_closedir(dirp);
924 					return (-1);
925 				}
926 				count++;
927 			}
928 		}
929 		if (rescan) {
930 			rindexa = sindex;
931 			/* LINTED: result fits into an int */
932 			lindex = (int)(ap->last - ap->head);
933 			if (count) {
934 				count = 0;
935 				while (rindexa < lindex) {
936 					size = expand(ap->head[rindexa].fname,
937 					    1, ap);
938 					if (size < 0) {
939 						rst_closedir(dirp);
940 						return (size);
941 					}
942 					count += size;
943 					rindexa++;
944 				}
945 			}
946 			/* LINTED: lint is confused about pointer size/type */
947 			bcopy((void *)(&ap->head[lindex]),
948 			    (void *)(&ap->head[sindex]),
949 			    (size_t)((ap->last - &ap->head[rindexa])) *
950 			    sizeof (*ap->head));
951 			ap->last -= lindex - sindex;
952 			*rescan = '/';
953 		}
954 		rst_closedir(dirp);
955 	}
956 	s = as;
957 	while ((c = *s) != '\0')
958 		*s++ = (c != DELIMCHAR ? c : '/');
959 
960 	return (count);
961 }
962 
963 /*
964  * Check for a name match
965  */
966 static int
967 gmatch(wchar_t *s, wchar_t *p)
968 {
969 	long	scc;	/* source character to text */
970 	wchar_t	c;	/* pattern character to match */
971 	char	ok;	/* [x-y] range match status */
972 	long	lc;	/* left character of [x-y] range */
973 
974 	scc = *s++;
975 	switch (c = *p++) {
976 
977 	case '[':
978 		ok = 0;
979 		lc = -1;
980 		while (c = *p++) {
981 			if (c == ']') {
982 				return (ok ? gmatch(s, p) : 0);
983 			} else if (c == '-') {
984 				wchar_t rc = *p++;
985 				/*
986 				 * Check both ends must belong to
987 				 * the same codeset.
988 				 */
989 				if (wcsetno(lc) != wcsetno(rc)) {
990 					/*
991 					 * If not, ignore the '-'
992 					 * operator and [x-y] is
993 					 * treated as if it were
994 					 * [xy].
995 					 */
996 					if (scc == lc)
997 						ok++;
998 					if (scc == (lc = rc))
999 						ok++;
1000 				} else if (lc <= scc && scc <= rc)
1001 					ok++;
1002 			} else {
1003 				lc = c;
1004 				if (scc == lc)
1005 					ok++;
1006 			}
1007 		}
1008 		/* No closing bracket => failure */
1009 		return (0);
1010 
1011 	default:
1012 		if (c != scc)
1013 			return (0);
1014 		/*FALLTHROUGH*/
1015 
1016 	case '?':
1017 		return (scc ? gmatch(s, p) : 0);
1018 
1019 	case '*':
1020 		if (*p == 0)
1021 			return (1);
1022 		s--;
1023 		while (*s) {
1024 			if (gmatch(s++, p))
1025 				return (1);
1026 		}
1027 		return (0);
1028 
1029 	case 0:
1030 		return (scc == 0);
1031 	}
1032 }
1033 
1034 /*
1035  * Construct a matched name.
1036  */
1037 static int
1038 addg(struct direct *dp, char *as1, char *as3, struct arglist *ap)
1039 {
1040 	char	*s1, *s2, *limit;
1041 	int	c;
1042 	char	buf[MAXPATHLEN + 1];
1043 
1044 	s2 = buf;
1045 	limit = buf + sizeof (buf) - 1;
1046 	s1 = as1;
1047 	while ((c = *s1++) != '\0' && s2 < limit) {
1048 		if (c == DELIMCHAR) {
1049 			*s2++ = '/';
1050 			break;
1051 		}
1052 		/* LINTED narrowing cast */
1053 		*s2++ = (char)c;
1054 	}
1055 	s1 = dp->d_name;
1056 	while ((*s2 = *s1++) != '\0' && s2 < limit)
1057 		s2++;
1058 	s1 = as3;
1059 	if (s1 != NULL && s2 < limit) {
1060 		*s2++ = '/';
1061 
1062 		while ((*s2++ = *++s1) != '\0' && s2 < limit) {
1063 			continue;
1064 			/*LINTED [empty loop body]*/
1065 		}
1066 	}
1067 	*s2 = '\0';
1068 	if (mkentry(buf, dp->d_ino, ap) == FAIL)
1069 		return (-1);
1070 	return (0);
1071 }
1072 
1073 
1074 /*
1075  * Resolve a "complex" pathname (as generated by myname()) into
1076  * a file descriptor and a relative path.  The file descriptor
1077  * will reference the hidden directory containing the attribute
1078  * named by the relative path.  If the provided path is not
1079  * complex, the returned file descriptor will be AT_FDCWD and rpath
1080  * will equal path.
1081  *
1082  * This function is intended to be used to transform a complex
1083  * pathname into a pair of handles that can be used to actually
1084  * manipulate the named file.  Since extended attributes have
1085  * an independant name space, a file descriptor for a directory
1086  * in the attribute name space is necessary to actually manipulate
1087  * the attribute file (via the path-relative xxxat() system calls
1088  * or a call to fchdir()).
1089  *
1090  * In the event of an error, the returned file descriptor will be
1091  * -1.  It is expected that callers will either check for this
1092  * condition directly, or attempt to use the descriptor, fail, and
1093  * generate an appropriate context-specific error message.
1094  *
1095  * This function is pretty much a no-op for "simple" (non-attribute)
1096  * paths.
1097  */
1098 void
1099 resolve(char *path, int *fd, char **rpath)
1100 {
1101 	int	tfd;
1102 
1103 	*fd = tfd = AT_FDCWD;
1104 	*rpath = path;
1105 	path = *rpath + strlen(*rpath) +1;
1106 	while (*path != '\0' &&
1107 		(*fd = openat64(tfd, *rpath, O_RDONLY)) > 0) {
1108 		if (tfd != AT_FDCWD) (void) close(tfd);
1109 		tfd = *fd;
1110 		*rpath = path;
1111 		path = *rpath + strlen(*rpath) +1;
1112 	}
1113 	if (*fd == AT_FDCWD)
1114 		return;
1115 	if (*fd < 0 || (*fd = openat64(tfd, ".", O_RDONLY|O_XATTR)) < 0) {
1116 		int saverr = errno;
1117 		(void) fprintf(stderr,
1118 			gettext("Warning: cannot fully resolve %s: %s"),
1119 			path, strerror(saverr));
1120 		(void) fflush(stderr);
1121 	}
1122 	if (tfd != AT_FDCWD) (void) close(tfd);
1123 }
1124 
1125 /*
1126  * Copy a complex pathname to another string.  Note that the
1127  * length returned by this function is the number of characters
1128  * up to (but not including) the final NULL.
1129  */
1130 int
1131 complexcpy(char *s1, char *s2, int max)
1132 {
1133 	int	nullseen = 0;
1134 	int	len = 0;
1135 
1136 	while (len++ < max) {
1137 		*s1++ = *s2;
1138 		if (*s2++ == '\0') {
1139 			if (nullseen)
1140 				return (len-1);
1141 			else
1142 				nullseen = 1;
1143 		} else {
1144 			nullseen = 0;
1145 		}
1146 	}
1147 	*s1 = '\0';
1148 	if (nullseen == 0)
1149 		*--s1 = '\0';
1150 	fprintf(stderr,
1151 		gettext("Warning: unterminated source string in complexcpy\n"));
1152 	return (max-1);
1153 }
1154