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 */
59PathName *_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 */
102PathName *_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 */
120char *_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 */
152char *_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 */
222char *_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 */
298char *_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 */
332size_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 */
371int _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 */
394int _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 */
417int _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 */
446char *_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 */
499char *_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 */
535int _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