root/usr/src/cmd/backup/dump/dumptraverse.c
/*
 * 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) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

#include "dump.h"
#include <sys/file.h>
#include <sys/mman.h>

static void lf_dmpindir(daddr32_t, int, u_offset_t *);
static void indir(daddr32_t, int, u_offset_t *);
static void lf_blksout(daddr32_t *, u_offset_t);
static void lf_dumpinode(struct dinode *);
static void dsrch(daddr32_t, ulong_t, u_offset_t);
void lf_dump(struct dinode *);

int     dadded;
int     nsubdir;
int     shortmeta;
static  char msgbuf[256];

void
pass(void (*fn)(struct dinode *), uchar_t *map)
{
        int bits;
        ino_t maxino;

        maxino = (unsigned)(sblock->fs_ipg * sblock->fs_ncg - 1);
        /*
         * Handle pass restarts.  We don't check for UFSROOTINO just in
         * case we need to restart on the root inode.
         */
        if (ino != 0) {
                bits = ~0;
                if (map != NULL) {
                        /* LINTED: lint seems to think map is signed */
                        map += (ino / NBBY);
                        bits = *map++;
                }
                bits >>= (ino % NBBY);
                resetino(ino);
                goto restart;
        }
        while (ino < maxino) {
                if ((ino % NBBY) == 0) {
                        bits = ~0;
                        if (map != NULL)
                                bits = *map++;
                }
restart:
                ino++;
                /*
                 * Ignore any inode less than UFSROOTINO and inodes that
                 * we have already done on a previous pass.
                 */
                if ((ino >= UFSROOTINO) && (bits & 1)) {
                        /*
                         * The following test is merely an optimization
                         * for common case where "add" will just return.
                         */
                        if (!(fn == add && BIT(ino, nodmap)))
                                (*fn)(getino(ino));
                }
                bits >>= 1;
        }
}

void
mark(struct dinode *ip)
{
        int f;

        f = ip->di_mode & IFMT;
        if (f == 0 || ip->di_nlink <= 0) {
                /* LINTED: 32-bit to 8-bit assignment ok */
                BIC(ino, clrmap);
                return;
        }
        /* LINTED: 32-bit to 8-bit assignment ok */
        BIS(ino, clrmap);
        if (f == IFDIR || f == IFATTRDIR) {
                /* LINTED: 32-bit to 8-bit assignment ok */
                BIS(ino, dirmap);
        }
        if (ip->di_ctime >= spcl.c_ddate) {
                if (f == IFSHAD)
                        return;
                /* LINTED: 32-bit to 8-bit assignment ok */
                BIS(ino, nodmap);
                /* attribute changes impact the root */
                if (f == IFATTRDIR)
                        BIS(UFSROOTINO, nodmap);
                if (f != IFREG && f != IFDIR && f != IFATTRDIR && f != IFLNK) {
                        o_esize += 1;
                        return;
                }
                est(ip);
        }
}

void
active_mark(struct dinode *ip)
{
        int f;

        f = ip->di_mode & IFMT;
        if (f == 0 || ip->di_nlink <= 0) {
                /* LINTED: 32-bit to 8-bit assignment ok */
                BIC(ino, clrmap);
                return;
        }
        /* LINTED: 32-bit to 8-bit assignment ok */
        BIS(ino, clrmap);
        if (f == IFDIR || f == IFATTRDIR) {
                /* LINTED: 32-bit to 8-bit assignment ok */
                BIS(ino, dirmap);
        }
        if (BIT(ino, activemap)) {
                /* LINTED: 32-bit to 8-bit assignment ok */
                BIS(ino, nodmap);
                /* attribute changes impact the root */
                if (f == IFATTRDIR)
                        BIS(UFSROOTINO, nodmap);
                if (f != IFREG && f != IFDIR && f != IFATTRDIR && f != IFLNK) {
                        o_esize += 1;
                        return;
                }
                est(ip);
        }
}

static struct shcount {
        struct shcount *higher, *lower;
        ino_t ino;
        unsigned long count;
} shcounts = {
        NULL, NULL,
        0,
        0
};
static struct shcount *shc = NULL;

void
markshad(struct dinode *ip)
{
        ino_t shadow;

        if (ip->di_shadow == 0)
                return;
        if (shc == NULL)
                shc = &shcounts;

        shadow = (ino_t)(unsigned)(ip->di_shadow);
        while ((shadow > shc->ino) && (shc->higher))
                shc = shc->higher;
        while ((shadow < shc->ino) && (shc->lower))
                shc = shc->lower;
        if (shadow != shc->ino) {
                struct shcount *new;

                new = (struct shcount *)xcalloc(1, sizeof (*new));
                new->higher = shc->higher;
                if (shc->higher != NULL)
                        shc->higher->lower = new;
                shc->higher = new;
                new->lower = shc;
                shc = new;
                shc->ino = shadow;
        }

        /* LINTED: 32-bit to 8-bit assignment ok */
        BIS(shadow, shamap);
        shc->count++;
}

void
estshad(struct dinode *ip)
{
        u_offset_t esizeprime;
        u_offset_t tmpesize;

        if (ip->di_size <= sizeof (union u_shadow))
                return;

        while ((ino > shc->ino) && (shc->higher))
                shc = shc->higher;
        while ((ino < shc->ino) && (shc->lower))
                shc = shc->lower;
        if (ino != shc->ino)
                return; /* xxx panic? complain? */

        tmpesize = (o_esize + f_esize);
        esizeprime = tmpesize;
        est(ip);
        esizeprime = tmpesize - esizeprime;
        esizeprime *= shc->count - 1;
        f_esize += esizeprime;
}

void
freeshad()
{
        if (shc == NULL)
                return;

        while (shc->higher)
                shc = shc->higher;
        while (shc->lower) {
                shc = shc->lower;
                if (shc->higher) /* else panic? */
                        (void) free(shc->higher);
        }
        /*
         * This should be unnecessary, but do it just to be safe.
         * Note that shc might be malloc'd or static, so can't free().
         */
        bzero(shc, sizeof (*shc));
}

void
add(struct dinode *ip)
{
        int i;
        u_offset_t filesize;

        if (BIT(ino, nodmap))
                return;
        if ((ip->di_mode & IFMT) != IFDIR &&
            (ip->di_mode & IFMT) != IFATTRDIR) {
                (void) snprintf(msgbuf, sizeof (msgbuf), gettext(
                    "Warning - directory at inode `%lu' vanished!\n"), ino);
                msg(msgbuf);
                /* LINTED: 32-bit to 8-bit assignment ok */
                BIC(ino, dirmap);
                return;
        }
        nsubdir = 0;
        dadded = 0;
        filesize = ip->di_size;
        for (i = 0; i < NDADDR; i++) {
                if (ip->di_db[i] != 0)
                        /* LINTED dblksize/blkoff does a safe cast here */
                        dsrch(ip->di_db[i], (ulong_t)dblksize(sblock, ip, i),
                            filesize);
                filesize -= (unsigned)(sblock->fs_bsize);
        }
        for (i = 0; i < NIADDR; i++) {
                if (ip->di_ib[i] != 0)
                        indir(ip->di_ib[i], i, &filesize);
        }
        if (dadded) {
                nadded++;
                if (!BIT(ino, nodmap)) {
                        /* LINTED: 32-bit to 8-bit assignment ok */
                        BIS(ino, nodmap);
                        if ((ip->di_mode & IFMT) == IFATTRDIR) {
                                /* attribute changes "auto-percolate" to root */
                                BIS(UFSROOTINO, nodmap);
                        }
                        est(ip);
                }
        }
        if (nsubdir == 0) {
                if (!BIT(ino, nodmap)) {
                        /* LINTED: 32-bit to 8-bit assignment ok */
                        BIC(ino, dirmap);
                }
        }
}

static void
indir(daddr32_t d, int n, u_offset_t *filesize)
{
        int i;
        daddr32_t idblk[MAXNINDIR];

        if ((unsigned)(sblock->fs_bsize) > sizeof (idblk)) {
                msg(gettext(
"Inconsistency detected: filesystem block size larger than valid maximum.\n"));
                dumpabort();
                /*NOTREACHED*/
        }

        if ((unsigned)NINDIR(sblock) > MAXNINDIR) {
                /*CSTYLED*/
                msg(gettext(
"Inconsistency detected: inode has more indirect \
blocks than valid maximum.\n"));
                dumpabort();
                /*NOTREACHED*/
        }

        if (dadded || *filesize == 0)
                return;

#ifdef  lint
        idblk[0] = '\0';
#endif  /* lint */

        /* xxx sanity check sblock contents before trusting them */
        bread(fsbtodb(sblock, d), (uchar_t *)idblk, (size_t)sblock->fs_bsize);
        if (n <= 0) {
                for (i = 0; i < NINDIR(sblock); i++) {
                        d = idblk[i];
                        if (d != 0)
                                dsrch(d, (ulong_t)(uint32_t)sblock->fs_bsize,
                                    *filesize);
                        *filesize -= (unsigned)(sblock->fs_bsize);
                }
        } else {
                n--;
                for (i = 0; i < NINDIR(sblock); i++) {
                        d = idblk[i];
                        if (d != 0)
                                indir(d, n, filesize);
                }
        }
}

void
dirdump(struct dinode *ip)
{
        /* watchout for dir inodes deleted and maybe reallocated */
        if (((ip->di_mode & IFMT) != IFDIR &&
            (ip->di_mode & IFMT) != IFATTRDIR) || ip->di_nlink < 2) {
                (void) snprintf(msgbuf, sizeof (msgbuf), gettext(
                    "Warning - directory at inode `%lu' vanished!\n"), ino);
                msg(msgbuf);
                return;
        }
        lf_dump(ip);
}

static u_offset_t loffset; /* current offset in file (ufsdump) */

static void
lf_dumpmeta(struct dinode *ip)
{
        if ((ip->di_shadow == 0) || shortmeta)
                return;

        lf_dumpinode(getino((ino_t)(unsigned)(ip->di_shadow)));
}

int
hasshortmeta(struct dinode **ip)
{
        ino_t savino;
        int rc;

        if ((*ip)->di_shadow == 0)
                return (0);
        savino = ino;
        *ip = getino((ino_t)(unsigned)((*ip)->di_shadow));
        rc = ((*ip)->di_size <= sizeof (union u_shadow));
        *ip = getino(ino = savino);
        return (rc);
}

void
lf_dumpinode(struct dinode *ip)
{
        int i;
        u_offset_t size;

        i = ip->di_mode & IFMT;

        if (i == 0 || ip->di_nlink <= 0)
                return;

        spcl.c_dinode = *ip;
        spcl.c_count = 0;

        if ((i != IFDIR && i != IFATTRDIR && i != IFREG && i != IFLNK &&
            i != IFSHAD) || ip->di_size == 0) {
                toslave(dospcl, ino);
                return;
        }

        size = NDADDR * (unsigned)(sblock->fs_bsize);
        if (size > ip->di_size)
                size = ip->di_size;

        lf_blksout(&ip->di_db[0], size);

        size = ip->di_size - size;
        if (size > 0) {
                for (i = 0; i < NIADDR; i++) {
                        lf_dmpindir(ip->di_ib[i], i, &size);
                        if (size == 0)
                                break;
                }
        }
}

void
lf_dump(struct dinode *ip)
{

        if ((!BIT(ino, nodmap)) && (!BIT(ino, shamap)))
                return;

        shortmeta = hasshortmeta(&ip);
        if (shortmeta) {
                ip = getino((ino_t)(unsigned)(ip->di_shadow));
                /* assume spcl.c_shadow is smaller than 1 block */
                bread(fsbtodb(sblock, ip->di_db[0]),
                    (uchar_t *)spcl.c_shadow.c_shadow, sizeof (spcl.c_shadow));
                spcl.c_flags |= DR_HASMETA;
        } else {
                spcl.c_flags &= ~DR_HASMETA;
        }
        ip = getino(ino);

        loffset = 0;

        if (newtape) {
                spcl.c_type = TS_TAPE;
        } else if (pos)
                spcl.c_type = TS_ADDR;
        else
                spcl.c_type = TS_INODE;

        newtape = 0;
        lf_dumpinode(ip);
        lf_dumpmeta(ip);
        pos = 0;
}

static void
lf_dmpindir(daddr32_t blk, int lvl, u_offset_t *size)
{
        int i;
        u_offset_t cnt;
        daddr32_t idblk[MAXNINDIR];

        if ((unsigned)(sblock->fs_bsize) > sizeof (idblk)) {
                msg(gettext(
"Inconsistency detected: filesystem block size larger than valid maximum.\n"));
                dumpabort();
                /*NOTREACHED*/
        }

        if ((unsigned)NINDIR(sblock) > MAXNINDIR) {
                msg(gettext(
"Inconsistency detected: inode has more indirect \
blocks than valid maximum.\n"));
                dumpabort();
                /*NOTREACHED*/
        }

        if (blk != 0)
                bread(fsbtodb(sblock, blk), (uchar_t *)idblk,
                    (size_t)sblock->fs_bsize);
        else
                bzero((char *)idblk, (size_t)sblock->fs_bsize);
        if (lvl <= 0) {
                cnt = (u_offset_t)(unsigned)NINDIR(sblock) *
                    (u_offset_t)(unsigned)(sblock->fs_bsize);
                if (cnt > *size)
                        cnt = *size;
                *size -= cnt;
                lf_blksout(&idblk[0], cnt);
                return;
        }
        lvl--;
        for (i = 0; i < NINDIR(sblock); i++) {
                lf_dmpindir(idblk[i], lvl, size);
                if (*size == 0)
                        return;
        }
}

static void
lf_blksout(daddr32_t *blkp, u_offset_t bytes)
{
        u_offset_t i;
        u_offset_t tbperfsb = (unsigned)(sblock->fs_bsize / tp_bsize);

        u_offset_t j, k, count;

        u_offset_t bytepos, diff;
        u_offset_t bytecnt = 0;
        off_t byteoff = 0;      /* bytes to skip within first f/s block */
        off_t fragoff = 0;      /* frags to skip within first f/s block */

        u_offset_t tpblkoff = 0; /* tape blocks to skip in first f/s block */
        u_offset_t tpblkskip = 0;       /* total tape blocks to skip  */
        u_offset_t skip;                /* tape blocks to skip this pass */

        if (pos) {
                /*
                 * We get here if a slave throws a signal to the
                 * master indicating a partially dumped file.
                 * Begin by figuring out what was undone.
                 */
                bytepos = (offset_t)pos * tp_bsize;

                if ((loffset + bytes) <= bytepos) {
                        /* This stuff was dumped already, forget it. */
                        loffset += (u_offset_t)tp_bsize *
                            /* LINTED: spurious complaint on sign-extending */
                            d_howmany(bytes, (u_offset_t)tp_bsize);
                        return;
                }

                if (loffset < bytepos) {
                        /*
                         * Some of this was dumped, some wasn't.
                         * Figure out what was done and skip it.
                         */
                        diff = bytepos - loffset;
                        /* LINTED: spurious complaint on sign-extending */
                        tpblkskip = d_howmany(diff, (u_offset_t)tp_bsize);
                        /* LINTED room after EOT is only a few MB */
                        blkp += (int)(diff / sblock->fs_bsize);

                        bytecnt = diff % (unsigned)(sblock->fs_bsize);
                        /* LINTED: result fits, due to modulus */
                        byteoff = bytecnt % (off_t)(sblock->fs_fsize);
                        /* LINTED: spurious complaint on sign-extending */
                        tpblkoff = d_howmany(bytecnt,
                            (u_offset_t)(unsigned)tp_bsize);
                        /* LINTED: result fits, due to modulus */
                        fragoff = bytecnt / (off_t)(sblock->fs_fsize);
                        bytecnt = (unsigned)(sblock->fs_bsize) - bytecnt;
                }
        }

        loffset += bytes;

        while (bytes > 0) {
                if (bytes < TP_NINDIR*tp_bsize)
                        /* LINTED: spurious complaint on sign-extending */
                        count = d_howmany(bytes, (u_offset_t)tp_bsize);
                else
                        count = TP_NINDIR;
                if (tpblkskip) {
                        if (tpblkskip < TP_NINDIR) {
                                bytes -= (tpblkskip * (u_offset_t)tp_bsize);
                                skip = tpblkskip;
                                tpblkskip = 0;
                        } else {
                                bytes -= (offset_t)TP_NINDIR*tp_bsize;
                                tpblkskip -= TP_NINDIR;
                                continue;
                        }
                } else
                        skip = 0;
                assert(tbperfsb >= tpblkoff);
                assert((count - skip) <= TP_NINDIR);
                for (j = 0, k = 0; j < count - skip; j++, k++) {
                        spcl.c_addr[j] = (blkp[k] != 0);
                        for (i = tbperfsb - tpblkoff; --i > 0; j++)
                                spcl.c_addr[j+1] = spcl.c_addr[j];
                        tpblkoff = 0;
                }
                /* LINTED (count - skip) will always fit into an int32_t */
                spcl.c_count = count - skip;
                toslave(dospcl, ino);
                bytecnt = MIN(bytes, bytecnt ?
                    bytecnt : (unsigned)(sblock->fs_bsize));
                j = 0;
                while (j < count - skip) {
                        if (*blkp != 0) {
                                /* LINTED: fragoff fits into 32 bits */
                                dmpblk(*blkp+(int32_t)fragoff,
                                    /* LINTED: bytecnt fits into 32 bits */
                                    (size_t)bytecnt, byteoff);
                        }
                        blkp++;
                        bytes -= bytecnt;
                        /* LINTED: spurious complaint on sign-extending */
                        j += d_howmany(bytecnt, (u_offset_t)tp_bsize);
                        bytecnt = MIN(bytes, (unsigned)(sblock->fs_bsize));
                        byteoff = 0;
                        fragoff = 0;
                }
                spcl.c_type = TS_ADDR;
                bytecnt = 0;
        }
        pos = 0;
}

void
bitmap(uchar_t *map, int typ)
{
        int i;
        u_offset_t count;
        uchar_t *cp;

        if (!newtape)
                spcl.c_type = typ;
        else
                newtape = 0;
        for (i = 0; i < TP_NINDIR; i++)
                spcl.c_addr[i] = 1;
        /* LINTED: spurious complaint on sign-extending */
        count = d_howmany(msiz * sizeof (map[0]), tp_bsize) - pos;
        for (cp = &map[pos * tp_bsize]; count > 0;
            count -= (u_offset_t)(unsigned)spcl.c_count) {
                if (leftover) {
                        spcl.c_count = leftover;
                        leftover = 0;
                } else {
                        /* LINTED value always less than INT32_MAX */
                        spcl.c_count = count > TP_NINDIR ? TP_NINDIR : count;
                }
                spclrec();
                for (i = 0; i < spcl.c_count; i++, cp += tp_bsize)
                        taprec(cp, 0, tp_bsize);
                spcl.c_type = TS_ADDR;
        }
}

static void
dsrch(daddr32_t d, ulong_t size, u_offset_t filesize)
{
        struct direct *dp;
        struct dinode *ip;
        ulong_t loc;
        char dblk[MAXBSIZE];

        if (dadded || filesize == 0)
                return;
        if (filesize > (u_offset_t)size)
                filesize = (u_offset_t)size;
        if (sizeof (dblk) < roundup(filesize, DEV_BSIZE)) {
                msg(gettext(
"Inconsistency detected: filesystem block size larger than valid maximum.\n"));
                dumpabort();
                /*NOTREACHED*/
        }

#ifdef  lint
        dblk[0] = '\0';
#endif  /* lint */

        /* LINTED ufs disk addresses always fit into 32 bits */
        bread(fsbtodb(sblock, d), (uchar_t *)dblk,
            /* LINTED from sizeof check above, roundup() <= max(size_t) */
            (size_t)(roundup(filesize, DEV_BSIZE)));
        loc = 0;
        while ((u_offset_t)loc < filesize) {
                /*LINTED [dblk is char[], loc (dp->d_reclen) % 4 == 0]*/
                dp = (struct direct *)(dblk + loc);
                if (dp->d_reclen == 0) {
                        (void) snprintf(msgbuf, sizeof (msgbuf), gettext(
                            "Warning - directory at inode `%lu' is "
                            "corrupted\n"), ino);
                        msg(msgbuf);
                        break;
                }
                loc += dp->d_reclen;
                if (dp->d_ino == 0)
                        continue;
                if (dp->d_name[0] == '.') {
                        if (dp->d_name[1] == '\0') {
                                if ((ino_t)(dp->d_ino) != ino) {
                                        (void) snprintf(msgbuf, sizeof (msgbuf),
                                            gettext(
                        "Warning - directory at inode `%lu' is corrupted:\n\
\t\".\" points to inode `%lu' - run fsck\n"),
                                            ino, dp->d_ino);
                                        msg(msgbuf);
                                }
                                continue;
                        }
                        if (dp->d_name[1] == '.' && dp->d_name[2] == '\0') {
                                if (!BIT(dp->d_ino, dirmap) &&
                                    ((ip = getino(ino)) == NULL ||
                                    (ip->di_mode & IFMT) != IFATTRDIR)) {
                                        (void) snprintf(msgbuf, sizeof (msgbuf),
                                            gettext(
                        "Warning - directory at inode `%lu' is corrupted:\n\
\t\"..\" points to non-directory inode `%lu' - run fsck\n"),
                                            ino, dp->d_ino);
                                        msg(msgbuf);
                                }
                                continue;
                        }
                }
                if (BIT(dp->d_ino, nodmap)) {
                        dadded++;
                        return;
                }
                if (BIT(dp->d_ino, dirmap))
                        nsubdir++;
        }
}

#define CACHESIZE 32

struct dinode *
getino(ino_t ino)
{
        static ino_t minino, maxino;
        static struct dinode itab[MAXINOPB];
        static struct dinode icache[CACHESIZE];
        static ino_t icacheval[CACHESIZE], lasti = 0;
        static int cacheoff = 0;
        int i;

        if (ino >= minino && ino < maxino) {
                lasti = ino;
                return (&itab[ino - minino]);
        }

        /* before we do major i/o, check for a secondary cache hit */
        for (i = 0; i < CACHESIZE; i++)
                if (icacheval[i] == ino)
                        return (icache + i);

        /* we need to do major i/o.  throw the last inode retrieved into */
        /* the cache.  note: this copies garbage the first time it is    */
        /* used, but no harm done.                                       */
        icacheval[cacheoff] = lasti;
        bcopy(itab + (lasti - minino), icache + cacheoff, sizeof (itab[0]));
        lasti = ino;
        if (++cacheoff >= CACHESIZE)
                cacheoff = 0;

#define INOPERDB (DEV_BSIZE / sizeof (struct dinode))
        minino = ino &~ (INOPERDB - 1);
        maxino = ((itog(sblock, ino) + 1) * (unsigned)(sblock->fs_ipg));
        if (maxino > minino + MAXINOPB)
                maxino = minino + MAXINOPB;
        bread(
            /* LINTED: can't make up for broken system macros here */
            (fsbtodb(sblock, itod(sblock, ino)) + itoo(sblock, ino) / INOPERDB),
            /* LINTED: (max - min) * size fits into a size_t */
            (uchar_t *)itab, (size_t)((maxino - minino) * sizeof (*itab)));
        return (&itab[ino - minino]);
}

#define BREADEMAX 32

#ifdef NO__LONGLONG__
#define DEV_LSEEK(fd, offset, whence) \
        lseek((fd), (((off_t)(offset))*DEV_BSIZE), (whence))
#else
#define DEV_LSEEK(fd, offset, whence) \
        llseek((fd), (((offset_t)((offset)))*DEV_BSIZE), (whence))
#endif

#define BREAD_FAIL(buf, size)   { \
                breaderrors += 1; \
                bzero(buf, (size_t)size); \
        }



void
bread(diskaddr_t da, uchar_t *ba, size_t cnt)
{
        caddr_t maddr;
        uchar_t *dest;
        int saverr;
        int n;
        size_t len;
        off64_t filoff;
        off64_t mapoff;
        off64_t displacement;

        static size_t pagesize = 0;
        static int breaderrors = 0;

        /* mechanics for caching small bread requests.  these are */
        /* often small ACLs that are used over and over.          */
        static uchar_t bcache[DEV_BSIZE * CACHESIZE];
        static diskaddr_t bcacheval[CACHESIZE];
        static int cacheoff = 0;
        int i;

        if ((cnt >= DEV_BSIZE) && (mapfd != -1)) {
                if (pagesize == 0)
                        pagesize = getpagesize();
                /*
                 * We depend on mmap(2)'s guarantee that mapping a
                 * partial page will cause the remainder of the page
                 * to be zero-filled.
                 */
                filoff = ((off64_t)da) * DEV_BSIZE;
                displacement = filoff & (pagesize - 1);
                mapoff = filoff - displacement;
                /* LINTED offset will fit into 32 bits */
                len = (size_t)roundup(cnt + (filoff - mapoff), pagesize);
                maddr = mmap64(NULL, len, PROT_READ, MAP_SHARED, mapfd, mapoff);
                if (maddr != MAP_FAILED) {
                        (void) memcpy(ba, maddr + displacement, cnt);
                        (void) munmap(maddr, len);
                        return;
                }
        }

        if (DEV_LSEEK(fi, da, L_SET) < 0) {
                saverr = errno;
                msg(gettext("bread: dev_seek error: %s\n"), strerror(saverr));
                /* Don't know where we are, return the least-harmful data */
                BREAD_FAIL(ba, cnt);
                return;
        }

        if (read(fi, ba, (size_t)cnt) == (size_t)cnt)
                return;

        while (cnt != 0) {

                if (da >= fsbtodb(sblock, sblock->fs_size)) {
                        msg(gettext(
                            "Warning - block %llu is beyond the end of `%s'\n"),
                            da, disk);
                        BREAD_FAIL(ba, cnt);
                        break;
                }

                if (DEV_LSEEK(fi, da, L_SET) < 0) {
                        msg(gettext("%s: %s error\n"), "bread", "DEV_LSEEK2");
                        BREAD_FAIL(ba, cnt);
                        break;
                }

                if (cnt < DEV_BSIZE) {
                        /* small read.  check for cache hit. */
                        for (i = 0; i < CACHESIZE; i++)
                                if (bcacheval[i] == da) {
                                        bcopy(bcache + (i * DEV_BSIZE),
                                            ba, cnt);
                                        return;
                                }

                        /* no cache hit; throw this one into the cache... */
                        len = cnt;
                        dest = bcache + (cacheoff * DEV_BSIZE);
                        bcacheval[cacheoff] = da;
                        if (++cacheoff >= CACHESIZE)
                                cacheoff = 0;
                } else {
                        len = DEV_BSIZE;
                        dest = ba;
                }

                n = read(fi, dest, DEV_BSIZE);
                if (n != DEV_BSIZE) {
                        n = MAX(n, 0);
                        bzero(dest+n, DEV_BSIZE-n);
                        breaderrors += 1;
                        msg(gettext(
                            "Warning - cannot read sector %llu of `%s'\n"),
                            da, disk);
                }
                if (dest != ba)
                        bcopy(dest, ba, len);

                da++;
                /* LINTED character pointers aren't signed */
                ba += len;
                cnt -= len;
        }

        if (breaderrors > BREADEMAX) {
                msg(gettext(
                    "More than %d block read errors from dump device `%s'\n"),
                    BREADEMAX, disk);
                dumpailing();
                breaderrors = 0;
        }
}