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