root/usr/src/cmd/backup/restore/tape.c
/*
 * 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 <setjmp.h>
#include "restore.h"
#include <byteorder.h>
#include <rmt.h>
#include <sys/mtio.h>
#include <utime.h>
#include <sys/errno.h>
#include <sys/fdio.h>
#include <sys/sysmacros.h>      /* for expdev */
#include <assert.h>
#include <limits.h>
#include <priv_utils.h>
#include <aclutils.h>

#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("<name unknown>");
        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("<file removal list>");
                        curfile.ts = TS_CLRI;
                        break;
                }
                if (checktype(header, TS_BITS) == GOOD) {
                        curfile.name = gettext("<file dump list>");
                        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);
}