summaryrefslogtreecommitdiffstats
path: root/private/crt32/dos/fullpath.c
blob: 30f27dfe4d6e44c527bc342d518eb2ad68befdef (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/***
*fullpath.c -
*
*	Copyright (c) 1987-1992, Microsoft Corporation. All rights reserved.
*
*Purpose: contains the function _fullpath which makes an absolute path out
*	of a relative path. i.e.  ..\pop\..\main.c => c:\src\main.c if the
*	current directory is c:\src\src
*
*Revision History:
*
*	12-21-87  WAJ	Initial version
*	01-08-88  WAJ	now treats / as an \
*	06-22-88  WAJ	now handles network paths  ie \\sl\users
*	01-31-89  SKS/JCR Renamed _canonic to _fullpath
*	04-03-89  WAJ	Now returns "d:\dir" for "."
*	05-09-89  SKS	Do not change the case of arguments
*	11-30-89  JCR	Preserve errno setting from _getdcwd() call on errors
*	03-07-90  GJF	Replaced _LOAD_DS with _CALLTYPE1, added #include
*			<cruntime.h>, removed #include <register.h> and fixed
*			the copyright.
*	04-25-90  JCR	Fixed an incorrect errno setting
*	06-14-90  SBM	Fixed bugs in which case of user provided drive letter
*			was not always preserved, and c:\foo\\bar did not
*			generate an error
*	08-10-90  SBM	Compiles cleanly with -W3
*	08-28-90  SBM	Fixed bug in which UNC names were being rejected
*	09-27-90  GJF	New-style function declarator.
*	01-18-91  GJF	ANSI naming.
*	11-30-92  KRS	Ported _MBCS code from 16-bit tree.
*	08-03-93  KRS	Change to use _ismbstrail instead of isdbcscode.
*	09-27-93  CFW	Avoid cast bug.
*
*******************************************************************************/

#include <cruntime.h>
#include <stdio.h>
#include <direct.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <internal.h>
#include <ctype.h>
#ifdef	_MBCS
#include <mbdata.h>
#include <mbctype.h>
#endif


#define ISSLASH(a)  ((a) == '\\' || (a) == '/')

/***
*char *_fullpath( char *buf, const char *path, maxlen );
*
*Purpose:
*
*	_fullpath - combines the current directory with path to form
*	an absolute path. i.e. _fullpath takes care of .\ and ..\
*	in the path.
*
*	The result is placed in buf. If the length of the result
*	is greater than maxlen NULL is returned, otherwise
*	the address of buf is returned.
*
*	If buf is NULL then a buffer is malloc'ed and maxlen is
*	ignored. If there are no errors then the address of this
*	buffer is returned.
*
*	If path specifies a drive, the curent directory of this
*	drive is combined with path. If the drive is not valid
*	and _fullpath needs the current directory of this drive
*	then NULL is returned.	If the current directory of this
*	non existant drive is not needed then a proper value is
*	returned.
*	For example:  path = "z:\\pop" does not need z:'s current
*	directory but path = "z:pop" does.
*
*
*
*Entry:
*	char *buf  - pointer to a buffer maintained by the user;
*	char *path - path to "add" to the current directory
*	int maxlen - length of the buffer pointed to by buf
*
*Exit:
*	Returns pointer to the buffer containing the absolute path
*	(same as buf if non-NULL; otherwise, malloc is
*	used to allocate a buffer)
*
*Exceptions:
*
*******************************************************************************/


char * _CALLTYPE1 _fullpath (
    char *UserBuf,
    const char *path,
    size_t maxlen
    )
{
    char    *RetValue;
    char    *buf;
    char    *StartBuf;
    char    *EndBuf;
    char    *SaveBuf;
    char    c;
    int     count;
    unsigned drive;
#ifdef _MBCS
const char  *StartPath = path;
#endif


    if( !path || !*path )   /* no work to do */
	return( _getcwd( UserBuf, maxlen ) );

    /* allocate buffer if necessary */

    if( !UserBuf )
	if( !(buf = malloc(_MAX_PATH)) ){
	    errno = ENOMEM;
	    return( NULL );
	    }
	else
	    maxlen = _MAX_PATH;

    else {
	if( maxlen < _MAX_DRIVE+1 ){	/* we need at least 4 chars for "A:\" */
	    errno = ERANGE;
	    return( NULL );
	    }
	buf = UserBuf;
	}

    RetValue = buf;
    EndBuf = buf + maxlen - 1;

    if( ISSLASH(*(path+1)) && ISSLASH(*path) ){ /* check for network drive */

	count = 0;			/* count the '\\'s */
	while( (c = *path) ){
	    *buf = c;
	    path++;

	    if( buf >= EndBuf ){
ReturnError1:
		errno = ERANGE;
ReturnError2:
		if( UserBuf == NULL )
		    free( RetValue );
		return( NULL );
		}

#ifdef _MBCS
	    if ( !_ISLEADBYTE(*(buf-2)))
#endif
	    if( ISSLASH( c ) ){
		/* ensure that path contains a '\\', not a '/' */
		*buf = '\\';

		/* ensure that '\\\\' is followed by something */
		if( ++count == 2 ){
		    if( !*path ){
			errno = EINVAL;
			goto ReturnError2;
			}
		    }

		/* check that third and fourth '\\'are preceded by non-'\\' */
		if( count >= 3 ){
		    if( *(buf-1) == '\\' ){
			errno = EINVAL;
			goto ReturnError2;
			}
		    }

		/* stop on fourth '\\', \\foo\bar\ <-- namely this one */
		if( count == 4 )
		    break;
		}

	    buf++;
	    }

	*buf = '\\';
	StartBuf = buf;
	}

    else {				/* not a UNC path */

	if( path[1] == ':' && isalpha((unsigned char)path[0]) ) { /* get drive information */
	    *buf = *path;
	    /*
	    ** drive must be in the range 1-26
	    ** "*buf" may be upper or lower case
	    */
	    drive =  (*buf++ - 'A' + 1) & 0x1F;
	    *buf++ = ':';
	    path += 2;
	    }
	else
	    drive = 0;

	if( ISSLASH( *path ) ){
	    if( drive == 0 ){
		*buf++ = (char)(_getdrive() + 'A' - 1);
		*buf++ = ':';
		}

	    path++;
	    }

	else {
	    /* preserve the driveletter in original case from path,
	       since _getdcwd will always return it in upper case */
	    if (drive)
		c = *(path-2);
	    if( !_getdcwd( drive, RetValue, maxlen ) )
		goto ReturnError2;	/* error: preserve errno value */
	    buf = RetValue + strlen( RetValue );
	    /* restore drive letter in original case */
	    if (drive)
		*RetValue = c;
#ifdef _MBCS
	    if (!_ISLEADBYTE(*(buf-2)))
#endif
	    if( ISSLASH( *(buf-1) ) )	/* buf must point to last slash */
		buf--;			/*  if this is a root directory */
	    }

	*buf = '\\';
	StartBuf = RetValue + 2;
	}


    while( *path ){

	if(    *path == '.'
	    && *(path+1) == '.'
	    && ( ISSLASH(*(path+2)) || !*(path+2) ) ){	/* handle .. */

	    do{
		buf--;
#ifdef _MBCS
	if (ISSLASH(*buf))
	    if (_ismbstrail(StartBuf,buf))
		buf--;
#endif
	    }
	    while( !ISSLASH( *buf ) && buf > StartBuf );

	    if( buf < StartBuf ) {	/* unable to do cd .. */
		errno = EACCES;
		goto ReturnError2;
		}

	    path += 2;
	    if( *path ) 		/* if it was "..\" */
		path++;
	    }

	else if( *path == '.' && ( ISSLASH(*(path+1)) || !*(path+1) )){

	    path++;			/* handle . */
	    if( *path ) 		/* if it was ".\" */
		path++;
	    }

	else {
	    SaveBuf = buf;	/* save for DBCS-safe test for null path */

	    while((*path) && (!ISSLASH(*path)) && (buf < EndBuf)) {
		*++buf = *path++;
#ifdef _MBCS
	    if (_ISLEADBYTE(*(path-1)))
		{
		if ((*path) && (buf < EndBuf))
		   *++buf = *path++;
		else
		    {	/* ill-formed DBCS character */
		    errno = EINVAL;
		    goto ReturnError2;
		    }
		}
#endif
	      }

	    if( buf >= EndBuf ) 	/* if buf == EndBuf then */
		goto ReturnError1;	/* no room for '\0'	 */

	    /* DBCS-safe test for non-empty path component */
	    if ((buf==SaveBuf) /* && (*buf=='\\') */ ) /* 2nd test redundent */
		{
		errno = EINVAL;
		goto ReturnError2;
		}

	    *++buf = '\\';
	    if( ISSLASH( *path ) )
#ifdef _MBCS
	      if (!_ismbstrail(StartPath,path))
#endif
		path++;
	    }
       }


    /* buf points to last character in the string which is an '\\' */

    if( *(buf-1) == ':' )	/* keep trailing '\' if */
#ifdef _MBCS
	  if (!_ismbstrail(StartBuf,buf-1))
#endif
		buf++;		/* this is a root directory */
    *buf = '\0';

    return( RetValue );
}