root/usr/src/cmd/fs.d/udfs/fsck/pass1.c
/*
 * Copyright 1999 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T     */
/*        All Rights Reserved   */

/*
 * Copyright (c) 1980, 1986, 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by the University of California, Berkeley and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. Neither the name of the University nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <stdio.h>
#include <strings.h>
#include <malloc.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/mntent.h>
#include <sys/vnode.h>
#include <sys/fs/udf_volume.h>
#include <sys/dkio.h>
#include <sys/vtoc.h>
#include "fsck.h"
#include "udfs.h"
#include <locale.h>

uint64_t maxuniqid;     /* maximum unique id on medium */

/*
 * for each large file ( size > MAXOFF_T) this global counter
 * gets incremented here.
 */

extern unsigned int largefile_count;
extern void     pwarn(char *, ...);
extern void     pfatal(char *, ...);
extern void     errexit(char *, ...);

extern int32_t  verifytag(struct tag *, uint32_t, struct tag *, int);
extern char     *tagerrs[];
extern void     maketag(struct tag *, struct tag *);
extern void     flush(int32_t, struct bufarea *);
extern void     putfilentry(struct bufarea *);
extern int32_t  bread(int32_t, char *, daddr_t, long);
extern void     bwrite(int, char *, daddr_t, long);
extern int32_t  dofix(struct inodesc *, char *);
extern int32_t  reply(char *);
extern void     ud_swap_short_ad(short_ad_t *);
extern void     ud_swap_long_ad(long_ad_t *);

extern void     dump16(char *, char *);

static void     adjust(struct fileinfo *);
static void     opndir(struct file_entry *);
static int32_t  getdir(struct file_entry *, struct bufarea **,
        u_offset_t *, struct file_id **);
static void ckinode(struct file_entry *);
struct bufarea *getfilentry();

/* Fields for traversing an allocation extent */
static uint32_t dir_adrsize;
static uint32_t dir_adrindx;
static uint32_t dir_naddrs;
static uint8_t *extbuf;
static uint8_t *dir_adrlist;

/* Keep track of where we are in the directory */
static u_offset_t dir_baseoff;
static uint32_t dir_basesize;
static uint8_t *dirbuf;
static uint8_t *dir_fidp;
static uint32_t baseblock;

#define MAXFIDSIZE 2048

static uint8_t fidbuf[MAXFIDSIZE];

void
pass1(void)
{
        struct file_entry *fp;
        struct fileinfo *fip;
        struct bufarea *bp;
        struct file_id *fidp;
        struct bufarea *fbp;
        int err;

        (void) cachefile(rootblock, rootlen);
        fip = &inphead[0];              /* The root */
        fip->fe_lseen = 0;              /* Didn't get here through directory */
        n_files = n_dirs = 0;
        while (fip->fe_block) {
                u_offset_t offset, end;

                markbusy(fip->fe_block, fip->fe_len);
                bp = getfilentry(fip->fe_block, fip->fe_len);
                if (bp == NULL) {
                        pwarn(gettext("Unable to read file entry at %x\n"),
                                fip->fe_block);
                        goto next;
                }
                fp = (struct file_entry *)bp->b_un.b_buf;
                fip->fe_lcount = fp->fe_lcount;
                fip->fe_type = fp->fe_icb_tag.itag_ftype;
                if (fp->fe_uniq_id >= maxuniqid)
                        maxuniqid = fp->fe_uniq_id + 1;

                if (fip->fe_block == rootblock &&
                                fip->fe_type != FTYPE_DIRECTORY)
                        errexit(gettext("Root file entry is not a "
                                "directory\n"));

                if (debug) {
                        (void) printf("do %x len %d type %d lcount %d"
                                " lseen %d end %llx\n",
                                fip->fe_block, fip->fe_len,
                                fip->fe_type, fip->fe_lcount,
                                fip->fe_lseen, fp->fe_info_len);
                }
                switch (fip->fe_type) {
                case FTYPE_DIRECTORY:
                        n_dirs++;
                        offset = 0;
                        end = fp->fe_info_len;
                        fbp = NULL;
                        opndir(fp);
                        for (offset = 0; offset < end;
                                        offset += FID_LENGTH(fidp)) {
                                err = getdir(fp, &fbp, &offset, &fidp);
                                if (err) {
                                        pwarn(gettext("Bad directory entry in "
                                                "file %x at offset %llx\n"),
                                                fip->fe_block, offset);
                                        offset = end;
                                }
                                if (fidp->fid_flags & FID_DELETED)
                                        continue;
                                (void) cachefile(fidp->fid_icb.lad_ext_loc,
                                        fidp->fid_icb.lad_ext_len);
                        }
                        if (dirbuf) {
                                free(dirbuf);
                                dirbuf = NULL;
                        }
                        if (fbp)
                                fbp->b_flags &= ~B_INUSE;
                        if (debug)
                                (void) printf("Done %x\n", fip->fe_block);
                        break;

                case FTYPE_FILE:
                case FTYPE_SYMLINK:
                        ckinode(fp);
                        /* FALLTHROUGH */
                default:
                        n_files++;
                        break;
                }
                putfilentry(bp);
                bp->b_flags &= ~B_INUSE;
        next:
                /* At end of this set of fips, get the next set */
                if ((++fip)->fe_block == (uint32_t)-1)
                        fip = fip->fe_nexthash;
        }

        /* Find bad link counts */
        fip = &inphead[0];
        while (fip->fe_block) {
                if (fip->fe_lcount != fip->fe_lseen)
                        adjust(fip);
                /* At end of this set of fips, get the next set */
                if ((++fip)->fe_block == (uint32_t)-1)
                        fip = fip->fe_nexthash;
        }
}

static void
opndir(struct file_entry *fp)
{
        if (dirbuf) {
                free(dirbuf);
                dirbuf = NULL;
        }
        if (extbuf) {
                free(extbuf);
                extbuf = NULL;
        }

        dir_baseoff = 0;
        dir_basesize = 0;
        dir_adrindx = 0;

        switch (fp->fe_icb_tag.itag_flags & 0x3) {
        case ICB_FLAG_SHORT_AD:
                dir_adrsize = sizeof (short_ad_t);
                dir_naddrs = fp->fe_len_adesc / sizeof (short_ad_t);
                dir_adrlist = (uint8_t *)(fp->fe_spec + fp->fe_len_ear);
                break;
        case ICB_FLAG_LONG_AD:
                dir_adrsize = sizeof (long_ad_t);
                dir_naddrs = fp->fe_len_adesc / sizeof (long_ad_t);
                dir_adrlist = (uint8_t *)(fp->fe_spec + fp->fe_len_ear);
                break;
        case ICB_FLAG_EXT_AD:
                errexit(gettext("Can't handle ext_ads in directories/n"));
                break;
        case ICB_FLAG_ONE_AD:
                dir_adrsize = 0;
                dir_naddrs = 0;
                dir_adrlist = NULL;
                dir_basesize = fp->fe_len_adesc;
                dir_fidp = (uint8_t *)(fp->fe_spec + fp->fe_len_ear);
                baseblock = fp->fe_tag.tag_loc;
                break;
        }
}

/* Allocate and read in an allocation extent */
/* ARGSUSED */
int
getallocext(struct file_entry *fp, uint32_t loc, uint32_t len)
{
        uint32_t nb;
        uint8_t *ap;
        int i;
        int err;
        struct alloc_ext_desc *aep;

        if (debug)
                (void) printf(" allocext loc %x len %x\n", loc, len);
        nb = roundup(len, secsize);
        if (extbuf)
                free(extbuf);
        extbuf = (uint8_t *)malloc(nb);
        if (extbuf == NULL)
                errexit(gettext("Can't allocate directory extent buffer\n"));
        if (bread(fsreadfd, (char *)extbuf,
                        fsbtodb(loc + part_start), nb) != 0) {
                (void) fprintf(stderr,
                        gettext("Can't read allocation extent\n"));
                return (1);
        }
        aep = (struct alloc_ext_desc *)extbuf;
        err = verifytag(&aep->aed_tag, loc, &aep->aed_tag, UD_ALLOC_EXT_DESC);
        if (err) {
                (void) printf(
                        gettext("Bad tag on alloc extent: %s\n"), tagerrs[err]);
                free(extbuf);
                return (1);
        }
        dir_adrlist = (uint8_t *)(aep + 1);
        dir_naddrs = aep->aed_len_aed / dir_adrsize;
        dir_adrindx = 0;

        /* Swap the descriptors */
        for (i = 0, ap = dir_adrlist; i < dir_naddrs; i++, ap += dir_adrsize) {
                if (dir_adrsize == sizeof (short_ad_t)) {
                        ud_swap_short_ad((short_ad_t *)ap);
                } else if (dir_adrsize == sizeof (long_ad_t)) {
                        ud_swap_long_ad((long_ad_t *)ap);
                }
        }

        return (0);
}

/*
 * Variables used in this function and their relationships:
 *  *poffset - read pointer in the directory
 *  dir_baseoff - offset at start of dirbuf
 *  dir_baselen - length of valid data in current extent
 *  dir_adrindx - index into current allocation extent for location of
 *      dir_baseoff
 *  dir_naddrs - number of entries in current allocation extent
 *  dir_fidp - pointer to dirbuf or immediate data in file entry
 *  baseblock - block address of dir_baseoff
 *  newoff - *poffset - dir_baseoff
 */
/* ARGSUSED1 */
static int32_t
getdir(struct file_entry *fp, struct bufarea **fbp,
        u_offset_t *poffset, struct file_id **fidpp)
{
        struct file_id *fidp = (struct file_id *)fidbuf;
        struct short_ad *sap;
        struct long_ad *lap;
        int i, newoff, xoff = 0;
        uint32_t block = 0, nb, len = 0, left;
        u_offset_t offset;
        int err, type = 0;


again:
        offset = *poffset;
again2:
        if (debug)
                (void) printf("getdir %llx\n", offset);
        newoff = offset - dir_baseoff;
        if (newoff >= dir_basesize) {
                if (dirbuf) {
                        free(dirbuf);
                        dirbuf = NULL;
                }
        } else {
                if (block == 0)
                        block = baseblock + (newoff / secsize);
                goto nextone;
        }

again3:
        switch (fp->fe_icb_tag.itag_flags & 0x3) {
        case ICB_FLAG_SHORT_AD:
                sap = &((short_ad_t *)dir_adrlist)[dir_adrindx];
                for (i = dir_adrindx; i < dir_naddrs; i++, sap++) {
                        len = EXTLEN(sap->sad_ext_len);
                        type = EXTYPE(sap->sad_ext_len);
                        if (type == 3) {
                                if (i < dir_naddrs - 1)
                                        errexit(gettext("Allocation extent not "
                                                "at end of list\n"));
                                markbusy(sap->sad_ext_loc, len);
                                if (getallocext(fp, sap->sad_ext_loc, len))
                                        return (1);
                                goto again3;
                        }
                        if (newoff < len)
                                break;
                        newoff -= len;
                        dir_baseoff += len;
                        if (debug)
                                (void) printf(
                                    " loc %x len %x\n", sap->sad_ext_loc,
                                        len);
                }
                dir_adrindx = i;
                if (debug)
                        (void) printf(" loc %x len %x\n", sap->sad_ext_loc,
                                sap->sad_ext_len);
                baseblock = sap->sad_ext_loc;
                if (block == 0)
                        block = baseblock;
                dir_basesize = len;
                if (type < 2)
                        markbusy(sap->sad_ext_loc, len);
                if (type != 0) {
                        *poffset += dir_basesize;
                        goto again;
                }
                nb = roundup(len, secsize);
                dirbuf = (uint8_t *)malloc(nb);
                if (dirbuf == NULL)
                        errexit(gettext("Can't allocate directory extent "
                                "buffer\n"));
                if (bread(fsreadfd, (char *)dirbuf,
                                fsbtodb(baseblock + part_start), nb) != 0) {
                        errexit(gettext("Can't read directory extent\n"));
                }
                dir_fidp = dirbuf;
                break;
        case ICB_FLAG_LONG_AD:
                lap = &((long_ad_t *)dir_adrlist)[dir_adrindx];
                for (i = dir_adrindx; i < dir_naddrs; i++, lap++) {
                        len = EXTLEN(lap->lad_ext_len);
                        type = EXTYPE(lap->lad_ext_len);
                        if (type == 3) {
                                if (i < dir_naddrs - 1)
                                        errexit(gettext("Allocation extent not "
                                                "at end of list\n"));
                                markbusy(lap->lad_ext_loc, len);
                                if (getallocext(fp, lap->lad_ext_loc, len))
                                        return (1);
                                goto again3;
                        }
                        if (newoff < len)
                                break;
                        newoff -= len;
                        dir_baseoff += len;
                        if (debug)
                                (void) printf(
                                    " loc %x len %x\n", lap->lad_ext_loc,
                                        len);
                }
                dir_adrindx = i;
                if (debug)
                        (void) printf(" loc %x len %x\n", lap->lad_ext_loc,
                                lap->lad_ext_len);
                baseblock = lap->lad_ext_loc;
                if (block == 0)
                        block = baseblock;
                dir_basesize = len;
                if (type < 2)
                        markbusy(lap->lad_ext_loc, len);
                if (type != 0) {
                        *poffset += dir_basesize;
                        goto again;
                }
                nb = roundup(len, secsize);
                dirbuf = (uint8_t *)malloc(nb);
                if (dirbuf == NULL)
                        errexit(gettext("Can't allocate directory extent "
                                "buffer\n"));
                if (bread(fsreadfd, (char *)dirbuf,
                                fsbtodb(baseblock + part_start), nb) != 0) {
                        errexit(gettext("Can't read directory extent\n"));
                }
                dir_fidp = dirbuf;
                break;
        case ICB_FLAG_EXT_AD:
                break;
        case ICB_FLAG_ONE_AD:
                errexit(gettext("Logic error in getdir - at ICB_FLAG_ONE_AD "
                        "case\n"));
                break;
        }
nextone:
        if (debug)
                (void) printf("getdirend blk %x dir_baseoff %llx newoff %x\n",
                        block, dir_baseoff, newoff);
        left = dir_basesize - newoff;
        if (xoff + left > MAXFIDSIZE)
                left = MAXFIDSIZE - xoff;
        bcopy((char *)dir_fidp + newoff, (char *)fidbuf + xoff, left);
        xoff += left;
        /*
         * If we have a fid that crosses an extent boundary, then force
         * a read of the next extent, and fill up the rest of the fid.
         */
        if (xoff < sizeof (fidp->fid_tag) ||
            xoff < sizeof (fidp->fid_tag) + SWAP16(fidp->fid_tag.tag_crc_len)) {
                offset += left;
                if (debug)
                        (void) printf("block crossing at offset %llx\n",
                                offset);
                goto again2;
        }
        err = verifytag(&fidp->fid_tag, block, &fidp->fid_tag, UD_FILE_ID_DESC);
        if (debug) {
                dump16((char *)fidp, "\n");
        }
        if (err) {
                pwarn(gettext("Bad directory tag: %s\n"), tagerrs[err]);
                return (err);
        }
        *fidpp = fidp;
        return (0);
}

static void
ckinode(struct file_entry *fp)
{
        struct short_ad *sap = NULL;
        struct long_ad *lap;
        int i, type, len;

        switch (fp->fe_icb_tag.itag_flags & 0x3) {
        case ICB_FLAG_SHORT_AD:
                dir_adrsize = sizeof (short_ad_t);
                dir_naddrs = fp->fe_len_adesc / sizeof (short_ad_t);
                sap = (short_ad_t *)(fp->fe_spec + fp->fe_len_ear);
again1:
                for (i = 0; i < dir_naddrs; i++, sap++) {
                        len = EXTLEN(sap->sad_ext_len);
                        type = EXTYPE(sap->sad_ext_len);
                        if (type < 2)
                                markbusy(sap->sad_ext_loc, len);
                        if (debug)
                                (void) printf(
                                    " loc %x len %x\n", sap->sad_ext_loc,
                                        sap->sad_ext_len);
                        if (type == 3) {
                                markbusy(sap->sad_ext_loc, len);
                                /* This changes dir_naddrs and dir_adrlist */
                                if (getallocext(fp, sap->sad_ext_loc, len))
                                        break;
                                sap = (short_ad_t *)dir_adrlist;
                                goto again1;
                        }
                }
                break;
        case ICB_FLAG_LONG_AD:
                dir_adrsize = sizeof (long_ad_t);
                dir_naddrs = fp->fe_len_adesc / sizeof (long_ad_t);
                lap = (long_ad_t *)(fp->fe_spec + fp->fe_len_ear);
again2:
                for (i = 0; i < dir_naddrs; i++, lap++) {
                        len = EXTLEN(lap->lad_ext_len);
                        type = EXTYPE(lap->lad_ext_len);
                        if (type < 2)
                                markbusy(lap->lad_ext_loc, len);
                        if (debug)
                                (void) printf(
                                    " loc %x len %x\n", lap->lad_ext_loc,
                                        lap->lad_ext_len);
                        if (type == 3) {
                                markbusy(sap->sad_ext_loc, len);
                                /* This changes dir_naddrs and dir_adrlist */
                                if (getallocext(fp, lap->lad_ext_loc, len))
                                        break;
                                lap = (long_ad_t *)dir_adrlist;
                                goto again2;
                        }
                }
                break;
        case ICB_FLAG_EXT_AD:
                break;
        case ICB_FLAG_ONE_AD:
                break;
        }
}

static void
adjust(struct fileinfo *fip)
{
        struct file_entry *fp;
        struct bufarea *bp;

        bp = getfilentry(fip->fe_block, fip->fe_len);
        if (bp == NULL)
                errexit(gettext("Unable to read file entry at %x\n"),
                        fip->fe_block);
        fp = (struct file_entry *)bp->b_un.b_buf;
        pwarn(gettext("LINK COUNT %s I=%x"),
                fip->fe_type == FTYPE_DIRECTORY ? "DIR" :
                fip->fe_type == FTYPE_SYMLINK ? "SYM" :
                fip->fe_type == FTYPE_FILE ? "FILE" : "???", fip->fe_block);
        (void) printf(gettext(" COUNT %d SHOULD BE %d"),
                fip->fe_lcount, fip->fe_lseen);
        if (preen) {
                if (fip->fe_lseen > fip->fe_lcount) {
                        (void) printf("\n");
                        pfatal(gettext("LINK COUNT INCREASING"));
                }
                (void) printf(gettext(" (ADJUSTED)\n"));
        }
        if (preen || reply(gettext("ADJUST")) == 1) {
                fp->fe_lcount = fip->fe_lseen;
                putfilentry(bp);
                dirty(bp);
                flush(fswritefd, bp);
        }
        bp->b_flags &= ~B_INUSE;
}

void
dofreemap(void)
{
        int i;
        char *bp, *fp;
        struct inodesc idesc;

        if (freemap == NULL)
                return;

        /* Flip bits in the busy map */
        bp = busymap;
        for (i = 0, bp = busymap; i < part_bmp_bytes; i++, bp++)
                *bp = ~*bp;

        /* Mark leftovers in byte as allocated */
        if (part_len % NBBY)
                bp[-1] &= (unsigned)0xff >> (NBBY - part_len % NBBY);
        bp = busymap;
        fp = freemap;
        bzero((char *)&idesc, sizeof (struct inodesc));
        idesc.id_type = ADDR;
        if (bcmp(bp, fp, part_bmp_bytes) != 0 &&
                dofix(&idesc, gettext("BLK(S) MISSING IN FREE BITMAP"))) {
                bcopy(bp, fp, part_bmp_bytes);
                maketag(&spacep->sbd_tag, &spacep->sbd_tag);
                bwrite(fswritefd, (char *)spacep, fsbtodb(part_bmp_loc),
                        part_bmp_sectors * secsize);
        }
}

void
dolvint(void)
{
        struct lvid_iu *lviup;
        struct inodesc idesc;

        bzero((char *)&idesc, sizeof (struct inodesc));
        idesc.id_type = ADDR;
        lviup = (struct lvid_iu *)&lvintp->lvid_fst[2];
        if ((lvintp->lvid_fst[0] != part_len - n_blks ||
            lvintp->lvid_int_type != LVI_CLOSE ||
            lviup->lvidiu_nfiles != n_files ||
            lviup->lvidiu_ndirs != n_dirs ||
            lvintp->lvid_uniqid < maxuniqid) &&
            dofix(&idesc, gettext("LOGICAL VOLUME INTEGRITY COUNTS WRONG"))) {
                lvintp->lvid_int_type = LVI_CLOSE;
                lvintp->lvid_fst[0] = part_len - n_blks;
                lviup->lvidiu_nfiles = n_files;
                lviup->lvidiu_ndirs = n_dirs;
                lvintp->lvid_uniqid = maxuniqid;
                maketag(&lvintp->lvid_tag, &lvintp->lvid_tag);
                bwrite(fswritefd, (char *)lvintp, fsbtodb(lvintblock),
                        lvintlen);
        }
}