/*
 * stat, fstat, lstat emulation for TOS
 *
 * written by Eric R. Smith and Jwahar Bammi, based on the original
 * from the old GCC library (which original was copyright 1988
 * Memorial University).
 */

#include	<assert.h>
#include	<stdio.h>
#include	<types.h>
#include	<stat.h>
#include	<ctype.h>
#include	<errno.h>
#include	<osbind.h>
#include	<unistd.h>
#include	<support.h>
#include	<string.h>
#include	<dirent.h>
#include	<device.h>
#include	"symdir.h"
#include	<time.h>
#include	"lib.h"
#include	<memory.h>

#ifndef NAME_MAX
#  include <limits.h>
#endif

#ifndef _LIB_NAME_MAX
#  define _LIB_NAME_MAX NAME_MAX
#endif


ino_t	__inode = 32;		/* used in readdir also */
int	_x_Bit_set_in_stat = 0;	/* default 0, need change in emacs:sysdep.c */

DIR *__opendir_chain = 0;	/* chain of open directories from opendir */

/* date for files (like root directories) that don't have one */
#define OLDDATE _unixtime(0,0)

/*
 * check file cache for the file whose canonical (_unx2dos) name is fullname.
 * this is only valid for TOS files, i.e. files that are NOT symbolic
 * links. If file is not found in cache, use Fsfirst to look it up.
 * All open directories (via opendir) are cached (we don't lose much by
 * doing this, and it's a good heuristic for which files are going to
 * need stat() in the near future; plus, opendir did Fsfirst()/Fsnext()
 * already. Programs like "ls" should win big time.
 *
 * BUG/FEATURE: if the _lHIDE flag is set, opendir() never records the
 * existence of .dir, and so stat() in an open directory won't find it.
 */

struct dirent *
_do_stat(fullname)
char *fullname;
{
	char path[FILENAME_MAX], name[_LIB_NAME_MAX], *tmp;
	DIR *dir;
	struct dirent *d;
	static struct dirent dtmp;
	static struct _dta	dtabuf;
	struct _dta *olddta;
	int r;

	strcpy(path, fullname);
	if((tmp = strrchr(path, '\\'))) {
		*tmp++ = 0;
		_dos2unx(tmp, name);
	}
	else {
		_dos2unx(path, name);
		path[0] = 0;
	}

/*
 * search through the chain of open directories, 1 at a time
 */
	for (dir = __opendir_chain; dir; dir = dir->D_nxtdir) {
		if (!strcmp(path, dir->D_path)) {
			for (d = dir->D_list; d; d = d->d_next) {
				if (!strcmp(name,d->d_name) &&
				    d->d_attribute != 0xff) {
					return d;
				}
			}
		}
	}
	d = &dtmp;
	olddta = (struct _dta *)Fgetdta();
	Fsetdta(&dtabuf);
	r = Fsfirst(fullname, FA_SYSTEM|FA_HIDDEN|FA_DIR);
	Fsetdta(olddta);
	if (r < 0) {
		errno = -r;
		return NULL;
	}
	d->d_ino = __inode++;
	d->d_time = dtabuf.dta_time;
	d->d_date = dtabuf.dta_date;
	d->d_attribute = dtabuf.dta_attribute;
	d->d_size = dtabuf.dta_size;

	return d;
}

static int
_stat(_path, st)
	const char	*_path;
	struct stat	*st;
{
	int	rval, nval;
	char	path[FILENAME_MAX];
	char	*ext, drv;
	int	fd;
	short	magic;
	struct dirent *d;
	struct _device *device;

	if (!_path) {
		errno = EFAULT;
		return -1;
	}

/*
 * NOTE: the new _unx2dos gives a complete, canonical TOS pathname
 * (i.e. stripped of . and .., and with a drive letter). It also returns
 * some useful information about what the given filename was (e.g. symlink,
 * or device)
 */
	nval = _unx2dos(_path, path);
	if (nval == _NM_DEV) {
		device = _dev_dosname(path);
		st->st_mode = S_IFCHR | 0600;
		st->st_attr = 0xfe;
		st->st_ino = st->st_rdev = device->dev;
		st->st_mtime = st->st_ctime = st->st_atime = 
			time((time_t *)0) - 2;
		st->st_dev = 0;
	/* if _tDEV is on, /dev/console & CON: are the same file */
		st->st_nlink = _tDEV ? 2 : 1;
		st->st_uid = geteuid();
		st->st_gid = getegid();
		st->st_blksize = 1024;
		return 0;
	}

	/* Deal with the root directory of a logical drive */
	if (path[0] == '\\' && path[1] == 0) {
		drv = Dgetdrv() + 'A';
		goto rootdir;
	}

	if ( (drv = path[0]) && path[1] == ':' &&
	     (path[2] == 0 || (path[2] == '\\' && path[3] == 0)) ) {
rootdir:
		st->st_mode = S_IFDIR | 0755;
		st->st_attr = FA_DIR;
		st->st_ino = isupper(drv) ? drv - 'A' : drv - 'a';
		st->st_mtime = st->st_ctime = st->st_atime = OLDDATE;
		goto fill_dir;
	}

	/* forbid wildcards in path names */
	if (index(path, '*') || index(path, '?')) {
		errno = ENOENT;
		return -1;
	}

	if (!(d = _do_stat(path))) {
	/* errno was set by _do_stat */
		if ((errno == ENOENT || errno == EPATH) && nval == _NM_LINK) {
		/* here we have a symbolic link to a deleted file */
			st->st_mode = S_IFLNK | 0644;
			st->st_ino = ++__inode;
			st->st_attr = 0xff;
			st->st_mtime = st->st_ctime = st->st_atime = OLDDATE;
			st->st_size = strlen(__link_to);
			st->st_blocks = 1;
			st->st_nlink = 1;
			goto fill_rest;
		}
		return -1;
	}
	st->st_mtime = st->st_ctime = st->st_atime =
		_unixtime(d->d_time, d->d_date);
	st->st_ino = d->d_ino;
	st->st_attr = d->d_attribute;
	st->st_mode = 0644 | (d->d_attribute & FA_DIR ?
			      S_IFDIR | 0111 : S_IFREG);
	if (d->d_attribute & FA_RDONLY)
		st->st_mode &= ~0222;	/* no write permission */
	if (d->d_attribute & FA_HIDDEN)
		st->st_mode &= ~0444;	/* no read permission */

/* check for a file with an executable extension */
	ext = strrchr(_path, '.');
	if (ext) {
		if (!strcmp(ext, ".ttp") || !strcmp(ext, ".prg") ||
		    !strcmp(ext, ".tos") || !strcmp(ext, ".g") ||
		    !strcmp(ext, ".sh")	 || !strcmp(ext, ".bat")) {
			st->st_mode |= 0111;
		}
	}
	if ( (st->st_mode & S_IFMT) == S_IFREG) {
		if (_x_Bit_set_in_stat) {
			if ((fd = Fopen(path,0)) < 0) {
				errno = -rval;
				return -1;
			}
			(void)Fread(fd,2,(char *)&magic);
			(void)Fclose(fd);
			if (magic == 0x601A) st->st_mode |= 0111;
		}
		st->st_size = d->d_size;
		st->st_blocks = (d->d_size + 1023) / 1024;
		st->st_nlink = 1; /* we dont have hard links */
	} else {
fill_dir:
		st->st_size = 1024;
		st->st_blocks = 1;
		st->st_nlink = 2;	/* "foo" && "foo/.." */
	}

fill_rest:
	if ((drv = *path) && path[1] == ':')
		st->st_dev = islower(drv) ? drv - 'a' : drv - 'A';
	else
		st->st_dev = Dgetdrv();
	st->st_rdev = 0;
	st->st_uid = geteuid();	/* the current user owns every file */
	st->st_gid = getegid();
	st->st_blksize = 1024;
	return nval;
}

int
stat(path, st)
	const char	*path;
	struct stat	*st;
{
	return _stat(path, st) < 0 ? -1 : 0;
}

#include <fcntl.h>
#include <memory.h>

int fstat(fd, st)
int fd;
struct stat *st;
{
    int old;
    int r;
    struct _device *dev = _dev_fd(fd);
    
    if(dev)
    {
	char *devname = (char *)alloca(strlen(dev->unxnm) + 6L);
	(void) strcpy(devname, "/dev/");
	(void) strcat(devname, dev->unxnm);
	return stat(devname, st);
    }
    
    fd = __OPEN_INDEX(fd);
    if((fd >= 0) && (fd < __NHANDLES))
    {
	if (__open_stat[fd].filename) {
	    /* we should turn off links, because the name we're going to give
	     * to 'stat' has already been _unx2dos'd
	     */
	    old = _lOK;
	    _lOK = 0;
	    r = stat(__open_stat[fd].filename, st);
	    _lOK = old;
	    return r;
	} else {
	    _DOSTIME timestruct;
	    
	    /* the following code is stolen from Eric R. Smith */
	    r = Fdatime(&timestruct, fd, 0);
	    if (r < 0) {			/* assume TTY */
		st->st_mode = S_IFCHR | 0600;
		st->st_attr = 0;
		st->st_mtime = st->st_ctime = st->st_atime =
		    time((time_t *)0) - 2;
		st->st_size = 0;
	    } else {
		long oldplace;
		
		st->st_mtime = st->st_atime = st->st_ctime =
		    _unixtime(timestruct.time,timestruct.date);
		st->st_mode = S_IFREG | 0644;		/* this may be false */
		st->st_attr = 0;			/* because this is */
		
		/* get current file location */
		oldplace = Fseek(0L, fd, SEEK_CUR);
		if (oldplace < 0) {		/* can't seek -- must be pipe */
		    st->st_mode = S_IFIFO | 0644;
		    st->st_size = 1024;
		} else {
		    short magic;
		    r = Fseek(0L, fd, SEEK_END);	/* go to end of file */
		    st->st_size = r;
		    (void)Fseek(0L, fd, SEEK_SET);	/* go to start of file */
		    /* check for executable file */
		    if (_x_Bit_set_in_stat && 
			Fread(fd, 2, (char *)&magic) == 2) {
			if (magic == 0x601a || magic == 0x2321)
			    st->st_mode |= 0111;
		    }
		    (void)Fseek(oldplace, fd, SEEK_SET);
		}
	    }
	    
	    /* all this stuff is likely bogus as well. sigh. */
	    st->st_dev = Dgetdrv();
	    st->st_rdev = 0;
	    st->st_uid = getuid();
	    st->st_gid = getgid();
	    st->st_blksize = 1024;
	    /* note: most Unixes measure st_blocks in 512 byte units */
	    st->st_blocks = (st->st_size + 511) / 512;
	    st->st_ino = ++__inode;
	    st->st_nlink = 1;
	    return 0;
	    
	}
    }
    errno = EBADF;
    return -1;
}

/*
 * "stat" that doesn't follow symbolic links
 * important exception: if _lAUTO is set and the link is an automatic
 * link, follow it anyways. This allows automatic links to be aliases
 * for real files (stat will correctly return the info about the
 * automatic link if the real file has been deleted).
 */

int
lstat(_path, st)
	const char *_path;
	struct stat *st;
{
	int r;

/* _unx2dos returns _NM_LINK if the last path component was a symbolic link
 * in this case, __link_name and __link_path are set to the name and path
 * of the symbolic link file, __link_to to its contents, and __link_flags
 * its flags.
 */
	r = _stat(_path, st);

	if (r < 0)
		return r;

	if (r == _NM_LINK && !(__link_flags & SD_AUTO))
	{
		st->st_size = strlen(__link_to);
		st->st_blocks = 1;
		st->st_mode = ((st->st_mode & ~S_IFMT) | S_IFLNK);
	}
	return 0;
}
