/* 
 * bin_UnixAZ.c --
 *
 * Copyright (c) 1991-1994 The Regents of the University of California.
 * Copyright (c) 1994 Sun Microsystems, Inc.
 * Copyright (c) 1994 Joseph V. Moss
 *
 * See the file "LICENSE" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tclInt.h"
#include "tclPort.h"
#include "binario.h"

/*
 * If the system doesn't define the EAGAIN errno, #define it from
 * EWOULDBLOCK, if it exists.  If neither exists, #define EAGAIN to a
 * bogus value that will never occur.
 */

#ifndef EAGAIN
#   ifdef EWOULDBLOCK
#	define EAGAIN EWOULDBLOCK
#   else
#	define EAGAIN -1901
#   endif
#endif

/*
 * Prototypes for local procedures defined in this file:
 */

static char *		GetOpenMode _ANSI_ARGS_((Tcl_Interp *interp,
			    char *string, int *modePtr));
static char *		EscapeStr _ANSI_ARGS_((ClientData clientd,
			    char *string, int length));

/*
 *----------------------------------------------------------------------
 *
 * Bin_GetsCmd --
 *
 *	This procedure is invoked to process the "bin_gets" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Bin_GetsCmd(clientd, interp, argc, argv)
    ClientData clientd;			/* Not used. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
#   define BUF_SIZE 200
    char buffer[BUF_SIZE+1];
    int totalCount, done, flags, binmode;
/*    BinClient *BinInfo = (BinClient *) clientd; */
    FILE *f;

    if ((argc != 2) && (argc != 3)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		" fileId ?varName?\"", (char *) NULL);
	return TCL_ERROR;
    }
    if (Tcl_GetOpenFile(interp, argv[1], 0, 1, &f) != TCL_OK) {
	return TCL_ERROR;
    }

    binmode = Tcl_FilePermissions(f) & TCL_FILE_BINARY;

    /*
     * We can't predict how large a line will be, so read it in
     * pieces, appending to the current result or to a variable.
     */

    totalCount = 0;
    done = 0;
    flags = 0;
    clearerr(f);
    while (!done) {
	register int c, count;
	register char *p;

	for (p = buffer, count = 0; count < BUF_SIZE-1; count++, p++) {
	    c = getc(f);
	    if (c == EOF) {
		if (ferror(f)) {
		    /*
		     * If the file is in non-blocking mode, return any
		     * bytes that were read before a block would occur.
		     */

		    if ((errno == EAGAIN)
			    && ((count > 0 || totalCount > 0))) {
			done = 1;
			break;
		    }
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "error reading \"", argv[1],
			    "\": ", Tcl_PosixError(interp), (char *) NULL);
		    return TCL_ERROR;
		} else if (feof(f)) {
		    if ((totalCount == 0) && (count == 0)) {
			totalCount = -1;
		    }
		    done = 1;
		    break;
		}
	    }
	    if (c == '\n') {
		done = 1;
		break;
	    }
	    *p = c;
	}
	*p = 0;
	if (binmode)
	    p = EscapeStr(clientd, buffer, count);
	else
	    p = buffer;
	if (argc == 2) {
	    Tcl_AppendResult(interp, p, (char *) NULL);
	    if (binmode)
		ckfree(p);
	} else {
	    if (Tcl_SetVar(interp, argv[2], p, flags|TCL_LEAVE_ERR_MSG) == NULL) {
		if (binmode)
		    ckfree(p);
		return TCL_ERROR;
	    }
	    flags = TCL_APPEND_VALUE;
	    if (binmode)
		ckfree(p);
	}
	totalCount += count;
    }

    if (argc == 3) {
	sprintf(interp->result, "%d", totalCount);
    }
    return TCL_OK;
}

char *
EscapeStr(clientd, strptr, length)
    ClientData clientd;			/* Not used. */
    char strptr[];			/* Ptr to buf containing string */
    int	length;				/* length of string */
{
	char	*buffer, *bufptr;
	int	i;
	BinClient *BinInfo = (BinClient *) clientd;

	buffer = ckalloc(length*2+1);

	for (bufptr = buffer, i=0; i<length; i++) {
		if (UCHAR(strptr[i]) == UCHAR(BinInfo->escChar)) {
			*bufptr++ = UCHAR(BinInfo->escChar);
			*bufptr++ = UCHAR(BinInfo->escChar);
		} else if (strptr[i] == '\0') {
			*bufptr++ = UCHAR(BinInfo->escChar);
			*bufptr++ = '0';
		} else {
			*bufptr++ = strptr[i];
		}
	}
	*bufptr = '\0';
	return buffer;
}


#ifdef O_NOCTTY
#ifdef O_NONBLOCK
#define	O_BINARY (~(O_RDONLY|O_WRONLY|O_RDWR|O_APPEND|O_CREAT|O_EXCL|O_NOCTTY|O_NONBLOCK|O_TRUNC))
#else
#define	O_BINARY (~(O_RDONLY|O_WRONLY|O_RDWR|O_APPEND|O_CREAT|O_EXCL|O_NOCTTY|O_NDELAY|O_TRUNC))
#endif
#else /* O_NOCTTY */
#ifdef O_NONBLOCK
#define	O_BINARY (~(O_RDONLY|O_WRONLY|O_RDWR|O_APPEND|O_CREAT|O_EXCL|O_NONBLOCK|O_TRUNC))
#else
#define	O_BINARY (~(O_RDONLY|O_WRONLY|O_RDWR|O_APPEND|O_CREAT|O_EXCL|O_NDELAY|O_TRUNC))
#endif
#endif /* O_NOCTTY */
/*
 *----------------------------------------------------------------------
 *
 * Bin_OpenCmd --
 *
 *	This procedure is invoked to process the "open" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Bin_OpenCmd(clientd, interp, argc, argv)
    ClientData clientd;			/* Not used. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
    int pipeline, fd, mode, prot, readWrite, permissions;
    BinClient *BinInfo = (BinClient *) clientd;
    char *access;
    FILE *f, *f2;

    if ((argc < 2) || (argc > 4)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		" filename ?access? ?permissions?\"", (char *) NULL);
	return TCL_ERROR;
    }
    prot = 0666;
    if (argc == 2) {
	if (BinInfo->defBinmode)
	    mode = O_RDONLY|O_BINARY;
	else
	    mode = O_RDONLY;
	access = "r";
    } else {
	if (BinInfo->defBinmode)
		mode = O_BINARY;
	else
		mode = 0;
	access = GetOpenMode(interp, argv[2], &mode);
	if (access == NULL) {
	    return TCL_ERROR;
	}
	if (argc == 4) {
	    if (Tcl_GetInt(interp, argv[3], &prot) != TCL_OK) {
		return TCL_ERROR;
	    }
	}
    }

    f = f2 = NULL;
    readWrite = mode & (O_RDWR|O_RDONLY|O_WRONLY);
    if (readWrite == O_RDONLY) {
	permissions = TCL_FILE_READABLE;
    } else if (readWrite == O_WRONLY) {
	permissions = TCL_FILE_WRITABLE;
    } else {
	permissions = TCL_FILE_READABLE|TCL_FILE_WRITABLE;
    }

    if (mode & O_BINARY) {
	permissions |= TCL_FILE_BINARY;
    }

    pipeline = 0;
    if (argv[1][0] == '|') {
	pipeline = 1;
    }

    /*
     * Open the file or create a process pipeline.
     */

    if (!pipeline) {
	char *fileName;
	Tcl_DString buffer;

	fileName = Tcl_TildeSubst(interp, argv[1], &buffer);
	if (fileName == NULL) {
	    return TCL_ERROR;
	}
	fd = open(fileName, mode, prot);
	Tcl_DStringFree(&buffer);
	if (fd < 0) {
	    Tcl_AppendResult(interp, "couldn't open \"", argv[1],
		    "\": ", Tcl_PosixError(interp), (char *) NULL);
	    return TCL_ERROR;
	}
	f = fdopen(fd, access);
	if (f == NULL) {
	    close(fd);
	    return TCL_ERROR;
	}
	Tcl_EnterFile(interp, f, permissions);
    } else {
	int *inPipePtr, *outPipePtr;
	int cmdArgc, inPipe, outPipe, numPids, *pidPtr, errorId;
	char **cmdArgv;
	OpenFile *oFilePtr;

	if (Tcl_SplitList(interp, argv[1]+1, &cmdArgc, &cmdArgv) != TCL_OK) {
	    return TCL_ERROR;
	}
	inPipePtr = (permissions & TCL_FILE_WRITABLE) ? &inPipe : NULL;
	outPipePtr = (permissions & TCL_FILE_READABLE) ? &outPipe : NULL;
	inPipe = outPipe = errorId = -1;
	numPids = Tcl_CreatePipeline(interp, cmdArgc, cmdArgv,
		&pidPtr, inPipePtr, outPipePtr, &errorId);
	ckfree((char *) cmdArgv);
	if (numPids < 0) {
	    pipelineError:
	    if (f != NULL) {
		fclose(f);
	    }
	    if (f2 != NULL) {
		fclose(f2);
	    }
	    if (numPids > 0) {
		Tcl_DetachPids(numPids, pidPtr);
		ckfree((char *) pidPtr);
	    }
	    if (errorId != -1) {
		close(errorId);
	    }
	    return TCL_ERROR;
	}
	if (permissions & TCL_FILE_READABLE) {
	    if (outPipe == -1) {
		if (inPipe != -1) {
		    close(inPipe);
		}
		Tcl_AppendResult(interp, "can't read output from command:",
			" standard output was redirected", (char *) NULL);
		goto pipelineError;
	    }
	    f = fdopen(outPipe, "r");
	}
	if (permissions & TCL_FILE_WRITABLE) {
	    if (inPipe == -1) {
		Tcl_AppendResult(interp, "can't write input to command:",
			" standard input was redirected", (char *) NULL);
		goto pipelineError;
	    }
	    if (f != NULL) {
		f2 = fdopen(inPipe, "w");
	    } else {
		f = fdopen(inPipe, "w");
	    }
	}
	Tcl_EnterFile(interp, f, permissions);
	oFilePtr = tclOpenFiles[fileno(f)];
	oFilePtr->f2 = f2;
	oFilePtr->numPids = numPids;
	oFilePtr->pidPtr = pidPtr;
	oFilePtr->errorId = errorId;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetOpenMode --
 *
 *	description.
 *
 * Results:
 *	Normally, sets *modePtr to an access mode for passing to "open",
 *	and returns a string that can be used as the access mode in a
 *	subsequent call to "fdopen".  If an error occurs, then returns
 *	NULL and sets interp->result to an error message.
 *
 * Side effects:
 *	None.
 *
 * Special note:
 *	This code is based on a prototype implementation contributed
 *	by Mark Diekhans.
 *
 *----------------------------------------------------------------------
 */

static char *
GetOpenMode(interp, string, modePtr)
    Tcl_Interp *interp;			/* Interpreter to use for error
					 * reporting. */
    char *string;			/* Mode string, e.g. "r+" or
					 * "RDONLY CREAT". */
    int *modePtr;			/* Where to store mode corresponding
					 * to string. */
{
    int mode, modeArgc, c, i, gotRW;
    char **modeArgv, *flag;
#define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR)

    /*
     * Check for the simpler fopen-like access modes (e.g. "r").  They
     * are distinguished from the POSIX access modes by the presence
     * of a lower-case first letter.
     */

    mode = *modePtr;
    if (islower(UCHAR(string[0]))) {
	switch (string[0]) {
	    case 'r':
		mode |= O_RDONLY;
		break;
	    case 'w':
		mode |= O_WRONLY|O_CREAT|O_TRUNC;
		break;
	    case 'a':
		mode |= O_WRONLY|O_CREAT|O_APPEND;
		break;
	    default:
		error:
		Tcl_AppendResult(interp,
			"illegal access mode \"", string, "\"", (char *) NULL);
		return NULL;
	}
	if (string[1] == '+') {
	    mode &= ~(O_RDONLY|O_WRONLY);
	    mode |= O_RDWR;
	    if (string[2] == 'b') {
		mode |= O_BINARY;
		string[2] = '\0';
	    } else if (string[2] == 'a') {
		mode &= ~O_BINARY;
		string[2] = '\0';
	    } else if (string[2] != 0) {
		goto error;
	    }
	} else if (string[1] == 'b') {
	    mode |= O_BINARY;
	    string[1] = '\0';
	} else if (string[1] == 'a') {
	    mode &= ~O_BINARY;
	    string[1] = '\0';
	} else if (string[1] != 0) {
	    goto error;
	}
	*modePtr = mode;
	return string;
    }

    /*
     * The access modes are specified using a list of POSIX modes
     * such as O_CREAT.
     */

    if (Tcl_SplitList(interp, string, &modeArgc, &modeArgv) != TCL_OK) {
	Tcl_AddErrorInfo(interp, "\n    while processing open access modes \"");
	Tcl_AddErrorInfo(interp, string);
	Tcl_AddErrorInfo(interp, "\"");
	return NULL;
    }
    gotRW = 0;
    for (i = 0; i < modeArgc; i++) {
	flag = modeArgv[i];
	c = flag[0];
	if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
	    mode = (mode & ~RW_MODES) | O_RDONLY;
	    gotRW = 1;
	} else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
	    mode = (mode & ~RW_MODES) | O_WRONLY;
	    gotRW = 1;
	} else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
	    mode = (mode & ~RW_MODES) | O_RDWR;
	    gotRW = 1;
	} else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
	    mode |= O_APPEND;
	} else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
	    mode |= O_CREAT;
	} else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
	    mode |= O_EXCL;
	} else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
#ifdef O_NOCTTY
	    mode |= O_NOCTTY;
#else
	    Tcl_AppendResult(interp, "access mode \"", flag,
		    "\" not supported by this system", (char *) NULL);
	    ckfree((char *) modeArgv);
	    return NULL;
#endif
	} else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
#ifdef O_NONBLOCK
	    mode |= O_NONBLOCK;
#else
	    mode |= O_NDELAY;
#endif
	} else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
	    mode |= O_TRUNC;
	} else if ((c == 'B') && (strcmp(flag, "BINARY") == 0)) {
	    mode |= O_BINARY;
	} else if ((c == 'A') && (strcmp(flag, "ASCII") == 0)) {
	    mode &= ~O_BINARY;
	} else {
	    Tcl_AppendResult(interp, "invalid access mode \"", flag,
		    "\": must be RDONLY, WRONLY, RDWR, APPEND, CREAT, EXCL,",
		    " NOCTTY, NONBLOCK, TRUNC, BINARY, or ASCII",
		    (char *) NULL);
	    ckfree((char *) modeArgv);
	    return NULL;
	}
    }
    ckfree((char *) modeArgv);
    if (!gotRW) {
	Tcl_AppendResult(interp, "access mode must include either",
		" RDONLY, WRONLY, or RDWR", (char *) NULL);
	return NULL;
    }
    *modePtr = mode;

    /*
     * The calculation of fdopen access mode below isn't really correct,
     * but it doesn't have to be.  All it has to do is to disinguish
     * read and write permissions, plus indicate append mode.
     */

    i = mode & RW_MODES;
    if (i == O_RDONLY) {
	return "r";
    }
    if (mode & O_APPEND) {
	if (i == O_WRONLY) {
	    return "a";
	} else {
	    return "a+";
	}
    }
    if (i == O_WRONLY) {
	return "w";
    }
    return "r+";
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_PutsCmd --
 *
 *	This procedure is invoked to process the "puts" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Bin_PutsCmd(clientd, interp, argc, argv)
    ClientData clientd;			/* Not used. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
    FILE *f;
    int i, newline;
    BinClient *BinInfo = (BinClient *) clientd;
    char *fileId;
    unsigned char *p;

    i = 1;
    newline = 1;
    if ((argc >= 2) && (strcmp(argv[1], "-nonewline") == 0)) {
	newline = 0;
	i++;
    }
    if ((i < (argc-3)) || (i >= argc)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		"\" ?-nonewline? ?fileId? string", (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * The code below provides backwards compatibility with an old
     * form of the command that is no longer recommended or documented.
     */

    if (i == (argc-3)) {
	if (strncmp(argv[i+2], "nonewline", strlen(argv[i+2])) != 0) {
	    Tcl_AppendResult(interp, "bad argument \"", argv[i+2],
		    "\": should be \"nonewline\"", (char *) NULL);
	    return TCL_ERROR;
	}
	newline = 0;
    }
    if (i == (argc-1)) {
	fileId = "stdout";
    } else {
	fileId = argv[i];
	i++;
    }

    if (Tcl_GetOpenFile(interp, fileId, 1, 1, &f) != TCL_OK) {
	return TCL_ERROR;
    }

    clearerr(f);
    
    if (Tcl_FilePermissions(f) & TCL_FILE_BINARY) {
	for (p = (unsigned char *) argv[i]; *p; p++) {
	    if ( *p == UCHAR(BinInfo->escChar) && *++p == UCHAR('0') ) {
		fputc('\0', f);
	    } else {
		fputc(*p, f);
	    }
	}
    } else {
	fputs(argv[i], f);
    }
    if (newline) {
	fputc('\n', f);
    }
    if (ferror(f)) {
	Tcl_AppendResult(interp, "error writing \"", fileId,
		"\": ", Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Bin_ReadCmd --
 *
 *	This procedure is invoked to process the "read" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Bin_ReadCmd(clientd, interp, argc, argv)
    ClientData clientd;			/* Not used. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
    int bytesLeft, bytesRead, askedFor, got;
#define READ_BUF_SIZE 4096
    char buffer[READ_BUF_SIZE+1], *p;
    int newline, i, binmode;
    FILE *f;

    if ((argc != 2) && (argc != 3)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		" fileId ?numBytes?\" or \"", argv[0],
		" ?-nonewline? fileId\"", (char *) NULL);
	return TCL_ERROR;
    }
    i = 1;
    newline = 1;
    if ((argc == 3) && (strcmp(argv[1], "-nonewline") == 0)) {
	newline = 0;
	i++;
    }
    if (Tcl_GetOpenFile(interp, argv[i], 0, 1, &f) != TCL_OK) {
	return TCL_ERROR;
    }

    binmode = Tcl_FilePermissions(f) & TCL_FILE_BINARY;
    /*
     * Compute how many bytes to read, and see whether the final
     * newline should be dropped.
     */

    if ((argc >= (i + 2)) && isdigit(UCHAR(argv[i+1][0]))) {
	if (Tcl_GetInt(interp, argv[i+1], &bytesLeft) != TCL_OK) {
	    return TCL_ERROR;
	}
    } else {
	bytesLeft = INT_MAX;

	/*
	 * The code below provides backward compatibility for an
	 * archaic earlier version of this command.
	 */

	if (argc >= (i + 2)) {
	    if (strncmp(argv[i+1], "nonewline", strlen(argv[i+1])) == 0) {
		newline = 0;
	    } else {
		Tcl_AppendResult(interp, "bad argument \"", argv[i+1],
			"\": should be \"nonewline\"", (char *) NULL);
		return TCL_ERROR;
	    }
	}
    }

    /*
     * Read the file in one or more chunks.
     */

    bytesRead = 0;
    clearerr(f);
    while (bytesLeft > 0) {
	askedFor = READ_BUF_SIZE;
	if (bytesLeft < READ_BUF_SIZE) {
	    askedFor = bytesLeft;
	}
	got = fread(buffer, 1, askedFor, f);
	if (ferror(f)) {
	    /*
	     * If the file is in non-blocking mode, break out of the
	     * loop and return any bytes that were read.
	     */

	    if ((errno == EAGAIN) && ((got > 0) || (bytesRead > 0))) {
		clearerr(f);
		bytesLeft = got;
	    } else {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "error reading \"", argv[i],
			"\": ", Tcl_PosixError(interp), (char *) NULL);
		return TCL_ERROR;
	    }
	}
	if (got != 0) {
	    buffer[got] = 0;
	    if (binmode)
		p = EscapeStr(clientd, buffer, got);
	    else
		p = buffer;
	    Tcl_AppendResult(interp, p, (char *) NULL);
	    if (binmode)
		ckfree(p);
	    bytesLeft -= got;
	    bytesRead += got;
	}
	if (got < askedFor) {
	    break;
	}
    }
    if ((newline == 0) && (bytesRead > 0)
	    && (interp->result[bytesRead-1] == '\n')) {
	interp->result[bytesRead-1] = 0;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_EofCmd --
 *
 *	This procedure is invoked to process the "eof" Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Bin_EofCmd(notUsed, interp, argc, argv)
    ClientData notUsed;			/* Not used. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
    FILE *f;

    if (argc != 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		" fileId\"", (char *) NULL);
	return TCL_ERROR;
    }
    if (Tcl_GetOpenFile(interp, argv[1], 0, 0, &f) != TCL_OK) {
	return TCL_ERROR;
    }
    if (feof(f)) {
	interp->result = "1";
    } else {
	interp->result = "0";
    }
    return TCL_OK;
}
