# # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/backup/restore/Makefile # # Copyright (c) 1983 Regents of the University of California. # All rights reserved. The Berkeley software License Agreement # specifies the terms and conditions for redistribution. # # CAUTION: FSTYPE must be defined before including ../Makefile.backup, # otherwise UTFROOTPKGUSRLIBFSTYPE doesn't get expanded # properly and the magic doesn't happen. FSTYPE= ufs UFSRESTORE= ufsrestore PROG= $(UFSRESTORE) ROOTFS_PROG= $(PROG) PRODUCT= $(PROG) include ../Makefile.backup OBJECTS= dirs.o interactive.o main.o restore.o \ symtab.o tape.o utilities.o SRCS= $(OBJECTS:%.o=%.c) POFILES= $(OBJECTS:%.o=%.po) ../lib/libdump.po POFILE= ufsrestore.po # XXX This is only needed for the check: target. It would be nice to # XXX automatically generate the list when needed. HEADERS= ../../../head/protocols/dumprestore.h \ ../include/byteorder.h ../include/memutils.h \ ../include/rmt.h restore.h CLOBBERFILES += $(PRODUCT) $(DEBUGPRODUCTS) LOCAL= . GENERAL= ../include GLOBAL= ../../../head CPPFLAGS= -I$(LOCAL) -I$(GENERAL) -I$(GLOBAL) \ $(CPPFLAGS.master) -D_LARGEFILE64_SOURCE=1 # Hammerhead: Suppress pointer-related warnings in legacy restore code CERRWARN += -Wno-incompatible-pointer-types CERRWARN += -Wno-int-to-pointer-cast LIBDUMP= ../lib/libdump.a LINTLIBDUMP= ../lib/llib-ldump.ln LDLIBS += $(BSTATIC) -L../lib -ldump $(BDYNAMIC) -lsocket -lnsl -lsec UFSROOTLINK= $(UFSROOTUSRSBIN)/$(PROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(PROG) FILEMODE= 04555 .KEEP_STATE: all: $(ROOTFS_PROG) $(PROG): $(OBJECTS) $$(LIBDUMP) $(LINK.c) -o $@ $(OBJECTS) $(LDLIBS) $(POST_PROCESS) $(LIBDUMP): FRC $(DO_LIBDIR) FRC: install: all $(UFSROOTPKGUSRLIBFSTYPE) $(UFSROOTLINK) lint: $(SRCS) $(LINTLIBDUMP) $(LINT.c) $(SRCS) $(LINTLIBDUMP) 2>&1 \ | sed -f lint.sed | grep -v '^[ ]' $(LINTLIBDUMP): FRC cd ../lib; pwd; $(MAKE) lint pwd check: $(CSTYLE) $(CSTYLEFLAGS) $(SRCS) $(HEADERS) $(HDRCHK) $(HDRCHKFLAGS) $(HEADERS) clean: $(RM) $(OBJECTS) $(DEBUGOBJS) *.ln $(UFSROOTLINK): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $(UFSROOTLINK) $(POFILE): $(POFILES) $(RM) $@; cat $(POFILES) > $@ ../lib/libdump.po: cd ../lib ; pwd ; $(MAKE) libdump.po include ../Makefile.targ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ #include "restore.h" #include #include #include #include struct context curfile; /* * Symbol table of directories read from tape. */ #define HASHSIZE 1000 #define INOHASH(val) (val % HASHSIZE) struct inotab { struct inotab *t_next; ino_t t_ino; offset_t t_seekpt; offset_t t_size; struct inotab *t_xattr; }; static struct inotab *inotab[HASHSIZE]; static struct inotab *xattrlist = NULL; /* * Information retained about directories. */ static struct modeinfo { ino_t ino; time_t timep[2]; mode_t mode; uid_t uid; gid_t gid; size_t metasize; } node; /* * Global variables for this file. */ static off64_t g_seekpt; /* some people have a local seekpt */ static FILE *df, *mf; static char dirfile[MAXPATHLEN] = "#"; /* No file */ static char modefile[MAXPATHLEN] = "#"; /* No file */ static RST_DIR *dirp; #define INIT_TEMPFILE(name, type) \ if (name[0] == '#') { \ if (tmpdir == (char *)NULL) /* can't happen; be paranoid */ \ tmpdir = "/tmp"; \ (void) snprintf(name, sizeof (name), \ "%s/rst" type "%ld.XXXXXX", tmpdir, dumpdate); \ (void) mktemp(name); \ } #define INIT_DIRFILE() INIT_TEMPFILE(dirfile, "dir") #define INIT_MODEFILE() INIT_TEMPFILE(modefile, "mode") /* * Format of old style directories. */ #define ODIRSIZ 14 struct odirect { ushort_t d_ino; char d_name[ODIRSIZ]; }; static ino_t search(ino_t, char *); static void putdir(char *, size_t); static void putent(struct direct *); static void skipmetadata(FILE *, size_t); static void flushent(void); static void dcvt(struct odirect *, struct direct *); static RST_DIR *rst_initdirfile(char *); static offset_t rst_telldir(RST_DIR *); static void rst_seekdir(RST_DIR *, offset_t, offset_t); static struct inotab *allocinotab(ino_t, struct dinode *, off64_t); static void nodeflush(void); static struct inotab *inotablookup(ino_t); /* * Extract directory contents, building up a directory structure * on disk for extraction by name. * If genmode is requested, save mode, owner, and times for all * directories on the tape. */ void extractdirs(int genmode) { int ts; struct dinode *ip; int saverr; struct inotab *itp; struct direct nulldir; static char dotname[] = "."; /* dirlookup/psearch writes to its arg */ vprintf(stdout, gettext("Extract directories from tape\n")); INIT_DIRFILE(); if ((df = safe_fopen(dirfile, "w", 0600)) == (FILE *)NULL) { saverr = errno; (void) fprintf(stderr, gettext("%s: %s - cannot create directory temporary\n"), progname, dirfile); errno = saverr; perror("fopen"); done(1); } if (genmode != 0) { INIT_MODEFILE(); if ((mf = safe_fopen(modefile, "w", 0600)) == (FILE *)NULL) { saverr = errno; (void) fprintf(stderr, gettext("%s: %s - cannot create modefile \n"), progname, modefile); errno = saverr; perror("fopen"); done(1); } } nulldir.d_ino = 0; nulldir.d_namlen = 1; (void) strcpy(nulldir.d_name, "/"); /* LINTED DIRSIZ will always fit into a ushort_t */ nulldir.d_reclen = (ushort_t)DIRSIZ(&nulldir); /* LINTED sign extension ok in assert */ assert(DIRSIZ(&nulldir) == (ulong_t)nulldir.d_reclen); for (;;) { curfile.name = gettext(""); curfile.action = USING; ip = curfile.dip; ts = curfile.ts; if (ts != TS_END && ts != TS_INODE) { getfile(null, null); continue; } if (ts == TS_INODE && ip == NULL) { (void) fprintf(stderr, gettext( "%s: extractdirs: Failed internal consistency check, curfile.dip is NULL\n"), progname); done(1); } if ((ts == TS_INODE && (ip->di_mode & IFMT) != IFDIR && (ip->di_mode & IFMT) != IFATTRDIR) || (ts == TS_END)) { (void) fflush(df); /* XXX Legitimate error, bad complaint string */ if (ferror(df)) panic("%s: %s\n", dirfile, strerror(errno)); (void) fclose(df); rst_closedir(dirp); dirp = rst_initdirfile(dirfile); if (dirp == NULL) perror("initdirfile"); if (mf != NULL) { (void) fflush(mf); /* XXX Legitimate error, bad complaint string */ if (ferror(mf)) panic("%s: %s\n", modefile, strerror(errno)); (void) fclose(mf); } if (dirlookup(dotname) == 0) { (void) fprintf(stderr, gettext( "Root directory is not on tape\n")); done(1); } return; } itp = allocinotab(curfile.ino, ip, g_seekpt); getfile(putdir, null); if (mf != NULL) nodeflush(); putent(&nulldir); flushent(); itp->t_size = g_seekpt - itp->t_seekpt; } } /* * skip over all the directories on the tape */ void skipdirs() { while (curfile.dip != NULL && ((curfile.dip->di_mode & IFMT) == IFDIR || (curfile.dip->di_mode & IFMT) == IFATTRDIR)) { skipfile(); } } /* * Recursively find names and inumbers of all files in subtree * pname and pass them off to be processed. */ void treescan(char *pname, ino_t ino, long (*todo)()) { struct inotab *itp; struct direct *dp; uint_t loclen; offset_t bpt; char locname[MAXCOMPLEXLEN]; itp = inotablookup(ino); if (itp == NULL) { /* * Pname is name of a simple file or an unchanged directory. */ (void) (*todo)(pname, ino, LEAF); return; } /* * Pname is a dumped directory name. */ if ((*todo)(pname, ino, NODE) == FAIL) return; /* * begin search through the directory * skipping over "." and ".." */ loclen = complexcpy(locname, pname, MAXCOMPLEXLEN); locname[loclen-1] = '/'; rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt); dp = rst_readdir(dirp); /* "." */ if (dp != NULL && strcmp(dp->d_name, ".") == 0) dp = rst_readdir(dirp); /* ".." */ else (void) fprintf(stderr, gettext("Warning: `.' missing from directory %s\n"), pname); if (dp != NULL && strcmp(dp->d_name, "..") == 0) dp = rst_readdir(dirp); /* first real entry */ else (void) fprintf(stderr, gettext("Warning: `..' missing from directory %s\n"), pname); bpt = rst_telldir(dirp); /* * a zero inode signals end of directory */ while (dp != NULL && dp->d_ino != 0) { locname[loclen] = '\0'; if ((loclen + dp->d_namlen) >= (sizeof (locname) - 2)) { (void) fprintf(stderr, gettext( "%s%s: ignoring name that exceeds %d char\n"), locname, dp->d_name, MAXCOMPLEXLEN); } else { /* Always fits by if() condition */ (void) strcpy(locname + loclen, dp->d_name); /* put a double null on string for lookupname() */ locname[loclen+dp->d_namlen+1] = '\0'; treescan(locname, dp->d_ino, todo); rst_seekdir(dirp, bpt, itp->t_seekpt); } dp = rst_readdir(dirp); bpt = rst_telldir(dirp); } if (dp == NULL) (void) fprintf(stderr, gettext("corrupted directory: %s.\n"), locname); } /* * Scan the directory table looking for extended attribute trees. * Recursively find names and inumbers in each tree and pass them * off to be processed. If the always parameter is not set, only * process the attribute tree if the attribute tree parent is to * be extracted. */ void attrscan(int always, long (*todo)()) { struct inotab *itp; struct entry *ep, *parent; struct direct *dp; char name[MAXCOMPLEXLEN]; int len; for (itp = xattrlist; itp != NULL; itp = itp->t_xattr) { rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt); if ((dp = rst_readdir(dirp)) != NULL && /* "." */ (dp = rst_readdir(dirp)) != NULL && /* ".." */ strcmp(dp->d_name, "..") == 0) { if ((parent = lookupino(dp->d_ino)) != NULL) { if (!always && (parent->e_flags & (NEW|EXTRACT)) == 0) continue; len = complexcpy(name, myname(parent), MAXCOMPLEXLEN - 3); name[len] = '.'; name[len+1] = '\0'; name[len+2] = '\0'; inattrspace = 1; if ((ep = lookupino(itp->t_ino)) == NULL) { ep = addentry(name, itp->t_ino, NODE|ROOT); } ep->e_flags |= XATTRROOT; treescan(name, itp->t_ino, todo); inattrspace = 0; } else { (void) fprintf(stderr, gettext("Warning: orphaned attribute " "directory\n")); } } else { (void) fprintf(stderr, gettext("Warning: `..' missing " "from attribute directory\n")); } } } /* * Search the directory tree rooted at inode ROOTINO * for the path pointed at by n. Note that n must be * modifiable, although it is returned in the same * condition it was given to us in. */ ino_t psearch(char *n) { char *cp, *cp1; ino_t ino; char c; ino = ROOTINO; if (*(cp = n) == '/') cp++; next: cp1 = cp + 1; while (*cp1 != '/' && *cp1) cp1++; c = *cp1; *cp1 = 0; ino = search(ino, cp); if (ino == 0) { *cp1 = c; return (0); } *cp1 = c; if (c == '/') { cp = cp1+1; goto next; } return (ino); } /* * search the directory inode ino * looking for entry cp */ static ino_t search(ino_t inum, char *cp) { struct direct *dp; struct inotab *itp; uint_t len; itp = inotablookup(inum); if (itp == NULL) return (0); rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt); len = strlen(cp); do { dp = rst_readdir(dirp); if (dp == NULL || dp->d_ino == 0) return (0); } while (dp->d_namlen != len || strncmp(dp->d_name, cp, len) != 0); return (dp->d_ino); } /* * Put the directory entries in the directory file */ static void putdir(char *buf, size_t size) { struct direct cvtbuf; struct odirect *odp; struct odirect *eodp; struct direct *dp; size_t loc, i; if (cvtflag) { /*LINTED [buf is char[] in getfile, size % fs_fsize == 0]*/ eodp = (struct odirect *)&buf[size]; /*LINTED [buf is char[] in getfile]*/ for (odp = (struct odirect *)buf; odp < eodp; odp++) if (odp->d_ino != 0) { dcvt(odp, &cvtbuf); putent(&cvtbuf); } } else { loc = 0; while (loc < size) { /*LINTED [buf is char[] in getfile, loc % 4 == 0]*/ dp = (struct direct *)(buf + loc); normdirect(byteorder, dp); i = DIRBLKSIZ - (loc & (DIRBLKSIZ - 1)); if (dp->d_reclen == 0 || (long)dp->d_reclen > i) { loc += i; continue; } loc += dp->d_reclen; if (dp->d_ino != 0) { putent(dp); } } } } /* * These variables are "local" to the following two functions. */ static char dirbuf[DIRBLKSIZ]; static int32_t dirloc = 0; static int32_t prev = 0; /* * add a new directory entry to a file. */ static void putent(struct direct *dp) { /* LINTED DIRSIZ will always fit in a ushort_t */ dp->d_reclen = (ushort_t)DIRSIZ(dp); /* LINTED sign extension ok in assert */ assert(DIRSIZ(dp) == (ulong_t)dp->d_reclen); if (dirloc + (long)dp->d_reclen > DIRBLKSIZ) { /*LINTED [prev += dp->d_reclen, prev % 4 == 0]*/ ((struct direct *)(dirbuf + prev))->d_reclen = DIRBLKSIZ - prev; (void) fwrite(dirbuf, 1, DIRBLKSIZ, df); if (ferror(df)) panic("%s: %s\n", dirfile, strerror(errno)); dirloc = 0; } bcopy((char *)dp, dirbuf + dirloc, (size_t)dp->d_reclen); prev = dirloc; dirloc += dp->d_reclen; } /* * flush out a directory that is finished. */ static void flushent(void) { /* LINTED prev += dp->d_reclen, prev % 4 == 0 */ ((struct direct *)(dirbuf + prev))->d_reclen = DIRBLKSIZ - prev; (void) fwrite(dirbuf, (size_t)dirloc, 1, df); if (ferror(df)) panic("%s: %s\n", dirfile, strerror(errno)); g_seekpt = ftello64(df); dirloc = 0; } static void dcvt(struct odirect *odp, struct direct *ndp) { (void) bzero((char *)ndp, sizeof (*ndp)); ndp->d_ino = odp->d_ino; /* Note that odp->d_name may not be null-terminated */ /* LINTED assertion always true */ assert(sizeof (ndp->d_name) > sizeof (odp->d_name)); (void) strncpy(ndp->d_name, odp->d_name, sizeof (odp->d_name)); ndp->d_name[sizeof (odp->d_name)] = '\0'; /* LINTED: strlen will fit into d_namlen */ ndp->d_namlen = strlen(ndp->d_name); /* LINTED sign extension ok in assert */ assert(DIRSIZ(ndp) == (ulong_t)ndp->d_reclen); /* LINTED DIRSIZ always fits in ushort_t */ ndp->d_reclen = (ushort_t)DIRSIZ(ndp); } /* * Initialize the directory file */ static RST_DIR * rst_initdirfile(char *name) { RST_DIR *dp; int fd; if ((fd = open(name, O_RDONLY | O_LARGEFILE)) == -1) return ((RST_DIR *)0); if ((dp = (RST_DIR *)malloc(sizeof (*dp))) == NULL) { (void) close(fd); return ((RST_DIR *)0); } dp->dd_fd = fd; dp->dd_loc = 0; dp->dd_refcnt = 1; return (dp); } /* * Simulate the opening of a directory */ RST_DIR * rst_opendir(char *name) { struct inotab *itp; ino_t ino; if ((ino = dirlookup(name)) > 0 && (itp = inotablookup(ino)) != NULL) { rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt); dirp->dd_refcnt++; return (dirp); } return ((RST_DIR *)0); } /* * Releases the hidden state created by rst_opendir(). * Specifically, the dirp it provided to the caller is malloc'd. */ void rst_closedir(RST_DIR *cdirp) { if ((cdirp != NULL) && (--(cdirp->dd_refcnt) < 1)) free(cdirp); } /* * return a pointer into a directory */ static offset_t rst_telldir(RST_DIR *tdirp) { offset_t pos = llseek(tdirp->dd_fd, (offset_t)0, SEEK_CUR); if (pos == (offset_t)-1) { perror("Could not determine position in directory file"); done(1); } return ((pos - tdirp->dd_size) + tdirp->dd_loc); } /* * Seek to an entry in a directory. * Only values returned by ``rst_telldir'' should be passed to rst_seekdir. * This routine handles many directories in a single file. * It takes the base of the directory in the file, plus * the desired seek offset into it. */ static void rst_seekdir(RST_DIR *sdirp, offset_t loc, offset_t base) { if (loc == rst_telldir(sdirp)) return; loc -= base; if (loc < 0) (void) fprintf(stderr, gettext("bad seek pointer to rst_seekdir %d\n"), loc); (void) llseek(sdirp->dd_fd, base + (loc & ~(DIRBLKSIZ - 1)), 0); sdirp->dd_loc = loc & (DIRBLKSIZ - 1); if (sdirp->dd_loc != 0) sdirp->dd_size = read(sdirp->dd_fd, sdirp->dd_buf, DIRBLKSIZ); } /* * get next entry in a directory. */ struct direct * rst_readdir(RST_DIR *rdirp) { struct direct *dp; for (;;) { if (rdirp->dd_loc == 0) { rdirp->dd_size = read(rdirp->dd_fd, rdirp->dd_buf, DIRBLKSIZ); if (rdirp->dd_size <= 0) { dprintf(stderr, gettext("error reading directory\n")); return ((struct direct *)0); } } if (rdirp->dd_loc >= rdirp->dd_size) { rdirp->dd_loc = 0; continue; } /*LINTED [rvalue will be aligned on int boundary]*/ dp = (struct direct *)(rdirp->dd_buf + rdirp->dd_loc); if (dp->d_reclen == 0 || (long)dp->d_reclen > (DIRBLKSIZ + 1 - rdirp->dd_loc)) { dprintf(stderr, gettext("corrupted directory: bad reclen %d\n"), dp->d_reclen); return ((struct direct *)0); } rdirp->dd_loc += dp->d_reclen; if (dp->d_ino == 0 && strcmp(dp->d_name, "/") != 0) continue; if ((ino_t)(dp->d_ino) >= maxino) { dprintf(stderr, gettext("corrupted directory: bad inum %lu\n"), dp->d_ino); continue; } return (dp); } } /* * Set the mode, owner, and times for all new or changed directories */ void setdirmodes(void) { FILE *smf; struct entry *ep; char *cp, *metadata = NULL; size_t metasize = 0; int override = -1; int saverr; static int complained_chown = 0; static int complained_chmod = 0; int dfd; vprintf(stdout, gettext("Set directory mode, owner, and times.\n")); /* XXX if modefile[0] == '#', shouldn't we just bail here? */ /* XXX why isn't it set already? */ INIT_MODEFILE(); smf = fopen64(modefile, "r"); if (smf == NULL) { perror("fopen"); (void) fprintf(stderr, gettext("cannot open mode file %s\n"), modefile); (void) fprintf(stderr, gettext("directory mode, owner, and times not set\n")); return; } clearerr(smf); for (;;) { (void) fread((char *)&node, 1, sizeof (node), smf); if (feof(smf)) break; ep = lookupino(node.ino); if (command == 'i' || command == 'x') { if (ep == NIL) { skipmetadata(smf, node.metasize); continue; } if (ep->e_flags & EXISTED) { if (override < 0) { if (reply(gettext( "Directories already exist, set modes anyway")) == FAIL) override = 0; else override = 1; } if (override == 0) { /* LINTED: result fits into short */ ep->e_flags &= ~NEW; skipmetadata(smf, node.metasize); continue; } } if (node.ino == ROOTINO && reply(gettext("set owner/mode for '.'")) == FAIL) { skipmetadata(smf, node.metasize); continue; } } if (ep == NIL) { panic(gettext("cannot find directory inode %d\n"), node.ino); skipmetadata(smf, node.metasize); continue; } cp = myname(ep); resolve(myname(ep), &dfd, &cp); if (dfd != AT_FDCWD) { if (fchdir(dfd) < 0) { saverr = errno; (void) fprintf(stderr, gettext("Can not set attribute context: %s\n"), strerror(saverr)); (void) close(dfd); continue; } } if (chmod(cp, node.mode) < 0 && !complained_chmod) { saverr = errno; (void) fprintf(stderr, gettext("Can not set directory permissions: %s\n"), strerror(saverr)); complained_chmod = 1; } if (node.metasize != 0) { if (node.metasize > metasize) metadata = realloc(metadata, metasize = node.metasize); if (metadata == NULL) { (void) fprintf(stderr, gettext("Cannot malloc metadata\n")); done(1); } (void) fread(metadata, 1, node.metasize, smf); metaproc(cp, metadata, node.metasize); } /* * BUG 4302943 * Since the ACLs must be set before fixing the ownership, * chown should be called only after metaproc */ if (chown(cp, node.uid, node.gid) < 0 && !complained_chown) { saverr = errno; (void) fprintf(stderr, gettext("Can not set directory ownership: %s\n"), strerror(saverr)); complained_chown = 1; } utime(cp, (struct utimbuf *)node.timep); /* LINTED: result fits into short */ ep->e_flags &= ~NEW; if (dfd != AT_FDCWD) { fchdir(savepwd); (void) close(dfd); } } if (ferror(smf)) panic(gettext("error setting directory modes\n")); if (metadata != NULL) (void) free(metadata); (void) fclose(smf); } void skipmetadata(FILE *f, size_t size) { /* XXX should we bail if this doesn't work? */ /* LINTED unsigned -> signed conversion ok here */ (void) fseeko(f, (off_t)size, SEEK_CUR); } /* * Generate a literal copy of a directory. */ int genliteraldir(char *name, ino_t ino) { struct inotab *itp; int ofile, dp; off64_t i; size_t size; char buf[BUFSIZ]; itp = inotablookup(ino); if (itp == NULL) { (void) fprintf(stderr, gettext("Cannot find directory inode %d named %s\n"), ino, name); return (FAIL); } if ((ofile = creat(name, 0666)) < 0) { (void) fprintf(stderr, "%s: ", name); (void) fflush(stderr); perror(gettext("cannot create file")); return (FAIL); } rst_seekdir(dirp, itp->t_seekpt, itp->t_seekpt); dp = dup(dirp->dd_fd); if (dp < 0) { perror(gettext("dup(2) failed")); (void) close(ofile); (void) unlink(name); return (FAIL); } for (i = itp->t_size; i != 0; i -= size) { /* LINTED cast is safe due to comparison */ size = i < BUFSIZ ? (size_t)i : BUFSIZ; /* XXX instead of done(), clean up and return FAIL? */ if (read(dp, buf, size) == -1) { (void) fprintf(stderr, gettext( "read error extracting inode %d, name %s\n"), curfile.ino, curfile.name); perror("read"); done(1); } if (write(ofile, buf, size) == -1) { (void) fprintf(stderr, gettext( "write error extracting inode %d, name %s\n"), curfile.ino, curfile.name); perror("write"); done(1); } } (void) close(dp); (void) close(ofile); return (GOOD); } /* * Determine the type of an inode */ int inodetype(ino_t ino) { struct inotab *itp; itp = inotablookup(ino); if (itp == NULL) return (LEAF); return (NODE); } /* * Allocate and initialize a directory inode entry. * If requested, save its pertinent mode, owner, and time info. */ static struct inotab * allocinotab(ino_t ino, struct dinode *dip, off64_t seekpt) { struct inotab *itp; itp = (struct inotab *)calloc(1, sizeof (*itp)); if (itp == 0) { (void) fprintf(stderr, gettext("no memory for directory table\n")); done(1); } itp->t_next = inotab[INOHASH(ino)]; inotab[INOHASH(ino)] = itp; itp->t_ino = ino; itp->t_seekpt = seekpt; if ((dip->di_mode & IFMT) == IFATTRDIR) { itp->t_xattr = xattrlist; xattrlist = itp; } if (mf == NULL) return (itp); node.ino = ino; node.timep[0] = dip->di_atime; node.timep[1] = dip->di_mtime; node.mode = dip->di_mode; node.uid = dip->di_suid == UID_LONG ? dip->di_uid : (uid_t)dip->di_suid; node.gid = dip->di_sgid == GID_LONG ? dip->di_gid : (gid_t)dip->di_sgid; return (itp); } void nodeflush() { char *metadata; if (mf == NULL) { (void) fprintf(stderr, gettext( "Inconsistency detected: modefile pointer is NULL\n")); done(1); } metaget(&metadata, &(node.metasize)); (void) fwrite((char *)&node, 1, sizeof (node), mf); if (node.metasize != 0) (void) fwrite(metadata, 1, node.metasize, mf); if (ferror(mf)) panic("%s: %s\n", modefile, strerror(errno)); } /* * Look up an inode in the table of directories */ static struct inotab * inotablookup(ino_t ino) { struct inotab *itp; for (itp = inotab[INOHASH(ino)]; itp != NULL; itp = itp->t_next) if (itp->t_ino == ino) return (itp); return ((struct inotab *)0); } /* * Clean up and exit */ void done(int exitcode) { closemt(ALLOW_OFFLINE); /* don't force offline on exit */ if (modefile[0] != '#') (void) unlink(modefile); if (dirfile[0] != '#') (void) unlink(dirfile); exit(exitcode); } /* * Copyright 1998,2001-2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1985 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ #include #include #include #include "restore.h" #include #include #include extern eucwidth_t wp; #define round(a, b) ((((a) + (b) - 1) / (b)) * (b)) /* * Things to handle interruptions. */ static jmp_buf reset; static int reset_OK; static char *nextarg = NULL; static int dontexpand; /* co-routine state set in getnext, used in expandarg */ static void getcmd(char *, char *, size_t, char *, size_t, struct arglist *); static void expandarg(char *, struct arglist *); static void printlist(char *, ino_t, char *, int); static void formatf(struct arglist *); static char *copynext(char *, char *, size_t); static int fcmp(struct afile *, struct afile *); static char *fmtentry(struct afile *); static void setpagercmd(void); static uint_t setpagerargs(char **); /* * Read and execute commands from the terminal. */ void runcmdshell(void) { struct entry *np; ino_t ino; static struct arglist alist = { 0, 0, 0, 0, 0 }; char curdir[MAXCOMPLEXLEN]; char name[MAXCOMPLEXLEN]; char cmd[BUFSIZ]; #ifdef lint curdir[0] = '\0'; #endif /* lint */ canon("/", curdir, sizeof (curdir)); loop: if (setjmp(reset) != 0) { for (; alist.head < alist.last; alist.head++) freename(alist.head->fname); nextarg = NULL; volno = 0; goto loop; /* make sure jmpbuf is up-to-date */ } reset_OK = 1; getcmd(curdir, cmd, sizeof (cmd), name, sizeof (name), &alist); /* * Using strncmp() to catch unique prefixes. */ switch (cmd[0]) { /* * Add elements to the extraction list. */ case 'a': if (strncmp(cmd, "add", strlen(cmd)) != 0) goto bad; if (name[0] == '\0') break; ino = dirlookup(name); if (ino == 0) break; if (mflag) pathcheck(name); treescan(name, ino, addfile); break; /* * Change working directory. */ case 'c': if (strncmp(cmd, "cd", strlen(cmd)) != 0) goto bad; if (name[0] == '\0') break; ino = dirlookup(name); if (ino == 0) break; if (inodetype(ino) == LEAF) { (void) fprintf(stderr, gettext("%s: not a directory\n"), name); break; } /* No need to canon(name), getcmd() did it for us */ (void) strncpy(curdir, name, sizeof (curdir)); curdir[sizeof (curdir) - 1] = '\0'; break; /* * Delete elements from the extraction list. */ case 'd': if (strncmp(cmd, "delete", strlen(cmd)) != 0) goto bad; if (name[0] == '\0') break; np = lookupname(name); if (np == NIL || (np->e_flags & NEW) == 0) { (void) fprintf(stderr, gettext("%s: not on extraction list\n"), name); break; } treescan(name, np->e_ino, deletefile); break; /* * Extract the requested list. */ case 'e': if (strncmp(cmd, "extract", strlen(cmd)) != 0) goto bad; attrscan(0, addfile); createfiles(); createlinks(); setdirmodes(); if (dflag) checkrestore(); volno = 0; break; /* * List available commands. */ case 'h': if (strncmp(cmd, "help", strlen(cmd)) != 0) goto bad; /*FALLTHROUGH*/ case '?': /* ANSI string catenation, to shut cstyle up */ (void) fprintf(stderr, "%s", gettext("Available commands are:\n" "\tls [arg] - list directory\n" "\tmarked [arg] - list items marked for extraction from directory\n" "\tcd arg - change directory\n" "\tpwd - print current directory\n" "\tadd [arg] - add `arg' to list of files to be extracted\n" "\tdelete [arg] - delete `arg' from list of files to be extracted\n" "\textract - extract requested files\n" "\tsetmodes - set modes of requested directories\n" "\tquit - immediately exit program\n" "\twhat - list dump header information\n" "\tverbose - toggle verbose flag (useful with ``ls'')\n" "\tpaginate - toggle pagination flag (affects ``ls'' and ``marked'')\n" "\tsetpager - set pagination command and arguments\n" "\thelp or `?' - print this list\n" "If no `arg' is supplied, the current directory is used\n")); break; /* * List a directory. */ case 'l': case 'm': if ((strncmp(cmd, "ls", strlen(cmd)) != 0) && (strncmp(cmd, "marked", strlen(cmd)) != 0)) goto bad; if (name[0] == '\0') break; ino = dirlookup(name); if (ino == 0) break; printlist(name, ino, curdir, *cmd == 'm'); break; /* * Print current directory or enable pagination. */ case 'p': if (strlen(cmd) < 2) goto ambiguous; if (strncmp(cmd, "pwd", strlen(cmd)) == 0) { if (curdir[1] == '\0') { (void) fprintf(stderr, "/\n"); } else { (void) fprintf(stderr, "%s\n", &curdir[1]); } } else if (strncmp(cmd, "paginate", strlen(cmd)) == 0) { if (paginating) { (void) fprintf(stderr, gettext("paging disabled\n")); paginating = 0; break; } if (vflag) { (void) fprintf(stderr, gettext("paging enabled (%s)\n"), pager_catenated); } else { (void) fprintf(stderr, gettext("paging enabled\n")); } if (dflag) { int index = 0; while (index < pager_len) { (void) fprintf(stderr, ">>>pager_vector[%d] = `%s'\n", index, pager_vector[index] ? pager_vector[index] : "(null)"); index += 1; } } paginating = 1; } else { goto bad; } break; /* * Quit. */ case 'q': if (strncmp(cmd, "quit", strlen(cmd)) != 0) goto bad; reset_OK = 0; return; case 'x': if (strncmp(cmd, "xit", strlen(cmd)) != 0) goto bad; reset_OK = 0; return; /* * Toggle verbose mode. */ case 'v': if (strncmp(cmd, "verbose", strlen(cmd)) != 0) goto bad; if (vflag) { (void) fprintf(stderr, gettext("verbose mode off\n")); vflag = 0; break; } (void) fprintf(stderr, gettext("verbose mode on\n")); vflag = 1; break; /* * Just restore requested directory modes, or set pagination command. */ case 's': if (strlen(cmd) < 4) goto ambiguous; if (strncmp(cmd, "setmodes", strlen(cmd)) == 0) { setdirmodes(); } else if (strncmp(cmd, "setpager", strlen(cmd)) == 0) { setpagercmd(); } else { goto bad; } break; /* * Print out dump header information. */ case 'w': if (strncmp(cmd, "what", strlen(cmd)) != 0) goto bad; printdumpinfo(); break; /* * Turn on debugging. */ case 'D': if (strncmp(cmd, "Debug", strlen(cmd)) != 0) goto bad; if (dflag) { (void) fprintf(stderr, gettext("debugging mode off\n")); dflag = 0; break; } (void) fprintf(stderr, gettext("debugging mode on\n")); dflag++; break; /* * Unknown command. */ default: bad: (void) fprintf(stderr, gettext("%s: unknown command; type ? for help\n"), cmd); break; ambiguous: (void) fprintf(stderr, gettext("%s: ambiguous command; type ? for help\n"), cmd); break; } goto loop; } static char input[MAXCOMPLEXLEN]; /* shared by getcmd() and setpagercmd() */ #define rawname input /* save space by reusing input buffer */ /* * Read and parse an interactive command. * The first word on the line is assigned to "cmd". If * there are no arguments on the command line, then "curdir" * is returned as the argument. If there are arguments * on the line they are returned one at a time on each * successive call to getcmd. Each argument is first assigned * to "name". If it does not start with "/" the pathname in * "curdir" is prepended to it. Finally "canon" is called to * eliminate any embedded ".." components. */ /* ARGSUSED */ static void getcmd(char *curdir, char *cmd, size_t cmdsiz, char *name, size_t namesiz, struct arglist *ap) { char *cp; char output[MAXCOMPLEXLEN]; /* * Check to see if still processing arguments. */ if (ap->head != ap->last) { (void) strncpy(name, ap->head->fname, namesiz); name[namesiz - 1] = '\0'; /* double null terminate string */ if ((strlen(name) + 2) > namesiz) { fprintf(stderr, gettext("name is too long, ignoring")); memset(name, 0, namesiz); } else { name[strlen(name) + 1] = '\0'; } freename(ap->head->fname); ap->head++; return; } if (nextarg != NULL) goto getnext; /* * Read a command line and trim off trailing white space. */ readagain: do { (void) fprintf(stderr, "%s > ", progname); (void) fflush(stderr); (void) fgets(input, sizeof (input), terminal); } while (!feof(terminal) && input[0] == '\n'); if (feof(terminal)) { (void) strncpy(cmd, "quit", cmdsiz); return; } /* trim off trailing white space and newline */ for (cp = &input[strlen(input) - 2]; cp >= &input[0] && isspace((uchar_t)*cp); cp--) { continue; /*LINTED [empty loop body]*/ } *++cp = '\0'; if ((strlen(input) + 2) > MAXCOMPLEXLEN) { fprintf(stderr, gettext("command is too long\n")); goto readagain; } else { /* double null terminate string */ *(cp + 1) = '\0'; } if (cp == &input[0]) goto readagain; /* * Copy the command into "cmd". */ cp = copynext(input, cmd, cmdsiz); ap->cmd = cmd; /* * If no argument, use curdir as the default. */ if (*cp == '\0') { (void) strncpy(name, curdir, namesiz); name[namesiz - 1] = '\0'; /* double null terminate string */ if ((strlen(name) + 2) > namesiz) { fprintf(stderr, gettext("name is too long, ignoring")); memset(name, 0, namesiz); } else { name[strlen(name) + 1] = '\0'; } return; } nextarg = cp; /* * Find the next argument. */ getnext: cp = copynext(nextarg, rawname, sizeof (rawname)); if (*cp == '\0') nextarg = NULL; else nextarg = cp; /* * If it an absolute pathname, canonicalize it and return it. */ if (rawname[0] == '/') { canon(rawname, name, namesiz); } else { /* * For relative pathnames, prepend the current directory to * it then canonicalize and return it. */ (void) snprintf(output, sizeof (output), "%s/%s", curdir, rawname); canon(output, name, namesiz); } expandarg(name, ap); /* * ap->head->fname guaranteed to be double null-terminated and * no more than MAXCOMPLEXLEN characters long. */ assert(namesiz >= (MAXCOMPLEXLEN)); (void) strcpy(name, ap->head->fname); /* double null terminate string */ name[strlen(name) + 1] = '\0'; freename(ap->head->fname); ap->head++; #undef rawname } /* * Strip off the next token of the input. */ static char * copynext(char *input, char *output, size_t outsize) { char *cp, *bp, *limit; char quote; dontexpand = 0; /* skip to argument */ for (cp = input; *cp != '\0' && isspace((uchar_t)*cp); cp++) { continue; /*LINTED [empty loop body]*/ } bp = output; limit = output + outsize - 1; /* -1 for the trailing \0 */ while (!isspace((uchar_t)*cp) && *cp != '\0' && bp < limit) { /* * Handle back slashes. */ if (*cp == '\\') { if (*++cp == '\0') { (void) fprintf(stderr, gettext( "command lines cannot be continued\n")); continue; } *bp++ = *cp++; continue; } /* * The usual unquoted case. */ if (*cp != '\'' && *cp != '"') { *bp++ = *cp++; continue; } /* * Handle single and double quotes. */ quote = *cp++; dontexpand = 1; while (*cp != quote && *cp != '\0' && bp < limit) *bp++ = *cp++; if (*cp++ == '\0') { (void) fprintf(stderr, gettext("missing %c\n"), (uchar_t)quote); cp--; continue; } } *bp = '\0'; if ((strlen(output) + 2) > outsize) { fprintf(stderr, gettext( "name is too long, ignoring")); memset(output, 0, outsize); } else { /* double null terminate string */ *(bp + 1) = '\0'; } return (cp); } /* * Canonicalize file names to always start with ``./'' and * remove any imbedded "." and ".." components. * * The pathname "canonname" is returned double null terminated. */ void canon(char *rawname, char *canonname, size_t limit) { char *cp, *np, *prefix; uint_t len; assert(limit > 3); if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0) prefix = ""; else if (rawname[0] == '/') prefix = "."; else prefix = "./"; (void) snprintf(canonname, limit, "%s%s", prefix, rawname); /* * Eliminate multiple and trailing '/'s */ for (cp = np = canonname; *np != '\0'; cp++) { *cp = *np++; while (*cp == '/' && *np == '/') np++; } *cp = '\0'; if ((strlen(canonname) + 2) > limit) { fprintf(stderr, gettext("canonical name is too long, ignoring name\n")); memset(canonname, 0, limit); } else { /* double null terminate string */ *(cp + 1) = '\0'; } if (*--cp == '/') *cp = '\0'; /* * Eliminate extraneous "." and ".." from pathnames. Uses * memmove(), as strcpy() might do the wrong thing for these * small overlaps. */ np = canonname; while (*np != '\0') { np++; cp = np; while (*np != '/' && *np != '\0') np++; if (np - cp == 1 && *cp == '.') { cp--; len = strlen(np); (void) memmove(cp, np, len); *(cp + len) = '\0'; /* double null terminate string */ *(cp + len + 1) = '\0'; np = cp; } if (np - cp == 2 && strncmp(cp, "..", 2) == 0) { cp--; /* find beginning of name */ while (cp > &canonname[1] && *--cp != '/') { continue; /*LINTED [empty loop body]*/ } len = strlen(np); (void) memmove(cp, np, len); *(cp + len) = '\0'; /* double null terminate string */ *(cp + len + 1) = '\0'; np = cp; } } } /* * globals (file name generation) * * "*" in params matches r.e ".*" * "?" in params matches r.e. "." * "[...]" in params matches character class * "[...a-z...]" in params matches a through z. */ static void expandarg(char *arg, struct arglist *ap) { static struct afile single; int size; ap->head = ap->last = (struct afile *)0; if (dontexpand) size = 0; else size = expand(arg, 0, ap); if (size == 0) { struct entry *ep; ep = lookupname(arg); single.fnum = ep ? ep->e_ino : 0; single.fname = savename(arg); ap->head = &single; ap->last = ap->head + 1; return; } if ((ap->last - ap->head) > ULONG_MAX) { (void) fprintf(stderr, gettext("Argument expansion too large to sort\n")); } else { /* LINTED pointer arith just range-checked */ qsort((char *)ap->head, (size_t)(ap->last - ap->head), sizeof (*ap->head), (int (*)(const void *, const void *)) fcmp); } } /* * Do an "ls" style listing of a directory */ static void printlist(char *name, ino_t ino, char *basename, int marked_only) { struct afile *fp; struct direct *dp; static struct arglist alist = { 0, 0, 0, 0, "ls" }; struct afile single; struct entry *np; RST_DIR *dirp; int list_entry; if ((dirp = rst_opendir(name)) == NULL) { single.fnum = ino; if (strncmp(name, basename, strlen(basename)) == 0) single.fname = savename(name + strlen(basename) + 1); else single.fname = savename(name); alist.head = &single; alist.last = alist.head + 1; if (alist.base != NULL) { free(alist.base); alist.base = NULL; } } else { alist.head = (struct afile *)0; (void) fprintf(stderr, "%s:\n", name); while (dp = rst_readdir(dirp)) { if (dp == NULL || dp->d_ino == 0) { rst_closedir(dirp); dirp = NULL; break; } if (!dflag && BIT(dp->d_ino, dumpmap) == 0) continue; if (vflag == 0 && (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)) continue; list_entry = 1; if (marked_only) { np = lookupino(dp->d_ino); if ((np == NIL) || ((np->e_flags & NEW) == 0)) list_entry = 0; } if (list_entry) { if (!mkentry(dp->d_name, dp->d_ino, &alist)) { rst_closedir(dirp); return; } } } } if (alist.head != 0) { if ((alist.last - alist.head) > ULONG_MAX) { (void) fprintf(stderr, gettext("Directory too large to sort\n")); } else { qsort((char *)alist.head, /* LINTED range-checked */ (size_t)(alist.last - alist.head), sizeof (*alist.head), (int (*)(const void *, const void *)) fcmp); } formatf(&alist); for (fp = alist.head; fp < alist.last; fp++) freename(fp->fname); alist.head = NULL; /* * Don't free alist.base, as we'll probably be called * again, and might as well re-use what we've got. */ } if (dirp != NULL) { (void) fprintf(stderr, "\n"); rst_closedir(dirp); } } /* * Print out a pretty listing of a directory */ static void formatf(struct arglist *ap) { struct afile *fp; struct entry *np; /* LINTED: result fits into an int */ int nentry = (int)(ap->last - ap->head); int i, j; uint_t len, w, width = 0, columns, lines; char *cp; FILE *output = stderr; if (ap->head == ap->last) return; if (paginating) { int fds[2]; if (pipe(fds) < 0) { perror(gettext("could not create pipe")); goto no_page; } switch (fork()) { case -1: perror(gettext("could not fork")); goto no_page; case 0: /* * Make sure final output still ends up in * the same place. */ (void) dup2(fileno(stderr), fileno(stdout)); (void) close(fds[0]); (void) dup2(fds[1], fileno(stdin)); execvp(pager_vector[0], pager_vector); perror(gettext("execvp of pager failed")); exit(1); /*NOTREACHED*/ default: (void) close(fds[1]); output = fdopen(fds[0], "w"); if (output != (FILE *)NULL) { break; } perror(gettext("could not open pipe to pager")); output = stderr; no_page: (void) fprintf(stderr, gettext("pagination disabled\n")); paginating = 0; } } for (fp = ap->head; fp < ap->last; fp++) { fp->ftype = inodetype(fp->fnum); np = lookupino(fp->fnum); if (np != NIL) fp->fflags = np->e_flags; else fp->fflags = 0; len = strlen(fmtentry(fp)); if (len > width) width = len; } width += 2; columns = 80 / width; if (columns == 0) columns = 1; lines = (nentry + columns - 1) / columns; for (i = 0; i < lines && !ferror(output); i++) { for (j = 0; j < columns && !ferror(output); j++) { fp = ap->head + j * lines + i; cp = fmtentry(fp); (void) fprintf(output, "%s", cp); if (fp + lines >= ap->last) { (void) fprintf(output, "\n"); break; } w = strlen(cp); while (w < width) { w++; if (fprintf(output, " ") < 0) break; } } } if (paginating) { (void) fclose(output); (void) wait((int *)NULL); } } /* * Comparison routine for qsort. */ static int fcmp(struct afile *f1, struct afile *f2) { return (strcoll(f1->fname, f2->fname)); } /* * Format a directory entry. */ static char * fmtentry(struct afile *fp) { static char fmtres[MAXCOMPLEXLEN]; static int precision = 0; ino_t i; char *cp, *dp, *limit; if (!vflag) { /* MAXCOMPLEXLEN assumed to be >= 1 */ fmtres[0] = '\0'; } else { if (precision == 0) { for (i = maxino; i != 0; i /= 10) precision++; if (sizeof (fmtres) < (unsigned)(precision + 2)) { (void) fprintf(stderr, gettext( "\nInternal check failed, minimum width %d exceeds available size %d\n"), (precision + 2), sizeof (fmtres)); done(1); } } (void) snprintf(fmtres, sizeof (fmtres), "%*ld ", precision, fp->fnum); } dp = &fmtres[strlen(fmtres)]; limit = fmtres + sizeof (fmtres) - 1; if (dflag && BIT(fp->fnum, dumpmap) == 0) *dp++ = '^'; else if ((fp->fflags & NEW) != 0) *dp++ = '*'; else *dp++ = ' '; for (cp = fp->fname; *cp && dp < limit; cp++) /* LINTED: precedence ok, can't fix system macro */ if (!vflag && (!ISPRINT(*cp, wp))) *dp++ = '?'; else *dp++ = *cp; if (fp->ftype == NODE && dp < limit) *dp++ = '/'; *dp++ = 0; return (fmtres); } /* * respond to interrupts */ /* ARGSUSED */ void onintr(int sig) { char buf[300]; if (command == 'i' && reset_OK) longjmp(reset, 1); (void) snprintf(buf, sizeof (buf), gettext("%s interrupted, continue"), progname); if (reply(buf) == FAIL) done(1); } /* * Set up pager_catenated and pager_vector. */ void initpagercmd(void) { char *cp; cp = getenv("PAGER"); if (cp != NULL) pager_catenated = strdup(cp); if ((pager_catenated == NULL) || (*pager_catenated == '\0')) { if (pager_catenated != NULL) free(pager_catenated); pager_catenated = strdup(DEF_PAGER); } if (pager_catenated == NULL) { (void) fprintf(stderr, gettext("out of memory\n")); done(1); } pager_vector = (char **)malloc(sizeof (char *)); if (pager_vector == NULL) { (void) fprintf(stderr, gettext("out of memory\n")); done(1); } pager_len = 1; cp = pager_catenated; (void) setpagerargs(&cp); } /* * Resets pager_catenated and pager_vector from user input. */ void setpagercmd(void) { uint_t catenate_length; int index; /* * We'll get called immediately after setting a pager, due to * our interaction with getcmd()'s internal state. Don't do * anything when that happens. */ if (*input == '\0') return; if (pager_len > 0) { for (index = 0; pager_vector[index] != (char *)NULL; index += 1) free(pager_vector[index]); free(pager_vector); free(pager_catenated); } pager_vector = (char **)malloc(2 * sizeof (char *)); if (pager_vector == NULL) { (void) fprintf(stderr, gettext("out of memory\n")); done(1); } pager_len = 2; pager_vector[0] = strdup(input); if (pager_vector[0] == NULL) { (void) fprintf(stderr, gettext("out of memory\n")); done(1); } if (dflag) (void) fprintf(stderr, gettext("got command `%s'\n"), input); catenate_length = setpagerargs(&nextarg) + strlen(pager_vector[0]) + 1; pager_catenated = (char *)malloc(catenate_length * (size_t)sizeof (char)); if (pager_catenated == (char *)NULL) { (void) fprintf(stderr, gettext("out of memory\n")); done(1); } for (index = 0; pager_vector[index] != (char *)NULL; index += 1) { if (index > 0) (void) strcat(pager_catenated, " "); (void) strcat(pager_catenated, pager_vector[index]); } } /* * Extract arguments for the pager command from getcmd()'s input buffer. */ static uint_t setpagerargs(char **source) { char word[MAXCOMPLEXLEN]; char *cp = *source; uint_t length = 0; while ((cp != (char *)NULL) && (*cp != '\0')) { cp = copynext(cp, word, sizeof (word)); if (dflag) fprintf(stderr, gettext("got word `%s'\n"), word); pager_vector = (char **)realloc(pager_vector, (size_t)sizeof (char *) * (pager_len + 1)); if (pager_vector == (char **)NULL) { (void) fprintf(stderr, gettext("out of memory\n")); done(1); } pager_vector[pager_len - 1] = strdup(word); if (pager_vector[pager_len - 1] == (char *)NULL) { (void) fprintf(stderr, gettext("out of memory\n")); done(1); } length += strlen(word) + 1; pager_len += 1; } pager_vector[pager_len - 1] = (char *)NULL; *source = cp; return (length); } /stdio.h".*sometimes ignored: fprintf/d /stdio.h".*always ignored: snprintf/d /stdio.h".*always ignored: printf/d /string.h".*sometimes ignored: memcpy/d /time.h".*sometimes ignored: time/d /myrcmd.c",.*assigned value never used: retval .*(22[0-9])/d /tape.c",.*assigned value never used: mt .*(22[0-9])/d /main.c",.*modification using a NULL pointer/d /dirs.c",.*name used but not defined: utime /d /lint suppression directive/d /byteorder.c",.*inconsistent use of a value type/d /byteorder.c",.*assigned value never used: allocated at.*byteorder.c/d /unistd.h".*always ignored: sleep/d /unistd.h".*always ignored: execvp/d /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ /* * Modified to recursively extract all files within a subtree * (supressed by the h option) and recreate the heirarchical * structure of that subtree and move extracted files to their * proper homes (supressed by the m option). * Includes the s (skip files) option for use with multiple * dumps on a single tape. * 8/29/80 by Mike Litzkow * * Modified to work on the new file system and to recover from * tape read errors. * 1/19/82 by Kirk McKusick * * Full incremental restore running entirely in user code and * interactive tape browser. * 1/19/83 by Kirk McKusick */ #include "restore.h" #include #include #include #include #include #include eucwidth_t wp; int bflag = 0, dflag = 0, vflag = 0, yflag = 0; int hflag = 1, mflag = 1, paginating = 0, offline = 0, autoload = 0; int autoload_tries; int autoload_period; int cvtflag = 0; /* Converting from old dump format */ char command = '\0'; long dumpnum = 1; int volno = 0; uint_t ntrec; /* blocking factor, in KB */ uint_t saved_ntrec; /* saved blocking factor, in KB */ ssize_t tape_rec_size = 0; /* tape record size (ntrec * tp_bsize) */ size_t newtapebuf_size = 0; /* save size of last call to newtapebuf */ char *progname; char *dumpmap; char *clrimap; char *c_label; /* if non-NULL, we must see this tape label */ ino_t maxino; time_t dumptime; time_t dumpdate; FILE *terminal; char *tmpdir; char *pager_catenated; char **pager_vector; int pager_len; int inattrspace = 0; int savepwd; int32_t tp_bsize = TP_BSIZE_MIN; struct byteorder_ctx *byteorder; static void set_tmpdir(void); int main(int argc, char *argv[]) { static struct arglist alist = { 0, 0, 0, 0, 0 }; int count; char *cp; char *fname; ino_t ino; char *inputdev; char *archivefile = 0; char *symtbl = RESTORESYMTABLE; char name[MAXPATHLEN]; int fflag = 0; struct sigaction sa, osa; int multiplier; char units; if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; if (strcmp("hsmrestore", progname) == 0) { (void) fprintf(stderr, gettext("hsmrestore emulation is no longer supported.\n")); done(1); } /* * Convert the effective uid of 0 to the single privilege * we really want. When running with all privileges, this * is a no-op. When the set-uid bit is stripped restore * still works for local tapes. Fail when trying to access * a remote tape in that case and not immediately. */ (void) __init_suid_priv(0, PRIV_NET_PRIVADDR, (char *)NULL); inputdev = DEFTAPE; /* * This doesn't work because ufsrestore is statically linked: * (void) setlocale(LC_ALL, ""); * The problem seems to be with LC_COLLATE, so set all the * others explicitly. Bug 1157128 was created against the I18N * library. When that bug is fixed this should go back to the way * it was. * XXX 1157128 was closed as a dup of 1099747. That bug was fixed by * disallowing setlocale() to anything other than "C". "" is * allowed, but only if none of the envars LC_ALL, LC_COLLATE, or LANG * select anything other than "C". */ (void) setlocale(LC_CTYPE, ""); (void) setlocale(LC_NUMERIC, ""); (void) setlocale(LC_TIME, ""); (void) setlocale(LC_MONETARY, ""); (void) setlocale(LC_MESSAGES, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); getwidth(&wp); if ((byteorder = byteorder_create()) == NULL) { (void) fprintf(stderr, gettext("Cannot create byteorder context\n")); done(1); } if ((savepwd = open(".", O_RDONLY)) < 0) { (void) fprintf(stderr, gettext("Cannot save current directory context\n")); done(1); } set_tmpdir(); autoload_period = 12; autoload_tries = 12; /* traditional default of ~2.5 minutes */ sa.sa_handler = onintr; sa.sa_flags = SA_RESTART; (void) sigemptyset(&sa.sa_mask); (void) sigaction(SIGINT, &sa, &osa); if (osa.sa_handler == SIG_IGN) (void) sigaction(SIGINT, &osa, (struct sigaction *)0); (void) sigaction(SIGTERM, &sa, &osa); if (osa.sa_handler == SIG_IGN) (void) sigaction(SIGTERM, &osa, (struct sigaction *)0); if (argc < 2) { usage: (void) fprintf(stderr, gettext("Usage:\n\ \t%s tabcdfhsvyLloT [file file ...]\n\ \t%s xabcdfhmsvyLloT [file file ...]\n\ \t%s iabcdfhmsvyLloT\n\ \t%s rabcdfsvyLloT\n\ \t%s RabcdfsvyLloT\n\n\ a requires an archive file name\n\ b requires a blocking factor\n\ f requires a dump file\n\ s requires a file number\n\ L requires a tape label\n\ If set, the envar TMPDIR selects where temporary files are kept\n"), progname, progname, progname, progname, progname); done(1); } argv++; /* the bag-of-options */ argc -= 2; /* count of parameters to the options */ command = '\0'; c_label = (char *)NULL; /* any tape's acceptable */ for (cp = *argv++; *cp; cp++) { switch (*cp) { /* BE CAUTIOUS OF FALLTHROUGHS */ case 'T': if (argc < 1) { (void) fprintf(stderr, gettext( "Missing autoload timeout period\n")); done(1); } count = atoi(*argv); if (count < 1) { (void) fprintf(stderr, gettext( "Unreasonable autoload timeout period `%s'\n"), *argv); done(1); } units = *(*argv + strlen(*argv) - 1); switch (units) { case 's': multiplier = 1; break; case 'h': multiplier = 3600; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'm': multiplier = 60; break; default: (void) fprintf(stderr, gettext( "Unknown timeout units indicator `%c'\n"), units); done(1); } autoload_tries = 1 + ((count * multiplier) / autoload_period); argv++; argc--; break; case 'l': autoload++; break; case 'o': offline++; break; case '-': break; case 'a': if (argc < 1) { (void) fprintf(stderr, gettext("missing archive file name\n")); done(1); } archivefile = *argv++; if (*archivefile == '\0') { (void) fprintf(stderr, gettext("empty archive file name\n")); done(1); } argc--; break; case 'c': cvtflag++; break; case 'd': dflag++; break; case 'D': /* * This used to be the Dflag, but it doesn't * hurt to always check, so was removed. This * case is here for backward compatability. */ break; case 'h': hflag = 0; break; case 'm': mflag = 0; break; case 'v': vflag++; break; case 'y': yflag++; break; case 'f': if (argc < 1) { (void) fprintf(stderr, gettext("missing device specifier\n")); done(1); } inputdev = *argv++; if (*inputdev == '\0') { (void) fprintf(stderr, gettext("empty device specifier\n")); done(1); } fflag++; argc--; break; case 'b': /* * change default tape blocksize */ bflag++; if (argc < 1) { (void) fprintf(stderr, gettext("missing block size\n")); done(1); } saved_ntrec = ntrec = atoi(*argv++); if (ntrec == 0 || (ntrec&1)) { (void) fprintf(stderr, gettext( "Block size must be a positive, even integer\n")); done(1); } ntrec /= (tp_bsize/DEV_BSIZE); argc--; break; case 's': /* * dumpnum (skip to) for multifile dump tapes */ if (argc < 1) { (void) fprintf(stderr, gettext("missing dump number\n")); done(1); } dumpnum = atoi(*argv++); if (dumpnum <= 0) { (void) fprintf(stderr, gettext( "Dump number must be a positive integer\n")); done(1); } argc--; break; case 't': case 'R': case 'r': case 'x': case 'i': if (command != '\0') { (void) fprintf(stderr, gettext( "%c and %c are mutually exclusive\n"), (uchar_t)*cp, (uchar_t)command); goto usage; } command = *cp; break; case 'L': if (argc < 1 || **argv == '\0') { (void) fprintf(stderr, gettext("Missing tape label name\n")); done(1); } c_label = *argv++; /* must get tape with this label */ if (strlen(c_label) > (sizeof (spcl.c_label) - 1)) { c_label[sizeof (spcl.c_label) - 1] = '\0'; (void) fprintf(stderr, gettext( "Truncating label to maximum supported length: `%s'\n"), c_label); } argc--; break; default: (void) fprintf(stderr, gettext("Bad key character %c\n"), (uchar_t)*cp); goto usage; } } if (command == '\0') { (void) fprintf(stderr, gettext("must specify i, t, r, R, or x\n")); goto usage; } setinput(inputdev, archivefile); if (argc == 0) { /* re-use last argv slot for default */ argc = 1; *--argv = mflag ? "." : "2"; } switch (command) { /* * Interactive mode. */ case 'i': setup(); extractdirs(1); initsymtable((char *)0); initpagercmd(); runcmdshell(); done(0); /* NOTREACHED */ /* * Incremental restoration of a file system. */ case 'r': setup(); if (dumptime > 0) { /* * This is an incremental dump tape. */ vprintf(stdout, gettext("Begin incremental restore\n")); initsymtable(symtbl); extractdirs(1); removeoldleaves(); vprintf(stdout, gettext("Calculate node updates.\n")); strcpy(name, "."); name[2] = '\0'; treescan(name, ROOTINO, nodeupdates); attrscan(1, nodeupdates); findunreflinks(); removeoldnodes(); } else { /* * This is a level zero dump tape. */ vprintf(stdout, gettext("Begin level 0 restore\n")); initsymtable((char *)0); extractdirs(1); vprintf(stdout, gettext("Calculate extraction list.\n")); strcpy(name, "."); name[2] = '\0'; treescan(name, ROOTINO, nodeupdates); attrscan(1, nodeupdates); } createleaves(symtbl); createlinks(); setdirmodes(); checkrestore(); if (dflag) { vprintf(stdout, gettext("Verify the directory structure\n")); strcpy(name, "."); name[2] = '\0'; treescan(name, ROOTINO, verifyfile); } dumpsymtable(symtbl, (long)1); done(0); /* NOTREACHED */ /* * Resume an incremental file system restoration. */ case 'R': setupR(); initsymtable(symtbl); skipmaps(); skipdirs(); createleaves(symtbl); createlinks(); setdirmodes(); checkrestore(); dumpsymtable(symtbl, (long)1); done(0); /* NOTREACHED */ /* * List contents of tape. */ case 't': setup(); extractdirs(0); initsymtable((char *)0); if (vflag) printdumpinfo(); while (argc--) { canon(*argv++, name, sizeof (name)); name[strlen(name)+1] = '\0'; ino = dirlookup(name); if (ino == 0) continue; treescan(name, ino, listfile); } done(0); /* NOTREACHED */ /* * Batch extraction of tape contents. */ case 'x': setup(); extractdirs(1); initsymtable((char *)0); while (argc--) { if (mflag) { canon(*argv++, name, sizeof (name)); if (expand(name, 0, &alist) == 0) { /* no meta-characters to expand */ ino = dirlookup(name); if (ino == 0) continue; pathcheck(name); } else { /* add each of the expansions */ while ((alist.last - alist.head) > 0) { fname = alist.head->fname; ino = dirlookup(fname); if (ino != 0) { pathcheck(fname); treescan(fname, ino, addfile); } freename(fname); alist.head++; } alist.head = (struct afile *)NULL; continue; /* argc loop */ } } else { ino = (ino_t)atol(*argv); if ((*(*argv++) == '-') || ino < ROOTINO) { (void) fprintf(stderr, gettext( "bad inode number: %ld\n"), ino); done(1); } name[0] = '\0'; } treescan(name, ino, addfile); attrscan(0, addfile); } createfiles(); createlinks(); setdirmodes(); if (dflag) checkrestore(); done(0); /* NOTREACHED */ } return (0); } /* * Determine where the user wants us to put our temporary files, * and make sure we can actually do so. Bail out if there's a problem. */ void set_tmpdir(void) { int fd; char name[MAXPATHLEN]; tmpdir = getenv("TMPDIR"); if ((tmpdir == (char *)NULL) || (*tmpdir == '\0')) tmpdir = "/tmp"; if (*tmpdir != '/') { (void) fprintf(stderr, gettext("TMPDIR is not an absolute path (`%s').\n"), tmpdir); done(1); } /* * The actual use of tmpdir is in dirs.c, and is of the form * tmpdir + "/rst" + type (three characters) + "%ld.XXXXXX" + * a trailing NUL, where %ld is an arbitrary time_t. * * Thus, the magic 31 is strlen(itoa(MAX_TIME_T)) + "/rst" + * ".XXXXXX" + '\0'. A time_t is 64 bits, so MAX_TIME_T is * LONG_MAX - nineteen digits. In theory, so many things in * ufsrestore will break once time_t's value goes beyond 32 * bits that it's not worth worrying about this particular * instance at this time, but we've got to start somewhere. * * Note that the use of a pid below is just for testing the * validity of the named directory. */ if (strlen(tmpdir) > (MAXPATHLEN - 31)) { (void) fprintf(stderr, gettext("TMPDIR too long\n")); done(1); } /* Guaranteed to fit by above test (sizeof(time_t) >= sizeof(pid_t)) */ (void) snprintf(name, sizeof (name), "%s/rstdir.%ld", tmpdir, getpid()); /* * This is effectively a stripped-down version of safe_open(), * because if the file exists, we want to fail. */ fd = open(name, O_CREAT|O_EXCL|O_RDWR, 0600); if (fd < 0) { perror(gettext("Can not create temporary file")); done(1); } (void) close(fd); if (unlink(name) < 0) { perror(gettext("Can not delete temporary file")); done(1); } } /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ #include "restore.h" /* undef MAXNAMLEN to prevent compiler warnings about redef in dirent.h */ #undef MAXNAMLEN #include static char *keyval(int); static void removexattrs(struct entry *); static void movexattrs(char *, char *); /* * This implements the 't' option. * List entries on the tape. */ long listfile(char *name, ino_t ino, int type) { long descend = hflag ? GOOD : FAIL; if (BIT(ino, dumpmap) == 0) { return (descend); } vprintf(stdout, "%s", type == LEAF ? gettext("leaf") : gettext("dir ")); (void) fprintf(stdout, "%10lu\t%s\n", ino, name); return (descend); } /* * This implements the 'x' option. * Request that new entries be extracted. */ long addfile(char *name, ino_t ino, int type) { struct entry *ep; long descend = hflag ? GOOD : FAIL; char buf[100]; /* Don't know if ino_t is long or long long, so be safe w/ *printf() */ if (BIT(ino, dumpmap) == 0) { if (mflag) { dprintf(stdout, gettext( "%s: not on the volume\n"), name); } else { dprintf(stdout, gettext( "inode %llu: not on the volume\n"), (u_longlong_t)ino); } return (descend); } if (!mflag) { (void) snprintf(buf, sizeof (buf), "./%llu", (u_longlong_t)ino); buf[sizeof (buf) - 1] = '\0'; name = buf; if (type == NODE) { (void) genliteraldir(name, ino); return (descend); } } ep = lookupino(ino); if (ep != NIL) { if (strcmp(name, myname(ep)) == 0) { /* LINTED: result fits into a short */ ep->e_flags |= NEW; return (descend); } type |= LINK; } ep = addentry(name, ino, type); if (type == NODE) newnode(ep); /* LINTED: result fits into a short */ ep->e_flags |= NEW; return (descend); } /* * This is used by the 'i' option to undo previous requests made by addfile. * Delete entries from the request queue. */ /* ARGSUSED */ long deletefile(char *name, ino_t ino, int type) { long descend = hflag ? GOOD : FAIL; struct entry *ep; if (BIT(ino, dumpmap) == 0) { return (descend); } ep = lookupino(ino); if (ep != NIL) { /* LINTED: result fits into a short */ ep->e_flags &= ~NEW; } return (descend); } /* * The following four routines implement the incremental * restore algorithm. The first removes old entries, the second * does renames and calculates the extraction list, the third * cleans up link names missed by the first two, and the final * one deletes old directories. * * Directories cannot be immediately deleted, as they may have * other files in them which need to be moved out first. As * directories to be deleted are found, they are put on the * following deletion list. After all deletions and renames * are done, this list is actually deleted. */ static struct entry *removelist; /* * Remove unneeded leaves from the old tree. * Remove directories from the lookup chains. */ void removeoldleaves(void) { struct entry *ep; ino_t i; vprintf(stdout, gettext("Mark entries to be removed.\n")); for (i = ROOTINO + 1; i < maxino; i++) { if (BIT(i, clrimap)) continue; ep = lookupino(i); if (ep == NIL) continue; while (ep != NIL) { dprintf(stdout, gettext("%s: REMOVE\n"), myname(ep)); removexattrs(ep->e_xattrs); if (ep->e_type == LEAF) { removeleaf(ep); freeentry(ep); } else { mktempname(ep); deleteino(ep->e_ino); /* * once the inode is deleted from the symbol * table, the e_next field is reusable */ ep->e_next = removelist; removelist = ep; } ep = ep->e_links; } } } /* * For each directory entry on the incremental tape, determine which * category it falls into as follows: * KEEP - entries that are to be left alone. * NEW - new entries to be added. * EXTRACT - files that must be updated with new contents. * LINK - new links to be added. * Renames are done at the same time. */ long nodeupdates(char *name, ino_t ino, int type) { struct entry *ep, *np, *ip; long descend = GOOD; int lookuptype = 0; int key = 0; /* key values */ #define ONTAPE 0x1 /* inode is on the tape */ #define INOFND 0x2 /* inode already exists */ #define NAMEFND 0x4 /* name already exists */ #define MODECHG 0x8 /* mode of inode changed */ /* * This routine is called once for each element in the * directory hierarchy, with a full path name. * The "type" value is incorrectly specified as LEAF for * directories that are not on the dump tape. * * Check to see if the file is on the tape. */ if (BIT(ino, dumpmap)) key |= ONTAPE; /* * Check to see if the name exists, and if the name is a link. */ np = lookupname(name); if (np != NIL) { key |= NAMEFND; ip = lookupino(np->e_ino); if (ip == NULL) { (void) fprintf(stderr, gettext("corrupted symbol table\n")); done(1); } if (ip != np) lookuptype = LINK; } /* * Check to see if the inode exists, and if one of its links * corresponds to the name (if one was found). */ ip = lookupino(ino); if (ip != NIL) { key |= INOFND; for (ep = ip->e_links; ep != NIL; ep = ep->e_links) { if (ep == np) { /* * Need to set the NEW flag on the hard link * so it gets created because we extract the * "parent". If the NAMEFND key is set, remove * the leaf. */ if (ip->e_flags & EXTRACT) { if (key & NAMEFND) { removeleaf(np); freeentry(np); np = NIL; key &= ~NAMEFND; } ep->e_flags |= NEW; } else { ip = ep; } break; } } } /* * If both a name and an inode are found, but they do not * correspond to the same file, then both the inode that has * been found and the inode corresponding to the name that * has been found need to be renamed. The current pathname * is the new name for the inode that has been found. Since * all files to be deleted have already been removed, the * named file is either a now-unneeded link, or it must live * under a new name in this dump level. If it is a link, it * can be removed. If it is not a link, it is given a * temporary name in anticipation that it will be renamed * when it is later found by inode number. */ if (((key & (INOFND|NAMEFND)) == (INOFND|NAMEFND)) && ip != np) { if (lookuptype == LINK) { removeleaf(np); freeentry(np); } else { dprintf(stdout, gettext("name/inode conflict, mktempname %s\n"), myname(np)); mktempname(np); } np = NIL; key &= ~NAMEFND; } if ((key & ONTAPE) && (((key & INOFND) && ip->e_type != type) || ((key & NAMEFND) && np->e_type != type))) key |= MODECHG; /* * Decide on the disposition of the file based on its flags. * Note that we have already handled the case in which * a name and inode are found that correspond to different files. * Thus if both NAMEFND and INOFND are set then ip == np. */ switch (key) { /* * A previously existing file has been found. * Mark it as KEEP so that other links to the inode can be * detected, and so that it will not be reclaimed by the search * for unreferenced names. */ case INOFND|NAMEFND: /* LINTED: result fits into a short */ ip->e_flags |= KEEP; dprintf(stdout, "[%s] %s: %s\n", keyval(key), name, flagvalues(ip)); break; /* * A file on the tape has a name which is the same as a name * corresponding to a different file in the previous dump. * Since all files to be deleted have already been removed, * this file is either a now-unneeded link, or it must live * under a new name in this dump level. If it is a link, it * can simply be removed. If it is not a link, it is given a * temporary name in anticipation that it will be renamed * when it is later found by inode number (see INOFND case * below). The entry is then treated as a new file. */ case ONTAPE|NAMEFND: case ONTAPE|NAMEFND|MODECHG: if (lookuptype == LINK || key == (ONTAPE|NAMEFND)) { removeleaf(np); freeentry(np); } else { /* * Create a temporary node only if MODECHG. */ mktempname(np); } /*FALLTHROUGH*/ /* * A previously non-existent file. * Add it to the file system, and request its extraction. * If it is a directory, create it immediately. * (Since the name is unused there can be no conflict) */ case ONTAPE: ep = addentry(name, ino, type); if (type == NODE) newnode(ep); /* LINTED: result fits into a short */ ep->e_flags |= NEW|KEEP; dprintf(stdout, "[%s] %s: %s\n", keyval(key), name, flagvalues(ep)); break; /* * A file with the same inode number, but a different * name has been found. If the other name has not already * been found (indicated by the KEEP flag, see above) then * this must be a new name for the file, and it is renamed. * If the other name has been found then this must be a * link to the file. Hard links to directories are not * permitted, and are either deleted or converted to * symbolic links. Finally, if the file is on the tape, * a request is made to extract it. */ case ONTAPE|INOFND: if (type == LEAF && (ip->e_flags & KEEP) == 0) { /* LINTED: result fits into a short */ ip->e_flags |= EXTRACT; } /*FALLTHROUGH*/ case INOFND: if ((ip->e_flags & KEEP) == 0) { renameit(myname(ip), name); moveentry(ip, name); /* LINTED: result fits into a short */ ip->e_flags |= KEEP; dprintf(stdout, "[%s] %s: %s\n", keyval(key), name, flagvalues(ip)); break; } if (ip->e_type == NODE) { descend = FAIL; (void) fprintf(stderr, gettext( "deleted hard link %s to directory %s\n"), name, myname(ip)); break; } ep = addentry(name, ino, type|LINK); /* LINTED: result fits into a short */ ep->e_flags |= NEW; dprintf(stdout, "[%s] %s: %s|LINK\n", keyval(key), name, flagvalues(ep)); break; /* * A previously known file which is to be updated. */ case ONTAPE|INOFND|NAMEFND: /* * Extract leaf nodes. */ if (type == LEAF) { /* LINTED: result fits into a short */ np->e_flags |= EXTRACT; } /* LINTED: result fits into a short */ np->e_flags |= KEEP; dprintf(stdout, "[%s] %s: %s\n", keyval(key), name, flagvalues(np)); break; /* * An inode is being reused in a completely different way. * Normally an extract can simply do an "unlink" followed * by a "creat". Here we must do effectively the same * thing. The complications arise because we cannot really * delete a directory since it may still contain files * that we need to rename, so we delete it from the symbol * table, and put it on the list to be deleted eventually. * Conversely if a directory is to be created, it must be * done immediately, rather than waiting until the * extraction phase. */ case ONTAPE|INOFND|MODECHG: case ONTAPE|INOFND|NAMEFND|MODECHG: if (ip->e_flags & KEEP) { badentry(ip, gettext("cannot KEEP and change modes")); break; } if (ip->e_type == LEAF) { /* changing from leaf to node */ removeleaf(ip); freeentry(ip); ip = addentry(name, ino, type); newnode(ip); } else { /* changing from node to leaf */ if ((ip->e_flags & TMPNAME) == 0) mktempname(ip); deleteino(ip->e_ino); ip->e_next = removelist; removelist = ip; ip = addentry(name, ino, type); } /* LINTED: result fits into a short */ ip->e_flags |= NEW|KEEP; dprintf(stdout, "[%s] %s: %s\n", keyval(key), name, flagvalues(ip)); break; /* * A hard link to a directory that has been removed. * Ignore it. */ case NAMEFND: dprintf(stdout, gettext("[%s] %s: Extraneous name\n"), keyval(key), name); descend = FAIL; break; /* * If we find a directory entry for a file that is not on * the tape, then we must have found a file that was created * while the dump was in progress. Since we have no contents * for it, we discard the name knowing that it will be on the * next incremental tape. */ case 0: (void) fprintf(stderr, gettext("%s: (inode %lu) not found on volume\n"), name, ino); break; /* * If any of these arise, something is grievously wrong with * the current state of the symbol table. */ case INOFND|NAMEFND|MODECHG: case NAMEFND|MODECHG: case INOFND|MODECHG: (void) fprintf(stderr, "[%s] %s: %s\n", keyval(key), name, gettext("inconsistent state")); done(1); /* NOTREACHED */ /* * These states "cannot" arise for any state of the symbol table. */ case ONTAPE|MODECHG: case MODECHG: default: (void) fprintf(stderr, "[%s] %s: %s\n", keyval(key), name, gettext("impossible state")); done(1); /* NOTREACHED */ } return (descend); } /* * Calculate the active flags in a key. */ static char * keyval(int key) { static char keybuf[32]; /* Note longest case is everything except |NIL */ (void) strcpy(keybuf, "|NIL"); keybuf[0] = '\0'; if (key & ONTAPE) (void) strcat(keybuf, "|ONTAPE"); if (key & INOFND) (void) strcat(keybuf, "|INOFND"); if (key & NAMEFND) (void) strcat(keybuf, "|NAMEFND"); if (key & MODECHG) (void) strcat(keybuf, "|MODECHG"); return (&keybuf[1]); } /* * Find unreferenced link names. */ void findunreflinks(void) { struct entry *ep, *np; ino_t i; vprintf(stdout, gettext("Find unreferenced names.\n")); for (i = ROOTINO; i < maxino; i++) { ep = lookupino(i); if (ep == NIL || ep->e_type == LEAF || BIT(i, dumpmap) == 0) continue; for (np = ep->e_entries; np != NIL; np = np->e_sibling) { if (np->e_flags == 0) { dprintf(stdout, gettext( "%s: remove unreferenced name\n"), myname(np)); removeleaf(np); freeentry(np); } } } /* * Any leaves remaining in removed directories are unreferenced. */ for (ep = removelist; ep != NIL; ep = ep->e_next) { for (np = ep->e_entries; np != NIL; np = np->e_sibling) { if (np->e_type == LEAF) { if (np->e_flags != 0) badentry(np, gettext( "unreferenced with flags")); dprintf(stdout, gettext( "%s: remove unreferenced name\n"), myname(np)); removeleaf(np); freeentry(np); } } } } /* * Remove old nodes (directories). * Note that this routine runs in O(N*D) where: * N is the number of directory entries to be removed. * D is the maximum depth of the tree. * If N == D this can be quite slow. If the list were * topologically sorted, the deletion could be done in * time O(N). */ void removeoldnodes(void) { struct entry *ep, **prev; long change; vprintf(stdout, gettext("Remove old nodes (directories).\n")); do { change = 0; prev = &removelist; for (ep = removelist; ep != NIL; ep = *prev) { if (ep->e_entries != NIL) { prev = &ep->e_next; continue; } *prev = ep->e_next; removenode(ep); freeentry(ep); change++; } } while (change); for (ep = removelist; ep != NIL; ep = ep->e_next) badentry(ep, gettext("cannot remove, non-empty")); } /* * This is the routine used to extract files for the 'r' command. * Extract new leaves. */ void createleaves(char *symtabfile) { struct entry *ep; char name[MAXCOMPLEXLEN]; ino_t first; int curvol; if (command == 'R') { vprintf(stdout, gettext("Continue extraction of new leaves\n")); } else { vprintf(stdout, gettext("Extract new leaves.\n")); dumpsymtable(symtabfile, volno); } first = lowerbnd(ROOTINO); curvol = volno; while (curfile.ino < maxino) { first = lowerbnd(first); /* * If the next available file is not the one which we * expect then we have missed one or more files. Since * we do not request files that were not on the tape, * the lost files must have been due to a tape read error, * or a file that was removed while the dump was in progress. * * The loop will terminate with first == maxino, if not * sooner. Due to the e_flags manipulation, lowerbnd() * will never return its argument. */ while (first < curfile.ino) { ep = lookupino(first); if (ep == NIL) { (void) fprintf(stderr, gettext("%d: bad first\n"), first); done(1); } (void) fprintf(stderr, gettext("%s: not found on volume\n"), myname(ep)); /* LINTED: result fits into a short */ ep->e_flags &= ~(NEW|EXTRACT); first = lowerbnd(first); } /* * If we find files on the tape that have no corresponding * directory entries, then we must have found a file that * was created while the dump was in progress. Since we have * no name for it, we discard it knowing that it will be * on the next incremental tape. */ if (first != curfile.ino) { (void) fprintf(stderr, gettext("expected next file %d, got %d\n"), first, curfile.ino); skipfile(); goto next; } ep = lookupino(curfile.ino); if (ep == NIL) { (void) fprintf(stderr, gettext("unknown file on volume\n")); done(1); } if ((ep->e_flags & (NEW|EXTRACT)) == 0) badentry(ep, gettext("unexpected file on volume")); /* * If the file is to be extracted, then the old file must * be removed since its type may change from one leaf type * to another (eg "file" to "character special"). But we * also need to preserve any existing extended attributes; * so first rename the file, then move its attributes, then * remove it. */ if ((ep->e_flags & EXTRACT) != 0) { char *sname = savename(ep->e_name); complexcpy(name, myname(ep), MAXCOMPLEXLEN); mktempname(ep); (void) extractfile(name); movexattrs(myname(ep), name); removeleaf(ep); freename(ep->e_name); ep->e_name = sname; ep->e_namlen = strlen(ep->e_name); /* LINTED: result fits into a short */ ep->e_flags &= ~REMOVED; } else { (void) extractfile(myname(ep)); } /* LINTED: result fits into a short */ ep->e_flags &= ~(NEW|EXTRACT); /* * We checkpoint the restore after every tape reel, so * as to simplify the amount of work required by the * 'R' command. */ next: if (curvol != volno) { dumpsymtable(symtabfile, volno); skipmaps(); curvol = volno; } } } /* * This is the routine used to extract files for the 'x' and 'i' commands. * Efficiently extract a subset of the files on a tape. */ void createfiles(void) { ino_t first, next, last; struct entry *ep; int curvol, nextvol; vprintf(stdout, gettext("Extract requested files\n")); first = lowerbnd(ROOTINO); last = upperbnd(maxino - 1); nextvol = volnumber(first); if (nextvol == 0) { curfile.action = SKIP; getvol(1); skipmaps(); skipdirs(); } for (;;) { first = lowerbnd(first); last = upperbnd(last); /* * Check to see if any files remain to be extracted */ if (first > last) return; /* * If a map of inode numbers to tape volumes is * available, then select the next volume to be read. */ if (nextvol > 0) { nextvol = volnumber(first); if (nextvol != volno) { curfile.action = UNKNOWN; getvol(nextvol); skipmaps(); } } /* * Reject any volumes with inodes greater than * the last one needed. This will only be true * if the above code has not selected a volume. */ while (curfile.ino > last) { curfile.action = SKIP; getvol(0); skipmaps(); skipdirs(); } /* * Decide on the next inode needed. * Skip across the inodes until it is found * or an out of order volume change is encountered */ next = lowerbnd(curfile.ino); do { curvol = volno; while (next > curfile.ino && volno == curvol) skipfile(); skipmaps(); skipdirs(); } while (volno == curvol + 1); /* * If volume change out of order occurred the * current state must be recalculated */ if (volno != curvol) continue; /* * If the current inode is greater than the one we were * looking for then we missed the one we were looking for. * Since we only attempt to extract files listed in the * dump map, the lost files must have been due to a tape * read error, or a file that was removed while the dump * was in progress. Thus we report all requested files * between the one we were looking for, and the one we * found as missing, and delete their request flags. */ while (next < curfile.ino) { ep = lookupino(next); if (ep == NIL) { (void) fprintf(stderr, gettext("corrupted symbol table\n")); done(1); } (void) fprintf(stderr, gettext("%s: not found on volume\n"), myname(ep)); /* LINTED: result fits into a short */ ep->e_flags &= ~NEW; next = lowerbnd(next); } /* * The current inode is the one that we are looking for, * so extract it per its requested name. */ if (next == curfile.ino && next <= last) { ep = lookupino(next); if (ep == NIL) { (void) fprintf(stderr, gettext("corrupted symbol table\n")); done(1); } (void) extractfile(myname(ep)); /* LINTED: result fits into a short */ ep->e_flags &= ~NEW; if (volno != curvol) skipmaps(); } } } /* * Add links. */ void createlinks(void) { struct entry *np, *ep; ino_t i; int dfd; char *to, *from; int saverr; vprintf(stdout, gettext("Add links\n")); for (i = ROOTINO; i < maxino; i++) { ep = lookupino(i); if (ep == NIL) continue; to = savename(myname(ep)); for (np = ep->e_links; np != NIL; np = np->e_links) { if ((np->e_flags & NEW) == 0) continue; resolve(myname(np), &dfd, &from); if (dfd != AT_FDCWD) { if (fchdir(dfd) < 0) { saverr = errno; (void) fprintf(stderr, gettext( "%s->%s: link failed: %s\n"), from, to, strerror(saverr)); (void) close(dfd); continue; } } if (ep->e_type == NODE) { (void) lf_linkit(to, from, SYMLINK); } else { (void) lf_linkit(to, from, HARDLINK); } /* LINTED: result fits into a short */ np->e_flags &= ~NEW; if (dfd != AT_FDCWD) { fchdir(savepwd); (void) close(dfd); } } freename(to); } } /* * Check the symbol table. * We do this to insure that all the requested work was done, and * that no temporary names remain. */ void checkrestore(void) { struct entry *ep; ino_t i; vprintf(stdout, gettext("Check the symbol table.\n")); for (i = ROOTINO; i < maxino; i++) { for (ep = lookupino(i); ep != NIL; ep = ep->e_links) { /* LINTED: result fits into a short */ ep->e_flags &= ~KEEP; if (ep->e_type == NODE) { /* LINTED: result fits into a short */ ep->e_flags &= ~(NEW|EXISTED); } if ((ep->e_flags & ~(XATTR|XATTRROOT)) != 0) badentry(ep, gettext("incomplete operations")); } } } /* * Compare with the directory structure on the tape * A paranoid check that things are as they should be. */ long verifyfile(char *name, ino_t ino, int type) { struct entry *np, *ep; long descend = GOOD; ep = lookupname(name); if (ep == NIL) { (void) fprintf(stderr, gettext("Warning: missing name %s\n"), name); return (FAIL); } np = lookupino(ino); if (np != ep) descend = FAIL; for (; np != NIL; np = np->e_links) if (np == ep) break; if (np == NIL) { (void) fprintf(stderr, gettext("missing inumber %d\n"), ino); done(1); } if (ep->e_type == LEAF && type != LEAF) badentry(ep, gettext("type should be LEAF")); return (descend); } /* * This routine does not actually remove any attribute files, it * just removes entries from the symbol table. The attribute files * themselves are assumed to be removed automatically when the * parent file is removed. */ static void removexattrs(struct entry *ep) { struct entry *np = ep; if (ep == NIL) return; for (np = ep->e_entries; np != NIL; np = np->e_sibling) { if (np->e_type == NODE) { removexattrs(np); } else { np->e_flags |= REMOVED; freeentry(np); } } ep->e_flags |= REMOVED; freeentry(ep); } /* * Move all the extended attributes associated with orig to * the file named by the second argument (targ). */ static void movexattrs(char *orig, char *targ) { char *to, *from; int fromfd, fromdir, tofd, todir, tfd; DIR *dirp = NULL; struct dirent *dp = NULL; fromfd = tofd = fromdir = todir = tfd = -1; resolve(orig, &tfd, &from); if (tfd == AT_FDCWD && pathconf(orig, _PC_XATTR_EXISTS) != 1) { /* no attributes to move */ return; } if ((fromfd = openat64(tfd, from, O_RDONLY|O_NONBLOCK)) == -1) { fprintf(stderr, gettext("%s: cannot move attributes: "), from); perror(""); if (tfd != AT_FDCWD) (void) close(tfd); goto out; } if (fpathconf(fromfd, _PC_XATTR_EXISTS) != 1) { /* no attributes to move */ if (tfd != AT_FDCWD) (void) close(tfd); goto out; } if ((fromdir = openat64(fromfd, ".", O_RDONLY|O_NONBLOCK|O_XATTR)) == -1) { fprintf(stderr, gettext("%s: cannot access attributes: "), from); perror(""); if (tfd != AT_FDCWD) (void) close(tfd); goto out; } if (tfd != AT_FDCWD) (void) close(tfd); resolve(targ, &tfd, &to); if ((tofd = openat64(tfd, to, O_RDONLY|O_NONBLOCK)) == -1 || (todir = openat64(tofd, ".", O_RDONLY|O_NONBLOCK|O_XATTR)) == -1) { fprintf(stderr, gettext("%s: cannot create attributes: "), to); perror(""); goto out; } if (tfd != AT_FDCWD) (void) close(tfd); (void) close(tofd); if ((tfd = dup(fromdir)) == -1 || (dirp = fdopendir(tfd)) == NULL) { fprintf(stderr, gettext("%s: cannot allocate DIR structure to attribute " "directory: "), from); perror(""); if (tfd != -1) (void) close(tfd); goto out; } while ((dp = readdir(dirp)) != NULL) { if ((dp->d_name[0] == '.' && dp->d_name[1] == '\0') || (dp->d_name[0] == '.' && dp->d_name[1] == '.' && dp->d_name[2] == '\0')) continue; if ((renameat(fromdir, dp->d_name, todir, dp->d_name)) == -1) { fprintf(stderr, gettext("%s: cannot move attribute %s: "), from, dp->d_name); goto out; } } out: if (fromfd != -1) (void) close(fromfd); if (tofd != -1) (void) close(tofd); if (dirp != NULL) (void) closedir(dirp); if (fromdir != -1) (void) close(fromdir); if (todir != -1) (void) close(todir); } /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _RESTORE_H #define _RESTORE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define ROOTINO UFSROOTINO #define SUPPORTS_MTB_TAPE_FORMAT #include #include #include /* * Flags */ extern int cvtflag; /* convert from old to new tape format */ extern int bflag; /* set input block size */ extern int dflag; /* print out debugging info */ extern int hflag; /* restore heirarchies */ extern int mflag; /* restore by name instead of inode number */ extern int vflag; /* print out actions taken */ extern int yflag; /* always try to recover from tape errors */ extern int paginating; /* paginate bulk interactive output */ extern int offline; /* take tape offline when closing */ extern int autoload; /* wait for tape to autoload; implies offline */ /* * Global variables */ extern int autoload_tries; /* number of times to check on autoload */ extern int autoload_period; /* seconds, tries*period = total wait time */ extern struct byteorder_ctx *byteorder; extern char *progname; /* our name */ extern char *dumpmap; /* map of inodes on this dump tape */ extern char *clrimap; /* map of inodes to be deleted */ extern char *c_label; /* label we expect to see on the tape */ extern ino_t maxino; /* highest numbered inode in this file system */ extern long dumpnum; /* location of the dump on this tape */ extern int volno; /* current volume being read */ extern uint_t ntrec; /* number of tp_bsize records per tape block */ extern uint_t saved_ntrec; /* number of tp_bsize records per tape block */ extern ssize_t tape_rec_size; /* tape record size (tp_bsize * ntrec) */ extern time_t dumptime; /* time that this dump begins */ extern time_t dumpdate; /* time that this dump was made */ extern char command; /* opration being performed */ extern FILE *terminal; /* file descriptor for the terminal input */ extern char *tmpdir; /* where to put the rst{dir,mode}... files */ extern char *pager_catenated; /* pager command and args */ extern char **pager_vector; /* pager_catenated split up for execve() */ extern int pager_len; /* # elements in pager_vector; includes NULL */ extern int inattrspace; /* true if currently scanning attribute space */ extern int savepwd; /* this is where restore is running from */ /* * Each file in the file system is described by one of these entries * Note that the e_next field is used by the symbol table hash lists * and then reused by the remove code after the entry is removed from * the symbol table. */ struct entry { char *e_name; /* the current name of this entry */ ushort_t e_namlen; /* length of this name */ char e_type; /* type of this entry, see below */ short e_flags; /* status flags, see below */ ino_t e_ino; /* inode number in previous file sys */ long e_index; /* unique index (for dumpped table) */ struct entry *e_parent; /* pointer to parent directory (..) */ struct entry *e_sibling; /* next element in this directory (.) */ struct entry *e_links; /* hard links to this inode */ struct entry *e_entries; /* for directories, their entries */ struct entry *e_xattrs; /* pointer to extended attribute root */ struct entry *e_next; /* hash chain list and removelist */ }; /* types */ #define LEAF 1 /* non-directory entry */ #define NODE 2 /* directory entry */ #define LINK 4 /* synthesized type, stripped by addentry */ #define ROOT 8 /* synthesized type, stripped by addentry */ /* flags */ #define EXTRACT 0x0001 /* entry is to be replaced from the tape */ #define NEW 0x0002 /* a new entry to be extracted */ #define KEEP 0x0004 /* entry is not to change */ #define REMOVED 0x0010 /* entry has been removed */ #define TMPNAME 0x0020 /* entry has been given a temporary name */ #define EXISTED 0x0040 /* directory already existed during extract */ #define XATTR 0x0080 /* file belongs in an attribute tree */ #define XATTRROOT 0x0100 /* directory is root of an attribute tree */ /* * functions defined on entry structs */ extern struct entry *lookupino(ino_t); extern struct entry *lookupname(char *); extern struct entry *addentry(char *, ino_t, int); extern void deleteino(ino_t); extern char *myname(struct entry *); extern void freeentry(struct entry *); extern void moveentry(struct entry *, char *); extern char *savename(char *); extern void freename(char *); extern void dumpsymtable(char *, int); extern void initsymtable(char *); extern void mktempname(struct entry *); extern char *gentempname(struct entry *); extern void newnode(struct entry *); extern void removenode(struct entry *); extern void removeleaf(struct entry *); extern ino_t lowerbnd(ino_t); extern ino_t upperbnd(ino_t); extern void badentry(struct entry *, char *); extern char *flagvalues(struct entry *); extern ino_t dirlookup(char *); #define NIL ((struct entry *)(0)) /* * Definitions for library routines operating on directories. * These definitions are used only for reading fake directory * entries from restore's temporary file "restoresymtable" * These have little to do with real directory entries. */ #if !defined(DEV_BSIZE) #define DEV_BSIZE 512 #endif #define DIRBLKSIZ DEV_BSIZE typedef struct _rstdirdesc { int dd_fd; int dd_refcnt; /* so rst_{open,close}dir() avoid leaking memory */ off64_t dd_loc; off64_t dd_size; char dd_buf[DIRBLKSIZ]; } RST_DIR; /* * Constants associated with entry structs */ #define HARDLINK 1 #define SYMLINK 2 #define TMPHDR "RSTTMP" /* * The entry describes the next file available on the tape */ extern struct context { char *name; /* name of file */ ino_t ino; /* inumber of file */ struct dinode *dip; /* pointer to inode */ int action; /* action being taken on this file */ int ts; /* TS_* type of tape record */ } curfile; /* actions */ #define USING 1 /* extracting from the tape */ #define SKIP 2 /* skipping */ #define UNKNOWN 3 /* disposition or starting point is unknown */ /* * Structure and routines associated with listing directories * and expanding meta-characters in pathnames. */ struct afile { ino_t fnum; /* inode number of file */ char *fname; /* file name */ short fflags; /* extraction flags, if any */ char ftype; /* file type, e.g. LEAF or NODE */ }; struct arglist { struct afile *head; /* start of argument list */ struct afile *last; /* end of argument list */ struct afile *base; /* current list arena */ int nent; /* maximum size of list */ char *cmd; /* the current command */ }; /* * Other exported routines */ #ifdef __STDC__ extern int mkentry(char *, ino_t, struct arglist *); extern int expand(char *, int, struct arglist *); extern ino_t psearch(char *); extern void metaget(char **data, size_t *size); extern void metaproc(char *, char *, size_t); extern long listfile(char *, ino_t, int); extern long addfile(char *, ino_t, int); extern long deletefile(char *, ino_t, int); extern long nodeupdates(char *, ino_t, int); extern long verifyfile(char *, ino_t, int); extern void extractdirs(int genmode); extern void skipdirs(void); extern void treescan(char *, ino_t, long (*)(char *, ino_t, int)); extern RST_DIR *rst_opendir(char *); extern void rst_closedir(RST_DIR *); extern struct direct *rst_readdir(RST_DIR *); extern void setdirmodes(void); extern int genliteraldir(char *, ino_t); extern int inodetype(ino_t); extern void done(int) __NORETURN; extern void runcmdshell(void); extern void canon(char *, char *, size_t); extern void onintr(int); extern void removeoldleaves(void); extern void findunreflinks(void); extern void removeoldnodes(void); extern void createleaves(char *); extern void createfiles(void); extern void createlinks(void); extern void checkrestore(void); extern void setinput(char *, char *); extern void newtapebuf(size_t); extern void setup(void); extern void setupR(void); extern void getvol(int); extern void printdumpinfo(void); extern int extractfile(char *); extern void skipmaps(void); extern void skipfile(void); extern void getfile(void (*)(char *, size_t), void (*)(char *, size_t)); extern void null(char *, size_t); extern void findtapeblksize(int); extern void flsht(void); extern void closemt(int); extern int readhdr(struct s_spcl *); extern int gethead(struct s_spcl *); extern int volnumber(ino_t); extern void findinode(struct s_spcl *); extern void pathcheck(char *); extern void renameit(char *, char *); extern int linkit(char *, char *, int); extern int lf_linkit(char *, char *, int); extern int reply(char *); /*PRINTFLIKE1*/ extern void panic(const char *, ...); extern char *lctime(time_t *); extern int safe_open(int, const char *file, int mode, int perms); extern FILE *safe_fopen(const char *filename, const char *smode, int perms); extern void reset_dump(void); extern void get_next_device(void); extern void initpagercmd(void); extern void resolve(char *, int *, char **); extern int complexcopy(char *, char *, int); #else /* !STDC */ extern int mkentry(); extern int expand(); extern ino_t psearch(); extern void metaget(); extern void metaproc(); extern long listfile(); extern long addfile(); extern long deletefile(); extern long nodeupdates(); extern long verifyfile(); extern void extractdirs(); extern void skipdirs(); extern void treescan(); extern RST_DIR *rst_opendir(); extern void rst_closedir(); extern struct direct *rst_readdir(); extern void setdirmodes(); extern int genliteraldir(); extern int inodetype(); extern void done(); extern void runcmdshell(); extern void canon(); extern void onintr(); extern void removeoldleaves(); extern void findunreflinks(); extern void removeoldnodes(); extern void createleaves(); extern void createfiles(); extern void createlinks(); extern void checkrestore(); extern void setinput(); extern void newtapebuf(); extern void setup(); extern void setupR(); extern void getvol(); extern void printdumpinfo(); extern int extractfile(); extern void skipmaps(); extern void skipfile(); extern void getfile(); extern void null(); extern void findtapeblksize(); extern void flsht(); extern void closemt(); extern int readhdr(); extern int gethead(); extern int volnumber(); extern void findinode(); extern void pathcheck(); extern void renameit(); extern int linkit(); extern int lf_linkit(); extern int reply(); extern void panic(); extern char *lctime(); extern int safe_open(); extern FILE *safe_fopen(); extern void reset_dump(); extern void get_next_device(); extern void initpagercmd(); extern void resolve(); extern int complexcopy(); #endif /* STDC */ /* * Useful macros */ #define MWORD(m, i) ((m)[(ino_t)((i)-1)/NBBY]) #define MBIT(i) (1<<((ino_t)((i)-1)%NBBY)) #define BIS(i, w) (MWORD((w), (i)) |= MBIT(i)) #define BIC(i, w) (MWORD((w), (i)) &= ~MBIT(i)) #define BIT(i, w) (MWORD((w), (i)) & MBIT(i)) /* * Macro used to get to the last segment of a complex string */ #define LASTPART(s) {int len = strlen(s)+1;\ while (s[len] != '\0')\ {s += len; len = strlen(s)+1; }\ } /* * Define maximum length of complex string. For now we use * MAXPATHLEN * 2 since recursion is not (yet) supported. * (add 3 for the 3 NULL characters in a two-part path) * Note that each component of a complex string is still * limited to MAXPATHLEN length. */ #define MAXCOMPLEXLEN (MAXPATHLEN*2 + 3) /* * Define an overflow-free version of howmany so that we don't * run into trouble with large files. */ #define d_howmany(x, y) ((x) / (y) + ((x) % (y) != 0)) /* * Defines used by findtapeblksize() */ #define TAPE_FILE 0 #define ARCHIVE_FILE 1 #undef setjmp #define setjmp(b) sigsetjmp((b), 1) #define longjmp siglongjmp #define jmp_buf sigjmp_buf #define chown lchown /* * Defaults */ #define TAPE "/dev/rmt/0b" /* default tape device */ #define RESTORESYMTABLE "./restoresymtable" #define dprintf if (dflag) (void) fprintf #define vprintf if (vflag) (void) fprintf #define GOOD 1 #define FAIL 0 #define ALLOW_OFFLINE 0 #define FORCE_OFFLINE 1 /* offline drive for autoload */ #define DEF_PAGER "/usr/bin/more" #ifdef __cplusplus } #endif #endif /* _RESTORE_H */ /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1996,1998,2001 by Sun Microsystems, Inc. * All rights reserved. */ /* * These routines maintain the symbol table which tracks the state * of the file system being restored. They provide lookup by either * name or inode number. They also provide for creation, deletion, * and renaming of entries. Because of the dynamic nature of pathnames, * names should not be saved, but always constructed just before they * are needed, by calling "myname". */ #include "restore.h" #include /* * The following variables define the inode symbol table. * The primary hash table is dynamically allocated based on * the number of inodes in the file system (maxino), scaled by * HASHFACTOR. The variable "entry" points to the hash table; * the variable "entrytblsize" indicates its size (in entries). */ #define HASHFACTOR 5 static struct entry **entry; static uint_t entrytblsize; static void addino(ino_t, struct entry *); static struct entry *lookupparent(char *); static void removeentry(struct entry *); /* * Look up an entry by inode number */ struct entry * lookupino(ino_t inum) { struct entry *ep; if (inum < ROOTINO || inum >= maxino) return (NIL); for (ep = entry[inum % entrytblsize]; ep != NIL; ep = ep->e_next) if (ep->e_ino == inum) return (ep); return (NIL); } /* * We now ignore inodes that are out of range. This * allows us to attempt to proceed in the face of * a corrupted archive, albeit with future complaints * about failed inode lookups. We only complain once * about range problems, to avoid irritating the user * without providing any useful information. Failed * lookups have the bogus name, which is useful, so * they always happen. */ static int complained_about_range = 0; /* * Add an entry into the entry table */ static void addino(ino_t inum, struct entry *np) { struct entry **epp; if (inum < ROOTINO || inum >= maxino) { if (!complained_about_range) { panic(gettext("%s: out of range %d\n"), "addino", inum); complained_about_range = 1; } return; } epp = &entry[inum % entrytblsize]; np->e_ino = inum; np->e_next = *epp; *epp = np; if (dflag) for (np = np->e_next; np != NIL; np = np->e_next) if (np->e_ino == inum) badentry(np, gettext("duplicate inum")); } /* * Delete an entry from the entry table. We assume our caller * arranges for the necessary memory reclamation, if needed. */ void deleteino(ino_t inum) { struct entry *next; struct entry **prev; if (inum < ROOTINO || inum >= maxino) { if (!complained_about_range) { panic(gettext("%s: out of range %d\n"), "deleteino", inum); complained_about_range = 1; } return; } prev = &entry[inum % entrytblsize]; for (next = *prev; next != NIL; next = next->e_next) { if (next->e_ino == inum) { next->e_ino = 0; *prev = next->e_next; return; } prev = &next->e_next; } } /* * Look up an entry by name. * NOTE: this function handles "complex" pathnames (as returned * by myname()) for extended file attributes. The name string * provided to this function should be terminated with *two* * NULL characters. */ struct entry * lookupname(char *name) { struct entry *ep; char *np, *cp; char buf[MAXPATHLEN]; if (strlen(name) > (sizeof (buf) - 1)) { (void) fprintf(stderr, gettext("%s: ignoring too-long name\n"), "lookupname"); return (NIL); } cp = name; for (ep = lookupino(ROOTINO); ep != NIL; ep = ep->e_entries) { np = buf; while (*cp != '/' && *cp != '\0') *np++ = *cp++; *np = '\0'; for (; ep != NIL; ep = ep->e_sibling) if (strcmp(ep->e_name, buf) == 0) break; if (*cp++ == '\0') { if (*cp != '\0') { ep = ep->e_xattrs; /* * skip over the "./" prefix on all * extended attribute paths */ cp += 2; } if (*cp == '\0') return (ep); } if (ep == NIL) break; } return (NIL); } /* * Look up the parent of a pathname. This routine accepts complex * names so the provided name argument must terminate with two NULLs. */ static struct entry * lookupparent(char *name) { struct entry *ep; char *tailindex, savechar, *lastpart; int xattrparent = 0; /* find the last component of the complex name */ lastpart = name; LASTPART(lastpart); tailindex = strrchr(lastpart, '/'); if (tailindex == 0) { if (lastpart == name) return (NIL); /* * tailindex normaly points to the '/' character * dividing the path, but in the case of an extended * attribute transition it will point to the NULL * separator in front of the attribute path. */ tailindex = lastpart - 1; xattrparent = 1; } else { *tailindex = '\0'; } savechar = *(tailindex+1); *(tailindex+1) = '\0'; ep = lookupname(name); if (ep != NIL && !xattrparent && ep->e_type != NODE) panic(gettext("%s is not a directory\n"), name); if (!xattrparent) *tailindex = '/'; *(tailindex+1) = savechar; return (ep); } /* * Determine the current pathname of a node or leaf. * The returned pathname will be multiple strings with NULL separators: * * .//entry\0/attrentry\0/...\0\0 * ^ ^ ^ ^ * return pntr entry attr recursive attr terminator * * Guaranteed to return a name that fits within MAXCOMPLEXLEN and is * terminated with two NULLs. */ char * myname(struct entry *ep) { char *cp; struct entry *root = lookupino(ROOTINO); static char namebuf[MAXCOMPLEXLEN]; cp = &namebuf[MAXCOMPLEXLEN - 3]; *(cp + 1) = '\0'; *(cp + 2) = '\0'; while (cp > &namebuf[ep->e_namlen]) { cp -= ep->e_namlen; bcopy(ep->e_name, cp, (size_t)ep->e_namlen); if (ep == root) return (cp); if (ep->e_flags & XATTRROOT) *(--cp) = '\0'; else *(--cp) = '/'; ep = ep->e_parent; } panic(gettext("%s%s: pathname too long\n"), "...", cp); return (cp); } /* * Unused symbol table entries are linked together on a freelist * headed by the following pointer. */ static struct entry *freelist = NIL; /* * add an entry to the symbol table */ struct entry * addentry(char *name, ino_t inum, int type) { struct entry *np, *ep; char *cp; if (freelist != NIL) { np = freelist; freelist = np->e_next; (void) bzero((char *)np, (size_t)sizeof (*np)); } else { np = (struct entry *)calloc(1, sizeof (*np)); if (np == NIL) { (void) fprintf(stderr, gettext("no memory to extend symbol table\n")); done(1); } } np->e_type = type & ~(LINK|ROOT); if (inattrspace) np->e_flags |= XATTR; ep = lookupparent(name); if (ep == NIL) { if (inum != ROOTINO || lookupino(ROOTINO) != NIL) { (void) fprintf(stderr, gettext( "%s: bad name %s\n"), "addentry", name); assert(0); done(1); } np->e_name = savename(name); /* LINTED: savename guarantees that strlen fits in e_namlen */ np->e_namlen = strlen(name); np->e_parent = np; addino(ROOTINO, np); return (np); } if (np->e_flags & XATTR) { /* * skip to the last part of the complex string: it * containes the extended attribute file name. */ LASTPART(name); } cp = strrchr(name, '/'); if (cp == NULL) cp = name; else cp++; np->e_name = savename(cp); /* LINTED: savename guarantees that strlen will fit */ np->e_namlen = strlen(np->e_name); np->e_parent = ep; /* * Extended attribute root directories must be linked to their * "parents" via the e_xattrs field. Other entries are simply * added to their parent directories e_entries list. */ if ((type & ROOT) && (np->e_flags & XATTR)) { /* link this extended attribute root dir to its "parent" */ ep->e_xattrs = np; } else { /* add this entry to the entry list of the parent dir */ np->e_sibling = ep->e_entries; ep->e_entries = np; } if (type & LINK) { ep = lookupino(inum); if (ep == NIL) { /* XXX just bail on this one and continue? */ (void) fprintf(stderr, gettext("link to non-existent name\n")); done(1); } np->e_ino = inum; np->e_links = ep->e_links; ep->e_links = np; } else if (inum != 0) { ep = lookupino(inum); if (ep != NIL) panic(gettext("duplicate entry\n")); else addino(inum, np); } return (np); } /* * delete an entry from the symbol table */ void freeentry(struct entry *ep) { struct entry *np; ino_t inum; if ((ep->e_flags & REMOVED) == 0) badentry(ep, gettext("not marked REMOVED")); if (ep->e_type == NODE) { if (ep->e_links != NIL) badentry(ep, gettext("freeing referenced directory")); if (ep->e_entries != NIL) badentry(ep, gettext("freeing non-empty directory")); } if (ep->e_ino != 0) { np = lookupino(ep->e_ino); if (np == NIL) badentry(ep, gettext("lookupino failed")); if (np == ep) { inum = ep->e_ino; deleteino(inum); if (ep->e_links != NIL) addino(inum, ep->e_links); } else { for (; np != NIL; np = np->e_links) { if (np->e_links == ep) { np->e_links = ep->e_links; break; } } if (np == NIL) badentry(ep, gettext("link not found")); } } removeentry(ep); freename(ep->e_name); ep->e_next = freelist; freelist = ep; } /* * Relocate an entry in the tree structure */ void moveentry(struct entry *ep, char *newname) { struct entry *np; char *cp; np = lookupparent(newname); if (np == NIL) badentry(ep, gettext("cannot move ROOT")); if (np != ep->e_parent) { removeentry(ep); ep->e_parent = np; ep->e_sibling = np->e_entries; np->e_entries = ep; } /* find the last component of the complex name */ LASTPART(newname); cp = strrchr(newname, '/') + 1; if (cp == (char *)1) cp = newname; freename(ep->e_name); ep->e_name = savename(cp); /* LINTED: savename guarantees that strlen will fit */ ep->e_namlen = strlen(cp); if (strcmp(gentempname(ep), ep->e_name) == 0) { /* LINTED: result fits in a short */ ep->e_flags |= TMPNAME; } else { /* LINTED: result fits in a short */ ep->e_flags &= ~TMPNAME; } } /* * Remove an entry in the tree structure */ static void removeentry(struct entry *ep) { struct entry *np; np = ep->e_parent; if (ep->e_flags & XATTRROOT) { if (np->e_xattrs == ep) np->e_xattrs = NIL; else badentry(ep, gettext( "parent does not reference this xattr tree")); } else if (np->e_entries == ep) { np->e_entries = ep->e_sibling; } else { for (np = np->e_entries; np != NIL; np = np->e_sibling) { if (np->e_sibling == ep) { np->e_sibling = ep->e_sibling; break; } } if (np == NIL) badentry(ep, gettext( "cannot find entry in parent list")); } } /* * Table of unused string entries, sorted by length. * * Entries are allocated in STRTBLINCR sized pieces so that names * of similar lengths can use the same entry. The value of STRTBLINCR * is chosen so that every entry has at least enough space to hold * a "struct strtbl" header. Thus every entry can be linked onto an * apprpriate free list. * * NB. The macro "allocsize" below assumes that "struct strhdr" * has a size that is a power of two. Also, an extra byte is * allocated for the string to provide space for the two NULL * string terminator required for extended attribute paths. */ struct strhdr { struct strhdr *next; }; #define STRTBLINCR ((size_t)sizeof (struct strhdr)) #define allocsize(size) (((size) + 2 + STRTBLINCR - 1) & ~(STRTBLINCR - 1)) static struct strhdr strtblhdr[allocsize(MAXCOMPLEXLEN) / STRTBLINCR]; /* * Allocate space for a name. It first looks to see if it already * has an appropriate sized entry, and if not allocates a new one. */ char * savename(char *name) { struct strhdr *np; size_t len, as; char *cp; if (name == NULL) { (void) fprintf(stderr, gettext("bad name\n")); done(1); } len = strlen(name); if (len > MAXPATHLEN) { (void) fprintf(stderr, gettext("name too long\n")); done(1); } as = allocsize(len); np = strtblhdr[as / STRTBLINCR].next; if (np != NULL) { strtblhdr[as / STRTBLINCR].next = np->next; cp = (char *)np; } else { /* Note that allocsize() adds 2 for the trailing \0s */ cp = malloc(as); if (cp == NULL) { (void) fprintf(stderr, gettext("no space for string table\n")); done(1); } } (void) strcpy(cp, name); /* add an extra null for complex (attribute) name support */ cp[len+1] = '\0'; return (cp); } /* * Free space for a name. The resulting entry is linked onto the * appropriate free list. */ void freename(char *name) { struct strhdr *tp, *np; /* NULL case should never happen, but might as well be careful */ if (name != NULL) { tp = &strtblhdr[allocsize(strlen(name)) / STRTBLINCR]; /*LINTED [name points to at least sizeof (struct strhdr)]*/ np = (struct strhdr *)name; np->next = tp->next; tp->next = np; } } /* * Useful quantities placed at the end of a dumped symbol table. */ struct symtableheader { int volno; uint_t stringsize; uint_t entrytblsize; time_t dumptime; time_t dumpdate; ino_t maxino; uint_t ntrec; }; /* * dump a snapshot of the symbol table */ void dumpsymtable(char *filename, int checkpt) { struct entry *ep, *tep; ino_t i; struct entry temp, *tentry; int mynum = 1; uint_t stroff; FILE *fp; struct symtableheader hdr; vprintf(stdout, gettext("Check pointing the restore\n")); if ((fp = safe_fopen(filename, "w", 0600)) == (FILE *)NULL) { perror("fopen"); (void) fprintf(stderr, gettext("cannot create save file %s for symbol table\n"), filename); done(1); } clearerr(fp); /* * Assign an index to each entry * Write out the string entries */ for (i = ROOTINO; i < maxino; i++) { for (ep = lookupino(i); ep != NIL; ep = ep->e_links) { ep->e_index = mynum++; (void) fwrite(ep->e_name, sizeof (ep->e_name[0]), (size_t)allocsize(ep->e_namlen), fp); } } /* * Convert e_name pointers to offsets, other pointers * to indices, and output */ tep = &temp; stroff = 0; for (i = ROOTINO; !ferror(fp) && i < maxino; i++) { for (ep = lookupino(i); !ferror(fp) && ep != NIL; ep = ep->e_links) { bcopy((char *)ep, (char *)tep, sizeof (*tep)); /* LINTED: type pun ok */ tep->e_name = (char *)stroff; stroff += allocsize(ep->e_namlen); tep->e_parent = (struct entry *)ep->e_parent->e_index; if (ep->e_links != NIL) tep->e_links = (struct entry *)ep->e_links->e_index; if (ep->e_sibling != NIL) tep->e_sibling = (struct entry *)ep->e_sibling->e_index; if (ep->e_entries != NIL) tep->e_entries = (struct entry *)ep->e_entries->e_index; if (ep->e_xattrs != NIL) tep->e_xattrs = (struct entry *)ep->e_xattrs->e_index; if (ep->e_next != NIL) tep->e_next = (struct entry *)ep->e_next->e_index; (void) fwrite((char *)tep, sizeof (*tep), 1, fp); } } /* * Convert entry pointers to indices, and output */ for (i = 0; !ferror(fp) && i < (ino_t)entrytblsize; i++) { if (entry[i] == NIL) tentry = NIL; else tentry = (struct entry *)entry[i]->e_index; (void) fwrite((char *)&tentry, sizeof (tentry), 1, fp); } if (!ferror(fp)) { /* Ought to have a checksum or magic number */ hdr.volno = checkpt; hdr.maxino = maxino; hdr.entrytblsize = entrytblsize; hdr.stringsize = stroff; hdr.dumptime = dumptime; hdr.dumpdate = dumpdate; hdr.ntrec = ntrec; (void) fwrite((char *)&hdr, sizeof (hdr), 1, fp); } if (ferror(fp)) { perror("fwrite"); panic(gettext("output error to file %s writing symbol table\n"), filename); } (void) fclose(fp); } /* * Initialize a symbol table from a file */ void initsymtable(char *filename) { char *base; off64_t tblsize; struct entry *ep; struct entry *baseep, *lep; struct symtableheader hdr; struct stat64 stbuf; uint_t i; int fd; vprintf(stdout, gettext("Initialize symbol table.\n")); if (filename == NULL) { if ((maxino / HASHFACTOR) > UINT_MAX) { (void) fprintf(stderr, gettext("file system too large\n")); done(1); } /* LINTED: result fits in entrytblsize */ entrytblsize = maxino / HASHFACTOR; entry = calloc((size_t)entrytblsize, sizeof (*entry)); if (entry == NULL) { (void) fprintf(stderr, gettext("no memory for entry table\n")); done(1); } ep = calloc(1, sizeof (*ep)); if (ep == NULL) { (void) fprintf(stderr, gettext("no memory for entry\n")); done(1); } ep->e_type = NODE; ep->e_name = savename("."); ep->e_namlen = 1; ep->e_parent = ep; addino(ROOTINO, ep); /* LINTED: result fits in a short */ ep->e_flags |= NEW; return; } if ((fd = open(filename, O_RDONLY|O_LARGEFILE)) < 0) { perror("open"); (void) fprintf(stderr, gettext("cannot open symbol table file %s\n"), filename); done(1); } if (fstat64(fd, &stbuf) < 0) { perror("stat"); (void) fprintf(stderr, gettext("cannot stat symbol table file %s\n"), filename); (void) close(fd); done(1); } /* * The symbol table file is too small so say we can't read it. */ if (stbuf.st_size < sizeof (hdr)) { (void) fprintf(stderr, gettext("cannot read symbol table file %s\n"), filename); (void) close(fd); done(1); } tblsize = stbuf.st_size - sizeof (hdr); if (tblsize > ULONG_MAX) { (void) fprintf(stderr, gettext("symbol table file too large\n")); (void) close(fd); done(1); } /* LINTED tblsize fits in a size_t */ base = calloc((size_t)sizeof (char), (size_t)tblsize); if (base == NULL) { (void) fprintf(stderr, gettext("cannot allocate space for symbol table\n")); (void) close(fd); done(1); } /* LINTED tblsize fits in a size_t */ if (read(fd, base, (size_t)tblsize) < 0 || read(fd, (char *)&hdr, sizeof (hdr)) < 0) { perror("read"); (void) fprintf(stderr, gettext("cannot read symbol table file %s\n"), filename); (void) close(fd); done(1); } (void) close(fd); switch (command) { case 'r': case 'M': /* * For normal continuation, insure that we are using * the next incremental tape */ if (hdr.dumpdate != dumptime) { if (hdr.dumpdate < dumptime) (void) fprintf(stderr, gettext( "Incremental volume too low\n")); else (void) fprintf(stderr, gettext( "Incremental volume too high\n")); done(1); } break; case 'R': /* * For restart, insure that we are using the same tape */ curfile.action = SKIP; dumptime = hdr.dumptime; dumpdate = hdr.dumpdate; if (!bflag) newtapebuf(hdr.ntrec); getvol(hdr.volno); break; default: (void) fprintf(stderr, gettext("initsymtable called from command %c\n"), (uchar_t)command); done(1); /*NOTREACHED*/ } maxino = hdr.maxino; entrytblsize = hdr.entrytblsize; /*LINTED [pointer cast alignment]*/ entry = (struct entry **) (base + tblsize - (entrytblsize * sizeof (*entry))); if (((ulong_t)entry % 4) != 0) { (void) fprintf(stderr, gettext("Symbol table file corrupted\n")); done(1); } /*LINTED [rvalue % 4 == 0] */ baseep = (struct entry *) (base + hdr.stringsize - sizeof (*baseep)); if (((ulong_t)baseep % 4) != 0) { (void) fprintf(stderr, gettext("Symbol table file corrupted\n")); done(1); } lep = (struct entry *)entry; for (i = 0; i < entrytblsize; i++) { if (entry[i] == NIL) continue; entry[i] = &baseep[(long)entry[i]]; } for (ep = &baseep[1]; ep < lep; ep++) { ep->e_name = base + (long)ep->e_name; ep->e_parent = &baseep[(long)ep->e_parent]; if (ep->e_sibling != NIL) ep->e_sibling = &baseep[(long)ep->e_sibling]; if (ep->e_links != NIL) ep->e_links = &baseep[(long)ep->e_links]; if (ep->e_entries != NIL) ep->e_entries = &baseep[(long)ep->e_entries]; if (ep->e_xattrs != NIL) ep->e_xattrs = &baseep[(long)ep->e_xattrs]; if (ep->e_next != NIL) ep->e_next = &baseep[(long)ep->e_next]; } } /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include "restore.h" #include #include #include #include #include #include #include /* for expdev */ #include #include #include #include #define MAXINO 65535 /* KLUDGE */ #define MAXTAPES 128 static size_t fssize = MAXBSIZE; /* preferred size of writes to filesystem */ int mt = -1; static int continuemap = 0; char magtape[BUFSIZ]; int pipein = 0; char *host; /* used in dumprmt.c */ daddr32_t rec_position; static char *archivefile; /* used in metamucil.c */ static int bct; /* block # index into tape record buffer */ static int numtrec; /* # of logical blocks in current tape record */ static char *tbf = NULL; static size_t tbfsize = 0; static int recsread; union u_spcl u_spcl; static union u_spcl endoftapemark; static struct s_spcl dumpinfo; static long blksread; /* # of logical blocks actually read/touched */ static long tapea; /* current logical block # on tape */ static uchar_t tapesread[MAXTAPES]; static jmp_buf restart; static int gettingfile = 0; /* restart has a valid frame */ static int ofile; static char *map, *beginmap; static char *endmap; static char lnkbuf[MAXPATHLEN + 2]; static int pathlen; static int inodeinfo; /* Have starting volume information */ static int hostinfo; /* Have dump host information */ static int autoload_tape(void); static void setdumpnum(void); static void metacheck(struct s_spcl *); static void xtrmeta(char *, size_t); static void metaskip(char *, size_t); static void xtrfile(char *, size_t); static void xtrskip(char *, size_t); static void xtrlnkfile(char *, size_t); static void xtrlnkskip(char *, size_t); static void xtrmap(char *, size_t); static void xtrmapskip(char *, size_t); static void readtape(char *); static int checkvol(struct s_spcl *, int); static void accthdr(struct s_spcl *); static int ishead(struct s_spcl *); static int checktype(struct s_spcl *, int); static void metaset(char *name); /* * Set up an input source */ void setinput(char *source, char *archive) { flsht(); archivefile = archive; if (bflag == 0) { ntrec = ((CARTRIDGETREC > HIGHDENSITYTREC) ? (NTREC > CARTRIDGETREC ? NTREC : CARTRIDGETREC) : (NTREC > HIGHDENSITYTREC ? NTREC : HIGHDENSITYTREC)); saved_ntrec = (ntrec * (tp_bsize/DEV_BSIZE)); } newtapebuf(ntrec); terminal = stdin; if (source == NULL) { /* A can't-happen */ (void) fprintf(stderr, gettext("Internal consistency check failed.\n")); done(1); } if (strchr(source, ':')) { char *tape; host = source; tape = strchr(host, ':'); *tape++ = '\0'; if (strlen(tape) > (sizeof (magtape) - 1)) { (void) fprintf(stderr, gettext("Tape name too long\n")); done(1); } (void) strcpy(magtape, tape); if (rmthost(host, ntrec) == 0) done(1); } else { if (strlen(source) > (sizeof (magtape) - 1)) { (void) fprintf(stderr, gettext("Tape name too long\n")); done(1); } /* Not remote, no need for privileges */ __priv_relinquish(); host = NULL; if (strcmp(source, "-") == 0) { /* * Since input is coming from a pipe we must establish * our own connection to the terminal. */ terminal = fopen("/dev/tty", "r"); if (terminal == NULL) { int saverr = errno; char *msg = gettext("Cannot open(\"/dev/tty\")"); errno = saverr; perror(msg); terminal = fopen("/dev/null", "r"); if (terminal == NULL) { saverr = errno; msg = gettext( "Cannot open(\"/dev/null\")"); errno = saverr; perror(msg); done(1); } } pipein++; if (archive) { (void) fprintf(stderr, gettext( "Cannot specify an archive file when reading from a pipe\n")); done(1); } } (void) strcpy(magtape, source); } } void newtapebuf(size_t size) { size_t nsize; nsize = size * tp_bsize; ntrec = size; if (nsize <= tbfsize) return; if (tbf != NULL) free(tbf); tbf = (char *)malloc(nsize); if (tbf == NULL) { (void) fprintf(stderr, gettext("Cannot allocate space for buffer\n")); done(1); } tbfsize = nsize; } /* * Verify that the tape drive can be accessed and * that it actually is a dump tape. */ void setup(void) { int i, j; int32_t *ip; struct stat stbuf; size_t mapsize; char *syment = RESTORESYMTABLE; vprintf(stdout, gettext("Verify volume and initialize maps\n")); if (archivefile) { mt = open(archivefile, O_RDONLY|O_LARGEFILE); if (mt < 0) { perror(archivefile); done(1); } volno = 0; } else if (host) { if ((mt = rmtopen(magtape, O_RDONLY)) < 0) { perror(magtape); done(1); } volno = 1; } else { if (pipein) mt = 0; else if ((mt = open(magtape, O_RDONLY|O_LARGEFILE)) < 0) { perror(magtape); done(1); } volno = 1; } setdumpnum(); flsht(); if (!pipein && !bflag) if (archivefile) findtapeblksize(ARCHIVE_FILE); else findtapeblksize(TAPE_FILE); if (bflag == 1) { tape_rec_size = saved_ntrec * DEV_BSIZE; } /* * Get the first header. If c_magic is NOT NFS_MAGIC or if * the checksum is in error, it will fail. The magic could then * be either OFS_MAGIC or MTB_MAGIC. If OFS_MAGIC, assume we * have an old dump, and try to convert it. If it is MTB_MAGIC, we * procees this after. */ if ((gethead(&spcl) == FAIL) && (spcl.c_magic != MTB_MAGIC)) { bct--; /* push back this block */ blksread--; tapea--; cvtflag++; if (gethead(&spcl) == FAIL) { (void) fprintf(stderr, gettext("Volume is not in dump format\n")); done(1); } (void) fprintf(stderr, gettext("Converting to new file system format.\n")); } /* * The above gethead will have failed if the magic is * MTB_MAGIC. If that is true, we need to adjust tp_bsize. * We have assumed to this time that tp_bsize was 1024, if * this is a newer dump, get the real tp_bsize from the header, * and recalculate ntrec, numtrec. */ if (spcl.c_magic == MTB_MAGIC) { tp_bsize = spcl.c_tpbsize; if ((tp_bsize % TP_BSIZE_MIN != 0) || (tp_bsize > TP_BSIZE_MAX)) { (void) fprintf(stderr, gettext("Volume is not in dump format\n")); done(1); } ntrec = (tape_rec_size/tp_bsize); numtrec = ntrec; newtapebuf(ntrec); bct--; /* push back this block */ blksread--; tapea--; /* we have to re-do this in case checksum is wrong */ if (gethead(&spcl) == FAIL) { (void) fprintf(stderr, gettext("Volume is not in dump format\n")); done(1); } } if (vflag) byteorder_banner(byteorder, stdout); if (pipein) { endoftapemark.s_spcl.c_magic = cvtflag ? OFS_MAGIC : ((tp_bsize == TP_BSIZE_MIN) ? NFS_MAGIC : MTB_MAGIC); endoftapemark.s_spcl.c_type = TS_END; /* * include this since the `resync' loop in findinode * expects to find a header with the c_date field * filled in. */ endoftapemark.s_spcl.c_date = spcl.c_date; ip = (int32_t *)&endoftapemark; /*LINTED [assertion always true]*/ assert((sizeof (endoftapemark) % sizeof (int32_t)) == 0); j = sizeof (endoftapemark) / sizeof (int32_t); i = 0; do i += *ip++; while (--j) ; endoftapemark.s_spcl.c_checksum = CHECKSUM - i; } if (vflag && command != 't') printdumpinfo(); dumptime = spcl.c_ddate; dumpdate = spcl.c_date; if (stat(".", &stbuf) < 0) { perror(gettext("cannot stat .")); done(1); } if (stbuf.st_blksize >= tp_bsize && stbuf.st_blksize <= MAXBSIZE) { /* LINTED: value fits in a size_t */ fssize = stbuf.st_blksize; } else { fssize = MAXBSIZE; } if (checkvol(&spcl, 1) == FAIL) { (void) fprintf(stderr, gettext("This is not volume 1 of the dump\n")); done(1); } if (readhdr(&spcl) == FAIL) panic(gettext("no header after volume mark!\n")); findinode(&spcl); /* sets curfile, resyncs the tape if need be */ if (checktype(&spcl, TS_CLRI) == FAIL) { (void) fprintf(stderr, gettext("Cannot find file removal list\n")); done(1); } maxino = (unsigned)((spcl.c_count * tp_bsize * NBBY) + 1); dprintf(stdout, "maxino = %lu\n", maxino); /* * Allocate space for at least MAXINO inodes to allow us * to restore partial dump tapes written before dump was * fixed to write out the entire inode map. */ if (maxino > ULONG_MAX) { (void) fprintf(stderr, gettext("file system too large\n")); done(1); } /* LINTED maxino size-checked above */ mapsize = (size_t)d_howmany(maxino > MAXINO ? maxino : MAXINO, NBBY); beginmap = map = calloc((size_t)1, mapsize); if (map == (char *)NIL) { (void) fprintf(stderr, gettext("no memory for file removal list\n")); done(1); } endmap = map + mapsize; clrimap = map; curfile.action = USING; continuemap = 1; getfile(xtrmap, xtrmapskip); if (MAXINO > maxino) maxino = MAXINO; if (checktype(&spcl, TS_BITS) == FAIL) { /* if we have TS_CLRI then no TS_BITS then a TS_END */ /* then we have an empty dump file */ if (gethead(&spcl) == GOOD && checktype(&spcl, TS_END) == GOOD) { if ((command == 'r') || (command == 'R')) { initsymtable(syment); dumpsymtable(syment, volno); } done(0); } /* otherwise we have an error */ (void) fprintf(stderr, gettext("Cannot find file dump list\n")); done(1); } /* LINTED maxino size-checked above */ mapsize = (size_t)d_howmany(maxino, NBBY); beginmap = map = calloc((size_t)1, mapsize); if (map == (char *)NULL) { (void) fprintf(stderr, gettext("no memory for file dump list\n")); done(1); } endmap = map + mapsize; dumpmap = map; curfile.action = USING; continuemap = 1; getfile(xtrmap, xtrmapskip); continuemap = 0; } /* * Initialize fssize variable for 'R' command to work. */ void setupR(void) { struct stat stbuf; if (stat(".", &stbuf) < 0) { perror(gettext("cannot stat .")); done(1); } if (stbuf.st_blksize >= tp_bsize && stbuf.st_blksize <= MAXBSIZE) { /* LINTED: value fits in a size_t */ fssize = stbuf.st_blksize; } else { fssize = MAXBSIZE; } } /* * Prompt user to load a new dump volume. * "Nextvol" is the next suggested volume to use. * This suggested volume is enforced when doing full * or incremental restores, but can be overrridden by * the user when only extracting a subset of the files. * * first_time is used with archive files and can have 1 of 3 states: * FT_STATE_1 Tape has not been read yet * FT_STATE_2 Tape has been read but not positioned past directory * information * FT_STATE_3 Tape has been read and is reading file information */ #define FT_STATE_1 1 #define FT_STATE_2 2 #define FT_STATE_3 3 void getvol(int nextvol) { int newvol; long savecnt, savetapea, wantnext; long i; union u_spcl tmpspcl; #define tmpbuf tmpspcl.s_spcl char buf[TP_BSIZE_MAX]; static int first_time = FT_STATE_1; if (tbf == NULL) { (void) fprintf(stderr, gettext( "Internal consistency failure in getvol: tbf is NULL\n")); done(1); } if (nextvol == 1) { for (i = 0; i < MAXTAPES; i++) tapesread[i] = 0; gettingfile = 0; } if (pipein) { if (nextvol != 1) panic(gettext("changing volumes on pipe input\n")); if (volno == 1) return; goto gethdr; } savecnt = blksread; /* ignore volume verification tape i/o */ savetapea = tapea; again: if (pipein) done(1); /* pipes do not get a second chance */ if (command == 'R' || command == 'r' || curfile.action != SKIP) { wantnext = 1; newvol = nextvol; } else { wantnext = 0; newvol = 0; } if (autoload) { if ((volno == 1) && (nextvol == 1)) { tapesread[volno-1]++; return; } if (autoload_tape()) { wantnext = 1; newvol = nextvol; goto gethdr; } } while (newvol <= 0) { int n = 0; for (i = 0; i < MAXTAPES; i++) if (tapesread[i]) n++; if (n == 0) { (void) fprintf(stderr, "%s", gettext( "You have not read any volumes yet.\n\ Unless you know which volume your file(s) are on you should start\n\ with the last volume and work towards the first.\n")); } else { (void) fprintf(stderr, gettext("You have read volumes")); (void) strcpy(tbf, ": "); for (i = 0; i < MAXTAPES; i++) if (tapesread[i]) { (void) fprintf(stderr, "%s%ld", tbf, i+1); (void) strcpy(tbf, ", "); } (void) fprintf(stderr, "\n"); } do { (void) fprintf(stderr, gettext("Specify next volume #: ")); (void) fflush(stderr); /* LINTED tbfsize is limited to a few MB */ (void) fgets(tbf, (int)tbfsize, terminal); } while (!feof(terminal) && tbf[0] == '\n'); if (feof(terminal)) done(1); newvol = atoi(tbf); if (newvol <= 0) { (void) fprintf(stderr, gettext( "Volume numbers are positive numerics\n")); } if (newvol > MAXTAPES) { (void) fprintf(stderr, gettext( "This program can only deal with %d volumes\n"), MAXTAPES); newvol = 0; } } if (newvol == volno) { tapesread[volno-1]++; return; } closemt(ALLOW_OFFLINE); /* * XXX: if we are switching devices, we should probably try * the device once without prompting to enable unattended * operation. */ if (host) (void) fprintf(stderr, gettext( "Mount volume %d\nthen enter volume name on host %s (default: %s) "), newvol, host, magtape); else (void) fprintf(stderr, gettext( "Mount volume %d\nthen enter volume name (default: %s) "), newvol, magtape); (void) fflush(stderr); /* LINTED tbfsize is limited to a few MB */ (void) fgets(tbf, (int)tbfsize, terminal); if (feof(terminal)) done(1); /* * XXX We don't allow rotating among tape hosts, just drives. */ if (tbf[0] != '\n') { (void) strncpy(magtape, tbf, sizeof (magtape)); magtape[sizeof (magtape) - 1] = '\0'; /* LINTED unsigned -> signed conversion ok */ i = (int)strlen(magtape); if (magtape[i - 1] == '\n') magtape[i - 1] = '\0'; } if ((host != NULL && (mt = rmtopen(magtape, O_RDONLY)) == -1) || (host == NULL && (mt = open(magtape, O_RDONLY|O_LARGEFILE)) == -1)) { int error = errno; (void) fprintf(stderr, gettext("Cannot open %s: %s\n"), magtape, strerror(error)); volno = -1; goto again; } gethdr: volno = newvol; setdumpnum(); flsht(); if (!pipein && !bflag && archivefile && (first_time == FT_STATE_1)) { first_time = FT_STATE_2; findtapeblksize(TAPE_FILE); } if (readhdr(&tmpbuf) == FAIL) { (void) fprintf(stderr, gettext("volume is not in dump format\n")); volno = 0; goto again; } if (checkvol(&tmpbuf, volno) == FAIL) { (void) fprintf(stderr, gettext("Wrong volume (%d)\n"), tmpbuf.c_volume); volno = 0; goto again; } if (((time_t)(tmpbuf.c_date) != dumpdate) || ((time_t)(tmpbuf.c_ddate) != dumptime)) { char *tmp_ct; time_t lc_date = (time_t)tmpbuf.c_date; /* * This is used to save the return value from lctime(), * since that's volatile across lctime() invocations. */ tmp_ct = strdup(lctime(&lc_date)); if (tmp_ct == (char *)0) { (void) fprintf(stderr, gettext( "Cannot allocate space for time string\n")); done(1); } (void) fprintf(stderr, gettext("Wrong dump date\n\tgot: %s\twanted: %s"), tmp_ct, lctime(&dumpdate)); volno = 0; free(tmp_ct); goto again; } tapesread[volno-1]++; blksread = savecnt; tapea = savetapea; /* * If continuing from the previous volume, skip over any * blocks read already at the end of the previous volume. * * If coming to this volume at random, skip to the beginning * of the next record. */ if (tmpbuf.c_type == TS_TAPE && (tmpbuf.c_flags & DR_NEWHEADER)) { if (!wantnext) { if (archivefile && first_time == FT_STATE_2) { first_time = FT_STATE_3; } recsread = tmpbuf.c_firstrec; tapea = tmpbuf.c_tapea; dprintf(stdout, "restore skipping %d records\n", tmpbuf.c_count); for (i = tmpbuf.c_count; i > 0; i--) readtape(buf); } else if (tmpbuf.c_firstrec != 0) { savecnt = blksread; savetapea = tapea; if (archivefile && first_time == FT_STATE_2) { /* * subtract 2, 1 for archive file's TS_END * and 1 for tape's TS_TAPE */ first_time = FT_STATE_3; i = tapea - tmpbuf.c_tapea - 2; } else { i = tapea - tmpbuf.c_tapea; } if (i > 0) dprintf(stdout, gettext( "restore skipping %d duplicate records\n"), i); else if (i < 0) dprintf(stdout, gettext( "restore duplicate record botch (%d)\n"), i); while (--i >= 0) readtape(buf); blksread = savecnt; tapea = savetapea + 1; /* <= (void) gethead() below */ } } if (curfile.action == USING) { if (volno == 1) panic(gettext("active file into volume 1\n")); return; } (void) gethead(&spcl); findinode(&spcl); /* do we always restart files in full? */ if (gettingfile) { /* i.e. will we lose metadata? */ gettingfile = 0; longjmp(restart, 1); /* will this set f1 & f2? */ } } /* * handle multiple dumps per tape by skipping forward to the * appropriate one. Note we don't use absolute positioning, * as that may take a very long time. */ static void setdumpnum(void) { struct mtop tcom; int retval; if (dumpnum == 1 || volno != 1) return; if (pipein) { (void) fprintf(stderr, gettext("Cannot have multiple dumps on pipe input\n")); done(1); } tcom.mt_op = MTFSF; tcom.mt_count = dumpnum - 1; if (host) retval = rmtioctl(MTFSF, dumpnum - 1); else retval = ioctl(mt, (int)MTIOCTOP, (char *)&tcom); if (retval < 0) perror("ioctl MTFSF"); } void printdumpinfo(void) { int i; time_t date; static char *epoch = NULL; if (epoch == NULL) { epoch = strdup(gettext("the epoch\n")); if (epoch == NULL) { (void) fprintf(stderr, gettext("Out of memory\n")); return; } } date = (time_t)dumpinfo.c_date; (void) fprintf(stdout, gettext("Dump date: %s"), lctime(&date)); date = (time_t)dumpinfo.c_ddate; (void) fprintf(stdout, gettext("Dumped from: %s"), (dumpinfo.c_ddate == 0) ? epoch : lctime(&date)); if (hostinfo) { (void) fprintf(stdout, gettext("Level %d dump of %s on %.*s:%s\n"), dumpinfo.c_level, dumpinfo.c_filesys, sizeof (dumpinfo.c_host), dumpinfo.c_host, dumpinfo.c_dev); (void) fprintf(stdout, gettext("Label: %.*s\n"), sizeof (dumpinfo.c_label), dumpinfo.c_label); } if (inodeinfo) { (void) fprintf(stdout, gettext("Starting inode numbers by volume:\n")); for (i = 1; i <= dumpinfo.c_volume; i++) (void) fprintf(stdout, gettext("\tVolume %d: %6d\n"), i, dumpinfo.c_inos[i]); } } int extractfile(char *name) { static int complained_chown = 0; static int complained_lchown = 0; static int complained_chmod = 0; static int complained_utime = 0; static int complained_mknod = 0; mode_t mode; time_t timep[2]; struct entry *ep; uid_t uid; gid_t gid; char *errmsg; int result, saverr; dev_t full_dev; int dfd; char *rname; curfile.name = name; curfile.action = USING; timep[0] = (time_t)curfile.dip->di_atime; timep[1] = (time_t)curfile.dip->di_mtime; mode = curfile.dip->di_mode; uid = curfile.dip->di_suid == UID_LONG ? curfile.dip->di_uid : (uid_t)curfile.dip->di_suid; gid = curfile.dip->di_sgid == GID_LONG ? curfile.dip->di_gid : (gid_t)curfile.dip->di_sgid; resolve(name, &dfd, &rname); if (dfd != AT_FDCWD) { if (fchdir(dfd) < 0) { saverr = errno; (void) fprintf(stderr, gettext( "%s: unable to set attribute context: %s\n"), rname, strerror(saverr)); skipfile(); (void) close(dfd); return (FAIL); } } switch (mode & IFMT) { default: (void) fprintf(stderr, gettext("%s: unknown file mode 0%lo\n"), rname, (ulong_t)(mode&IFMT)); skipfile(); result = FAIL; break; case IFSOCK: vprintf(stdout, gettext("skipped socket %s\n"), rname); skipfile(); result = GOOD; break; case IFDIR: if (mflag) { ep = lookupname(name); if (ep == NIL || ep->e_flags & EXTRACT) { panic(gettext( "directory %s was not restored\n"), rname); skipfile(); result = FAIL; break; } skipfile(); result = GOOD; break; } vprintf(stdout, gettext("extract file %s\n"), rname); result = genliteraldir(rname, curfile.ino); break; case IFLNK: lnkbuf[0] = '\0'; pathlen = 0; getfile(xtrlnkfile, xtrlnkskip); if (pathlen == 0) { vprintf(stdout, gettext( "%s: zero length symbolic link (ignored)\n"), rname); result = GOOD; break; } if ((result = lf_linkit(lnkbuf, rname, SYMLINK)) != GOOD) break; /* 1254700: set uid/gid (previously missing) */ if (lchown(rname, uid, gid) < 0 && !complained_lchown) { /* Just a warning */ saverr = errno; errmsg = gettext( "Unable to restore ownership of symlink %s: %s\n"); (void) fprintf(stderr, errmsg, rname, strerror(saverr)); (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_lchown = 1; } metaset(rname); result = GOOD; break; case IFCHR: case IFBLK: case IFIFO: vprintf(stdout, gettext("extract special file %s\n"), rname); /* put device rdev into dev_t expanded format */ /* XXX does this always do the right thing? */ /* XXX does dump do the right thing? */ if (((curfile.dip->di_ordev & 0xFFFF0000) == 0) || ((curfile.dip->di_ordev & 0xFFFF0000) == 0xFFFF0000)) { full_dev = expdev((unsigned)(curfile.dip->di_ordev)); } else { /* LINTED sign extension ok */ full_dev = (unsigned)(curfile.dip->di_ordev); } if (mknod(rname, mode, full_dev) < 0) { struct stat64 s[1]; saverr = errno; if ((stat64(rname, s)) || ((s->st_mode & S_IFMT) != (mode & S_IFMT)) || (s->st_rdev != full_dev)) { if (saverr != EPERM || !complained_mknod) { (void) fprintf(stderr, "%s: ", rname); (void) fflush(stderr); errno = saverr; perror(gettext( "cannot create special file")); if (saverr == EPERM) { (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_mknod = 1; } } skipfile(); result = FAIL; break; } } if (chown(rname, uid, gid) < 0 && !complained_chown) { /* Just a warning */ saverr = errno; errmsg = gettext( "Unable to restore ownership of %s: %s\n"); (void) fprintf(stderr, errmsg, rname, strerror(saverr)); (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_chown = 1; } if (chmod(rname, mode) < 0 && !complained_chmod) { saverr = errno; errmsg = gettext( "Unable to restore permissions on %s: %s\n"); (void) fprintf(stderr, errmsg, rname, strerror(saverr)); (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_chmod = 1; } skipfile(); metaset(rname); /* skipfile() got the metadata, if any */ if (utime(rname, (struct utimbuf *)timep) < 0 && !complained_utime) { saverr = errno; errmsg = gettext( "Unable to restore times on %s: %s\n"); (void) fprintf(stderr, errmsg, rname, strerror(saverr)); (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_utime = 1; } result = GOOD; break; case IFREG: vprintf(stdout, gettext("extract file %s\n"), rname); /* * perform a restrictive creat(2) initally, we'll * fchmod(2) according to the archive later after * we've written the blocks. */ ofile = creat64(rname, 0600); if (ofile < 0) { saverr = errno; errmsg = gettext("cannot create file"); (void) fprintf(stderr, "%s: ", rname); (void) fflush(stderr); errno = saverr; perror(errmsg); skipfile(); result = FAIL; break; } if (fchown(ofile, uid, gid) < 0 && !complained_chown) { /* Just a warning */ saverr = errno; errmsg = gettext( "Unable to restore ownership of %s: %s\n"); (void) fprintf(stderr, errmsg, rname, strerror(saverr)); (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_chown = 1; } getfile(xtrfile, xtrskip); metaset(rname); /* * the fchmod(2) has to come after getfile() as some POSIX * implementations clear the S_ISUID and S_ISGID bits of the * file after every write(2). */ if (fchmod(ofile, mode) < 0 && !complained_chmod) { saverr = errno; errmsg = gettext( "Unable to restore permissions on %s: %s\n"); (void) fprintf(stderr, errmsg, rname, strerror(saverr)); (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_chmod = 1; } /* * Some errors don't get reported until we close(2), so * check for them. * XXX unlink the file if an error is reported? */ if (close(ofile) < 0) { saverr = errno; errmsg = gettext("error closing file"); (void) fprintf(stderr, "%s: ", rname); (void) fflush(stderr); errno = saverr; perror(errmsg); result = FAIL; break; } if (utime(rname, (struct utimbuf *)timep) < 0 && !complained_utime) { saverr = errno; errmsg = gettext( "Unable to restore times on %s: %s\n"); (void) fprintf(stderr, errmsg, rname, strerror(saverr)); (void) fprintf(stderr, gettext( "Additional such failures will be ignored.\n")); complained_utime = 1; } result = GOOD; break; } if (dfd != AT_FDCWD) { fchdir(savepwd); (void) close(dfd); } return (result); } /* * skip over bit maps on the tape */ void skipmaps(void) { continuemap = 1; while (checktype(&spcl, TS_CLRI) == GOOD || checktype(&spcl, TS_BITS) == GOOD) skipfile(); continuemap = 0; } /* * skip over a file on the tape */ void skipfile(void) { curfile.action = SKIP; getfile(null, null); } /* * Do the file extraction, calling the supplied functions * with the blocks */ void getfile(void (*f1)(), void (*f2)()) { int i; size_t curblk = 0; offset_t size = (offset_t)spcl.c_dinode.di_size; static char clearedbuf[MAXBSIZE]; char buf[TP_BSIZE_MAX]; char *bufptr; char junk[TP_BSIZE_MAX]; assert(MAXBSIZE >= tp_bsize); metaset(NULL); /* flush old metadata */ if (checktype(&spcl, TS_END) == GOOD) { panic(gettext("ran off end of volume\n")); return; } if (ishead(&spcl) == FAIL) { panic(gettext("not at beginning of a file\n")); return; } metacheck(&spcl); /* check for metadata in header */ if (!gettingfile && setjmp(restart) != 0) { gettingfile = 0; /* paranoia; longjmp'er should do */ return; } gettingfile++; loop: if ((spcl.c_dinode.di_mode & IFMT) == IFSHAD) { f1 = xtrmeta; f2 = metaskip; } for (i = 0, bufptr = buf; i < spcl.c_count; i++) { if ((i >= TP_NINDIR) || (spcl.c_addr[i])) { readtape(bufptr); bufptr += tp_bsize; curblk++; if (curblk == (fssize / tp_bsize)) { (*f1)(buf, size > tp_bsize ? (size_t)(fssize) : /* LINTED size <= tp_bsize */ (curblk - 1) * tp_bsize + (size_t)size); curblk = 0; bufptr = buf; } } else { if (curblk > 0) { (*f1)(buf, size > tp_bsize ? (size_t)(curblk * tp_bsize) : /* LINTED size <= tp_bsize */ (curblk - 1) * tp_bsize + (size_t)size); curblk = 0; bufptr = buf; } (*f2)(clearedbuf, size > tp_bsize ? /* LINTED size <= tp_bsize */ (long)tp_bsize : (size_t)size); } if ((size -= tp_bsize) <= 0) { for (i++; i < spcl.c_count; i++) if ((i >= TP_NINDIR) || (spcl.c_addr[i])) readtape(junk); break; } } if (curblk > 0) { /* * Ok to cast size to size_t here. The above for loop reads * data into the buffer then writes it to the output file. The * call to f1 here is to write out the data that's in the * buffer that has not yet been written to the file. * This will be less than N-KB of data, since the * above loop writes to the file in filesystem- * blocksize chunks. */ /* LINTED: size fits into a size_t at this point */ (*f1)(buf, (curblk * tp_bsize) + (size_t)size); curblk = 0; bufptr = buf; } if ((readhdr(&spcl) == GOOD) && (checktype(&spcl, TS_ADDR) == GOOD)) { if (continuemap) size = (offset_t)spcl.c_count * tp_bsize; /* big bitmap */ else if ((size <= 0) && ((spcl.c_dinode.di_mode & IFMT) == IFSHAD)) { /* LINTED unsigned to signed conversion ok */ size = spcl.c_dinode.di_size; } if (size > 0) goto loop; } if (size > 0) dprintf(stdout, gettext("Missing address (header) block for %s\n"), curfile.name); findinode(&spcl); gettingfile = 0; } /* * The next routines are called during file extraction to * put the data into the right form and place. */ static void xtrfile(char *buf, size_t size) { if (write(ofile, buf, (size_t)size) == -1) { int saverr = errno; (void) fprintf(stderr, gettext("write error extracting inode %d, name %s\n"), curfile.ino, curfile.name); errno = saverr; perror("write"); done(1); } } /* * Even though size is a size_t, it's seeking to a relative * offset. Thus, the seek could go beyond 2 GB, so lseek64 is needed. */ /*ARGSUSED*/ static void xtrskip(char *buf, size_t size) { if (lseek64(ofile, (offset_t)size, 1) == -1) { int saverr = errno; (void) fprintf(stderr, gettext("seek error extracting inode %d, name %s\n"), curfile.ino, curfile.name); errno = saverr; perror("lseek64"); done(1); } } /* these are local to the next five functions */ static char *metadata = NULL; static size_t metasize = 0; static void metacheck(struct s_spcl *head) { if (! (head->c_flags & DR_HASMETA)) return; if ((metadata = malloc(metasize = (size_t)sizeof (head->c_shadow))) == NULL) { (void) fprintf(stderr, gettext("Cannot malloc for metadata\n")); done(1); } bcopy(&(head->c_shadow), metadata, metasize); } static void xtrmeta(char *buf, size_t size) { if ((metadata == NULL) && ((spcl.c_dinode.di_mode & IFMT) != IFSHAD)) return; if ((metadata = realloc(metadata, metasize + size)) == NULL) { (void) fprintf(stderr, gettext("Cannot malloc for metadata\n")); done(1); } bcopy(buf, metadata + metasize, size); metasize += size; } /* ARGSUSED */ static void metaskip(char *buf, size_t size) { if (metadata == NULL) return; if ((metadata = realloc(metadata, metasize + size)) == NULL) { (void) fprintf(stderr, gettext("Cannot malloc for metadata\n")); done(1); } bzero(metadata + metasize, size); metasize += size; } static void metaset(char *name) { if (metadata == NULL) return; if (name != NULL) metaproc(name, metadata, metasize); (void) free(metadata); metadata = NULL; metasize = 0; } void metaget(char **data, size_t *size) { *data = metadata; *size = metasize; } static void fsd_acl(char *name, char *aclp, unsigned size) { static aclent_t *aclent = NULL; ufs_acl_t *diskacl; static int n = 0; acl_t *set_aclp; uint_t i; int saverr, j; if (aclp == NULL) { if (aclent != NULL) free(aclent); aclent = NULL; n = 0; return; } /*LINTED [aclp is malloc'd]*/ diskacl = (ufs_acl_t *)aclp; /* LINTED: result fits in an int */ j = size / sizeof (*diskacl); normacls(byteorder, diskacl, j); i = n; n += j; aclent = realloc(aclent, n * (size_t)sizeof (*aclent)); if (aclent == NULL) { (void) fprintf(stderr, gettext("Cannot malloc acl list\n")); done(1); } j = 0; while (i < n) { aclent[i].a_type = diskacl[j].acl_tag; aclent[i].a_id = diskacl[j].acl_who; aclent[i].a_perm = diskacl[j].acl_perm; ++i; ++j; } set_aclp = acl_to_aclp(ACLENT_T, aclent, n); if (set_aclp == NULL) { (void) fprintf(stderr, gettext("Cannot build acl_t\n")); done(1); } if (acl_set(name, set_aclp) == -1) { static int once = 0; /* * Treat some errors from the acl subsystem specially to * avoid being too noisy: * * ENOSYS - ACLs not supported on this file system * EPERM - not the owner or not privileged * * The following is also supported for backwards compat. * since acl(2) used to return the wrong errno: * * EINVAL - not the owner of the object */ if (errno == ENOSYS || errno == EPERM || errno == EINVAL) { if (once == 0) { saverr = errno; ++once; fprintf(stderr, gettext("setacl failed: %s\n"), strerror(saverr)); } } else { saverr = errno; fprintf(stderr, gettext("setacl on %s failed: %s\n"), name, strerror(saverr)); } } acl_free(set_aclp); } static struct fsdtypes { int type; void (*function)(); } fsdtypes[] = { {FSD_ACL, fsd_acl}, {FSD_DFACL, fsd_acl}, {0, NULL} }; void metaproc(char *name, char *mdata, size_t msize) { struct fsdtypes *fsdtype; ufs_fsd_t *fsd; char *c; /* * for the whole shadow inode, dispatch each piece * to the appropriate function. */ c = mdata; /* LINTED (c - mdata) fits into a size_t */ while ((size_t)(c - mdata) < msize) { /*LINTED [mdata is malloc'd]*/ fsd = (ufs_fsd_t *)c; assert((fsd->fsd_size % 4) == 0); /* LINTED: lint thinks pointers are signed */ c += FSD_RECSZ(fsd, fsd->fsd_size); if ((fsd->fsd_type == FSD_FREE) || ((unsigned)(fsd->fsd_size) <= sizeof (ufs_fsd_t)) || (c > (mdata + msize))) break; for (fsdtype = fsdtypes; fsdtype->type; fsdtype++) if (fsdtype->type == fsd->fsd_type) (*fsdtype->function)(name, fsd->fsd_data, (unsigned)(fsd->fsd_size) - sizeof (fsd->fsd_type) - sizeof (fsd->fsd_size)); /* ^^^ be sure to change if fsd ever changes ^^^ */ } /* reset the state of all the functions */ for (fsdtype = fsdtypes; fsdtype->type; fsdtype++) (*fsdtype->function)(NULL, NULL, 0); } static void xtrlnkfile(char *buf, size_t size) { /* LINTED: signed/unsigned mix ok */ pathlen += size; if (pathlen > MAXPATHLEN) { (void) fprintf(stderr, gettext("symbolic link name: %s->%s%s; too long %d\n"), curfile.name, lnkbuf, buf, pathlen); done(1); } buf[size] = '\0'; (void) strcat(lnkbuf, buf); /* add an extra NULL to make this a legal complex string */ lnkbuf[pathlen+1] = '\0'; } /*ARGSUSED*/ static void xtrlnkskip(char *buf, size_t size) { (void) fprintf(stderr, gettext("unallocated block in symbolic link %s\n"), curfile.name); done(1); } static void xtrmap(char *buf, size_t size) { if ((map+size) > endmap) { int64_t mapsize, increment; int64_t diff; if (spcl.c_type != TS_ADDR) { (void) fprintf(stderr, gettext("xtrmap: current record not TS_ADDR\n")); done(1); } if ((spcl.c_count < 0) || (spcl.c_count > TP_NINDIR)) { (void) fprintf(stderr, gettext("xtrmap: illegal c_count field (%d)\n"), spcl.c_count); done(1); } increment = d_howmany( ((spcl.c_count * tp_bsize * NBBY) + 1), NBBY); mapsize = endmap - beginmap + increment; if (mapsize > UINT_MAX) { (void) fprintf(stderr, gettext("xtrmap: maximum bitmap size exceeded")); done(1); } diff = map - beginmap; /* LINTED mapsize checked above */ beginmap = realloc(beginmap, (size_t)mapsize); if (beginmap == NULL) { (void) fprintf(stderr, gettext("xtrmap: realloc failed\n")); done(1); } map = beginmap + diff; endmap = beginmap + mapsize; /* LINTED endmap - map cannot exceed 32 bits */ bzero(map, (size_t)(endmap - map)); maxino = NBBY * mapsize + 1; } bcopy(buf, map, size); /* LINTED character pointers aren't signed */ map += size; } /*ARGSUSED*/ static void xtrmapskip(char *buf, size_t size) { (void) fprintf(stderr, gettext("hole in map\n")); done(1); } /*ARGSUSED*/ void null(char *buf, size_t size) { } /* * Do the tape i/o, dealing with volume changes * etc.. */ static void readtape(char *b) { int i; int rd, newvol; int cnt; struct s_spcl *sp; int32_t expected_magic; if (tbf == NULL) { (void) fprintf(stderr, gettext( "Internal consistency failure in readtape: tbf is NULL\n")); done(1); } expected_magic = ((tp_bsize == TP_BSIZE_MIN) ? NFS_MAGIC : MTB_MAGIC); top: if (bct < numtrec) { /* * check for old-dump floppy EOM -- it may appear in * the middle of a buffer. The Dflag used to be used for * this, but since it doesn't hurt to always do this we * got rid of the Dflag. */ /*LINTED [tbf = malloc()]*/ sp = &((union u_spcl *)&tbf[bct*tp_bsize])->s_spcl; if (sp->c_magic == expected_magic && sp->c_type == TS_EOM && (time_t)(sp->c_date) == dumpdate && (time_t)(sp->c_ddate) == dumptime) { for (i = 0; i < ntrec; i++) /*LINTED [tbf = malloc()]*/ ((struct s_spcl *) &tbf[i*tp_bsize])->c_magic = 0; bct = 0; rd = 0; i = 0; goto nextvol; } bcopy(&tbf[(bct++*tp_bsize)], b, (size_t)tp_bsize); blksread++; tapea++; return; } /*LINTED [assertion always true]*/ assert(sizeof (union u_spcl) == TP_BSIZE_MAX); for (i = 0; i < ntrec; i++) /*LINTED [tbf = malloc()]*/ ((struct s_spcl *)&tbf[i*sizeof (struct s_spcl)])->c_magic = 0; if (numtrec == 0) { /* LINTED unsigned/signed assignment ok */ numtrec = ntrec; } /* LINTED unsigned/signed assignment ok */ cnt = ntrec*tp_bsize; rd = 0; getmore: if (host) i = rmtread(&tbf[rd], cnt); else i = read(mt, &tbf[rd], cnt); /* * Check for mid-tape short read error. * If found, return rest of buffer. */ if (numtrec < ntrec && i != 0) { /* LINTED unsigned/signed assignment ok */ numtrec = ntrec; goto top; } /* * Handle partial block read. */ if (i > 0 && i != ntrec*tp_bsize) { if (pipein) { rd += i; cnt -= i; if (cnt > 0) goto getmore; i = rd; } else { if (i % tp_bsize != 0) panic(gettext( "partial block read: %d should be %d\n"), i, ntrec * tp_bsize); numtrec = i / tp_bsize; if (numtrec == 0) /* * it's possible to read only 512 bytes * from a QIC device... */ i = 0; } } /* * Handle read error. */ if (i < 0) { switch (curfile.action) { default: (void) fprintf(stderr, gettext( "Read error while trying to set up volume\n")); break; case UNKNOWN: (void) fprintf(stderr, gettext( "Read error while trying to resynchronize\n")); break; case USING: (void) fprintf(stderr, gettext( "Read error while restoring %s\n"), curfile.name); break; case SKIP: (void) fprintf(stderr, gettext( "Read error while skipping over inode %d\n"), curfile.ino); break; } if (!yflag && !reply(gettext("continue"))) done(1); /* LINTED: unsigned->signed conversion ok */ i = (int)(ntrec*tp_bsize); bzero(tbf, (size_t)i); if ((host != 0 && rmtseek(i, 1) < 0) || (host == 0 && (lseek64(mt, (offset_t)i, 1) == (off64_t)-1))) { perror(gettext("continuation failed")); done(1); } } /* * Handle end of tape. The Dflag used to be used, but since it doesn't * hurt to always check we got rid if it. */ /* * if the first record in the buffer just read is EOM, * change volumes. */ /*LINTED [tbf = malloc()]*/ sp = &((union u_spcl *)tbf)->s_spcl; if (i != 0 && sp->c_magic == expected_magic && sp->c_type == TS_EOM && (time_t)(sp->c_date) == dumpdate && (time_t)(sp->c_ddate) == dumptime) { i = 0; } nextvol: if (i == 0) { if (!pipein) { newvol = volno + 1; volno = 0; numtrec = 0; getvol(newvol); readtape(b); /* XXX tail recursion, not goto top? */ return; } /* XXX if panic returns, should we round rd up? */ /* XXX if we do, then we should zero the intervening space */ if (rd % tp_bsize != 0) panic(gettext("partial block read: %d should be %d\n"), rd, ntrec * tp_bsize); bcopy((char *)&endoftapemark, &tbf[rd], (size_t)tp_bsize); } bct = 0; bcopy(&tbf[(bct++*tp_bsize)], b, (size_t)tp_bsize); blksread++; recsread++; tapea++; rec_position++; } void findtapeblksize(int arfile) { int i; if (tbf == NULL) { (void) fprintf(stderr, gettext( "Internal consistency failure in findtapeblksize: " "tbf is NULL\n")); assert(tbf != NULL); done(1); } for (i = 0; i < ntrec; i++) /*LINTED [tbf = malloc()]*/ ((struct s_spcl *)&tbf[i * tp_bsize])->c_magic = 0; bct = 0; if (host && arfile == TAPE_FILE) tape_rec_size = rmtread(tbf, ntrec * tp_bsize); else tape_rec_size = read(mt, tbf, ntrec * tp_bsize); recsread++; rec_position++; if (tape_rec_size == (ssize_t)-1) { int saverr = errno; char *errmsg = gettext("Media read error"); errno = saverr; perror(errmsg); done(1); } if (tape_rec_size % tp_bsize != 0) { (void) fprintf(stderr, gettext( "Record size (%d) is not a multiple of dump block size (%d)\n"), tape_rec_size, tp_bsize); done(1); } ntrec = (int)tape_rec_size / tp_bsize; /* LINTED unsigned/signed assignment ok */ numtrec = ntrec; vprintf(stdout, gettext("Media block size is %d\n"), ntrec*2); } void flsht(void) { /* LINTED unsigned/signed assignment ok */ bct = ntrec+1; } void closemt(int mode) { /* * If mode == FORCE_OFFLINE then we're not done but * we need to change tape. So, rewind and unload current * tape before loading the new one. */ static struct mtop mtop = { MTOFFL, 0 }; if (mt < 0) return; if (offline || mode == FORCE_OFFLINE) (void) fprintf(stderr, gettext("Rewinding tape\n")); if (host) { if (offline || mode == FORCE_OFFLINE) (void) rmtioctl(MTOFFL, 1); rmtclose(); } else if (pipein) { char buffy[MAXBSIZE]; while (read(mt, buffy, sizeof (buffy)) > 0) { continue; /*LINTED [assertion always true]*/ } (void) close(mt); } else { /* * Only way to tell if this is a floppy is to issue an ioctl * but why waste one - if the eject fails, tough! */ if (offline || mode == FORCE_OFFLINE) (void) ioctl(mt, MTIOCTOP, &mtop); (void) ioctl(mt, FDEJECT, 0); (void) close(mt); } mt = -1; } static int checkvol(struct s_spcl *b, int t) { if (b->c_volume != t) return (FAIL); return (GOOD); } int readhdr(struct s_spcl *b) { if (gethead(b) == FAIL) { dprintf(stdout, gettext("readhdr fails at %ld blocks\n"), blksread); return (FAIL); } return (GOOD); } /* * read the tape into buf, then return whether or * or not it is a header block. */ int gethead(struct s_spcl *buf) { int i; union u_ospcl { char dummy[TP_BSIZE_MIN]; struct s_ospcl { int32_t c_type; int32_t c_date; int32_t c_ddate; int32_t c_volume; int32_t c_tapea; ushort_t c_inumber; int32_t c_magic; int32_t c_checksum; struct odinode { unsigned short odi_mode; ushort_t odi_nlink; ushort_t odi_uid; ushort_t odi_gid; int32_t odi_size; int32_t odi_rdev; char odi_addr[36]; int32_t odi_atime; int32_t odi_mtime; int32_t odi_ctime; } c_dinode; int32_t c_count; char c_baddr[256]; } s_ospcl; } u_ospcl; if (cvtflag) { readtape((char *)(&u_ospcl.s_ospcl)); bzero((char *)buf, (size_t)TP_BSIZE_MIN); buf->c_type = u_ospcl.s_ospcl.c_type; buf->c_date = u_ospcl.s_ospcl.c_date; buf->c_ddate = u_ospcl.s_ospcl.c_ddate; buf->c_volume = u_ospcl.s_ospcl.c_volume; buf->c_tapea = u_ospcl.s_ospcl.c_tapea; buf->c_inumber = u_ospcl.s_ospcl.c_inumber; buf->c_checksum = u_ospcl.s_ospcl.c_checksum; buf->c_magic = u_ospcl.s_ospcl.c_magic; buf->c_dinode.di_mode = u_ospcl.s_ospcl.c_dinode.odi_mode; /* LINTED: unsigned/signed combination ok */ buf->c_dinode.di_nlink = u_ospcl.s_ospcl.c_dinode.odi_nlink; buf->c_dinode.di_size = (unsigned)(u_ospcl.s_ospcl.c_dinode.odi_size); buf->c_dinode.di_uid = u_ospcl.s_ospcl.c_dinode.odi_uid; buf->c_dinode.di_gid = u_ospcl.s_ospcl.c_dinode.odi_gid; buf->c_dinode.di_suid = UID_LONG; buf->c_dinode.di_sgid = GID_LONG; buf->c_dinode.di_ordev = u_ospcl.s_ospcl.c_dinode.odi_rdev; buf->c_dinode.di_atime = u_ospcl.s_ospcl.c_dinode.odi_atime; buf->c_dinode.di_mtime = u_ospcl.s_ospcl.c_dinode.odi_mtime; buf->c_dinode.di_ctime = u_ospcl.s_ospcl.c_dinode.odi_ctime; buf->c_count = u_ospcl.s_ospcl.c_count; bcopy(u_ospcl.s_ospcl.c_baddr, buf->c_addr, sizeof (u_ospcl.s_ospcl.c_baddr)); /*CONSTANTCONDITION*/ assert(sizeof (u_ospcl.s_ospcl) < sizeof (union u_spcl)); /* we byte-swap the new spclrec, but checksum the old */ /* (see comments in normspcl()) */ if (normspcl(byteorder, buf, (int *)(&u_ospcl.s_ospcl), sizeof (u_ospcl.s_ospcl), OFS_MAGIC)) return (FAIL); buf->c_magic = ((tp_bsize == TP_BSIZE_MIN) ? NFS_MAGIC : MTB_MAGIC); } else { readtape((char *)buf); if (normspcl(byteorder, buf, (int *)buf, tp_bsize, ((tp_bsize == TP_BSIZE_MIN) ? NFS_MAGIC : MTB_MAGIC))) return (FAIL); } switch (buf->c_type) { case TS_CLRI: case TS_BITS: /* * Have to patch up missing information in bit map headers */ buf->c_inumber = 0; buf->c_dinode.di_size = (offset_t)buf->c_count * tp_bsize; for (i = 0; i < buf->c_count && i < TP_NINDIR; i++) buf->c_addr[i] = 1; break; case TS_TAPE: case TS_END: if (dumpinfo.c_date == 0) { dumpinfo.c_date = spcl.c_date; dumpinfo.c_ddate = spcl.c_ddate; } if (!hostinfo && spcl.c_host[0] != '\0') { bcopy(spcl.c_label, dumpinfo.c_label, sizeof (spcl.c_label)); bcopy(spcl.c_filesys, dumpinfo.c_filesys, sizeof (spcl.c_filesys)); bcopy(spcl.c_dev, dumpinfo.c_dev, sizeof (spcl.c_dev)); bcopy(spcl.c_host, dumpinfo.c_host, sizeof (spcl.c_host)); dumpinfo.c_level = spcl.c_level; hostinfo++; if (c_label != NULL && strncmp(c_label, spcl.c_label, sizeof (spcl.c_label)) != 0) { (void) fprintf(stderr, gettext( "Incorrect tape label. Expected `%s', got `%.*s'\n"), c_label, sizeof (spcl.c_label), spcl.c_label); done(1); } } if (!inodeinfo && (spcl.c_flags & DR_INODEINFO)) { dumpinfo.c_volume = spcl.c_volume; bcopy(spcl.c_inos, dumpinfo.c_inos, sizeof (spcl.c_inos)); inodeinfo++; } buf->c_inumber = 0; break; case TS_INODE: case TS_ADDR: break; default: panic(gettext("%s: unknown inode type %d\n"), "gethead", buf->c_type); return (FAIL); } if (dflag) accthdr(buf); return (GOOD); } /* * Check that a header is where it belongs and predict the next header */ static void accthdr(struct s_spcl *header) { static ino_t previno = (ino_t)(unsigned)-1; static int prevtype; static long predict; int blks, i; if (header->c_type == TS_TAPE) { if (header->c_firstrec) (void) fprintf(stderr, gettext("Volume header begins with record %d"), header->c_firstrec); else (void) fprintf(stderr, gettext("Volume header")); (void) fprintf(stderr, "\n"); previno = (ino_t)(unsigned)-1; return; } if (previno == (ino_t)(unsigned)-1) goto newcalc; switch (prevtype) { case TS_BITS: (void) fprintf(stderr, gettext("Dump mask header")); break; case TS_CLRI: (void) fprintf(stderr, gettext("Remove mask header")); break; case TS_INODE: (void) fprintf(stderr, gettext("File header, ino %d at record %d"), previno, rec_position); break; case TS_ADDR: (void) fprintf(stderr, gettext("File continuation header, ino %d"), previno); break; case TS_END: (void) fprintf(stderr, gettext("End of media header")); break; } if (predict != blksread - 1) (void) fprintf(stderr, gettext("; predicted %ld blocks, got %ld blocks"), predict, blksread - 1); (void) fprintf(stderr, "\n"); newcalc: blks = 0; if (header->c_type != TS_END) for (i = 0; i < header->c_count; i++) if ((i >= TP_NINDIR) || (header->c_addr[i] != 0)) blks++; predict = blks; blksread = 0; prevtype = header->c_type; previno = header->c_inumber; } /* * Try to determine which volume a file resides on. */ int volnumber(ino_t inum) { int i; if (inodeinfo == 0) return (0); for (i = 1; i <= dumpinfo.c_volume; i++) if (inum < (ino_t)(unsigned)(dumpinfo.c_inos[i])) break; return (i - 1); } /* * Find an inode header. * Note that *header must be stable storage, as curfile will end up with * pointers into it. */ void findinode(struct s_spcl *header) { long skipcnt = 0; int i; char buf[TP_BSIZE_MAX]; curfile.name = gettext(""); curfile.action = UNKNOWN; curfile.dip = (struct dinode *)NULL; curfile.ino = 0; curfile.ts = 0; if (ishead(header) == FAIL) { skipcnt++; while (gethead(header) == FAIL || (time_t)(header->c_date) != dumpdate) skipcnt++; } for (;;) { if (checktype(header, TS_ADDR) == GOOD) { /* * Skip up to the beginning of the next record */ for (i = 0; i < header->c_count; i++) if ((i >= TP_NINDIR) || (header->c_addr[i])) readtape(buf); (void) gethead(header); continue; } if (checktype(header, TS_INODE) == GOOD) { curfile.dip = &header->c_dinode; if (curfile.dip->di_suid != UID_LONG) curfile.dip->di_uid = curfile.dip->di_suid; if (curfile.dip->di_sgid != GID_LONG) curfile.dip->di_gid = curfile.dip->di_sgid; curfile.ino = header->c_inumber; curfile.ts = TS_INODE; break; } if (checktype(header, TS_END) == GOOD) { curfile.ino = maxino; curfile.ts = TS_END; break; } if (checktype(header, TS_CLRI) == GOOD) { curfile.name = gettext(""); curfile.ts = TS_CLRI; break; } if (checktype(header, TS_BITS) == GOOD) { curfile.name = gettext(""); curfile.ts = TS_BITS; break; } while (gethead(header) == FAIL) skipcnt++; } if (skipcnt > 0) (void) fprintf(stderr, gettext("resync restore, skipped %d blocks\n"), skipcnt); } /* * return whether or not the buffer contains a header block */ static int ishead(struct s_spcl *buf) { if (buf->c_magic != ((tp_bsize == TP_BSIZE_MIN) ? NFS_MAGIC : MTB_MAGIC)) return (FAIL); return (GOOD); } static int checktype(struct s_spcl *b, int t) { if (b->c_type != t) return (FAIL); return (GOOD); } /* * If autoloading is enabled, attempt to do it. If we succeed, * return non-zero. */ static int autoload_tape(void) { int result = 0; /* assume failure */ int tries; int fd; if (autoload) { /* * Wait for the tape to autoload. Note that the delay * period doesn't take into account however long it takes * for the open to fail (measured at 21 seconds for an * Exabyte 8200 under 2.7 on an Ultra 2). */ /* rewind tape and offline drive before loading new tape */ closemt(FORCE_OFFLINE); (void) fprintf(stderr, gettext("Attempting to autoload next volume\n")); for (tries = 0; tries < autoload_tries; tries++) { if (host) { if (rmtopen(magtape, O_RDONLY) >= 0) { rmtclose(); result = 1; break; } } else { if ((fd = open(magtape, O_RDONLY|O_LARGEFILE, 0600)) >= 0) { (void) close(fd); result = 1; break; } } (void) sleep(autoload_period); } if (result == 0) { /* Assume caller will deal with manual change-over */ (void) fprintf(stderr, gettext("Autoload timed out\n")); } else { if ((host != NULL && (mt = rmtopen(magtape, O_RDONLY)) == -1) || (host == NULL && (mt = open(magtape, O_RDONLY|O_LARGEFILE)) == -1)) { (void) fprintf(stderr, gettext( "Autoload could not re-open tape\n")); result = 0; } else { (void) fprintf(stderr, gettext( "Tape loaded\n")); } } } return (result); } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ #include "restore.h" #include #include #include #include /* LINTED: this file really is necessary */ #include #include /* * Insure that all the components of a pathname exist. Note that * lookupname() and addentry() both expect complex names as * input arguments, so a double NULL needs to be added to each name. */ void pathcheck(char *name) { char *cp, save; struct entry *ep; char *start; start = strchr(name, '/'); if (start == 0) return; for (cp = start; *cp != '\0'; cp++) { if (*cp != '/') continue; *cp = '\0'; save = *(cp+1); *(cp+1) = '\0'; ep = lookupname(name); if (ep == NIL) { ep = addentry(name, psearch(name), NODE); newnode(ep); } /* LINTED: result fits in a short */ ep->e_flags |= NEW|KEEP; *cp = '/'; *(cp+1) = save; } } /* * Change a name to a unique temporary name. */ void mktempname(struct entry *ep) { char *newname; if (ep->e_flags & TMPNAME) badentry(ep, gettext("mktempname: called with TMPNAME")); /* LINTED: result fits in a short */ ep->e_flags |= TMPNAME; newname = savename(gentempname(ep)); renameit(myname(ep), newname); freename(ep->e_name); ep->e_name = newname; /* LINTED: savename guarantees strlen will fit */ ep->e_namlen = strlen(ep->e_name); } /* * Generate a temporary name for an entry. */ char * gentempname(struct entry *ep) { static char name[MAXPATHLEN]; struct entry *np; long i = 0; for (np = lookupino(ep->e_ino); np != NIL && np != ep; np = np->e_links) i++; if (np == NIL) badentry(ep, gettext("not on ino list")); (void) snprintf(name, sizeof (name), "%s%ld%lu", TMPHDR, i, ep->e_ino); return (name); } /* * Rename a file or directory. */ void renameit(char *fp, char *tp) { int fromfd, tofd; char *from, *to; char tobuf[MAXPATHLEN]; char *pathend; resolve(fp, &fromfd, &from); /* * The to pointer argument is assumed to be either a fully * specified path (starting with "./") or a simple temporary * file name (starting with TMPHDR). If passed a simple temp * file name, we need to set up the descriptors explicitly. */ if (strncmp(tp, TMPHDR, sizeof (TMPHDR) - 1) == 0) { tofd = fromfd; if ((pathend = strrchr(from, '/')) != NULL) { strncpy(tobuf, from, pathend - from + 1); tobuf[pathend - from + 1] = '\0'; strlcat(tobuf, tp, sizeof (tobuf)); to = tobuf; } else { to = tp; } } else resolve(tp, &tofd, &to); if (renameat(fromfd, from, tofd, to) < 0) { int saverr = errno; (void) fprintf(stderr, gettext("Warning: cannot rename %s to %s: %s\n"), from, to, strerror(saverr)); (void) fflush(stderr); } else { vprintf(stdout, gettext("rename %s to %s\n"), from, to); } if (fromfd != AT_FDCWD) (void) close(fromfd); if (tofd != AT_FDCWD) (void) close(tofd); } /* * Create a new node (directory). Note that, because we have no * mkdirat() function, fchdir() must be used set up the appropriate * name space context prior to the call to mkdir() if we are * operating in attribute space. */ void newnode(struct entry *np) { char *cp; int dfd; if (np->e_type != NODE) badentry(np, gettext("newnode: not a node")); resolve(myname(np), &dfd, &cp); if (dfd != AT_FDCWD) { if (fchdir(dfd) < 0) { int saverr = errno; (void) fprintf(stderr, gettext("Warning: cannot create %s: %s"), cp, strerror(saverr)); (void) fflush(stderr); (void) close(dfd); return; } } if (mkdir(cp, 0777) < 0) { int saverr = errno; /* LINTED: result fits in a short */ np->e_flags |= EXISTED; (void) fprintf(stderr, gettext("Warning: ")); (void) fflush(stderr); (void) fprintf(stderr, "%s: %s\n", cp, strerror(saverr)); } else { vprintf(stdout, gettext("Make node %s\n"), cp); } if (dfd != AT_FDCWD) { fchdir(savepwd); (void) close(dfd); } } /* * Remove an old node (directory). See comment above on newnode() * for explanation of fchdir() use below. */ void removenode(struct entry *ep) { char *cp; int dfd; if (ep->e_type != NODE) badentry(ep, gettext("removenode: not a node")); if (ep->e_entries != NIL) badentry(ep, gettext("removenode: non-empty directory")); /* LINTED: result fits in a short */ ep->e_flags |= REMOVED; /* LINTED: result fits in a short */ ep->e_flags &= ~TMPNAME; resolve(myname(ep), &dfd, &cp); if (dfd != AT_FDCWD) { if (fchdir(dfd) < 0) { int saverr = errno; (void) fprintf(stderr, gettext("Warning: cannot remove %s: %s"), cp, strerror(saverr)); (void) fflush(stderr); (void) close(dfd); return; } } if (rmdir(cp) < 0) { /* NOTE: could use unlinkat (..,REMOVEDIR) */ int saverr = errno; (void) fprintf(stderr, gettext("Warning: %s: %s\n"), cp, strerror(saverr)); (void) fflush(stderr); } else { vprintf(stdout, gettext("Remove node %s\n"), cp); } if (dfd != AT_FDCWD) { (void) fchdir(savepwd); (void) close(dfd); } } /* * Remove a leaf. */ void removeleaf(struct entry *ep) { char *cp; int dfd; if (ep->e_type != LEAF) badentry(ep, gettext("removeleaf: not a leaf")); /* LINTED: result fits in a short */ ep->e_flags |= REMOVED; /* LINTED: result fits in a short */ ep->e_flags &= ~TMPNAME; resolve(myname(ep), &dfd, &cp); if (unlinkat(dfd, cp, 0) < 0) { int saverr = errno; (void) fprintf(stderr, gettext("Warning: %s: %s\n"), cp, strerror(saverr)); (void) fflush(stderr); } else { vprintf(stdout, gettext("Remove leaf %s\n"), cp); } if (dfd != AT_FDCWD) (void) close(dfd); } /* * Create a link. * This function assumes that the context has already been set * for the link file to be created (i.e., we have "fchdir-ed" * into attribute space already if this is an attribute link). */ int lf_linkit(char *existing, char *new, int type) { char linkbuf[MAXPATHLEN]; struct stat64 s1[1], s2[1]; char *name; int dfd, l, result; resolve(existing, &dfd, &name); if (dfd == -1) { (void) fprintf(stderr, gettext( "Warning: cannot restore %s link %s->%s\n"), (type == SYMLINK ? "symbolic" : "hard"), new, existing); result = FAIL; goto out; } if (type == SYMLINK) { if (symlink(name, new) < 0) { /* No trailing \0 from readlink(2) */ if (((l = readlink(new, linkbuf, sizeof (linkbuf))) > 0) && (l == strlen(name)) && (strncmp(linkbuf, name, l) == 0)) { vprintf(stdout, gettext("Symbolic link %s->%s ok\n"), new, name); result = GOOD; goto out; } else { int saverr = errno; (void) fprintf(stderr, gettext( "Warning: cannot create symbolic link %s->%s: %s"), new, name, strerror(saverr)); (void) fflush(stderr); result = FAIL; goto out; } } } else if (type == HARDLINK) { if (link(name, new) < 0) { int saverr = errno; if ((stat64(name, s1) == 0) && (stat64(new, s2) == 0) && (s1->st_dev == s2->st_dev) && (s1->st_ino == s2->st_ino)) { vprintf(stdout, gettext("Hard link %s->%s ok\n"), new, name); result = GOOD; goto out; } else { (void) fprintf(stderr, gettext( "Warning: cannot create hard link %s->%s: %s\n"), new, name, strerror(saverr)); (void) fflush(stderr); result = FAIL; goto out; } } } else { panic(gettext("%s: unknown type %d\n"), "linkit", type); result = FAIL; goto out; } result = GOOD; if (type == SYMLINK) vprintf(stdout, gettext("Create symbolic link %s->%s\n"), new, name); else vprintf(stdout, gettext("Create hard link %s->%s\n"), new, name); out: if (dfd != AT_FDCWD) { (void) close(dfd); } return (result); } /* * Find lowest-numbered inode (above "start") that needs to be extracted. * Caller knows that a return value of maxino means there's nothing left. */ ino_t lowerbnd(ino_t start) { struct entry *ep; for (; start < maxino; start++) { ep = lookupino(start); if (ep == NIL || ep->e_type == NODE) continue; if (ep->e_flags & (NEW|EXTRACT)) return (start); } return (start); } /* * Find highest-numbered inode (below "start") that needs to be extracted. */ ino_t upperbnd(ino_t start) { struct entry *ep; for (; start > ROOTINO; start--) { ep = lookupino(start); if (ep == NIL || ep->e_type == NODE) continue; if (ep->e_flags & (NEW|EXTRACT)) return (start); } return (start); } /* * report on a badly formed entry */ void badentry(struct entry *ep, char *msg) { (void) fprintf(stderr, gettext("bad entry: %s\n"), msg); (void) fprintf(stderr, gettext("name: %s\n"), myname(ep)); (void) fprintf(stderr, gettext("parent name %s\n"), myname(ep->e_parent)); if (ep->e_sibling != NIL) (void) fprintf(stderr, gettext("sibling name: %s\n"), myname(ep->e_sibling)); if (ep->e_entries != NIL) (void) fprintf(stderr, gettext("next entry name: %s\n"), myname(ep->e_entries)); if (ep->e_links != NIL) (void) fprintf(stderr, gettext("next link name: %s\n"), myname(ep->e_links)); if (ep->e_xattrs != NIL) (void) fprintf(stderr, gettext("attribute root name: %s\n"), myname(ep->e_xattrs)); if (ep->e_next != NIL) (void) fprintf(stderr, gettext("next hashchain name: %s\n"), myname(ep->e_next)); (void) fprintf(stderr, gettext("entry type: %s\n"), ep->e_type == NODE ? gettext("NODE") : gettext("LEAF")); (void) fprintf(stderr, gettext("inode number: %lu\n"), ep->e_ino); panic(gettext("flags: %s\n"), flagvalues(ep)); /* Our callers are expected to handle our returning. */ } /* * Construct a string indicating the active flag bits of an entry. */ char * flagvalues(struct entry *ep) { static char flagbuf[BUFSIZ]; (void) strlcpy(flagbuf, gettext("|NIL"), sizeof (flagbuf)); flagbuf[0] = '\0'; if (ep->e_flags & REMOVED) (void) strlcat(flagbuf, gettext("|REMOVED"), sizeof (flagbuf)); if (ep->e_flags & TMPNAME) (void) strlcat(flagbuf, gettext("|TMPNAME"), sizeof (flagbuf)); if (ep->e_flags & EXTRACT) (void) strlcat(flagbuf, gettext("|EXTRACT"), sizeof (flagbuf)); if (ep->e_flags & NEW) (void) strlcat(flagbuf, gettext("|NEW"), sizeof (flagbuf)); if (ep->e_flags & KEEP) (void) strlcat(flagbuf, gettext("|KEEP"), sizeof (flagbuf)); if (ep->e_flags & EXISTED) (void) strlcat(flagbuf, gettext("|EXISTED"), sizeof (flagbuf)); if (ep->e_flags & XATTR) (void) strlcat(flagbuf, gettext("|XATTR"), sizeof (flagbuf)); if (ep->e_flags & XATTRROOT) (void) strlcat(flagbuf, gettext("|XATTRROOT"), sizeof (flagbuf)); return (&flagbuf[1]); } /* * Check to see if a name is on a dump tape. */ ino_t dirlookup(char *name) { ino_t ino; ino = psearch(name); if (ino == 0 || BIT(ino, dumpmap) == 0) (void) fprintf(stderr, gettext("%s is not on volume\n"), name); return (ino); } /* * Elicit a reply. */ int reply(char *question) { char *yesorno = gettext("yn"); /* must be two characters, "yes" first */ int c; do { (void) fprintf(stderr, "%s? [%s] ", question, yesorno); (void) fflush(stderr); c = getc(terminal); while (c != '\n' && getc(terminal) != '\n') { if (ferror(terminal)) { (void) fprintf(stderr, gettext( "Error reading response\n")); (void) fflush(stderr); return (FAIL); } if (feof(terminal)) return (FAIL); } if (isupper(c)) c = tolower(c); } while (c != yesorno[0] && c != yesorno[1]); if (c == yesorno[0]) return (GOOD); return (FAIL); } /* * handle unexpected inconsistencies */ /* * Note that a panic w/ EOF on the tty means all panics will return... */ #include /* VARARGS1 */ void panic(const char *msg, ...) { va_list args; va_start(args, msg); (void) vfprintf(stderr, msg, args); va_end(args); if (reply(gettext("abort")) == GOOD) { if (reply(gettext("dump core")) == GOOD) abort(); done(1); } } /* * Locale-specific version of ctime */ char * lctime(time_t *tp) { static char buf[256]; struct tm *tm; tm = localtime(tp); (void) strftime(buf, sizeof (buf), "%c\n", tm); return (buf); } static int statcmp(const struct stat *left, const struct stat *right) { int result = 1; if ((left->st_dev == right->st_dev) && (left->st_ino == right->st_ino) && (left->st_mode == right->st_mode) && (left->st_nlink == right->st_nlink) && (left->st_uid == right->st_uid) && (left->st_gid == right->st_gid) && (left->st_rdev == right->st_rdev) && (left->st_ctim.tv_sec == right->st_ctim.tv_sec) && (left->st_ctim.tv_nsec == right->st_ctim.tv_nsec) && (left->st_mtim.tv_sec == right->st_mtim.tv_sec) && (left->st_mtim.tv_nsec == right->st_mtim.tv_nsec) && (left->st_blksize == right->st_blksize) && (left->st_blocks == right->st_blocks)) { result = 0; } return (result); } /* * Safely open a file. */ int safe_open(int dfd, const char *filename, int mode, int perms) { static int init_syslog = 1; int fd; int working_mode; int saverr; char *errtext; struct stat pre_stat, pre_lstat; struct stat post_stat, post_lstat; if (init_syslog) { openlog(progname, LOG_CONS, LOG_DAEMON); init_syslog = 0; } /* * Don't want to be spoofed into trashing something we * shouldn't, thus the following rigamarole. If it doesn't * exist, we create it and proceed. Otherwise, require that * what's there be a real file with no extraneous links and * owned by whoever ran us. * * The silliness with using both lstat() and fstat() is to avoid * race-condition games with someone replacing the file with a * symlink after we've opened it. If there was an flstat(), * we wouldn't need the fstat(). * * The initial open with the hard-coded flags is ok even if we * are intending to open only for reading. If it succeeds, * then the file did not exist, and we'll synthesize an appropriate * complaint below. Otherwise, it does exist, so we won't be * truncating it with the open. */ if ((fd = openat(dfd, filename, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_LARGEFILE, perms)) < 0) { if (errno == EEXIST) { if (fstatat(dfd, filename, &pre_lstat, AT_SYMLINK_NOFOLLOW) < 0) { saverr = errno; (void) close(fd); errno = saverr; return (-1); } if (fstatat(dfd, filename, &pre_stat, 0) < 0) { saverr = errno; (void) close(fd); errno = saverr; return (-1); } working_mode = mode & (O_WRONLY|O_RDWR|O_RDONLY); working_mode |= O_LARGEFILE; if ((fd = openat(dfd, filename, working_mode)) < 0) { if (errno == ENOENT) { errtext = gettext( "Unexpected condition detected: %s used to exist, but doesn't any longer\n"); (void) fprintf(stderr, errtext, filename); syslog(LOG_WARNING, errtext, filename); errno = ENOENT; } return (-1); } if (fstatat(fd, NULL, &post_lstat, AT_SYMLINK_NOFOLLOW) < 0) { saverr = errno; (void) close(fd); errno = saverr; return (-1); } if (fstatat(fd, NULL, &post_stat, 0) < 0) { saverr = errno; (void) close(fd); errno = saverr; return (-1); } if (statcmp(&pre_lstat, &post_lstat) != 0) { errtext = gettext( "Unexpected condition detected: %s's lstat(2) information changed\n"); (void) fprintf(stderr, errtext, filename); syslog(LOG_WARNING, errtext, filename); errno = EPERM; return (-1); } if (statcmp(&pre_stat, &post_stat) != 0) { errtext = gettext( "Unexpected condition detected: %s's stat(2) information changed\n"); (void) fprintf(stderr, errtext, filename); syslog(LOG_WARNING, errtext, filename); errno = EPERM; return (-1); } /* * If inode, device, or type are wrong, bail out. */ if ((!S_ISREG(post_lstat.st_mode) || (post_stat.st_ino != post_lstat.st_ino) || (post_stat.st_dev != post_lstat.st_dev))) { errtext = gettext( "Unexpected condition detected: %s is not a regular file\n"); (void) fprintf(stderr, errtext, filename); syslog(LOG_WARNING, errtext, filename); (void) close(fd); errno = EPERM; return (-1); } /* * Bad link count implies someone's linked our * target to something else, which we probably * shouldn't step on. */ if (post_lstat.st_nlink != 1) { errtext = gettext( "Unexpected condition detected: %s must have exactly one link\n"); (void) fprintf(stderr, errtext, filename); syslog(LOG_WARNING, errtext, filename); (void) close(fd); errno = EPERM; return (-1); } /* * Root might make a file, but non-root might * need to open it. If the permissions let us * get this far, then let it through. */ if (post_lstat.st_uid != getuid() && post_lstat.st_uid != 0) { errtext = gettext( "Unsupported condition detected: %s must be owned by uid %ld or 0\n"); (void) fprintf(stderr, errtext, filename, (long)getuid()); syslog(LOG_WARNING, errtext, filename, (long)getuid()); (void) close(fd); errno = EPERM; return (-1); } if (mode & (O_WRONLY|O_TRUNC)) { if (ftruncate(fd, (off_t)0) < 0) { (void) fprintf(stderr, "ftruncate(%s): %s\n", filename, strerror(errno)); (void) close(fd); return (-1); } } } else { /* * Didn't exist, but couldn't open it. */ return (-1); } } else { /* * If truncating open succeeded for a read-only open, * bail out, as we really shouldn't have succeeded. */ if (mode & O_RDONLY) { /* Undo the O_CREAT */ (void) unlinkat(dfd, filename, 0); (void) fprintf(stderr, "open(%s): %s\n", filename, strerror(ENOENT)); (void) close(fd); errno = ENOENT; return (-1); } } return (fd); } /* * STDIO version of safe_open. Equivalent to fopen64(...). */ FILE * safe_fopen(const char *filename, const char *smode, int perms) { int fd; int bmode; /* * accepts only modes "r", "r+", and "w" */ if (smode[0] == 'r') { if (smode[1] == '\0') { bmode = O_RDONLY; } else if ((smode[1] == '+') && (smode[2] == '\0')) { bmode = O_RDWR; } } else if ((smode[0] == 'w') && (smode[1] == '\0')) { bmode = O_WRONLY; } else { (void) fprintf(stderr, gettext("internal error: safe_fopen: invalid mode `%s'\n"), smode); return (NULL); } fd = safe_open(AT_FDCWD, filename, bmode, perms); /* * caller is expected to report error. */ if (fd >= 0) return (fdopen(fd, smode)); return ((FILE *)NULL); } /* * Read the contents of a directory. */ int mkentry(char *name, ino_t ino, struct arglist *ap) { struct afile *fp; if (ap->base == NULL) { ap->nent = 20; ap->base = (struct afile *)calloc((unsigned)ap->nent, sizeof (*(ap->base))); if (ap->base == NULL) { (void) fprintf(stderr, gettext("%s: out of memory\n"), ap->cmd); return (FAIL); } } if (ap->head == NULL) ap->head = ap->last = ap->base; fp = ap->last; fp->fnum = ino; fp->fname = savename(name); fp++; if (fp == ap->head + ap->nent) { ap->base = (struct afile *)realloc((char *)ap->base, (size_t)(2 * ap->nent * (size_t)sizeof (*(ap->base)))); if (ap->base == NULL) { (void) fprintf(stderr, gettext("%s: out of memory\n"), ap->cmd); return (FAIL); } ap->head = ap->base; fp = ap->head + ap->nent; ap->nent *= 2; } ap->last = fp; return (GOOD); } static int gmatch(wchar_t *, wchar_t *); static int addg(struct direct *, char *, char *, struct arglist *); /* * XXX This value is ASCII (but not language) dependent. In * ASCII, it is the DEL character (unlikely to appear in paths). * If you are compiling on an EBCDIC-based machine, re-define * this (0x7f is '"') to be something like 0x7 (DEL). It's * either this hack or re-write the expand() algorithm... */ #define DELIMCHAR ((char)0x7f) /* * Expand a file name. * "as" is the pattern to expand. * "rflg" non-zero indicates that we're recursing. * "ap" is where to put the results of the expansion. * * Our caller guarantees that "as" is at least the string ".". */ int expand(char *as, int rflg, struct arglist *ap) { int count, size; char dir = 0; char *rescan = 0; RST_DIR *dirp; char *s, *cs; int sindex, rindexa, lindex; struct direct *dp; char slash; char *rs; char c; wchar_t w_fname[PATH_MAX+1]; wchar_t w_pname[PATH_MAX+1]; /* * check for meta chars */ s = cs = as; slash = 0; while (*cs != '*' && *cs != '?' && *cs != '[') { if (*cs++ == 0) { if (rflg && slash) break; else return (0); } else if (*cs == '/') { slash++; } } for (;;) { if (cs == s) { s = ""; break; } else if (*--cs == '/') { *cs = 0; if (s == cs) s = "/"; break; } } if ((dirp = rst_opendir(s)) != NULL) dir++; count = 0; if (*cs == 0) *cs++ = DELIMCHAR; if (dir) { /* * check for rescan */ rs = cs; do { if (*rs == '/') { rescan = rs; *rs = 0; } } while (*rs++); /* LINTED: result fits into an int */ sindex = (int)(ap->last - ap->head); (void) mbstowcs(w_pname, cs, PATH_MAX); w_pname[PATH_MAX - 1] = 0; while ((dp = rst_readdir(dirp)) != NULL && dp->d_ino != 0) { if (!dflag && BIT(dp->d_ino, dumpmap) == 0) continue; if ((*dp->d_name == '.' && *cs != '.')) continue; (void) mbstowcs(w_fname, dp->d_name, PATH_MAX); w_fname[PATH_MAX - 1] = 0; if (gmatch(w_fname, w_pname)) { if (addg(dp, s, rescan, ap) < 0) { rst_closedir(dirp); return (-1); } count++; } } if (rescan) { rindexa = sindex; /* LINTED: result fits into an int */ lindex = (int)(ap->last - ap->head); if (count) { count = 0; while (rindexa < lindex) { size = expand(ap->head[rindexa].fname, 1, ap); if (size < 0) { rst_closedir(dirp); return (size); } count += size; rindexa++; } } /* LINTED: lint is confused about pointer size/type */ bcopy((void *)(&ap->head[lindex]), (void *)(&ap->head[sindex]), (size_t)((ap->last - &ap->head[rindexa])) * sizeof (*ap->head)); ap->last -= lindex - sindex; *rescan = '/'; } rst_closedir(dirp); } s = as; while ((c = *s) != '\0') *s++ = (c != DELIMCHAR ? c : '/'); return (count); } /* * Check for a name match */ static int gmatch(wchar_t *s, wchar_t *p) { long scc; /* source character to text */ wchar_t c; /* pattern character to match */ char ok; /* [x-y] range match status */ long lc; /* left character of [x-y] range */ scc = *s++; switch (c = *p++) { case '[': ok = 0; lc = -1; while (c = *p++) { if (c == ']') { return (ok ? gmatch(s, p) : 0); } else if (c == '-') { wchar_t rc = *p++; /* * Check both ends must belong to * the same codeset. */ if (wcsetno(lc) != wcsetno(rc)) { /* * If not, ignore the '-' * operator and [x-y] is * treated as if it were * [xy]. */ if (scc == lc) ok++; if (scc == (lc = rc)) ok++; } else if (lc <= scc && scc <= rc) ok++; } else { lc = c; if (scc == lc) ok++; } } /* No closing bracket => failure */ return (0); default: if (c != scc) return (0); /*FALLTHROUGH*/ case '?': return (scc ? gmatch(s, p) : 0); case '*': if (*p == 0) return (1); s--; while (*s) { if (gmatch(s++, p)) return (1); } return (0); case 0: return (scc == 0); } } /* * Construct a matched name. */ static int addg(struct direct *dp, char *as1, char *as3, struct arglist *ap) { char *s1, *s2, *limit; int c; char buf[MAXPATHLEN + 1]; s2 = buf; limit = buf + sizeof (buf) - 1; s1 = as1; while ((c = *s1++) != '\0' && s2 < limit) { if (c == DELIMCHAR) { *s2++ = '/'; break; } /* LINTED narrowing cast */ *s2++ = (char)c; } s1 = dp->d_name; while ((*s2 = *s1++) != '\0' && s2 < limit) s2++; s1 = as3; if (s1 != NULL && s2 < limit) { *s2++ = '/'; while ((*s2++ = *++s1) != '\0' && s2 < limit) { continue; /*LINTED [empty loop body]*/ } } *s2 = '\0'; if (mkentry(buf, dp->d_ino, ap) == FAIL) return (-1); return (0); } /* * Resolve a "complex" pathname (as generated by myname()) into * a file descriptor and a relative path. The file descriptor * will reference the hidden directory containing the attribute * named by the relative path. If the provided path is not * complex, the returned file descriptor will be AT_FDCWD and rpath * will equal path. * * This function is intended to be used to transform a complex * pathname into a pair of handles that can be used to actually * manipulate the named file. Since extended attributes have * an independant name space, a file descriptor for a directory * in the attribute name space is necessary to actually manipulate * the attribute file (via the path-relative xxxat() system calls * or a call to fchdir()). * * In the event of an error, the returned file descriptor will be * -1. It is expected that callers will either check for this * condition directly, or attempt to use the descriptor, fail, and * generate an appropriate context-specific error message. * * This function is pretty much a no-op for "simple" (non-attribute) * paths. */ void resolve(char *path, int *fd, char **rpath) { int tfd; *fd = tfd = AT_FDCWD; *rpath = path; path = *rpath + strlen(*rpath) +1; while (*path != '\0' && (*fd = openat64(tfd, *rpath, O_RDONLY)) > 0) { if (tfd != AT_FDCWD) (void) close(tfd); tfd = *fd; *rpath = path; path = *rpath + strlen(*rpath) +1; } if (*fd == AT_FDCWD) return; if (*fd < 0 || (*fd = openat64(tfd, ".", O_RDONLY|O_XATTR)) < 0) { int saverr = errno; (void) fprintf(stderr, gettext("Warning: cannot fully resolve %s: %s"), path, strerror(saverr)); (void) fflush(stderr); } if (tfd != AT_FDCWD) (void) close(tfd); } /* * Copy a complex pathname to another string. Note that the * length returned by this function is the number of characters * up to (but not including) the final NULL. */ int complexcpy(char *s1, char *s2, int max) { int nullseen = 0; int len = 0; while (len++ < max) { *s1++ = *s2; if (*s2++ == '\0') { if (nullseen) return (len-1); else nullseen = 1; } else { nullseen = 0; } } *s1 = '\0'; if (nullseen == 0) *--s1 = '\0'; fprintf(stderr, gettext("Warning: unterminated source string in complexcpy\n")); return (max-1); }