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