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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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