forked from simonsj/fdupes-jody
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jody_paths.c
152 lines (127 loc) · 4.35 KB
/
jody_paths.c
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/* Jody Bruchon's path manipulation code library
*
* Copyright (C) 2014-2017 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 "jody_paths.h"
/* Collapse dot-dot and single dot path components
* This code MUST be passed a full file pathname (starting with '/') */
extern int 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 (*path != '/') return -1;
p = path; out = path;
while (*p != '\0') {
/* Abort if we're too close to the end of the buffer */
if (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 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 (!src || !dest) goto error_null_param;
/* Get working directory path and prefix to pathnames if needed */
if (*src != '/' || *dest != '/') {
if (!getcwd(p1, PATHBUF_SIZE * 2)) goto error_getcwd;
*(p1 + (PATHBUF_SIZE * 2) - 1) = '\0';
strncat(p1, "/", PATHBUF_SIZE * 2);
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 (collapse_dotdot(p1) != 0) goto error_cdd;
if (collapse_dotdot(p2) != 0) goto error_cdd;
/* 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 (*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) == '/'))
goto error_dir_end;
if (*(rel_path - 1) == '/') goto error_dir_end;
*rel_path = '\0';
return 0;
error_null_param:
fprintf(stderr, "Internal error: get_relative_name has NULL parameter\n");
fprintf(stderr, "Report this as a serious bug to the author\n");
exit(EXIT_FAILURE);
error_getcwd:
fprintf(stderr, "error: couldn't get the current directory\n");
return -1;
error_cdd:
fprintf(stderr, "internal error: collapse_dotdot() call failed\n");
return -2;
error_dir_end:
fprintf(stderr, "internal error: get_relative_name() result has directory at end\n");
return -3;
}