#
/*	v7-- a utility to read from an unmounted version 7	*/
/*		unix filesystem.				*/
/*		Mike Karels, Dept. of Molecular Biology		*/
/*		University of California, Berkeley		*/

#include <sys/param.h>
#include <sys/ino.h>
#include <sys/inode.h>
#include <sys/dir.h>
#include <sys/filsys.h>
#include <signal.h>
#include <stdio.h>

#ifndef BLKSIZE
#define	BLKSIZE	BSIZE
#endif
#define	MAXLINE 2000		/* maximum input line length */
char	linein[MAXLINE];
#define MAXARG	30	/* maximum size of input args */
int	ac;	/* number of arguments to subroutine */
char	*arg[MAXARG];	/* pointers to subroutine arguments */

#define MAXSIZE 500	/* max number of directory entries	*/

struct filsys sb;	/* superblock */
struct dinode dir_ino, fil_ino;	/* current dir, file inodes */
struct direct dir[MAXSIZE], cur_dir, root = { ROOTINO, "/" };
int	dir_sz, fd, fo, rflag;
ino_t	imax;
long	lseek(), atol();
char	*cmd[] = { "ls", "cd", "cp", "cat", "?", "", "q", "cpdir",
		"c7d", "printi", "printblk", "cpblk", "rootino" };
#define NCMD	13	/* number of commands in cmd[] */




main(argc,argv)
int argc;
char *argv[];
{
	int i, count, null();

	if (argc < 2) {
		fprintf(stderr,"Usage: v7 file, where file is block special\n");
		exit(1);
	}
	if (strcmp(argv[1],"-r") == 0) {
		rflag = 1;
		argv++;
	}
	if ((fd=open(argv[1],0)) < 0) {
		fprintf(stderr,"can't open %s\n",argv[1]);
		exit(1);
	}
	if (rflag)
		imax = 64000L;
	else {
		if ((lseek(fd,(long)BLKSIZE,0) < 0) ||
		   (read(fd,&sb,BLKSIZE) < BLKSIZE)) {
			fprintf(stderr,"can't read superblock\n");
			exit(1);
		   }
		imax = BLKSIZE / sizeof(struct dinode) * (sb.s_isize-1);
		if (readdir(&root) < 0) exit(1);
		cur_dir.d_ino = root.d_ino;
		strcpy(cur_dir.d_name,root.d_name);
	}
	signal(SIGINT,SIG_IGN);

	while(1) {
		printf("* ");
		if (getline(linein) == EOF) exit(0);
		if (linein[0] == '!') {
			signal(SIGINT,null);
			system(linein+1);
			signal(SIGINT,SIG_IGN);
			continue;
		}
		if (parse(linein) == 0)
			continue;
		for (i = 0; (i<NCMD) && (strcmp(arg[0],cmd[i])); i++)  ;
		switch(i) {
			case 0: ls(ac,arg);
				break;
			case 1: cd(arg[1]);
				break;
			case 2: cp(ac,arg);
				break;
			case 3: cp(ac,arg);  /* arg[0]="cat" */
				break;
			case 4: msg();
				break;
			case 5: break;
			case 6: exit(0);
			case 7: cpdir(arg[1]);
				break;
			case 8: if (chdir(arg[1]) < 0)
				perror("c7d");
				break;
			case 9: printi(ac,arg);
				break;
			case 10: printblk(ac,arg);
				break;
			case 11: cpblk(ac,arg);
				break;
			case 12: readroot((ino_t)atol(arg[1]));
				break;
			case NCMD: fprintf(stderr,"invalid command\n");
				break;
		}
	}
}

getline(linein)		/* reads input line into linein */
char linein[]; {
	int i, c;

	for (i=0; i<MAXLINE-1 && (c=getchar()) != '\n' && c != EOF;
		i++) linein[i] = c;
	if (c == '\n') {
		linein[i] = '\0';
		return(i);
	}
	else if (c == EOF) return(EOF);
	else {
		fprintf(stderr,"input line too long\n");
		fflush(stdin);
		linein[0] = '\0';
		return(0);
	}
}

parse(line)	/* parse into words, producing ac and *arg */
char *line;
{
	int inword;
	char *c;
	ac = 0;
	inword = 0;
	for (c = line; *c && ac<MAXARG; c++) {
		if (*c==' ' || *c=='\t') {
			if (inword)
				inword = 0;
			*c = '\0';
			continue;
		}
		else if (inword==0) {
			arg[ac++] = c;
			inword = 1;
		}
	}
	if (ac == MAXARG) {
		fprintf(stderr,"too many arguments\n");
		return(0);
	}
	arg[ac] = NULL;
	return(ac);
}

null() {}		/* trap here on interrupt during system() */

ls(ac,av)	/* list directory 'arg[1]', current dir if no name */
char **av; {	/* -i option gives inode numbers */
	int i, j, flag, iflag, col;
	struct direct save_dir;

	if ( ac>1 && strcmp(av[1],"-i") == 0) {
		ac--;
		av++;
		iflag = 1;
		col = 3;
	} else {
		iflag = 0;
		col = 4;
	}
	flag = 0;
	if (ac>1) {
		flag = 1;
		save_dir.d_ino = cur_dir.d_ino;
		strcpy(save_dir.d_name,cur_dir.d_name);
		if (cd(av[1]) < 0) return;
	}
	j=0;
	for (i=0; i<dir_sz; i++)
		if (dir[i].d_ino != 0 ) 
		   if (iflag)
			printf("%6d %-15.14s%c", dir[i].d_ino, dir[i].d_name,
				++j%col ? ' ':'\n');
		   else
			printf("%-15.14s%c", dir[i].d_name,
				++j%col ? ' ':'\n');
	if (j%col) putchar('\n');
	if (flag) {
		readdir(&save_dir);
		cur_dir.d_ino = save_dir.d_ino;
		strcpy(cur_dir.d_name,save_dir.d_name);
	}
}
cpdir(arg)	/* copy contents of current directory */
char *arg; {
	int i;

	if (arg[0] != '\0') {
		fprintf(stderr,"no arguments allowed for cpdir\n");
		return(-1);
	}
	for (i=0; i<dir_sz; i++)
		if ((dir[i].d_ino != 0) && (dir[i].d_name[0] != '.')) 
			cp(dir[i].d_name,dir[i].d_name,1);
	return(0);
}

cd(name)		/* returns 0 if successful, else -1 */
char *name; {	/* returns to previous directory if unsuccesssful */
	int i, ino;
	char *c, *rindex();
	struct direct *pdir, *find(), save_dir;

	if ((i=iindex(name,'/')) >= 0) {
		if (i==0) {
			readdir(&root);
			name++;
		}
		if (name[0]=='\0') {
			cur_dir.d_ino = root.d_ino;
			strcpy(cur_dir.d_name,root.d_name);
			return(0);
		}
		if (*((c=rindex(name,'/'))+1) == '\0') *c = '\0';
			/* removes trailing '/' if present */
		while ((i=iindex(name,'/')) != -1) {
			name[i] = '\0';
			if ((pdir=find(name)) == NULL) {
				fprintf(stderr,"can't find %s\n",name);
				readdir(&cur_dir);
				return(-1);
			}
			if (readdir(pdir) < 0) return(-1);
			name += i+1;
		}
	}
	if ((pdir=find(name))==NULL) {
		fprintf(stderr,"can't find %s\n",name);
		readdir(&cur_dir);
		return(-1);
	}
	ino = pdir->d_ino;
	if (readdir(pdir) >= 0) {
		cur_dir.d_ino = ino;
		strcpy(cur_dir.d_name,name);
		return(0);
	}
	else return(-1);
}

iindex(s,c)
char *s, c; {
	int i;

	for (i=0; ; i++) {
		if (s[i] == c) return(i);
		if (s[i] == NULL) return(-1);
	}
}

struct direct *find(name)	/* returns pointer to "name" entry */
char *name; {			/* in dir[], NULL if not found */
	int i;

	for (i=0; i<dir_sz; i++)
		if ((strcmp(dir[i].d_name,name) == 0) &&
			(dir[i].d_ino != 0))
			break;
	if (i==dir_sz) return(NULL);
	return(&(dir[i]));
}

#define MODE 0644
#define STDOUT 1
char	buf[BLKSIZE];
off_t	size;

daddr_t
ctodaddr(addr)		/* convert addr[3] to daddr_t */
char *addr;
{
	daddr_t blk;
	blk = ((daddr_t)(*addr & 0377) << 16) +
		(daddr_t)(*(addr+1) & 0377) +
		((daddr_t)(*(addr+2) & 0377) << 8);
	return(blk);
}

int flush();

cp(ac,arg)	/* copies ifile to ofile if arg[0]=="cp" */
char **arg;	/* cats ifile if arg[0]=="cat" */
{

	int n, i, blk, flag, mode;
	ino_t ino;
	char *tname, ofile;
	struct direct *pdir, save_dir;

	flag = 0;
	if ((ino = (ino_t)atoi(arg[1])) > 0) {
		if (readi(ino,&fil_ino) < 0) return;
	} else {
		if (iindex(arg[1],'/') != -1) {
			flag = 1;
			save_dir.d_ino = cur_dir.d_ino;
			strcpy(save_dir.d_name,cur_dir.d_name);
			tname = rindex(arg[1],'/');
			if (tname == arg[1])	/* file in root directory */
				arg[1] = root.d_name;
			*tname = '\0';
			if (cd(arg[1]) < 0) return;
			arg[1] = tname + 1;
		}
		if ((pdir = find(arg[1])) == NULL) {
			fprintf(stderr,"can't find %s\n",arg[1]);
			goto quit;
		}
		if (readi(pdir->d_ino,&fil_ino) < 0) goto quit;
	}
	if (strcmp(arg[0],"cp") == 0)
		mode = 1;
	else mode = 0;
	if (ac<3 &&  mode == 1)
		ofile = arg[1];
	else ofile = arg[2];
	if ((mode == 1) && ((fo=open(ofile,0) != -1))) {
		fprintf(stderr,"%s already exists\n",ofile);
		goto quit;
	}
	if (mode == 1)  {
		if ((fo= creat(ofile,MODE)) < 0) {
			fprintf(stderr,"can't create %s\n",ofile);
			goto quit;
		}
	} else {
		signal(SIGINT,flush);
		fo = dup(STDOUT);
	}
	if (!(fil_ino.di_mode & IFREG)) {
		/* is special file or directory */
		fprintf(stderr,"cp: %s not a regular file\n",arg[1]);
		goto quit;
	}
	size = fil_ino.di_size;
	for (blk = 0; size > 0L && blk < NADDR-3; blk++, size -= BLKSIZE) {
		n = readblk(ctodaddr(&fil_ino.di_addr[3*blk]),buf);
		if (n > 0)
			if (write(fo,buf,(int)size>n?n:(int)size)
				< 0) {
				if (mode == 1)
					fprintf(stderr,"write error\n");
				return;
			}
	}
	if (size > 0L)
		rindir(ctodaddr(&fil_ino.di_addr[3*(NADDR-3)]),mode);
	if (size > 0L)
		riindir(ctodaddr(&fil_ino.di_addr[3*(NADDR-2)]));
	if (size > 0L)
		riiindir(ctodaddr(&fil_ino.di_addr[3*(NADDR-1)]));
quit:	if (fo != STDOUT) close(fo);
	signal(SIGINT,SIG_IGN);
	if (flag) {
		readdir(&save_dir);
		cur_dir.d_ino = save_dir.d_ino;
		strcpy(cur_dir.d_name,save_dir.d_name);
	}
}


#define DAPB	BLKSIZE/sizeof(daddr_t)

rindir(blk,mode)	/* read indirect block and blocks it specifies */
daddr_t blk;		/* mode as in cp()	*/
{
	int i, count;
	union {
		char	c[BLKSIZE];
		daddr_t	blk[DAPB];
	} indir;

	if (readblk(blk,&indir) < 0) {
		size = 0;
		return;
	}
	for (i=0; size > 0L && i < DAPB; i++, size -= BLKSIZE) {
		count = readblk(indir.blk[i],buf);
		if (count > 0)
			if (write(fo,buf,((int)size>count)?count:(int)size)
				< 0) {
				if (mode == 1)
					fprintf(stderr,"write error\n");
				return;
			}
	}
}

riindir(blk)		/* read double-indirect blocks */
daddr_t blk;
{
	int i;
	union {
		char	c[BLKSIZE];
		daddr_t	blk[DAPB];
	} iindir;

	if (readblk(blk,&iindir) < 0) {
		size = 0;
		return;
	}
	for (i=0; size > 0L && i < DAPB; i++, size -= BLKSIZE)
		rindir(iindir.blk[i]);
}

riiindir(blk)		/* read triple-indirect blocks */
daddr_t blk;
{
	int i;
	union {
		char	c[BLKSIZE];
		daddr_t	blk[DAPB];
	} iiindir;

	if (readblk(blk,&iiindir) < 0) {
		size = 0;
		return;
	}
	for (i=0; size > 0L && i < DAPB; i++, size -= BLKSIZE)
		riindir(iiindir.blk[i]);
}

#define	DIRBLK	BLKSIZE/sizeof(struct direct)

readdir(pdir)		/* reads pdir->d_inode into dir_ino, then */
struct direct *pdir;	/* reads corresponding directory into dir */
{			/* reads cur_dir on error, then returns -1 */
	int blk, i, n;
	daddr_t indir[DAPB];
	off_t sz;

	if (pdir->d_ino == 0) return(-1);
	if (readi(pdir->d_ino,&dir_ino) < 0) return(-1);
	if (!(dir_ino.di_mode & IFDIR)) {	/* not a directory */
		fprintf(stderr,"%s not a directory\n",pdir->d_name);
		readdir(&cur_dir);
		return(-1);
	}
	size = dir_ino.di_size;
	sz = 0L;
	for (blk=0; size>0L && blk<10; blk++) {
		if (readblk(ctodaddr(&dir_ino.di_addr[3*blk]),&dir[sz])
			< BLKSIZE) return(-1);
		sz += (size<BLKSIZE ? size : BLKSIZE)
			/sizeof(struct direct);
		size -= BLKSIZE;
	}
	if (size > 0L) {
		if (readblk(ctodaddr(&dir_ino.di_addr[3*10]),indir)
			< BLKSIZE) return(-1);
		for (blk=0; size>0L && sz<MAXSIZE; blk++) {
			if (readblk(indir[blk],&dir[sz])
				< BLKSIZE) return(-1);
			sz += (size<BLKSIZE ? size : BLKSIZE)
				/sizeof(struct direct);
			size -= BLKSIZE;
		}
	}
	if (size > 0L) {
		fprintf(stderr,"directory has more than %d entries\n",
			MAXSIZE);
		return(-1);
	}
	dir_sz = sz;
	return(dir_sz);
}

readi(inum,pinode)	/* reads inode inum into *pinode */
ino_t inum;
struct dinode *pinode; {

	if (inum < 1 || inum > imax) {
		fprintf(stderr,"bad inode number, %u\n",inum);
		return(-1);
	}
	if ((lseek(fd,((long)2*BLKSIZE +
		(long)(inum-1)*(sizeof(struct dinode))),0) < 0) ||
	(read(fd,pinode,sizeof(struct dinode)) < 0)) {
		fprintf(stderr,"can't read inode %u\n",inum);
		return(-1);
	}
	return(0);
}

readblk(blk,buff)	/* reads block blk into buff */
daddr_t blk;
char *buff; {
	int n;

	if (blk == 0) {
		for (n=0; n<BLKSIZE; n++)
			buff[n] = '\0';
		return(BLKSIZE);
	}
	if (!rflag && ((blk < sb.s_isize-1) || (blk >= sb.s_fsize))) {
		fprintf(stderr,"bad block number, %ld\n",blk);
		return(-1);
	}
	if (lseek(fd,(long)(BLKSIZE * blk),0) < 0) {
		fprintf(stderr,"seek error, block %ld\n",blk);
		return(-1);
	}
	if ((n=read(fd,buff,BLKSIZE)) != BLKSIZE)
		fprintf(stderr,"read error, block %ld\n",blk);
	return(n);
}

flush() {		/* closes fo to terminate a cat() */
	close(fo);
	return;
}

msg() {
	printf("commands:\n");
	printf("\t ls [-i] [dir]: list directory contents, current dir default\n");
	printf("\t cd name: change to directory 'name'\n");
	printf("\t cat name1: print file 'name1' on terminal\n");
	printf("\t cp name1 [name2]: copy internal file 'name1' to external 'name2'\n");
	printf("\t\tname2 defaults to name1.\n");
	printf("\t\t(An i-number can be used instead of name1 for cp or cat.)\n");
	printf("\t cpdir: copy all files in current internal directory\n");
	printf("\t\tto current external directory\n");
	printf("\t printi ino ...: print contents of inode 'ino'\n");
	printf("\t printblk blk ...: print contents of block 'blk'\n");
	printf("\t cpblk file blk ...: copy contents of 'blk' to external ");
	printf("file 'file'\n\t\t(append to file if it exists)\n");
	printf("\t rootino ino: read directory with inode 'ino', making it");
	printf("\n\t\tthe root directory\n");
	printf("\t ! : shell escape; the rest of the line is passed to the shell\n");
	printf("\t q or ^d: quit\n");
}

char *ctime();

printi(ac,av)
char **av;
{
	int i;
	ino_t inum;
	while (--ac) {
		inum= (ino_t)atol(*++av);
		printf("\nInode %u\n",inum);
		if (readi(inum,&fil_ino) < 0) return;
		printf("flags: %o\tnlinks: %d\tuid: %d\tgid: %d\n",
			fil_ino.di_mode,fil_ino.di_nlinks,fil_ino.di_uid,
			fil_ino.di_gid);
		printf("size: %ld\n",fil_ino.di_size);
		printf("blocks:");
		for (i=0; i<13; i++)
			printf(" %ld",ctodaddr(&fil_ino.di_addr[3*i]));
		putchar('\n');
		printf("access time: %s",ctime(&fil_ino.di_atime));
		printf("last modified: %s",ctime(&fil_ino.di_mtime));
		printf("time created: %s",ctime(&fil_ino.di_ctime));
	}
}

printblk(ac,av)
char **av;
{
	int count;
	daddr_t blk;
	while (--ac) {
		blk = (daddr_t) atol(*++av);
		if ((count = readblk(blk,buf)) > 0)
			write(STDOUT,buf,count);
	}
}

cpblk(ac,av)
char **av;
{
	int count;
	daddr_t blk;
	if (ac<3) {
		printf("usage: cpblk file blk ...\n");
		return;
	}
	if ((fo = open(av[1],1)) < 0)
		if ((fo = creat(av[1],0644)) < 0) {
			fprintf(stderr,"can't create %s\n",av[1]);
			return;
		}
	lseek(fo,0L,2);
	--ac;
	av++;
	while (--ac) {
		blk = atol(*++av);
		if ((count = readblk(blk,buf)) < 0)
			return;
		write(fo,buf,count);
	}
	close(fo);
}

readroot(ino)	/* make root directory that specified by ino */
ino_t ino;
{
	root.d_ino = ino;
	readdir(&root);
}
