1 /** @file path.c File path manipulation.
2
3 @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4
5 @par License
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9
10 1. Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16 <small>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</small>
26 */
27
28 #include "the_Foundation/path.h"
29 #include "the_Foundation/fileinfo.h"
30 #include "the_Foundation/string.h"
31
32 #include <unistd.h>
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36
37 #if defined (iPlatformWindows)
38 # include <direct.h>
39 # define mkdir(path, attr) _mkdir(path)
40 #endif
41
42 #if defined (iPlatformCygwin) || defined (iPlatformMsys)
43 # include <sys/cygwin.h>
44 # define iHaveCygwinPathConversion
45 #endif
46
cwd_Path(void)47 iString *cwd_Path(void) {
48 char *cwd = getcwd(NULL, 0);
49 #if defined (iHaveCygwinPathConversion)
50 iString *str = unixToWindows_Path(cwd);
51 free(cwd);
52 return str;
53 #else
54 if (cwd) {
55 iBlock block;
56 const size_t len = strlen(cwd);
57 initPrealloc_Block(&block, cwd, len, len + 1);
58 iString *d = newBlock_String(&block);
59 deinit_Block(&block);
60 return d;
61 }
62 return new_String();
63 #endif
64 }
65
setCwd_Path(const iString * path)66 iBool setCwd_Path(const iString *path) {
67 return !chdir(cstr_String(path));
68 }
69
home_Path(void)70 iString *home_Path(void) {
71 iString *home = new_String();
72 #if defined (iPlatformMsys) || defined (iPlatformWindows)
73 format_String(home, "%s%s", getenv("HOMEDRIVE"), getenv("HOMEPATH"));
74 #else
75 setCStr_String(home, getenv("HOME"));
76 #endif
77 return home;
78 }
79
isAbsolute_Path(const iString * d)80 iBool isAbsolute_Path(const iString *d) {
81 #if !defined (iPlatformWindows)
82 if (startsWith_String(d, "~")) {
83 return iTrue;
84 }
85 #endif
86 if (startsWith_String(d, iPathSeparator)) {
87 return iTrue;
88 }
89 #if defined (iPlatformWindows) || defined (iHaveCygwinPathConversion)
90 /* Also accept Unix-style paths as absolute since we can convert them. */
91 if (startsWith_String(d, "/")) {
92 return iTrue;
93 }
94 /* Check for drive letters. */
95 if (size_String(d) >= 3) {
96 iStringConstIterator i;
97 init_StringConstIterator(&i, d);
98 const iChar drive = upper_Char(i.value);
99 if (drive < 'A' || drive > 'Z') {
100 return iFalse;
101 }
102 next_StringConstIterator(&i);
103 if (i.value != ':') {
104 return iFalse;
105 }
106 next_StringConstIterator(&i);
107 return i.value == '\\' || i.value == '/';
108 }
109 #endif
110 return iFalse;
111 }
112
makeAbsolute_Path(const iString * d)113 iString *makeAbsolute_Path(const iString *d) {
114 iString *abs;
115 iString *path = copy_String(d);
116 clean_Path(path);
117 if (isAbsolute_Path(path)) {
118 abs = copy_String(path);
119 }
120 else {
121 abs = cwd_Path();
122 append_Path(abs, path);
123 clean_Path(abs);
124 }
125 delete_String(path);
126 return abs;
127 }
128
makeRelative_Path(const iString * d)129 iString *makeRelative_Path(const iString *d) {
130 iString *rel = copy_String(d);
131 iString *cwd = cwd_Path();
132 if (startsWith_String(d, cstr_String(cwd))) {
133 remove_Block(&rel->chars, 0, size_String(cwd));
134 if (startsWith_String(rel, iPathSeparator)) {
135 remove_Block(&rel->chars, 0, 1);
136 }
137 }
138 delete_String(cwd);
139 return rel;
140 }
141
142 #define iPathMaxSegments 128
143
splitSegments_Path_(const iRangecc path,iRangecc * segments,size_t * count,iBool * changed)144 static iBool splitSegments_Path_(const iRangecc path, iRangecc *segments,
145 size_t *count, iBool *changed) {
146 iRangecc seg = iNullRange;
147 while (nextSplit_Rangecc(path, iPathSeparator, &seg)) {
148 if (*count > 0 && size_Range(&seg) == 0) {
149 /* Skip repeated slashes. */
150 *changed = iTrue;
151 continue;
152 }
153 #if defined (iPlatformMsys) || !defined (iPlatformWindows)
154 if (*count == 0 && !iCmpStrRange(seg, "~")) {
155 const iString *home = collect_String(home_Path());
156 if (!isEmpty_String(home)) {
157 if (!splitSegments_Path_(range_String(home), segments, count, changed)) {
158 return iFalse;
159 }
160 *changed = iTrue;
161 continue;
162 }
163 }
164 #endif
165 if (!iCmpStrRange(seg, ".")) {
166 *changed = iTrue;
167 continue; // No change in directory.
168 }
169 if (!iCmpStrRange(seg, "..")) {
170 if (*count > 0 && iCmpStrRange(segments[*count - 1], "..")) {
171 (*count)--; // Go up a directory.
172 *changed = iTrue;
173 continue;
174 }
175 }
176 if (*count == iPathMaxSegments) {
177 iAssert(*count < iPathMaxSegments);
178 return iFalse; // Couldn't clean it.
179 }
180 segments[(*count)++] = seg;
181 }
182 return iTrue;
183 }
184
clean_Path(iString * d)185 void clean_Path(iString *d) {
186 if (isEmpty_String(d)) return;
187 #if defined (iHaveCygwinPathConversion)
188 /* Convert to a Windows path, not forcing it to an absolute path. */ {
189 iString *winPath = unixToWindowsRelative_Path(cstr_String(d));
190 set_String(d, winPath);
191 delete_String(winPath);
192 }
193 #elif defined (iPlatformWindows)
194 /* Use the correct separators. */
195 replace_Block(&d->chars, '/', '\\');
196 #endif
197 iRangecc segments[iPathMaxSegments];
198 size_t count = 0;
199 iBool changed = iFalse;
200 splitSegments_Path_(range_String(d), segments, &count, &changed);
201 /* Recompose the remaining segments. */
202 if (changed) {
203 if (count == 0) {
204 setCStr_String(d, ".");
205 return;
206 }
207 iString cleaned;
208 init_String(&cleaned);
209 for (size_t i = 0; i < count; ++i) {
210 if (i != 0 || (isAbsolute_Path(d)
211 #if defined (iPlatformWindows) || defined (iHaveCygwinPathConversion)
212 && startsWith_String(d, iPathSeparator)
213 #endif
214 )) {
215 appendCStr_String(&cleaned, iPathSeparator);
216 }
217 appendRange_String(&cleaned, segments[i]);
218 }
219 set_String(d, &cleaned);
220 deinit_String(&cleaned);
221 }
222 }
223
append_Path(iString * d,const iString * path)224 void append_Path(iString *d, const iString *path) {
225 if (isAbsolute_Path(path)) {
226 set_String(d, path);
227 }
228 else {
229 if (!endsWith_String(d, iPathSeparator)) {
230 appendCStr_String(d, iPathSeparator);
231 }
232 append_String(d, path);
233 }
234 clean_Path(d);
235 }
236
concat_Path(const iString * d,const iString * path)237 iString *concat_Path(const iString *d, const iString *path) {
238 iString *cat = copy_String(d);
239 append_Path(cat, path);
240 return cat;
241 }
242
concatCStr_Path(const iString * d,const char * path)243 iString *concatCStr_Path(const iString *d, const char *path) {
244 iString p;
245 initCStr_String(&p, path);
246 iString *cat = concat_Path(d, &p);
247 deinit_String(&p);
248 return cat;
249 }
250
concatPath_CStr(const char * dir,const char * path)251 const char *concatPath_CStr(const char *dir, const char *path) {
252 iString d;
253 initCStr_String(&d, dir);
254 iString *cat = concatCStr_Path(&d, path);
255 deinit_String(&d);
256 return cstr_String(collect_String(cat));
257 }
258
mkdir_Path(const iString * path)259 iBool mkdir_Path(const iString *path) {
260 return mkdir(cstr_String(path), 0755) == 0;
261 }
262
rmdir_Path(const iString * path)263 iBool rmdir_Path(const iString *path) {
264 return rmdir(cstr_String(path)) == 0;
265 }
266
baseName_Path(const iString * d)267 iRangecc baseName_Path(const iString *d) {
268 const size_t sep = lastIndexOfCStr_String(d, iPathSeparator);
269 return (iRangecc){ cstr_String(d) + (sep == iInvalidSize ? 0 : (sep + 1)),
270 constEnd_String(d) };
271 }
272
withoutExtension_Path(const iString * d)273 iRangecc withoutExtension_Path(const iString *d) {
274 iRangecc base = baseName_Path(d);
275 while (base.start < base.end && *base.start != '.') {
276 base.start++;
277 }
278 if (isEmpty_Range(&base)) {
279 return range_String(d);
280 }
281 return (iRangecc){ constBegin_String(d), base.start };
282 }
283
dirName_Path(const iString * d)284 iRangecc dirName_Path(const iString *d) {
285 return dirNameSep_Path(d, iPathSeparator);
286 }
287
dirNameSep_Path(const iString * d,const char * separator)288 iRangecc dirNameSep_Path(const iString *d, const char *separator) {
289 const size_t sep = lastIndexOfCStr_String(d, separator);
290 if (sep == iInvalidSize) {
291 static const char *dot = ".";
292 return (iRangecc){ dot, dot + 1 };
293 }
294 return (iRangecc){ cstr_String(d), cstr_String(d) + sep };
295 }
296
makeDirs_Path(const iString * path)297 void makeDirs_Path(const iString *path) {
298 iString *clean = copy_String(path);
299 clean_Path(clean);
300 iString *dir = newRange_String(dirName_Path(clean));
301 if (!fileExists_FileInfo(dir)) {
302 makeDirs_Path(dir);
303 }
304 delete_String(dir);
305 if (!mkdir_Path(clean)) {
306 iDebug("[Path] failed to create directory: %s\n", strerror(errno));
307 }
308 delete_String(clean);
309 }
310
311 #if defined (iHaveCygwinPathConversion)
unixToWindows_(const char * cstr,iBool makeAbsolute)312 static iString *unixToWindows_(const char *cstr, iBool makeAbsolute) {
313 if (!cstr) {
314 return new_String();
315 }
316 uint16_t *winPath = NULL;
317 if (!iCmpStrN(cstr, "~/", 2) || !iCmpStrN(cstr, "~\\", 2)) {
318 /* Expand the home directory. */
319 winPath = cygwin_create_path(CCP_POSIX_TO_WIN_W | CCP_RELATIVE, cstr + 2);
320 iString *conv = newUtf16_String(winPath);
321 iString *str = home_Path();
322 append_Path(str, conv);
323 free(winPath);
324 delete_String(conv);
325 return str;
326 }
327 winPath = cygwin_create_path(CCP_POSIX_TO_WIN_W |
328 (makeAbsolute ? CCP_ABSOLUTE : CCP_RELATIVE),
329 cstr);
330 if (winPath) {
331 iString *str = newUtf16_String(winPath);
332 free(winPath);
333 return str;
334 }
335 return new_String();
336 }
337
unixToWindowsRelative_Path(const char * cstr)338 iString *unixToWindowsRelative_Path(const char *cstr) {
339 return unixToWindows_(cstr, iFalse);
340 }
341
unixToWindows_Path(const char * cstr)342 iString *unixToWindows_Path(const char *cstr) {
343 return unixToWindows_(cstr, iTrue /* absolute */);
344 }
345 #endif
346