/* osos2/osblock.c */



/*
 * Session file handling routines for OS/2.  We use the OS/2 Control
 * Program API (DosOpen(), DosWrite(), etc.) because the standard
 * open(), read(), and write() calls don't afford enough control over
 * file sharing and other attributes.  Some things are actually easier
 * this way, anyhow.  :-)
 */
#include <process.h>
#include "elvis.h"

#define INCL_DOSFILEMGR
#define INCL_DOSERRORS
#ifdef __EMX__
# define USE_OS2_TOOLKIT_HEADERS
#endif
#include <os2.h>
#ifdef CHAR
# undef CHAR
#endif

#ifndef DEFAULT_SESSION
# define DEFAULT_SESSION "%sELV%05d.SES"
#endif


static HFILE fd = NULLHANDLE;	/* file handle of the session file */

static char sessionDir[128];
static char *sessionDirPtr = NULL;
#ifdef FEATURE_RAM
static BLK **blklist;
static int nblks;
#endif

/* This function creates a new block file, and returns True if successful,
 * or False if failed because the file was already busy.
 */
BOOLEAN blkopen (force, buf)
	BOOLEAN	force;		/* if True, open even if "in use" flag set */
	BLK		*buf;		/* buffer, holds SUPER block */
{
	static char	dfltname[256];
	int	i;
	APIRET rc;
	ULONG open_flags;
	ULONG open_mode;
	ULONG action;
	ULONG actual;

#ifdef FEATURE_RAM
	if (o_session && !CHARcmp(o_session, toCHAR("ram")))
	{
		nblks = 1024;
		blklist = (BLK **)calloc(nblks, sizeof(BLK *));
		blklist[0] = (BLK *)malloc(o_blksize);
		memcpy(blklist[0], buf, o_blksize);
		return True;
	}
#endif

    /* This is a little bit dirty :-)  We try to find a directory for
     * our session files from the list o_sessionpath.  It will be the
     * first writable directory from the list we can find.  The dirty
     * part is we don't want to do this more than once.
     */
    if (sessionDirPtr == NULL) {
        char pathlist[128];
        char *ptr = pathlist;
        unsigned last;
        FILESTATUS fileInfo = {{0}};
        sprintf (sessionDir, "%c", OSPATHDELIM);

		/* search through sessionpath for a writable directory */
		if (!o_sessionpath)
			o_sessionpath = toCHAR(".");
        /* endif */

        /* Go through the directory list in o_sessionpath and use
         * the first writable one for the session file. */
        pathlist[sizeof pathlist - 1u] = '\0';
        strncpy (pathlist, o_sessionpath, sizeof pathlist - 1u);
        if ((ptr = strtok (pathlist, sessionDir)) != NULL) {
            do {
                last = strlen (ptr);
                if (ptr[last-1] == OSPATHSEP)
                    ptr[last-1] = '\0';
                /* endif */
                if (DosQueryPathInfo ((PSZ)ptr, FIL_STANDARD, &fileInfo, 
                        (ULONG)sizeof fileInfo) == NO_ERROR 
                        && (fileInfo.attrFile & 0x01) == 0
                        && (fileInfo.attrFile & 0x10) != 0)
                    break;
                /* endif */
            } while ((ptr = strtok (NULL, sessionDir)) != NULL);
        }/* if */

        /* Found a directory for the session file?  If not, just use
         * the current one else use the one found. */
        if (ptr == NULL)
            sprintf (sessionDir, "%c", '.');
        else {
            strncpy (sessionDir, ptr, sizeof sessionDir - 1u);
            sessionDir[sizeof sessionDir - 1u] = '\0';
        } /* if */

        /* Append a path delimiter and terminate string.  Be sure
         * not to write outside the buffer boundaries... */
        if ((last = strlen (sessionDir)) < sizeof sessionDir - 1u){
            sessionDir[last] = OSPATHSEP;
            sessionDir[last+1] = '\0';
        }/* if */
    } /* if */

	/* If elvis runs other programs, prevent them from inheriting
	 * the session file's descriptor.  Also prevent write access
	 * to the session file by other processes while this process
	 * is using it.
	 */
	open_mode = OPEN_FLAGS_NOINHERIT 
			  | OPEN_SHARE_DENYWRITE
			  | OPEN_ACCESS_READWRITE;

	/* If no session file was explicitly requested, try successive
	 * defaults until we find an existing file (if we're trying to
	 * recover) or a non-existent file (if we're not trying to recover).
	 */
	if (!o_session) {
		i = 1;

		o_session = toCHAR (dfltname);
		o_tempsession = True;
		do
		{
			sprintf (dfltname, DEFAULT_SESSION, sessionDir, i++);
			open_flags = o_recovering
				? OPEN_ACTION_FAIL_IF_NEW   | OPEN_ACTION_OPEN_IF_EXISTS
				: OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_FAIL_IF_EXISTS;

			rc = DosOpen (tochar8 (o_session),
						 &fd, 
						 &action,
						 0L,
						 FILE_NORMAL,
						 open_flags,
						 open_mode,
						 NULL);

		} while (rc == ERROR_OPEN_FAILED);
	} else {
		/* Try to open the session file */
		open_flags = 
			OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW;

		rc = DosOpen (tochar8(o_session),
					 &fd, 
					 &action,
					 0L,
					 FILE_NORMAL,
					 open_flags,
					 open_mode,
					 NULL);
	} /* if */

	/* Error checking. */
	switch (rc)
	{
	case NO_ERROR:
		break;

	case ERROR_SHARING_VIOLATION:
		msg(MSG_FATAL, "session file busy");

	default:
		msg(MSG_FATAL, "no such session");
	} /* if */

	if (action == FILE_EXISTED) {
		/* we're opening an existing session -- definitely not temporary */
		o_tempsession = False;
	} else {
		o_newsession = True;
		rc = DosWrite (fd, buf, BLKSIZE, &actual);
		if (rc != NO_ERROR || actual < BLKSIZE) {
			DosClose (fd);
			DosDelete (tochar8(o_session));
			fd = NULLHANDLE;
			msg (MSG_FATAL, "no such session");
		} else {
			(void)DosSetFilePtr (fd, 0L, FILE_BEGIN, &actual);
		} /* if */
	} /* if */

	/* Read the first block & mark the session file as being "in use".
	 * If already marked as "in use" and !force, then fail.
	 */
	rc = DosRead (fd, buf, sizeof buf->super, &actual);
	if (rc != NO_ERROR || actual != sizeof buf->super) {
		msg (MSG_FATAL, "blkopen's read failed");
	} /* if */
	if (buf->super.inuse && !force) {
		return False;
	} /* if */
	buf->super.inuse = getpid();

	(void)DosSetFilePtr (fd, 0L, FILE_BEGIN, &actual);
	(void)DosWrite (fd, buf, sizeof buf->super, &actual);

	/* done! */
	return True;
}

/* This function closes the session file, given its handle */
void blkclose (buf)
	BLK	*buf;	/* buffer, holds superblock */
{
	blkread (buf, 0);
	buf->super.inuse = 0L;
	blkwrite (buf, 0);
	(void)DosClose (fd);
	fd = NULLHANDLE;
	if (o_tempsession) {
		(void)DosDelete (tochar8(o_session));
	} /* if */
}

/* Write the contents of buf into record # blkno, for the block file
 * identified by blkhandle.  Blocks are numbered starting at 0.  The
 * requested block may be past the end of the file, in which case
 * this function is expected to extend the file.
 */
void blkwrite (buf, blkno)
	BLK		*buf;		/* buffer, holds contents of block */
	_BLKNO_	blkno;		/* where to write it */
{
	LONG offset;
	ULONG actual;

#ifdef FEATURE_RAM
	/* store it in RAM */
	if (nblks > 0)
	{
		if (blkno >= nblks)
		{
			blklist = (BLK **)realloc(blklist,
						(nblks + 1024) * sizeof(BLK *));
			memset(&blklist[nblks], 0, 1024 * sizeof(BLK *));
			nblks += 1024;
		}
		if (!blklist[blkno])
			blklist[blkno] = malloc(o_blksize);
		memcpy(blklist[blkno], buf, o_blksize);
		return;
	}
#endif

	/* write the block */
	offset = (LONG)blkno * (LONG)o_blksize;
	if (DosSetFilePtr (fd, offset, FILE_BEGIN, &actual) != NO_ERROR
		|| DosWrite (fd, buf, o_blksize, &actual) != NO_ERROR
		|| actual != o_blksize)
	{
		msg (MSG_FATAL, "blkwrite(%d) failed", blkno);
	} /* if */
}

/* Read the contends of record # blkno into buf, for the block file
 * identified by blkhandle.  The request block will always exist;
 * it will never be beyond the end of the file.
 */
void blkread (buf, blkno)
	BLK		*buf;		/* buffer, where buffer should be read into */
	_BLKNO_	blkno;		/* where to read from */
{
	LONG offset;
	ULONG actual;

#ifdef FEATURE_RAM
	if (nblks > 0)
	{
		memcpy(buf, blklist[blkno], o_blksize);
		return;
	}
#endif

	/* read the block */
	offset = (LONG)blkno * (LONG)o_blksize;
	if (DosSetFilePtr (fd, offset, FILE_BEGIN, &actual) != NO_ERROR
		|| DosRead (fd, buf, o_blksize, &actual) != NO_ERROR
		|| actual != o_blksize)
	{
		msg (MSG_FATAL, "blkread failed");
	} /* if */
}

/* Force changes out to disk. */
void blksync P_((void))
{
#ifdef FEATURE_RAM
	if (nblks > 0)
		return;
#endif

	(void)DosResetBuffer (fd);
}
