/* Various utilities - OS/2 versions
   Copyright (C) 1994, 1995, 1996 the Free Software Foundation.

   Written 1994, 1995, 1996 by:
   Juan Grigera, Miguel de Icaza, Janne Kukonlehto, Dugan Porter,
   Jakub Jelinek, Mauricio Plaza.

   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>

#define INCL_DOS
#define INCL_PM
#define INCL_DOSPROCESS
#define INCL_DOSFILEMGR
#define INCL_DOSDEVICES   /* Device values */
#define INCL_DOSDATETIME
#define INCL_DOSERRORS
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/stat.h>
#include <errno.h>
#include <io.h>
#include <fcntl.h>
#include <signal.h>		/* my_system */
#include <limits.h>		/* INT_MAX */
#include <sys/time.h>		/* select: timeout */
#include <sys/param.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <process.h>
#include "../src/fs.h"
#include "../src/util.h"
#include "../src/dialog.h"

#ifdef get_default_editor
#undef get_default_editor
#endif

char *get_default_shell();


#ifndef ENOTEMPTY
#define ENOTEMPTY ERROR_DIR_NOT_EMPTY
#endif

char *
get_owner (int uid)
{
    return "none";
}

char *
get_group (int gid)
{
    return "none";
}

/* Pipes are guaranteed to be able to hold at least 4096 bytes */
/* More than that would be unportable */
#define MAX_PIPE_SIZE 4096

static int error_pipe[2];	/* File descriptors of error pipe */
static int old_error;		/* File descriptor of old standard error */

/* Creates a pipe to hold standard error for a later analysis. */
/* The pipe can hold 4096 bytes. Make sure no more is written */
/* or a deadlock might occur. */
void 
open_error_pipe (void)
{
   return;
}

void 
close_error_pipe (int error, char *text)
{
   return;
}

void 
check_error_pipe (void)
{
    char error[MAX_PIPE_SIZE];
    int len = 0;
    if (old_error >= 0){
	while (len < MAX_PIPE_SIZE)
	{
	    int rvalue;

	    rvalue = read (error_pipe[0], error + len, 1);
	    len ++;
	    if (rvalue <= 0)
		break;
	}
	error[len] = 0;
	close (error_pipe[0]);
    }
    if (len > 0)
        message (0, " Warning ", error);
}


static int 
StartWindowsProg (char *name, SHORT type)
{
#if 0 /* FIXME: PM DDL's should be loaded (or not loaded) at run time */
   PROGDETAILS  pDetails;

   memset(&pDetails, 0, sizeof(PROGDETAILS)) ;
   pDetails.Length = sizeof(pDetails);
   pDetails.pszExecutable = name;      /* program name */
   pDetails.pszStartupDir = NULL;   /* default directory for new app. */
   pDetails.pszParameters = NULL;   /* command line */
   pDetails.progt.fbVisible = SHE_VISIBLE ;
   pDetails.pszEnvironment = NULL;

   switch (type) {
   case 0:
      /* Win Standard */
      pDetails.progt.progc = PROG_31_ENHSEAMLESSCOMMON ;
      break;
   case 1:
      /* Win 3.1 Protect */
      pDetails.progt.progc = PROG_31_ENHSEAMLESSCOMMON ;
      break;
   case 2:
      /* Win 3.1 Enh. Protect */
      pDetails.progt.progc = PROG_31_ENHSEAMLESSCOMMON ;
      break;
   default:
      pDetails.progt.progc = PROG_31_ENHSEAMLESSCOMMON ;
     break;
   }
   WinStartApp(NULLHANDLE, 
               &pDetails, 
               NULL, 
               NULL, 
               SAF_INSTALLEDCMDLINE|SAF_STARTCHILDAPP) ;
#endif
   return 0;
}


static int 
os2_system (int as_shell_command, const char *shell, const char *command, char *parm);

/* 
  as_shell_command = 1: If a program is started during input line, CTRL-O
                        or RETURN 
                   = 0: F3, F4
*/
int 
my_system (int as_shell_command, const char *shell, const char *command)
{
   char *sh;            /* This is the shell -- always! */
   char *cmd;           /* This is the command (only the command) */
   char *parm;          /* This is the parameter (can be more than one) */
   register int length, i;
   char temp[4096];     /* That's enough! */
   char *t, *t1;

   sh = get_default_shell();
   if (!strcmp("/bin/sh", shell)) {	/* Translate "/bin/sh" to "sh" */
	return spawnlp(P_WAIT, "sh", shell, "-c", command, NULL);
   } else if (( (t = strrchr(shell,'/'))	/* Specialcase Bourne */
		|| (t = strrchr(shell,'\\')))
		&& (t1 = strchr(t, '.'))
		&& (
		    ((t1 - t == 4) && strncmp("bash", t, 4))
		    || ((t1 - t == 3) && strncmp("ksh", t, 3))
		    || ((t1 - t == 2) && strncmp("sh", t, 2))
		   )) {
	return spawnlp(P_WAIT, shell, shell, "-c", command, NULL);
   } else if (strcmp(sh, shell)) {
      /* 
         Not equal  -- That means: shell is the program and command is the
         parameter 
      */
      cmd  = (char *) shell;
      parm = (char *) command;
   } else {
      /* look into the command and take out the program */
      if (command) {
         strcpy(temp, command);
         length = strlen(command);
         for (i=length-1; i>=0; i--) {
            if (command[i] == ' ') {
               temp[i] = (char) 0;
               length--;
            } else 
                break;
         }
         if (i==-1) {
            /* only blanks */
            return -1;
         }
         if (parm = strchr(temp, (char) ' ')) {
            *parm = (char) 0;
            parm++;
         }
         cmd  = (char *) temp;
      } else {
         /* command is NULL */
         cmd = parm = NULL;
      }
   }
   return os2_system (as_shell_command, sh, cmd, parm);
}

static int 
ux_startp (const char *shell, const char *command, const char *parm) 
{
    if (parm) {
         spawnlp (P_WAIT, 
              (char *) shell, 
              (char *) shell, 
              "/c", 
              (char *) command, 
              (char *) parm,
              (char *) 0);
    } else {
         spawnlp (P_WAIT, 
              (char *) shell, 
              (char *) shell, 
              "/c", 
              (char *) command, 
              (char *) 0);
    }
    return 0;
}


static int 
os2_system (int as_shell_command, const char *shell, const char *command, char *parm)
{
   register int i, j;
   ULONG        AppType = 0;                    /* Application type flags (returned) */
   APIRET       rc = NO_ERROR;                  /* Return Code */
   char         pathValue[5] = "PATH";          /* For DosSearchPath */
   UCHAR        searchResult[MC_MAXPATHLEN * 2 + 1];     /* For DosSearchPath */
   
   char         *cmdString;
   char         *postFix[3];
   char         *line;
   /* ------------------------------------------------------- */
   STARTDATA    StartData;
   CHAR         ObjBuf[100];
   ULONG        SessionID;
   PID          pid;

   if (command == NULL) {
      /* .ado: just start a shell, we don't need the parameter */
      spawnl (P_WAIT, 
              (char *) shell, 
              (char *) shell, 
              (char *) command, (char *) 0);  
      return 0;
   }

   memset(&StartData, 0, sizeof(StartData)) ;
   StartData.Length             = sizeof(StartData);    
   StartData.Related            = SSF_RELATED_CHILD;
   StartData.FgBg               = SSF_FGBG_BACK;
   StartData.TraceOpt           = SSF_TRACEOPT_NONE;   
   StartData.PgmTitle           = NULL;                
   StartData.TermQ              = NULL;                 
   StartData.InheritOpt         = SSF_INHERTOPT_PARENT;
   StartData.IconFile           = 0;              
   StartData.PgmHandle          = 0;             
   StartData.PgmControl         = SSF_CONTROL_VISIBLE ; 
   StartData.ObjectBuffer       = ObjBuf;          
   StartData.ObjectBuffLen      = 100;            
   StartData.PgmInputs          = parm;

   postFix[0] = ".exe";
   postFix[1] = ".cmd";
   postFix[2] = ".bat";

   i = strlen(command);
   if (command[i-1] == ' ') {
      /* The user has used ALT-RETURN */
      i--;
   }
   cmdString = (char *) malloc(i+1);
   for (j=0; j<i; j++) {
      cmdString[j] = command[j];
   }
   cmdString[j] = (char) 0;

   if ((i < 5) || ((i > 4) && (cmdString[i-4]) != '.')) {
      /* without Extension */
      line = (char *) malloc(i+5);
      rc = 1;
      for (i=0; (i<3 && rc); i++) {
         /* Search for the file */
         strcpy(line, cmdString);
         strcat(line, postFix[i]);
         rc = DosSearchPath((SEARCH_IGNORENETERRS | SEARCH_ENVIRONMENT | SEARCH_CUR_DIRECTORY),
                            (PSZ) pathValue,
                            line,
                            searchResult,
                            sizeof(searchResult));
      }
      free (line);
   } else {         
      /* Just search */
      rc = DosSearchPath((SEARCH_IGNORENETERRS | SEARCH_ENVIRONMENT | SEARCH_CUR_DIRECTORY),
                         (PSZ) pathValue,
                         cmdString,
                         searchResult,
                         sizeof(searchResult));
   }
   free(cmdString);
   if (rc != 0) {
      /* Internal command or the program was written with absolut path */
      return ux_startp(shell, command, parm);
   }

   /* Application to be started */
   StartData.PgmName            = searchResult;
   StartData.Environment        = NULL;
   rc = DosQueryAppType(searchResult, &AppType);
   if (rc == NO_ERROR) {
      StartData.SessionType = PROG_WINDOWABLEVIO;
      if ((AppType & 0x00000007) == FAPPTYP_WINDOWAPI) {
         /* Window API */
         StartData.SessionType = PROG_PM;
         return DosStartSession(&StartData, &SessionID, &pid);
      }
      if ((AppType & 0x00000007) == FAPPTYP_WINDOWCOMPAT) {
         /* Window compat */
         return ux_startp(shell, command, parm);
      }
      if (AppType & 0x0000ffff & FAPPTYP_DOS) {
         /* PC/DOS Format */
        StartData.SessionType = PROG_WINDOWEDVDM;
        return DosStartSession(&StartData, &SessionID, &pid);
      }
      if (AppType & 0x0000ffff & FAPPTYP_WINDOWSREAL) {
         /* Windows real mode app */
        return StartWindowsProg(searchResult, 0);
      }
      if (AppType & 0x0000ffff & FAPPTYP_WINDOWSPROT) {
         /* Windows Protect mode app*/
        return StartWindowsProg(searchResult, 1);
      }
      if (AppType & 0x0000ffff & FAPPTYP_WINDOWSPROT31) {
         /* Windows 3.1 Protect mode app*/
        return StartWindowsProg(searchResult, 2);
      }
      rc = DosStartSession(&StartData, &SessionID, &pid) ;
   } else {
      /* It's not a known exe type or it's a CMD/BAT file */
      i = strlen(searchResult);
      if ((toupper(searchResult[--i]) == 'T') && 
          (toupper(searchResult[--i]) == 'A') &&  
          (toupper(searchResult[--i]) == 'B') &&  
          (searchResult[--i] == '.')   ) {
        StartData.SessionType = PROG_WINDOWEDVDM;
        rc = DosStartSession(&StartData, &SessionID, &pid) ;
      } else {
         rc = ux_startp (shell, command, parm);
      }
   }
   return rc;
}

char *tilde_expand (char *directory)
{
    return strdup (directory);
}


/* Canonicalize path, and return a new path. Do everything in situ.
   The new path differs from path in:
	Multiple BACKSLASHs are collapsed to a single BACKSLASH.
	Leading `./'s and trailing `/.'s are removed.
	Trailing BACKSLASHs are removed.
	Non-leading `../'s and trailing `..'s are handled by removing
	portions of the path. */
char *
canonicalize_pathname (char *path)
{
    int i, start;
    char stub_char;

    stub_char = (*path == PATH_SEP) ? PATH_SEP : '.';

    /* Walk along path looking for things to compact. */
    i = 0;
    for (;;) {
        if (!path[i])
	    break;

      	while (path[i] && path[i] != PATH_SEP)
	    i++;

      	start = i++;

      	/* If we didn't find any slashes, then there is nothing left to do. */
      	if (!path[start])
	    break;

        /* Handle multiple BACKSLASHs in a row. */
        while (path[i] == PATH_SEP)
	    i++;

        if ((start + 1) != i) {
	    strcpy (path + start + 1, path + i);
	    i = start + 1;
	}

        /* Handle backquoted BACKSLASH. */
/*         if (start > 0 && path[start - 1] == '\\')
	    continue; */

        /* Check for trailing BACKSLASH. */
        if (start && !path[i]) {
	zero_last:
	    path[--i] = '\0';
	    break;
	}

        /* Check for `../', `./' or trailing `.' by itself. */
        if (path[i] == '.') {
	    /* Handle trailing `.' by itself. */
	    if (!path[i + 1])
	        goto zero_last;

	    /* Handle `./'. */
	    if (path[i + 1] == PATH_SEP) {
	        strcpy (path + i, path + i + 1);
	        i = start;
	        continue;
	    }

	    /* Handle `../' or trailing `..' by itself. 
	       Remove the previous ?/ part with the exception of
	       ../, which we should leave intact. */
	    if (path[i + 1] == '.' && (path[i + 2] == PATH_SEP || !path[i + 2])) {
	        while (--start > -1 && path[start] != PATH_SEP);
	        if (!strncmp (path + start + 1, "..\\", 3))
	            continue;
	        strcpy (path + start + 1, path + i + 2);
	        i = start;
	        continue;
	    }
	}
    }

    if (!*path) {
        *path = stub_char;
        path[1] = '\0';
    }
    return path;
}


void 
my_statfs (struct my_statfs *myfs_stats, char *path)
{
    PFSALLOCATE pBuf;
    PFSINFO     pFsInfo;
    ULONG       lghBuf;

    ULONG       diskNum = 0;
    ULONG       logical = 0;

    UCHAR       szDeviceName[3] = "A:";
    PBYTE       pszFSDName      = NULL;  /* pointer to FS name            */
    APIRET      rc              = NO_ERROR; /* Return code                */
    BYTE        fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
    ULONG       cbBuffer   = sizeof(fsqBuffer);        /* Buffer length) */
    PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2) fsqBuffer;

    int i, len = 0;

    /* ------------------------------------------------------------------ */

    lghBuf = sizeof(FSALLOCATE);
    pBuf = (PFSALLOCATE) malloc(lghBuf);

    /* Get the free number of Bytes */
    rc = DosQueryFSInfo(0L, FSIL_ALLOC, (PVOID) pBuf, lghBuf);
    /* KBytes available */
    myfs_stats->avail = pBuf->cSectorUnit * pBuf->cUnitAvail * pBuf->cbSector / 1024;
    /* KBytes total */
    myfs_stats->total = pBuf->cSectorUnit * pBuf->cUnit * pBuf->cbSector / 1024; 
    myfs_stats->nfree = pBuf->cUnitAvail;
    myfs_stats->nodes = pBuf->cbSector;

    lghBuf  = sizeof(FSINFO);
    pFsInfo = (PFSINFO) malloc(lghBuf);
    rc      = DosQueryFSInfo(0L, 
                             FSIL_VOLSER, 
                             (PVOID) pFsInfo, 
                             lghBuf);
    /* Get name */
    myfs_stats->device = strdup(pFsInfo->vol.szVolLabel);    /* Label of the Disk */

    /* Get the current disk for DosQueryFSAttach */
    rc = DosQueryCurrentDisk(&diskNum, &logical);

    szDeviceName[0] = (UCHAR) (diskNum + (ULONG) 'A' - 1);
    /* Now get the type of the disk */
    rc = DosQueryFSAttach(szDeviceName, 
                          0L, 
                          FSAIL_QUERYNAME, 
                          pfsqBuffer, 
                          &cbBuffer);

    pszFSDName = pfsqBuffer->szName + pfsqBuffer->cbName + 1;
    myfs_stats->mpoint = strdup(pszFSDName);    /* FAT, HPFS ... */

    myfs_stats->type = pBuf->idFileSystem;
    /* What is about 3 ?*/
    if (myfs_stats->type == 0) {
       myfs_stats->typename = (char *) malloc(11);
       strcpy(myfs_stats->typename, "Local Disk");
    } else {
       myfs_stats->typename = (char *) malloc(13);
       strcpy(myfs_stats->typename, "Other Device");
    }

    free(pBuf);
    free(pFsInfo);
}

#ifndef __EMX__
int 
gettimeofday (struct timeval* tvp, void *p)
{
   DATETIME     pdt = {0};
   if (p != NULL)		/* what is "p"? */
	return 0;	
	
    /* Since MC only calls this func from get_random_hint we return 
     * some value, not exactly the "correct" one
     */
    DosGetDateTime(&pdt);
    tvp->tv_usec = (pdt.hours * 60 + pdt.minutes) * 60 + pdt.seconds;
    /* Number of milliseconds since Windows started */
    tvp->tv_sec = tvp->tv_usec * 1000 + pdt.hundredths * 10;
    return 0;
}
#endif

/* FAKE functions */

int 
look_for_exe(const char* pathname)
{
   int j;
   char *p;
   int lgh = strlen(pathname);

   if (lgh < 4) {
      return 0;
   } else {
      p = (char *) pathname;
      for (j=0; j<lgh-4; j++) {
         p++;
      }
      if (!stricmp(p, ".exe") || 
          !stricmp(p, ".bat") || 
          !stricmp(p, ".com") || 
          !stricmp(p, ".cmd")) {
         return 1;
      }
   }
   return 0;
}

int 
lstat (const char* pathname, struct stat *buffer)
{
   int rc = stat (pathname, buffer);
#ifdef __BORLANDC__
   if (rc == 0) {
     if (!(buffer->st_mode & S_IFDIR)) {
        if (!look_for_exe(pathname)) {
           buffer->st_mode &= !S_IXUSR & !S_IXGRP & !S_IXOTH;
	}
     }
   }
#endif
   return rc;
}

int 
getuid ()	      
{
    return 0;
}

int 
getgid ()	      
{
    return 0;
}

int 
readlink (char* path, char* buf, int size)
{
    return -1;
}

int 
symlink (char *n1, char *n2)
{
    return -1;
}

int 
link (char *p1, char *p2)
{
    return -1;
}

int 
chown (char *path, int owner, int group)
{
    return -1;
}

int 
mknod (char *path, int mode, int dev)
{
    return -1;
}

void 
init_uid_gid_cache (void)
{
    return;
}

int 
mc_doublepopen (int inhandle, int inlen, pid_t *the_pid, char *command, ...)
{
	return -1;
}

int 
mc_doublepclose (int pipe, pid_t pid)
{
	return 0;
}

#ifndef HAVE_VFS
/*hacks to get it compile, remove these after vfs works */
char *
vfs_get_current_dir (void)
{
	return NULL;
}
#endif

int 
vfs_current_is_extfs (void)
{
	return 0;
}

#ifndef HAVE_VFS
int 
vfs_file_is_ftp (char *filename)
{
	return 0;
}

int
mc_utime (char *path, void *times)
{
	return 0;
}


void
extfs_run (char *file)
{
   return;
}
#endif

void *
getgrent(void) {   return NULL; }

void
setgrent(void) {}

void
endgrent(void) {}

int
setreuid(uid_t ruid, uid_t euid) {    return -1; }

pid_t
setsid(void) { return (pid_t)-1; }

int
mkfifo(const char *path, mode_t mode) { return -1; }

int 
socketpair(int i, int i1, int i2, int *i3)
{
   return -1;
}


#ifndef HAVE_VFS
int
mc_chdir(char *pathname)
{
   APIRET       ret;
   register int lgh = strlen(pathname);

   /* Set the current drive */
   if (lgh == 0) {
      return -1;
   } else {
      /* First set the default drive */
      if (lgh > 1) {
         if (pathname[1] == ':') {
             ret = DosSetDefaultDisk(toupper(pathname[0]) - 'A' + 1); 
         }
      }
      /* After that, set the current dir! */
      ret = DosSetCurrentDir(pathname);
   }
   return ret;
}
 
int
mc_chmod(char *pathName, int unxmode)
{
   /* OS/2 does not need S_REG */
   int os2Mode = unxmode & 0x0FFF;
   return chmod(pathName, os2Mode);
}
#endif

static int
conv_os2_unx_rc(int os2rc)
{
   int errCode;
   switch (os2rc) {
      case ERROR_FILE_NOT_FOUND:
      case ERROR_PATH_NOT_FOUND:
      case ERROR_FILENAME_EXCED_RANGE:
         errCode = ENOENT;
         break;
      case ERROR_NOT_DOS_DISK:
      case ERROR_SHARING_VIOLATION:
      case ERROR_SHARING_BUFFER_EXCEEDED:
      case ERROR_ACCESS_DENIED:
         errCode = EACCES;
         break;
      case ERROR_INVALID_PARAMETER:
         errCode = EINVAL;
         break;
      default: 
         errCode = EINVAL;
        break;
   }
   return errCode;
}

#ifndef HAVE_VFS
int
mc_open (char *file, int flags, int pmode)
{
    return open(file, (flags | O_BINARY), pmode);
}

int
mc_unlink(char *pathName)
{
   /* Use OS/2 API to delete a file, if the file is set as read-only, 
      the file will be deleted without asking the user! */
   APIRET       rc;
   rc = DosDelete(pathName);
   if (!rc) {
      return 0;
   }
   if (rc == ERROR_ACCESS_DENIED) {
      chmod(pathName, (S_IREAD|S_IWRITE));
      rc = DosDelete(pathName);
      if (rc) {
         errno = conv_os2_unx_rc(rc) ;
         return -1;
      } else {
         return 0;
      }
   } else {
      errno = conv_os2_unx_rc(rc) ;
      return -1;
   }
}
#endif

char *
get_default_editor (void)
{
	char *tmp;
	APIRET  rc;
	char    pathValue[5] = "PATH";
	UCHAR   searchResult[MC_MAXPATHLEN + 1];
	
        /* EPM is not always be installed */
	rc = DosSearchPath((SEARCH_IGNORENETERRS | SEARCH_ENVIRONMENT | SEARCH_CUR_DIRECTORY),
			   (PSZ) pathValue,
			   "EPM.EXE",
			   searchResult,
			   sizeof(searchResult));
	if (rc != 0) {
                /* The system editor is always there */
		return strdup("e.exe");
	} else {
                /* Let it be searched from my_system */
		return strdup("epm.exe");
	}
}

/* get_default_shell
   Get the default shell for the current hardware platform
   TODO: Get the value of %OS2_SHELL% or %SHELL%: which one?
*/
char *
get_default_shell()
{
    return getenv ("COMSPEC");
}

int
errno_dir_not_empty (int err)
{
    if (err == ENOTEMPTY)
	return 1;
    return 0;
}

/* The MC library directory is by default the directory where mc.exe
   is situated. It is recommended to specify this directory via MCHOME
   environment variable, otherwise you will be unable to rename mc.exe */
char *
get_mc_lib_dir ()
{
    HMODULE mc_hm;
    int rc;
    char *cur = NULL;
    char *mchome = getenv("MCHOME");

    if (mchome && *mchome)
	return mchome;
    mchome = malloc(MC_MAXPATHLEN);
    rc = DosQueryModuleHandle ("MC.EXE", &mc_hm);
    if (!rc)
	rc = DosQueryModuleName (mc_hm, MC_MAXPATHLEN, mchome);
    if (!rc)
    {
	for (cur = mchome + strlen(mchome); \
	    (cur > mchome) && (*cur != PATH_SEP); cur--);
	*cur = 0;
	cur = strdup(mchome);
	free(mchome);
    }
    if (!cur || !*cur) {
	free(cur);
        return "C:\\MC";
    }
    return cur;
}

int get_user_rights (struct stat *buf)
{
    return 2;
}
void init_groups (void)
{
}
void delete_groups (void)
{
}
