/**************************************************
UZI (Unix Z80 Implementation) Utilities:  fsck.c
***************************************************/

#define _HAVE_TYPES_H_
typedef short           int16;
typedef unsigned short  uint16;

#include <stdio.h>

#include <sys/unix.h>
#define MAIN
#include <sys/extern.h>
#undef MAIN

#define MAXDEPTH 20   /* Maximum depth of directory tree to search */

/* This checks a filesystem */

int dev;
struct filesys filsys;

char *bitmap;
int16 *linkmap;


char *strcpy(), *strcat(), *sprintf();
char *daread();
char *calloc();
char *tst_read();

int main( argc, argv )
int	argc;
char	*argv[];
{
	char	*buf;

	if( argc!=2 || argv[1][0]<'0' || argv[1][0]>'9' )
	{
		fprintf( stderr, "Usage: fsck device#\n" );
		exit( -1 );
	}

	dev = atoi( argv[1] );

	bufinit();
	if( d_open(dev) )
	{
		fprintf( stderr, "Can't open device number %d\n", dev );
		exit( -1 );
	}

	/* Read in the super block. */
	buf = daread(1);
	bcopy( buf, (char*)&filsys, sizeof(struct filesys) );

	/* Verify the fsize and isize parameters */
	if (filsys.s_mounted != SMOUNTED)
	{
		printf( "Device %d has invalid magic number %d. Fix?\n",
		        dev, filsys.s_mounted );
		if (!yes())
			exit(-1);
		filsys.s_mounted = SMOUNTED;
		dwrite( (blkno_t)1, (char*)&filsys );
	}

	printf( "Device %d has fsize = %d and isize = %d. Continue?\n",
	         dev, filsys.s_fsize, filsys.s_isize );
	if (!yes())
		exit(-1);

	bitmap  = calloc( filsys.s_fsize, sizeof(char) );
	linkmap = (int16*)calloc( 8*filsys.s_isize, sizeof(int16) );

	if (!bitmap || !linkmap)
	{
		fprintf(stderr,"Not enough memory.\n");
		exit(-1);
	}

	printf("Pass 1: Checking inodes.\n");
	pass1();

	printf("Pass 2: Rebuilding free list.\n");
	pass2();

	printf("Pass 3: Checking block allocation.\n");
	pass3();

	printf("Pass 4: Checking directory entries.\n");
	pass4();

	printf("Pass 5: Checking link counts.\n");
	pass5();

	bufsync();
	d_close(dev);
	printf("Done.\n");
	
	exit(0);
}



/* Pass 1 checks each inode independently for validity, zaps bad block
numbers in the inodes, and builds the block allocation map. */

pass1()
{
    uint16 n;
    struct dinode ino;
    uint16 mode;
    blkno_t b;
    blkno_t bno;
    uint16 icount;
    blkno_t *buf;

    blkno_t getblkno();

    icount = 0;

    for (n = ROOTINODE; n < 8*(filsys.s_isize-2); ++n)
    {
	iread(n, &ino);
	linkmap[n] = -1;

	if (ino.i_mode == 0)
	    continue;

	mode = ino.i_mode & F_MASK;

	/* Check mode */
	if (mode != F_REG && mode != F_DIR && mode != F_BDEV && mode != F_CDEV)
	{
	    printf("Inode %d with mode 0%o is not of correct type. Zap?\n",
		    n, ino.i_mode);
	    if (yes())
	    {
		ino.i_mode = 0;
		ino.i_nlink = 0;
		iwrite(n, &ino);
		continue;
	    }
	}

	linkmap[n] = 0;
	++icount;
	/* Check size */

	if (ino.i_size.o_offset >= 512)
	{
	    printf("Inode %d offset is too big with value of %d. Fix?\n",
		    n,ino.i_size.o_offset);
	    if (yes())
	    {
		while (ino.i_size.o_offset >= 512)
		{
		    ino.i_size.o_offset -= 512;
		    ++ino.i_size.o_blkno;
		}
		iwrite(n,&ino);
	    }
	}


	if (ino.i_size.o_offset < 0)
	{
	    printf("Inode %d offset is negative with value of %d. Fix?\n",
		    n,ino.i_size.o_offset);
	    if (yes())
	    {
		ino.i_size.o_offset = 0;
		iwrite(n,&ino);
	    }
	}

	
	/* Check blocks and build free block map */
	if (mode == F_REG || mode == F_DIR)
	{
	    /* Check singly indirect blocks */

	    for (b=18; b < 20; ++b)
	    {
		if (ino.i_addr[b] != 0 && (ino.i_addr[b] < filsys.s_isize ||
			ino.i_addr[b] >= filsys.s_fsize))
		{
		    printf("Inode %d singly indirect block %d is out of range with value of %u. Zap?\n",
			    n, b, ino.i_addr[b]);
		    if (yes())
		    {
			ino.i_addr[b] = 0;
			iwrite(n, &ino);
		    }
		}

		if (ino.i_addr[b] != 0 && ino.i_size.o_blkno < 18)
		{
		    printf("Inode %d singly indirect block %d is past end of file with value of %u. Zap?\n",
			    n, b, ino.i_addr[b]);
		    if (yes())
		    {
			ino.i_addr[b] = 0;
			iwrite(n, &ino);
		    }
		}
		if (ino.i_addr[b] != 0)
		    bitmap[ino.i_addr[b]] = 1;
	    }

	    /* Check the double indirect blocks */
	    if (ino.i_addr[19] != 0)
	    {
		buf = (blkno_t *)daread(ino.i_addr[19]);
		for (b=0; b < 256; ++b)
		{
		    if (buf[b] != 0 && (buf[b] < filsys.s_isize ||
			    buf[b] >= filsys.s_fsize))
		    {
			printf("Inode %d doubly indirect block %d is out of range with value of %u. Zap?\n",
				n, b, buf[b]);
			if (yes())
			{
			    buf[b] = 0;
			    dwrite(b, (char *)buf);
			}
		    }
		    if (buf[b] != 0)
			bitmap[buf[b]] = 1;
		    
		}
	    }


	    /* Check the rest */
	    for (bno=0; bno <= ino.i_size.o_blkno; ++bno)
	    {
		b = getblkno(&ino, bno);

	        if (b != 0 && (b < filsys.s_isize ||
			b >= filsys.s_fsize))
		{
		    printf("Inode %d block %d is out of range with value of %u. Zap?\n",
			    n, bno, b);
		    if (yes())
		    {
			setblkno(&ino, bno,0);
			iwrite(n, &ino);
		    }
		}

		if (b != 0)
		    bitmap[b] = 1;
	    }

	}
    }

    /* Fix free inode count in super block */
    if (filsys.s_tinode != 8*(filsys.s_isize-2) - ROOTINODE - icount)
    {
	printf("Free inode count in super block is %u should be %u. Fix?\n",
	    filsys.s_tinode, 8*(filsys.s_isize-2) - ROOTINODE - icount);
	
	if (yes())
	{
	    filsys.s_tinode = 8*(filsys.s_isize-2) - ROOTINODE - icount;
	    dwrite((blkno_t)1,(char *)&filsys);
	}
    }
}
		    


/* Clear inode free list, rebuild block free list using bit map. */

pass2()
{
    blkno_t j;
    blkno_t oldtfree;

    printf("Rebuild free list?\n");
    if (!yes())
	return;

    oldtfree = filsys.s_tfree;

    /* Initialize the super-block */

    filsys.s_ninode =  0;
    filsys.s_nfree =  1;
    filsys.s_free[0] =  0;
    filsys.s_tfree =  0;

    /* Free each block, building the free list */

    for (j= filsys.s_fsize-1; j >= filsys.s_isize; --j)
    {
	if (bitmap[j] == 0)
	{
	    if (filsys.s_nfree == 50)
	    {
		dwrite(j, (char *)&filsys.s_nfree);
		filsys.s_nfree = 0;
	    }

	    ++filsys.s_tfree;
	    filsys.s_free[(filsys.s_nfree)++] = j;
	}
    }

    dwrite((blkno_t)1,(char *)&filsys);

    if (oldtfree != filsys.s_tfree)
	printf("During free list regeneration s_tfree was changed to %d from %d.\n",
		filsys.s_tfree, oldtfree);

}



/* Pass 3 finds and fixes multiply allocated blocks. */
pass3()
{
    uint16 n;
    struct dinode ino;
    uint16 mode;
    blkno_t b;
    blkno_t bno;
    blkno_t newno;
    blkno_t blk_alloc();
    blkno_t getblkno();

    for (b = filsys.s_isize; b < filsys.s_fsize; ++b)
	bitmap[b] = 0;

    for (n = ROOTINODE; n < 8*(filsys.s_isize-2); ++n)
    {
	iread(n, &ino);

	mode = ino.i_mode & F_MASK;
	if (mode != F_REG && mode != F_DIR)
	    continue;

	/* Check singly indirect blocks */

	for (b=18; b < 20; ++b)
	{
	    if (ino.i_addr[b] != 0)
	    {
		if (bitmap[ino.i_addr[b]] != 0)
		{
		    printf("Indirect block %d in inode %u value %u multiply allocated. Fix?\n", b, n, ino.i_addr[b]);
		    if (yes())
		    {
			newno = blk_alloc(&filsys);
			if (newno == 0)
			    printf("Sorry... No more free blocks.\n");
			else
			{
			    dwrite(newno,daread(ino.i_addr[b]));
			    ino.i_addr[b] = newno;
			    iwrite(n, &ino);
			}
		    }
		}
		else
		    bitmap[ino.i_addr[b]] = 1;
	    }
	}

	/* Check the rest */
	for (bno=0; bno <= ino.i_size.o_blkno; ++bno)
	{
	    b = getblkno(&ino, bno);

	    if (b != 0)
	    {
		if (bitmap[b] != 0)
		{
		    printf("Block %d in inode %u value %u multiply allocated. Fix?\n",
		    	    bno, n, b);
		    if (yes())
		    {
			newno = blk_alloc(&filsys);
			if (newno == 0)
			    printf("Sorry... No more free blocks.\n");
			else
			{
			    dwrite(newno,daread(b));
			    setblkno(&ino, bno, newno);
			    iwrite(n, &ino);
			}
		    }
		}
		else
		    bitmap[b] = 1;
	    }
	}

    }

}

int depth;

/* Pass 4 traverses the directory tree, fixing bad directory entries
and finding the actual number of references to each inode. */

pass4()
{
    depth = 0;
    linkmap[ROOTINODE] = 1;
    ckdir(ROOTINODE,ROOTINODE,"/");
    if (depth != 0)
	panic("Inconsistent depth");
}



/* This recursively checks the directories */
ckdir(inum, pnum, name)
uint16 inum;
uint16 pnum;
char *name;
{
    struct dinode ino;
    struct direct dentry;
    uint16 j;
    int c;
    int nentries;
    char ename[150];

    iread(inum, &ino);
    if ((ino.i_mode & F_MASK) != F_DIR)
	return;
    ++depth;

    if (ino.i_size.o_offset % 16 != 0)
    {
	printf("Directory inode %d has improper length. Fix?\n");
	if (yes())
	{
	    ino.i_size.o_offset &= (~0x0f);
	    ++ino.i_size.o_offset;
	    iwrite(inum, &ino);
	}
    }

    nentries = 8*ino.i_size.o_blkno + ino.i_size.o_offset/16;

    for (j=0; j < nentries; ++j)
    {
	dirread(&ino, j, &dentry);

	if (dentry.d_ino == 0)
	    continue;

	if (dentry.d_ino < ROOTINODE || dentry.d_ino >= 8*filsys.s_isize)
	{
	    printf("Directory entry %s%-1.14s has out-of-range inode %u. Zap?\n",
		   name, dentry.d_name, dentry.d_ino);
	    if (yes())
	    {
		dentry.d_ino = 0;
		dentry.d_name[0] = '\0';
		dirwrite(&ino, j, &dentry);
		continue;
	    }
	}

	if (dentry.d_ino && linkmap[dentry.d_ino] == -1)
	{
	    printf("Directory entry %s%-1.14s points to bogus inode %u. Zap?\n",
		   name, dentry.d_name, dentry.d_ino);
	    if (yes())
	    {
		dentry.d_ino = 0;
		dentry.d_name[0] = '\0';
		dirwrite(&ino, j, &dentry);
		continue;
	    }
	}

	++linkmap[dentry.d_ino];

	for (c=0; c<14 && dentry.d_name[c]; ++c)
	{
	    if (dentry.d_name[c] == '/')
	    {
		printf("Directory entry %s%-1.14s contains slash. Fix?\n",
		       name, dentry.d_name);
		if (yes())
		{
		    dentry.d_name[c] = 'X';
		    dirwrite(&ino, j, &dentry);
		}
	    }
	}

	if (strncmp(dentry.d_name,".",14) == 0 && dentry.d_ino != inum)
	{
	    printf("Dot entry %s%-1.14s points to wrong place. Fix?\n",
		   name, dentry.d_name);
	    if (yes())
	    {
		dentry.d_ino = inum;
		dirwrite(&ino, j, &dentry);
	    }
	}

	if (strncmp(dentry.d_name,"..",14) == 0 && dentry.d_ino != pnum)
	{
	    printf("DotDot entry %s%-1.14s points to wrong place. Fix?\n",
		   name, dentry.d_name);
	    if (yes())
	    {
		dentry.d_ino = pnum;
		dirwrite(&ino, j, &dentry);
	    }
	}

	if (dentry.d_ino != pnum && dentry.d_ino != inum && depth < MAXDEPTH) 
	{
	    strcpy(ename,name);
	    strcat(ename,dentry.d_name);
	    strcat(ename,"/");
	    ckdir(dentry.d_ino,inum,ename);
	}
    }
    --depth;
}


/* Pass 5 compares the link counts found in pass 4 with the inodes. */

pass5()
{
    uint16 n;
    struct dinode ino;

    for (n = ROOTINODE; n < 8*(filsys.s_isize-2); ++n)
    {
	iread(n, &ino);

	if (ino.i_mode == 0)
	{
	    if (linkmap[n] != -1)
		panic("Inconsistent linkmap");
	    continue;
	}

	if (linkmap[n] == -1 && ino.i_mode != 0)
	    panic("Inconsistent linkmap");

	if (linkmap[n] > 0 && ino.i_nlink != linkmap[n])
	{
	    printf("Inode %d has link count %d should be %d. Fix?\n",
		    n, ino.i_nlink, linkmap[n]);
	    if (yes())
	    {
		ino.i_nlink = linkmap[n];
		iwrite(n, &ino);
	    }
	}

	if (linkmap[n] == 0)
	{
	    if ((ino.i_mode&F_MASK) == F_BDEV ||
	        (ino.i_mode&F_MASK) == F_CDEV ||
	        (ino.i_size.o_blkno == 0 && ino.i_size.o_offset == 0))
	    {
		printf("Useless inode %d with mode 0%o has become detached. Link count is %d. Zap?\n",
			n, ino.i_mode, ino.i_nlink);
		if (yes())
		{
		    ino.i_nlink = 0;
		    ino.i_mode = 0;
		    iwrite(n, &ino);
		    ++filsys.s_tinode;
		    dwrite((blkno_t)1,(char *)&filsys);
		}
	    }
	    else
	    {
		printf("Inode %d has become detached. Link count is %d. Fix?\n",
			n, ino.i_nlink);
		if (yes())
		{
		    ino.i_nlink = 1;
		    iwrite(n, &ino);
		    mkentry(n);
		}
	    }
	}
    }
}


/* This makes an entry in "lost+found" for inode n */

mkentry(inum)
uint16 inum;
{
    struct dinode rootino;
    struct direct dentry;
    uint16 d;

    iread(ROOTINODE,&rootino);
    for (d=0; d < 32*rootino.i_size.o_blkno + rootino.i_size.o_offset/16; ++d)
    {
	dirread(&rootino,d,&dentry);
	if (dentry.d_ino == 0 && dentry.d_name[0] == '\0')
	{
	   dentry.d_ino = inum;
	   sprintf(dentry.d_name,"l+f%d",inum);
	   dirwrite(&rootino,d,&dentry);
	   return;
	}
    }
    printf("Sorry... No empty slots in root directory.\n");
}

/* Beginning of fsck1.c */

/* Getblkno gets a pointer index, and a number of a block in the file.
It returns the number of the block on the disk.  A value of zero
means an unallocated block. */

char *daread();

blkno_t 
getblkno(ino,num)
struct dinode *ino;
blkno_t num;
{

    blkno_t indb;
    blkno_t dindb;
    blkno_t *buf;

    if (num < 18) /* Direct block */
    {
	return(ino->i_addr[num]);
    }

    if (num < 256+18)  /* Single indirect */
    {
	indb = ino->i_addr[18];
	if (indb==0)
	    return(0);
	buf = (blkno_t *)daread(indb);
	return(buf[num - 18]);
    }

    /* Double indirect */
    indb = ino->i_addr[19];
    if(indb==0)
	return(0);

    buf = (blkno_t *)daread(indb);

    dindb = buf[ (num-(18+256)) >> 8];
    buf = (blkno_t *)daread(dindb);

    return(buf[ (num-(18+256)) & 0x00ff]);
}


/* Setblkno sets the given block number of the given file to the given
disk block number, possibly creating or modifiying the indirect blocks.
A return of zero means there were no blocks available to create an 
indirect block. This should never happen in fsck. */


setblkno(ino,num,dnum)
struct dinode *ino;
blkno_t num;
blkno_t dnum;
{

    blkno_t indb;
    blkno_t dindb;
    blkno_t *buf;
    char *zerobuf();


    if (num < 18) /* Direct block */
    {
	ino->i_addr[num] = dnum;
    }
    else if (num < 256+18)  /* Single indirect */
    {
	indb = ino->i_addr[18];
	if (indb==0)
	    panic("Missing indirect block");

	buf = (blkno_t *)daread(indb);
	buf[num - 18] = dnum;
	dwrite(indb,(char *)buf);
    }
    else /* Double indirect */
    {
	indb = ino->i_addr[19];
	if (indb == 0)
	    panic("Missing indirect block");

	buf = (blkno_t *)daread(indb);
	dindb = buf[ (num-(18+256)) >> 8];
	if (dindb==0)
	    panic("Missing indirect block");

	buf = (blkno_t *)daread(dindb);
	buf[ (num-(18+256)) & 0x00ff] = num;
	dwrite(indb,(char *)buf);
    }

}


/* Blk_alloc allocates an unused block.
A returned block number of zero means no more blocks. */

blkno_t
blk_alloc(filsys)
struct filesys *filsys;
{

    blkno_t newno;
    blkno_t *buf;
    int16 j;

    newno = filsys->s_free[--filsys->s_nfree];
    ifnot (newno)
    {
	++filsys->s_nfree;
	return(0);
    }

    /* See if we must refill the s_free array */

    ifnot (filsys->s_nfree)
    {
	buf = (blkno_t *)daread(newno);
	filsys->s_nfree = buf[0];
	for (j=0; j < 50; j++)
	{
	    filsys->s_free[j] = buf[j+1];
	}
    }

    --filsys->s_tfree;

    if (newno < filsys->s_isize || newno >= filsys->s_fsize)
    {
	printf("Free list is corrupt.  Did you rebuild it?\n");
	return(0);
    }

    dwrite((blkno_t)1,(char *)filsys);
    return(newno);
}



char *daread( blk )
uint16	blk;
{
	static char	bf[512];
	bufptr	buf;
	char	*bread();
	bufptr	bfind();
	if( buf=bfind( dev, blk ) )
	{
		bcopy( buf, bf, 512 );
		return( bf );
	}	
	if( !(buf=bread(dev,blk,0)) )
	{
		fprintf( stderr, "Read of block %d failed.\n", blk );
		abort();
	}
	bcopy( buf, bf, 512 );
	bfree( buf, 0 );
	return( bf );
}



int dwrite( blk, addr )
uint16	blk;
char	*addr;
{
	char	*bread();
	int	bfree();
	char	*buf = bread( dev, blk, 1 );
	bcopy( addr, buf, 512 );
	if( bfree( buf, 1 ) )
	{
		fprintf(stderr,"Write of block %d failed.\n",blk);
		abort();
	}
}



iread(ino, buf)
uint16 ino;
struct dinode *buf;
{
    struct dinode *addr;

    addr = (struct dinode *) daread((ino>>3) + 2);
    bcopy((char *)&addr[ino & 7], (char *)buf, sizeof(struct dinode));
}


iwrite(ino, buf)
uint16 ino;
struct dinode *buf;
{
    struct dinode *addr;

    addr = (struct dinode *) daread((ino>>3) + 2);
    bcopy((char *)buf, (char *)&addr[ino & 7], sizeof(struct dinode));
    dwrite((ino>>3) + 2,(char *)addr);
}


dirread(ino, j, dentry)
struct dinode *ino;
uint16 j;
struct direct *dentry;
{
    blkno_t blkno;
    char *buf;

    blkno = getblkno(ino,(blkno_t)j/32);
    if (blkno == 0)
	panic("Missing block in directory");
    buf = daread(blkno);
    bcopy(buf+(16*j), (char *)dentry, 16);
}


dirwrite(ino, j, dentry)
struct dinode *ino;
uint16 j;
struct direct *dentry;
{
    blkno_t blkno;
    char *buf;

    blkno = getblkno(ino,(blkno_t)j/32);
    if (blkno == 0)
	panic("Missing block in directory");
    buf = daread(blkno);
    bcopy((char *)dentry, buf+(16*j), 16);
    dwrite(blkno,buf);
}


yes()
{
    char line[20];

    if (!fgets(line,sizeof(line),stdin) || (*line != 'y' && *line != 'Y'))
	return(0);

    return(1);
}


