/*
 * Copyright 2015 Gary Mills
 * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Computer Consoles Inc.
 *
 * 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.
 */

/*
 *  fsdb - file system debugger
 *
 *  usage: fsdb [-o suboptions] special
 *  options/suboptions:
 *	-o
 *		?		display usage
 *		o		override some error conditions
 *		p="string"	set prompt to string
 *		w		open for write
 */

#include <sys/param.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <inttypes.h>
#include <sys/sysmacros.h>

#ifdef sun
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/vnode.h>
#include <sys/mntent.h>
#include <sys/wait.h>
#include <sys/fs/ufs_fsdir.h>
#include <sys/fs/ufs_fs.h>
#include <sys/fs/ufs_inode.h>
#include <sys/fs/ufs_acl.h>
#include <sys/fs/ufs_log.h>
#else
#include <sys/dir.h>
#include <ufs/fs.h>
#include <ufs/dinode.h>
#include <paths.h>
#endif /* sun */

#include <stdio.h>
#include <setjmp.h>

#define	OLD_FSDB_COMPATIBILITY	/* To support the obsoleted "-z" option */

#ifndef _PATH_BSHELL
#define	_PATH_BSHELL	"/bin/sh"
#endif /* _PATH_BSHELL */
/*
 * Defines from the 4.3-tahoe file system, for systems with the 4.2 or 4.3
 * file system.
 */
#ifndef FS_42POSTBLFMT
#define	cg_blktot(cgp) (((cgp))->cg_btot)
#define	cg_blks(fs, cgp, cylno) (((cgp))->cg_b[cylno])
#define	cg_inosused(cgp) (((cgp))->cg_iused)
#define	cg_blksfree(cgp) (((cgp))->cg_free)
#define	cg_chkmagic(cgp) ((cgp)->cg_magic == CG_MAGIC)
#endif

/*
 * Never changing defines.
 */
#define	OCTAL		8		/* octal base */
#define	DECIMAL		10		/* decimal base */
#define	HEX		16		/* hexadecimal base */

/*
 * Adjustable defines.
 */
#define	NBUF		10		/* number of cache buffers */
#define	PROMPTSIZE	80		/* size of user definable prompt */
#define	MAXFILES	40000		/* max number of files ls can handle */
#define	FIRST_DEPTH	10		/* default depth for find and ls */
#define	SECOND_DEPTH	100		/* second try at depth (maximum) */
#define	INPUTBUFFER	1040		/* size of input buffer */
#define	BYTESPERLINE	16		/* bytes per line of /dxo output */
#define	NREG		36		/* number of save registers */

#define	DEVPREFIX	"/dev/"		/* Uninteresting part of "special" */

#if defined(OLD_FSDB_COMPATIBILITY)
#define	FSDB_OPTIONS	"o:wp:z:"
#else
#define	FSDB_OPTIONS	"o:wp:"
#endif /* OLD_FSDB_COMPATIBILITY */


/*
 * Values dependent on sizes of structs and such.
 */
#define	NUMB		3			/* these three are arbitrary, */
#define	BLOCK		5			/* but must be different from */
#define	FRAGMENT	7			/* the rest (hence odd). */
#define	BITSPERCHAR	8			/* couldn't find it anywhere  */
#define	CHAR		(sizeof (char))
#define	SHORT		(sizeof (short))
#define	LONG		(sizeof (long))
#define	U_OFFSET_T	(sizeof (u_offset_t))	/* essentially "long long" */
#define	INODE		(sizeof (struct dinode))
#define	DIRECTORY	(sizeof (struct direct))
#define	CGRP		(sizeof (struct cg))
#define	SB		(sizeof (struct fs))
#define	BLKSIZE		(fs->fs_bsize)		/* for clarity */
#define	FRGSIZE		(fs->fs_fsize)
#define	BLKSHIFT	(fs->fs_bshift)
#define	FRGSHIFT	(fs->fs_fshift)
#define	SHADOW_DATA	(sizeof (struct ufs_fsd))

/*
 * Messy macros that would otherwise clutter up such glamorous code.
 */
#define	itob(i)		(((u_offset_t)itod(fs, (i)) << \
	(u_offset_t)FRGSHIFT) + (u_offset_t)itoo(fs, (i)) * (u_offset_t)INODE)
#define	min(x, y)	((x) < (y) ? (x) : (y))
#define	STRINGSIZE(d)	((long)d->d_reclen - \
				((long)&d->d_name[0] - (long)&d->d_ino))
#define	letter(c)	((((c) >= 'a')&&((c) <= 'z')) ||\
				(((c) >= 'A')&&((c) <= 'Z')))
#define	digit(c)	(((c) >= '0') && ((c) <= '9'))
#define	HEXLETTER(c)	(((c) >= 'A') && ((c) <= 'F'))
#define	hexletter(c)	(((c) >= 'a') && ((c) <= 'f'))
#define	octaldigit(c)	(((c) >= '0') && ((c) <= '7'))
#define	uppertolower(c)	((c) - 'A' + 'a')
#define	hextodigit(c)	((c) - 'a' + 10)
#define	numtodigit(c)	((c) - '0')

#if !defined(loword)
#define	loword(X)	(((ushort_t *)&X)[1])
#endif /* loword */

#if !defined(lobyte)
#define	lobyte(X)	(((unsigned char *)&X)[1])
#endif /* lobyte */

/*
 * buffer cache structure.
 */
static struct lbuf {
	struct	lbuf  *fwd;
	struct	lbuf  *back;
	char	*blkaddr;
	short	valid;
	u_offset_t	blkno;
} lbuf[NBUF], bhdr;

/*
 * used to hold save registers (see '<' and '>').
 */
struct	save_registers {
	u_offset_t	sv_addr;
	u_offset_t	sv_value;
	long		sv_objsz;
} regs[NREG];

/*
 * cd, find, and ls use this to hold filenames.  Each filename is broken
 * up by a slash.  In other words, /usr/src/adm would have a len field
 * of 2 (starting from 0), and filenames->fname[0-2] would hold usr,
 * src, and adm components of the pathname.
 */
static struct filenames {
	ino_t	ino;		/* inode */
	long	len;		/* number of components */
	char	flag;		/* flag if using SECOND_DEPTH allocator */
	char	find;		/* flag if found by find */
	char	**fname;	/* hold components of pathname */
} *filenames, *top;

enum log_enum { LOG_NDELTAS, LOG_ALLDELTAS, LOG_CHECKSCAN };
#ifdef sun
struct fs	*fs;
static union {
	struct fs	un_filesystem;
	char		un_sbsize[SBSIZE];
} fs_un;
#define	filesystem	fs_un.un_filesystem
#else
struct fs filesystem, *fs;	/* super block */
#endif /* sun */

/*
 * Global data.
 */
static char		*input_path[MAXPATHLEN];
static char		*stack_path[MAXPATHLEN];
static char		*current_path[MAXPATHLEN];
static char		input_buffer[INPUTBUFFER];
static char		*prompt;
static char		*buffers;
static char		scratch[64];
static char		BASE[] = "o u     x";
static char		PROMPT[PROMPTSIZE];
static char		laststyle = '/';
static char		lastpo = 'x';
static short		input_pointer;
static short		current_pathp;
static short		stack_pathp;
static short		input_pathp;
static short		cmp_level;
static int		nfiles;
static short		type = NUMB;
static short		dirslot;
static short		fd;
static short		c_count;
static short		error;
static short		paren;
static short		trapped;
static short		doing_cd;
static short		doing_find;
static short		find_by_name;
static short		find_by_inode;
static short		long_list;
static short		recursive;
static short		objsz = SHORT;
static short		override = 0;
static short		wrtflag = O_RDONLY;
static short		base = HEX;
static short		acting_on_inode;
static short		acting_on_directory;
static short		should_print = 1;
static short		clear;
static short		star;
static u_offset_t	addr;
static u_offset_t	bod_addr;
static u_offset_t	value;
static u_offset_t	erraddr;
static long		errcur_bytes;
static u_offset_t	errino;
static long		errinum;
static long		cur_cgrp;
static u_offset_t	cur_ino;
static long		cur_inum;
static u_offset_t	cur_dir;
static long		cur_block;
static long		cur_bytes;
static long		find_ino;
static u_offset_t	filesize;
static u_offset_t	blocksize;
static long		stringsize;
static long		count = 1;
static long		commands;
static long		read_requests;
static long		actual_disk_reads;
static jmp_buf		env;
static long		maxfiles;
static long		cur_shad;

#ifndef sun
extern char	*malloc(), *calloc();
#endif
static char		getachar();
static char		*getblk(), *fmtentry();

static offset_t		get(short);
static long		bmap();
static long		expr();
static long		term();
static long		getnumb();
static u_offset_t	getdirslot();
static unsigned long	*print_check(unsigned long *, long *, short, int);

static void		usage(char *);
static void		ungetachar(char);
static void		getnextinput();
static void		eat_spaces();
static void		restore_inode(ino_t);
static void		find();
static void		ls(struct filenames *, struct filenames *, short);
static void		formatf(struct filenames *, struct filenames *);
static void		parse();
static void		follow_path(long, long);
static void		getname();
static void		freemem(struct filenames *, int);
static void		print_path(char **, int);
static void		fill();
static void		put(u_offset_t, short);
static void		insert(struct lbuf *);
static void		puta();
static void		fprnt(char, char);
static void		index();
#ifdef _LARGEFILE64_SOURCE
static void		printll
	(u_offset_t value, int fieldsz, int digits, int lead);
#define	print(value, fieldsz, digits, lead) \
	printll((u_offset_t)value, fieldsz, digits, lead)
#else /* !_LARGEFILE64_SOURCE */
static void		print(long value, int fieldsz, int digits, int lead);
#endif /* _LARGEFILE64_SOURCE */
static void		printsb(struct fs *);
static void		printcg(struct cg *);
static void		pbits(unsigned char *, int);
static void		old_fsdb(int, char *) __NORETURN;	/* For old fsdb functionality */

static int		isnumber(char *);
static int		icheck(u_offset_t);
static int		cgrp_check(long);
static int		valid_addr();
static int		match(char *, int);
static int		devcheck(short);
static int		bcomp();
static int		compare(char *, char *, short);
static int		check_addr(short, short *, short *, short);
static int		fcmp();
static int		ffcmp();

static int		getshadowslot(long);
static void		getshadowdata(long *, int);
static void		syncshadowscan(int);
static void		log_display_header(void);
static void		log_show(enum log_enum);

#ifdef sun
static void		err();
#else
static int		err();
#endif /* sun */

/* Suboption vector */
static char *subopt_v[] = {
#define	OVERRIDE	0
	"o",
#define	NEW_PROMPT	1
	"p",
#define	WRITE_ENABLED	2
	"w",
#define	ALT_PROMPT	3
	"prompt",
	NULL
};

/*
 * main - lines are read up to the unprotected ('\') newline and
 *	held in an input buffer.  Characters may be read from the
 *	input buffer using getachar() and unread using ungetachar().
 *	Reading the whole line ahead allows the use of debuggers
 *	which would otherwise be impossible since the debugger
 *	and fsdb could not share stdin.
 */

int
main(int argc, char *argv[])
{

	char		c, *cptr;
	short		i;
	struct direct	*dirp;
	struct lbuf	*bp;
	char		*progname;
	volatile short	colon;
	short		mode;
	long		temp;

	/* Options/Suboptions processing */
	int	opt;
	char	*subopts;
	char	*optval;

	/*
	 * The following are used to support the old fsdb functionality
	 * of clearing an inode. It's better to use 'clri'.
	 */
	int			inum;	/* Inode number to clear */
	char			*special;

	setbuf(stdin, NULL);
	progname = argv[0];
	prompt = &PROMPT[0];
	/*
	 * Parse options.
	 */
	while ((opt = getopt(argc, argv, FSDB_OPTIONS)) != EOF) {
		switch (opt) {
#if defined(OLD_FSDB_COMPATIBILITY)
		case 'z':	/* Hack - Better to use clri */
			(void) fprintf(stderr, "%s\n%s\n%s\n%s\n",
"Warning: The '-z' option of 'fsdb_ufs' has been declared obsolete",
"and may not be supported in a future version of Solaris.",
"While this functionality is currently still supported, the",
"recommended procedure to clear an inode is to use clri(8).");
			if (isnumber(optarg)) {
				inum = atoi(optarg);
				special = argv[optind];
				/* Doesn't return */
				old_fsdb(inum, special);
			} else {
				usage(progname);
				exit(31+1);
			}
			/* Should exit() before here */
			/*NOTREACHED*/
#endif /* OLD_FSDB_COMPATIBILITY */
		case 'o':
			/* UFS Specific Options */
			subopts = optarg;
			while (*subopts != '\0') {
				switch (getsubopt(&subopts, subopt_v,
								&optval)) {
				case OVERRIDE:
					printf("error checking off\n");
					override = 1;
					break;

				/*
				 * Change the "-o prompt=foo" option to
				 * "-o p=foo" to match documentation.
				 * ALT_PROMPT continues support for the
				 * undocumented "-o prompt=foo" option so
				 * that we don't break anyone.
				 */
				case NEW_PROMPT:
				case ALT_PROMPT:
					if (optval == NULL) {
						(void) fprintf(stderr,
							"No prompt string\n");
						usage(progname);
					}
					(void) strncpy(PROMPT, optval,
								PROMPTSIZE);
					break;

				case WRITE_ENABLED:
					/* suitable for open */
					wrtflag = O_RDWR;
					break;

				default:
					usage(progname);
					/* Should exit here */
				}
			}
			break;

		default:
			usage(progname);
		}
	}

	if ((argc - optind) != 1) {	/* Should just have "special" left */
		usage(progname);
	}
	special = argv[optind];

	/*
	 * Unless it's already been set, the default prompt includes the
	 * name of the special device.
	 */
	if (*prompt == '\0')
		(void) sprintf(prompt, "%s > ", special);

	/*
	 * Attempt to open the special file.
	 */
	if ((fd = open(special, wrtflag)) < 0) {
		perror(special);
		exit(1);
	}
	/*
	 * Read in the super block and validate (not too picky).
	 */
	if (llseek(fd, (offset_t)(SBLOCK * DEV_BSIZE), 0) == -1) {
		perror(special);
		exit(1);
	}

#ifdef sun
	if (read(fd, &filesystem, SBSIZE) != SBSIZE) {
		printf("%s: cannot read superblock\n", special);
		exit(1);
	}
#else
	if (read(fd, &filesystem, sizeof (filesystem)) != sizeof (filesystem)) {
		printf("%s: cannot read superblock\n", special);
		exit(1);
	}
#endif /* sun */

	fs = &filesystem;
	if ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC)) {
		if (!override) {
			printf("%s: Bad magic number in file system\n",
								special);
			exit(1);
		}

		printf("WARNING: Bad magic number in file system. ");
		printf("Continue? (y/n): ");
		(void) fflush(stdout);
		if (gets(input_buffer) == NULL) {
			exit(1);
		}

		if (*input_buffer != 'y' && *input_buffer != 'Y') {
			exit(1);
		}
	}

	if ((fs->fs_magic == FS_MAGIC &&
	    (fs->fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 &&
	    fs->fs_version != UFS_VERSION_MIN)) ||
	    (fs->fs_magic == MTB_UFS_MAGIC &&
	    (fs->fs_version > MTB_UFS_VERSION_1 ||
	    fs->fs_version < MTB_UFS_VERSION_MIN))) {
		if (!override) {
			printf("%s: Unrecognized UFS version number: %d\n",
			    special, fs->fs_version);
			exit(1);
		}

		printf("WARNING: Unrecognized UFS version number. ");
		printf("Continue? (y/n): ");
		(void) fflush(stdout);
		if (gets(input_buffer) == NULL) {
			exit(1);
		}

		if (*input_buffer != 'y' && *input_buffer != 'Y') {
			exit(1);
		}
	}
#ifdef FS_42POSTBLFMT
	if (fs->fs_postblformat == FS_42POSTBLFMT)
		fs->fs_nrpos = 8;
#endif
	printf("fsdb of %s %s -- last mounted on %s\n",
		special,
		(wrtflag == O_RDWR) ? "(Opened for write)" : "(Read only)",
		&fs->fs_fsmnt[0]);
#ifdef sun
	printf("fs_clean is currently set to ");
	switch (fs->fs_clean) {

	case FSACTIVE:
		printf("FSACTIVE\n");
		break;
	case FSCLEAN:
		printf("FSCLEAN\n");
		break;
	case FSSTABLE:
		printf("FSSTABLE\n");
		break;
	case FSBAD:
		printf("FSBAD\n");
		break;
	case FSSUSPEND:
		printf("FSSUSPEND\n");
		break;
	case FSLOG:
		printf("FSLOG\n");
		break;
	case FSFIX:
		printf("FSFIX\n");
		if (!override) {
			printf("%s: fsck may be running on this file system\n",
								special);
			exit(1);
		}

		printf("WARNING: fsck may be running on this file system. ");
		printf("Continue? (y/n): ");
		(void) fflush(stdout);
		if (gets(input_buffer) == NULL) {
			exit(1);
		}

		if (*input_buffer != 'y' && *input_buffer != 'Y') {
			exit(1);
		}
		break;
	default:
		printf("an unknown value (0x%x)\n", fs->fs_clean);
		break;
	}

	if (fs->fs_state == (FSOKAY - fs->fs_time)) {
		printf("fs_state consistent (fs_clean CAN be trusted)\n");
	} else {
		printf("fs_state inconsistent (fs_clean CAN'T trusted)\n");
	}
#endif /* sun */
	/*
	 * Malloc buffers and set up cache.
	 */
	buffers = malloc(NBUF * BLKSIZE);
	bhdr.fwd = bhdr.back = &bhdr;
	for (i = 0; i < NBUF; i++) {
		bp = &lbuf[i];
		bp->blkaddr = buffers + (i * BLKSIZE);
		bp->valid = 0;
		insert(bp);
	}
	/*
	 * Malloc filenames structure.  The space for the actual filenames
	 * is allocated as it needs it. We estimate the size based on the
	 * number of inodes(objects) in the filesystem and the number of
	 * directories.  The number of directories are padded by 3 because
	 * each directory traversed during a "find" or "ls -R" needs 3
	 * entries.
	 */
	maxfiles = (long)((((u_offset_t)fs->fs_ncg * (u_offset_t)fs->fs_ipg) -
	    (u_offset_t)fs->fs_cstotal.cs_nifree) +
	    ((u_offset_t)fs->fs_cstotal.cs_ndir * (u_offset_t)3));

	filenames = (struct filenames *)calloc(maxfiles,
	    sizeof (struct filenames));
	if (filenames == NULL) {
		/*
		 * If we could not allocate memory for all of files
		 * in the filesystem then, back off to the old fixed
		 * value.
		 */
		maxfiles = MAXFILES;
		filenames = (struct filenames *)calloc(maxfiles,
		    sizeof (struct filenames));
		if (filenames == NULL) {
			printf("out of memory\n");
			exit(1);
		}
	}

	restore_inode(2);
	/*
	 * Malloc a few filenames (needed by pwd for example).
	 */
	for (i = 0; i < MAXPATHLEN; i++) {
		input_path[i] = calloc(1, MAXNAMLEN);
		stack_path[i] = calloc(1, MAXNAMLEN);
		current_path[i] = calloc(1, MAXNAMLEN);
		if (current_path[i] == NULL) {
			printf("out of memory\n");
			exit(1);
		}
	}
	current_pathp = -1;

	(void) signal(2, err);
	(void) setjmp(env);

	getnextinput();
	/*
	 * Main loop and case statement.  If an error condition occurs
	 * initialization and recovery is attempted.
	 */
	for (;;) {
		if (error) {
			freemem(filenames, nfiles);
			nfiles = 0;
			c_count = 0;
			count = 1;
			star = 0;
			error = 0;
			paren = 0;
			acting_on_inode = 0;
			acting_on_directory = 0;
			should_print = 1;
			addr = erraddr;
			cur_ino = errino;
			cur_inum = errinum;
			cur_bytes = errcur_bytes;
			printf("?\n");
			getnextinput();
			if (error)
				continue;
		}
		c_count++;

		switch (c = getachar()) {

		case '\n': /* command end */
			freemem(filenames, nfiles);
			nfiles = 0;
			if (should_print && laststyle == '=') {
				ungetachar(c);
				goto calc;
			}
			if (c_count == 1) {
				clear = 0;
				should_print = 1;
				erraddr = addr;
				errino = cur_ino;
				errinum = cur_inum;
				errcur_bytes = cur_bytes;
				switch (objsz) {
				case DIRECTORY:
					if ((addr = getdirslot(
							(long)dirslot+1)) == 0)
						should_print = 0;
					if (error) {
						ungetachar(c);
						continue;
					}
					break;
				case INODE:
					cur_inum++;
					addr = itob(cur_inum);
					if (!icheck(addr)) {
						cur_inum--;
						should_print = 0;
					}
					break;
				case CGRP:
				case SB:
					cur_cgrp++;
					addr = cgrp_check(cur_cgrp);
					if (addr == 0) {
						cur_cgrp--;
						continue;
					}
					break;
				case SHADOW_DATA:
					if ((addr = getshadowslot(
					    (long)cur_shad + 1)) == 0)
						should_print = 0;
					if (error) {
						ungetachar(c);
						continue;
					}
					break;
				default:
					addr += objsz;
					cur_bytes += objsz;
					if (valid_addr() == 0)
						continue;
				}
			}
			if (type == NUMB)
				trapped = 0;
			if (should_print)
				switch (objsz) {
				case DIRECTORY:
					fprnt('?', 'd');
					break;
				case INODE:
					fprnt('?', 'i');
					if (!error)
						cur_ino = addr;
					break;
				case CGRP:
					fprnt('?', 'c');
					break;
				case SB:
					fprnt('?', 's');
					break;
				case SHADOW_DATA:
					fprnt('?', 'S');
					break;
				case CHAR:
				case SHORT:
				case LONG:
					fprnt(laststyle, lastpo);
				}
			if (error) {
				ungetachar(c);
				continue;
			}
			c_count = colon = acting_on_inode = 0;
			acting_on_directory = 0;
			should_print = 1;
			getnextinput();
			if (error)
				continue;
			erraddr = addr;
			errino = cur_ino;
			errinum = cur_inum;
			errcur_bytes = cur_bytes;
			continue;

		case '(': /* numeric expression or unknown command */
		default:
			colon = 0;
			if (digit(c) || c == '(') {
				ungetachar(c);
				addr = expr();
				type = NUMB;
				value = addr;
				continue;
			}
			printf("unknown command or bad syntax\n");
			error++;
			continue;

		case '?': /* general print facilities */
		case '/':
			fprnt(c, getachar());
			continue;

		case ';': /* command separator and . */
		case '\t':
		case ' ':
		case '.':
			continue;

		case ':': /* command indicator */
			colon++;
			commands++;
			should_print = 0;
			stringsize = 0;
			trapped = 0;
			continue;

		case ',': /* count indicator */
			colon = star = 0;
			if ((c = getachar()) == '*') {
				star = 1;
				count = BLKSIZE;
			} else {
				ungetachar(c);
				count = expr();
				if (error)
					continue;
				if (!count)
					count = 1;
			}
			clear = 0;
			continue;

		case '+': /* address addition */
			colon = 0;
			c = getachar();
			ungetachar(c);
			if (c == '\n')
				temp = 1;
			else {
				temp = expr();
				if (error)
					continue;
			}
			erraddr = addr;
			errcur_bytes = cur_bytes;
			switch (objsz) {
			case DIRECTORY:
				addr = getdirslot((long)(dirslot + temp));
				if (error)
					continue;
				break;
			case INODE:
				cur_inum += temp;
				addr = itob(cur_inum);
				if (!icheck(addr)) {
					cur_inum -= temp;
					continue;
				}
				break;
			case CGRP:
			case SB:
				cur_cgrp += temp;
				if ((addr = cgrp_check(cur_cgrp)) == 0) {
					cur_cgrp -= temp;
					continue;
				}
				break;
			case SHADOW_DATA:
				addr = getshadowslot((long)(cur_shad + temp));
				if (error)
				    continue;
				break;

			default:
				laststyle = '/';
				addr += temp * objsz;
				cur_bytes += temp * objsz;
				if (valid_addr() == 0)
					continue;
			}
			value = get(objsz);
			continue;

		case '-': /* address subtraction */
			colon = 0;
			c = getachar();
			ungetachar(c);
			if (c == '\n')
				temp = 1;
			else {
				temp = expr();
				if (error)
					continue;
			}
			erraddr = addr;
			errcur_bytes = cur_bytes;
			switch (objsz) {
			case DIRECTORY:
				addr = getdirslot((long)(dirslot - temp));
				if (error)
					continue;
				break;
			case INODE:
				cur_inum -= temp;
				addr = itob(cur_inum);
				if (!icheck(addr)) {
					cur_inum += temp;
					continue;
				}
				break;
			case CGRP:
			case SB:
				cur_cgrp -= temp;
				if ((addr = cgrp_check(cur_cgrp)) == 0) {
					cur_cgrp += temp;
					continue;
				}
				break;
			case SHADOW_DATA:
				addr = getshadowslot((long)(cur_shad - temp));
				if (error)
					continue;
				break;
			default:
				laststyle = '/';
				addr -= temp * objsz;
				cur_bytes -= temp * objsz;
				if (valid_addr() == 0)
					continue;
			}
			value = get(objsz);
			continue;

		case '*': /* address multiplication */
			colon = 0;
			temp = expr();
			if (error)
				continue;
			if (objsz != INODE && objsz != DIRECTORY)
				laststyle = '/';
			addr *= temp;
			value = get(objsz);
			continue;

		case '%': /* address division */
			colon = 0;
			temp = expr();
			if (error)
				continue;
			if (!temp) {
				printf("divide by zero\n");
				error++;
				continue;
			}
			if (objsz != INODE && objsz != DIRECTORY)
				laststyle = '/';
			addr /= temp;
			value = get(objsz);
			continue;

		case '=': { /* assignment operation */
			short tbase;
calc:
			tbase = base;

			c = getachar();
			if (c == '\n') {
				ungetachar(c);
				c = lastpo;
				if (acting_on_inode == 1) {
					if (c != 'o' && c != 'd' && c != 'x' &&
					    c != 'O' && c != 'D' && c != 'X') {
						switch (objsz) {
						case LONG:
							c = lastpo = 'X';
							break;
						case SHORT:
							c = lastpo = 'x';
							break;
						case CHAR:
							c = lastpo = 'c';
						}
					}
				} else {
					if (acting_on_inode == 2)
						c = lastpo = 't';
				}
			} else if (acting_on_inode)
				lastpo = c;
			should_print = star = 0;
			count = 1;
			erraddr = addr;
			errcur_bytes = cur_bytes;
			switch (c) {
			case '"': /* character string */
				if (type == NUMB) {
					blocksize = BLKSIZE;
					filesize = BLKSIZE * 2;
					cur_bytes = blkoff(fs, addr);
					if (objsz == DIRECTORY ||
								objsz == INODE)
						lastpo = 'X';
				}
				puta();
				continue;
			case '+': /* =+ operator */
				temp = expr();
				value = get(objsz);
				if (!error)
					put(value+temp, objsz);
				continue;
			case '-': /* =- operator */
				temp = expr();
				value = get(objsz);
				if (!error)
					put(value-temp, objsz);
				continue;
			case 'b':
			case 'c':
				if (objsz == CGRP)
					fprnt('?', c);
				else
					fprnt('/', c);
				continue;
			case 'i':
				addr = cur_ino;
				fprnt('?', 'i');
				continue;
			case 's':
				fprnt('?', 's');
				continue;
			case 't':
			case 'T':
				laststyle = '=';
				printf("\t\t");
				{
					/*
					 * Truncation is intentional so
					 * ctime is happy.
					 */
					time_t tvalue = (time_t)value;
					printf("%s", ctime(&tvalue));
				}
				continue;
			case 'o':
				base = OCTAL;
				goto otx;
			case 'd':
				if (objsz == DIRECTORY) {
					addr = cur_dir;
					fprnt('?', 'd');
					continue;
				}
				base = DECIMAL;
				goto otx;
			case 'x':
				base = HEX;
otx:
				laststyle = '=';
				printf("\t\t");
				if (acting_on_inode)
					print(value & 0177777L, 12, -8, 0);
				else
					print(addr & 0177777L, 12, -8, 0);
				printf("\n");
				base = tbase;
				continue;
			case 'O':
				base = OCTAL;
				goto OTX;
			case 'D':
				base = DECIMAL;
				goto OTX;
			case 'X':
				base = HEX;
OTX:
				laststyle = '=';
				printf("\t\t");
				if (acting_on_inode)
					print(value, 12, -8, 0);
				else
					print(addr, 12, -8, 0);
				printf("\n");
				base = tbase;
				continue;
			default: /* regular assignment */
				ungetachar(c);
				value = expr();
				if (error)
					printf("syntax error\n");
				else
					put(value, objsz);
				continue;
			}
		}

		case '>': /* save current address */
			colon = 0;
			should_print = 0;
			c = getachar();
			if (!letter(c) && !digit(c)) {
				printf("invalid register specification, ");
				printf("must be letter or digit\n");
				error++;
				continue;
			}
			if (letter(c)) {
				if (c < 'a')
					c = uppertolower(c);
				c = hextodigit(c);
			} else
				c = numtodigit(c);
			regs[c].sv_addr = addr;
			regs[c].sv_value = value;
			regs[c].sv_objsz = objsz;
			continue;

		case '<': /* restore saved address */
			colon = 0;
			should_print = 0;
			c = getachar();
			if (!letter(c) && !digit(c)) {
				printf("invalid register specification, ");
				printf("must be letter or digit\n");
				error++;
				continue;
			}
			if (letter(c)) {
				if (c < 'a')
					c = uppertolower(c);
				c = hextodigit(c);
			} else
				c = numtodigit(c);
			addr = regs[c].sv_addr;
			value = regs[c].sv_value;
			objsz = regs[c].sv_objsz;
			continue;

		case 'a':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("at", 2)) {		/* access time */
				acting_on_inode = 2;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_atime;
				value = get(LONG);
				type = 0;
				continue;
			}
			goto bad_syntax;

		case 'b':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("block", 2)) {	/* block conversion */
				if (type == NUMB) {
					value = addr;
					cur_bytes = 0;
					blocksize = BLKSIZE;
					filesize = BLKSIZE * 2;
				}
				addr = value << FRGSHIFT;
				bod_addr = addr;
				value = get(LONG);
				type = BLOCK;
				dirslot = 0;
				trapped++;
				continue;
			}
			if (match("bs", 2)) {		/* block size */
				acting_on_inode = 1;
				should_print = 1;
				if (icheck(cur_ino) == 0)
					continue;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_blocks;
				value = get(LONG);
				type = 0;
				continue;
			}
			if (match("base", 2)) {		/* change/show base */
showbase:
				if ((c = getachar()) == '\n') {
					ungetachar(c);
					printf("base =\t\t");
					switch (base) {
					case OCTAL:
						printf("OCTAL\n");
						continue;
					case DECIMAL:
						printf("DECIMAL\n");
						continue;
					case HEX:
						printf("HEX\n");
						continue;
					}
				}
				if (c != '=') {
					printf("missing '='\n");
					error++;
					continue;
				}
				value = expr();
				switch (value) {
				default:
					printf("invalid base\n");
					error++;
					break;
				case OCTAL:
				case DECIMAL:
				case HEX:
					base = (short)value;
				}
				goto showbase;
			}
			goto bad_syntax;

		case 'c':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("cd", 2)) {		/* change directory */
				top = filenames - 1;
				eat_spaces();
				if ((c = getachar()) == '\n') {
					ungetachar(c);
					current_pathp = -1;
					restore_inode(2);
					continue;
				}
				ungetachar(c);
				temp = cur_inum;
				doing_cd = 1;
				parse();
				doing_cd = 0;
				if (nfiles != 1) {
					restore_inode((ino_t)temp);
					if (!error) {
						print_path(input_path,
							(int)input_pathp);
						if (nfiles == 0)
							printf(" not found\n");
						else
							printf(" ambiguous\n");
						error++;
					}
					continue;
				}
				restore_inode(filenames->ino);
				if ((mode = icheck(addr)) == 0)
					continue;
				if ((mode & IFMT) != IFDIR) {
					restore_inode((ino_t)temp);
					print_path(input_path,
							(int)input_pathp);
					printf(" not a directory\n");
					error++;
					continue;
				}
				for (i = 0; i <= top->len; i++)
					(void) strcpy(current_path[i],
						top->fname[i]);
				current_pathp = top->len;
				continue;
			}
			if (match("cg", 2)) {		/* cylinder group */
				if (type == NUMB)
					value = addr;
				if (value > fs->fs_ncg - 1) {
					printf("maximum cylinder group is ");
					print(fs->fs_ncg - 1, 8, -8, 0);
					printf("\n");
					error++;
					continue;
				}
				type = objsz = CGRP;
				cur_cgrp = (long)value;
				addr = cgtod(fs, cur_cgrp) << FRGSHIFT;
				continue;
			}
			if (match("ct", 2)) {		/* creation time */
				acting_on_inode = 2;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_ctime;
				value = get(LONG);
				type = 0;
				continue;
			}
			goto bad_syntax;

		case 'd':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("directory", 2)) {	/* directory offsets */
				if (type == NUMB)
					value = addr;
				objsz = DIRECTORY;
				type = DIRECTORY;
				addr = (u_offset_t)getdirslot((long)value);
				continue;
			}
			if (match("db", 2)) {		/* direct block */
				acting_on_inode = 1;
				should_print = 1;
				if (type == NUMB)
					value = addr;
				if (value >= NDADDR) {
					printf("direct blocks are 0 to ");
					print(NDADDR - 1, 0, 0, 0);
					printf("\n");
					error++;
					continue;
				}
				addr = cur_ino;
				if (!icheck(addr))
					continue;
				addr = (long)
					&((struct dinode *)(uintptr_t)cur_ino)->
								di_db[value];
				bod_addr = addr;
				cur_bytes = (value) * BLKSIZE;
				cur_block = (long)value;
				type = BLOCK;
				dirslot = 0;
				value = get(LONG);
				if (!value && !override) {
					printf("non existent block\n");
					error++;
				}
				continue;
			}
			goto bad_syntax;

		case 'f':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("find", 3)) {		/* find command */
				find();
				continue;
			}
			if (match("fragment", 2)) {	/* fragment conv. */
				if (type == NUMB) {
					value = addr;
					cur_bytes = 0;
					blocksize = FRGSIZE;
					filesize = FRGSIZE * 2;
				}
				if (min(blocksize, filesize) - cur_bytes >
							FRGSIZE) {
					blocksize = cur_bytes + FRGSIZE;
					filesize = blocksize * 2;
				}
				addr = value << FRGSHIFT;
				bod_addr = addr;
				value = get(LONG);
				type = FRAGMENT;
				dirslot = 0;
				trapped++;
				continue;
			}
			if (match("file", 4)) {		/* access as file */
				acting_on_inode = 1;
				should_print = 1;
				if (type == NUMB)
					value = addr;
				addr = cur_ino;
				if ((mode = icheck(addr)) == 0)
					continue;
				if (!override) {
					switch (mode & IFMT) {
					case IFCHR:
					case IFBLK:
					    printf("special device\n");
					    error++;
					    continue;
					}
				}
				if ((addr = (u_offset_t)
				    (bmap((long)value) << FRGSHIFT)) == 0)
					continue;
				cur_block = (long)value;
				bod_addr = addr;
				type = BLOCK;
				dirslot = 0;
				continue;
			}
			if (match("fill", 4)) {		/* fill */
				if (getachar() != '=') {
					printf("missing '='\n");
					error++;
					continue;
				}
				if (objsz == INODE || objsz == DIRECTORY ||
				    objsz == SHADOW_DATA) {
					printf(
					    "can't fill inode or directory\n");
					error++;
					continue;
				}
				fill();
				continue;
			}
			goto bad_syntax;

		case 'g':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("gid", 1)) {		/* group id */
				acting_on_inode = 1;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_gid;
				value = get(SHORT);
				type = 0;
				continue;
			}
			goto bad_syntax;

		case 'i':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("inode", 2)) { /* i# to inode conversion */
				if (c_count == 2) {
					addr = cur_ino;
					value = get(INODE);
					type = 0;
					laststyle = '=';
					lastpo = 'i';
					should_print = 1;
					continue;
				}
				if (type == NUMB)
					value = addr;
				addr = itob(value);
				if (!icheck(addr))
					continue;
				cur_ino = addr;
				cur_inum = (long)value;
				value = get(INODE);
				type = 0;
				continue;
			}
			if (match("ib", 2)) {	/* indirect block */
				acting_on_inode = 1;
				should_print = 1;
				if (type == NUMB)
					value = addr;
				if (value >= NIADDR) {
					printf("indirect blocks are 0 to ");
					print(NIADDR - 1, 0, 0, 0);
					printf("\n");
					error++;
					continue;
				}
				addr = (long)&((struct dinode *)(uintptr_t)
						cur_ino)->di_ib[value];
				cur_bytes = (NDADDR - 1) * BLKSIZE;
				temp = 1;
				for (i = 0; i < value; i++) {
					temp *= NINDIR(fs) * BLKSIZE;
					cur_bytes += temp;
				}
				type = BLOCK;
				dirslot = 0;
				value = get(LONG);
				if (!value && !override) {
					printf("non existent block\n");
					error++;
				}
				continue;
			}
			goto bad_syntax;

		case 'l':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("log_head", 8)) {
				log_display_header();
				should_print = 0;
				continue;
			}
			if (match("log_delta", 9)) {
				log_show(LOG_NDELTAS);
				should_print = 0;
				continue;
			}
			if (match("log_show", 8)) {
				log_show(LOG_ALLDELTAS);
				should_print = 0;
				continue;
			}
			if (match("log_chk", 7)) {
				log_show(LOG_CHECKSCAN);
				should_print = 0;
				continue;
			}
			if (match("log_otodb", 9)) {
				if (log_lodb((u_offset_t)addr, &temp)) {
					addr = temp;
					should_print = 1;
					laststyle = '=';
				} else
					error++;
				continue;
			}
			if (match("ls", 2)) {		/* ls command */
				temp = cur_inum;
				recursive = long_list = 0;
				top = filenames - 1;
				for (;;) {
					eat_spaces();
					if ((c = getachar()) == '-') {
						if ((c = getachar()) == 'R') {
							recursive = 1;
							continue;
						} else if (c == 'l') {
							long_list = 1;
						} else {
							printf(
							    "unknown option ");
							printf("'%c'\n", c);
							error++;
							break;
						}
					} else
						ungetachar(c);
					if ((c = getachar()) == '\n') {
						if (c_count != 2) {
							ungetachar(c);
							break;
						}
					}
					c_count++;
					ungetachar(c);
					parse();
					restore_inode((ino_t)temp);
					if (error)
						break;
				}
				recursive = 0;
				if (error || nfiles == 0) {
					if (!error) {
						print_path(input_path,
							(int)input_pathp);
						printf(" not found\n");
					}
					continue;
				}
				if (nfiles) {
				    cmp_level = 0;
				    qsort((char *)filenames, nfiles,
					sizeof (struct filenames), ffcmp);
				    ls(filenames, filenames + (nfiles - 1), 0);
				} else {
				    printf("no match\n");
				    error++;
				}
				restore_inode((ino_t)temp);
				continue;
			}
			if (match("ln", 2)) {		/* link count */
				acting_on_inode = 1;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_nlink;
				value = get(SHORT);
				type = 0;
				continue;
			}
			goto bad_syntax;

		case 'm':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			addr = cur_ino;
			if ((mode = icheck(addr)) == 0)
				continue;
			if (match("mt", 2)) {		/* modification time */
				acting_on_inode = 2;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_mtime;
				value = get(LONG);
				type = 0;
				continue;
			}
			if (match("md", 2)) {		/* mode */
				acting_on_inode = 1;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_mode;
				value = get(SHORT);
				type = 0;
				continue;
			}
			if (match("maj", 2)) {	/* major device number */
				acting_on_inode = 1;
				should_print = 1;
				if (devcheck(mode))
					continue;
				addr = (uintptr_t)&((struct dinode *)(uintptr_t)
							cur_ino)->di_ordev;
				{
					long	dvalue;
					dvalue = get(LONG);
					value = major(dvalue);
				}
				type = 0;
				continue;
			}
			if (match("min", 2)) {	/* minor device number */
				acting_on_inode = 1;
				should_print = 1;
				if (devcheck(mode))
					continue;
				addr = (uintptr_t)&((struct dinode *)(uintptr_t)
							cur_ino)->di_ordev;
				{
					long	dvalue;
					dvalue = (long)get(LONG);
					value = minor(dvalue);
				}
				type = 0;
				continue;
			}
			goto bad_syntax;

		case 'n':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("nm", 1)) {		/* directory name */
				objsz = DIRECTORY;
				acting_on_directory = 1;
				cur_dir = addr;
				if ((cptr = getblk(addr)) == 0)
					continue;
				/*LINTED*/
				dirp = (struct direct *)(cptr+blkoff(fs, addr));
				stringsize = (long)dirp->d_reclen -
						((long)&dirp->d_name[0] -
							(long)&dirp->d_ino);
				addr = (long)&((struct direct *)
						(uintptr_t)addr)->d_name[0];
				type = 0;
				continue;
			}
			goto bad_syntax;

		case 'o':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("override", 1)) {	/* override flip flop */
				override = !override;
				if (override)
					printf("error checking off\n");
				else
					printf("error checking on\n");
				continue;
			}
			goto bad_syntax;

		case 'p':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("pwd", 2)) {		/* print working dir */
				print_path(current_path, (int)current_pathp);
				printf("\n");
				continue;
			}
			if (match("prompt", 2)) {	/* change prompt */
				if ((c = getachar()) != '=') {
					printf("missing '='\n");
					error++;
					continue;
				}
				if ((c = getachar()) != '"') {
					printf("missing '\"'\n");
					error++;
					continue;
				}
				i = 0;
				prompt = &prompt[0];
				while ((c = getachar()) != '"' && c != '\n') {
					prompt[i++] = c;
					if (i >= PROMPTSIZE) {
						printf("string too long\n");
						error++;
						break;
					}
				}
				prompt[i] = '\0';
				continue;
			}
			goto bad_syntax;

		case 'q':
			if (!colon)
				goto no_colon;
			if (match("quit", 1)) {		/* quit */
				if ((c = getachar()) != '\n') {
					error++;
					continue;
				}
				exit(0);
			}
			goto bad_syntax;

		case 's':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("sb", 2)) {		/* super block */
				if (c_count == 2) {
					cur_cgrp = -1;
					type = objsz = SB;
					laststyle = '=';
					lastpo = 's';
					should_print = 1;
					continue;
				}
				if (type == NUMB)
					value = addr;
				if (value > fs->fs_ncg - 1) {
					printf("maximum super block is ");
					print(fs->fs_ncg - 1, 8, -8, 0);
					printf("\n");
					error++;
					continue;
				}
				type = objsz = SB;
				cur_cgrp = (long)value;
				addr = cgsblock(fs, cur_cgrp) << FRGSHIFT;
				continue;
			}
			if (match("shadow", 2)) {	/* shadow inode data */
				if (type == NUMB)
					value = addr;
				objsz = SHADOW_DATA;
				type = SHADOW_DATA;
				addr = getshadowslot(value);
				continue;
			}
			if (match("si", 2)) {   /* shadow inode field */
				acting_on_inode = 1;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_shadow;
				value = get(LONG);
				type = 0;
				continue;
			}

			if (match("sz", 2)) {		/* file size */
				acting_on_inode = 1;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_size;
				value = get(U_OFFSET_T);
				type = 0;
				objsz = U_OFFSET_T;
				laststyle = '=';
				lastpo = 'X';
				continue;
			}
			goto bad_syntax;

		case 'u':
			if (colon)
				colon = 0;
			else
				goto no_colon;
			if (match("uid", 1)) {		/* user id */
				acting_on_inode = 1;
				should_print = 1;
				addr = (long)&((struct dinode *)
						(uintptr_t)cur_ino)->di_uid;
				value = get(SHORT);
				type = 0;
				continue;
			}
			goto bad_syntax;

		case 'F': /* buffer status (internal use only) */
			if (colon)
				colon = 0;
			else
				goto no_colon;
			for (bp = bhdr.fwd; bp != &bhdr; bp = bp->fwd)
				printf("%8" PRIx64 " %d\n",
				    bp->blkno, bp->valid);
			printf("\n");
			printf("# commands\t\t%ld\n", commands);
			printf("# read requests\t\t%ld\n", read_requests);
			printf("# actual disk reads\t%ld\n", actual_disk_reads);
			continue;
no_colon:
		printf("a colon should precede a command\n");
		error++;
		continue;
bad_syntax:
		printf("more letters needed to distinguish command\n");
		error++;
		continue;
		}
	}
}

/*
 * usage - print usage and exit
 */
static void
usage(char *progname)
{
	printf("usage:   %s [options] special\n", progname);
	printf("options:\n");
	printf("\t-o		Specify ufs filesystem sepcific options\n");
	printf("		Available suboptions are:\n");
	printf("\t\t?		display usage\n");
	printf("\t\to		override some error conditions\n");
	printf("\t\tp=\"string\"	set prompt to string\n");
	printf("\t\tw		open for write\n");
	exit(1);
}

/*
 * getachar - get next character from input buffer.
 */
static char
getachar()
{
	return (input_buffer[input_pointer++]);
}

/*
 * ungetachar - return character to input buffer.
 */
static void
ungetachar(char c)
{
	if (input_pointer == 0) {
		printf("internal problem maintaining input buffer\n");
		error++;
		return;
	}
	input_buffer[--input_pointer] = c;
}

/*
 * getnextinput - display the prompt and read an input line.
 *	An input line is up to 128 characters terminated by the newline
 *	character.  Handle overflow, shell escape, and eof.
 */
static void
getnextinput()
{
	int	i;
	char	c;
	short	pid, rpid;
	int	retcode;

newline:
	i = 0;
	printf("%s", prompt);
ignore_eol:
	while ((c = getc(stdin)) != '\n' && !(c == '!' && i == 0) &&
					!feof(stdin) && i <= INPUTBUFFER - 2)
		input_buffer[i++] = c;
	if (i > 0 && input_buffer[i - 1] == '\\') {
		input_buffer[i++] = c;
		goto ignore_eol;
	}
	if (feof(stdin)) {
		printf("\n");
		exit(0);
	}
	if (c == '!') {
		if ((pid = fork()) == 0) {
			(void) execl(_PATH_BSHELL, "sh", "-t", 0);
			error++;
			return;
		}
		while ((rpid = wait(&retcode)) != pid && rpid != -1)
			;
		printf("!\n");
		goto newline;
	}
	if (c != '\n')
		printf("input truncated to 128 characters\n");
	input_buffer[i] = '\n';
	input_pointer = 0;
}

/*
 * eat_spaces - read extraneous spaces.
 */
static void
eat_spaces()
{
	char	c;

	while ((c = getachar()) == ' ')
		;
	ungetachar(c);
}

/*
 * restore_inode - set up all inode indicators so inum is now
 *	the current inode.
 */
static void
restore_inode(ino_t inum)
{
	errinum = cur_inum = inum;
	addr = errino = cur_ino = itob(inum);
}

/*
 * match - return false if the input does not match string up to
 *	upto letters.   Then proceed to chew up extraneous letters.
 */
static int
match(char *string, int upto)
{
	int	i, length = strlen(string) - 1;
	char	c;
	int	save_upto = upto;

	while (--upto) {
		string++;
		if ((c = getachar()) != *string) {
			for (i = save_upto - upto; i; i--) {
				ungetachar(c);
				c = *--string;
			}
			return (0);
		}
		length--;
	}
	while (length--) {
		string++;
		if ((c = getachar()) != *string) {
			ungetachar(c);
			return (1);
		}
	}
	return (1);
}

/*
 * expr - expression evaluator.  Will evaluate expressions from
 *	left to right with no operator precedence.  Parentheses may
 *	be used.
 */
static long
expr()
{
	long	numb = 0, temp;
	char	c;

	numb = term();
	for (;;) {
		if (error)
			return (~0);	/* error is set so value is ignored */
		c = getachar();
		switch (c) {

		case '+':
			numb += term();
			continue;

		case '-':
			numb -= term();
			continue;

		case '*':
			numb *= term();
			continue;

		case '%':
			temp = term();
			if (!temp) {
				printf("divide by zero\n");
				error++;
				return (~0);
			}
			numb /= temp;
			continue;

		case ')':
			paren--;
			return (numb);

		default:
			ungetachar(c);
			if (paren && !error) {
				printf("missing ')'\n");
				error++;
			}
			return (numb);
		}
	}
}

/*
 * term - used by expression evaluator to get an operand.
 */
static long
term()
{
	char	c;

	switch (c = getachar()) {

	default:
		ungetachar(c);
		/*FALLTHRU*/
	case '+':
		return (getnumb());

	case '-':
		return (-getnumb());

	case '(':
		paren++;
		return (expr());
	}
}

/*
 * getnumb - read a number from the input stream.  A leading
 *	zero signifies octal interpretation, a leading '0x'
 *	signifies hexadecimal, and a leading '0t' signifies
 *	decimal.  If the first character is a character,
 *	return an error.
 */
static long
getnumb()
{

	char		c, savec;
	long		number = 0, tbase, num;
	extern short	error;

	c = getachar();
	if (!digit(c)) {
		error++;
		ungetachar(c);
		return (-1);
	}
	if (c == '0') {
		tbase = OCTAL;
		if ((c = getachar()) == 'x')
			tbase = HEX;
		else if (c == 't')
			tbase = DECIMAL;
		else ungetachar(c);
	} else {
		tbase = base;
		ungetachar(c);
	}
	for (;;) {
		num = tbase;
		c = savec = getachar();
		if (HEXLETTER(c))
			c = uppertolower(c);
		switch (tbase) {
		case HEX:
			if (hexletter(c)) {
				num = hextodigit(c);
				break;
			}
			/*FALLTHRU*/
		case DECIMAL:
			if (digit(c))
				num = numtodigit(c);
			break;
		case OCTAL:
			if (octaldigit(c))
				num = numtodigit(c);
		}
		if (num == tbase)
			break;
		number = number * tbase + num;
	}
	ungetachar(savec);
	return (number);
}

/*
 * find - the syntax is almost identical to the unix command.
 *		find dir [-name pattern] [-inum number]
 *	Note:  only one of -name or -inum may be used at a time.
 *	       Also, the -print is not needed (implied).
 */
static void
find()
{
	struct filenames	*fn;
	char			c;
	long			temp;
	short			mode;

	eat_spaces();
	temp = cur_inum;
	top = filenames - 1;
	doing_cd = 1;
	parse();
	doing_cd = 0;
	if (nfiles != 1) {
		restore_inode((ino_t)temp);
		if (!error) {
			print_path(input_path, (int)input_pathp);
			if (nfiles == 0)
				printf(" not found\n");
			else
				printf(" ambiguous\n");
			error++;
			return;
		}
	}
	restore_inode(filenames->ino);
	freemem(filenames, nfiles);
	nfiles = 0;
	top = filenames - 1;
	if ((mode = icheck(addr)) == 0)
		return;
	if ((mode & IFMT) != IFDIR) {
		print_path(input_path, (int)input_pathp);
		printf(" not a directory\n");
		error++;
		return;
	}
	eat_spaces();
	if ((c = getachar()) != '-') {
		restore_inode((ino_t)temp);
		printf("missing '-'\n");
		error++;
		return;
	}
	find_by_name = find_by_inode = 0;
	c = getachar();
	if (match("name", 4)) {
		eat_spaces();
		find_by_name = 1;
	} else if (match("inum", 4)) {
		eat_spaces();
		find_ino = expr();
		if (error) {
			restore_inode((ino_t)temp);
			return;
		}
		while ((c = getachar()) != '\n')
			;
		ungetachar(c);
		find_by_inode = 1;
	} else {
		restore_inode((ino_t)temp);
		printf("use -name or -inum with find\n");
		error++;
		return;
	}
	doing_find = 1;
	parse();
	doing_find = 0;
	if (error) {
		restore_inode((ino_t)temp);
		return;
	}
	for (fn = filenames; fn <= top; fn++) {
		if (fn->find == 0)
			continue;
		printf("i#: ");
		print(fn->ino, 12, -8, 0);
		print_path(fn->fname, (int)fn->len);
		printf("\n");
	}
	restore_inode((ino_t)temp);
}

/*
 * ls - do an ls.  Should behave exactly as ls(1).
 *	Only -R and -l is supported and -l gives different results.
 */
static void
ls(struct filenames *fn0, struct filenames *fnlast, short level)
{
	struct filenames	*fn, *fnn;

	fn = fn0;
	for (;;) {
		fn0 = fn;
		if (fn0->len) {
			cmp_level = level;
			qsort((char *)fn0, fnlast - fn0 + 1,
				sizeof (struct filenames), fcmp);
		}
		for (fnn = fn, fn++; fn <= fnlast; fnn = fn, fn++) {
			if (fnn->len != fn->len && level == fnn->len - 1)
				break;
			if (fnn->len == 0)
				continue;
			if (strcmp(fn->fname[level], fnn->fname[level]))
				break;
		}
		if (fn0->len && level != fn0->len - 1)
			ls(fn0, fnn, level + 1);
		else {
			if (fn0 != filenames)
				printf("\n");
			print_path(fn0->fname, (int)(fn0->len - 1));
			printf(":\n");
			if (fn0->len == 0)
				cmp_level = level;
			else
				cmp_level = level + 1;
			qsort((char *)fn0, fnn - fn0 + 1,
				sizeof (struct filenames), fcmp);
			formatf(fn0, fnn);
			nfiles -= fnn - fn0 + 1;
		}
		if (fn > fnlast)
			return;
	}
}

/*
 * formatf - code lifted from ls.
 */
static void
formatf(struct filenames *fn0, struct filenames *fnlast)
{
	struct filenames	*fn;
	int			width = 0, w, nentry = fnlast - fn0 + 1;
	int			i, j, columns, lines;
	char			*cp;

	if (long_list) {
		columns = 1;
	} else {
		for (fn = fn0; fn <= fnlast; fn++) {
			int len = strlen(fn->fname[cmp_level]) + 2;

			if (len > width)
				width = len;
		}
		width = (width + 8) &~ 7;
		columns = 80 / width;
		if (columns == 0)
			columns = 1;
	}
	lines = (nentry + columns - 1) / columns;
	for (i = 0; i < lines; i++) {
		for (j = 0; j < columns; j++) {
			fn = fn0 + j * lines + i;
			if (long_list) {
				printf("i#: ");
				print(fn->ino, 12, -8, 0);
			}
			if ((cp = fmtentry(fn)) == NULL) {
				printf("cannot read inode %ld\n", fn->ino);
				return;
			}
			printf("%s", cp);
			if (fn + lines > fnlast) {
				printf("\n");
				break;
			}
			w = strlen(cp);
			while (w < width) {
				w = (w + 8) &~ 7;
				(void) putchar('\t');
			}
		}
	}
}

/*
 * fmtentry - code lifted from ls.
 */
static char *
fmtentry(struct filenames *fn)
{
	static char	fmtres[BUFSIZ];
	struct dinode	*ip;
	char		*cptr, *cp, *dp;

	dp = &fmtres[0];
	for (cp = fn->fname[cmp_level]; *cp; cp++) {
		if (*cp < ' ' || *cp >= 0177)
			*dp++ = '?';
		else
			*dp++ = *cp;
	}
	addr = itob(fn->ino);
	if ((cptr = getblk(addr)) == 0)
		return (NULL);
	cptr += blkoff(fs, addr);
	/*LINTED*/
	ip = (struct dinode *)cptr;
	switch (ip->di_mode & IFMT) {
	case IFDIR:
		*dp++ = '/';
		break;
	case IFLNK:
		*dp++ = '@';
		break;
	case IFSOCK:
		*dp++ = '=';
		break;
#ifdef IFIFO
	case IFIFO:
		*dp++ = 'p';
		break;
#endif
	case IFCHR:
	case IFBLK:
	case IFREG:
		if (ip->di_mode & 0111)
			*dp++ = '*';
		else
			*dp++ = ' ';
		break;
	default:
		*dp++ = '?';

	}
	*dp++ = 0;
	return (fmtres);
}

/*
 * fcmp - routine used by qsort.  Will sort first by name, then
 *	then by pathname length if names are equal.  Uses global
 *	cmp_level to tell what component of the path name we are comparing.
 */
static int
fcmp(struct filenames *f1, struct filenames *f2)
{
	int value;

	if ((value = strcmp(f1->fname[cmp_level], f2->fname[cmp_level])))
		return (value);
	return (f1->len - f2->len);
}

/*
 * ffcmp - routine used by qsort.  Sort only by pathname length.
 */
static int
ffcmp(struct filenames *f1, struct filenames *f2)
{
	return (f1->len - f2->len);
}

/*
 * parse - set up the call to follow_path.
 */
static void
parse()
{
	int	i;
	char	c;

	stack_pathp = input_pathp = -1;
	if ((c = getachar()) == '/') {
		while ((c = getachar()) == '/')
			;
		ungetachar(c);
		cur_inum = 2;
		c = getachar();
		if ((c == '\n') || ((doing_cd) && (c == ' '))) {
			ungetachar(c);
			if (doing_cd) {
				top++;
				top->ino = 2;
				top->len = -1;
				nfiles = 1;
				return;
			}
		} else
			ungetachar(c);
	} else {
		ungetachar(c);
		stack_pathp = current_pathp;
		if (!doing_find)
			input_pathp = current_pathp;
		for (i = 0; i <= current_pathp; i++) {
			if (!doing_find)
				(void) strcpy(input_path[i], current_path[i]);
			(void) strcpy(stack_path[i], current_path[i]);
		}
	}
	getname();
	follow_path((long)(stack_pathp + 1), cur_inum);
}

/*
 * follow_path - called by cd, find, and ls.
 *	input_path holds the name typed by the user.
 *	stack_path holds the name at the current depth.
 */
static void
follow_path(long level, long inum)
{
	struct direct		*dirp;
	char			**ccptr, *cptr;
	int			i;
	struct filenames	*tos, *bos, *fn, *fnn, *fnnn;
	long			block;
	short			mode;

	tos = top + 1;
	restore_inode((ino_t)inum);
	if ((mode = icheck(addr)) == 0)
		return;
	if ((mode & IFMT) != IFDIR)
	    return;
	block = cur_bytes = 0;
	while (cur_bytes < filesize) {
	    if (block == 0 || bcomp(addr)) {
		error = 0;
		if ((addr = ((u_offset_t)bmap(block++) <<
				(u_offset_t)FRGSHIFT)) == 0)
		    break;
		if ((cptr = getblk(addr)) == 0)
		    break;
		cptr += blkoff(fs, addr);
	    }
		/*LINTED*/
	    dirp = (struct direct *)cptr;
	    if (dirp->d_ino) {
		if (level > input_pathp || doing_find ||
			compare(input_path[level], &dirp->d_name[0], 1)) {
		    if ((doing_find) &&
			((strcmp(dirp->d_name, ".") == 0 ||
					strcmp(dirp->d_name, "..") == 0)))
			goto duplicate;
		    if (++top - filenames >= maxfiles) {
			printf("too many files\n");
			error++;
			return;
		    }
		    top->fname = (char **)calloc(FIRST_DEPTH, sizeof (char **));
		    top->flag = 0;
		    if (top->fname == 0) {
			printf("out of memory\n");
			error++;
			return;
		    }
		    nfiles++;
		    top->ino = dirp->d_ino;
		    top->len = stack_pathp;
		    top->find = 0;
		    if (doing_find) {
			if (find_by_name) {
			    if (compare(input_path[0], &dirp->d_name[0], 1))
				top->find = 1;
			} else if (find_by_inode)
			    if (find_ino == dirp->d_ino)
				top->find = 1;
		    }
		    if (top->len + 1 >= FIRST_DEPTH && top->flag == 0) {
			ccptr = (char **)calloc(SECOND_DEPTH, sizeof (char **));
			if (ccptr == 0) {
			    printf("out of memory\n");
			    error++;
			    return;
			}
			for (i = 0; i < FIRST_DEPTH; i++)
				ccptr[i] = top->fname[i];
			free((char *)top->fname);
			top->fname = ccptr;
			top->flag = 1;
		    }
		    if (top->len >= SECOND_DEPTH) {
			printf("maximum depth exceeded, try to cd lower\n");
			error++;
			return;
		    }
			/*
			 * Copy current depth.
			 */
		    for (i = 0; i <= stack_pathp; i++) {
			top->fname[i] = calloc(1, strlen(stack_path[i])+1);
			if (top->fname[i] == 0) {
			    printf("out of memory\n");
			    error++;
			    return;
			}
			(void) strcpy(top->fname[i], stack_path[i]);
		    }
			/*
			 * Check for '.' or '..' typed.
			 */
		    if ((level <= input_pathp) &&
				(strcmp(input_path[level], ".") == 0 ||
					strcmp(input_path[level], "..") == 0)) {
			if (strcmp(input_path[level], "..") == 0 &&
							top->len >= 0) {
			    free(top->fname[top->len]);
			    top->len -= 1;
			}
		    } else {
			/*
			 * Check for duplicates.
			 */
			if (!doing_cd && !doing_find) {
			    for (fn = filenames; fn < top; fn++) {
				if (fn->ino == dirp->d_ino &&
					    fn->len == stack_pathp + 1) {
				    for (i = 0; i < fn->len; i++)
					if (strcmp(fn->fname[i], stack_path[i]))
					    break;
				    if (i != fn->len ||
					    strcmp(fn->fname[i], dirp->d_name))
					continue;
				    freemem(top, 1);
				    if (top == filenames)
					top = NULL;
				    else
					top--;
				    nfiles--;
				    goto duplicate;
				}
			    }
			}
			top->len += 1;
			top->fname[top->len] = calloc(1,
						strlen(&dirp->d_name[0])+1);
			if (top->fname[top->len] == 0) {
			    printf("out of memory\n");
			    error++;
			    return;
			}
			(void) strcpy(top->fname[top->len], &dirp->d_name[0]);
		    }
		}
	    }
duplicate:
	    addr += dirp->d_reclen;
	    cptr += dirp->d_reclen;
	    cur_bytes += dirp->d_reclen;
	}
	if (top < filenames)
	    return;
	if ((doing_cd && level == input_pathp) ||
		(!recursive && !doing_find && level > input_pathp))
	    return;
	bos = top;
	/*
	 * Check newly added entries to determine if further expansion
	 * is required.
	 */
	for (fn = tos; fn <= bos; fn++) {
		/*
		 * Avoid '.' and '..' if beyond input.
		 */
	    if ((recursive || doing_find) && (level > input_pathp) &&
		(strcmp(fn->fname[fn->len], ".") == 0 ||
			strcmp(fn->fname[fn->len], "..") == 0))
		continue;
	    restore_inode(fn->ino);
	    if ((mode = icheck(cur_ino)) == 0)
		return;
	    if ((mode & IFMT) == IFDIR || level < input_pathp) {
		/*
		 * Set up current depth, remove current entry and
		 * continue recursion.
		 */
		for (i = 0; i <= fn->len; i++)
		    (void) strcpy(stack_path[i], fn->fname[i]);
		stack_pathp = fn->len;
		if (!doing_find &&
			(!recursive || (recursive && level <= input_pathp))) {
			/*
			 * Remove current entry by moving others up.
			 */
		    freemem(fn, 1);
		    fnn = fn;
		    for (fnnn = fnn, fnn++; fnn <= top; fnnn = fnn, fnn++) {
			fnnn->ino = fnn->ino;
			fnnn->len = fnn->len;
			if (fnnn->len + 1 < FIRST_DEPTH) {
			    fnnn->fname = (char **)calloc(FIRST_DEPTH,
							sizeof (char **));
			    fnnn->flag = 0;
			} else if (fnnn->len < SECOND_DEPTH) {
			    fnnn->fname = (char **)calloc(SECOND_DEPTH,
							sizeof (char **));
			    fnnn->flag = 1;
			} else {
			    printf("maximum depth exceeded, ");
			    printf("try to cd lower\n");
			    error++;
			    return;
			}
			for (i = 0; i <= fnn->len; i++)
			    fnnn->fname[i] = fnn->fname[i];
		    }
		    if (fn == tos)
			fn--;
		    top--;
		    bos--;
		    nfiles--;
		}
		follow_path(level + 1, cur_inum);
		if (error)
			return;
	    }
	}
}

/*
 * getname - break up the pathname entered by the user into components.
 */
static void
getname()
{
	int	i;
	char	c;

	if ((c = getachar()) == '\n') {
	    ungetachar(c);
	    return;
	}
	ungetachar(c);
	input_pathp++;
clear:
	for (i = 0; i < MAXNAMLEN; i++)
	    input_path[input_pathp][i] = '\0';
	for (;;) {
	    c = getachar();
	    if (c == '\\') {
		if ((int)strlen(input_path[input_pathp]) + 1 >= MAXNAMLEN) {
		    printf("maximum name length exceeded, ");
		    printf("truncating\n");
		    return;
		}
		input_path[input_pathp][strlen(input_path[input_pathp])] = c;
		input_path[input_pathp][strlen(input_path[input_pathp])] =
						getachar();
		continue;
	    }
	    if (c == ' ' || c == '\n') {
		ungetachar(c);
		return;
	    }
	    if (!doing_find && c == '/') {
		if (++input_pathp >= MAXPATHLEN) {
		    printf("maximum path length exceeded, ");
		    printf("truncating\n");
		    input_pathp--;
		    return;
		}
		goto clear;
	    }
	    if ((int)strlen(input_path[input_pathp]) >= MAXNAMLEN) {
		printf("maximum name length exceeded, truncating\n");
		return;
	    }
	    input_path[input_pathp][strlen(input_path[input_pathp])] = c;
	}
}

/*
 * compare - check if a filename matches the pattern entered by the user.
 *	Handles '*', '?', and '[]'.
 */
static int
compare(char *s1, char *s2, short at_start)
{
	char	c, *s;

	s = s2;
	while ((c = *s1) != '\0') {
		if (c == '*') {
			if (at_start && s == s2 && !letter(*s2) && !digit(*s2))
				return (0);
			if (*++s1 == 0)
				return (1);
			while (*s2) {
				if (compare(s1, s2, 0))
					return (1);
				if (error)
					return (0);
				s2++;
			}
		}
		if (*s2 == 0)
			return (0);
		if (c == '\\') {
			s1++;
			goto compare_chars;
		}
		if (c == '?') {
			if (at_start && s == s2 && !letter(*s2) && !digit(*s2))
				return (0);
			s1++;
			s2++;
			continue;
		}
		if (c == '[') {
			s1++;
			if (*s2 >= *s1++) {
				if (*s1++ != '-') {
					printf("missing '-'\n");
					error++;
					return (0);
				}
				if (*s2 <= *s1++) {
					if (*s1++ != ']') {
						printf("missing ']'");
						error++;
						return (0);
					}
					s2++;
					continue;
				}
			}
		}
compare_chars:
		if (*s1++ == *s2++)
			continue;
		else
			return (0);
	}
	if (*s1 == *s2)
		return (1);
	return (0);
}

/*
 * freemem - free the memory allocated to the filenames structure.
 */
static void
freemem(struct filenames *p, int numb)
{
	int	i, j;

	if (numb == 0)
		return;
	for (i = 0; i < numb; i++, p++) {
		for (j = 0; j <= p->len; j++)
			free(p->fname[j]);
		free((char *)p->fname);
	}
}

/*
 * print_path - print the pathname held in p.
 */
static void
print_path(char *p[], int pntr)
{
	int	i;

	printf("/");
	if (pntr >= 0) {
		for (i = 0; i < pntr; i++)
			printf("%s/", p[i]);
		printf("%s", p[pntr]);
	}
}

/*
 * fill - fill a section with a value or string.
 *	addr,count:fill=[value, "string"].
 */
static void
fill()
{
	char		*cptr;
	int		i;
	short		eof_flag, end = 0, eof = 0;
	long		temp, tcount;
	u_offset_t	taddr;

	if (wrtflag == O_RDONLY) {
		printf("not opened for write '-w'\n");
		error++;
		return;
	}
	temp = expr();
	if (error)
		return;
	if ((cptr = getblk(addr)) == 0)
		return;
	if (type == NUMB)
		eof_flag = 0;
	else
		eof_flag = 1;
	taddr = addr;
	switch (objsz) {
	case LONG:
		addr &= ~(LONG - 1);
		break;
	case SHORT:
		addr &= ~(SHORT - 1);
		temp &= 0177777L;
		break;
	case CHAR:
		temp &= 0377;
	}
	cur_bytes -= taddr - addr;
	cptr += blkoff(fs, addr);
	tcount = check_addr(eof_flag, &end, &eof, 0);
	for (i = 0; i < tcount; i++) {
		switch (objsz) {
		case LONG:
			/*LINTED*/
			*(long *)cptr = temp;
			break;
		case SHORT:
			/*LINTED*/
			*(short *)cptr = temp;
			break;
		case CHAR:
			*cptr = temp;
		}
		cptr += objsz;
	}
	addr += (tcount - 1) * objsz;
	cur_bytes += (tcount - 1) * objsz;
	put((u_offset_t)temp, objsz);
	if (eof) {
		printf("end of file\n");
		error++;
	} else if (end) {
		printf("end of block\n");
		error++;
	}
}

/*
 * get - read a byte, short or long from the file system.
 *	The entire block containing the desired item is read
 *	and the appropriate data is extracted and returned.
 */
static offset_t
get(short lngth)
{

	char		*bptr;
	u_offset_t	temp = addr;

	objsz = lngth;
	if (objsz == INODE || objsz == SHORT)
		temp &= ~(SHORT - 1);
	else if (objsz == DIRECTORY || objsz == LONG || objsz == SHADOW_DATA)
		temp &= ~(LONG - 1);
	if ((bptr = getblk(temp)) == 0)
		return (-1);
	bptr += blkoff(fs, temp);
	switch (objsz) {
	case CHAR:
		return ((offset_t)*bptr);
	case SHORT:
	case INODE:
		/*LINTED*/
		return ((offset_t)(*(short *)bptr));
	case LONG:
	case DIRECTORY:
	case SHADOW_DATA:
		/*LINTED*/
		return ((offset_t)(*(long *)bptr));
	case U_OFFSET_T:
		/*LINTED*/
		return (*(offset_t *)bptr);
	}
	return (0);
}

/*
 * cgrp_check - make sure that we don't bump the cylinder group
 *	beyond the total number of cylinder groups or before the start.
 */
static int
cgrp_check(long cgrp)
{
	if (cgrp < 0) {
		if (objsz == CGRP)
			printf("beginning of cylinder groups\n");
		else
			printf("beginning of super blocks\n");
		error++;
		return (0);
	}
	if (cgrp >= fs->fs_ncg) {
		if (objsz == CGRP)
			printf("end of cylinder groups\n");
		else
			printf("end of super blocks\n");
		error++;
		return (0);
	}
	if (objsz == CGRP)
		return (cgtod(fs, cgrp) << FRGSHIFT);
	else
		return (cgsblock(fs, cgrp) << FRGSHIFT);
}

/*
 * icheck -  make sure we can read the block containing the inode
 *	and determine the filesize (0 if inode not allocated).  Return
 *	0 if error otherwise return the mode.
 */
int
icheck(u_offset_t address)
{
	char		*cptr;
	struct dinode	*ip;

	if ((cptr = getblk(address)) == 0)
		return (0);
	cptr += blkoff(fs, address);
	/*LINTED*/
	ip = (struct dinode *)cptr;
	if ((ip->di_mode & IFMT) == 0) {
		if (!override) {
			printf("inode not allocated\n");
			error++;
			return (0);
		}
		blocksize = filesize = 0;
	} else {
		trapped++;
		filesize = ip->di_size;
		blocksize = filesize * 2;
	}
	return (ip->di_mode);
}

/*
 * getdirslot - get the address of the directory slot desired.
 */
static u_offset_t
getdirslot(long slot)
{
	char		*cptr;
	struct direct	*dirp;
	short		i;
	char		*string = &scratch[0];
	short		bod = 0, mode, temp;

	if (slot < 0) {
		slot = 0;
		bod++;
	}
	if (type != DIRECTORY) {
		if (type == BLOCK)
			string = "block";
		else
			string = "fragment";
		addr = bod_addr;
		if ((cptr = getblk(addr)) == 0)
			return (0);
		cptr += blkoff(fs, addr);
		cur_bytes = 0;
		/*LINTED*/
		dirp = (struct direct *)cptr;
		for (dirslot = 0; dirslot < slot; dirslot++) {
			/*LINTED*/
			dirp = (struct direct *)cptr;
			if (blocksize > filesize) {
				if (cur_bytes + (long)dirp->d_reclen >=
								filesize) {
					printf("end of file\n");
					erraddr = addr;
					errcur_bytes = cur_bytes;
					stringsize = STRINGSIZE(dirp);
					error++;
					return (addr);
				}
			} else {
				if (cur_bytes + (long)dirp->d_reclen >=
								blocksize) {
					printf("end of %s\n", string);
					erraddr = addr;
					errcur_bytes = cur_bytes;
					stringsize = STRINGSIZE(dirp);
					error++;
					return (addr);
				}
			}
			cptr += dirp->d_reclen;
			addr += dirp->d_reclen;
			cur_bytes += dirp->d_reclen;
		}
		if (bod) {
			if (blocksize > filesize)
				printf("beginning of file\n");
			else
				printf("beginning of %s\n", string);
			erraddr = addr;
			errcur_bytes = cur_bytes;
			error++;
		}
		stringsize = STRINGSIZE(dirp);
		return (addr);
	} else {
		addr = cur_ino;
		if ((mode = icheck(addr)) == 0)
			return (0);
		if (!override && (mode & IFDIR) == 0) {
			printf("inode is not a directory\n");
			error++;
			return (0);
		}
		temp = slot;
		i = cur_bytes = 0;
		for (;;) {
			if (i == 0 || bcomp(addr)) {
				error = 0;
				if ((addr = (bmap((long)i++) << FRGSHIFT)) == 0)
					break;
				if ((cptr = getblk(addr)) == 0)
					break;
				cptr += blkoff(fs, addr);
			}
			/*LINTED*/
			dirp = (struct direct *)cptr;
			value = dirp->d_ino;
			if (!temp--)
				break;
			if (cur_bytes + (long)dirp->d_reclen >= filesize) {
				printf("end of file\n");
				dirslot = slot - temp - 1;
				objsz = DIRECTORY;
				erraddr = addr;
				errcur_bytes = cur_bytes;
				stringsize = STRINGSIZE(dirp);
				error++;
				return (addr);
			}
			addr += dirp->d_reclen;
			cptr += dirp->d_reclen;
			cur_bytes += dirp->d_reclen;
		}
		dirslot = slot;
		objsz = DIRECTORY;
		if (bod) {
			printf("beginning of file\n");
			erraddr = addr;
			errcur_bytes = cur_bytes;
			error++;
		}
		stringsize = STRINGSIZE(dirp);
		return (addr);
	}
}


/*
 * getshadowslot - get the address of the shadow data desired
 */
static int
getshadowslot(long shadow)
{
	struct ufs_fsd		fsd;
	short			bod = 0, mode;
	long			taddr, tcurbytes;

	if (shadow < 0) {
		shadow = 0;
		bod++;
	}
	if (type != SHADOW_DATA) {
		if (shadow < cur_shad) {
			printf("can't scan shadow data in reverse\n");
			error++;
			return (0);
		}
	} else {
		addr = cur_ino;
		if ((mode = icheck(addr)) == 0)
			return (0);
		if (!override && (mode & IFMT) != IFSHAD) {
			printf("inode is not a shadow\n");
			error++;
			return (0);
		}
		cur_bytes = 0;
		cur_shad = 0;
		syncshadowscan(1);	/* force synchronization */
	}

	for (; cur_shad < shadow; cur_shad++) {
		taddr = addr;
		tcurbytes = cur_bytes;
		getshadowdata((long *)&fsd, LONG + LONG);
		addr = taddr;
		cur_bytes = tcurbytes;
		if (cur_bytes + (long)fsd.fsd_size > filesize) {
			syncshadowscan(0);
			printf("end of file\n");
			erraddr = addr;
			errcur_bytes = cur_bytes;
			error++;
			return (addr);
		}
		addr += fsd.fsd_size;
		cur_bytes += fsd.fsd_size;
		syncshadowscan(0);
	}
	if (type == SHADOW_DATA)
		objsz = SHADOW_DATA;
	if (bod) {
		printf("beginning of file\n");
		erraddr = addr;
		errcur_bytes = cur_bytes;
		error++;
	}
	return (addr);
}

static void
getshadowdata(long *buf, int len)
{
	long	tfsd;

	len /= LONG;
	for (tfsd = 0; tfsd < len; tfsd++) {
		buf[tfsd] = get(SHADOW_DATA);
		addr += LONG;
		cur_bytes += LONG;
		syncshadowscan(0);
	}
}

static void
syncshadowscan(int force)
{
	long	curblkoff;
	if (type == SHADOW_DATA && (force ||
	    lblkno(fs, addr) != (bhdr.fwd)->blkno)) {
		curblkoff = blkoff(fs, cur_bytes);
		addr = bmap(lblkno(fs, cur_bytes)) << FRGSHIFT;
		addr += curblkoff;
		cur_bytes += curblkoff;
		(void) getblk(addr);
		objsz = SHADOW_DATA;
	}
}



/*
 * putf - print a byte as an ascii character if possible.
 *	The exceptions are tabs, newlines, backslashes
 *	and nulls which are printed as the standard C
 *	language escapes. Characters which are not
 *	recognized are printed as \?.
 */
static void
putf(char c)
{

	if (c <= 037 || c >= 0177 || c == '\\') {
		printf("\\");
		switch (c) {
		case '\\':
			printf("\\");
			break;
		case '\t':
			printf("t");
			break;
		case '\n':
			printf("n");
			break;
		case '\0':
			printf("0");
			break;
		default:
			printf("?");
		}
	} else {
		printf("%c", c);
		printf(" ");
	}
}

/*
 * put - write an item into the buffer for the current address
 *	block.  The value is checked to make sure that it will
 *	fit in the size given without truncation.  If successful,
 *	the entire block is written back to the file system.
 */
static void
put(u_offset_t item, short lngth)
{

	char	*bptr, *sbptr;
	long	s_err, nbytes;
	long	olditem;

	if (wrtflag == O_RDONLY) {
		printf("not opened for write '-w'\n");
		error++;
		return;
	}
	objsz = lngth;
	if ((sbptr = getblk(addr)) == 0)
		return;
	bptr = sbptr + blkoff(fs, addr);
	switch (objsz) {
	case LONG:
	case DIRECTORY:
		/*LINTED*/
		olditem = *(long *)bptr;
		/*LINTED*/
		*(long *)bptr = item;
		break;
	case SHORT:
	case INODE:
		/*LINTED*/
		olditem = (long)*(short *)bptr;
		item &= 0177777L;
		/*LINTED*/
		*(short *)bptr = item;
		break;
	case CHAR:
		olditem = (long)*bptr;
		item &= 0377;
		*bptr = lobyte(loword(item));
		break;
	default:
		error++;
		return;
	}
	if ((s_err = llseek(fd, (offset_t)(addr & fs->fs_bmask), 0)) == -1) {
		error++;
		printf("seek error : %" PRIx64 "\n", addr);
		return;
	}
	if ((nbytes = write(fd, sbptr, BLKSIZE)) != BLKSIZE) {
		error++;
		printf("write error : addr   = %" PRIx64 "\n", addr);
		printf("            : s_err  = %lx\n", s_err);
		printf("            : nbytes = %lx\n", nbytes);
		return;
	}
	if (!acting_on_inode && objsz != INODE && objsz != DIRECTORY) {
		index(base);
		print(olditem, 8, -8, 0);
		printf("\t=\t");
		print(item, 8, -8, 0);
		printf("\n");
	} else {
		if (objsz == DIRECTORY) {
			addr = cur_dir;
			fprnt('?', 'd');
		} else {
			addr = cur_ino;
			objsz = INODE;
			fprnt('?', 'i');
		}
	}
}

/*
 * getblk - check if the desired block is in the file system.
 *	Search the incore buffers to see if the block is already
 *	available. If successful, unlink the buffer control block
 *	from its position in the buffer list and re-insert it at
 *	the head of the list.  If failure, use the last buffer
 *	in the list for the desired block. Again, this control
 *	block is placed at the head of the list. This process
 *	will leave commonly requested blocks in the in-core buffers.
 *	Finally, a pointer to the buffer is returned.
 */
static char *
getblk(u_offset_t address)
{

	struct lbuf	*bp;
	long		s_err, nbytes;
	unsigned long	block;

	read_requests++;
	block = lblkno(fs, address);
	if (block >= fragstoblks(fs, fs->fs_size)) {
		printf("cannot read block %lu\n", block);
		error++;
		return (0);
	}
	for (bp = bhdr.fwd; bp != &bhdr; bp = bp->fwd)
		if (bp->valid && bp->blkno == block)
			goto xit;
	actual_disk_reads++;
	bp = bhdr.back;
	bp->blkno = block;
	bp->valid = 0;
	if ((s_err = llseek(fd, (offset_t)(address & fs->fs_bmask), 0)) == -1) {
		error++;
		printf("seek error : %" PRIx64 "\n", address);
		return (0);
	}
	if ((nbytes = read(fd, bp->blkaddr, BLKSIZE)) != BLKSIZE) {
		error++;
		printf("read error : addr   = %" PRIx64 "\n", address);
		printf("           : s_err  = %lx\n", s_err);
		printf("           : nbytes = %lx\n", nbytes);
		return (0);
	}
	bp->valid++;
xit:	bp->back->fwd = bp->fwd;
	bp->fwd->back = bp->back;
	insert(bp);
	return (bp->blkaddr);
}

/*
 * insert - place the designated buffer control block
 *	at the head of the linked list of buffers.
 */
static void
insert(struct lbuf *bp)
{

	bp->back = &bhdr;
	bp->fwd = bhdr.fwd;
	bhdr.fwd->back = bp;
	bhdr.fwd = bp;
}

/*
 * err - called on interrupts.  Set the current address
 *	back to the last address stored in erraddr. Reset all
 *	appropriate flags.  A reset call is made to return
 *	to the main loop;
 */
#ifdef sun
/*ARGSUSED*/
static void
err(int sig)
#else
err()
#endif /* sun */
{
	freemem(filenames, nfiles);
	nfiles = 0;
	(void) signal(2, err);
	addr = erraddr;
	cur_ino = errino;
	cur_inum = errinum;
	cur_bytes = errcur_bytes;
	error = 0;
	c_count = 0;
	printf("\n?\n");
	(void) fseek(stdin, 0L, 2);
	longjmp(env, 0);
}

/*
 * devcheck - check that the given mode represents a
 *	special device. The IFCHR bit is on for both
 *	character and block devices.
 */
static int
devcheck(short md)
{
	if (override)
		return (0);
	switch (md & IFMT) {
	case IFCHR:
	case IFBLK:
		return (0);
	}

	printf("not character or block device\n");
	error++;
	return (1);
}

/*
 * nullblk - return error if address is zero.  This is done
 *	to prevent block 0 from being used as an indirect block
 *	for a large file or as a data block for a small file.
 */
static int
nullblk(long bn)
{
	if (bn != 0)
		return (0);
	printf("non existent block\n");
	error++;
	return (1);
}

/*
 * puta - put ascii characters into a buffer.  The string
 *	terminates with a quote or newline.  The leading quote,
 *	which is optional for directory names, was stripped off
 *	by the assignment case in the main loop.
 */
static void
puta()
{
	char		*cptr, c;
	int		i;
	char		*sbptr;
	short		terror = 0;
	long		maxchars, s_err, nbytes, temp;
	u_offset_t	taddr = addr;
	long		tcount = 0, item, olditem = 0;

	if (wrtflag == O_RDONLY) {
		printf("not opened for write '-w'\n");
		error++;
		return;
	}
	if ((sbptr = getblk(addr)) == 0)
		return;
	cptr = sbptr + blkoff(fs, addr);
	if (objsz == DIRECTORY) {
		if (acting_on_directory)
			maxchars = stringsize - 1;
		else
			maxchars = LONG;
	} else if (objsz == INODE)
		maxchars = objsz - (addr - cur_ino);
	else
		maxchars = min(blocksize - cur_bytes, filesize - cur_bytes);
	while ((c = getachar()) != '"') {
		if (tcount >= maxchars) {
			printf("string too long\n");
			if (objsz == DIRECTORY)
				addr = cur_dir;
			else if (acting_on_inode || objsz == INODE)
				addr = cur_ino;
			else
				addr = taddr;
			erraddr = addr;
			errcur_bytes = cur_bytes;
			terror++;
			break;
		}
		tcount++;
		if (c == '\n') {
			ungetachar(c);
			break;
		}
		temp = (long)*cptr;
		olditem <<= BITSPERCHAR;
		olditem += temp & 0xff;
		if (c == '\\') {
			switch (c = getachar()) {
			case 't':
				*cptr++ = '\t';
				break;
			case 'n':
				*cptr++ = '\n';
				break;
			case '0':
				*cptr++ = '\0';
				break;
			default:
				*cptr++ = c;
				break;
			}
		}
		else
			*cptr++ = c;
	}
	if (objsz == DIRECTORY && acting_on_directory)
		for (i = tcount; i <= maxchars; i++)
			*cptr++ = '\0';
	if ((s_err = llseek(fd, (offset_t)(addr & fs->fs_bmask), 0)) == -1) {
		error++;
		printf("seek error : %" PRIx64 "\n", addr);
		return;
	}
	if ((nbytes = write(fd, sbptr, BLKSIZE)) != BLKSIZE) {
		error++;
		printf("write error : addr   = %" PRIx64 "\n", addr);
		printf("            : s_err  = %lx\n", s_err);
		printf("            : nbytes = %lx\n", nbytes);
		return;
	}
	if (!acting_on_inode && objsz != INODE && objsz != DIRECTORY) {
		addr += tcount;
		cur_bytes += tcount;
		taddr = addr;
		if (objsz != CHAR) {
			addr &= ~(objsz - 1);
			cur_bytes -= taddr - addr;
		}
		if (addr == taddr) {
			addr -= objsz;
			taddr = addr;
		}
		tcount = LONG - (taddr - addr);
		index(base);
		if ((cptr = getblk(addr)) == 0)
			return;
		cptr += blkoff(fs, addr);
		switch (objsz) {
		case LONG:
			/*LINTED*/
			item = *(long *)cptr;
			if (tcount < LONG) {
				olditem <<= tcount * BITSPERCHAR;
				temp = 1;
				for (i = 0; i < (tcount*BITSPERCHAR); i++)
					temp <<= 1;
				olditem += item & (temp - 1);
			}
			break;
		case SHORT:
			/*LINTED*/
			item = (long)*(short *)cptr;
			if (tcount < SHORT) {
				olditem <<= tcount * BITSPERCHAR;
				temp = 1;
				for (i = 0; i < (tcount * BITSPERCHAR); i++)
					temp <<= 1;
				olditem += item & (temp - 1);
			}
			olditem &= 0177777L;
			break;
		case CHAR:
			item = (long)*cptr;
			olditem &= 0377;
		}
		print(olditem, 8, -8, 0);
		printf("\t=\t");
		print(item, 8, -8, 0);
		printf("\n");
	} else {
		if (objsz == DIRECTORY) {
			addr = cur_dir;
			fprnt('?', 'd');
		} else {
			addr = cur_ino;
			objsz = INODE;
			fprnt('?', 'i');
		}
	}
	if (terror)
		error++;
}

/*
 * fprnt - print data.  'count' elements are printed where '*' will
 *	print an entire blocks worth or up to the eof, whichever
 *	occurs first.  An error will occur if crossing a block boundary
 *	is attempted since consecutive blocks don't usually have
 *	meaning.  Current print types:
 *		/		b   - print as bytes (base sensitive)
 *				c   - print as characters
 *				o O - print as octal shorts (longs)
 *				d D - print as decimal shorts (longs)
 *				x X - print as hexadecimal shorts (longs)
 *		?		c   - print as cylinder groups
 *				d   - print as directories
 *				i   - print as inodes
 *				s   - print as super blocks
 *				S   - print as shadow data
 */
static void
fprnt(char style, char po)
{
	int		i;
	struct fs	*sb;
	struct cg	*cg;
	struct direct	*dirp;
	struct dinode	*ip;
	int		tbase;
	char		c, *cptr, *p;
	long		tinode, tcount, temp;
	u_offset_t	taddr;
	short		offset, mode, end = 0, eof = 0, eof_flag;
	unsigned short	*sptr;
	unsigned long	*lptr;
	offset_t	curoff, curioff;

	laststyle = style;
	lastpo = po;
	should_print = 0;
	if (count != 1) {
		if (clear) {
			count = 1;
			star = 0;
			clear = 0;
		} else
			clear = 1;
	}
	tcount = count;
	offset = blkoff(fs, addr);

	if (style == '/') {
		if (type == NUMB)
			eof_flag = 0;
		else
			eof_flag = 1;
		switch (po) {

		case 'c': /* print as characters */
		case 'b': /* or bytes */
			if ((cptr = getblk(addr)) == 0)
				return;
			cptr += offset;
			objsz = CHAR;
			tcount = check_addr(eof_flag, &end, &eof, 0);
			if (tcount) {
				for (i = 0; tcount--; i++) {
					if (i % 16 == 0) {
						if (i)
							printf("\n");
						index(base);
					}
					if (po == 'c') {
						putf(*cptr++);
						if ((i + 1) % 16)
							printf("  ");
					} else {
						if ((i + 1) % 16 == 0)
							print(*cptr++ & 0377L,
								2, -2, 0);
						else
							print(*cptr++ & 0377L,
								4, -2, 0);
					}
					addr += CHAR;
					cur_bytes += CHAR;
				}
				printf("\n");
			}
			addr -= CHAR;
			erraddr = addr;
			cur_bytes -= CHAR;
			errcur_bytes = cur_bytes;
			if (eof) {
				printf("end of file\n");
				error++;
			} else if (end) {
				if (type == BLOCK)
					printf("end of block\n");
				else
					printf("end of fragment\n");
				error++;
			}
			return;

		case 'o': /* print as octal shorts */
			tbase = OCTAL;
			goto otx;
		case 'd': /* print as decimal shorts */
			tbase = DECIMAL;
			goto otx;
		case 'x': /* print as hex shorts */
			tbase = HEX;
otx:
			if ((cptr = getblk(addr)) == 0)
				return;
			taddr = addr;
			addr &= ~(SHORT - 1);
			cur_bytes -= taddr - addr;
			cptr += blkoff(fs, addr);
			/*LINTED*/
			sptr = (unsigned short *)cptr;
			objsz = SHORT;
			tcount = check_addr(eof_flag, &end, &eof, 0);
			if (tcount) {
				for (i = 0; tcount--; i++) {
					sptr = (unsigned short *)print_check(
							/*LINTED*/
							(unsigned long *)sptr,
							&tcount, tbase, i);
					switch (po) {
					case 'o':
						printf("%06o ", *sptr++);
						break;
					case 'd':
						printf("%05d  ", *sptr++);
						break;
					case 'x':
						printf("%04x   ", *sptr++);
					}
					addr += SHORT;
					cur_bytes += SHORT;
				}
				printf("\n");
			}
			addr -= SHORT;
			erraddr = addr;
			cur_bytes -= SHORT;
			errcur_bytes = cur_bytes;
			if (eof) {
				printf("end of file\n");
				error++;
			} else if (end) {
				if (type == BLOCK)
					printf("end of block\n");
				else
					printf("end of fragment\n");
				error++;
			}
			return;

		case 'O': /* print as octal longs */
			tbase = OCTAL;
			goto OTX;
		case 'D': /* print as decimal longs */
			tbase = DECIMAL;
			goto OTX;
		case 'X': /* print as hex longs */
			tbase = HEX;
OTX:
			if ((cptr = getblk(addr)) == 0)
				return;
			taddr = addr;
			addr &= ~(LONG - 1);
			cur_bytes -= taddr - addr;
			cptr += blkoff(fs, addr);
			/*LINTED*/
			lptr = (unsigned long *)cptr;
			objsz = LONG;
			tcount = check_addr(eof_flag, &end, &eof, 0);
			if (tcount) {
				for (i = 0; tcount--; i++) {
					lptr = print_check(lptr, &tcount,
								tbase, i);
					switch (po) {
					case 'O':
						printf("%011lo    ", *lptr++);
						break;
					case 'D':
						printf("%010lu     ", *lptr++);
						break;
					case 'X':
						printf("%08lx       ", *lptr++);
					}
					addr += LONG;
					cur_bytes += LONG;
				}
				printf("\n");
			}
			addr -= LONG;
			erraddr = addr;
			cur_bytes -= LONG;
			errcur_bytes = cur_bytes;
			if (eof) {
				printf("end of file\n");
				error++;
			} else if (end) {
				if (type == BLOCK)
					printf("end of block\n");
				else
					printf("end of fragment\n");
				error++;
			}
			return;

		default:
			error++;
			printf("no such print option\n");
			return;
		}
	} else
		switch (po) {

		case 'c': /* print as cylinder group */
			if (type != NUMB)
				if (cur_cgrp + count > fs->fs_ncg) {
					tcount = fs->fs_ncg - cur_cgrp;
					if (!star)
						end++;
				}
			addr &= ~(LONG - 1);
			for (/* void */; tcount--; /* void */) {
				erraddr = addr;
				errcur_bytes = cur_bytes;
				if (type != NUMB) {
					addr = cgtod(fs, cur_cgrp)
						<< FRGSHIFT;
					cur_cgrp++;
				}
				if ((cptr = getblk(addr)) == 0) {
					if (cur_cgrp)
						cur_cgrp--;
					return;
				}
				cptr += blkoff(fs, addr);
				/*LINTED*/
				cg = (struct cg *)cptr;
				if (type == NUMB) {
					cur_cgrp = cg->cg_cgx + 1;
					type = objsz = CGRP;
					if (cur_cgrp + count - 1 > fs->fs_ncg) {
						tcount = fs->fs_ncg - cur_cgrp;
						if (!star)
							end++;
					}
				}
				if (! override && !cg_chkmagic(cg)) {
					printf("invalid cylinder group ");
					printf("magic word\n");
					if (cur_cgrp)
						cur_cgrp--;
					error++;
					return;
				}
				printcg(cg);
				if (tcount)
					printf("\n");
			}
			cur_cgrp--;
			if (end) {
				printf("end of cylinder groups\n");
				error++;
			}
			return;

		case 'd': /* print as directories */
			if ((cptr = getblk(addr)) == 0)
				return;
			if (type == NUMB) {
				if (fragoff(fs, addr)) {
					printf("address must be at the ");
					printf("beginning of a fragment\n");
					error++;
					return;
				}
				bod_addr = addr;
				type = FRAGMENT;
				dirslot = 0;
				cur_bytes = 0;
				blocksize = FRGSIZE;
				filesize = FRGSIZE * 2;
			}
			cptr += offset;
			objsz = DIRECTORY;
			while (tcount-- && cur_bytes < filesize &&
				cur_bytes < blocksize && !bcomp(addr)) {
				/*LINTED*/
				dirp = (struct direct *)cptr;
				tinode = dirp->d_ino;
				printf("i#: ");
				if (tinode == 0)
					printf("free\t");
				else
					print(tinode, 12, -8, 0);
				printf("%s\n", &dirp->d_name[0]);
				erraddr = addr;
				errcur_bytes = cur_bytes;
				addr += dirp->d_reclen;
				cptr += dirp->d_reclen;
				cur_bytes += dirp->d_reclen;
				dirslot++;
				stringsize = STRINGSIZE(dirp);
			}
			addr = erraddr;
			cur_dir = addr;
			cur_bytes = errcur_bytes;
			dirslot--;
			if (tcount >= 0 && !star) {
				switch (type) {
				case FRAGMENT:
					printf("end of fragment\n");
					break;
				case BLOCK:
					printf("end of block\n");
					break;
				default:
					printf("end of directory\n");
				}
				error++;
			} else
				error = 0;
			return;

		case 'i': /* print as inodes */
			/*LINTED*/
			if ((ip = (struct dinode *)getblk(addr)) == 0)
				return;
			for (i = 1; i < fs->fs_ncg; i++)
				if (addr < (cgimin(fs, i) << FRGSHIFT))
					break;
			i--;
			offset /= INODE;
			temp = (addr - (cgimin(fs, i) << FRGSHIFT)) >> FRGSHIFT;
			temp = (i * fs->fs_ipg) + fragstoblks(fs, temp) *
							INOPB(fs) + offset;
			if (count + offset > INOPB(fs)) {
				tcount = INOPB(fs) - offset;
				if (!star)
					end++;
			}
			objsz = INODE;
			ip += offset;
			for (i = 0; tcount--; ip++, temp++) {
				if ((mode = icheck(addr)) == 0)
					if (!override)
						continue;
				p = " ugtrwxrwxrwx";

				switch (mode & IFMT) {
				case IFDIR:
					c = 'd';
					break;
				case IFCHR:
					c = 'c';
					break;
				case IFBLK:
					c = 'b';
					break;
				case IFREG:
					c = '-';
					break;
				case IFLNK:
					c = 'l';
					break;
				case IFSOCK:
					c = 's';
					break;
				case IFSHAD:
					c = 'S';
					break;
				case IFATTRDIR:
					c = 'A';
					break;
				default:
					c = '?';
					if (!override)
						goto empty;

				}
				printf("i#: ");
				print(temp, 12, -8, 0);
				printf("   md: ");
				printf("%c", c);
				for (mode = mode << 4; *++p; mode = mode << 1) {
					if (mode & IFREG)
						printf("%c", *p);
					else
						printf("-");
				}
				printf("  uid: ");
				print(ip->di_uid, 8, -4, 0);
				printf("      gid: ");
				print(ip->di_gid, 8, -4, 0);
				printf("\n");
				printf("ln: ");
				print((long)ip->di_nlink, 8, -4, 0);
				printf("       bs: ");
				print(ip->di_blocks, 12, -8, 0);
				printf("c_flags : ");
				print(ip->di_cflags, 12, -8, 0);
				printf("   sz : ");
#ifdef _LARGEFILE64_SOURCE
				printll(ip->di_size, 20, -16, 0);
#else /* !_LARGEFILE64_SOURCE */
				print(ip->di_size, 12, -8, 0);
#endif /* _LARGEFILE64_SOURCE */
				if (ip->di_shadow) {
					printf("   si: ");
					print(ip->di_shadow, 12, -8, 0);
				}
				printf("\n");
				if (ip->di_oeftflag) {
					printf("ai: ");
					print(ip->di_oeftflag, 12, -8, 0);
					printf("\n");
				}
				printf("\n");
				switch (ip->di_mode & IFMT) {
				case IFBLK:
				case IFCHR:
					printf("maj: ");
					print(major(ip->di_ordev), 4, -2, 0);
					printf("  min: ");
					print(minor(ip->di_ordev), 4, -2, 0);
					printf("\n");
					break;
				default:
					/*
					 * only display blocks below the
					 * current file size
					 */
					curoff = 0LL;
					for (i = 0; i < NDADDR; ) {
						if (ip->di_size <= curoff)
							break;
						printf("db#%x: ", i);
						print(ip->di_db[i], 11, -8, 0);

						if (++i % 4 == 0)
							printf("\n");
						else
							printf("  ");
						curoff += fs->fs_bsize;
					}
					if (i % 4)
						printf("\n");

					/*
					 * curioff keeps track of the number
					 * of bytes covered by each indirect
					 * pointer in the inode, and is added
					 * to curoff each time to get the
					 * actual offset into the file.
					 */
					curioff = fs->fs_bsize *
					    (fs->fs_bsize / sizeof (daddr_t));
					for (i = 0; i < NIADDR; i++) {
						if (ip->di_size <= curoff)
							break;
						printf("ib#%x: ", i);
						print(ip->di_ib[i], 11, -8, 0);
						printf("  ");
						curoff += curioff;
						curioff *= (fs->fs_bsize /
						    sizeof (daddr_t));
					}
					if (i)
						printf("\n");
					break;
				}
				if (count == 1) {
					time_t t;

					t = ip->di_atime;
					printf("\taccessed: %s", ctime(&t));
					t = ip->di_mtime;
					printf("\tmodified: %s", ctime(&t));
					t = ip->di_ctime;
					printf("\tcreated : %s", ctime(&t));
				}
				if (tcount)
					printf("\n");
empty:
				if (c == '?' && !override) {
					printf("i#: ");
					print(temp, 12, -8, 0);
					printf("  is unallocated\n");
					if (count != 1)
						printf("\n");
				}
				cur_ino = erraddr = addr;
				errcur_bytes = cur_bytes;
				cur_inum++;
				addr = addr + INODE;
			}
			addr = erraddr;
			cur_bytes = errcur_bytes;
			cur_inum--;
			if (end) {
				printf("end of block\n");
				error++;
			}
			return;

		case 's': /* print as super block */
			if (cur_cgrp == -1) {
				addr = SBLOCK * DEV_BSIZE;
				type = NUMB;
			}
			addr &= ~(LONG - 1);
			if (type != NUMB)
				if (cur_cgrp + count > fs->fs_ncg) {
					tcount = fs->fs_ncg - cur_cgrp;
					if (!star)
						end++;
				}
			for (/* void */; tcount--; /* void */) {
				erraddr = addr;
				cur_bytes = errcur_bytes;
				if (type != NUMB) {
					addr = cgsblock(fs, cur_cgrp)
							<< FRGSHIFT;
					cur_cgrp++;
				}
				if ((cptr = getblk(addr)) == 0) {
					if (cur_cgrp)
						cur_cgrp--;
					return;
				}
				cptr += blkoff(fs, addr);
				/*LINTED*/
				sb = (struct fs *)cptr;
				if (type == NUMB) {
					for (i = 0; i < fs->fs_ncg; i++)
						if (addr == cgsblock(fs, i) <<
								FRGSHIFT)
							break;
					if (i == fs->fs_ncg)
						cur_cgrp = 0;
					else
						cur_cgrp = i + 1;
					type = objsz = SB;
					if (cur_cgrp + count - 1 > fs->fs_ncg) {
						tcount = fs->fs_ncg - cur_cgrp;
						if (!star)
							end++;
					}
				}
				if ((sb->fs_magic != FS_MAGIC) &&
				    (sb->fs_magic != MTB_UFS_MAGIC)) {
					cur_cgrp = 0;
					if (!override) {
						printf("invalid super block ");
						printf("magic word\n");
						cur_cgrp--;
						error++;
						return;
					}
				}
				if (sb->fs_magic == FS_MAGIC &&
				    (sb->fs_version !=
					UFS_EFISTYLE4NONEFI_VERSION_2 &&
				    sb->fs_version != UFS_VERSION_MIN)) {
					cur_cgrp = 0;
					if (!override) {
						printf("invalid super block ");
						printf("version number\n");
						cur_cgrp--;
						error++;
						return;
					}
				}
				if (sb->fs_magic == MTB_UFS_MAGIC &&
				    (sb->fs_version > MTB_UFS_VERSION_1 ||
				    sb->fs_version < MTB_UFS_VERSION_MIN)) {
					cur_cgrp = 0;
					if (!override) {
						printf("invalid super block ");
						printf("version number\n");
						cur_cgrp--;
						error++;
						return;
					}
				}
				if (cur_cgrp == 0)
					printf("\tsuper block:\n");
				else {
					printf("\tsuper block in cylinder ");
					printf("group ");
					print(cur_cgrp - 1, 0, 0, 0);
					printf(":\n");
				}
				printsb(sb);
				if (tcount)
					printf("\n");
			}
			cur_cgrp--;
			if (end) {
				printf("end of super blocks\n");
				error++;
			}
			return;

		case 'S': /* print as shadow data */
			if (type == NUMB) {
				type = FRAGMENT;
				cur_shad = 0;
				cur_bytes = fragoff(fs, addr);
				bod_addr = addr - cur_bytes;
				/* no more than two fragments */
				filesize = fragroundup(fs,
				    bod_addr + FRGSIZE + 1);
			}
			objsz = SHADOW_DATA;
			while (tcount-- &&
			    (cur_bytes + SHADOW_DATA) <= filesize &&
			    (type != SHADOW_DATA ||
			    (cur_bytes + SHADOW_DATA)) <= blocksize) {
				/*LINTED*/
				struct ufs_fsd fsd;
				long tcur_bytes;

				taddr = addr;
				tcur_bytes = cur_bytes;
				index(base);
				getshadowdata((long *)&fsd, LONG + LONG);
				printf("  type: ");
				print((long)fsd.fsd_type, 8, -8, 0);
				printf("  size: ");
				print((long)fsd.fsd_size, 8, -8, 0);
				tbase = fsd.fsd_size - LONG - LONG;
				if (tbase > 256)
					tbase = 256;
				for (i = 0; i < tbase; i++) {
					if (i % LONG == 0) {
						if (i % 16 == 0) {
							printf("\n");
							index(base);
						} else
							printf("  ");
						getshadowdata(&temp, LONG);
						p = (char *)&temp;
					} else
						printf(" ");
					printf("%02x", (int)(*p++ & 0377L));
				}
				printf("\n");
				addr = taddr;
				cur_bytes = tcur_bytes;
				erraddr = addr;
				errcur_bytes = cur_bytes;
				addr += FSD_RECSZ((&fsd), fsd.fsd_size);
				cur_bytes += FSD_RECSZ((&fsd), fsd.fsd_size);
				cur_shad++;
				syncshadowscan(0);
			}
			addr = erraddr;
			cur_bytes = errcur_bytes;
			cur_shad--;
			if (tcount >= 0 && !star) {
				switch (type) {
				case FRAGMENT:
					printf("end of fragment\n");
					break;
				default:
					printf("end of shadow data\n");
				}
				error++;
			} else
				error = 0;
			return;
		default:
			error++;
			printf("no such print option\n");
			return;
		}
}

/*
 * valid_addr - call check_addr to validate the current address.
 */
static int
valid_addr()
{
	short	end = 0, eof = 0;
	long	tcount = count;

	if (!trapped)
		return (1);
	if (cur_bytes < 0) {
		cur_bytes = 0;
		if (blocksize > filesize) {
			printf("beginning of file\n");
		} else {
			if (type == BLOCK)
				printf("beginning of block\n");
			else
				printf("beginning of fragment\n");
		}
		error++;
		return (0);
	}
	count = 1;
	(void) check_addr(1, &end, &eof, (filesize < blocksize));
	count = tcount;
	if (eof) {
		printf("end of file\n");
		error++;
		return (0);
	}
	if (end == 2) {
		if (erraddr > addr) {
			if (type == BLOCK)
				printf("beginning of block\n");
			else
				printf("beginning of fragment\n");
			error++;
			return (0);
		}
	}
	if (end) {
		if (type == BLOCK)
			printf("end of block\n");
		else
			printf("end of fragment\n");
		error++;
		return (0);
	}
	return (1);
}

/*
 * check_addr - check if the address crosses the end of block or
 *	end of file.  Return the proper count.
 */
static int
check_addr(short eof_flag, short *end, short *eof, short keep_on)
{
	long	temp, tcount = count, tcur_bytes = cur_bytes;
	u_offset_t	taddr = addr;

	if (bcomp(addr + count * objsz - 1) ||
	    (keep_on && taddr < (bmap(cur_block) << FRGSHIFT))) {
		error = 0;
		addr = taddr;
		cur_bytes = tcur_bytes;
		if (keep_on) {
			if (addr < erraddr) {
				if (cur_bytes < 0) {
					(*end) = 2;
					return (0);	/* Value ignored */
				}
				temp = cur_block - lblkno(fs, cur_bytes);
				cur_block -= temp;
				if ((addr = bmap(cur_block) << FRGSHIFT) == 0) {
					cur_block += temp;
					return (0);	/* Value ignored */
				}
				temp = tcur_bytes - cur_bytes;
				addr += temp;
				cur_bytes += temp;
				return (0);	/* Value ignored */
			} else {
				if (cur_bytes >= filesize) {
					(*eof)++;
					return (0);	/* Value ignored */
				}
				temp = lblkno(fs, cur_bytes) - cur_block;
				cur_block += temp;
				if ((addr = bmap(cur_block) << FRGSHIFT) == 0) {
					cur_block -= temp;
					return (0);	/* Value ignored */
				}
				temp = tcur_bytes - cur_bytes;
				addr += temp;
				cur_bytes += temp;
				return (0);	/* Value ignored */
			}
		}
		tcount = (blkroundup(fs, addr+1)-addr) / objsz;
		if (!star)
			(*end) = 2;
	}
	addr = taddr;
	cur_bytes = tcur_bytes;
	if (eof_flag) {
		if (blocksize > filesize) {
			if (cur_bytes >= filesize) {
				tcount = 0;
				(*eof)++;
			} else if (tcount > (filesize - cur_bytes) / objsz) {
				tcount = (filesize - cur_bytes) / objsz;
				if (!star || tcount == 0)
					(*eof)++;
			}
		} else {
			if (cur_bytes >= blocksize) {
				tcount = 0;
				(*end)++;
			} else if (tcount > (blocksize - cur_bytes) / objsz) {
				tcount = (blocksize - cur_bytes) / objsz;
				if (!star || tcount == 0)
					(*end)++;
			}
		}
	}
	return (tcount);
}

/*
 * print_check - check if the index needs to be printed and delete
 *	rows of zeros from the output.
 */
unsigned long *
print_check(unsigned long *lptr, long *tcount, short tbase, int i)
{
	int		j, k, temp = BYTESPERLINE / objsz;
	short		first_time = 0;
	unsigned long	*tlptr;
	unsigned short	*tsptr, *sptr;

	sptr = (unsigned short *)lptr;
	if (i == 0)
		first_time = 1;
	if (i % temp == 0) {
		if (*tcount >= temp - 1) {
			if (objsz == SHORT)
				tsptr = sptr;
			else
				tlptr = lptr;
			k = *tcount - 1;
			for (j = i; k--; j++)
				if (objsz == SHORT) {
					if (*tsptr++ != 0)
						break;
				} else {
					if (*tlptr++ != 0)
						break;
				}
			if (j > (i + temp - 1)) {
				j = (j - i) / temp;
				while (j-- > 0) {
					if (objsz == SHORT)
						sptr += temp;
					else
						lptr += temp;
					*tcount -= temp;
					i += temp;
					addr += BYTESPERLINE;
					cur_bytes += BYTESPERLINE;
				}
				if (first_time)
					printf("*");
				else
					printf("\n*");
			}
			if (i)
				printf("\n");
			index(tbase);
		} else {
			if (i)
				printf("\n");
			index(tbase);
		}
	}
	if (objsz == SHORT)
		/*LINTED*/
		return ((unsigned long *)sptr);
	else
		return (lptr);
}

/*
 * index - print a byte index for the printout in base b
 *	with leading zeros.
 */
static void
index(int b)
{
	int	tbase = base;

	base = b;
	print(addr, 8, 8, 1);
	printf(":\t");
	base = tbase;
}

/*
 * print - print out the value to digits places with/without
 *	leading zeros and right/left justified in the current base.
 */
static void
#ifdef _LARGEFILE64_SOURCE
printll(u_offset_t value, int fieldsz, int digits, int lead)
#else /* !_LARGEFILE64_SOURCE */
print(long value, int fieldsz, int digits, int lead)
#endif /* _LARGEFILE64_SOURCE */
{
	int	i, left = 0;
	char	mode = BASE[base - OCTAL];
	char	*string = &scratch[0];

	if (digits < 0) {
		left = 1;
		digits *= -1;
	}
	if (base != HEX)
		if (digits)
			digits = digits + (digits - 1)/((base >> 1) - 1) + 1;
		else
			digits = 1;
	if (lead) {
		if (left)
			(void) sprintf(string, "%%%c%d%d.%d"
#ifdef _LARGEFILE64_SOURCE
				"ll"
#endif /* _LARGEFILE64_SOURCE */
				"%c", '-', 0, digits, lead, mode);
		else
			(void) sprintf(string, "%%%d%d.%d"
#ifdef _LARGEFILE64_SOURCE
				"ll"
#endif /* _LARGEFILE64_SOURCE */
				"%c", 0, digits, lead, mode);
	} else {
		if (left)
			(void) sprintf(string, "%%%c%d"
#ifdef _LARGEFILE64_SOURCE
				"ll"
#endif /* _LARGEFILE64_SOURCE */
				"%c", '-', digits, mode);
		else
			(void) sprintf(string, "%%%d"
#ifdef _LARGEFILE64_SOURCE
				"ll"
#endif /* _LARGEFILE64_SOURCE */
				"%c", digits, mode);
	}
	printf(string, value);
	for (i = 0; i < fieldsz - digits; i++)
		printf(" ");
}

/*
 * Print out the contents of a superblock.
 */
static void
printsb(struct fs *fs)
{
	int c, i, j, k, size;
	caddr_t sip;
	time_t t;

	t = fs->fs_time;
#ifdef FS_42POSTBLFMT
	if (fs->fs_postblformat == FS_42POSTBLFMT)
		fs->fs_nrpos = 8;
	printf("magic\t%lx\tformat\t%s\ttime\t%s", fs->fs_magic,
	    fs->fs_postblformat == FS_42POSTBLFMT ? "static" : "dynamic",
	    ctime(&t));
#else
	printf("magic\t%x\ttime\t%s",
	    fs->fs_magic, ctime(&t));
#endif
	printf("version\t%x\n", fs->fs_version);
	printf("nbfree\t%ld\tndir\t%ld\tnifree\t%ld\tnffree\t%ld\n",
	    fs->fs_cstotal.cs_nbfree, fs->fs_cstotal.cs_ndir,
	    fs->fs_cstotal.cs_nifree, fs->fs_cstotal.cs_nffree);
	printf("ncg\t%ld\tncyl\t%ld\tsize\t%ld\tblocks\t%ld\n",
	    fs->fs_ncg, fs->fs_ncyl, fs->fs_size, fs->fs_dsize);
	printf("bsize\t%ld\tshift\t%ld\tmask\t0x%08lx\n",
	    fs->fs_bsize, fs->fs_bshift, fs->fs_bmask);
	printf("fsize\t%ld\tshift\t%ld\tmask\t0x%08lx\n",
	    fs->fs_fsize, fs->fs_fshift, fs->fs_fmask);
	printf("frag\t%ld\tshift\t%ld\tfsbtodb\t%ld\n",
	    fs->fs_frag, fs->fs_fragshift, fs->fs_fsbtodb);
	printf("cpg\t%ld\tbpg\t%ld\tfpg\t%ld\tipg\t%ld\n",
	    fs->fs_cpg, fs->fs_fpg / fs->fs_frag, fs->fs_fpg, fs->fs_ipg);
	printf("minfree\t%ld%%\toptim\t%s\tmaxcontig %ld\tmaxbpg\t%ld\n",
	    fs->fs_minfree, fs->fs_optim == FS_OPTSPACE ? "space" : "time",
	    fs->fs_maxcontig, fs->fs_maxbpg);
#ifdef FS_42POSTBLFMT
#ifdef sun
	printf("rotdelay %ldms\tfs_id[0] 0x%lx\tfs_id[1] 0x%lx\trps\t%ld\n",
	    fs->fs_rotdelay, fs->fs_id[0], fs->fs_id[1], fs->fs_rps);
#else
	printf("rotdelay %dms\theadswitch %dus\ttrackseek %dus\trps\t%d\n",
	    fs->fs_rotdelay, fs->fs_headswitch, fs->fs_trkseek, fs->fs_rps);
#endif /* sun */
	printf("ntrak\t%ld\tnsect\t%ld\tnpsect\t%ld\tspc\t%ld\n",
	    fs->fs_ntrak, fs->fs_nsect, fs->fs_npsect, fs->fs_spc);
	printf("trackskew %ld\n", fs->fs_trackskew);
#else
	printf("rotdelay %ldms\trps\t%ld\n",
	    fs->fs_rotdelay, fs->fs_rps);
	printf("ntrak\t%ld\tnsect\t%ld\tspc\t%ld\n",
	    fs->fs_ntrak, fs->fs_nsect, fs->fs_spc);
#endif
	printf("si %ld\n", fs->fs_si);
	printf("nindir\t%ld\tinopb\t%ld\tnspf\t%ld\n",
	    fs->fs_nindir, fs->fs_inopb, fs->fs_nspf);
	printf("sblkno\t%ld\tcblkno\t%ld\tiblkno\t%ld\tdblkno\t%ld\n",
	    fs->fs_sblkno, fs->fs_cblkno, fs->fs_iblkno, fs->fs_dblkno);
	printf("sbsize\t%ld\tcgsize\t%ld\tcgoffset %ld\tcgmask\t0x%08lx\n",
	    fs->fs_sbsize, fs->fs_cgsize, fs->fs_cgoffset, fs->fs_cgmask);
	printf("csaddr\t%ld\tcssize\t%ld\tshift\t%ld\tmask\t0x%08lx\n",
	    fs->fs_csaddr, fs->fs_cssize, fs->fs_csshift, fs->fs_csmask);
	printf("cgrotor\t%ld\tfmod\t%d\tronly\t%d\n",
	    fs->fs_cgrotor, fs->fs_fmod, fs->fs_ronly);
#ifdef FS_42POSTBLFMT
	if (fs->fs_cpc != 0)
		printf("blocks available in each of %ld rotational positions",
			fs->fs_nrpos);
	else
		printf("insufficient space to maintain rotational tables\n");
#endif
	for (c = 0; c < fs->fs_cpc; c++) {
		printf("\ncylinder number %d:", c);
#ifdef FS_42POSTBLFMT
		for (i = 0; i < fs->fs_nrpos; i++) {
			/*LINTED*/
			if (fs_postbl(fs, c)[i] == -1)
				continue;
			printf("\n   position %d:\t", i);
			/*LINTED*/
			for (j = fs_postbl(fs, c)[i], k = 1; /* void */;
						j += fs_rotbl(fs)[j], k++) {
				printf("%5d", j);
				if (k % 12 == 0)
					printf("\n\t\t");
				if (fs_rotbl(fs)[j] == 0)
					break;
			}
		}
#else
		for (i = 0; i < NRPOS; i++) {
			if (fs->fs_postbl[c][i] == -1)
				continue;
			printf("\n   position %d:\t", i);
			for (j = fs->fs_postbl[c][i], k = 1; /* void */;
						j += fs->fs_rotbl[j], k++) {
				printf("%5d", j);
				if (k % 12 == 0)
					printf("\n\t\t");
				if (fs->fs_rotbl[j] == 0)
					break;
			}
		}
#endif
	}
	printf("\ncs[].cs_(nbfree, ndir, nifree, nffree):");
	sip = calloc(1, fs->fs_cssize);
	fs->fs_u.fs_csp = (struct csum *)sip;
	for (i = 0, j = 0; i < fs->fs_cssize; i += fs->fs_bsize, j++) {
		size = fs->fs_cssize - i < fs->fs_bsize ?
		    fs->fs_cssize - i : fs->fs_bsize;
		(void) llseek(fd,
			(offset_t)fsbtodb(fs, (fs->fs_csaddr + j * fs->fs_frag))
				* fs->fs_fsize / fsbtodb(fs, 1), 0);
		if (read(fd, sip, size) != size) {
			free(fs->fs_u.fs_csp);
			return;
		}
		sip += size;
	}
	for (i = 0; i < fs->fs_ncg; i++) {
		struct csum *cs = &fs->fs_cs(fs, i);
		if (i % 4 == 0)
			printf("\n     ");
		printf("%d:(%ld,%ld,%ld,%ld) ", i, cs->cs_nbfree, cs->cs_ndir,
						cs->cs_nifree, cs->cs_nffree);
	}
	free(fs->fs_u.fs_csp);
	printf("\n");
	if (fs->fs_ncyl % fs->fs_cpg) {
		printf("cylinders in last group %d\n",
		    i = fs->fs_ncyl % fs->fs_cpg);
		printf("blocks in last group %ld\n",
		    i * fs->fs_spc / NSPB(fs));
	}
}

/*
 * Print out the contents of a cylinder group.
 */
static void
printcg(struct cg *cg)
{
	int i, j;
	time_t t;

	printf("\ncg %ld:\n", cg->cg_cgx);
	t = cg->cg_time;
#ifdef FS_42POSTBLFMT
	printf("magic\t%lx\ttell\t%llx\ttime\t%s",
	    fs->fs_postblformat == FS_42POSTBLFMT ?
	    ((struct ocg *)cg)->cg_magic : cg->cg_magic,
	    fsbtodb(fs, cgtod(fs, cg->cg_cgx)) * fs->fs_fsize / fsbtodb(fs, 1),
	    ctime(&t));
#else
	printf("magic\t%x\ttell\t%llx\ttime\t%s",
	    cg->cg_magic,
	    fsbtodb(fs, cgtod(fs, cg->cg_cgx)) * fs->fs_fsize / fsbtodb(fs, 1),
	    ctime(&t));
#endif
	printf("cgx\t%ld\tncyl\t%d\tniblk\t%d\tndblk\t%ld\n",
	    cg->cg_cgx, cg->cg_ncyl, cg->cg_niblk, cg->cg_ndblk);
	printf("nbfree\t%ld\tndir\t%ld\tnifree\t%ld\tnffree\t%ld\n",
	    cg->cg_cs.cs_nbfree, cg->cg_cs.cs_ndir,
	    cg->cg_cs.cs_nifree, cg->cg_cs.cs_nffree);
	printf("rotor\t%ld\tirotor\t%ld\tfrotor\t%ld\nfrsum",
	    cg->cg_rotor, cg->cg_irotor, cg->cg_frotor);
	for (i = 1, j = 0; i < fs->fs_frag; i++) {
		printf("\t%ld", cg->cg_frsum[i]);
		j += i * cg->cg_frsum[i];
	}
	printf("\nsum of frsum: %d\niused:\t", j);
	pbits((unsigned char *)cg_inosused(cg), fs->fs_ipg);
	printf("free:\t");
	pbits(cg_blksfree(cg), fs->fs_fpg);
	printf("b:\n");
	for (i = 0; i < fs->fs_cpg; i++) {
		/*LINTED*/
		if (cg_blktot(cg)[i] == 0)
			continue;
		/*LINTED*/
		printf("   c%d:\t(%ld)\t", i, cg_blktot(cg)[i]);
#ifdef FS_42POSTBLFMT
		for (j = 0; j < fs->fs_nrpos; j++) {
			if (fs->fs_cpc == 0 ||
				/*LINTED*/
			    fs_postbl(fs, i % fs->fs_cpc)[j] == -1)
				continue;
			/*LINTED*/
			printf(" %d", cg_blks(fs, cg, i)[j]);
		}
#else
		for (j = 0; j < NRPOS; j++) {
			if (fs->fs_cpc == 0 ||
			    fs->fs_postbl[i % fs->fs_cpc][j] == -1)
				continue;
			printf(" %d", cg->cg_b[i][j]);
		}
#endif
		printf("\n");
	}
}

/*
 * Print out the contents of a bit array.
 */
static void
pbits(unsigned char *cp, int max)
{
	int i;
	int count = 0, j;

	for (i = 0; i < max; i++)
		if (isset(cp, i)) {
			if (count)
				printf(",%s", count % 6 ? " " : "\n\t");
			count++;
			printf("%d", i);
			j = i;
			while ((i+1) < max && isset(cp, i+1))
				i++;
			if (i != j)
				printf("-%d", i);
		}
	printf("\n");
}

/*
 * bcomp - used to check for block over/under flows when stepping through
 *	a file system.
 */
static int
bcomp(addr)
	u_offset_t	addr;
{
	if (override)
		return (0);

	if (lblkno(fs, addr) == (bhdr.fwd)->blkno)
		return (0);
	error++;
	return (1);
}

/*
 * bmap - maps the logical block number of a file into
 *	the corresponding physical block on the file
 *	system.
 */
static long
bmap(long bn)
{
	int		j;
	struct dinode	*ip;
	int		sh;
	long		nb;
	char		*cptr;

	if ((cptr = getblk(cur_ino)) == 0)
		return (0);

	cptr += blkoff(fs, cur_ino);

	/*LINTED*/
	ip = (struct dinode *)cptr;

	if (bn < NDADDR) {
		nb = ip->di_db[bn];
		return (nullblk(nb) ? 0L : nb);
	}

	sh = 1;
	bn -= NDADDR;
	for (j = NIADDR; j > 0; j--) {
		sh *= NINDIR(fs);
		if (bn < sh)
			break;
		bn -= sh;
	}
	if (j == 0) {
		printf("file too big\n");
		error++;
		return (0L);
	}
	addr = (uintptr_t)&ip->di_ib[NIADDR - j];
	nb = get(LONG);
	if (nb == 0)
		return (0L);
	for (; j <= NIADDR; j++) {
		sh /= NINDIR(fs);
		addr = (nb << FRGSHIFT) + ((bn / sh) % NINDIR(fs)) * LONG;
		if (nullblk(nb = get(LONG)))
			return (0L);
	}
	return (nb);
}

#if defined(OLD_FSDB_COMPATIBILITY)

/*
 * The following are "tacked on" to support the old fsdb functionality
 * of clearing an inode. (All together now...) "It's better to use clri".
 */

#define	ISIZE	(sizeof (struct dinode))
#define	NI	(MAXBSIZE/ISIZE)


static struct	dinode	di_buf[NI];

static union {
	char		dummy[SBSIZE];
	struct fs	sblk;
} sb_un;

#define	sblock sb_un.sblk

static void
old_fsdb(int inum, char *special)
{
	int		f;	/* File descriptor for "special" */
	int		j;
	int		status = 0;
	u_offset_t	off;
	long		gen;
	time_t		t;

	f = open(special, 2);
	if (f < 0) {
		perror("open");
		printf("cannot open %s\n", special);
		exit(31+4);
	}
	(void) llseek(f, (offset_t)SBLOCK * DEV_BSIZE, 0);
	if (read(f, &sblock, SBSIZE) != SBSIZE) {
		printf("cannot read %s\n", special);
		exit(31+4);
	}
	if (sblock.fs_magic != FS_MAGIC) {
		printf("bad super block magic number\n");
		exit(31+4);
	}
	if (inum == 0) {
		printf("%d: is zero\n", inum);
		exit(31+1);
	}
	off = (u_offset_t)fsbtodb(&sblock, itod(&sblock, inum)) * DEV_BSIZE;
	(void) llseek(f, off, 0);
	if (read(f, (char *)di_buf, sblock.fs_bsize) != sblock.fs_bsize) {
		printf("%s: read error\n", special);
		status = 1;
	}
	if (status)
		exit(31+status);

	/*
	 * Update the time in superblock, so fsck will check this filesystem.
	 */
	(void) llseek(f, (offset_t)(SBLOCK * DEV_BSIZE), 0);
	(void) time(&t);
	sblock.fs_time = (time32_t)t;
	if (write(f, &sblock, SBSIZE) != SBSIZE) {
		printf("cannot update %s\n", special);
		exit(35);
	}

	printf("clearing %u\n", inum);
	off = (u_offset_t)fsbtodb(&sblock, itod(&sblock, inum)) * DEV_BSIZE;
	(void) llseek(f, off, 0);
	read(f, (char *)di_buf, sblock.fs_bsize);
	j = itoo(&sblock, inum);
	gen = di_buf[j].di_gen;
	(void) memset((caddr_t)&di_buf[j], 0, ISIZE);
	di_buf[j].di_gen = gen + 1;
	(void) llseek(f, off, 0);
	write(f, (char *)di_buf, sblock.fs_bsize);
	exit(31+status);
}

static int
isnumber(char *s)
{
	int	c;

	if (s == NULL)
		return (0);
	while ((c = *s++) != '\0')
		if (c < '0' || c > '9')
			return (0);
	return (1);
}
#endif /* OLD_FSDB_COMPATIBILITY */

enum boolean { True, False };
extent_block_t	*log_eb;
ml_odunit_t	*log_odi;
int		lufs_tid;	/* last valid TID seen */

/*
 * no single value is safe to use to indicate
 * lufs_tid being invalid so we need a
 * seperate variable.
 */
enum boolean	lufs_tid_valid;

/*
 * log_get_header_info - get the basic info of the logging filesystem
 */
int
log_get_header_info(void)
{
	char		*b;
	int		nb;

	/*
	 * Mark the global tid as invalid everytime we're called to
	 * prevent any false positive responses.
	 */
	lufs_tid_valid = False;

	/*
	 * See if we've already set up the header areas. The only problem
	 * with this approach is we don't reread the on disk data though
	 * it shouldn't matter since we don't operate on a live disk.
	 */
	if ((log_eb != NULL) && (log_odi != NULL))
		return (1);

	/*
	 * Either logging is disabled or we've not running 2.7.
	 */
	if (fs->fs_logbno == 0) {
		printf("Logging doesn't appear to be enabled on this disk\n");
		return (0);
	}

	/*
	 * To find the log we need to first pick up the block allocation
	 * data. The block number for that data is fs_logbno in the
	 * super block.
	 */
	if ((b = getblk((u_offset_t)ldbtob(logbtodb(fs, fs->fs_logbno))))
	    == 0) {
		printf("getblk() indicates an error with logging block\n");
		return (0);
	}

	/*
	 * Next we need to figure out how big the extent data structure
	 * really is. It can't be more then fs_bsize and you could just
	 * allocate that but, why get sloppy.
	 * 1 is subtracted from nextents because extent_block_t contains
	 * a single extent_t itself.
	 */
	log_eb = (extent_block_t *)b;
	if (log_eb->type != LUFS_EXTENTS) {
		printf("Extents block has invalid type (0x%x)\n",
		    log_eb->type);
		return (0);
	}
	nb = sizeof (extent_block_t) +
	    (sizeof (extent_t) * (log_eb->nextents - 1));

	log_eb = (extent_block_t *)malloc(nb);
	if (log_eb == NULL) {
		printf("Failed to allocate memory for extent block log\n");
		return (0);
	}
	memcpy(log_eb, b, nb);

	if (log_eb->nextbno != 0)
		/*
		 * Currently, as of 11-Dec-1997 the field nextbno isn't
		 * implemented. If someone starts using this sucker we'd
		 * better warn somebody.
		 */
		printf("WARNING: extent block field nextbno is non-zero!\n");

	/*
	 * Now read in the on disk log structure. This is always in the
	 * first block of the first extent.
	 */
	b = getblk((u_offset_t)ldbtob(logbtodb(fs, log_eb->extents[0].pbno)));
	log_odi = (ml_odunit_t *)malloc(sizeof (ml_odunit_t));
	if (log_odi == NULL) {
		free(log_eb);
		log_eb = NULL;
		printf("Failed to allocate memory for ondisk structure\n");
		return (0);
	}
	memcpy(log_odi, b, sizeof (ml_odunit_t));

	/*
	 * Consistency checks.
	 */
	if (log_odi->od_version != LUFS_VERSION_LATEST) {
		free(log_eb);
		log_eb = NULL;
		free(log_odi);
		log_odi = NULL;
		printf("Version mismatch in on-disk version of log data\n");
		return (0);
	} else if (log_odi->od_badlog) {
		printf("WARNING: Log was marked as bad\n");
	}

	return (1);
}

static void
log_display_header(void)
{
	int x;
	if (!log_get_header_info())
		/*
		 * No need to display anything here. The previous routine
		 * has already done so.
		 */
		return;

	if (fs->fs_magic == FS_MAGIC)
		printf("Log block number: 0x%x\n------------------\n",
		    fs->fs_logbno);
	else
		printf("Log frag number: 0x%x\n------------------\n",
		    fs->fs_logbno);
	printf("Extent Info\n\t# Extents  : %d\n\t# Bytes    : 0x%x\n",
	    log_eb->nextents, log_eb->nbytes);
	printf("\tNext Block : 0x%x\n\tExtent List\n\t--------\n",
	    log_eb->nextbno);
	for (x = 0; x < log_eb->nextents; x++)
		printf("\t  [%d] lbno 0x%08x pbno 0x%08x nbno 0x%08x\n",
		    x, log_eb->extents[x].lbno, log_eb->extents[x].pbno,
		    log_eb->extents[x].nbno);
	printf("\nOn Disk Info\n\tbol_lof    : 0x%08x\n\teol_lof    : 0x%08x\n",
	    log_odi->od_bol_lof, log_odi->od_eol_lof);
	printf("\tlog_size   : 0x%08x\n",
	    log_odi->od_logsize);
	printf("\thead_lof   : 0x%08x\tident : 0x%x\n",
	    log_odi->od_head_lof, log_odi->od_head_ident);
	printf("\ttail_lof   : 0x%08x\tident : 0x%x\n\thead_tid   : 0x%08x\n",
	    log_odi->od_tail_lof, log_odi->od_tail_ident, log_odi->od_head_tid);
	printf("\tcheck sum  : 0x%08x\n", log_odi->od_chksum);
	if (log_odi->od_chksum !=
	    (log_odi->od_head_ident + log_odi->od_tail_ident))
		printf("bad checksum: found 0x%08x, should be 0x%08x\n",
		    log_odi->od_chksum,
		    log_odi->od_head_ident + log_odi->od_tail_ident);
	if (log_odi->od_head_lof == log_odi->od_tail_lof)
		printf("\t --- Log is empty ---\n");
}

/*
 * log_lodb -- logical log offset to disk block number
 */
int
log_lodb(u_offset_t off, diskaddr_t *pblk)
{
	uint32_t	lblk = (uint32_t)btodb(off);
	int	x;

	if (!log_get_header_info())
		/*
		 * No need to display anything here. The previous routine
		 * has already done so.
		 */
		return (0);

	for (x = 0; x < log_eb->nextents; x++)
		if ((lblk >= log_eb->extents[x].lbno) &&
		    (lblk < (log_eb->extents[x].lbno +
			log_eb->extents[x].nbno))) {
			*pblk = (diskaddr_t)lblk - log_eb->extents[x].lbno +
				logbtodb(fs, log_eb->extents[x].pbno);
			return (1);
		}
	return (0);
}

/*
 * String names for the enumerated types. These are only used
 * for display purposes.
 */
char *dt_str[] = {
	"DT_NONE", "DT_SB", "DT_CG", "DT_SI", "DT_AB",
	"DT_ABZERO", "DT_DIR", "DT_INODE", "DT_FBI",
	"DT_QR", "DT_COMMIT", "DT_CANCEL", "DT_BOT",
	"DT_EOT", "DT_UD", "DT_SUD", "DT_SHAD", "DT_MAX"
};

/*
 * log_read_log -- transfer information from the log and adjust offset
 */
int
log_read_log(u_offset_t *addr, caddr_t va, int nb, uint32_t *chk)
{
	int		xfer;
	caddr_t		bp;
	diskaddr_t	pblk;
	sect_trailer_t	*st;

	while (nb) {
		if (!log_lodb(*addr, &pblk)) {
			printf("Invalid log offset\n");
			return (0);
		}

		/*
		 * fsdb getblk() expects offsets not block number.
		 */
		if ((bp = getblk((u_offset_t)dbtob(pblk))) == NULL)
			return (0);

		xfer = MIN(NB_LEFT_IN_SECTOR(*addr), nb);
		if (va != NULL) {
			memcpy(va, bp + blkoff(fs, *addr), xfer);
			va += xfer;
		}
		nb -= xfer;
		*addr += xfer;

		/*
		 * If the log offset is now at a sector trailer
		 * run the checks if requested.
		 */
		if (NB_LEFT_IN_SECTOR(*addr) == 0) {
			if (chk != NULL) {
				st = (sect_trailer_t *)
				    (bp + blkoff(fs, *addr));
				if (*chk != st->st_ident) {
					printf(
			"Expected sector trailer id 0x%08x, but saw 0x%08x\n",
						*chk, st->st_ident);
					return (0);
				} else {
					*chk = st->st_ident + 1;
					/*
					 * We update the on disk structure
					 * transaction ID each time we see
					 * one. By comparing this value
					 * to the last valid DT_COMMIT record
					 * we can determine if our log is
					 * completely valid.
					 */
					log_odi->od_head_tid = st->st_tid;
				}
			}
			*addr += sizeof (sect_trailer_t);
		}
		if ((int32_t)*addr == log_odi->od_eol_lof)
			*addr = log_odi->od_bol_lof;
	}
	return (1);
}

u_offset_t
log_nbcommit(u_offset_t a)
{
	/*
	 * Comments are straight from ufs_log.c
	 *
	 * log is the offset following the commit header. However,
	 * if the commit header fell on the end-of-sector, then lof
	 * has already been advanced to the beginning of the next
	 * sector. So do nothgin. Otherwise, return the remaining
	 * bytes in the sector.
	 */
	if ((a & (DEV_BSIZE - 1)) == 0)
		return (0);
	else
		return (NB_LEFT_IN_SECTOR(a));
}

/*
 * log_show --  pretty print the deltas. The number of which is determined
 *		by the log_enum arg. If LOG_ALLDELTAS the routine, as the
 *		name implies dumps everything. If LOG_NDELTAS, the routine
 *		will print out "count" deltas starting at "addr". If
 *		LOG_CHECKSCAN then run through the log checking the st_ident
 *		for valid data.
 */
static void
log_show(enum log_enum l)
{
	struct delta	d;
	int32_t		bol, eol;
	int		x = 0;
	uint32_t	chk;

	if (!log_get_header_info())
		/*
		 * No need to display any error messages here. The previous
		 * routine has already done so.
		 */
		return;

	bol = log_odi->od_head_lof;
	eol = log_odi->od_tail_lof;
	chk = log_odi->od_head_ident;

	if (bol == eol) {
		if ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN)) {
			printf("Empty log.\n");
			return;
		} else
			printf("WARNING: empty log. addr may generate bogus"
			    " information");
	}

	/*
	 * Only reset the "addr" if we've been requested to show all
	 * deltas in the log.
	 */
	if ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN))
		addr = (u_offset_t)bol;

	if (l != LOG_CHECKSCAN) {
		printf("       Log Offset       Delta       Count     Type\n");
		printf("-----------------------------------------"
			"-----------------\n");
	}

	while ((bol != eol) && ((l == LOG_ALLDELTAS) ||
	    (l == LOG_CHECKSCAN) || count--)) {
		if (!log_read_log(&addr, (caddr_t)&d, sizeof (d),
		    ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN)) ?
		    &chk : NULL))
			/*
			 * Two failures are possible. One from getblk()
			 * which prints out a message or when we've hit
			 * an invalid block which may or may not indicate
			 * an error
			 */
			goto end_scan;

		if ((uint32_t)d.d_nb > log_odi->od_logsize) {
			printf("Bad delta entry. size out of bounds\n");
			return;
		}
		if (l != LOG_CHECKSCAN)
			printf("[%04d]  %08x  %08x.%08x %08x  %s\n", x++, bol,
			    d.d_mof, d.d_nb,
			    dt_str[d.d_typ >= DT_MAX ? DT_MAX : d.d_typ]);

		switch (d.d_typ) {
		case DT_CANCEL:
		case DT_ABZERO:
			/*
			 * These two deltas don't have log space
			 * associated with the entry even though
			 * d_nb is non-zero.
			 */
			break;

		case DT_COMMIT:
			/*
			 * Commit records have zero size yet, the
			 * rest of the current disk block is avoided.
			 */
			addr += log_nbcommit(addr);
			lufs_tid = log_odi->od_head_tid;
			lufs_tid_valid = True;
			break;

		default:
			if (!log_read_log(&addr, NULL, d.d_nb,
			    ((l == LOG_ALLDELTAS) ||
			    (l == LOG_CHECKSCAN)) ? &chk : NULL))
				goto end_scan;
			break;
		}
		bol = (int32_t)addr;
	}

end_scan:
	if (lufs_tid_valid == True) {
		if (lufs_tid == log_odi->od_head_tid)
			printf("scan -- okay\n");
		else
			printf("scan -- some transactions have been lost\n");
	} else {
		printf("scan -- failed to find a single valid transaction\n");
		printf("        (possibly due to an empty log)\n");
	}
}