/*
** Splint - annotation-assisted static program checker
** Copyright (C) 1994-2003 University of Virginia,
**         Massachusetts Institute of Technology
**
** 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.
** 
** The GNU General Public License is available from http://www.gnu.org/ or
** the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
** MA 02111-1307, USA.
**
** For information on splint: info@splint.org
** To report a bug: splint-bug@splint.org
** For more information: http://www.splint.org
*/
/*
** osd.c
**
** Provide a system-independent interface to system-dependent
** file operations.
*/

/*
 * Modified by Herbert 04/19/97:
 * - added conditional 'OS2'
 * - added include of new header portab.h.
 * - changed occurance of '/' as path delimiter to a macro.
 * - added DOS / OS/2 specific stuff in osd_getPath.
 * Herbert 06/12/2000:
 * - added OS/2 specific includes before osd_getPid()
 * - handle files like in WIN32 for OS/2 in osd_fileExists()
 * Herbert 02/17/2002:
 * - added OS/2 support to absolute file names
 */

/*@-allmacros*/
/*@ignore@*/
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
/* Fix suggested by Lars Rasmussen */
# include <errno.h>

# if defined(WIN32)
# include <windows.h>
# include <process.h>
# include <direct.h>
# include <io.h>  /* _open() */
# define open(pathname, flags, mode) _open(pathname, flags, mode)
# define getpid() _getpid()
# define getcwd(buf, size) _getcwd(buf, size)
extern /*@external@*/ int _flushall (void) /*@modifies fileSystem@*/ ;
# else
# include <unistd.h>
# endif

# ifndef system
extern /*@external@*/ int system (const char *) /*@modifies fileSystem@*/ ;
# endif

# ifndef unlink
/* This should be defined by unistd.h */
/*@-redecl@*/
extern /*@external@*/ int unlink (const char *) /*@modifies fileSystem@*/ ;
/*@=redecl@*/
# endif


/*@end@*/
/*@=allmacros*/
# include "splintMacros.nf"
# include "basic.h"
# include "osd.h"


# if defined (WIN32)
/*@constant observer char *DEFAULT_TMPDIR; @*/
# define DEFAULT_TMPDIR "\\WINDOWS\\TEMP\\"
# elif defined(P_tmpdir)
/*@constant observer char *DEFAULT_TMPDIR; @*/
# define DEFAULT_TMPDIR P_tmpdir
# else
/*@constant observer char *DEFAULT_TMPDIR; @*/
# define DEFAULT_TMPDIR "/tmp/"
# endif

# if defined (OS2) || defined (WIN32)
/*@constant char ALTCONNECTCHAR@*/
# define ALTCONNECTCHAR '/'
/*@constant char DRIVECONNECTCHAR@*/
# define DRIVECONNECTCHAR ':'
# endif

/*
** MAXPATHLEN defines the longest permissable path length.
**
** POSIX defines PATHMAX in limits.h
*/

# if defined (PATH_MAX)
/*@constant size_t MAXPATHLEN; @*/
# define MAXPATHLEN PATH_MAX
# else
/*@constant size_t MAXPATHLEN; @*/
# define MAXPATHLEN 1024
# endif

#ifndef S_ISREG
/*@-macrounrecog@*/
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
/*@=macrounrecog@*/
#endif

#ifndef S_ISDIR
/*@-macrounrecog@*/
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
/*@=macrounrecog@*/
#endif

# ifndef S_IWUSR
/*@-macrounrecog@*/
# define S_IWUSR _S_IWRITE
/*@=macrounrecog@*/
# endif

# ifndef S_IRUSR
/*@-macrounrecog@*/
# define S_IRUSR _S_IREAD
/*@=macrounrecog@*/
# endif


# if defined (S_IRUSR) && defined (S_IWUSR) && \
     defined (S_IRGRP) && defined (S_IWGRP) && \
     defined (S_IROTH) && defined (S_IWOTH)
# define S_IRWALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
# else
# define S_IRWALL 0666
# endif

#ifndef O_RDONLY
#define O_RDONLY 0
#endif


bool
osd_fileExists (cstring filespec)
{
# if defined (WIN32) || defined (OS2)
  FILE *test = fileTable_openReadFile (context_fileTable (), filespec);
  
  if (test != NULL) 
    {
      (void) fileTable_closeFile (context_fileTable (),test);
      return TRUE;
    } 
  else
    { 
      return FALSE;
    }
# else 
  struct stat buf;
  /*@i3@*/ return (stat (cstring_toCharsSafe (filespec), &buf) == 0);
# endif
}

bool
osd_fileIsReadable (cstring f)
{
# if defined (WIN32) || defined (OS2)
  FILE *fl = fileTable_openReadFile (context_fileTable (), f);

  if (fl != NULL)
    {
      check (fileTable_closeFile (context_fileTable (), fl));
      return (TRUE);
    }
  else
    {
      return (FALSE);
    }
#else
  struct stat buf;
  /*@i3@*/ return (stat (cstring_toCharsSafe (f), &buf) == 0 && S_ISREG(buf.st_mode));
#endif
}

static bool
osd_fileIsExecutable (/*@unused@*/ char *filespec)
{
  /*@-compdestroy@*/ /* possible memory leaks here? */
  struct stat buf;
  if (stat (filespec, &buf) == 0)
    { 
      /* mask by file type */
      /*@-type@*/ /* confusion about __mode_t and mode_t types */
      if (!S_ISDIR(buf.st_mode))
      /*@=type@*/
	{
	  /* as long as it is an executable file */
# if defined (WIN32)
	  int com_or_exe_pos = strlen( filespec) - 4;
	  return stricmp( &filespec[com_or_exe_pos], ".exe") == 0
	    || stricmp( &filespec[com_or_exe_pos], ".com") == 0
	    || stricmp( &filespec[com_or_exe_pos], ".bat") == 0
	    || stricmp( &filespec[com_or_exe_pos], ".cmd") == 0;
# else
	  return (((buf.st_mode & S_IXUSR)
# if defined (S_IXGRP) && defined (S_IXOTH)
		   | (buf.st_mode & S_IXGRP)
                   | (buf.st_mode & S_IXOTH)
# endif
		   ) != 0);
# endif
	}
    }
  return (FALSE); 
  /*@=compdestroy@*/
}

cstring osd_pathListConcat (const char* p0, const char* p1)
{
  char *res;
  size_t l0, l1;

  llassert (p0 != NULL && p1 != NULL);

  l0 = strlen (p0);
  l1 = strlen (p1);

  res = dmalloc (l0 + 1 + l1 + 1);
  strcpy (res, p0);
  res[l0] = PATH_SEPARATOR;
  strcpy (res + l0 + 1, p1);

  return cstring_fromChars (res);
}

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      Find the next directory from a directory path.
**
**  FORMAL PARAMETERS:
**
**      char ** current_dir :
**	    Points to the current position in the path string.  The first time
**	    you call this routine, this should point to the first character of
**	    the path.  On return, this will be updated to point to the
**	    terminating \0 or : of the first directory found.  You can then pass
**	    it unchanged for subsequent calls; this routine will correctly skip
**	    over the :.
**
**	char ** dir :
**	    On exit, this will point to the first character of the directory
**	    that was found.  This will be a pointer directly into the client's
**	    path string.
**
**	unsigned int * len :
**	    On exit, this will contain the length of the directory that was
**	    found, not counting any terminating \0 or :.  If no directory was
**	    found, this will be 0.
**
**  RETURN VALUE:
**	TRUE if we found another directory.
**	FALSE otherwise.
**
**  DESIGN:
**
**      We return a pointer and length, rather than a string, because of a)
**	historical reasons; and b) this avoids having to allocate storage.
**
**
**
**--
*/

static bool
nextdir (d_char *current_dir, d_char *dir, size_t *len)
{
  char *tchar;

  if (**current_dir == '\0')
    {
      *len = 0;
      *dir = NULL;
      return FALSE;
    }

  *dir = (**current_dir == PATH_SEPARATOR ? *current_dir + 1 : *current_dir);
  
  /* Find next PATH_SEPARATOR or end of string */
  for (tchar = *dir; *tchar != '\0' && *tchar != PATH_SEPARATOR; tchar++)
    {
      ;
    }
  
  *current_dir = tchar;
  *len = size_fromInt (tchar - *dir);
  return TRUE;
}

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      This function attempts to locate a file in a search list.  On Ultrix,
**      it searches for the file on the path.
**
**  FORMAL PARAMETERS:
**
**      path:	    search path where to look for the file.
**	file:	    name of file to search for.
**	returnPath: if a file is found, this is where the concatenated
**		    directory and file name are returned.
**
**  RETURN VALUE:
**
**      OSD_FILEFOUND:	    the file was found on the search list.
**	OSD_FILENOTFOUND    the file was not found.
**	OSD_PATHTOOLONG	    the concatenated directory and file name are too
**			    long.
**
**  SIDE EFFECTS:
**
**      None
**
**  PRECONDITIONS:
**
**  	Requires that parameters, path and file, are valid C strings.
**
**
**--
*/

static filestatus
osd_getPathFilter (cstring path, cstring file, cstring *returnPath,
                   bool (*pathFilter) (cstring filespec))
{
  filestatus rVal = OSD_FILENOTFOUND;
  
  /*@access cstring@*/
  
  llassert (cstring_isDefined (file));
  llassert (returnPath != NULL);

  *returnPath = cstring_undefined;

  if (  osd_pathIsAbsolute (file)
     || path == NULL
     || *path == '\0'
     )
    {
      if (pathFilter (file))
	{
	  rVal = OSD_FILEFOUND;
   	  *returnPath = cstring_fromCharsNew (file);
	}
    }
  else
    {
      char *fullPath;
      char *dirPtr;
      size_t dirLen;
      char aPath[MAXPATHLEN];

      fullPath = path;
      while (nextdir (&fullPath, &dirPtr, &dirLen) 
	     && rVal == OSD_FILENOTFOUND)
	{
	  if ((dirLen + 1 + strlen (file) + 1) <= sizeof(aPath))
	    {
	      strncpy (aPath, dirPtr, dirLen);
              aPath[dirLen] = CONNECTCHAR;
	      strcpy (aPath + dirLen + 1, file);
	      if (pathFilter (aPath))
		{
		  rVal = OSD_FILEFOUND;
                  *returnPath = cstring_fromCharsNew (aPath);
		}
	    }
	  else
	    {
	      rVal = OSD_PATHTOOLONG;
	    }
	}	
    }

  return rVal;
  /*@noaccess cstring@*/
}

extern filestatus
osd_getPath (cstring path, cstring file, cstring *returnPath)
{
  return osd_getPathFilter (path, file, returnPath, osd_fileExists);
}

extern filestatus
osd_getExePath (cstring path, cstring file, cstring *returnPath)
{
  return osd_getPathFilter (path, file, returnPath, osd_fileIsExecutable);
}

static bool s_tempError = FALSE;

static void osd_setTempError (void)
  /*@modifies internalState@*/ 
{
  s_tempError = TRUE;
}

int osd_system (cstring cmd)
{
  int res;
# if defined (WIN32)
  (void) _flushall (); 
# endif

  res = system (cstring_toCharsSafe (cmd));
  return res;
}

int osd_unlink (cstring fname)
{
  int res;

  res = unlink (cstring_toCharsSafe (fname));

  if (res != 0)
    {
      if (!s_tempError)
	{
	  llcontbug (message ("Cannot remove temporary file: %s (%s)",
			      fname,
			      cstring_fromChars (strerror (errno))));
	}
    }
  
  return res;
}

int
osd_getPid (void)
{
  return (int) getpid ();
}
  
bool osd_isConnectChar (char c)
{
  if (c == CONNECTCHAR) 
    {
      return TRUE;
    }

# if defined (OS2) || defined (WIN32)
  if (c == ALTCONNECTCHAR)
    {
      return TRUE;
    }
# endif

  return FALSE;
}

/*
** Returns true if c2 starts with the same path as c1
**
** This is called by context_isSystemDir to determine if a
** directory is on the system path.
**
** In unix, this is just a string comparison.  For Win32 and OS2, we need a more
** complex comparison.
*/

static bool osd_equalCanonicalPrefix (cstring dirpath, cstring prefixpath)
{
  llassert (cstring_isDefined (prefixpath));

  if (cstring_isEmpty (dirpath)) 
    {
      return (cstring_isEmpty (prefixpath));
    }

# if defined (WIN32) || defined (OS2)

  /*@access cstring@*/

  {
    int len = size_toInt (strlen (prefixpath));
    int i = 0;
    int slen = 0;
    char *dirdrive, *prefixdrive;

    /*
    ** If one has a drive specification, but the other doesn't, skip it.
    */
    
    dirdrive = strchr (dirpath, DRIVECONNECTCHAR);
    prefixdrive = strchr (prefixpath, DRIVECONNECTCHAR);
    if (dirdrive == NULL && prefixdrive != NULL)
      {
        prefixpath = prefixdrive + 1;
      }
    else if (prefixdrive == NULL && dirdrive != NULL)
      {
        dirpath = dirdrive + 1;
      }

    for (i = 0, slen = 0; i < len; i++, slen++)
      {
	/* Allow any number of connect characters in any combination:
	 * c:/usr//src\/foo == c:\\usr/src\/foo 
	 * After this we'll be at the last of such a sequence */

	if (osd_isConnectChar (dirpath[slen]) && osd_isConnectChar (prefixpath[i]))
	  {
	    /* Skip one or more connect chars */

	    for (; osd_isConnectChar (dirpath[slen+1]); ++slen)
	      {
		; 
	      }
	    
	    for (; osd_isConnectChar (prefixpath[i+1]); ++i)
	      {
		;
	      }
	  }
	/* DOS-like systems use case-insensitive path specs! */
	else if (toupper (dirpath[slen]) != toupper (prefixpath[i]))
	  {
	    return FALSE;
	  }

      }
  }

  /*@noaccess cstring@*/ 

  return TRUE;
# else
  return (cstring_equalPrefix (dirpath, prefixpath));
# endif
}

bool
osd_pathPrefixInPathList (cstring path, cstring pathList)
{
  cstring tmpPathList, curPathList;
  cstring curPath;
  bool res, finished;

  llassert (cstring_isDefined (pathList));

  tmpPathList = cstring_copy (pathList);
  curPathList = tmpPathList;

  res = FALSE;
  finished = FALSE;
  do 
    {
      curPath = curPathList;
      curPathList = cstring_afterChar (curPath, PATH_SEPARATOR);
      if (cstring_isDefined (curPathList))
        {
          /*@access cstring@*/
          *curPathList = '\0';
          curPathList += 1;
          /*@noaccess cstring@*/
        } 
      else
        {
          finished = TRUE;
        }

      /* 2001-09-09:
      **   herbert: don't compare with an empty name! 
      **   should return false for empty directory path
      */
      if (cstring_isEmpty (curPath))
        {
          break;
        }

      if (osd_equalCanonicalPrefix (path, curPath))
	{
          res = TRUE;
          break;
	}
    } 
  while (!finished);

  cstring_free (tmpPathList);
  return res;
}


/*
** absolute paths
**
** This code is adapted from:
**
** http://src.openresources.com/debian/src/devel/HTML/S/altgcc_2.7.2.2.orig%20altgcc-2.7.2.2.orig%20protoize.c.html#1297
**
**
**  Protoize program - Original version by Ron Guilmette (rfg@segfault.us.com).
**   Copyright (C) 1989, 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
**
** This file is part of GNU CC.
**
** GNU CC 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.
**
** GNU CC 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 GNU CC; see the file COPYING.  If not, write to
** the Free Software Foundation, 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.  
*/

/* 
** Return the absolutized filename for the given relative
** filename.  Note that if that filename is already absolute, it may
** still be returned in a modified form because this routine also
** eliminates redundant slashes and single dots and eliminates double
** dots to get a shortest possible filename from the given input
** filename.  The absolutization of relative filenames is made by
** assuming that the given filename is to be taken as relative to
** the first argument (cwd) or to the current directory if cwd is
** NULL.  
*/

/* A pointer to the current directory filename (used by abspath).  */
static /*@only@*/ cstring osd_cwd = cstring_undefined;

static void osd_setWorkingDirectory (void)
{
# if !(defined (WIN32))
  char *buf = dmalloc (sizeof (*buf) * MAXPATHLEN);
  char *cwd = getcwd (buf, MAXPATHLEN);

  llassert (cstring_isUndefined (osd_cwd));

  if (cwd == NULL)
    {
      lldiagmsg (message ("Cannot get working directory: %s\n",
			  lldecodeerror (errno)));
      osd_cwd = cstring_makeLiteral ("<missing directory>");
    }
  else
    {
      osd_pathMakeNative (cwd);
      osd_cwd = cstring_fromCharsNew (cwd);
    }

  sfree (buf);
# else
  ; /* Don't know how to do this for non-POSIX platforms */
# endif
}

void osd_initMod (void)
{
  osd_setWorkingDirectory ();
}

void osd_destroyMod (void)
{
  cstring_free (osd_cwd);
  osd_cwd = cstring_undefined;
}

cstring osd_absolutePath (cstring filename)
{
# if !(defined (WIN32))
  char *abs_buffer;
  char *endp, *outp, *inp;

  /*@access cstring@*/
  llassert (cstring_isDefined (osd_cwd));
  llassert (cstring_isDefined (filename));

  abs_buffer = (char *) dmalloc (cstring_length (osd_cwd) + cstring_length (filename) + 2);
  endp = abs_buffer;
  
  /*
  ** Copy the  filename (possibly preceded by the current working
  ** directory name) into the absolutization buffer.  
  */
  
  {
    const char *src_p;

    if (!osd_pathIsAbsolute (filename))
      {
        src_p = osd_cwd;

        while ((*endp++ = *src_p++) != '\0') 
	  {
	    continue;
	  }

        *(endp-1) = CONNECTCHAR;                        /* overwrite null */
      }

    src_p = filename;

    while ((*endp++ = *src_p++) != '\0')
      {
	continue;
      }
  }
  
  /* Now make a copy of abs_buffer into abs_buffer, shortening the
     filename (by taking out slashes and dots) as we go.  */
  
  outp = inp = abs_buffer;
  *outp++ = *inp++;             /* copy first slash */
# if defined (apollo)
  if (inp[0] == '/')
    *outp++ = *inp++;           /* copy second slash */
# endif
  for (;;)
    {
      if (inp[0] == '\0')
	{
	  break;
	}
      else if (osd_isConnectChar (inp[0]) && osd_isConnectChar (outp[-1]))
	{
	  inp++;
	  continue;
	}
      else if (inp[0] == '.' && osd_isConnectChar (outp[-1]))
	{
	  if (inp[1] == '\0')
	    {
	      break;
	    }
	  else if (osd_isConnectChar (inp[1]))
	    {
	      inp += 2;
	      continue;
	    }
	  else if ((inp[1] == '.') 
		   && (inp[2] == '\0' || osd_isConnectChar (inp[2])))
	    {
	      inp += (osd_isConnectChar (inp[2])) ? 3 : 2;
	      outp -= 2;
	
	      while (outp >= abs_buffer && !osd_isConnectChar (*outp))
		{
		  outp--;
		}

	      if (outp < abs_buffer)
		{
		  /* Catch cases like /.. where we try to backup to a
                     point above the absolute root of the logical file
                     system.  */
		  
		  llfatalbug (message ("Invalid file name: %s", filename));
		}

	      *++outp = '\0';
	      continue;
	    }
	  else
	    {
	      ;
	    }
	}
      else
	{
	  ;
	}

      *outp++ = *inp++;
    }
  
  /* On exit, make sure that there is a trailing null, and make sure that
     the last character of the returned string is *not* a slash.  */
  
  *outp = '\0';
  if (osd_isConnectChar (outp[-1]))
    *--outp  = '\0';
  
  /*@noaccess cstring@*/
  return cstring_fromChars (abs_buffer);
# else
  DPRINTF (("Here: %s", filename));
  return cstring_copy (filename);
# endif
}

/* 
** Given a filename (and possibly a directory name from which the filename
** is relative) return a string which is the shortest possible
** equivalent for the corresponding full (absolutized) filename.  The
** shortest possible equivalent may be constructed by converting the
** absolutized filename to be a relative filename (i.e. relative to
** the actual current working directory).  However if a relative filename
** is longer, then the full absolute filename is returned.
**
** KNOWN BUG:   subpart of the original filename is actually a symbolic link.  
**
** this is really horrible code...surely someone has written a less buggy version of this!
*/

cstring osd_outputPath (cstring filename)
{
# if !(defined (WIN32))
  char *rel_buffer;
  char *rel_buf_p;
  cstring cwd_p = osd_cwd;
  char *path_p;
  int unmatched_slash_count = 0;
  size_t filename_len = cstring_length (filename);
  
  llassertretval (filename_len > 0, filename);

  /*@access cstring@*/
  path_p = filename;
  rel_buffer = (char *) dmalloc (filename_len);
  rel_buf_p = rel_buffer;
  *rel_buf_p = '\0';

  if (cwd_p == NULL) 
    {
      /* Need to prevent recursive assertion failures */
      return cstring_copy (filename);
    }

  llassert (cwd_p != NULL);
  llassert (path_p != NULL);

  while ((*cwd_p != '\0') && (*cwd_p == *path_p))
    {
      cwd_p++;
      path_p++;
    }
  
  if ((*cwd_p == '\0') && (*path_p == '\0' || osd_isConnectChar (*path_p)))  /* whole pwd matched */
    {
      if (*path_p == '\0')             /* input *is* the current path! */
	{
	  cstring_free (rel_buffer);
	  return cstring_makeLiteral (".");
	}
      else
	{
	  cstring_free (rel_buffer);
	  return cstring_fromCharsNew (path_p + 1);
	}
    }
  else
    {
#if 1
      /* evans 2002-02-05 This is horrible code, which I've removed.  I couldn't
      ** find any test cases that need it, so I hope I'm not breaking anything.
      */

      /* drl   2002-10/14 I had to put this code back.
      ** The case that needs it is when splint is given an absolute path name of
      ** a file outside of the current directory and the subdirectories below the
      ** current directory. e.g. cd /home/; splint /tmp/prog.c
      */

      if (*path_p != '\0')
        {
          --cwd_p;
          --path_p;

          while (cwd_p >= osd_cwd && !osd_isConnectChar (*cwd_p)) /* backup to last slash */
            {
              --cwd_p;
              --path_p;
            }
          cwd_p++;
          path_p++;
          unmatched_slash_count++;
        }

      /* Find out how many directory levels in cwd were *not* matched.  */
      while (*cwd_p != '\0')
	{
	  if (osd_isConnectChar (*cwd_p++))
	    unmatched_slash_count++;
	}
      
      /* Now we know how long the "short name" will be.
         Reject it if longer than the input.  */
      if (unmatched_slash_count * 3 + strlen (path_p) >= filename_len)
	{
	  cstring_free (rel_buffer);
	  /* fprintf (stderr, "Returning filename: %s [%p]\n", filename); */
	  return cstring_copy (filename);
	}
#endif

      /* For each of them, put a `../' at the beginning of the short name.  */
      while (unmatched_slash_count-- > 0)
        {
          /* Give up if the result gets to be longer
             than the absolute path name.  */
	  char * temp_rel_buf_p;

	  /*drl This comment is necessary because for some reason Splint
	    does not realize that the pasts where rel_buf_p is released
	    do not reach here*/
	  /*@-usereleased@*/
	  temp_rel_buf_p = rel_buf_p;
	  /*@-usereleased@*/
	  
          if (rel_buffer + filename_len <= temp_rel_buf_p + 3)
	    {
	      sfree (rel_buffer);
	      return cstring_copy (filename);
	    }

          *rel_buf_p++ = '.';
          *rel_buf_p++ = '.';
          *rel_buf_p++ = CONNECTCHAR;
        }
      
      /* Then tack on the unmatched part of the desired file's name.  */

      do
        {
          if (rel_buffer + filename_len <= rel_buf_p)
	    {
	      cstring_free (rel_buffer);
	      return cstring_copy (filename);
	    }
        } /*@-usereleased@*/
      while ((*rel_buf_p++ = *path_p++) != '\0') ;

      
      /*@=usereleased@*/ /* Splint limitation: shouldn't need these */
      --rel_buf_p;

      if (osd_isConnectChar (*(rel_buf_p-1)))
        *--rel_buf_p = '\0';

      /* fprintf (stderr, "Returning buffer: %s [%p]\n", rel_buffer, rel_buffer); */
      return rel_buffer;
    }
  /*@noaccess cstring@*/
# else
  return cstring_copy (filename);
# endif
}

cstring osd_getCurrentDirectory (void)
{
# if defined(WIN32) || defined(OS2)
  return cstring_makeLiteralTemp ("");
# else
  return cstring_makeLiteralTemp ("./");
# endif
}


int osd_openForReading (const char *pathname)
{
  return open(pathname, O_RDONLY, S_IRWALL);
}

/* Read LEN bytes at PTR from descriptor DESC, retrying if necessary.
   Return a negative value if an error occurs, otherwise return the
   actual number of bytes read, which must be LEN unless end-of-file
   was reached.  */

int osd_readSafe (int desc, char *ptr, int len)
{
  int left = len;

  while (left > 0)
    {
# if defined (WIN32)
      /*@-compdef@*/ /* ptr is an out parameter */
      int nchars = _read (desc, ptr, (unsigned) left);
      /*@=compdef@*/
# else
      int nchars = (int) read (desc, ptr, size_fromInt (left));
# endif

      if (nchars < 0)
	{
# if defined (EINTR)
	  if (errno == EINTR)
	    continue;
# endif
	  return nchars;
	}

      if (nchars == 0) {
	break;
      }

      ptr += nchars;
      left -= nchars;
    }

  return len - left;
}

int osd_close(int fid)
{
  return close(fid);
}


FILE *osd_createTmpFile (cstring fname, bool read)
{
  int fdesc;
  int flags;
  FILE *res;

  flags = (read ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC | O_EXCL;
  fdesc = open (cstring_toCharsSafe (fname), flags, S_IRUSR | S_IWUSR);
  if (fdesc == -1)
    {
      osd_setTempError ();
      return NULL;
    }
  res = fdopen (fdesc, read ? "w+" : "w");
  if (res == NULL) 
    {
      DPRINTF (("Error opening: %s", fname));
      osd_setTempError ();
      close(fdesc);
      return NULL;
    }
  DPRINTF (("Opening file: %s / %p", fname, res));
  return res;
}

int osd_fcloseall(void)
{
# if defined (WIN32)
  /*@-unrecog@*/
  return  _fcloseall ();
  /*@=unrecog@*/
# else
  return 0;
# endif
}

/*
** Get the file-mode and data size of the file open on FD
** and store them in *MODE_POINTER and *SIZE_POINTER.
*/

int
osd_file_mode_and_size (int fd, filemode *mode_pointer, size_t *size_pointer)
{
  struct stat sbuf;

  if (fstat (fd, &sbuf) < 0) {
    *mode_pointer = 0;
    *size_pointer = 0;
    /*@-compdestroy@*/ /* possibly spurious warnings here (or memory leak) */
    return (-1);
    /*@=compdestroy@*/
  }

  if (mode_pointer != NULL)
    {
      if (S_ISREG (sbuf.st_mode))
        {
          *mode_pointer = OSD_MODEREGULAR;
        }
      else if (S_ISDIR (sbuf.st_mode))
        {
          *mode_pointer = OSD_MODEDIRECTORY;
        }
      else
        {
          *mode_pointer = OSD_MODEOTHER;
        }
    }

  if (size_pointer != NULL)
    {
      *size_pointer = (size_t) sbuf.st_size;
    }

  /*@-compdestroy@*/ /* possibly spurious warnings here (or memory leak) */
  return 0;
  /*@=compdestroy@*/
}

bool osd_pathIsAbsolute (const char* pathname)
{
  llassert (pathname != NULL && *pathname != '\0');

  return (osd_isConnectChar (*pathname)
# if defined (WIN32) || defined (OS2)
          || (isalpha(*pathname) && *(pathname + 1) == DRIVECONNECTCHAR)
# endif
          );
}

void osd_pathMakePosix (char *pathname)
{
#if defined (WIN32) || defined (OS2)
  cstring_replaceAll (fname, CONNECTCHAR, ALTCONNECTCHAR);
#endif
}

void osd_pathMakeNative (char *pathname)
{
#if defined (WIN32) || defined (OS2)
  cstring_replaceAll (fname, ALTCONNECTCHAR, CONNECTCHAR);
#endif
}

char* osd_pathBase (char *path)
{
  char *base;

  base = strrchr (path, CONNECTCHAR);
# if defined (OS2) || defined (WIN32)
  {
    char *altbase = strrchr (path, ALTCONNECTCHAR);
    if (altbase != NULL && (base == NULL || (altbase > base)))
      {
        base = altbase;
      }
  }
# endif

  if (base == NULL)
    {
      base = path;
    }
  else
    {
      ++base; /* skip found char */
    }

  return base;
}


cstring osd_getTempDir(void)
{
# if defined (WIN32) || defined (OS2)
  const char *env;
  env = getenv ("TMP");
  if (env == NULL)
    {
      env = getenv ("TEMP");
    }
  if (env != NULL)
    {
      return cstring_makeLiteral (env);
    }
# endif

  return cstring_makeLiteral (DEFAULT_TMPDIR);
}

cstring osd_removePreDirs (cstring s)
{
  /*@access cstring@*/

  llassert (cstring_isDefined (s));

  while (*s == '.' && *(s + 1) == CONNECTCHAR) 
    {
      s += 2;
    }

# if defined (OS2)
  /* remove remainders from double path delimiters... */
  while (*s == CONNECTCHAR) 
    {
      ++s;
    }
# endif

  return s;
  /*@noaccess cstring@*/
}

bool osd_pathHasDir(const char* pathname)
{
  return strchr (pathname, CONNECTCHAR) != NULL
# if defined (OS2) || defined (WIN32)
    || strchr (pathname, ALTCONNECTCHAR) != NULL
# endif
    ;
}

