1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
/* Jody Bruchon's path manipulation code library
*
* Copyright (C) 2014-2023 by Jody Bruchon <jody@jodybruchon.com>
* Released under The MIT License
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "likely_unlikely.h"
#include "libjodycode.h"
/* Collapse dot-dot and single dot path components
* This code MUST be passed a full file pathname (starting with '/') */
extern int jc_collapse_dotdot(char * const path)
{
char *p; /* string copy input */
char *out; /* string copy output */
unsigned int i = 0;
/* Fail if not passed an absolute path */
if (unlikely(*path != '/')) return -1;
p = path; out = path;
while (*p != '\0') {
/* Abort if we're too close to the end of the buffer */
if (unlikely(i >= (PATHBUF_SIZE - 3))) return -2;
/* Skip repeated slashes */
while (*p == '/' && *(p + 1) == '/') {
p++; i++;
}
/* Scan for '/./', '/..', '/.\0' combinations */
if (*p == '/' && *(p + 1) == '.'
&& (*(p + 2) == '.' || *(p + 2) == '/' || *(p + 2) == '\0')) {
/* Check for '../' or terminal '..' */
if (*(p + 2) == '.' && (*(p + 3) == '/' || *(p + 3) == '\0')) {
/* Found a dot-dot; pull everything back to the previous directory */
p += 3; i += 3;
/* If already at root, skip over the dot-dot */
if (i == 0) continue;
/* Don't seek back past the first character */
if ((uintptr_t)out == (uintptr_t)path) continue;
out--;
while (*out != '/') out--;
if (*p == '\0') break;
continue;
} else if (*(p + 2) == '/' || *(p + 2) == '\0') {
/* Found a single dot; seek input ptr past it */
p += 2; i += 2;
if (*p == '\0') break;
continue;
}
/* Fall through: not a dot or dot-dot, just a slash */
}
/* Copy all remaining text */
*out = *p;
p++; out++; i++;
}
/* If only a root slash remains, be sure to keep it */
if ((uintptr_t)out == (uintptr_t)path) {
*out = '/';
out++;
}
/* Output must always be terminated properly */
*out = '\0';
return 0;
}
/* Create a relative symbolic link path for a destination file */
extern int jc_make_relative_link_name(const char * const src,
const char * const dest, char * rel_path)
{
static char p1[PATHBUF_SIZE * 2], p2[PATHBUF_SIZE * 2];
static char *sp, *dp, *ss;
if (unlikely(!src || !dest)) return -1;
/* Get working directory path and prefix to pathnames if needed */
if (*src != '/' || *dest != '/') {
if (!getcwd(p1, PATHBUF_SIZE * 2)) return -2;
*(p1 + (PATHBUF_SIZE * 2) - 1) = '\0';
strncat(p1, "/", PATHBUF_SIZE * 2 - 1);
strncpy(p2, p1, PATHBUF_SIZE * 2);
}
/* If an absolute path is provided, use it as-is */
if (*src == '/') *p1 = '\0';
if (*dest == '/') *p2 = '\0';
/* Concatenate working directory to relative paths */
strncat(p1, src, PATHBUF_SIZE);
strncat(p2, dest, PATHBUF_SIZE);
/* Collapse . and .. path components */
if (unlikely(jc_collapse_dotdot(p1) != 0)) return -3;
if (unlikely(jc_collapse_dotdot(p2) != 0)) return -3;
/* Find where paths differ, remembering each slash along the way */
sp = p1; dp = p2; ss = p1;
while (*sp == *dp && *sp != '\0' && *dp != '\0') {
if (*sp == '/') ss = sp;
sp++; dp++;
}
/* If paths are 100% identical then the files are the same file */
if (*sp == '\0' && *dp == '\0') return 1;
/* Replace dirs in destination path with dot-dot */
while (*dp != '\0') {
if (*dp == '/') {
*rel_path++ = '.'; *rel_path++ = '.'; *rel_path++ = '/';
}
dp++;
}
/* Copy the file name into rel_path and return */
ss++;
while (unlikely(*ss != '\0')) *rel_path++ = *ss++;
/* . and .. dirs at end are invalid */
if (*(rel_path - 1) == '.')
if (*(rel_path - 2) == '/' ||
(*(rel_path - 2) == '.' && *(rel_path - 3) == '/'))
return -4;
if (unlikely(*(rel_path - 1) == '/')) return -4;
*rel_path = '\0';
return 0;
}
|