/*	SCCS Id: @(#)unixunix.c	3.2	94/11/07	*/
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed.  See license for details. */

/* This file collects some Unix dependencies */

#include "hack.h"	/* mainly for index() which depends on BSD */

#include <errno.h>
#include <sys/stat.h>
#if defined(NO_FILE_LINKS) || defined(SUNOS4) || defined(POSIX_TYPES)
#include <fcntl.h>
#endif
#include <signal.h>

#ifdef _M_UNIX
extern void NDECL(sco_mapon);
extern void NDECL(sco_mapoff);
#endif

#ifdef LINUX
extern void NDECL(linux_mapon);
extern void NDECL(linux_mapoff);
#endif

static int FDECL (veryold, (int));
static int NDECL (eraseoldlocks);

static struct stat buf, hbuf;

void
gethdate(name)
	const char *name;
{
/* old version - for people short of space */
/*
/* register char *np;
/*	if(stat(name, &hbuf))
/*		error("Cannot get status of %s.",
/*			(np = rindex(name, '/')) ? np+1 : name);
/*
/* version using PATH from: seismo!gregc@ucsf-cgl.ARPA (Greg Couch) */

/*
 * The problem with   #include	<sys/param.h>   is that this include file
 * does not exist on all systems, and moreover, that it sometimes includes
 * <sys/types.h> again, so that the compiler sees these typedefs twice.
 */
#define		MAXPATHLEN	1024

	register const char *np, *path;
	char filename[MAXPATHLEN+1];

	if (index(name, '/') != (char *)0 ||
					(path = getenv("PATH")) == (char *)0)
		path = "";

	for (;;) {
		if ((np = index(path, ':')) == (char *)0)
			np = path + strlen(path);	/* point to end str */
		if (np - path <= 1)			/* %% */
			Strcpy(filename, name);
		else {
			(void) strncpy(filename, path, np - path);
			filename[np - path] = '/';
			Strcpy(filename + (np - path) + 1, name);
		}
		if (stat(filename, &hbuf) == 0)
			return;
		if (*np == '\0')
			break;
		path = np + 1;
	}
#if defined(BOS) && defined(NHSTDC)
/*
 *	This one is really **STUPID**.  I don't know why it's happening
 *	as similar constructs work elsewhere, but...
 */
	if((np = rindex(name, '/')))
	     error("Cannot get status of %s.", np+1);
	else error("Cannot get status of %s.", name);
#else
	error("Cannot get status of %s.",
		(np = rindex(name, '/')) ? np+1 : name);
#endif
}

#if 0
int
uptodate(fd)
int fd;
{
	if(fstat(fd, &buf)) {
		pline("Cannot get status of saved level? ");
		wait_synch();
		return(0);
	}
	if(buf.st_mtime < hbuf.st_mtime) {
		pline("Saved level is out of date. ");
		wait_synch();
		return(0);
	}
	return(1);
}
#endif

/* see whether we should throw away this xlock file */
static int
veryold(fd)
int fd;
{
	time_t date;

	if(fstat(fd, &buf)) return(0);			/* cannot get status */
#ifndef INSURANCE
	if(buf.st_size != sizeof(int)) return(0);	/* not an xlock file */
#endif
#ifdef BSD
	(void) time((long *)(&date));
#else
	(void) time(&date);
#endif
	if(date - buf.st_mtime < 3L*24L*60L*60L) {	/* recent */
#ifndef NETWORK
		extern int errno;
#endif
		int lockedpid;	/* should be the same size as hackpid */

		if(read(fd, (genericptr_t)&lockedpid, sizeof(lockedpid)) !=
			sizeof(lockedpid))
			/* strange ... */
			return(0);

		/* From: Rick Adams <seismo!rick>
		/* This will work on 4.1cbsd, 4.2bsd and system 3? & 5.
		/* It will do nothing on V7 or 4.1bsd. */
#ifndef NETWORK
		/* It will do a VERY BAD THING if the playground is shared
		   by more than one machine! -pem */
		if(!(kill(lockedpid, 0) == -1 && errno == ESRCH))
#endif
			return(0);
	}
	(void) close(fd);
	return(1);
}

static int
eraseoldlocks()
{
	register int i;

	/* cannot use maxledgerno() here, because we need to find a lock name
	 * before starting everything (including the dungeon initialization
	 * that sets astral_level, needed for maxledgerno()) up
	 */
	for(i = 1; i <= MAXDUNGEON*MAXLEVEL + 1; i++) {
		/* try to remove all */
		set_levelfile_name(lock, i);
		(void) unlink(lock);
	}
	set_levelfile_name(lock, 0);
	if(unlink(lock)) return(0);			/* cannot remove it */
	return(1);					/* success! */
}

void
getlock()
{
	extern int errno;
	register int i = 0, fd, c;

#ifdef TTY_GRAPHICS
	/* idea from rpick%ucqais@uccba.uc.edu
	 * prevent automated rerolling of characters
	 * test input (fd0) so that tee'ing output to get a screen dump still
	 * works
	 * also incidentally prevents development of any hack-o-matic programs
	 */
	/* added check for window-system type -dlc */
	if (!strcmp(windowprocs.name, "tty"))
	    if (!isatty(0))
		error("You must play from a terminal.");
#endif

	/* we ignore QUIT and INT at this point */
	if (!lock_file_area(HLOCK_AREA, HLOCK, 10)) {
		wait_synch();
		error("%s", "");
	}

	regularize(lock);
	set_levelfile_name(lock, 0);

	if(locknum) {
		if(locknum > 25) locknum = 25;

		do {
			lock[0] = 'a' + i++;

#ifndef FILE_AREAS
			if((fd = open(lock, 0, 0)) == -1) {
#else
			if((fd = open_area(FILE_AREA_LEVL, lock, 0, 0)) == -1) {
#endif
			    if(errno == ENOENT) goto gotlock; /* no such file */
			    perror(lock);
			    unlock_file_area(HLOCK_AREA, HLOCK);
			    error("Cannot open %s", lock);
			}

			if(veryold(fd) /* closes fd if true */
							&& eraseoldlocks())
				goto gotlock;
			(void) close(fd);
		} while(i < locknum);

		unlock_file_area(HLOCK_AREA, HLOCK);
		error("Too many hacks running now.");
	} else {
#ifndef FILE_AREAS
		if((fd = open(lock, 0, 0)) == -1) {
#else
		if((fd = open_area(FILE_AREA_LEVL, lock, 0, 0)) == -1) {
#endif
			if(errno == ENOENT) goto gotlock;    /* no such file */
			perror(lock);
			unlock_file_area(HLOCK_AREA, HLOCK);
			error("Cannot open %s", lock);
		}

		if(veryold(fd) /* closes fd if true */ && eraseoldlocks())
			goto gotlock;
		(void) close(fd);

		if(iflags.window_inited) {
		    c = yn("There is already a game in progress under your name.  Destroy old game?");
		} else {
		    (void) printf("\nThere is already a game in progress under your name.");
		    (void) printf("  Destroy old game? [yn] ");
		    (void) fflush(stdout);
		    c = getchar();
		    (void) putchar(c);
		    (void) fflush(stdout);
		    while (getchar() != '\n') ; /* eat rest of line and newline */
		}
		if(c == 'y' || c == 'Y')
			if(eraseoldlocks())
				goto gotlock;
			else {
				unlock_file_area(HLOCK_AREA, HLOCK);
				error("Couldn't destroy old game.");
			}
		else {
			unlock_file_area(HLOCK_AREA, HLOCK);
			error("%s", "");
		}
	}

gotlock:
#ifndef FILE_AREAS
	fd = creat(lock, FCMASK);
#else
	fd = creat_area(FILE_AREA_LEVL, lock, FCMASK);
#endif
	unlock_file_area(HLOCK_AREA, HLOCK);
	if(fd == -1) {
		error("cannot creat lock file.");
	} else {
		if(write(fd, (genericptr_t) &hackpid, sizeof(hackpid))
		    != sizeof(hackpid)){
			error("cannot write lock");
		}
		if(close(fd) == -1) {
			error("cannot close lock");
		}
	}
}

void
regularize(s)	/* normalize file name - we don't like .'s, /'s, spaces */
register char *s;
{
	register char *lp;
#if defined(SYSV) && !defined(AIX_31) && defined(COMPRESS)
	int i;
#endif

	while((lp=index(s, '.')) || (lp=index(s, '/')) || (lp=index(s,' ')))
		*lp = '_';
#if defined(SYSV) && !defined(AIX_31)
	/* avoid problems with 14 character file name limit */
# ifdef COMPRESS
	/* leave room for .e from error and .Z from compress appended to
	 * save files */
#  ifdef COMPRESS_EXTENSION
	i = 12 - strlen(COMPRESS_EXTENSION);
#  else
	i = 10;		/* should never happen... */
#  endif
	if(strlen(s) > i)
		s[i] = '\0';
# else
	if(strlen(s) > 11)
		/* leave room for .nn appended to level files */
		s[11] = '\0';
# endif
#endif
}

#ifdef SHELL
int
dosh()
{
	register char *str;
	if(child(0)) {
		if((str = getenv("SHELL")) != (char*)0)
			(void) execl(str, str, (char *)0);
		else
			(void) execl("/bin/sh", "sh", (char *)0);
		raw_print("sh: cannot execute.");
		exit(EXIT_FAILURE);
	}
	return 0;
}
#endif /* SHELL /**/

#if defined(SHELL) || defined(DEF_PAGER) || defined(DEF_MAILREADER)
int
child(wt)
int wt;
{
	register int f;
	suspend_nhwindows((char *)0);	/* also calls end_screen() */
#ifdef _M_UNIX
	sco_mapon();
#endif
#ifdef LINUX
	linux_mapon();
#endif
	if((f = fork()) == 0){		/* child */
		(void) setgid(getgid());
		(void) setuid(getuid());
#ifdef CHDIR
		(void) chdir(getenv("HOME"));
#endif
		return(1);
	}
	if(f == -1) {	/* cannot fork */
		pline("Fork failed.  Try again.");
		return(0);
	}
	/* fork succeeded; wait for child to exit */
	(void) signal(SIGINT,SIG_IGN);
	(void) signal(SIGQUIT,SIG_IGN);
	(void) wait( (int *) 0);
#ifdef _M_UNIX
	sco_mapoff();
#endif
#ifdef LINUX
	linux_mapoff();
#endif
	(void) signal(SIGINT, (SIG_RET_TYPE) done1);
#ifdef WIZARD
	if(wizard) (void) signal(SIGQUIT,SIG_DFL);
#endif
	if(wt) {
		raw_print("");
		wait_synch();
	}
	resume_nhwindows();
	return(0);
}
#endif

#ifdef FILE_AREAS

/*
 * Unix file areas are directories with trailing slashes.
 */

char *make_file_name(filearea, filename)
const char *filearea, *filename;
{
	char *buf;
	int lenarea;
	if (filearea && filename[0]!='/')
	{
		lenarea = strlen(filearea);
		buf = (char *)alloc(lenarea+strlen(filename)+1);
		strcpy(buf, filearea);
		strcpy(buf+lenarea, filename);
	}
	else
	{
		buf = (char *)alloc(strlen(filename)+1);
		strcpy(buf, filename);
	}
	return buf;
}

FILE *
fopen_datafile_area(filearea, filename, mode)
const char *filearea, *filename, *mode;
{
	FILE *fp;
	char *buf;
	buf = make_file_name(filearea, filename);
	fp = fopen(buf, mode);
	return fp;
}

int
chmod_area(filearea, filename, mode)
const char *filearea, *filename;
int mode;
{
	int retval;
	char *buf;
	buf = make_file_name(filearea, filename);
	retval = chmod(buf, mode);
	free(buf);
	return retval;
}

int
open_area(filearea, filename, flags, mode)
const char *filearea, *filename;
int flags, mode;
{
	int fd;
	char *buf;
	buf = make_file_name(filearea, filename);
	fd = open(buf, flags, mode);
	free(buf);
	return fd;
}

int
creat_area(filearea, filename, mode)
const char *filearea, *filename;
int mode;
{
	int fd;
	char *buf;
	buf = make_file_name(filearea, filename);
	fd = creat(buf, mode);
	free(buf);
	return fd;
}

int
rename_area(filearea, oldfilename, newfilename)
const char *filearea, *oldfilename, *newfilename;
{
	int retval;
	char *oldpath,*newpath;
	int lenarea;
	oldpath = make_file_name(filearea, oldfilename);
	newpath = make_file_name(filearea, newfilename);
	retval = rename(oldpath, newpath);
	free(oldpath);
	free(newpath);
	return retval;
}

int
remove_area(filearea, filename)
const char *filearea, *filename;
{
	int retval;
	char *buf;
	buf = make_file_name(filearea, filename);
	retval = remove(buf);
	free(buf);
	return retval;
}

FILE *
freopen_area(filearea, filename, mode, stream)
const char *filearea, *filename, *mode;
FILE *stream;
{
	FILE *fp;
	char *buf;
	buf = make_file_name(filearea, filename);
	fp = freopen(buf, mode, stream);
	free(buf);
	return fp;
}

/* ----------  BEGIN FILE LOCKING HANDLING ----------- */

/*
 * ALI
 *
 * We assume that filenames are unique and so locks
 * don't need to take the filearea into account. Since
 * these locks are only used for RECORD, LOGFILE and HLOCK,
 * this assumption is currently valid.
 */

static int nesting = 0;

static int lockfd=-1;   /* for lock_file_area() to pass to unlock_file_area() */

#define HUP     if (!program_state.done_hup)

static char *
make_lockname(filename, lockname)
const char *filename;
char *lockname;
{
# ifdef NO_FILE_LINKS
	Strcpy(lockname, LOCKDIR);
	if (LOCKDIR[sizeof(LOCKDIR)-2]!='/')
		Strcat(lockname, "/");
	Strcat(lockname, filename);
# else
# ifdef FILE_AREAS
	Strcpy(lockname, FILE_AREA_VAR);
	Strcat(lockname, filename);
# else
	Strcpy(lockname, filename);
# endif
# endif	/* NO_FILE_LINKS */
	Strcat(lockname, "_lock");
	return lockname;
}

static boolean
assert_lock(filename, lockname)
const char *filename, *lockname;
{
# ifdef NO_FILE_LINKS
    return (lockfd = open(lockname, O_RDWR|O_CREAT|O_EXCL, 0666)) != -1;
# else
    if (link(filename, lockname) == -1)
	if (errno == EXDEV)
	    return (lockfd = open(lockname, O_RDWR|O_CREAT|O_EXCL, 0666)) != -1;
	else
	    return FALSE;
    else
    {
	lockfd=-1;
	return TRUE;
    }
# endif
}

/*
 * lock a file
 *
 * Note: The area says where the file is, not where the lock is.
 */

boolean
lock_file_area(filearea, filename, retryct)
const char *filearea, *filename;
int retryct;
{
	int retval=TRUE;
	char *lockname, locknambuf[BUFSZ];
	char *buf;

	nesting++;
	if (nesting > 1) {
	    impossible("TRIED TO NEST LOCKS");
	    return TRUE;
	}

	buf = make_file_name(filearea, filename);
	lockname = make_lockname(filename, locknambuf);

	while (retval && !assert_lock(buf, lockname)) {
	    register int errnosv = errno;

	    switch (errnosv) {  /* George Barbanis */
	    case EEXIST:
		if (retryct--) {
		    HUP raw_printf(
			    "Waiting for access to %s.  (%d retries left).",
			    filename, retryct);
# if defined(SYSV) || defined(ULTRIX)
		    (void)
# endif
			sleep(1);
		} else {
		    HUP (void) raw_print("I give up.  Sorry.");
		    HUP raw_printf("Perhaps there is an old %s around?",
					lockname);
		    nesting--;
		    retval = FALSE;
		}

		break;
	    case ENOENT:
		HUP raw_printf("Can't find file %s to lock!", filename);
		nesting--;
		retval = FALSE;
	    case EACCES:
		HUP raw_printf("No write permission to lock %s!", filename);
		nesting--;
		retval = FALSE;
	    default:
		HUP perror(lockname);
		HUP raw_printf(
			     "Cannot lock %s for unknown reason (%d).",
			       filename, errnosv);
		nesting--;
		retval = FALSE;
	    }

	}

	free(buf);
	return retval;
}

/* unlock file, which must be currently locked by lock_file_area */
void
unlock_file_area(filearea, filename)
const char *filearea, *filename;
{
	char *lockname, locknambuf[BUFSZ];

	if (nesting == 1) {
		lockname = make_lockname(filename, locknambuf);

		if (unlink(lockname) < 0)
			HUP raw_printf("Can't unlink %s.", lockname);
		if (lockfd!=-1)
		{
			(void) close(lockfd);
			lockfd=-1;
		}
	}

	nesting--;
}

/* ----------  END FILE LOCKING HANDLING ----------- */

#endif	/* FILE_AREAS */
