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