/* 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, 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.  */

#define INCL_DOSSESMGR
#define INCL_DOSERRORS
#define INCL_DOSPROCESS
#include <os2.h>
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fnmatch.h>
#include <process.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "config.h"
#include "cvs.h"

/* Expand wildcards in argv.  We probably should be expanding wildcards
   via expand_wild instead; that way we could expand only filenames and
   not tag names and the like.  */

void
os2_initialize (pargc, pargv)
    int *pargc;
    char **pargv[];
{
    if (*pargc > 3 && strcmp((*pargv)[(*pargc)-2], "pserver") == 0) {
	int s = _impsockhandle(atoi((*pargv)[(*pargc)-1]), 0);
	dup2(s, STDIN_FILENO);
	dup2(s, STDOUT_FILENO);
	close(s);
	(*pargc)--;
    }
    _wildcard (pargc, pargv);
}


/* Modifies 'stat' so that always the same inode is returned.  EMX never
   returns the same value for st_ino.  Without this modification,
   release_delete in module src/release.c refuses to work.  Care must
   be taken if someone is using the value of st_ino (but as far as I know,
   no callers are).  */

int
os2_stat (name, buffer)
    const char *name;
    struct stat *buffer;
{
    int rc = stat (name, buffer);

    /* There are no inodes on OS/2.  */
    buffer->st_ino = 42;

    return rc;
}


/* We must not only change the directory, but also the current drive.
   Otherwise it is be impossible to have the working directory and the
   repository on different drives.  */

int
os2_chdir (name)
    const char *name;
{
    int len;

    len = strlen (name);
    if (len == 2 && name[len-1] == ':')
    {
	char dir[] = "X:/";

	dir[0] = name[0];
	return _chdir2 (dir);
    }	
    return _chdir2 (name);
}


/* getwd must return a drive specification.  */

char *
xgetwd ()
{
    return _getcwd2 (NULL, 1);
}


/* fnmatch must recognize OS/2 filename conventions: Filename case
   must be preserved, but ignored in searches.  It would perhaps be better
   to just have CVS pick how to match based on FILENAMES_CASE_INSENSITIVE
   or something rather than having an OS/2-specific version of CVS_FNMATCH.
   Note that lib/fnmatch.c uses FOLD_FN_CHAR; that is how we get
   case-insensitivity on NT (and VMS, I think).  */

#define _FNM_OS2           1
#define _FNM_IGNORECASE    128

int
os2_fnmatch (pattern, name, flags)
    const char *pattern;
    const char *name;
    int flags;
{
    return fnmatch (pattern, name, _FNM_IGNORECASE | _FNM_OS2 | flags);
}

int os2_chmod (name, mode)
    const char* name;
    int mode;
{
    FILE* fp;
    if ((fp = RCS_get_cached (name)) == NULL)
	return chmod (name, mode);
#ifdef HAVE_FCHMOD
    return fchmod (fileno (fp), mode);
#else
    RCS_cache_close ();
    return chmod (name, mode);
#endif
}

static int rename_readonly_file (oldname, newname)
    const char* oldname;
    const char* newname;
{
    struct stat sb;
    int result;

    if (stat (oldname, &sb) < 0 ||
	    chmod (oldname, sb.st_mode | S_IWRITE) < 0)
	return -1;

    result = rename (oldname, newname);

    return chmod (result < 0 ? oldname : newname, sb.st_mode) < 0 ?
	-1 : result;
}

#define mUnknown     -1
#define mWriteable    0
#define mReadOnly     1
#define mRETRY_MAX 1000

int os2_rename (oldname, newname)
    const char* oldname;
    const char* newname;
{
    int retval, retry;
    int fmode = mUnknown;
    struct stat sb;

    if (RCS_get_cached (oldname) != NULL)
	RCS_cache_close ();

    for ( retry = 0; retry < mRETRY_MAX; retry++ ) {
       if ( fmode == mUnknown && stat (oldname, &sb) == 0 ) {
          fmode = (sb.st_mode & S_IWRITE) ? mWriteable : mReadOnly;
       }
       /* Nice to know but we aren't really going to honor it .. :-) */
       retval = rename(oldname, newname);
       if ( retval != 0 && errno == EPERM ) {
          if (stat(oldname, &sb) == 0) {
             if ( chmod(oldname, sb.st_mode | S_IWRITE) == 0 ) {
                /** If rename() is successful oldname will no-longer exist
                 ** so there is no recovery if chmod() fails later.
                 */
                retval = rename(oldname, newname);
                if (retval == 0) {
                   chmod(newname, sb.st_mode);
                }
             }
          }
       }
       if (retval == 0) {
          break;
       }
       if ( retry == 0 ) {
          /* Only get 'useful' information when on pbsw01? */
          error( 0, errno, "What's has this file open?" );
       }
       if ( (retry % 250) == 0 ) {
          error( 0, errno, "Unable to rename %s to %s, will retry.", oldname, newname );
       }
       
       DosSleep( 1500 ); /* Sleep for 1.5 seconds */
    }
    if (retval != 0) {
       error( 0, errno, "# of retries %d expired ... sorry.", mRETRY_MAX );
    }
    return retval;
}

#define REXX_MAGIC	0x2a2f	// '*/'
#define PROC_MAGIC	0x2123	// '!#'

static int __set_errno(APIRET rc)
{
    switch (rc) {
	case NO_ERROR:
	    return 0;
	case ERROR_FILE_NOT_FOUND:
	case ERROR_PATH_NOT_FOUND:
	case ERROR_INVALID_DRIVE:
	    errno = ENOENT;
	    break;
	case ERROR_TOO_MANY_OPEN_FILES:
	    errno = EMFILE;
	    break;
	case ERROR_BAD_FORMAT:
	case ERROR_INVALID_EXE_SIGNATURE:
	case ERROR_EXE_MARKED_INVALID:
	case ERROR_BAD_EXE_FORMAT:
	    errno = ENOEXEC;
	    break;
	case ERROR_SHARING_VIOLATION:
	case ERROR_DRIVE_LOCKED:
	    errno = EPERM;
	    break;
	default:
	    errno = EIO;
	    break;
    }
    return -1;
}

static int _queryapptype(char const* path)
{
    APIRET rc;
    ULONG apptype;

    rc = DosQueryAppType((PCSZ)path, &apptype);
    if (rc != NO_ERROR)
	return __set_errno(rc);
    return (int)(apptype & FAPPTYP_WINDOWAPI);
}

static char const* _sloshify(char const* path)
{
    char* temp;
    char* p;

    if ((temp = strdup(path)) == NULL) {
	errno = ENOMEM;
	return NULL;
    }
    for (p = temp; (p = strchr(p, '/')) != NULL; *p++ = '\\')
        ;
    return temp;
}

static int shell_spawnvp(int mode, char const* path, char const* argv[])
{
    size_t i;
    char const** shell_argv;
    char const* shell;
    int result;

    if ((shell = getenv("EMXSHELL")) == NULL &&
	(shell = getenv("COMSPEC")) == NULL) {
	errno = ENOEXEC;
	return -1;
    }
    if ((path = _sloshify(path)) == NULL)
	return -1;
    for (i = 0; argv[i] != NULL; i++)
	;
    if ((shell_argv = calloc(i+4, sizeof(char const*))) == NULL) {
	free((void*)path);
	errno = ENOMEM;
	return -1;
    }
    shell_argv[0] = shell;
    shell_argv[1] = "/c";
    shell_argv[2] = path;
    for (i = 1; argv[i] != NULL; i++)
	shell_argv[i+2] = argv[i];
    shell_argv[i+2] = NULL;
    result = spawnvp(mode, shell_argv[0], (char**)shell_argv);
    free((void*)shell_argv);
    free((void*)path);
    if (result < 0 && errno == ENOENT)
	errno = ENOEXEC;
    return result;
}

static int proc_spawnvp(
    int mode,
    char const* path,
    char const* argv[],
    char* proc)
{
    size_t i;
    char temp[MAXPATHLEN];
    char full[MAXPATHLEN];
    char const** proc_argv;
    int proc_argc;
    int result;
    
    proc[strcspn(proc, "\r\n")] = '\0';
    proc_argc = 0;
    if ((proc_argv = (char const**)_splitargs(proc, &proc_argc)) == NULL)
	return -1;
    if (proc_argc == 0) {
	free((void*)proc_argv);
	errno = ENOEXEC;
	return -1;
    }
    _defext(strcpy(temp, proc_argv[0]), "exe");
    if (_path(full, temp) < 0 &&
	    (errno != ENOENT || _path(full, _getname(temp)) < 0)) {
	free((void*)proc_argv);
	errno = ENOEXEC;
	return -1;
    }
    for (i = 1; argv[i] != NULL; i++)
	;
    if ((proc_argv = realloc(proc_argv,
	    (proc_argc+i+2)*sizeof(char const*))) == NULL) {
	errno = ENOMEM;
	return -1;
    }
    proc_argv[0] = full;
    proc_argv[proc_argc++] = path;
    for (i = 1; argv[i] != NULL; i++)
	proc_argv[proc_argc++] = argv[i];
    proc_argv[proc_argc] = NULL;
    result = spawnv(mode, proc_argv[0], (char**)proc_argv);
    free((void*)proc_argv);
    if (result < 0 && errno == ENOENT)
	errno = ENOEXEC;
    return result;
}

static int rexx_spawnvp(int mode, char const* path, char const* argv[])
{
    char proc[MAXPATHLEN];

    if (_path(proc, "rxrun.exe") < 0) {
	if (errno != ENOENT)
	    return -1;
	return shell_spawnvp(mode, path, argv);
    }
    return proc_spawnvp(mode, path, argv, proc);
}

static int search(char* full, char const* path);

static int _search(
    char* full,
    char const* dir,
    char const* path)
{
    static char const* ext[] = {
	".exe",
	".cmd",
	".pl",
	".sh",
	".tcl",
	"."
    };
    size_t i;

    for (i = 0; i < sizeof(ext)/sizeof(ext[0]); i++) {
	char temp[MAXPATHLEN];
	int result;

	strcat(strcat(strcpy(temp, dir), path), ext[i]);
	if ((result = search(full, temp)) >= 0 || errno != ENOENT)
	    return result;
    }
    return -1;
}

static int search(char* full, char const* path)
{
    char temp[MAXPATHLEN];

    if (_getext(path) == NULL) {
	if (path[strcspn(path, ":\\/")] == '\0') {
	    int result;

	    if (_getcwd2(temp, sizeof(temp)) == NULL)
		return -1;
	    strcat(temp, "/");
	    if ((result = _search(full, temp, path)) >= 0 ||
		    (errno != ENOENT && errno != ENOEXEC))
		return result;
	}
	return _search(full, "", path);
    }
    if (_path(temp, path) < 0)
	return -1;
    return _abspath(full, temp, MAXPATHLEN);
}

int os2_spawnvp(int mode, char const* path, char const* argv[])
{
    char const* ext;
    char temp[MAXPATHLEN];
    char proc[BUFSIZ];
    int fd, saved_errno, result;
    char* p;

    if (mode != P_WAIT && mode != P_NOWAIT) {
	errno = EINVAL;
	return -1;
    }
    if (search(temp, path) < 0)
	return -1;
    if (stricmp(ext = _getext2(temp), ".exe") == 0) {
	int apptype, realmode, status;

	if ((apptype = _queryapptype(temp)) < 0)
	    return -1;
	switch (apptype) {
	    case FAPPTYP_WINDOWCOMPAT:
		realmode = mode;
		break;
	    case FAPPTYP_WINDOWAPI:
		realmode = P_SESSION;
		break;
	    default:
		errno = ENOEXEC;
		return -1;
	}
	if ((result = spawnv(realmode, temp, (char**)argv)) < 0)
	    return -1;
	if (realmode != P_SESSION || mode == P_NOWAIT)
	    return result;
	status = 0;
	if (waitpid(result, &status, 0) < 0)
	    return -1;
	return WIFEXITED(status) ? WEXITSTATUS(status) : 255;
    }
    if ((fd = open(temp, O_RDONLY|O_BINARY)) < 0)
	return -1;
    memset(proc, '\0', sizeof(proc));
    result = read(fd, proc, sizeof(proc)-1);
    saved_errno = errno;
    (void)close(fd);
    if (result <= 0) {
	errno = result < 0 ? saved_errno : ENOEXEC;
	return -1;
    }
    if (stricmp(ext, ".cmd") == 0) {
	if (*(unsigned short*)proc == PROC_MAGIC) {
	    errno = ENOEXEC;
	    return -1;
	}
	if (*(unsigned short*)proc == REXX_MAGIC)
	    return rexx_spawnvp(mode, temp, argv);
	for (p = proc; p+9 < proc+sizeof(proc) && isspace(*p); p++)
	    ;
	if (strnicmp(p, "extproc", 7) != 0 || !isspace(p[7]))
	    return shell_spawnvp(mode, temp, argv);
	p += 8;
    }
    else if (*(unsigned short*)proc != PROC_MAGIC) {
	errno = ENOEXEC;
	return -1;
    }
    else
	p = &proc[2];
    return proc_spawnvp(mode, temp, argv, p);
}

static int _makeargv(char const* s, char const*** argv)
{
    int inquote = 0;
    int argc = 1, maxargc = 0;
    char* q;
    char const* p;

    if ((q = strdup(s)) == NULL) {
        errno = ENOMEM;
        return -1;
    }
    *argv = NULL;
    for (p = s; *p != '\0'; ) {
        char const* arg;
        while (isspace(*p))
            ++p;

        /* Eat One Argv */
        for (arg = q; *p != '\0' && (inquote || !isspace(*p)); p++) 
        {
#if 1
            if (*p == '\"' || *p == '\'' || *p == '`') /* Begin quote */
            {
                inquote ^= *p;
                /* !first_quote&&!last_quote */
                if (inquote != *p && inquote) *q++ = *p; /* keep quote */
            }
            else if (inquote == '\"' && *p == '\\') 
            {
                /* Perl 'style' only handle \\ replacement if 
                   inside ", otherwise treat as a literal     */
                switch (*++p) 
                {
                  case 'a':  *q++ = '\a'; break;
                  case 'b':  *q++ = '\b'; break;
                  case 'f':  *q++ = '\f'; break;
                  case 'n':  *q++ = '\n'; break;
                  case 'r':  *q++ = '\r'; break;
                  case 't':  *q++ = '\t'; break;
                  case 'v':  *q++ = '\v'; break;
                  case '\\': *q++ = '\\'; break;
                  case '\'': *q++ = '\''; break;
                  case '\"': *q++ = '\"'; break;
                  default:   *q++ = '\\'; /* copy it forward, bogus escape */
                             *q++ = *p;   break;
                }
            }
            else
#endif /* 1 */
            {
                *q++ = *p;
            }
        }
        *q++ = '\0';
        if (argc+1 >= maxargc) {
            char const** newargv;
            maxargc += 10;
            newargv = realloc(*argv, maxargc*sizeof(char const*));
            if (newargv == NULL) {
                if (argc > 1)
                    free((void*)(*argv)[0]);
                errno = ENOMEM;
                return -1;
            }
            *argv = newargv;
        }
        (*argv)[argc++] = arg;
    }
    (*argv)[0] = (*argv)[argc] = NULL;
    return argc;
}


FILE* os2_popen(char const* command, char const* mode)
{
    int fd[2] = { -1, -1 };
    int saved_fd = -1, saved_errno, i = -1;
    char const** argv;
    char* args;
    FILE* fp = NULL;

    if (mode[0] != 'r' && mode[0] != 'w') {
	errno = EINVAL;
	return NULL;
    }
    if ((args = strdup(command)) == NULL)
	return NULL;

    if (_makeargv(args, &argv) < 0)
	return NULL;

    if (trace) 
    {
      int argc;

      fprintf( stdout, "(trace) os2_popen( '%s', '%s' ) -> _makeargv()\n", command, mode );
      for (argc=0; argv[argc]; argc++ )
        {
          fprintf( stdout, "\targv[%d] -> '%s'\n", argc, argv[argc] );
        }
    }

    if (pipe(fd) < 0)
	goto error;
    if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0)
	goto error;
    if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) < 0)
	goto error;
    i = mode[0] == 'r' ? STDOUT_FILENO : STDIN_FILENO;
    if ((saved_fd = dup(i)) < 0)
	goto error;
    if (dup2(fd[i], i) < 0)
	goto error;
    if (close(fd[i]) < 0)
	goto error;
    if ((fp = fdopen(fd[1-i], mode)) == NULL)
	goto error;
    if ((fp->_pid = os2_spawnvp(P_NOWAIT, argv[1], &argv[1])) < 0)
	goto error;
    (void)dup2(saved_fd, i);
    (void)close(saved_fd);
    free((void*)argv[1]);
    free((void*)argv);
    free(args);
    return fp;
error:
    saved_errno = errno;
    if (fd[0] >= 0)
	(void)close(fd[0]);
    if (fd[1] >= 0)
	(void)close(fd[1]);
    if (saved_fd >= 0) {
	(void)dup2(saved_fd, i);
	(void)close(saved_fd);
    }
    if (fp != NULL)
	(void)fclose(fp);
    free((void*)argv[1]);
    free((void*)argv);
    free(args);
    errno = saved_errno;
    return NULL;
}

