/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*      Copyright (c) 1984 AT&T */
/*        All Rights Reserved   */

/*      THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T     */
/*      The copyright notice above does not evidence any        */
/*      actual or intended publication of such source code.     */

#ident  "$Header: ls.c 1.22 90/04/10 $@(#)ls:ls.c        1.36"
/*
*       list file or directory;
*       define DOTSUP to suppress listing of files beginning with dot
*/

/* Sequent changes:
 * Additions to support internal hash of passwords and groups, and possible
 * presence of a hashed password file.  Additions to support symbolic links.
 */

#include        <sys/param.h>
#include        <sys/types.h>
#include        <sys/sysmacros.h>
#include        <sys/stat.h>
#include        <sys/statfs.h>
#include        <sys/ufsinode.h>
#include        <stdio.h>
#include        <ctype.h>
#include        <dirent.h>
#include	<string.h>
#include	<math.h>
#include	<euc.h>
#include	<getwidth.h>

#ifndef STANDALONE
#define TERMINFO
#endif

/* -DNOTERMINFO can be defined on the cc command line to prevent
 * the use of terminfo.  This should be done on systems not having
 * the terminfo feature (pre 6.0 sytems ?).
 * As a result, columnar listings assume 80 columns for output,
 * unless told otherwise via the COLUMNS environment variable.
 */
#ifdef NOTERMINFO
#undef TERMINFO
#endif

#ifdef TERMINFO
#include        <curses.h>
#include        <term.h>
#endif

#define DOTSUP  1
#define ISARG   0100000 /* this bit equals 1 in lflags of structure lbuf 
			*  if *namep is to be used;
			*/

/*      Date and time   formats   */

#define FORMAT1  " %b %e  %Y "
#define FORMAT2  " %b %e %H:%M "        /*
					 * b --- abbreviated month name
					   e --- day number
					   Y --- year in the form ccyy
					   H --- hour (24 hour version)
					   M --- minute   */

struct  lbuf    {
	union   {
		char    lname[MAXNAMLEN];   /* used for filename in a directory */
		char    *namep;          /* for name in ls-command; */
	} ln;
	char    ltype;          /* filetype */
	ino_t   lnum;   /* inode number of file */
	short   lflags;         /* 0777 bits used as r,w,x permissions */
	short   lnl;            /* number of links to file */
	unsigned short  luid;
	unsigned short  lgid;
	ulong    lsize;          /* filesize or major/minor dev numbers */
	ulong    lblocks;        /* number of blocks the file takes up */
	long    lmtime;
	char    *linkto;        /* symbolic link value */
};

struct dchain {
	char *dc_name;          /* path name */
	struct dchain *dc_next; /* next directory in the chain */
};

struct dchain *dfirst;  /* start of the dir chain */
struct dchain *cdfirst; /* start of the durrent dir chain */
struct dchain *dtemp;   /* temporary - used for linking */
char *curdir;           /* the current directory */

int     nfiles = 0;     /* number of flist entries in current use */
int     nargs = 0;      /* number of flist entries used for arguments */
int     maxfils = 0;    /* number of flist/lbuf entries allocated */
int     maxn = 0;       /* number of flist entries with lbufs assigned */
int     quantn = 64;    /* allocation growth quantum */

struct  lbuf    *nxtlbf;        /* pointer to next lbuf to be assigned */
struct  lbuf    **flist;        /* pointer to list of lbuf pointers */
struct  lbuf    *gstat();

int     aflg, bflg, cflg, dflg, fflg, gflg, iflg, lflg, mflg;
int     nflg, oflg, pflg, qflg, sflg, tflg, uflg, xflg;
int     Cflg, Fflg, Rflg, Lflg;
int     rflg = 1;   /* initialized to 1 for special use in compar() */
int     flags;
int     err = 0;        /* Contains return code */

char    *dmark; /* Used if -p option active. Contains "/" or NULL. */

unsigned        lastuid = -1, lastgid = -1;
int     statreq;    /* is > 0 if any of sflg, (n)lflg, tflg are on */

char    *dotp = ".";
char    *makename();
char    *savestr();

long    nblocks();
ulong    tblocks;  /* total number of blocks of files in a directory */
long    year, now, onehourfromnow;

int     num_cols = 80;
int     colwidth;
int     filewidth;
int     fixedwidth;
int     curcol;
int	ndigits;		/* how many digits for inode */
int     compar();

static char     time_buf[50];   /* array to hold day and time */

eucwidth_t	wp;
int	difset1;	/* difference in each code set	*/
int	difset2;	/*  between cswidth and		*/
int	difset3;	/*  scrwidth			*/

#include <utmp.h>
#include <pwd.h>
#include <shadow.h>
#include <grp.h>

#define NMAX    15      /* Same hard wired value from System V ls V 1.36 */

#define SCPYN(a, b)     strncpy(a, b, NMAX)

#define MAX_UID         2048    /* size of hash table for user ids */
#define MAX_GID         300     /* size of hash table for group ids */
#define BUCKET_LEN      5       /* how many buckets to walk through */

char u_names[MAX_UID][NMAX + 1];      /* hash buffer for names */
uid_t u_vals[MAX_UID];
char g_names[MAX_GID][NMAX + 1];                        /* hash buffer for groups */
gid_t g_vals[MAX_GID];

int   lastu_index;      /* where we cached last uid */
uid_t lastu = 0xfff0;	/* value of last uid */
int   lastg_index;      /* where we cached last gid */
gid_t lastg = 0xfff0;	/* value of last gid */

char *getname_uid();    /* get name given a uid */
char *getname_gid();    /* get group given a gid */
extern struct passwd *getpwuid();
extern struct group *getgrgid();
extern int stat();
extern int lstat();

main(argc, argv)
int argc;
char *argv[];
{
	extern char     *optarg;
	extern char     *basename();
	extern int      optind;
	int     amino, opterr=0;
	int     c;
	register struct lbuf *ep;
	struct  lbuf    lb;
	int     i, width;
	long    time();
	char *malloc();
	void qsort(), exit();
	struct  winsize win;
	getwidth(&wp);
	difset1 = wp._eucw1 - wp._scrw1;
	difset2 = wp._eucw2 - wp._scrw2;
	difset3 = wp._eucw3 - wp._scrw3;

#ifdef STANDALONE
	if (argv[0][0] == '\0')
		argc = getargv("ls", &argv, 0);
#endif

	lb.lmtime = time((long *) NULL);
	year = lb.lmtime - 6L*30L*24L*60L*60L; /* 6 months ago */
	onehourfromnow = lb.lmtime + 60L*60L;
	now = lb.lmtime + 60;
	while ((c=getopt(argc, argv,
			"RadCxmnlogrtucpFbqisfL")) != EOF) switch(c) {
		case 'R':
			Rflg++;
			statreq++;
			continue;
		case 'a':
			aflg++;
			continue;
		case 'd':
			dflg++;
			continue;
		case 'C':
			Cflg = 1;
			mflg = 0;
			continue;
		case 'x':
			xflg = 1;
			Cflg = 1;
			mflg = 0;
			continue;
		case 'm':
			Cflg = 0;
			mflg = 1;
			continue;
		case 'n':
			nflg++;
		case 'l':
			lflg++;
			statreq++;
			continue;
		case 'o':
			oflg++;
			lflg++;
			statreq++;
			continue;
		case 'g':
			gflg++;
			lflg++;
			statreq++;
			continue;
		case 'r':
			rflg = -1;
			continue;
		case 't':
			tflg++;
			statreq++;
			continue;
		case 'u':
			uflg++;
			continue;
		case 'c':
			cflg++;
			continue;
		case 'p':
			pflg++;
			statreq++;
			continue;
		case 'F':
			Fflg++;
			statreq++;
			continue;
		case 'b':
			bflg = 1;
			qflg = 0;
			continue;
		case 'q':
			qflg = 1;
			bflg = 0;
			continue;
		case 'i':
			iflg++;
			continue;
		case 's':
			sflg++;
			statreq++;
			continue;
		case 'f':
			fflg++;
			continue;
		case '?':
			opterr++;
			continue;
		case 'L':
			Lflg++;
			continue;
		}
	if(opterr) {
		fprintf(stderr,gettxt("ls:1","usage: ls -RadCxmnlogrtucpFbqisfL [files]\n"));
		exit(2);
	}

	if (fflg) {
		aflg++;
		lflg = 0;
		sflg = 0;
		tflg = 0;
		statreq = 0;
	}

	fixedwidth = 2;
	if (pflg || Fflg)
		fixedwidth++;
	if (iflg)
		fixedwidth += 6;
	if (sflg)
		fixedwidth += 5;

	if (lflg) {
		if (!gflg && !oflg)
			gflg = oflg = 1;
		else
		if (gflg && oflg)
			gflg = oflg = 0;
		Cflg = mflg = 0;
	}

	if (Cflg || mflg) {
		char *getenv();
		char *clptr;
		if ((clptr = getenv("COLUMNS")) != NULL)
			num_cols = atoi(clptr);
		else if (ioctl(1, TIOCGWINSZ, &win) >= 0 && win.ws_col > 0)
			num_cols = win.ws_col;
#ifdef TERMINFO
		else {
			setupterm((char *)0,1,&i); /* get term description */
			resetterm();    /* undo what setupterm changed */
			if (i == 1)
				num_cols = columns;
		}
#endif
		if (num_cols < 20 || num_cols > 160)
			/* assume it is an error */
			num_cols = 80;
	}

	/* allocate space for flist and the associated  */
	/* data structures (lbufs)                      */
	maxfils = quantn;
	if((flist=(struct lbuf **)malloc((unsigned)(maxfils * sizeof(struct lbuf *)))) == NULL
	|| (nxtlbf = (struct lbuf *)malloc((unsigned)(quantn * sizeof(struct lbuf)))) == NULL) {
		fprintf(stderr, gettxt("ls:3","ls: out of memory\n"));
		exit(2);
	}
	if ((amino=(argc-optind))==0) { /* case when no names are given
					* in ls-command and current 
					* directory is to be used 
					*/
		argv[optind] = dotp;
	}
	for (i=0; i < (amino ? amino : 1); i++) {
		if (Cflg || mflg) {
			width = strlen(argv[optind]);
			width -= setcount(argv[optind]);
			if (width > filewidth)
				filewidth = width;
		}
		if ((ep = gstat((*argv[optind] ? argv[optind] : dotp), 1))==NULL)
		{
			err = 2;
			optind++;
			continue;
		}
		ep->ln.namep = (*argv[optind] ? argv[optind] : dotp);
		ep->lflags |= ISARG;
		optind++;
		nargs++;        /* count good arguments stored in flist */
	}
	colwidth = fixedwidth + filewidth;
	if (iflg && (ndigits > 5))
		colwidth += ndigits - 5;
	qsort(flist, (unsigned)nargs, sizeof(struct lbuf *), compar);
	for (i=0; i<nargs; i++)
		if (flist[i]->ltype=='d' && dflg==0 || fflg)
			break;
	pem(&flist[0],&flist[i], 0);
	for (; i<nargs; i++) {
		pdirectory(flist[i]->ln.namep, (amino>1), nargs);
		/* -R: print subdirectories found */
		while (dfirst || cdfirst) {
			/* Place direct subdirs on front in right order */
			while (cdfirst) {
				/* reverse cdfirst onto front of dfirst */
				dtemp = cdfirst;
				cdfirst = cdfirst -> dc_next;
				dtemp -> dc_next = dfirst;
				dfirst = dtemp;
			}
			/* take off first dir on dfirst & print it */
			dtemp = dfirst;
			dfirst = dfirst->dc_next;
			pdirectory (dtemp->dc_name, 1, nargs);
			free (dtemp->dc_name);
			free ((char *)dtemp);
		}
	}
	exit(err);
	/*NOTREACHED*/
}

/*
 * pdirectory: print the directory name, labelling it if title is
 * nonzero, using lp as the place to start reading in the dir.
 */
pdirectory (name, title, lp)
char *name;
int title;
int lp;
{
	register struct dchain *dp;
	register struct lbuf *ap;
	register char *pname;
	register int j;

	filewidth = 0;
	ndigits = 0;
	curdir = name;
	if (title) {
		putc('\n', stdout);
		pprintf(name, ":");
		new_line();
	}
	nfiles = lp;
	rddir(name);
	if (fflg==0)
		qsort(&flist[lp],(unsigned)(nfiles - lp),sizeof(struct lbuf *),compar);
	if (Rflg) for (j = nfiles - 1; j >= lp; j--) {
		ap = flist[j];
		if (ap->ltype == 'd' && strcmp(ap->ln.lname, ".") &&
				strcmp(ap->ln.lname, "..")) {
			dp = (struct dchain *)calloc(1,sizeof(struct dchain));
			if (dp == NULL)
				fprintf(stderr,gettxt("ls:3","ls: out of memory\n"));
			pname = makename(curdir, ap->ln.lname);
			dp->dc_name = (char *)calloc(1,strlen(pname)+1);
			if (dp->dc_name == NULL) {
				fprintf(stderr,gettxt("ls:3","ls: out of memory\n"));
				free(dp);
			}
			else {
				strcpy(dp->dc_name, pname);
				dp -> dc_next = dfirst;
				dfirst = dp;
			}
		}
	}
	if (lflg || sflg)
		curcol += printf(gettxt("ls:4","total %lu"), tblocks);
	pem(&flist[lp],&flist[nfiles],lflg||sflg);
}

/*
 * pem: print 'em.  Print a list of files (e.g. a directory) bounded
 * by slp and lp.
 */
pem(slp, lp, tot_flag)
	register struct lbuf **slp, **lp;
	int tot_flag;
{
	int ncols, nrows, row, col;
	register struct lbuf **ep;

	if (Cflg || mflg)
		ncols = num_cols / colwidth;

	/*
	 * With a very long file name, ncols can be 0.  The following
	 * compare was "ncols == 1".  To prevent a later division
	 * by zero this was changed to "ncols <= 1".
	 */
	if (ncols <= 1 || mflg || xflg || !Cflg) {
		for (ep = slp; ep < lp; ep++)
			pentry(*ep);
		new_line();
		return;
	}
	/* otherwise print -C columns */
	if (tot_flag)
		slp--;
	nrows = (lp - slp - 1) / ncols + 1;
	for (row = 0; row < nrows; row++) {
		col = (row == 0 && tot_flag);
		for (; col < ncols; col++) {
			ep = slp + (nrows * col) + row;
			if (ep < lp)
				pentry(*ep);
		}
		new_line();
	}
}

pentry(ap)  /* print one output entry;
	    *  if uid/gid is not found in the appropriate
	    *  file (passwd/group), then print uid/gid instead of 
	    *  user/group name;
	    */
struct lbuf *ap;
{
	struct  {
		char    dminor,
			dmajor;
	};
	register struct lbuf *p;
	register char *cp;
	char *s;
	char *cp1, *cp2;
	char sname[BUFSIZ + 1];


	p = ap;
	column();
	if (iflg)
		if (mflg && !lflg)
			curcol += printf("%u ", p->lnum);
		else {
			if (ndigits > 5)
				curcol += printf("%*u ", ndigits, p->lnum);
			else {
				if (p->lnum > 99999)
					curcol += printf("%u ", p->lnum);
				else
					curcol += printf("%5u ", p->lnum);
			}
		}
	if (sflg)
		curcol += printf( (mflg && !lflg) ? "%lu " : "%4lu " ,
			(p->ltype != 'b' && p->ltype != 'c') ?
				p->lblocks : 0L );
	if (lflg) {
		putchar(p->ltype);
		curcol++;
		pmode(p->lflags);
		curcol += printf("%4d ", p->lnl);
		if (oflg)
			if (!nflg && ((s = getname_uid(p->luid)) != (char *) NULL))
				curcol += printf("%-9.9s", s);
			else
				curcol += printf("%-9u", p->luid);
		if (gflg)
			if (!nflg && ((s = getname_gid(p->lgid)) != (char *) NULL))
				curcol += printf("%-9.9s", s);
			else
				curcol += printf("%-9u", p->lgid);
		if (p->ltype=='b' || p->ltype=='c')
			curcol += printf("%3d,%3d", major((int)p->lsize), minor((int)p->lsize));
		else
			curcol += printf("%7lu", p->lsize);
		/*
		 * Because of possible time skew problems between NFS
		 * clients and servers, only consider the file time
		 * in the future if it's greater than one hour from now.
		 */
		if((p->lmtime < year) || (p->lmtime > onehourfromnow))
			{
			cftime(time_buf, FORMAT1, &p->lmtime);
			curcol += printf("%s", time_buf);
			}
		else
			{
			cftime(time_buf, FORMAT2, &p->lmtime);
			curcol += printf("%s", time_buf);
			}
	}
	dmark = "";
	if ((pflg || Fflg) && p->ltype == 'd')
		dmark = "/";
	else
	if (Fflg && (p->ltype == 'l'))
		dmark = "@";
	else
	if (Fflg && (p->lflags & 0111))
		dmark = "*";
	else
		dmark = "";
	
	/* May change if was a symbolic link and "ls -l" */
	if (lflg && p->linkto) {
		strcpy(sname, " -> ");
		for (cp1 = &sname[4], cp2 = p->linkto; *cp2; cp2++)
			*cp1++ = *cp2;
		*cp1 = '\0';
		dmark = sname;
	}
	if (p->lflags & ISARG) {
		if (qflg || bflg)
			pprintf(p->ln.namep,dmark);
		else
			curcol += (printf("%s%s",p->ln.namep,dmark)
					-setcount(p->ln.namep) );
	}
	else {
		if (qflg || bflg)
			pprintf(p->ln.lname,dmark);
		else
			curcol += ( printf("%s%s",p->ln.lname,dmark)
					-setcount(p->ln.lname) );
	}
}

/* print various r,w,x permissions 
 */
pmode(aflag)
{
	/* these arrays are declared static to allow initializations */
	static int      m0[] = { 1, S_IREAD>>0, 'r', '-' };
	static int      m1[] = { 1, S_IWRITE>>0, 'w', '-' };
	static int      m2[] = { 3, S_ISUID|S_IEXEC, 's', S_IEXEC, 'x', S_ISUID, 'S', '-' };
	static int      m3[] = { 1, S_IREAD>>3, 'r', '-' };
	static int      m4[] = { 1, S_IWRITE>>3, 'w', '-' };
	static int      m5[] = { 3, S_ISGID|(S_IEXEC>>3),'s', S_IEXEC>>3,'x', S_ISGID,'l', '-'};
	static int      m6[] = { 1, S_IREAD>>6, 'r', '-' };
	static int      m7[] = { 1, S_IWRITE>>6, 'w', '-' };
	static int      m8[] = { 3, S_ISVTX|(S_IEXEC>>6),'t', S_IEXEC>>6,'x', S_ISVTX,'T', '-'};

	static int  *m[] = { m0, m1, m2, m3, m4, m5, m6, m7, m8};

	register int **mp;

	flags = aflag;
	for (mp = &m[0]; mp < &m[sizeof(m)/sizeof(m[0])];)
		select(*mp++);
}

select(pairp)
register int *pairp;
{
	register int n;

	n = *pairp++;
	while (n-->0) {
		if((flags & *pairp) == *pairp) {
			pairp++;
			break;
		}else {
			pairp += 2;
		}
	}
	putchar(*pairp);
	curcol++;
}

/*
 * column: get to the beginning of the next column.
 */
column()
{

	if (curcol == 0)
		return;
	if (mflg) {
		putc(',', stdout);
		curcol++;
		if (curcol + colwidth + 2 > num_cols) {
			putc('\n', stdout);
			curcol = 0;
			return;
		}
		putc(' ', stdout);
		curcol++;
		return;
	}
	if (Cflg == 0) {
		putc('\n', stdout);
		curcol = 0;
		return;
	}
	if ((curcol / colwidth + 2) * colwidth > num_cols) {
		putc('\n', stdout);
		curcol = 0;
		return;
	}
	do {
		putc(' ', stdout);
		curcol++;
	} while (curcol % colwidth);
}

new_line()
{
	if (curcol) {
		putc('\n',stdout);
		curcol = 0;
	}
}

/* read each filename in directory dir and store its
 *  status in flist[nfiles] 
 *  use makename() to form pathname dir/filename;
 */
rddir(dir)
char *dir;
{
	struct dirent *dentry;
	DIR *dirf;
	register int j;
	register struct lbuf *ep;
	register int width;
	int n;

	if ((dirf = opendir(dir)) == NULL) {
		fflush(stdout);
		fprintf(stderr, gettxt("ls:5","can not access directory %s\n"), dir);
		err = 2;
		return;
	}
	else {
                errno = 0;
                if (((dentry = readdir(dirf)) == NULL) && (errno != 0)) {
                        /* root reading across NFS can get to this error case */
                        printf(gettxt("ls:6", "%s unreadable\n"), dir); /* not stderr ! */
                        closedir(dirf);
                        err = 2;
                        return;
                }
		tblocks = 0;
		do {
			if (aflg==0 && dentry->d_name[0]=='.' 
# ifndef DOTSUP
				&& (dentry->d_name[1]=='\0' || dentry->d_name[1]=='.'
				&& dentry->d_name[2]=='\0')
# endif
				)  /* check for directory items '.', '..', 
				   *  and items without valid inode-number;
				   */
				continue;
			if (Cflg || mflg) {
				width = strlen(dentry->d_name);
				width -= setcount(dentry->d_name);
				if (width > filewidth)
					filewidth = width;
			}
			ep = gstat(makename(dir, dentry->d_name), 0);
			if (ep==NULL)
				continue;
			else {
			     ep->lnum = dentry->d_ino;
			     if (iflg && (ep->lnum > 99999)) {
				 n = log10((double) ep->lnum) + 1;
				 if (n > ndigits)
					ndigits = n;
			     }
			     for (j=0; dentry->d_name[j] != '\0'; j++)
				 ep->ln.lname[j] = dentry->d_name[j];
			     ep->ln.lname[j] = '\0';
			}
		} while (dentry = readdir(dirf));
		(void) closedir(dirf);
		colwidth = fixedwidth + filewidth;
		if (iflg && (ndigits > 5))
			colwidth += (ndigits - 5);
	}
}

/* get status of file and recomputes tblocks;
 * argfl = 1 if file is a name in ls-command and  = 0
 * for filename in a directory whose name is an
 * argument in the command;
 * stores a pointer in flist[nfiles] and
 * returns that pointer;
 * returns NULL if failed;
 */
struct lbuf *
gstat(file, argfl)
char *file;
{
	struct stat statb, stb1;
	register struct lbuf *rep;
	static int nomocore;
	int cc, n;
	char *malloc(), *realloc();
	long block_size, frag_size;	/* block and frag sizes */

	int (*statf)() = Lflg ? stat : lstat;
	char buf[BUFSIZ+1];

	if (nomocore)
		return(NULL);
	else if (nfiles >= maxfils) { 
/* all flist/lbuf pair assigned files time to get some more space */
		maxfils += quantn;
		if((flist=(struct lbuf **)realloc((char *)flist, (unsigned)(maxfils * sizeof(struct lbuf *)))) == NULL
		|| (nxtlbf = (struct lbuf *)malloc((unsigned)(quantn * sizeof(struct lbuf)))) == NULL) {
			fprintf(stderr, gettxt("ls:3","ls: out of memory\n"));
			nomocore = 1;
			return(NULL);
		}
	}

/* nfiles is reset to nargs for each directory
 * that is given as an argument maxn is checked
 * to prevent the assignment of an lbuf to a flist entry
 * that already has one assigned.
 */
	if(nfiles >= maxn) {
		rep = nxtlbf++;
		flist[nfiles++] = rep;
		maxn = nfiles;
	}else {
		rep = flist[nfiles++];
	}
	rep->lflags = 0;
	rep->linkto = (char *) 0;
	if (argfl || statreq) {
		if ((*statf)(file, &statb) < 0) {
			if (statf == lstat || lstat(file, &statb) < 0) {
				perror(file);
				nfiles--;
				return (0);
			}
		}
		if (link_statfs(file, &statb, &block_size, &frag_size) < 0) {
			perror(file);
			nfiles--;
			return(NULL);
		}
		if ((*statf)(file, &statb) < 0) {
			if (statf == lstat || lstat(file, &statb) < 0) {
				perror(file);
				nfiles--;
				return (0);
			}
		}
		rep->lnum = statb.st_ino;
		if (iflg && (statb.st_ino > 99999)) {
			n = log10((double) statb.st_ino) + 1;
			if (n > ndigits)
				ndigits = n;
		}
		rep->lsize = statb.st_size;
		rep->lblocks = nblocks(statb.st_size, block_size, frag_size);
		switch(statb.st_mode&S_IFMT) {
		case S_IFDIR:
			rep->ltype = 'd';
			break;
		case S_IFBLK:
			rep->ltype = 'b';
			rep->lsize = statb.st_rdev;
			break;
		case S_IFCHR:
			rep->ltype = 'c';
			rep->lsize = statb.st_rdev;
			break;
		case S_IFIFO:
			rep->ltype = 'p';
			break;
		case S_IFLNK:
			rep->ltype = 'l';
			if (lflg) {
				buf[0] = 0;
				cc = readlink(file, buf, BUFSIZ);
				if (cc >= 0) {
					/*
					 * here we follow the symbolic
					 * link to generate the proper
					 * Fflg marker for the object,
					 * eg, /bin -> /pub/bin/
					 *
					 * Note: We do not stat file if a null
					 * symlink.  Must set "linkto" so it points
					 * to a null, for the later code that
					 * creates "dmark" to work.  It needs to
					 * find that "linkto" is non-null.
					 */
					if (cc && Fflg && !stat(file, &stb1))
						switch (stb1.st_mode & S_IFMT){
						case S_IFDIR:
							buf[cc++] = '/';
							break;
						default:
						if ( (stb1.st_mode & ~S_IFMT)
						    & 0111)
							buf[cc++] = '*';
							break;
						}
					buf[cc] = 0;
					rep->linkto = savestr(buf);
				}
				break;
			}
			/*
			*  this is a hack from UCB to avoid having 
			*  ls /bin behave differently from ls /bin/
			*  when /bin is a symbolic link.  We hack the
			*  hack to have that happen, but only for
			*  explicit arguments, by inspecting argfl.
			*/
			if (argfl == 0 || stat(file, &stb1) < 0)
				break;
			if ((stb1.st_mode & S_IFMT) == S_IFDIR) {
				statb = stb1;
				rep->ltype = 'd';
				rep->lsize = statb.st_size;
				rep->lblocks = nblocks(statb.st_size,
				  block_size, frag_size);
			}
			break;
		default:
			rep->ltype = '-';
		}
		rep->lflags = statb.st_mode & ~S_IFMT;
		/* mask ISARG and other file-type bits */
		rep->luid = statb.st_uid;
		rep->lgid = statb.st_gid;
		rep->lnl = statb.st_nlink;
		if(uflg)
			rep->lmtime = statb.st_atime;
		else if (cflg)
			rep->lmtime = statb.st_ctime;
		else
			rep->lmtime = statb.st_mtime;
		if (rep->ltype != 'b' && rep->ltype != 'c')
		   tblocks += rep->lblocks;
	}
	return(rep);
}

/*
long nblock(size)
long size;
{
	long blocks, tot;

	blocks = tot = (size + NBPSCTR - 1) >> SCTRSHFT;
	if(blocks > DIRECT)
		tot += ((blocks - DIRECT - 1) >> INSHFT) + 1;
	if(blocks > DIRECT + INDIR)
		tot += ((blocks - DIRECT - INDIR - 1) >> (INSHFT * 2)) + 1;
	if(blocks > DIRECT + INDIR + INDIR*INDIR)
		tot++;
	return(tot);
}
*/

/* returns pathname of the form dir/file;
 *  dir is a null-terminated string;
 */
char *
makename(dir, file) 
char *dir, *file;
{
	static char dfile[MAXNAMLEN];  /*  Maximum length of a
					*  file/dir name in ls-command;
					*  dfile is static as this is returned
					*  by makename();
					*/
	register char *dp, *fp;
	register int i;

	dp = dfile;
	fp = dir;
	while (*fp)
		*dp++ = *fp++;
	if (dp > dfile && *(dp - 1) != '/')
		*dp++ = '/';
	fp = file;
	for (i=0; *fp != '\0'; i++)
		*dp++ = *fp++;
	*dp = '\0';
	return(dfile);
}

/*
 * getname_uid - Get a user name given user id.
 *
 */

char *
getname_uid(uid)
uid_t uid;
{
	register int i, s, h;
	struct passwd *pw;

	/*
	 * Typical case is that we are called with the same uid as in
	 * our last call so we keep possibility of a quick return of
	 * the most recent user.
	 */

	if (   (uid == lastu)
	    && (u_vals[lastu_index] == uid)
	    && (u_names[lastu_index][0] != '\0') )
		return(&u_names[lastu_index][0]);

	/* Well we didn't have it.  So see if it is in the hash table. */

	h = uid % MAX_UID;

	for (i = 0, s = h; i < BUCKET_LEN; i++, s++) {
		if (s >= MAX_UID)
			s = 0;
		if ( (u_vals[s] == uid) && (u_names[s][0] != '\0') )
			return(&u_names[s][0]);
		if (u_names[s][0] == '\0')
			break;
	}

	/*
	 * Not in the table at all.  So we will have to look it up
	 * and insert it.  If index i from the for loop is less than
	 * the number of alternate slots we can use we want to insert
	 * at that index (the next free one).  If all were filled then
	 * we overwrite the slot found by direct hash.
	 */
	
	if ((pw = getpwuid(uid)) == (struct passwd *) NULL)
		return((char *) NULL);

	if (i == BUCKET_LEN)
		s = h;

	SCPYN(u_names[s], pw->pw_name);
	u_vals[s] = uid;
	lastu_index = s;
	lastu = uid;

	return(&u_names[s][0]);
}

/*
 * getname_gid - Get a group name given group id.
 *
 */

char *
getname_gid(gid)
gid_t gid;
{
	register int i, s, h;
	struct group *gr;

	/*
	 * Typical case is that we are called with the same gid as in
	 * our last call so we keep possibility of a quick return of
	 * the most recent group.
	 */

	if (   (gid == lastg)
	    && (g_vals[lastg_index] == gid)
	    && (g_names[lastg_index][0] != '\0') )
		return(&g_names[lastg_index][0]);

	/* Well we didn't have it.  So see if it is in the hash table. */

	h = gid % MAX_GID;

	for (i = 0, s = h; i < BUCKET_LEN; i++, s++) {
		if (s >= MAX_GID)
			s = 0;
		if ( (g_vals[s] == gid) && (g_names[s][0] != '\0') )
			return(&g_names[s][0]);
		if (g_names[s][0] == '\0')
			break;
	}

	/*
	 * Not in the table at all.  So we will have to look it up
	 * and insert it.  If index i from the for loop is less than
	 * the number of alternate slots we can use we want to insert
	 * at that index (the next free one).  If all were filled then
	 * we overwrite the slot found by direct hash.
	 */

	if ((gr = getgrgid(gid)) == (struct group *) NULL)
		return((char *) NULL);

	if (i == BUCKET_LEN)
		s = h;

	SCPYN(g_names[s], gr->gr_name);
	g_vals[s] = gid;
	lastg_index = s;
	lastg = gid;

	return(&g_names[s][0]);
}

compar(pp1, pp2)  /* return >0 if item pointed by pp2 should appear first */
struct lbuf **pp1, **pp2;
{
	register struct lbuf *p1, *p2;

	p1 = *pp1;
	p2 = *pp2;
	if (dflg==0) {
/* compare two names in ls-command one of which is file
 *  and the other is a directory;
 *  this portion is not used for comparing files within
 *  a directory name of ls-command;
 */
		if (p1->lflags&ISARG && p1->ltype=='d') {
			if (!(p2->lflags&ISARG && p2->ltype=='d'))
				return(1);
		}
		else {
			if (p2->lflags&ISARG && p2->ltype=='d')
				return(-1);
		}
	}
	if (tflg) {
		if(p2->lmtime == p1->lmtime)
			return(0);
		else if(p2->lmtime > p1->lmtime)
			     return(rflg);
		else return(-rflg);
	}
	else
	     return(rflg * strcmp(p1->lflags&ISARG? p1->ln.namep: p1->ln.lname,
				p2->lflags&ISARG? p2->ln.namep: p2->ln.lname));
}

pprintf(s1,s2)
	char *s1, *s2;
{
	register char *s;
	register int   c;
	register int  cc;
	int i;

	for (s = s1, i = 0; i < 2; i++, s = s2)
		while(c = (unsigned char) *s++) {
		/* only use isprint module when current system is for	*/
		/* single-byte language or an character is within ascii	*/
		/* in case of multi-byte language system.		*/
		/* other multi-byte character is regarded as graphic	*/
		/* character.						*/
		/* 	if ( ! isprint(c) ) {		*/
			if ( ! ISPRINT(c, wp) ) {
				if (qflg)
					c = '?';
				else if (bflg) {
					curcol += 3;
					putc ('\\', stdout);
					cc = '0' + (c>>6 & 07);
					putc (cc, stdout);
					cc = '0' + (c>>3 & 07);
					putc (cc, stdout);
					c = '0' + (c & 07);
				}
			}
			curcol++;
			putc(c, stdout);
		}
		curcol -= setcount(s1);
}

/*
 * Concerning the wrong alignment of filenames in case of x or C option.
 * This function counts the number of shift codes and the difference 
 * between EUC width and display width.
 * And returns with that value.
 */
setcount(s)
register char *s;
{
	register int cnt=0;
	register char c;
	register int i;

	while (c = *s++) {
		if (NOTASCII(c)) {
			i = ISSET2(c) ? difset2 :
					ISSET3(c) ? difset3 : difset1 ;

			if (ISSET2(c) || ISSET3(c)) {
				cnt++;
				s++;
			}

			if (i > 0)
				s += i;
			else if (i < 0)
				cnt -= i;
		}
	}
	return (cnt);
}

char *
savestr(str)
	char *str;
{
	char *cp = malloc(strlen(str) + 1);

	if (cp == NULL) {
		fprintf(stderr, gettxt("ls:3","ls: out of memory\n"));
		exit(1);
	}
	(void) strcpy(cp, str);
	return (cp);
}

/*
 * link_statfs()
 *
 * Either stat the file that a sym link points to (typical case).
 * But if the -L flag was used, or the sym link point to a file
 * that doesn't exist, then stat the "base" of the file (strip off
 * the last components of the path until we find something that
 * isn't a sym link.
 */

extern int errno;

int   cache_valid;
dev_t cache_dev;
long  cache_block_size;
long  cache_frag_size;

link_statfs(file, statb, block_size, frag_size)
	char *file;
	struct stat *statb;
	long *block_size;
	long *frag_size;
{
	int e;
	struct stat sb;
	struct statfs statfsb;
	char buf[MAXPATHLEN];
	char *p;

	if (Lflg == 0) {
		/* See if this device in cache */
		if (cache_valid) {
			if (statb->st_dev == cache_dev) {
				*block_size = cache_block_size;
				*frag_size = cache_frag_size;
				return(0);
			}
		}
		/* Was not in cache. */
		if (statfs(file, &statfsb, sizeof(struct statfs), 0) < 0) {
			e = errno;
			if ((statb->st_mode & S_IFMT) != S_IFLNK) {
				errno = e;
				return(-1);
			}
			/*
			 * It was a link and probably did not exist.
			 * So we will just find some component of the
			 * path that does exist.
			 * Pardon the use of goto's.
			 */
			goto trim_path;
		}
		cache_valid = 1;
		cache_dev = statb->st_dev;
		*block_size = cache_block_size = statfsb.f_bsize;
		*frag_size = cache_frag_size = statfsb.f_frsize;
		return(0);
	}

	/*
	 * Lflg so we want the base if it is a symbolic link.
	 * Otherwise just the normal statfs will do.
	 */

	if (lstat(file, &sb) < 0)
		return(-1);
	
	if ((sb.st_mode & S_IFMT) != S_IFLNK) {
		if (cache_valid) {
			if (sb.st_dev == cache_dev) {
				*block_size = cache_block_size;
				*frag_size = cache_frag_size;
				return(0);
			}
		}
		if (statfs(file, &statfsb, sizeof(struct statfs), 0) < 0)
			return(-1);
		cache_valid = 1;
		cache_dev = statb->st_dev;
		*block_size = cache_block_size = statfsb.f_bsize;
		*frag_size = cache_frag_size = statfsb.f_frsize;
		return(0);
	}

	/*
	 * Well it is a sym link.  We are going to remove ending components
	 * from the path.  Eventually we end up with some path that is not
	 * a symbolic link and do a statfs on that.  If we run out of path
	 * we simply do a statfs on "." which had better work!
	 *
	 * Don't update cache on symlinks.
	 */
	
trim_path:

	strncpy(buf, file, MAXPATHLEN);
	for (;;) {
		p = strrchr(buf, '/');
		if (p == (char *) NULL)
			break;
		/* First char may have been "/", prevent walking back that far. */
		if (p == buf)
			break;
		*p = 0;
		if (lstat(buf, &sb) < 0)
			return(-1);
		if ((sb.st_mode & S_IFMT) != S_IFLNK) {
			if (cache_valid) {
				if (sb.st_dev == cache_dev) {
					*block_size = cache_block_size;
					*frag_size = cache_frag_size;
					return(0);
				}
			}
			if (statfs(buf, &statfsb, sizeof(struct statfs), 0) < 0)
				return(-1);
			else {
				*block_size = statfsb.f_bsize;
				*frag_size = statfsb.f_frsize;
				return(0);
			}
		}
	}


	if (statfs(".", &statfsb, sizeof(struct statfs), 0) < 0)
		return(-1);
	else {
		*block_size = statfsb.f_bsize;
		*frag_size = statfsb.f_frsize;
		return(0);
	}
}
