/* Copyright (C) 1992, 1995, 1996, 1997, 1998, 1999, 2000 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gp_os2.c,v 1.10.2.3.2.1 2003/01/17 00:49:02 giles Exp $ */
/* Common platform-specific routines for OS/2 and MS-DOS */
/* compiled with GCC/EMX */

#define INCL_DOS
#define INCL_SPL
#define INCL_SPLDOSPRINT
#define INCL_SPLERRORS
#define INCL_BASE
#define INCL_ERRORS
#define INCL_WIN
#include <os2.h>

#include "pipe_.h"
#include "stdio_.h"
#include "string_.h"
#include <fcntl.h>

#ifdef __IBMC__
#define popen fopen		/* doesn't support popen */
#define pclose fclose		/* doesn't support pclose */
#else
#include <dos.h>
#endif
/* Define the regs union tag for short registers. */
#  define rshort x
#define intdos(a,b) _int86(0x21, a, b)

#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gsexit.h"
#include "gsmemory.h"
#include "gsstruct.h"
#include "gp.h"
#include "gpmisc.h"
#include "gsutil.h"
#include "stdlib.h"		/* need _osmode, exit */
#include "time_.h"
#include <time.h>		/* should this be in time_.h? */
#include "gdevpm.h"
#ifdef __EMX__
#include <sys/emxload.h>
#endif

#if defined(__DLL__) && defined( __EMX__)
/* This isn't provided in any of the libraries */
/* We set this to the process environment in gp_init */
char *fake_environ[3] =
{"", NULL, NULL};
char **environ = fake_environ;
char **_environ = fake_environ;
HWND hwndtext = (HWND) NULL;

#endif

#ifdef __DLL__
/* use longjmp instead of exit when using DLL */
#include <setjmp.h>
extern jmp_buf gsdll_env;

#endif

#ifdef __DLL__
#define isos2 TRUE
#else
#define isos2 (_osmode == OS2_MODE)
#endif
char pm_prntmp[256];		/* filename of printer spool temporary file */


/* ------ Miscellaneous ------ */

/* Get the string corresponding to an OS error number. */
/* All reasonable compilers support it. */
const char *
gp_strerror(int errnum)
{
    return strerror(errnum);
}

/* use Unix version for date and time */
/* ------ Date and time ------ */

/* Read the current time (in seconds since Jan. 1, 1970) */
/* and fraction (in nanoseconds since midnight). */
void
gp_get_realtime(long *pdt)
{
    struct timeval tp;
    struct timezone tzp;

    if (gettimeofday(&tp, &tzp) == -1) {
	lprintf("Ghostscript: gettimeofday failed!\n");
	tp.tv_sec = tp.tv_usec = 0;
    }
    /* tp.tv_sec is #secs since Jan 1, 1970 */
    pdt[0] = tp.tv_sec;
    pdt[1] = tp.tv_usec * 1000;

#ifdef DEBUG_CLOCK
    printf("tp.tv_sec = %d  tp.tv_usec = %d  pdt[0] = %ld  pdt[1] = %ld\n",
	   tp.tv_sec, tp.tv_usec, pdt[0], pdt[1]);
#endif
}

/* Read the current user CPU time (in seconds) */
/* and fraction (in nanoseconds).  */
void
gp_get_usertime(long *pdt)
{
    gp_get_realtime(pdt);	/* Use an approximation for now.  */
}


/* ------ Console management ------ */

/* Answer whether a given file is the console (input or output). */
/* This is not a standard gp procedure, */
/* but the MS Windows configuration needs it, */
/* and other MS-DOS configurations might need it someday. */
/* Don't know if it is needed for OS/2. */
bool
gp_file_is_console(FILE * f)
{
#ifndef __DLL__
    if (!isos2) {
	union REGS regs;

	if (f == NULL)
	    return false;
	regs.h.ah = 0x44;	/* ioctl */
	regs.h.al = 0;		/* get device info */
	regs.rshort.bx = fileno(f);
	intdos(&regs, &regs);
	return ((regs.h.dl & 0x80) != 0 && (regs.h.dl & 3) != 0);
    }
#endif
    if ((f == gs_stdin) || (f == gs_stdout) || (f == gs_stderr))
	return true;
    return false;
}

/* ------ File naming and accessing ------ */

/* Define the character used for separating file names in a list. */
const char gp_file_name_list_separator = ';';

/* Define the default scratch file name prefix. */
const char gp_scratch_file_name_prefix[] = "gs";

/* Define the name of the null output file. */
const char gp_null_file_name[] = "nul";

/* Define the name that designates the current directory. */
const char gp_current_directory_name[] = ".";

/* Define the string to be concatenated with the file mode */
/* for opening files without end-of-line conversion. */
const char gp_fmode_binary_suffix[] = "b";

/* Define the file modes for binary reading or writing. */
const char gp_fmode_rb[] = "rb";
const char gp_fmode_wb[] = "wb";

/* Answer whether a path_string can meaningfully have a prefix applied */
bool
gp_pathstring_not_bare(const char *fname, uint len)
{   /* A file name is not bare if it contains a drive specifications	*/
    /* (second character is a :) or if it starts with a '.', '/' or '\\'*/
    /* or it contains '/../' (parent reference)				*/
    if ((len > 0) && ((*fname == '/') || (*fname == '\\') ||
	  (*fname == '.') || ((len > 2) && (fname[1] == ':'))))
	return true;
    while (len-- > 3) {
        int c = *fname++;

	if (((c == '/') || (c == '\\')) &&
	    ((len >= 3) && (bytes_compare(fname, 2, "..", 2) == 0) &&
			((fname[2] == '/') || (fname[2] == '\\'))))
	    return true;
}
    return false;
}

/* Answer whether the file_name references the directory	*/
/* containing the specified path (parent). 			*/
bool
gp_file_name_references_parent(const char *fname, unsigned len)
{
    int i = 0, last_sep_pos = -1;

    /* A file name references its parent directory if it starts */
    /* with ../ or ..\  or if one of these strings follows / or \ */
    while (i < len) {
	if (fname[i] == '/' || fname[i] == '\\') {
	    last_sep_pos = i++;
	    continue;
	}
	if (fname[i++] != '.')
	    continue;
        if (i > last_sep_pos + 2 || (i < len && fname[i] != '.'))
	    continue;
	i++;
	/* have separator followed by .. */
	if (i < len && (fname[i] == '/' || fname[i++] == '\\'))
	    return true;
    }
    return false;
}


/* Answer the string to be used for combining a directory/device prefix */
/* with a base file name. The prefix directory/device is examined to	*/
/* determine if a separator is needed and may return an empty string	*/
const char *
gp_file_name_concat_string(const char *prefix, uint plen)
{
    if (plen > 0)
	switch (prefix[plen - 1]) {
	    case ':':
	    case '/':
	    case '\\':
		return "";
	};
    return "/";
}


/* ------ File enumeration ------ */


struct file_enum_s {
    FILEFINDBUF3 findbuf;
    HDIR hdir;
    char *pattern;
    int patlen;			/* orig pattern length */
    int pat_size;		/* allocate space for pattern */
    int head_size;		/* pattern length through last */
    /* :, / or \ */
    int first_time;
    gs_memory_t *memory;
};
gs_private_st_ptrs1(st_file_enum, struct file_enum_s, "file_enum",
		    file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern);

/* Initialize an enumeration.  may NEED WORK ON HANDLING * ? \. */
file_enum *
gp_enumerate_files_init(const char *pat, uint patlen, gs_memory_t * mem)
{
    file_enum *pfen = gs_alloc_struct(mem, file_enum, &st_file_enum, "gp_enumerate_files");
    int pat_size = 2 * patlen + 1;
    char *pattern;
    int hsize = 0;
    int i;

    if (pfen == 0)
	return 0;

    /* pattern could be allocated as a string, */
    /* but it's simpler for GC and freeing to allocate it as bytes. */

    pattern = (char *)gs_alloc_bytes(mem, pat_size,
				     "gp_enumerate_files(pattern)");
    if (pattern == 0)
	return 0;
    memcpy(pattern, pat, patlen);
    /* find directory name = header */
    for (i = 0; i < patlen; i++) {
	switch (pat[i]) {
	    case '\\':
		if (i + 1 < patlen && pat[i + 1] == '\\')
		    i++;
		/* falls through */
	    case ':':
	    case '/':
		hsize = i + 1;
	}
    }
    pattern[patlen] = 0;
    pfen->pattern = pattern;
    pfen->patlen = patlen;
    pfen->pat_size = pat_size;
    pfen->head_size = hsize;
    pfen->memory = mem;
    pfen->first_time = 1;
    pfen->hdir = HDIR_CREATE;
    return pfen;
}

/* Enumerate the next file. */
uint
gp_enumerate_files_next(file_enum * pfen, char *ptr, uint maxlen)
{
    APIRET rc;
    ULONG cFilenames = 1;

    if (!isos2) {
	/* CAN'T DO IT SO JUST RETURN THE PATTERN. */
	if (pfen->first_time) {
	    char *pattern = pfen->pattern;
	    uint len = strlen(pattern);

	    pfen->first_time = 0;
	    if (len > maxlen)
		return maxlen + 1;
	    strcpy(ptr, pattern);
	    return len;
	}
	return -1;
    }
    /* else OS/2 */
    if (pfen->first_time) {
	rc = DosFindFirst(pfen->pattern, &pfen->hdir, FILE_NORMAL,
			  &pfen->findbuf, sizeof(pfen->findbuf),
			  &cFilenames, FIL_STANDARD);
	pfen->first_time = 0;
    } else {
	rc = DosFindNext(pfen->hdir, &pfen->findbuf, sizeof(pfen->findbuf),
			 &cFilenames);
    }
    if (rc)
	return -1;

    if (pfen->head_size + pfen->findbuf.cchName < maxlen) {
	memcpy(ptr, pfen->pattern, pfen->head_size);
	strcpy(ptr + pfen->head_size, pfen->findbuf.achName);
	return pfen->head_size + pfen->findbuf.cchName;
    }
    if (pfen->head_size >= maxlen)
	return 0;		/* no hope at all */

    memcpy(ptr, pfen->pattern, pfen->head_size);
    strncpy(ptr + pfen->head_size, pfen->findbuf.achName,
	    maxlen - pfen->head_size - 1);
    return maxlen;
}

/* Clean up the file enumeration. */
void
gp_enumerate_files_close(file_enum * pfen)
{
    gs_memory_t *mem = pfen->memory;

    if (isos2)
	DosFindClose(pfen->hdir);
    gs_free_object(mem, pfen->pattern,
		   "gp_enumerate_files_close(pattern)");
    gs_free_object(mem, pfen, "gp_enumerate_files_close");
}

/*************************************************************/
/* from gp_iwatc.c and gp_itbc.c */

/* Intel processor, EMX/GCC specific routines for Ghostscript */
#include <signal.h>
#include "stat_.h"
#include "string_.h"

/* Library routines not declared in a standard header */
/* extern char *getenv(P1(const char *)); */

/* Forward declarations */
private void handle_FPE(P1(int));

/* Do platform-dependent initialization. */
void
gp_init(void)
{
#if defined(__DLL__) && defined(__EMX__)
    PTIB pptib;
    PPIB pppib;
    int i;
    char *p;

    /* get environment of EXE */
    DosGetInfoBlocks(&pptib, &pppib);
    for (i = 0, p = pppib->pib_pchenv; *p; p += strlen(p) + 1)
	i++;
    _environ = environ = (char **)malloc((i + 2) * sizeof(char *));

    for (i = 0, p = pppib->pib_pchenv; *p; p += strlen(p) + 1) {
	environ[i] = p;
	i++;
    }
    environ[i] = p;
    i++;
    environ[i] = NULL;
#endif

    /* keep gsos2.exe in memory for number of minutes specified in */
    /* environment variable GS_LOAD */
#ifdef __EMX__
    _emxload_env("GS_LOAD");
#endif

    /* Set up the handler for numeric exceptions. */
    signal(SIGFPE, handle_FPE);
}


/* Trap numeric exceptions.  Someday we will do something */
/* more appropriate with these. */
private void
handle_FPE(int sig)
{
    eprintf("Numeric exception:\n");
    exit(1);
}

/* Do platform-dependent cleanup. */
void
gp_exit(int exit_status, int code)
{
#if defined(__DLL__) && defined(__EMX__)
    if (environ != fake_environ) {
	free(environ);
	environ = _environ = fake_environ;
    }
#endif
}

/* Exit the program. */
void
gp_do_exit(int exit_status)
{
}

/* ------ Printer accessing ------ */
private int pm_find_queue(char *queue_name, char *driver_name);
private int is_os2_spool(const char *queue);
private int pm_spool(char *filename, const char *queue);

/* Put a printer file (which might be stdout) into binary or text mode. */
/* This is not a standard gp procedure, */
/* but all MS-DOS configurations need it. */
void
gp_set_file_binary(int prnfno, int binary)
{
#ifndef __IBMC__
    union REGS regs;

    regs.h.ah = 0x44;		/* ioctl */
    regs.h.al = 0;		/* get device info */
    regs.rshort.bx = prnfno;
    intdos(&regs, &regs);
    if (((regs.rshort.flags) & 1) != 0 || !(regs.h.dl & 0x80))
	return;			/* error, or not a device */
    if (binary)
	regs.h.dl |= 0x20;	/* binary (no ^Z intervention) */
    else
	regs.h.dl &= ~0x20;	/* text */
    regs.h.dh = 0;
    regs.h.ah = 0x44;		/* ioctl */
    regs.h.al = 1;		/* set device info */
    intdos(&regs, &regs);
#endif
}

/* Open a connection to a printer.  A null file name means use the */
/* standard printer connected to the machine, if any. */
/* Return NULL if the connection could not be opened. */
/* filename can be one of the following values
 *   ""                Spool in default queue
 *   "\\spool\queue"   Spool in "queue"
 *   "|command"        open an output pipe using popen()
 *   "filename"        open filename using fopen()
 *   "port"            open port using fopen()
 */
FILE *
gp_open_printer(char fname[gp_file_name_sizeof], int binary_mode)
{
    FILE *pfile;

    if ((strlen(fname) == 0) || is_os2_spool(fname)) {
	if (isos2) {
	    /* default or spool */
	    if (pm_spool(NULL, fname))	/* check if spool queue valid */
		return NULL;
	    pfile = gp_open_scratch_file(gp_scratch_file_name_prefix,
				     pm_prntmp, (binary_mode ? "wb" : "w"));
	} else
	    pfile = fopen("PRN", (binary_mode ? "wb" : "w"));
    } else if ((isos2) && (fname[0] == '|'))
	/* pipe */
	pfile = popen(fname + 1, (binary_mode ? "wb" : "w"));
    else
	/* normal file or port */
	pfile = fopen(fname, (binary_mode ? "wb" : "w"));

    if (pfile == (FILE *) NULL)
	return (FILE *) NULL;
    if (!isos2)
	gp_set_file_binary(fileno(pfile), binary_mode);
    return pfile;
}

/* Close the connection to the printer. */
void
gp_close_printer(FILE * pfile, const char *fname)
{
    if (isos2 && (fname[0] == '|'))
	pclose(pfile);
    else
	fclose(pfile);

    if ((strlen(fname) == 0) || is_os2_spool(fname)) {
	/* spool temporary file */
	pm_spool(pm_prntmp, fname);
	unlink(pm_prntmp);
    }
}

/* ------ File accessing -------- */

/* Set a file into binary or text mode. */
int
gp_setmode_binary(FILE * pfile, bool binary)
{
    gp_set_file_binary(fileno(pfile), binary);
    return 0;
}

/* ------ Printer Spooling ------ */
#ifndef NERR_BufTooSmall
#define NERR_BufTooSmall 2123	/* For SplEnumQueue */
#endif

/* If queue_name is NULL, list available queues */
/* If strlen(queue_name)==0, return default queue and driver name */
/* If queue_name supplied, return driver_name */
/* returns 0 if OK, non-zero for error */
private int
pm_find_queue(char *queue_name, char *driver_name)
{
    SPLERR splerr;
    USHORT jobCount;
    ULONG cbBuf;
    ULONG cTotal;
    ULONG cReturned;
    ULONG cbNeeded;
    ULONG ulLevel;
    ULONG i;
    PSZ pszComputerName;
    PBYTE pBuf;
    PPRQINFO3 prq;

    ulLevel = 3L;
    pszComputerName = (PSZ) NULL;
    splerr = SplEnumQueue(pszComputerName, ulLevel, pBuf, 0L,	/* cbBuf */
			  &cReturned, &cTotal,
			  &cbNeeded, NULL);
    if (splerr == ERROR_MORE_DATA || splerr == NERR_BufTooSmall) {
	if (!DosAllocMem((PVOID) & pBuf, cbNeeded,
			 PAG_READ | PAG_WRITE | PAG_COMMIT)) {
	    cbBuf = cbNeeded;
	    splerr = SplEnumQueue(pszComputerName, ulLevel, pBuf, cbBuf,
				  &cReturned, &cTotal,
				  &cbNeeded, NULL);
	    if (splerr == NO_ERROR) {
		/* Set pointer to point to the beginning of the buffer.           */
		prq = (PPRQINFO3) pBuf;

		/* cReturned has the count of the number of PRQINFO3 structures.  */
		for (i = 0; i < cReturned; i++) {
		    if (queue_name) {
			/* find queue name and return driver name */
			if (strlen(queue_name) == 0) {	/* use default queue */
			    if (prq->fsType & PRQ3_TYPE_APPDEFAULT)
				strcpy(queue_name, prq->pszName);
			}
			if (strcmp(prq->pszName, queue_name) == 0) {
			    char *p;

			    for (p = prq->pszDriverName; *p && (*p != '.'); p++)
				/* do nothing */ ;
			    *p = '\0';	/* truncate at '.' */
			    if (driver_name != NULL)
				strcpy(driver_name, prq->pszDriverName);
			    DosFreeMem((PVOID) pBuf);
			    return 0;
			}
		    } else {
			/* list queue details */
			if (prq->fsType & PRQ3_TYPE_APPDEFAULT)
			    eprintf1("  %s  (DEFAULT)\n", prq->pszName);
			else
			    eprintf1("  %s\n", prq->pszName);
		    }
		    prq++;
		}		/*endfor cReturned */
	    }
	    DosFreeMem((PVOID) pBuf);
	}
    }
    /* end if Q level given */ 
    else {
	/* If we are here we had a bad error code. Print it and some other info. */
	eprintf4("SplEnumQueue Error=%ld, Total=%ld, Returned=%ld, Needed=%ld\n",
		splerr, cTotal, cReturned, cbNeeded);
    }
    if (splerr)
	return splerr;
    if (queue_name)
	return -1;
    return 0;
}


/* return TRUE if queue looks like a valid OS/2 queue name */
private int
is_os2_spool(const char *queue)
{
    char *prefix = "\\\\spool\\";	/* 8 characters long */
    int i;

    for (i = 0; i < 8; i++) {
	if (prefix[i] == '\\') {
	    if ((*queue != '\\') && (*queue != '/'))
		return FALSE;
	} else if (tolower(*queue) != prefix[i])
	    return FALSE;
	queue++;
    }
    return TRUE;
}

#define PRINT_BUF_SIZE 16384

/* Spool file to queue */
/* return 0 if successful, non-zero if error */
/* if filename is NULL, return 0 if spool queue is valid, non-zero if error */
private int
pm_spool(char *filename, const char *queue)
{
    HSPL hspl;
    PDEVOPENSTRUC pdata;
    PSZ pszToken = "*";
    ULONG jobid;
    BOOL rc;
    char queue_name[256];
    char driver_name[256];
    char *buffer;
    FILE *f;
    int count;

    if (strlen(queue) != 0) {
	/* queue specified */
	strcpy(queue_name, queue + 8);	/* skip over \\spool\ */
    } else {
	/* get default queue */
	queue_name[0] = '\0';
    }
    if (pm_find_queue(queue_name, driver_name)) {
	/* error, list valid queue names */
	eprintf("Invalid queue name.  Use one of:\n");
	pm_find_queue(NULL, NULL);
	return 1;
    }
    if (!filename)
	return 0;		/* we were only asked to check the queue */


    if ((buffer = malloc(PRINT_BUF_SIZE)) == (char *)NULL) {
	eprintf("Out of memory in pm_spool\n");
	return 1;
    }
    if ((f = fopen(filename, "rb")) == (FILE *) NULL) {
	free(buffer);
	eprintf1("Can't open temporary file %s\n", filename);
	return 1;
    }
    /* Allocate memory for pdata */
    if (!DosAllocMem((PVOID) & pdata, sizeof(DEVOPENSTRUC),
		     (PAG_READ | PAG_WRITE | PAG_COMMIT))) {
	/* Initialize elements of pdata */
	pdata->pszLogAddress = queue_name;
	pdata->pszDriverName = driver_name;
	pdata->pdriv = NULL;
	pdata->pszDataType = "PM_Q_RAW";
	pdata->pszComment = "Ghostscript";
	pdata->pszQueueProcName = NULL;
	pdata->pszQueueProcParams = NULL;
	pdata->pszSpoolerParams = NULL;
	pdata->pszNetworkParams = NULL;

	hspl = SplQmOpen(pszToken, 4L, (PQMOPENDATA) pdata);
	if (hspl == SPL_ERROR) {
	    eprintf("SplQmOpen failed.\n");
	    DosFreeMem((PVOID) pdata);
	    free(buffer);
	    fclose(f);
	    return 1;		/* failed */
	}
	rc = SplQmStartDoc(hspl, "Ghostscript");
	if (!rc) {
	    eprintf("SplQmStartDoc failed.\n");
	    DosFreeMem((PVOID) pdata);
	    free(buffer);
	    fclose(f);
	    return 1;
	}
	/* loop, copying file to queue */
	while (rc && (count = fread(buffer, 1, PRINT_BUF_SIZE, f)) != 0) {
	    rc = SplQmWrite(hspl, count, buffer);
	    if (!rc)
		eprintf("SplQmWrite failed.\n");
	}
	free(buffer);
	fclose(f);

	if (!rc) {
	    eprintf("Aborting Spooling.\n");
	    SplQmAbort(hspl);
	} else {
	    SplQmEndDoc(hspl);
	    rc = SplQmClose(hspl);
	    if (!rc)
		eprintf("SplQmClose failed.\n");
	}
    } else
	rc = 0;			/* no memory */
    return !rc;
}

/* ------ File naming and accessing ------ */

/* Create and open a scratch file with a given name prefix. */
/* Write the actual file name at fname. */
FILE *
gp_open_scratch_file(const char *prefix, char fname[gp_file_name_sizeof],
		     const char *mode)
{
#ifdef __IBMC__
    char *temp = 0;
    char *tname;
    int prefix_length = strlen(prefix);

    if (!gp_pathstring_not_bare(prefix, prefix_length)) {
	temp = getenv("TMPDIR");
	if (temp == 0)
	    temp = getenv("TEMP");
    }
    *fname = 0;
    tname = _tempnam(temp, (char *)prefix);
    if (tname) {
	if (strlen(tname) > gp_file_name_sizeof - 1) {
	    free(tname);
	    return 0;		/* file name too long */
	}
	strcpy(fname, tname);
	free(tname);
    }
#else
    /* The -7 is for XXXXXX plus a possible final \. */
    int prefix_length = strlen(prefix);
    int len = gp_file_name_sizeof - prefix_length - 7;

    if (gp_pathstring_not_bare(prefix, prefix_length) ||
	gp_gettmpdir(fname, &len) != 0)
	*fname = 0;
    else {
	char last = '\\';
	char *temp;

	/* Prevent X's in path from being converted by mktemp. */
	for (temp = fname; *temp; temp++)
	    *temp = last = tolower(*temp);
	switch (last) {
	default:
	    strcat(fname, "\\");
	case ':':
	case '\\':
	    ;
	}
    }
    if (strlen(fname) + prefix_length + 7 >= gp_file_name_sizeof)
	return 0;		/* file name too long */
    strcat(fname, prefix);
    strcat(fname, "XXXXXX");
    mktemp(fname);
#endif
    return gp_fopentemp(fname, mode);
}

/* Open a file with the given name, as a stream of uninterpreted bytes. */
FILE *
gp_fopen(const char *fname, const char *mode)
{
    return fopen(fname, mode);
}

