/*
 * 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 <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/param.h>
#include <sys/inode.h>
#include <sys/fs.h>
#include <sys/dir.h>
#include "fsck.h"

void
pass2()
{
    register DINODE *dp;
    struct inodesc rootdesc;

    bzero((char *)&rootdesc, sizeof(struct inodesc));
    rootdesc.id_type = ADDR;
    rootdesc.id_func = pass2check;
    rootdesc.id_number = ROOTINO;
    pathp = pathname;
    switch (getstate(ROOTINO)) {

    case USTATE:
        pfatal("ROOT INODE UNALLOCATED\n");
        if (reply("ALLOCATE") == 0)
            errexit("");
        if (allocdir(ROOTINO, ROOTINO) != ROOTINO)
            errexit("CANNOT ALLOCATE ROOT INODE\n");
        descend(&rootdesc, ROOTINO);
        break;

    case DCLEAR:
        pfatal("DUPS/BAD IN ROOT INODE\n");
        if (reply("REALLOCATE")) {
            freeino(ROOTINO);
            if (allocdir(ROOTINO, ROOTINO) != ROOTINO)
                errexit("CANNOT ALLOCATE ROOT INODE\n");
            descend(&rootdesc, ROOTINO);
            break;
        }
        if (reply("CONTINUE") == 0)
            errexit("");
        setstate(ROOTINO, DSTATE);
        descend(&rootdesc, ROOTINO);
        break;

    case FSTATE:
    case FCLEAR:
        pfatal("ROOT INODE NOT DIRECTORY\n");
        if (reply("REALLOCATE")) {
            freeino(ROOTINO);
            if (allocdir(ROOTINO, ROOTINO) != ROOTINO)
                errexit("CANNOT ALLOCATE ROOT INODE\n");
            descend(&rootdesc, ROOTINO);
            break;
        }
        if (reply("FIX") == 0)
            errexit("");
        dp = ginode(ROOTINO);
        dp->di_mode &= ~IFMT;
        dp->di_mode |= IFDIR;
        inodirty();
        setstate(ROOTINO, DSTATE);
        /* fall into ... */

    case DSTATE:
        descend(&rootdesc, ROOTINO);
        break;

    default:
        errexit("BAD STATE %d FOR ROOT INODE\n", getstate(ROOTINO));
    }
}

int
pass2check(idesc)
    struct inodesc *idesc;
{
    register DIRECT *dirp = idesc->id_dirp;
    char *curpathloc;
    int n, entrysize, ret = 0;
    DINODE *dp;
    static DIRECT proto;
    static char namebuf[MAXPATHLEN];

    /*
     * check for "."
     */
    if (idesc->id_entryno != 0)
        goto chk1;
    if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
        if (dirp->d_ino != idesc->id_number) {
            direrr(idesc->id_number, "BAD INODE NUMBER FOR '.'");
            dirp->d_ino = idesc->id_number;
            if (reply("FIX") == 1)
                ret |= ALTERED;
        }
        goto chk1;
    }
    direrr(idesc->id_number, "MISSING '.'");
    proto.d_ino = idesc->id_number;
    proto.d_namlen = 1;
    (void)strcpy(proto.d_name, ".");
    entrysize = DIRSIZ(&proto);
    if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
        pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
            dirp->d_name);
    } else if (dirp->d_reclen < entrysize) {
        pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
    } else if (dirp->d_reclen < 2 * entrysize) {
        proto.d_reclen = dirp->d_reclen;
        bcopy((char *)&proto, (char *)dirp, entrysize);
        if (reply("FIX") == 1)
            ret |= ALTERED;
    } else {
        n = dirp->d_reclen - entrysize;
        proto.d_reclen = entrysize;
        bcopy((char *)&proto, (char *)dirp, entrysize);
        idesc->id_entryno++;
        declncnt(dirp->d_ino);
        dirp = (DIRECT *)((char *)(dirp) + entrysize);
        bzero((char *)dirp, n);
        dirp->d_reclen = n;
        if (reply("FIX") == 1)
            ret |= ALTERED;
    }
chk1:
    if (idesc->id_entryno > 1)
        goto chk2;
    proto.d_ino = idesc->id_parent;
    proto.d_namlen = 2;
    (void)strcpy(proto.d_name, "..");
    entrysize = DIRSIZ(&proto);
    if (idesc->id_entryno == 0) {
        n = DIRSIZ(dirp);
        if (dirp->d_reclen < n + entrysize)
            goto chk2;
        proto.d_reclen = dirp->d_reclen - n;
        dirp->d_reclen = n;
        idesc->id_entryno++;
        declncnt(dirp->d_ino);
        dirp = (DIRECT *)((char *)(dirp) + n);
        bzero((char *)dirp, n);
        dirp->d_reclen = n;
    }
    if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
        if (dirp->d_ino != idesc->id_parent) {
            direrr(idesc->id_number, "BAD INODE NUMBER FOR '..'");
            dirp->d_ino = idesc->id_parent;
            if (reply("FIX") == 1)
                ret |= ALTERED;
        }
        goto chk2;
    }
    direrr(idesc->id_number, "MISSING '..'");
    if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
        pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
            dirp->d_name);
    } else if (dirp->d_reclen < entrysize) {
        pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
    } else {
        proto.d_reclen = dirp->d_reclen;
        bcopy((char *)&proto, (char *)dirp, entrysize);
        if (reply("FIX") == 1)
            ret |= ALTERED;
    }
chk2:
    if (dirp->d_ino == 0)
        return (ret|KEEPON);
    if (dirp->d_namlen <= 2 &&
        dirp->d_name[0] == '.' &&
        idesc->id_entryno >= 2) {
        if (dirp->d_namlen == 1) {
            direrr(idesc->id_number, "EXTRA '.' ENTRY");
            dirp->d_ino = 0;
            if (reply("FIX") == 1)
                ret |= ALTERED;
            return (KEEPON | ret);
        }
        if (dirp->d_name[1] == '.') {
            direrr(idesc->id_number, "EXTRA '..' ENTRY");
            dirp->d_ino = 0;
            if (reply("FIX") == 1)
                ret |= ALTERED;
            return (KEEPON | ret);
        }
    }
    curpathloc = pathp;
    *pathp++ = '/';
    if (pathp + dirp->d_namlen >= endpathname) {
        *pathp = '\0';
        errexit("NAME TOO LONG %s%s\n", pathname, dirp->d_name);
    }
    bcopy(dirp->d_name, pathp, dirp->d_namlen + 1);
    pathp += dirp->d_namlen;
    idesc->id_entryno++;
    n = 0;
    if (dirp->d_ino > imax || dirp->d_ino <= 0) {
        direrr(dirp->d_ino, "I OUT OF RANGE");
        n = reply("REMOVE");
    } else {
again:
        switch (getstate(dirp->d_ino)) {
        case USTATE:
            direrr(dirp->d_ino, "UNALLOCATED");
            n = reply("REMOVE");
            break;

        case DCLEAR:
        case FCLEAR:
            direrr(dirp->d_ino, "DUP/BAD");
            if ((n = reply("REMOVE")) == 1)
                break;
            dp = ginode(dirp->d_ino);
            setstate(dirp->d_ino, DIRCT(dp) ? DSTATE : FSTATE);
            goto again;

        case DFOUND:
            if (idesc->id_entryno > 2) {
                getpathname(namebuf, dirp->d_ino, dirp->d_ino);
                pwarn("%s %s %s\n", pathname,
                    "IS AN EXTRANEOUS HARD LINK TO DIRECTORY",
                    namebuf);
                if (preen)
                    printf(" (IGNORED)\n");
                else if ((n = reply("REMOVE")) == 1)
                    break;
            }
            /* fall through */

        case FSTATE:
            declncnt(dirp->d_ino);
            break;

        case DSTATE:
            descend(idesc, dirp->d_ino);
            if (getstate(dirp->d_ino) == DFOUND) {
                declncnt(dirp->d_ino);
            } else if (getstate(dirp->d_ino) == DCLEAR) {
                dirp->d_ino = 0;
                ret |= ALTERED;
            } else
                errexit("BAD RETURN STATE %d FROM DESCEND\n",
                    getstate(dirp->d_ino));
            break;

        default:
            errexit("BAD STATE %d FOR INODE I=%u\n",
                getstate(dirp->d_ino), dirp->d_ino);
        }
    }
    pathp = curpathloc;
    *pathp = '\0';
    if (n == 0)
        return (ret|KEEPON);
    dirp->d_ino = 0;
    return (ret|KEEPON|ALTERED);
}
