/*
 * stat, fstat, lstat emulation for TOS
 * written by Eric R. Smith and placed in the public domain
 */

#include <limits.h>
#include <types.h>
#include <stat.h>
#include <ctype.h>
#include <errno.h>
#include <osbind.h>
#include <mintbind.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <ioctl.h>	/* for FSTAT */
#include "lib.h"

extern int __mint;

ino_t	__inode = 32;		/* used in readdir also */

/* for backwards compatibilty: if nonzero, files are checked to see if
 * they have the TOS executable magic number in them
 */

int	_x_Bit_set_in_stat = 0;



/* date for files (like root directories) that don't have one */
#define OLDDATE _unixtime(0,0)

/*
 * macro for converting a long in DOS format to one in Unix format. "x"
 * _must_ be an lvalue!
 */

#define CONVERT(x) (x = _unixtime( ((short *)&x)[0], ((short *)&x)[1] ))

/*
 * common routine for stat() and lstat(); if "lflag" is 0, then symbolic
 * links are automatically followed (like stat), if 1 then they are not
 * (like lstat)
 */

static int do_stat __PROTO((const char *_path, struct stat *st, 
	int lflag));

static int
do_stat(_path, st, lflag)
	const char *_path;
	struct stat *st;
	int lflag;
{
	long	r;
	_DTA	*olddta;
	int	nval;
	char	path[PATH_MAX];
	char	*ext, drv;
	int	fd;
	short	magic;
	_DTA	d;
	int	isdot = 0;

	if (!_path) {
		errno = EFAULT;
		return -1;
	}

/*
 * _unx2dos returns 1 for device names (like /dev/con)
 */
	nval = _unx2dos(_path, path);

/* for MiNT 0.9 and up, we use the built in stat() call */
	if (__mint >= 9) {
		r = Fxattr(lflag, path, st);
		if (r) {
			errno = (int) -r;
			return -1;
		}
		CONVERT(st->st_mtime);
		CONVERT(st->st_atime);
		CONVERT(st->st_ctime);
	/* Most versions of Unix count in 512 byte blocks */
		st->st_blocks = (st->st_blocks * st->st_blksize) / 512;
		return 0;
	}

/* otherwise, check to see if we have a name like CON: or AUX: */
	if (nval == 1) {
		st->st_mode = S_IFCHR | 0600;
		st->st_attr = 0;
		st->st_ino = ++__inode;
		st->st_rdev = 0;
		st->st_mtime = st->st_ctime = st->st_atime = 
			time((time_t *)0) - 2;
		st->st_dev = 0;
		st->st_nlink = 1;
		st->st_uid = geteuid();
		st->st_gid = getegid();
		st->st_size = st->st_blocks = 0;
		st->st_blksize = 1024;
		return 0;
	}

/* A file name: check for root directory of a drive */
	if (path[0] == '\\' && path[1] == 0) {
		drv = Dgetdrv() + 'A';
		goto rootdir;
	}

	if ( ((drv = path[0]) != 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_dev = isupper(drv) ? drv - 'A' : drv - 'a';
		st->st_ino = 2;
		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;
	}

/* OK, here we're going to have to do an Fsfirst to get the date */
/* NOTE: Fsfirst(".",-1) or Fsfirst("..",-1) both fail under TOS,
 * so we kludge around this by using the fact that Fsfirst(".\*.*"
 * or "..\*.*" will return the correct file first (except, of course,
 * in root directories :-( ).
 */

/* find the end of the string */
	for (ext = path; ext[0] && ext[1]; ext++) ;

/* add appropriate kludge if necessary */
	if (*ext == '.' && (ext == path || ext[-1] == '\\' || ext[-1] == '.')) {
		isdot = 1;
		strcat(path, "\\*.*");
	}
	olddta = Fgetdta();
	Fsetdta(&d);
	r = Fsfirst(path, 0xff);
	Fsetdta(olddta);
	if (r < 0) {
		if (isdot && r == -ENOENT) goto rootdir;
		errno = (int) -r;
		return -1;
	}	

	if (isdot && ((d.dta_name[0] != '.') || (d.dta_name[1]))) {
		goto rootdir;
	}

	st->st_mtime = st->st_ctime = st->st_atime =
		_unixtime(d.dta_time, d.dta_date);
	if (((drv = *path) != 0) && path[1] == ':')
		st->st_dev = toupper(drv) - 'A';
	else
		st->st_dev = Dgetdrv();

	st->st_ino = __inode++;
	st->st_attr = d.dta_attribute;
	if (__mint && st->st_dev == ('Q' - 'A'))
			st->st_mode = 0644 | S_IFIFO;
	else {
		st->st_mode = 0644 | (st->st_attr & FA_DIR ?
			      S_IFDIR | 0111 : S_IFREG);
	}

	if (st->st_attr & FA_RDONLY)
		st->st_mode &= ~0222;	/* no write permission */
	if (st->st_attr & 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 = (int) Fopen(path,0)) < 0) {
				errno = -fd;
				return -1;
			}
			magic = 0;
			(void)Fread(fd,2,(char *)&magic);
			(void)Fclose(fd);
			if (magic == 0x601A	/* TOS executable */
			    || magic == 0x2321) /* "#!" shell file */
				st->st_mode |= 0111;
		}
		st->st_size = d.dta_size;
	/* in Unix, blocks are measured in 512 bytes */
		st->st_blocks = (st->st_size + 511) / 512;
		st->st_nlink = 1; /* we dont have hard links */
	} else {
fill_dir:
		st->st_size = 1024;
		st->st_blocks = 2;
		st->st_nlink = 2;	/* "foo" && "foo/.." */
	}

	st->st_rdev = 0;
	st->st_uid = geteuid();	/* the current user owns every file */
	st->st_gid = getegid();
	st->st_blksize = 1024;
	return 0;
}


/* 
 * fstat: if we're not running under MiNT, this is pretty bogus.
 * what we can really find is:
 * modification time: via Fdatime()
 * file size: via Fseek()
 * fortunately, these are the things most programs are interested in.
 * BUG: passing an invalid file descriptor gets back a stat structure for
 * a tty.
 */

int
fstat(fd, st)
int fd;
struct stat *st;
{
	long oldplace, r;
	_DOSTIME timeptr;
	short magic;

	if (__mint >= 9) {		/* use FSTAT Fcntl */
		r = Fcntl(fd, st, FSTAT);
		if (r) {
			errno = (int) -r;
			return -1;
		}
		CONVERT(st->st_mtime);
		CONVERT(st->st_atime);
		CONVERT(st->st_ctime);
		st->st_blocks = (st->st_blocks * st->st_blksize) / 512;
		return 0;
	}

	r = Fdatime(&timeptr, 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 {
		st->st_mtime = st->st_atime = st->st_ctime =
			_unixtime(timeptr.time, timeptr.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 {
			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 (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;
}

int
lstat(path, st)
	const char *path;
	struct stat *st;
{
	return do_stat(path, st, 1);
}

int
stat(path, st)
	const char *path;
	struct stat *st;
{
	return do_stat(path, st, 0);
}
