1 /*
2 * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3 *
4 * All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, and/or sell copies of the Software, and to permit persons
11 * to whom the Software is furnished to do so, provided that the above
12 * copyright notice(s) and this permission notice appear in all copies of
13 * the Software and that both the above copyright notice(s) and this
14 * permission notice appear in supporting documentation.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 *
26 * Except as contained in this notice, the name of a copyright holder
27 * shall not be used in advertising or otherwise to promote the sale, use
28 * or other dealings in this Software without prior written authorization
29 * of the copyright holder.
30 */
31
32 #pragma ident "%Z%%M% %I% %E% SMI"
33
34 /*
35 * If file-system access is to be excluded, this module has no function,
36 * so all of its code should be excluded.
37 */
38 #ifndef WITHOUT_FILE_SYSTEM
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <errno.h>
43 #include <string.h>
44 #include <ctype.h>
45 #include <limits.h>
46
47 #include <unistd.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
50
51 #include "pathutil.h"
52
53 /*.......................................................................
54 * Create a new PathName object.
55 *
56 * Output:
57 * return PathName * The new object, or NULL on error.
58 */
_new_PathName(void)59 PathName *_new_PathName(void)
60 {
61 PathName *path; /* The object to be returned */
62 /*
63 * Allocate the container.
64 */
65 path = (PathName *) malloc(sizeof(PathName));
66 if(!path) {
67 errno = ENOMEM;
68 return NULL;
69 };
70 /*
71 * Before attempting any operation that might fail, initialize the
72 * container at least up to the point at which it can safely be passed
73 * to _del_PathName().
74 */
75 path->name = NULL;
76 path->dim = 0;
77 /*
78 * Figure out the maximum length of an expanded pathname.
79 */
80 path->dim = _pu_pathname_dim();
81 if(path->dim == 0)
82 return _del_PathName(path);
83 /*
84 * Allocate the pathname buffer.
85 */
86 path->name = (char *)malloc(path->dim * sizeof(char));
87 if(!path->name) {
88 errno = ENOMEM;
89 return _del_PathName(path);
90 };
91 return path;
92 }
93
94 /*.......................................................................
95 * Delete a PathName object.
96 *
97 * Input:
98 * path PathName * The object to be deleted.
99 * Output:
100 * return PathName * The deleted object (always NULL).
101 */
_del_PathName(PathName * path)102 PathName *_del_PathName(PathName *path)
103 {
104 if(path) {
105 if(path->name)
106 free(path->name);
107 free(path);
108 };
109 return NULL;
110 }
111
112 /*.......................................................................
113 * Return the pathname to a zero-length string.
114 *
115 * Input:
116 * path PathName * The pathname container.
117 * Output:
118 * return char * The cleared pathname buffer, or NULL on error.
119 */
_pn_clear_path(PathName * path)120 char *_pn_clear_path(PathName *path)
121 {
122 /*
123 * Check the arguments.
124 */
125 if(!path) {
126 errno = EINVAL;
127 return NULL;
128 };
129 path->name[0] = '\0';
130 return path->name;
131 }
132
133 /*.......................................................................
134 * Append a string to a pathname, increasing the size of the pathname
135 * buffer if needed.
136 *
137 * Input:
138 * path PathName * The pathname container.
139 * string const char * The string to be appended to the pathname.
140 * Note that regardless of the slen argument,
141 * this should be a '\0' terminated string.
142 * slen int The maximum number of characters to append
143 * from string[], or -1 to append the whole
144 * string.
145 * remove_escapes int If true, remove the backslashes that escape
146 * spaces, tabs, backslashes etc..
147 * Output:
148 * return char * The pathname string path->name[], which may
149 * have been reallocated, or NULL if there was
150 * insufficient memory to extend the pathname.
151 */
_pn_append_to_path(PathName * path,const char * string,int slen,int remove_escapes)152 char *_pn_append_to_path(PathName *path, const char *string, int slen,
153 int remove_escapes)
154 {
155 int pathlen; /* The length of the pathname */
156 int i;
157 /*
158 * Check the arguments.
159 */
160 if(!path || !string) {
161 errno = EINVAL;
162 return NULL;
163 };
164 /*
165 * Get the current length of the pathname.
166 */
167 pathlen = strlen(path->name);
168 /*
169 * How many characters should be appended?
170 */
171 if(slen < 0 || slen > strlen(string))
172 slen = strlen(string);
173 /*
174 * Resize the pathname if needed.
175 */
176 if(!_pn_resize_path(path, pathlen + slen))
177 return NULL;
178 /*
179 * Append the string to the output pathname, removing any escape
180 * characters found therein.
181 */
182 if(remove_escapes) {
183 int is_escape = 0;
184 for(i=0; i<slen; i++) {
185 is_escape = !is_escape && string[i] == '\\';
186 if(!is_escape)
187 path->name[pathlen++] = string[i];
188 };
189 /*
190 * Terminate the string.
191 */
192 path->name[pathlen] = '\0';
193 } else {
194 /*
195 * Append the string directly to the pathname.
196 */
197 memcpy(path->name + pathlen, string, slen);
198 path->name[pathlen + slen] = '\0';
199 };
200 return path->name;
201 }
202
203 /*.......................................................................
204 * Prepend a string to a pathname, increasing the size of the pathname
205 * buffer if needed.
206 *
207 * Input:
208 * path PathName * The pathname container.
209 * string const char * The string to be prepended to the pathname.
210 * Note that regardless of the slen argument,
211 * this should be a '\0' terminated string.
212 * slen int The maximum number of characters to prepend
213 * from string[], or -1 to append the whole
214 * string.
215 * remove_escapes int If true, remove the backslashes that escape
216 * spaces, tabs, backslashes etc..
217 * Output:
218 * return char * The pathname string path->name[], which may
219 * have been reallocated, or NULL if there was
220 * insufficient memory to extend the pathname.
221 */
_pn_prepend_to_path(PathName * path,const char * string,int slen,int remove_escapes)222 char *_pn_prepend_to_path(PathName *path, const char *string, int slen,
223 int remove_escapes)
224 {
225 int pathlen; /* The length of the pathname */
226 int shift; /* The number of characters to shift the suffix by */
227 int i,j;
228 /*
229 * Check the arguments.
230 */
231 if(!path || !string) {
232 errno = EINVAL;
233 return NULL;
234 };
235 /*
236 * Get the current length of the pathname.
237 */
238 pathlen = strlen(path->name);
239 /*
240 * How many characters should be appended?
241 */
242 if(slen < 0 || slen > strlen(string))
243 slen = strlen(string);
244 /*
245 * Work out how far we need to shift the original path string to make
246 * way for the new prefix. When removing escape characters, we need
247 * final length of the new prefix, after unescaped backslashes have
248 * been removed.
249 */
250 if(remove_escapes) {
251 int is_escape = 0;
252 for(shift=0,i=0; i<slen; i++) {
253 is_escape = !is_escape && string[i] == '\\';
254 if(!is_escape)
255 shift++;
256 };
257 } else {
258 shift = slen;
259 };
260 /*
261 * Resize the pathname if needed.
262 */
263 if(!_pn_resize_path(path, pathlen + shift))
264 return NULL;
265 /*
266 * Make room for the prefix at the beginning of the string.
267 */
268 memmove(path->name + shift, path->name, pathlen+1);
269 /*
270 * Copy the new prefix into the vacated space at the beginning of the
271 * output pathname, removing any escape characters if needed.
272 */
273 if(remove_escapes) {
274 int is_escape = 0;
275 for(i=j=0; i<slen; i++) {
276 is_escape = !is_escape && string[i] == '\\';
277 if(!is_escape)
278 path->name[j++] = string[i];
279 };
280 } else {
281 memcpy(path->name, string, slen);
282 };
283 return path->name;
284 }
285
286 /*.......................................................................
287 * If needed reallocate a given pathname buffer to allow a string of
288 * a given length to be stored in it.
289 *
290 * Input:
291 * path PathName * The pathname container object.
292 * length size_t The required length of the pathname buffer,
293 * not including the terminating '\0'.
294 * Output:
295 * return char * The pathname buffer, or NULL if there was
296 * insufficient memory.
297 */
_pn_resize_path(PathName * path,size_t length)298 char *_pn_resize_path(PathName *path, size_t length)
299 {
300 /*
301 * Check the arguments.
302 */
303 if(!path) {
304 errno = EINVAL;
305 return NULL;
306 };
307 /*
308 * If the pathname buffer isn't large enough to accomodate a string
309 * of the specified length, attempt to reallocate it with the new
310 * size, plus space for a terminating '\0'. Also add a bit of
311 * head room to prevent too many reallocations if the initial length
312 * turned out to be very optimistic.
313 */
314 if(length + 1 > path->dim) {
315 size_t dim = length + 1 + PN_PATHNAME_INC;
316 char *name = (char *) realloc(path->name, dim);
317 if(!name)
318 return NULL;
319 path->name = name;
320 path->dim = dim;
321 };
322 return path->name;
323 }
324
325 /*.......................................................................
326 * Estimate the largest amount of space needed to store a pathname.
327 *
328 * Output:
329 * return size_t The number of bytes needed, including space for the
330 * terminating '\0'.
331 */
_pu_pathname_dim(void)332 size_t _pu_pathname_dim(void)
333 {
334 int maxlen; /* The return value excluding space for the '\0' */
335 /*
336 * If the POSIX PATH_MAX macro is defined in limits.h, use it.
337 */
338 #ifdef PATH_MAX
339 maxlen = PATH_MAX;
340 /*
341 * If we have pathconf, use it.
342 */
343 #elif defined(_PC_PATH_MAX)
344 errno = 0;
345 maxlen = pathconf(FS_ROOT_DIR, _PC_PATH_MAX);
346 if(maxlen <= 0 || errno)
347 maxlen = MAX_PATHLEN_FALLBACK;
348 /*
349 * None of the above approaches worked, so substitute our fallback
350 * guess.
351 */
352 #else
353 maxlen = MAX_PATHLEN_FALLBACK;
354 #endif
355 /*
356 * Return the amount of space needed to accomodate a pathname plus
357 * a terminating '\0'.
358 */
359 return maxlen + 1;
360 }
361
362 /*.......................................................................
363 * Return non-zero if the specified path name refers to a directory.
364 *
365 * Input:
366 * pathname const char * The path to test.
367 * Output:
368 * return int 0 - Not a directory.
369 * 1 - pathname[] refers to a directory.
370 */
_pu_path_is_dir(const char * pathname)371 int _pu_path_is_dir(const char *pathname)
372 {
373 struct stat statbuf; /* The file-statistics return buffer */
374 /*
375 * Look up the file attributes.
376 */
377 if(stat(pathname, &statbuf) < 0)
378 return 0;
379 /*
380 * Is the file a directory?
381 */
382 return S_ISDIR(statbuf.st_mode) != 0;
383 }
384
385 /*.......................................................................
386 * Return non-zero if the specified path name refers to a regular file.
387 *
388 * Input:
389 * pathname const char * The path to test.
390 * Output:
391 * return int 0 - Not a regular file.
392 * 1 - pathname[] refers to a regular file.
393 */
_pu_path_is_file(const char * pathname)394 int _pu_path_is_file(const char *pathname)
395 {
396 struct stat statbuf; /* The file-statistics return buffer */
397 /*
398 * Look up the file attributes.
399 */
400 if(stat(pathname, &statbuf) < 0)
401 return 0;
402 /*
403 * Is the file a regular file?
404 */
405 return S_ISREG(statbuf.st_mode) != 0;
406 }
407
408 /*.......................................................................
409 * Return non-zero if the specified path name refers to an executable.
410 *
411 * Input:
412 * pathname const char * The path to test.
413 * Output:
414 * return int 0 - Not an executable file.
415 * 1 - pathname[] refers to an executable file.
416 */
_pu_path_is_exe(const char * pathname)417 int _pu_path_is_exe(const char *pathname)
418 {
419 struct stat statbuf; /* The file-statistics return buffer */
420 /*
421 * Look up the file attributes.
422 */
423 if(stat(pathname, &statbuf) < 0)
424 return 0;
425 /*
426 * Is the file a regular file which is executable by the current user.
427 */
428 return S_ISREG(statbuf.st_mode) != 0 &&
429 (statbuf.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR)) &&
430 access(pathname, X_OK) == 0;
431 }
432
433 /*.......................................................................
434 * Search backwards for the potential start of a filename. This
435 * looks backwards from the specified index in a given string,
436 * stopping at the first unescaped space or the start of the line.
437 *
438 * Input:
439 * string const char * The string to search backwards in.
440 * back_from int The index of the first character in string[]
441 * that follows the pathname.
442 * Output:
443 * return char * The pointer to the first character of
444 * the potential pathname, or NULL on error.
445 */
_pu_start_of_path(const char * string,int back_from)446 char *_pu_start_of_path(const char *string, int back_from)
447 {
448 int i, j;
449 /*
450 * Check the arguments.
451 */
452 if(!string || back_from < 0) {
453 errno = EINVAL;
454 return NULL;
455 };
456 /*
457 * Search backwards from the specified index.
458 */
459 for(i=back_from-1; i>=0; i--) {
460 int c = string[i];
461 /*
462 * Stop on unescaped spaces.
463 */
464 if(isspace((int)(unsigned char)c)) {
465 /*
466 * The space can't be escaped if we are at the start of the line.
467 */
468 if(i==0)
469 break;
470 /*
471 * Find the extent of the escape characters which precedes the space.
472 */
473 for(j=i-1; j>=0 && string[j]=='\\'; j--)
474 ;
475 /*
476 * If there isn't an odd number of escape characters before the space,
477 * then the space isn't escaped.
478 */
479 if((i - 1 - j) % 2 == 0)
480 break;
481 };
482 };
483 return (char *)string + i + 1;
484 }
485
486 /*.......................................................................
487 * Find the length of a potential filename starting from a given
488 * point. This looks forwards from the specified index in a given string,
489 * stopping at the first unescaped space or the end of the line.
490 *
491 * Input:
492 * string const char * The string to search backwards in.
493 * start_from int The index of the first character of the pathname
494 * in string[].
495 * Output:
496 * return char * The pointer to the character that follows
497 * the potential pathname, or NULL on error.
498 */
_pu_end_of_path(const char * string,int start_from)499 char *_pu_end_of_path(const char *string, int start_from)
500 {
501 int c; /* The character being examined */
502 int escaped = 0; /* True when the next character is escaped */
503 int i;
504 /*
505 * Check the arguments.
506 */
507 if(!string || start_from < 0) {
508 errno = EINVAL;
509 return NULL;
510 };
511 /*
512 * Search forwards from the specified index.
513 */
514 for(i=start_from; (c=string[i]) != '\0'; i++) {
515 if(escaped) {
516 escaped = 0;
517 } else if(isspace(c)) {
518 break;
519 } else if(c == '\\') {
520 escaped = 1;
521 };
522 };
523 return (char *)string + i;
524 }
525
526 /*.......................................................................
527 * Return non-zero if the specified path name refers to an existing file.
528 *
529 * Input:
530 * pathname const char * The path to test.
531 * Output:
532 * return int 0 - The file doesn't exist.
533 * 1 - The file does exist.
534 */
_pu_file_exists(const char * pathname)535 int _pu_file_exists(const char *pathname)
536 {
537 struct stat statbuf;
538 return stat(pathname, &statbuf) == 0;
539 }
540
541 #endif /* ifndef WITHOUT_FILE_SYSTEM */
542