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