xref: /illumos-gate/usr/src/lib/libtecla/common/pathutil.c (revision 1da57d551424de5a9d469760be7c4b4d4f10a755)
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