/*
 * This file has been modified as part of the MH-MiNT project. See
 * the file Changes.MH for details and dates.
 */

/*
Copyright 1991,1992 Eric R. Smith.
Copyright 1992,1993,1994 Atari Corporation.
All rights reserved.
 */

/* PROC pseudo-filesystem routines */
/* basically just to allow 'ls -l X:' to give a list of active processes
 * some things to note:
 * process names are given as name.XXX, where 'XXX' is the pid of the
 *   process
 * process attributes depend on the run queue as follows:
 *   RUNNING:	0x00		(normal)
 *   READY:	0x01		(read-only)
 *   WAIT:	0x20		(archive bit)
 *   IOBOUND:	0x21		(archive bit+read-only)
 *   ZOMBIE:	0x22		(archive+hidden)
 *   TSR:	0x02		(hidden)
 *   STOP:	0x24		(archive bit+system)
 * the general principle is: inactive processes have the archive bit (0x20)
 * set, terminated processes have the hidden bit (0x02) set, stopped processes
 * have the system bit (0x04) set, and the read-only bit is used to
 * otherwise distinguish states (which is unfortunate, since it would be
 * nice if this bit corresponded with file permissions).
 */

#include "mint.h"


static long	ARGS_ON_STACK proc_root	P_((int drv, fcookie *fc));
static long	ARGS_ON_STACK proc_lookup	P_((fcookie *dir, const char *name, fcookie *fc));
static long	ARGS_ON_STACK proc_getxattr	P_((fcookie *fc, XATTR *xattr));
static long	ARGS_ON_STACK proc_chattr	P_((fcookie *fc, int attrib));
static long	ARGS_ON_STACK proc_chown	P_((fcookie *fc, int uid, int gid));
static long	ARGS_ON_STACK proc_chmode	P_((fcookie *fc, unsigned mode));
static long	ARGS_ON_STACK proc_rmdir	P_((fcookie *dir, const char *name));
static long	ARGS_ON_STACK proc_remove	P_((fcookie *dir, const char *name));
static long	ARGS_ON_STACK proc_getname	P_((fcookie *root, fcookie *dir, char *pathname,
						    int size));
static long	ARGS_ON_STACK proc_rename	P_((fcookie *olddir, char *oldname,
				    fcookie *newdir, const char *newname));
static long	ARGS_ON_STACK proc_opendir	P_((DIR *dirh, int flags));
static long	ARGS_ON_STACK proc_readdir	P_((DIR *dirh, char *nm, int nmlen, fcookie *));
static long	ARGS_ON_STACK proc_rewinddir	P_((DIR *dirh));
static long	ARGS_ON_STACK proc_closedir	P_((DIR *dirh));
static long	ARGS_ON_STACK proc_pathconf	P_((fcookie *dir, int which));
static long	ARGS_ON_STACK proc_dfree	P_((fcookie *dir, long *buf));
static DEVDRV *	ARGS_ON_STACK proc_getdev	P_((fcookie *fc, long *devsp));

static long	ARGS_ON_STACK proc_open	P_((FILEPTR *f));
static long	ARGS_ON_STACK proc_write	P_((FILEPTR *f, const char *buf, long bytes));
static long	ARGS_ON_STACK proc_read	P_((FILEPTR *f, char *buf, long bytes));
static long	ARGS_ON_STACK proc_lseek	P_((FILEPTR *f, long where, int whence));
static long	ARGS_ON_STACK proc_ioctl	P_((FILEPTR *f, int mode, void *buf));
static long	ARGS_ON_STACK proc_datime	P_((FILEPTR *f, short *time, int rwflag));
static long	ARGS_ON_STACK proc_close	P_((FILEPTR *f, int pid));
static long ARGS_ON_STACK proc_readlabel P_((fcookie *dir, char *name, int namelen));

/* dummy routines from biosfs.c */
extern long	ARGS_ON_STACK null_select	P_((FILEPTR *f, long p, int mode));
extern void	ARGS_ON_STACK null_unselect	P_((FILEPTR *f, long p, int mode));

static PROC *	name2proc	P_((const char *name));


DEVDRV proc_device = {
	proc_open, proc_write, proc_read, proc_lseek, proc_ioctl, proc_datime,
	proc_close, null_select, null_unselect
};

FILESYS proc_filesys = {
	(FILESYS *)0,
	FS_NO_C_CACHE,
	proc_root,
	proc_lookup, nocreat, proc_getdev, proc_getxattr,
	proc_chattr, proc_chown, proc_chmode,
	nomkdir, proc_rmdir, proc_remove, proc_getname, proc_rename,
	proc_opendir, proc_readdir, proc_rewinddir, proc_closedir,
	proc_pathconf, proc_dfree,
	nowritelabel, proc_readlabel, nosymlink, noreadlink, nohardlink,
	nofscntl, nodskchng
};

long ARGS_ON_STACK 
proc_root(drv, fc)
	int drv;
	fcookie *fc;
{
	if (drv == PROCDRV) {
		fc->fs = &proc_filesys;
		fc->dev = drv;
		fc->index = 0L;
		return 0;
	}
	fc->fs = 0;
	return EINTRN;
}

static PROC *
name2proc(name)
	const char *name;
{
	const char *pstr;
	char c;
	int i;

	pstr = name;
	while ( (c = *name++) != 0) {
		if (c == '.')
			pstr = name;
	}
	if (!isdigit(*pstr) && *pstr != '-')
		return 0;
	i = (int)atol(pstr);
	if (i == -1)
		return curproc;
	else if (i == -2)
		i = curproc->ppid;
	return pid2proc(i);
}

static long ARGS_ON_STACK 
proc_lookup(dir, name, fc)
	fcookie *dir;
	const char *name;
	fcookie *fc;
{
	PROC *p;

	if (dir->index != 0) {
		DEBUG(("proc_lookup: bad directory"));
		return EPTHNF;
	}

/* special case: an empty name in a directory means that directory */
/* so does "." */
	if (!*name || (name[0] == '.' && name[1] == 0)) {
		*fc = *dir;
		return 0;
	}

/* another special case: ".." could be a mount point */
	if (!strcmp(name, "..")) {
		*fc = *dir;
		return EMOUNT;
	}

	if (0 == (p = name2proc(name))) {
		DEBUG(("proc_lookup: name not found"));
		return EFILNF;
	} else {
		fc->index = (long)p;
		fc->fs = &proc_filesys;
		fc->dev = PROC_RDEV_BASE | p->pid;
	}
	return 0;
}

static int p_attr[NUM_QUEUES] = {	/* attributes corresponding to queues */
	0,			/* "RUNNING" */
	0x01,			/* "READY" */
	0x20,			/* "WAITING" */
	0x21,			/* "IOBOUND" */
	0x22,			/* "ZOMBIE" */
	0x02,			/* "TSR" */
	0x24,			/* "STOPPED" */
	0x21			/* "SELECT" (same as IOBOUND) */
};

static long ARGS_ON_STACK 
proc_getxattr(fc, xattr)
	fcookie *fc;
	XATTR *xattr;
{
	PROC *p;
	extern int proctime, procdate;	/* see dosmem.c */

	xattr->blksize = 1;
	if (fc->index == 0) {
		/* the root directory */
		xattr->index = 0;
		xattr->dev = xattr->rdev = PROCDRV;
		xattr->nlink = 1;
		xattr->uid = xattr->gid = 0;
		xattr->size = xattr->nblocks = 0;
		xattr->mtime = xattr->atime = xattr->ctime = proctime;
		xattr->mdate = xattr->adate = xattr->cdate = procdate;
		xattr->mode = S_IFDIR | DEFAULT_DIRMODE;
		xattr->attr = FA_DIR;
		return 0;
	}

	p = (PROC *)fc->index;
	xattr->index = p->pid;
	xattr->dev = xattr->rdev = PROC_RDEV_BASE | p->pid;
	xattr->nlink = 1;
	xattr->uid = p->euid; xattr->gid = p->egid;
	xattr->size = xattr->nblocks = memused(p);
	xattr->mtime = xattr->ctime = xattr->atime = p->starttime;
	xattr->mdate = xattr->cdate = xattr->adate = p->startdate;
	xattr->mode = S_IFMEM | S_IRUSR | S_IWUSR;
	xattr->attr = p_attr[p->wait_q];
	return 0;
}

static long ARGS_ON_STACK 
proc_chattr(fc, attrib)
	fcookie *fc;
	int attrib;
{
	UNUSED(fc); UNUSED(attrib);

	return EACCDN;
}

static long ARGS_ON_STACK 
proc_chown(fc, uid, gid)
	fcookie *fc;
	int uid, gid;
{
	UNUSED(fc); UNUSED(uid); UNUSED(gid);
	return EINVFN;
}

static long ARGS_ON_STACK 
proc_chmode(fc, mode)
	fcookie *fc;
	unsigned mode;
{
	UNUSED(fc); UNUSED(mode);
	return EINVFN;
}

static long ARGS_ON_STACK 
proc_rmdir(dir, name)
	fcookie *dir;
	const char *name;
{
	UNUSED(dir); UNUSED(name);
	return EPTHNF;
}

static long ARGS_ON_STACK 
proc_remove(dir, name)
	fcookie *dir;
	const char *name;
{
	PROC *p;

	if (dir->index != 0)
		return EPTHNF;
	p = name2proc(name);
	if (!p)
		return EFILNF;

/* this check is necessary because the Fdelete code checks for
 * write permission on the directory, not on individual
 * files
 */
	if (curproc->euid && curproc->ruid != p->ruid) {
		DEBUG(("proc_remove: wrong user"));
		return EACCDN;
	}
	post_sig(p, SIGTERM);
	check_sigs();		/* it might have been us */
	return 0;
}

static long ARGS_ON_STACK 
proc_getname(root, dir, pathname, size)
	fcookie *root, *dir; char *pathname;
	int size;
{
	PROC *p;
	char buffer[20]; /* enough if proc names no longer than 8 chars */

	UNUSED(root);

	if (dir->index == 0)
		*buffer = 0;
	else {
		p = (PROC *)dir->index;
		ksprintf(buffer, "%s.03d", p->name, p->pid);
	}
	if (strlen(buffer) < size) {
		strcpy(pathname, buffer);
		return 0;
	}
	else
		return ERANGE;
}

static long ARGS_ON_STACK 
proc_rename(olddir, oldname, newdir, newname)
	fcookie *olddir;
	char *oldname;
	fcookie *newdir;
	const char *newname;
{
	PROC *p;
	int i;

	if (olddir->index != 0 || newdir->index != 0)
		return EPTHNF;
	if ((p = name2proc(oldname)) == 0)
		return EFILNF;

	oldname = p->name;
	for (i = 0; i < PNAMSIZ; i++) {
		if (*newname == 0 || *newname == '.') {
			*oldname = 0; break;
		}
		*oldname++ = *newname++;
	}
	return 0;
}

static long ARGS_ON_STACK 
proc_opendir(dirh, flags)
	DIR *dirh;
	int flags;
{
	UNUSED(flags);

	dirh->index = 0;
	return 0;
}

static long ARGS_ON_STACK 
proc_readdir(dirh, name, namelen, fc)
	DIR *dirh;
	char *name;
	int namelen;
	fcookie *fc;
{
	int i;
	int giveindex = (dirh->flags == 0);
	PROC *p;

	do {
		i = dirh->index++;
/* BUG: we shouldn't have the magic number "1000" for maximum proc pid */
		if (i >= 1000) {
			p = 0;
			break;
		}
		p = pid2proc(i);
	} while (!p);

	if (!p)
		return ENMFIL;

	fc->index = (long)p;
	fc->fs = &proc_filesys;
	fc->dev = PROC_RDEV_BASE | p->pid;

	if (giveindex) {
		namelen -= (int)sizeof(long);
		if (namelen <= 0) return ERANGE;
		*((long *)name) = (long)p->pid;
		name += sizeof(long);
	}
	if (namelen < strlen(p->name) + 5)
		return ENAMETOOLONG;

	ksprintf(name, "%s.%03d", p->name, p->pid);
	return 0;
}

static long ARGS_ON_STACK 
proc_rewinddir(dirh)
	DIR *dirh;
{
	dirh->index = 0;
	return 0;
}

static long ARGS_ON_STACK 
proc_closedir(dirh)
	DIR *dirh;
{
	UNUSED(dirh);
	return 0;
}
static long ARGS_ON_STACK 
proc_pathconf(dir, which)
	fcookie *dir;
	int which;
{
	UNUSED(dir);

	switch(which) {
	case -1:
		return DP_MAXREQ;
	case DP_IOPEN:
		return UNLIMITED;	/* no internal limit on open files */
	case DP_MAXLINKS:
		return 1;		/* we don't have hard links */
	case DP_PATHMAX:
		return PATH_MAX;	/* max. path length */
	case DP_NAMEMAX:
		return PNAMSIZ + 4;	/* max. length of individual name */
					/* the "+4" is for the pid: ".123" */
	case DP_ATOMIC:
		return UNLIMITED;	/* all writes are atomic */
	case DP_TRUNC:
		return DP_DOSTRUNC;	/* file names are truncated to 8.3 */
	case DP_CASE:
		return DP_CASEINSENS;	/* case preserved, but ignored */
	case DP_MODEATTR:
		return (0777L << 8)|
				DP_FT_DIR|DP_FT_MEM;
	case DP_XATTRFIELDS:
		return DP_INDEX|DP_DEV|DP_NLINK|DP_UID|DP_GID|DP_BLKSIZE|DP_SIZE|
				DP_NBLOCKS;
	default:
		return EINVFN;
	}
}

static long ARGS_ON_STACK 
proc_dfree(dir, buf)
	fcookie *dir;
	long *buf;
{
	long size;
/* "sector" size is the size of the smallest amount of memory that can be
   allocated. see mem.h for the definition of ROUND
 */
	long secsiz = ROUND(1);

	UNUSED(dir);

	size = tot_rsize(core, 0) + tot_rsize(alt, 0);
	*buf++ = size/secsiz;			/* number of free clusters */
	size = tot_rsize(core, 1) + tot_rsize(alt, 1);
	*buf++ = size/secsiz;			/* total number of clusters */
	*buf++ = secsiz;			/* sector size (bytes) */
	*buf = 1;				/* cluster size (in sectors) */
	return 0;
}

static DEVDRV * ARGS_ON_STACK 
proc_getdev(fc, devsp)
	fcookie *fc;
	long *devsp;
{
	PROC *p;

	p = (PROC *)fc->index;

	*devsp = (long)p;
	return &proc_device;
}

/*
 * PROC device driver
 */

/*
 * BUG: file locking and the O_SHMODE restrictions are not implemented
 * for processes
 */

static long ARGS_ON_STACK 
proc_open(f)
	FILEPTR *f;
{
	UNUSED(f);

	return 0;
}

static long ARGS_ON_STACK 
proc_write(f, buf, nbytes)
	FILEPTR *f; const char *buf; long nbytes;
{
	PROC *p = (PROC *)f->devinfo;
	char *where;
	long bytes_written = 0;
	int prot_hold;

	where = (char *)f->pos;

TRACE(("proc_write to pid %d: %ld bytes to %lx", p->pid, nbytes, where));

	prot_hold = mem_access_for(p, (ulong)where,nbytes);
	if (prot_hold == 0) {
	    DEBUG(("Can't Fwrite that memory: not all the same or not owner."));
	    return EACCDN;
	}
	if (prot_hold == 1) {
	    DEBUG(("Attempt to Fwrite memory crossing a managed boundary"));
	    return EACCDN;
	}

	bytes_written = nbytes;
	while (nbytes-- > 0) {
		*where++ = *buf++;
	}
	cpush((void *)f->pos, bytes_written);	/* flush cached data */

	/* MEMPROT: done with temp mapping (only call if temp'ed above) */
	if (prot_hold != -1) prot_temp((ulong)f->pos,bytes_written,prot_hold);

	f->pos += bytes_written;
	return bytes_written;
}

static long ARGS_ON_STACK 
proc_read(f, buf, nbytes)
	FILEPTR *f; char *buf; long nbytes;
{
	PROC *p = (PROC *)f->devinfo;
	char *where;
	long bytes_read = 0;
	int prot_hold;

	where = (char *)f->pos;

TRACE(("proc_read from pid %d: %ld bytes from %lx", p->pid, nbytes, where));

	prot_hold = mem_access_for(p, (ulong)where,nbytes);
	if (prot_hold == 0) {
	    DEBUG(("Can't Fread that memory: not all the same."));
	    return EACCDN;
	}
	if (prot_hold == 1) {
	    DEBUG(("Attempt to Fread memory crossing a managed boundary"));
	    return EACCDN;
	}

	bytes_read = nbytes;
	while (nbytes-- > 0) {
		*buf++ = *where++;
	}

	/* MEMPROT: done with temp mapping (only call if temp'ed above) */
	if (prot_hold != -1) prot_temp((ulong)f->pos,bytes_read,prot_hold);

	f->pos += bytes_read;
	return bytes_read;
}

/*
 * proc_ioctl: currently, the only IOCTL's available are:
 * PPROCADDR: get address of PROC structure's "interesting" bits
 * PCTXTSIZE: get the size of the CONTEXT structure
 * PBASEADDR: get address of process basepage
 * PSETFLAGS: set the memory allocation flags (e.g. to malloc from fastram)
 * PGETFLAGS: get the memory allocation flags
 * PTRACESFLAGS: set the process tracing flags
 * PTRACEGFLAGS: get the process tracing flags
 * PTRACEGO: restart the process (T1=0/T1=0)
 * PTRACEFLOW: restart the process (T1=0/T0=1)
 * PTRACESTEP: restart the process (T1=1/T0=0)
 * PTRACE11: restart the process (T1=1/T0=1)
 * PLOADINFO: get information about the process name and command line
 */

static long ARGS_ON_STACK 
proc_ioctl(f, mode, buf)
	FILEPTR *f; int mode; void *buf;
{
	PROC *p;
	extern long mcpu;	/* in main.c */
	short sr;

	p = (PROC *)f->devinfo;
	switch(mode) {
	case PPROCADDR:
		*((long *)buf) = (long)&p->magic;
		return 0;
	case PBASEADDR:
		if (p == rootproc)
			*((long *)buf) = (long)_base;
		else
			*((long *)buf) = (long)p->base;
		return 0;
	case PCTXTSIZE:
		*((long *)buf) = sizeof(CONTEXT);
		return 0;
	case PFSTAT:
	    {
		FILEPTR *pf;
		int pfd = (*(ushort *)buf);
		if (pfd < MIN_HANDLE || pfd >= MAX_OPEN ||
		    (pf = p->handle[pfd]) == 0)
			return EIHNDL;
		return (*pf->fc.fs->getxattr)(&pf->fc, (XATTR *)buf);
	    }
	case PSETFLAGS:
	    {
		int newflags = (ushort)(*(long *)buf);
		if ((newflags & F_OS_SPECIAL) &&
		    (!(p->memflags & F_OS_SPECIAL))) {
			/* you're making the process OS_SPECIAL */
			TRACE(("Fcntl OS_SPECIAL pid %d",p->pid));
			p->memflags = newflags;
			mem_prot_special(p);
		}
		/* note: only the low 16 bits are actually used */
		p->memflags = *((long *)buf);
		return 0;
	    }
	case PGETFLAGS:
		*((long *)buf) = p->memflags;
		return 0;
	case PTRACESFLAGS:
		if (p->ptracer == curproc || p->ptracer == 0) {
			p->ptraceflags = *(ushort *)buf;
			if (p->ptraceflags == 0) {
				p->ptracer = 0;
				p->ctxt[CURRENT].ptrace = 0;
				p->ctxt[SYSCALL].ptrace = 0;
		/* if the process is stopped, restart it */
				if (p->wait_q == STOP_Q) {
					p->sigpending &= ~STOPSIGS;
					post_sig(p, SIGCONT);
				}
			} else if (p == curproc) {
				p->ptracer = pid2proc(p->ppid);
			} else {
				p->ptracer = curproc;
			}
		} else {
			DEBUG(("proc_ioctl: process already being traced"));
			return EACCDN;
		}
		return 0;
	case PTRACEGFLAGS:
		if (p->ptracer == curproc) {
			*(ushort *)buf = p->ptraceflags;
			return 0;
		} else {
			return EACCDN;
		}
	case PTRACE11:
		return EINVFN;
	case PTRACEFLOW:
		if (mcpu < 20) {
			DEBUG(("proc_ioctl: wrong processor"));
			return EINVFN;
		}
		/* fall through */
	case PTRACEGO:
	case PTRACESTEP:
		if (!p->ptracer) {
			DEBUG(("proc_ioctl(PTRACE): process not being traced"));
			return EACCDN;
		}
		else if (p->wait_q != STOP_Q) {
			DEBUG(("proc_ioctl(PTRACE): process not stopped"));
			return EACCDN;
		}
		else if (p->wait_cond &&
		    (1L << ((p->wait_cond >> 8) & 0x1f)) & STOPSIGS) {
			DEBUG(("proc_ioctl(PTRACE): process stopped by job control"));
			return EACCDN;
		}
		if (buf && *(ushort *)buf >= NSIG) {
			DEBUG(("proc_ioctl(PTRACE): illegal signal number"));
			return ERANGE;
		}
		p->ctxt[SYSCALL].sr &= 0x3fff;	/* clear both trace bits */
		p->ctxt[SYSCALL].sr |= (mode - PTRACEGO) << 14;
		/* Discard the saved frame */
		p->ctxt[SYSCALL].sfmt = 0;
		p->sigpending = 0;
		if (buf && *(ushort *)buf != 0) {
TRACE(("PTRACEGO: sending signal %d to pid %d", *(ushort *)buf, p->pid));
			post_sig(p, *(ushort *)buf);

/* another SIGNULL hack... within check_sigs() we watch for a pending
 * SIGNULL, if we see this then we allow delivery of a signal to the
 * process, rather than telling the parent.
 */
			p->sigpending |= 1L;
		} else {
TRACE(("PTRACEGO: no signal"));
		}
/* wake the process up */
		sr = spl7();
		rm_q(p->wait_q, p);
		add_q(READY_Q, p);
		spl(sr);
		return 0;
	/* jr: PLOADINFO returns information about params passed to Pexec */
	case PLOADINFO:
		{
			struct ploadinfo *pl = buf;

			if (!p->fname[0]) return EFILNF;
			strncpy (pl->cmdlin, p->cmdlin, 128);
			if (strlen (p->fname) <= pl->fnamelen)
				strcpy (pl->fname, p->fname);
			else
				return ENAMETOOLONG;
		}
		return 0;

	case FIONREAD:
	case FIONWRITE:
		*((long *)buf) = 1L;	/* we're always ready for i/o */
		return 0;
	case FIOEXCEPT:
		*((long *)buf) = 0L;
		return 0;
	default:
		DEBUG(("procfs: bad Fcntl command"));
	}
	return EINVFN;
}

static long ARGS_ON_STACK 
proc_lseek(f, where, whence)
	FILEPTR *f; long where; int whence;
{
	switch(whence) {
	case 0:
	case 2:
		f->pos = where;
		break;
	case 1:
		f->pos += where;
		break;
	default:
		return EINVFN;
	}
	return f->pos;
}

static long ARGS_ON_STACK 
proc_datime(f, timeptr, rwflag)
	FILEPTR *f;
	short *timeptr;
	int rwflag;
{
	PROC *p;

	p = (PROC *)f->devinfo;
	if (rwflag) {
		return EACCDN;
	}
	else {
		*timeptr++ = p->starttime;
		*timeptr = p->startdate;
	}
	return 0;
}

static long ARGS_ON_STACK 
proc_close(f, pid)
	FILEPTR *f;
	int pid;
{
	UNUSED(f); UNUSED(pid);
	return 0;
}

static long ARGS_ON_STACK 
proc_readlabel(dir, name, namelen)
	fcookie *dir;
	char *name;
	int namelen;
{
	UNUSED(dir);

	strncpy(name, "Processes", namelen-1);
	name[namelen-1] = 0;
	return (sizeof "Processes" <= namelen) ? 0 : ENAMETOOLONG;
}
