filesubr.c   [plain text]


/* filesubr.c --- subroutines for dealing with files under OS/2
   Jim Blandy <jimb@cyclic.com> and Karl Fogel <kfogel@cyclic.com>

   This file is part of GNU CVS.

   GNU CVS 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.  */

/* These functions were moved out of subr.c because they need different
   definitions under operating systems (like, say, Windows NT) with different
   file system semantics.  */

#include <io.h>

#include "os2inc.h"
#include "cvs.h"

static int deep_remove_dir PROTO((const char *path));

/*
 * Copies "from" to "to".
 */
void
copy_file (from, to)
    const char *from;
    const char *to;
{
    struct stat sb;
    struct utimbuf t;
    int fdin, fdout;

    if (trace)
#ifdef SERVER_SUPPORT
	(void) fprintf (stderr, "%c-> copy(%s,%s)\n",
			(server_active) ? 'S' : ' ', from, to);
#else
	(void) fprintf (stderr, "-> copy(%s,%s)\n", from, to);
#endif
    if (noexec)
	return;

    if ((fdin = open (from, O_RDONLY | O_BINARY)) < 0)
	error (1, errno, "cannot open %s for copying", from);
    if (fstat (fdin, &sb) < 0)
	error (1, errno, "cannot fstat %s", from);
    if ((fdout = open (to, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY,
					   (int) sb.st_mode & 07777)) < 0)
	error (1, errno, "cannot create %s for copying", to);
    if (sb.st_size > 0)
    {
	char buf[BUFSIZ];
	int n;

	for (;;) 
	{
	    n = read (fdin, buf, sizeof(buf));
	    if (n == -1)
	    {
#ifdef EINTR
		if (errno == EINTR)
		    continue;
#endif
		error (1, errno, "cannot read file %s for copying", from);
	    }
            else if (n == 0) 
		break;
  
	    if (write(fdout, buf, n) != n) {
		error (1, errno, "cannot write file %s for copying", to);
	    }
	}

#ifdef HAVE_FSYNC
	if (fsync (fdout)) 
	    error (1, errno, "cannot fsync file %s after copying", to);
#endif
    }

    if (close (fdin) < 0) 
	error (0, errno, "cannot close %s", from);
    if (close (fdout) < 0)
	error (1, errno, "cannot close %s", to);

    /* now, set the times for the copied file to match those of the original */
    memset ((char *) &t, 0, sizeof (t));
    t.actime = sb.st_atime;
    t.modtime = sb.st_mtime;
    (void) utime ((char *)to, &t);
}

/* FIXME-krp: these functions would benefit from caching the char * &
   stat buf.  */

/*
 * Returns non-zero if the argument file is a directory, or is a symbolic
 * link which points to a directory.
 */
int
isdir (file)
    const char *file;
{
    struct stat sb;

    if (stat (file, &sb) < 0)
	return (0);
    return (S_ISDIR (sb.st_mode));
}

/*
 * Returns non-zero if the argument file is a symbolic link.
 */
int
islink (file)
    const char *file;
{
#ifdef S_ISLNK
    struct stat sb;

    if (lstat (file, &sb) < 0)
	return (0);
    return (S_ISLNK (sb.st_mode));
#else
    return (0);
#endif
}

/*
 * Returns non-zero if the argument file exists.
 */
int
isfile (file)
    const char *file;
{
    struct stat sb;

    if (stat (file, &sb) < 0)
	return (0);
    return (1);
}

/*
 * Returns non-zero if the argument file is readable.
 * XXX - must be careful if "cvs" is ever made setuid!
 */
int
isreadable (file)
    const char *file;
{
    return (access (file, R_OK) != -1);
}

/*
 * Returns non-zero if the argument file is writable
 * XXX - muct be careful if "cvs" is ever made setuid!
 */
int
iswritable (file)
    const char *file;
{
    return (access (file, W_OK) != -1);
}

/*
 * Returns non-zero if the argument file is accessable according to
 * mode.  If compiled with SETXID_SUPPORT also works if cvs has setxid
 * bits set.
 */
int
isaccessible (file, mode)
    const char *file;
    const int mode;
{
    return access(file, mode) == 0;
}


/*
 * Open a file and die if it fails
 */
FILE *
open_file (name, mode)
    const char *name;
    const char *mode;
{
    FILE *fp;

    if ((fp = fopen (name, mode)) == NULL)
	error (1, errno, "cannot open %s", name);
    return (fp);
}

/*
 * Make a directory and die if it fails
 */
void
make_directory (name)
    const char *name;
{
    struct stat buf;

    if (stat (name, &buf) == 0 && (!S_ISDIR (buf.st_mode)))
	    error (0, 0, "%s already exists but is not a directory", name);
    if (!noexec && mkdir ((char *)name) < 0)
	error (1, errno, "cannot make directory %s", name);
}

/*
 * Make a path to the argument directory, printing a message if something
 * goes wrong.
 */
void
make_directories (name)
    const char *name;
{
    char *cp;

    if (noexec)
	return;

    if (mkdir ((char *)name) == 0 || errno == EACCES)
	return;
    if (! existence_error (errno))
    {
	error (0, errno, "cannot make path to %s", name);
	return;
    }
    if ((cp = strrchr (name, '/')) == NULL)
	return;
    *cp = '\0';
    make_directories (name);
    *cp++ = '/';
    if (*cp == '\0')
	return;
    (void) mkdir ((char *)name);
}

/* Create directory NAME if it does not already exist; fatal error for
   other errors.  Returns 0 if directory was created; 1 if it already
   existed.  */
int
mkdir_if_needed (name)
    char *name;
{
    if (mkdir (name) < 0)
    {
	/* Now, let me get this straight.  In IBM C/C++
	   under OS/2, the error string for EEXIST is:

	       "The file already exists",

           and the error string for EACCES is:

	       "The file or directory specified is read-only".

           Nonetheless, mkdir() will set EACCES if the
	   directory *exists*, according both to the
	   documentation and its actual behavior.

	   I'm sure that this made sense, to someone,
	   somewhere, sometime.  Just not me, here, now.  */
	if (errno != EEXIST
#ifdef EACCES
            && errno != EACCES
#endif
	    )
	    error (1, errno, "cannot make directory %s", name);
	return 1;
    }
    return 0;
}

/*
 * Change the mode of a file, either adding write permissions, or removing
 * all write permissions.  Adding write permissions honors the current umask
 * setting.
 */
void
xchmod (fname, writable)
    char *fname;
    int writable;
{
    char *attrib_cmd;
    char *attrib_option;
    char *whole_cmd;
    char *p;
    char *q;

    if (!isfile (fname))
    {
	error (0, 0, "cannot change mode of file %s; it does not exist",
	       fname);
	return;
    }

    attrib_cmd = "attrib "; /* No, really? */

    if (writable)
        attrib_option = "-r ";  /* make writeable */
    else
        attrib_option = "+r ";  /* make read-only */
        
    whole_cmd = xmalloc (strlen (attrib_cmd)
                         + strlen (attrib_option)
                         + strlen (fname)
                         + 1);

    strcpy (whole_cmd, attrib_cmd);
    strcat (whole_cmd, attrib_option);

    /* Copy fname to the end of whole_cmd, translating / to \.
	   Attrib doesn't take / but many parts of CVS rely
       on being able to use it.  */
    p = whole_cmd + strlen (whole_cmd);
    q = fname;
    while (*q)
    {
	if (*q == '/')
	    *p++ = '\\';
	else
	    *p++ = *q;
	++q;
    }
    *p = '\0';

    system (whole_cmd);
    free (whole_cmd);
}


/* Read the value of a symbolic link.
   Under OS/2, this function always returns EINVAL.  */
int
readlink (char *path, char *buf, int buf_size)
{
    errno = EINVAL;
    return -1;
}

/*
 * unlink a file, if possible.
 */
int
unlink_file (f)
    const char *f;
{
    if (trace)
#ifdef SERVER_SUPPORT
	(void) fprintf (stderr, "%c-> unlink(%s)\n",
			(server_active) ? 'S' : ' ', f);
#else
	(void) fprintf (stderr, "-> unlink(%s)\n", f);
#endif
    if (noexec)
	return (0);

   /* Win32 unlink is stupid - it fails if the file is read-only.
    * OS/2 is similarly stupid.  It does have a remove() function,
    * but the documentation does not make clear why remove() is or
    * isn't preferable to unlink().  I'll use unlink() because the
    * name is closer to our interface, what the heck.  Also, we know
    * unlink()'s error code when trying to remove a directory.
    */
    if (isfile (f))
	xchmod ((char *)f, 1);
    return (unlink (f));
}

/*
 * Unlink a file or dir, if possible.  If it is a directory do a deep
 * removal of all of the files in the directory.  Return -1 on error
 * (in which case errno is set).
 */
int
unlink_file_dir (f)
    const char *f;
{
    if (trace)
#ifdef SERVER_SUPPORT
	(void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n",
			(server_active) ? 'S' : ' ', f);
#else
	(void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
#endif
    if (noexec)
	return (0);

    if (unlink_file (f) != 0)
    {
        /* under OS/2, unlink returns EACCES if the path
	   is a directory.  */
        if (errno == EACCES)
                return deep_remove_dir (f);
        else
		/* The file wasn't a directory and some other
		 * error occured
		 */
                return -1;
    }
    /* We were able to remove the file from the disk */
    return 0;
}

/* Remove a directory and everything it contains.  Returns 0 for
 * success, -1 for failure (in which case errno is set).
 */

static int
deep_remove_dir (path)
    const char *path;
{
    DIR		  *dirp;
    struct dirent *dp;
    char	   buf[PATH_MAX];

    if (rmdir ((char *)path) != 0 && errno == EACCES)
    {
	if ((dirp = opendir ((char *)path)) == NULL)
	    /* If unable to open the directory return
	     * an error
	     */
	    return -1;

	while ((dp = readdir (dirp)) != NULL)
	{
	    if (strcmp (dp->d_name, ".") == 0 ||
			strcmp (dp->d_name, "..") == 0)
		continue;

	    sprintf (buf, "%s/%s", path, dp->d_name);

	    if (unlink_file (buf) != 0 )
	    {
		if (errno == EACCES)
		{
		    if (deep_remove_dir (buf))
		    {
			closedir (dirp);
			return -1;
		    }
		}
		else
		{
		    /* buf isn't a directory, or there are
		     * some sort of permision problems
		     */
		    closedir (dirp);
		    return -1;
		}
	    }
	}
	closedir (dirp);
	return rmdir ((char *)path);
    }
    /* Was able to remove the directory return 0 */
    return 0;
}


/*
 * Rename a file and die if it fails
 */
void
rename_file (from, to)
    const char *from;
    const char *to;
{
    if (trace)
#ifdef SERVER_SUPPORT
	(void) fprintf (stderr, "%c-> rename(%s,%s)\n",
			(server_active) ? 'S' : ' ', from, to);
#else
	(void) fprintf (stderr, "-> rename(%s,%s)\n", from, to);
#endif
    if (noexec)
	return;

    unlink_file (to);
    if (rename (from, to) != 0)
	error (1, errno, "cannot rename file %s to %s", from, to);
}


/* Read NCHARS bytes from descriptor FD into BUF.
   Return the number of characters successfully read.
   The number returned is always NCHARS unless end-of-file or error.  */
static size_t
block_read (fd, buf, nchars)
    int fd;
    char *buf;
    size_t nchars;
{
    char *bp = buf;
    size_t nread;

    do 
    {
	nread = read (fd, bp, nchars);
	if (nread == (size_t)-1)
	{
#ifdef EINTR
	    if (errno == EINTR)
		continue;
#endif
	    return (size_t)-1;
	}

	if (nread == 0)
	    break; 

	bp += nread;
	nchars -= nread;
    } while (nchars != 0);

    return bp - buf;
} 

    
/*
 * Compare "file1" to "file2". Return non-zero if they don't compare exactly.
 */
int
xcmp (file1, file2)
    const char *file1;
    const char *file2;
{
    char *buf1, *buf2;
    struct stat sb1, sb2;
    int fd1, fd2;
    int ret;

    if ((fd1 = open (file1, O_RDONLY | O_BINARY)) < 0)
	error (1, errno, "cannot open file %s for comparing", file1);
    if ((fd2 = open (file2, O_RDONLY | O_BINARY)) < 0)
	error (1, errno, "cannot open file %s for comparing", file2);
    if (fstat (fd1, &sb1) < 0)
	error (1, errno, "cannot fstat %s", file1);
    if (fstat (fd2, &sb2) < 0)
	error (1, errno, "cannot fstat %s", file2);

    /* A generic file compare routine might compare st_dev & st_ino here 
       to see if the two files being compared are actually the same file.
       But that won't happen in CVS, so we won't bother. */

    if (sb1.st_size != sb2.st_size)
	ret = 1;
    else if (sb1.st_size == 0)
	ret = 0;
    else
    {
	/* FIXME: compute the optimal buffer size by computing the least
	   common multiple of the files st_blocks field */
	size_t buf_size = 8 * 1024;
	size_t read1;
	size_t read2;

	buf1 = xmalloc (buf_size);
	buf2 = xmalloc (buf_size);

	do 
	{
	    read1 = block_read (fd1, buf1, buf_size);
	    if (read1 == (size_t)-1)
		error (1, errno, "cannot read file %s for comparing", file1);

	    read2 = block_read (fd2, buf2, buf_size);
	    if (read2 == (size_t)-1)
		error (1, errno, "cannot read file %s for comparing", file2);

	    /* assert (read1 == read2); */

	    ret = memcmp(buf1, buf2, read1);
	} while (ret == 0 && read1 == buf_size);

	free (buf1);
	free (buf2);
    }
	
    (void) close (fd1);
    (void) close (fd2);
    return (ret);
}


/* The equivalence class mapping for filenames.
   OS/2 filenames are case-insensitive, but case-preserving.  Both /
   and \ are path element separators. 
   Thus, this table maps both upper and lower case to lower case, and
   both / and \ to /.  

   Much thanks to Jim Blandy, who already invented this wheel in the
   Windows NT port. */

#if 0
main ()
{
  int c;

  for (c = 0; c < 256; c++)
    {
      int t;

      if (c == '\\')
        t = '/';
      else
        t = tolower (c);
      
      if ((c & 0x7) == 0x0)
         printf ("    ");
      printf ("0x%02x,", t);
      if ((c & 0x7) == 0x7)
         putchar ('\n');
      else if ((c & 0x7) == 0x3)
         putchar (' ');
    }
}
#endif


unsigned char
OS2_filename_classes[] =
{
    0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,
    0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f,
    0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17,
    0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f,
    0x20,0x21,0x22,0x23, 0x24,0x25,0x26,0x27,
    0x28,0x29,0x2a,0x2b, 0x2c,0x2d,0x2e,0x2f,
    0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37,
    0x38,0x39,0x3a,0x3b, 0x3c,0x3d,0x3e,0x3f,
    0x40,0x61,0x62,0x63, 0x64,0x65,0x66,0x67,
    0x68,0x69,0x6a,0x6b, 0x6c,0x6d,0x6e,0x6f,
    0x70,0x71,0x72,0x73, 0x74,0x75,0x76,0x77,
    0x78,0x79,0x7a,0x5b, 0x2f,0x5d,0x5e,0x5f,
    0x60,0x61,0x62,0x63, 0x64,0x65,0x66,0x67,
    0x68,0x69,0x6a,0x6b, 0x6c,0x6d,0x6e,0x6f,
    0x70,0x71,0x72,0x73, 0x74,0x75,0x76,0x77,
    0x78,0x79,0x7a,0x7b, 0x7c,0x7d,0x7e,0x7f,
    0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87,
    0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f,
    0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97,
    0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f,
    0xa0,0xa1,0xa2,0xa3, 0xa4,0xa5,0xa6,0xa7,
    0xa8,0xa9,0xaa,0xab, 0xac,0xad,0xae,0xaf,
    0xb0,0xb1,0xb2,0xb3, 0xb4,0xb5,0xb6,0xb7,
    0xb8,0xb9,0xba,0xbb, 0xbc,0xbd,0xbe,0xbf,
    0xc0,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7,
    0xc8,0xc9,0xca,0xcb, 0xcc,0xcd,0xce,0xcf,
    0xd0,0xd1,0xd2,0xd3, 0xd4,0xd5,0xd6,0xd7,
    0xd8,0xd9,0xda,0xdb, 0xdc,0xdd,0xde,0xdf,
    0xe0,0xe1,0xe2,0xe3, 0xe4,0xe5,0xe6,0xe7,
    0xe8,0xe9,0xea,0xeb, 0xec,0xed,0xee,0xef,
    0xf0,0xf1,0xf2,0xf3, 0xf4,0xf5,0xf6,0xf7,
    0xf8,0xf9,0xfa,0xfb, 0xfc,0xfd,0xfe,0xff,
};

/* Like strcmp, but with the appropriate tweaks for file names.
   Under OS/2, filenames are case-insensitive but case-preserving, and
   both \ and / are path element separators.  */ 
int
fncmp (const char *n1, const char *n2)
{
    while (*n1 && *n2
           && (OS2_filename_classes[(unsigned char) *n1]
	       == OS2_filename_classes[(unsigned char) *n2]))
        n1++, n2++;
    return (OS2_filename_classes[(unsigned char) *n1]
            - OS2_filename_classes[(unsigned char) *n2]);
}

/* Fold characters in FILENAME to their canonical forms.  
   If FOLD_FN_CHAR is not #defined, the system provides a default
   definition for this.  */
void
fnfold (char *filename)
{
    while (*filename)
    {
        *filename = FOLD_FN_CHAR (*filename);
	filename++;
    }
}


/* Generate a unique temporary filename.  Returns a pointer to a newly
   malloc'd string containing the name.  Returns successfully or not at
   all.  */
char *
cvs_temp_name ()
{
    char value[L_tmpnam + 1];
    char *retval;

    /* FIXME: Does OS/2 have some equivalent to TMPDIR?  */
    retval = tmpnam (value);
    if (retval == NULL)
	error (1, errno, "cannot generate temporary filename");
    return xstrdup (retval);
}

/* Return non-zero iff FILENAME is absolute.
   Trivial under Unix, but more complicated under other systems.  */
int
isabsolute (filename)
    const char *filename;
{
    return (ISDIRSEP (filename[0])
            || (filename[0] != '\0'
                && filename[1] == ':'
                && ISDIRSEP (filename[2])));
}

/* Return a pointer into PATH's last component.  */
char *
last_component (char *path)
{
    char *scan;
    char *last = 0;

    for (scan = path; *scan; scan++)
        if (ISDIRSEP (*scan))
	    last = scan;

    if (last && (last != path))
        return last + 1;
    else
        return path;
}


/* Return the home directory.  Returns a pointer to storage
   managed by this function or its callees (currently getenv).  */
char *
get_homedir ()
{
    return getenv ("HOME");
}

/* See cvs.h for description.  */
void
expand_wild (argc, argv, pargc, pargv)
    int argc;
    char **argv;
    int *pargc;
    char ***pargv;
{
    int i;
    int new_argc;
    char **new_argv;
    /* Allocated size of new_argv.  We arrange it so there is always room for
	   one more element.  */
    int max_new_argc;

    new_argc = 0;
    /* Add one so this is never zero.  */
    max_new_argc = argc + 1;
    new_argv = (char **) xmalloc (max_new_argc * sizeof (char *));
    for (i = 0; i < argc; ++i)
    {
	HDIR          FindHandle = 0x0001;
	FILEFINDBUF3  FindBuffer;
	ULONG         FindCount = 1;
	APIRET        rc;          /* Return code */
#define ALL_FILES (FILE_ARCHIVED|FILE_DIRECTORY|FILE_SYSTEM|FILE_HIDDEN|FILE_READONLY) 

	/* DosFindFirst, called with a string like 'dir/file' will return
	 * *only* the file part. So what we have to do here is to save the
	 * directory part, and add it later to the returned filename.
	 */

	/* Path + name */
	char *PathName = argv [i];

	/* Path only, including slash */
	char *Path = NULL;

	/* Name without path */
	char *Name = last_component (PathName);

	if (Name > PathName)
	{
	    /* We have a path component, save it */
	    Path = xmalloc (Name - PathName + 1);
	    memcpy (Path, PathName, Name - PathName);
	    Path [Name - PathName] = '\0';
	}

	rc = DosFindFirst(PathName,     	 /* File pattern */
			  &FindHandle,           /* Directory search handle */
			  ALL_FILES,             /* Search attribute */
			  (PVOID) &FindBuffer,   /* Result buffer */
			  sizeof(FindBuffer),    /* Result buffer length */
			  &FindCount,            /* Number of entries to find */
			  FIL_STANDARD);	 /* Return level 1 file info */

	if (rc != 0)
	{
	    if (rc == ERROR_NO_MORE_FILES)
	    {
		/* No match.  The file specified didn't contain a wildcard (in which case
		   we clearly should return it unchanged), or it contained a wildcard which
		   didn't match (in which case it might be better for it to be an error,
		   but we don't try to do that).  */
		new_argv [new_argc++] = xstrdup (argv[i]);
		if (new_argc == max_new_argc)
		{
		    max_new_argc *= 2;
		    new_argv = xrealloc (new_argv, max_new_argc * sizeof (char *));
		}
	    }
	    else
	    {
                error (1, rc, "cannot find %s", PathName);
	    }
	}
	else
	{
	    while (1)
	    {
		/*
		 * Don't match ".", "..", and files starting with '.'
		 * (unless pattern also starts with '.').  This is
		 * (more or less) what standard Unix globbing does.
		 */
		if ((strcmp(FindBuffer.achName, ".") != 0) &&
		    (strcmp(FindBuffer.achName, "..") != 0) &&
		    ((argv[i][0] == '.') || (FindBuffer.achName[0] != '.')))
		{
		    /* Be sure to add the path if needed */
		    char *NewArg;
		    if (Path)
		    {
			unsigned Len =
			    strlen (Path) + strlen (FindBuffer.achName) + 1;
			NewArg = xmalloc (Len);
			strcpy (NewArg, Path);
			strcat (NewArg, FindBuffer.achName);
		    }
		    else
		    {
			NewArg = xstrdup (FindBuffer.achName);
		    }
		    new_argv [new_argc++] = NewArg;
		    if (new_argc == max_new_argc)
		    {
			max_new_argc *= 2;
			new_argv = xrealloc (new_argv, max_new_argc * sizeof (char *));
		    }
		}

		rc = DosFindNext (FindHandle,
				  (PVOID) &FindBuffer,
				  sizeof(FindBuffer),
				  &FindCount);
		if (rc == ERROR_NO_MORE_FILES)
		    break;
		else if (rc != NO_ERROR)
		    error (1, rc, "cannot find %s", argv[i]);
	    }
	    rc = DosFindClose(FindHandle);
	    if (rc != 0)
		error (1, rc, "cannot close %s", argv[i]);
	}
	if (Path != NULL)
	    free (Path);
    }
    *pargc = new_argc;
    *pargv = new_argv;
}

/* Change drive and directory to path DIR.  */

int
os2_chdir (const char *Dir)
{
    /* If the path includes a drive, change the current drive to the one
       given.  */
    if (strlen (Dir) >= 2 && Dir [1] == ':')
    {
	/* A drive is given in Dir. Extract the drive from the string, then
	 * remove the drive from Dir by incrementing it.
	 */
	int Drive = Dir [0];
	Dir += 2;

	/* Check if the given drive is valid, convert to a drive number
	 * (A: == 1, B: == 2, etc.). The compare below assumes ascii, but
	 * that is not a problem with OS/2.
	 */
	if (Drive >= 'a' && Drive <= 'z')
	{
	    Drive -= 'a' - 1;
	}
	else if (Drive >= 'A' && Drive <= 'Z')
	{
	    Drive -= 'A' - 1;
	}
	else
	{
	    /* An invalid drive letter. Set errno and return an error */
	    errno = EACCES;
	    return -1;
	}

	/* We have a valid drive given, so change the drive now */
	if (DosSetDefaultDisk (Drive) != 0)
	{
	    /* We had an error. Assume that the drive does not exist */
#ifdef ENODEV
	    errno = ENODEV;
#else
	    /* IBM C/C++ Tools 2.01 seems to lack ENODEV.  */
	    errno = ENOENT;
#endif
	    return -1;
	}

    }

    /* Now we have a path without a drive left. Make it the current dir */
    return chdir (Dir);
}