/***
*getcwd.c - get current working directory (OS/2 version)
*
*	Copyright (c) 1985-1993, Microsoft Corporation. All rights reserved.
*
*Purpose:
*
*	contains functions _getcwd, _getdcwd and _getcdrv for getting the
*	current working directory.  getcwd gets the c.w.d. for the default disk
*	drive, whereas _getdcwd allows one to get the c.w.d. for whatever disk
*	drive is specified. _getcdrv gets the current drive.
*
*Revision History:
*	09-09-83  RKW	created
*	05-??-84  DCW	added conditional compilation to handle case of library
*			where SS != DS (can't take address of a stack variable).
*	09-??-84  DCW	changed comparison of path length to maxlen to take the
*			terminating null character into account.
*	11-28-84  DCW	changed to return errno values compatibly with the
*			System 3 version.
*	05-19-86  SKS	adapted for OS/2
*	11-19-86  SKS	if pnbuf==NULL, maxlen is ignored;
*			eliminated use of intermediate buffer "buf[]"; added
*			entry point "_getdcwd()" which takes a drive number.
*	12-03-86  SKS	if pnbuf==NULL, maxlen is the minimum allocation size
*	02-05-87  BCM	fixed comparison in _getdcwd,
*			(unsigned)(len+3) > (int)(maxlen), to handle maxlen < 0,
*			since implicit cast to (unsigned) was occurring.
*	12-11-87  JCR	Added "_LOAD_DS" to declaration
*	12-21-87  WAJ	Added _getcdrv()
*	06-22-88  WAJ	_getcdrv() is now made for all OS/2 libs
*	10-03-88  JCR	386: Change DOS calls to SYS calls
*	10-04-88  JCR	386: Removed 'far' keyword
*	10-10-88  GJF	Made API names match DOSCALLS.H
*	01-31-89  JCR	Remove _getcdrv(), which has been renamed _getdrive()
*	04-12-89  JCR	Use new OS/2 system calls
*	05-25-89  JCR	386 OS/2 calls use '_syscall' calling convention
*	11-27-89  JCR	Corrected ERRNO values
*	12-12-89  JCR	Fixed bogus syscall introduced in previous fix (oops)
*	03-07-90  GJF	Replaced _LOAD_DS by _CALLTYPE1, added #include
*			<cruntime.h>, removed #include <register.h>, removed
*			some leftover 16-bit support and fixed the copyright.
*			Also, cleaned up the formatting a bit.
*	07-24-90  SBM	Compiles cleanly with -W3 (removed unreferenced
*			variable), removed redundant includes, removed
*			'32' from API names
*	08-10-90  SBM	Compiles cleanly with -W3 with new build of compiler
*	09-27-90  GJF	New-style function declarator.
*	12-04-90  SRW	Changed to include <oscalls.h> instead of <doscalls.h>
*	12-06-90  SRW	Added _CRUISER_ and _WIN32 conditionals.
*	01-16-91  GJF	ANSI naming.
*	08-21-91  JCR	Test DOSQUERYCURRENTDIR call for error return (bug fix)
*	04-23-92  GJF	Fixed initialization of DriveVar[].
*	04-28-92  GJF	Revised Win32 version.
*	12-13-93  GJF	In _getdcwd(), was using local array to hold env
*			string which was _putenv-ed. Ouch!
*
*******************************************************************************/

#include <cruntime.h>
#include <os2dll.h>
#include <msdos.h>
#include <errno.h>
#include <malloc.h>
#include <oscalls.h>
#include <stdlib.h>
#include <internal.h>
#include <direct.h>


#ifdef	_CRUISER_

/***
*char *_getcwd(pnbuf, maxlen) - get current working directory of default drive
*
*Purpose:
*	_getcwd gets the current working directory for the user,
*	placing it in the buffer pointed to by pnbuf.  It returns
*	the length of the string put in the buffer.  If the length
*	of the string exceeds the length of the buffer, maxlen,
*	then NULL is returned.	If pnbuf = NULL, maxlen is ignored.
*	An entry point "_getdcwd()" is defined with takes the above
*	parameters, plus a drive number.  "_getcwd()" is implemented
*	as a call to "_getcwd()" with the default drive (0).
*
*	If pnbuf = NULL, maxlen is ignored, and a buffer is automatically
*	allocated using malloc() -- a pointer to which is returned by
*	_getcwd().
*
*	side effects: no global data is used or affected
*
*Entry:
*	char *pnbuf = pointer to a buffer maintained by the user;
*	int maxlen = length of the buffer pointed to by pnbuf;
*
*Exit:
*	Returns pointer to the buffer containing the c.w.d. name
*	(same as pnbuf if non-NULL; otherwise, malloc is
*	used to allocate a buffer)
*
*Exceptions:
*******************************************************************************/

/*
** _getcwd() is just a call to _getdcwd() with the default drive
*/

char * _CALLTYPE1 _getcwd (
	char *pnbuf,
	int maxlen
	)
{
	return(_getdcwd(0, pnbuf, maxlen));
}


/***
*char *_getdcwd(drive, pnbuf, maxlen) - get c.w.d. for given drive
*
*Purpose:
*	_getdcwd gets the current working directory for the user,
*	placing it in the buffer pointed to by pnbuf.  It returns
*	the length of the string put in the buffer.  If the length
*	of the string exceeds the length of the buffer, maxlen,
*	then NULL is returned.	If pnbuf = NULL, maxlen is ignored,
*	and a buffer is automatically allocated using malloc() --
*	a pointer to which is returned by _getdcwd().
*
*	side effects: no global data is used or affected
*
*Entry:
*	int drive   - number of the drive being inquired about
*		      0 = default, 1 = 'a:', 2 = 'b:', etc.
*	char *pnbuf - pointer to a buffer maintained by the user;
*	int maxlen  - length of the buffer pointed to by pnbuf;
*
*Exit:
*	Returns pointer to the buffer containing the c.w.d. name
*	(same as pnbuf if non-NULL; otherwise, malloc is
*	used to allocate a buffer)
*
*Exceptions:
*
*******************************************************************************/

char * _CALLTYPE1 _getdcwd (
	int drive,
	char *pnbuf,
	REG2 int maxlen
	)
{
	REG1 char *p;
	char dirbuf[1];

	unsigned len = 1;
	unsigned oserr;

	/*
	** Get default drive if necessary
	*/
	if (drive == 0)
		drive = _getdrive();

	/*
	** Ask DOS the length of the current directory string
	*/

	if ((oserr = DOSQUERYCURRENTDIR(drive,(char *)dirbuf,(unsigned *)&len))
		!= ERROR_BUFFER_OVERFLOW) {
    oserr_rtn:				/* common error return */
		errno = EACCES; 	/* probably bogus drive */
		_doserrno = oserr;
		return(NULL);
	}

	/* see if need to try to allocate buffer in heap */

	if (!(p = pnbuf)) {
		if (((int)len+3) > maxlen)
			maxlen = len+3;
		if (!(p = malloc(maxlen))) {
			errno = ENOMEM;
			_doserrno = E_nomem;
			return(p);
		}
		pnbuf = p;
	}

	/* set up string - prepend drive letter + colon to the path name
	 */

	*p++ = (char)(drive + 'A' - 1); /* drive specifier */
	*p++ = ':';
	*p++ = '\\';

	/* check to make sure it all fits in the supplied (or created) buffer.
	 */

	if ((int)len+3 > maxlen) {
		errno = ERANGE; /* Won't fit in user buffer */
		return(NULL);
	}

	/* get root relative path name
	 */

	if (oserr = DOSQUERYCURRENTDIR(drive,(char *)p,(unsigned *)&len))
		goto oserr_rtn; /* join other DOSQUERYCURRENTDIRDIR error return */

	return(p-3);

}


#else	/* ndef _CRUISER_ */

#ifdef	_WIN32_


/***
*char *_getcwd(pnbuf, maxlen) - get current working directory of default drive
*
*Purpose:
*	_getcwd gets the current working directory for the user,
*	placing it in the buffer pointed to by pnbuf.  It returns
*	the length of the string put in the buffer.  If the length
*	of the string exceeds the length of the buffer, maxlen,
*	then NULL is returned.	If pnbuf = NULL, maxlen is ignored.
*	An entry point "_getdcwd()" is defined with takes the above
*	parameters, plus a drive number.  "_getcwd()" is implemented
*	as a call to "_getcwd()" with the default drive (0).
*
*	If pnbuf = NULL, maxlen is ignored, and a buffer is automatically
*	allocated using malloc() -- a pointer to which is returned by
*	_getcwd().
*
*	side effects: no global data is used or affected
*
*Entry:
*	char *pnbuf = pointer to a buffer maintained by the user;
*	int maxlen = length of the buffer pointed to by pnbuf;
*
*Exit:
*	Returns pointer to the buffer containing the c.w.d. name
*	(same as pnbuf if non-NULL; otherwise, malloc is
*	used to allocate a buffer)
*
*Exceptions:
*
*******************************************************************************/


char * _CALLTYPE1 _getcwd (
	char *pnbuf,
	int maxlen
	)
{
	char *retval;

	_mlock(_ENV_LOCK);

	retval = _getdcwd_lk(0, pnbuf, maxlen);

	_munlock(_ENV_LOCK);

	return retval;
}


/***
*char *_getdcwd(drive, pnbuf, maxlen) - get c.w.d. for given drive
*
*Purpose:
*	_getdcwd gets the current working directory for the user,
*	placing it in the buffer pointed to by pnbuf.  It returns
*	the length of the string put in the buffer.  If the length
*	of the string exceeds the length of the buffer, maxlen,
*	then NULL is returned.	If pnbuf = NULL, maxlen is ignored,
*	and a buffer is automatically allocated using malloc() --
*	a pointer to which is returned by _getdcwd().
*
*	side effects: no global data is used or affected
*
*Entry:
*	int drive   - number of the drive being inquired about
*		      0 = default, 1 = 'a:', 2 = 'b:', etc.
*	char *pnbuf - pointer to a buffer maintained by the user;
*	int maxlen  - length of the buffer pointed to by pnbuf;
*
*Exit:
*	Returns pointer to the buffer containing the c.w.d. name
*	(same as pnbuf if non-NULL; otherwise, malloc is
*	used to allocate a buffer)
*
*Exceptions:
*
*******************************************************************************/


#ifdef	MTHREAD

char * _CALLTYPE1 _getdcwd (
	int drive,
	char *pnbuf,
	int maxlen
	)
{
	char *retval;

	_mlock(_ENV_LOCK);

	retval = _getdcwd_lk(drive, pnbuf, maxlen);

	_munlock(_ENV_LOCK);

	return retval;
}

char * _CALLTYPE1 _getdcwd_lk (
	int drive,
	char *pnbuf,
	int maxlen
	)
#else

char * _CALLTYPE1 _getdcwd (
	int drive,
	char *pnbuf,
	int maxlen
	)
#endif

{
	char *p;
	char dirbuf[1];
	int len;
	char DirOnDriveVar[4];
	char *envval;

	/*
	 * Only works for default drive in Win32 environment.
	 */
	if ( drive != 0 ) {
		/*
		 * Not the default drive - make sure it's valid.
		 */
		if ( !_ValidDrive(drive) ) {
                        errno = EACCES;
                        return NULL;
		}

		/*
		 * Get special environment variable that specifies the current
		 * directory on drive.
		 */
		DirOnDriveVar[0] = '=';
		DirOnDriveVar[1] = (char)('A' + (char)drive - (char)1);
		DirOnDriveVar[2] = ':';
		DirOnDriveVar[3] = '\0';

		if ( (envval = _getenv_lk(DirOnDriveVar)) == NULL ) {

		    /*
		     * Need to define the environment variable, allocate
		     * space from the heap to hold the string
		     */
		    if ( (envval = malloc( 8 * sizeof(char) )) == NULL ) {
			errno = ENOMEM;     /* must be out of heap memory */
			return NULL;
		    }

		    /*
		     * Environment variable not defined! Define it to be the
		     * root on that drive.
		     */
		    envval[0] = envval[3] = '=';
		    envval[1] = envval[4] = (char)('A' + (char)drive
					    - (char)1);
		    envval[2] = envval[5] = ':';
		    envval[6] = '\\';
		    envval[7] = '\0';
		    if ( _putenv_lk(envval) != 0 ) {
			    errno = ENOMEM; /* must be out of heap memory */
		            return NULL;
		    }
		    envval += 4;
		}

		len = strlen(envval) + 1;

        } else {

	    /*
	     * Ask OS the length of the current directory string
	     */
	    len = GetCurrentDirectory(sizeof(dirbuf), (LPSTR)dirbuf) + 1;
        }

	/*
	 * Set up the buffer.
	 */
	if ( (p = pnbuf) == NULL ) {
		/*
		 * Allocate a buffer for the user.
		 */
		if ( (p = malloc(__max(len, maxlen))) == NULL ) {
			errno = ENOMEM;
			return NULL;
		}
	}
	else if ( len > maxlen ) {
		/*
		 * Won't fit in the user-supplied buffer!
		 */
		errno = ERANGE; /* Won't fit in user buffer */
		return NULL;
	}

	/*
	 * Place the current directory string into the user buffer
	 */

	if ( drive != 0 )
		/*
		 * Copy value of special environment variable into user buffer.
		 */
		strcpy(p, envval);
	else
		/*
		 * Get the current directory directly from the OS
		 */
		if ( GetCurrentDirectory(len,p) == 0 ) {
			/*
			 * Oops. For lack of a better idea, assume some sort
			 * of access error has occurred.
			 */
			errno = EACCES;
			_doserrno = GetLastError();
			return NULL;
		}

	return p;
}

#else	/* ndef _WIN32_ */

#error ERROR - ONLY CRUISER OR WIN32 TARGET SUPPORTED!

#endif	/* _WIN32_ */

#endif	/* _CRUISER_ */