1 /*
2  * Copyright (c) 2016-2021, The OSKAR Developers.
3  * See the LICENSE file at the top-level directory of this distribution.
4  */
5 
6 #include "utility/oskar_dir.h"
7 
8 #ifndef OSKAR_OS_WIN
9 #define _BSD_SOURCE
10 #include <dirent.h>
11 #include <errno.h>
12 #include <limits.h>
13 #include <pwd.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17 #else
18 #define WIN32_LEAN_AND_MEAN
19 #include <windows.h>
20 #endif
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #ifdef __cplusplus
27 extern "C" {
28 #endif
29 
oskar_dir_cwd(void)30 char* oskar_dir_cwd(void)
31 {
32     char* str = 0;
33     size_t len = 0;
34 #ifndef OSKAR_OS_WIN
35     do
36     {
37         len += 256;
38         str = (char*) realloc(str, len);
39         if (!str) return 0;
40     }
41     while (getcwd(str, len) == NULL);
42 #else
43     len = GetCurrentDirectory(0, NULL);
44     str = (char*) calloc(len, sizeof(char));
45     if (!str) return 0;
46     GetCurrentDirectory((DWORD) len, str);
47 #endif
48     return str;
49 }
50 
51 
oskar_dir_exists(const char * dir_path)52 int oskar_dir_exists(const char* dir_path)
53 {
54 #ifndef OSKAR_OS_WIN
55     struct stat s;
56     return (!stat(dir_path, &s) && S_ISDIR(s.st_mode)) ? 1 : 0;
57 #else
58     int attrib = GetFileAttributes(dir_path);
59     return (attrib != INVALID_FILE_ATTRIBUTES &&
60             (attrib & FILE_ATTRIBUTE_DIRECTORY));
61 #endif
62 }
63 
64 
oskar_dir_file_exists(const char * dir_path,const char * file_name)65 int oskar_dir_file_exists(const char* dir_path, const char* file_name)
66 {
67     FILE* f = 0;
68     char* path = 0;
69     path = oskar_dir_get_path(dir_path, file_name);
70     f = fopen(path, "rb");
71     if (f)
72     {
73         fclose(f);
74         free(path);
75         return 1;
76     }
77     free(path);
78     return 0;
79 }
80 
81 
oskar_dir_get_home_path(const char * item_name,int * exists)82 char* oskar_dir_get_home_path(const char* item_name, int* exists)
83 {
84     char *path = 0, *home_dir = 0;
85     home_dir = oskar_dir_home();
86     path = oskar_dir_get_path(home_dir, item_name);
87     if (exists) *exists = oskar_dir_file_exists(home_dir, item_name);
88     free(home_dir);
89     return path;
90 }
91 
92 
oskar_dir_get_path(const char * dir_path,const char * item_name)93 char* oskar_dir_get_path(const char* dir_path, const char* item_name)
94 {
95     char* buffer = 0;
96     int buf_len = 0, dir_path_len = 0;
97     dir_path_len = (int) strlen(dir_path);
98     buf_len = 2 + dir_path_len + (int) strlen(item_name);
99     buffer = (char*) calloc(buf_len, sizeof(char));
100     if (!buffer) return 0;
101     if (dir_path[dir_path_len - 1] == oskar_dir_separator())
102     {
103         sprintf(buffer, "%s%s", dir_path, item_name);
104     }
105     else
106     {
107         sprintf(buffer, "%s%c%s", dir_path, oskar_dir_separator(), item_name);
108     }
109     return buffer;
110 }
111 
112 
oskar_dir_home(void)113 char* oskar_dir_home(void)
114 {
115     char *tmp = 0, *home_dir = 0;
116 #ifndef OSKAR_OS_WIN
117     char *buffer = 0;
118     struct passwd pwd, *result = 0;
119     tmp = getenv("HOME");
120     if (!tmp)
121     {
122         long int buf_len = sysconf(_SC_GETPW_R_SIZE_MAX);
123         if (buf_len == -1) buf_len = 16384;
124         buffer = (char*) malloc((size_t) buf_len);
125         if (!buffer) return 0;
126         getpwuid_r(geteuid(), &pwd, buffer, (size_t) buf_len, &result);
127         if (result != NULL) tmp = pwd.pw_dir;
128     }
129     if (tmp)
130     {
131         home_dir = (char*) calloc(1 + strlen(tmp), 1);
132         if (home_dir) strcpy(home_dir, tmp);
133     }
134     free(buffer);
135 #else
136     tmp = getenv("USERPROFILE");
137     if (tmp)
138     {
139         home_dir = (char*) calloc(1 + strlen(tmp), 1);
140         if (home_dir) strcpy(home_dir, tmp);
141     }
142 #endif
143     return home_dir;
144 }
145 
146 
name_cmp(const void * a,const void * b)147 static int name_cmp(const void* a, const void* b)
148 {
149     return strcmp(*(char* const*)a, *(char* const*)b);
150 }
151 
152 
153 /* From http://c-faq.com/lib/regex.html */
match(const char * pattern,const char * str)154 static int match(const char *pattern, const char *str)
155 {
156     switch (*pattern)
157     {
158     case '\0': return !*str;
159     case '*':  return match(pattern+1, str) || (*str && match(pattern, str+1));
160     case '?':  return *str && match(pattern+1, str+1);
161     default:   return *pattern == *str && match(pattern+1, str+1);
162     }
163 }
164 
165 
item(const char * dir_path,const char * name,const char * wildcard,int match_files,int match_dirs,int * i,int num_items,char ** items)166 static void item(const char* dir_path, const char* name, const char* wildcard,
167         int match_files, int match_dirs, int* i, int num_items, char** items)
168 {
169     if (!strcmp(name, ".") || !strcmp(name, "..")) return;
170     if (wildcard && !match(wildcard, name)) return;
171     if (match_files ^ match_dirs)
172     {
173         int rejected = 0;
174         char* item_path = 0;
175         item_path = oskar_dir_get_path(dir_path, name);
176         const int is_dir = oskar_dir_exists(item_path);
177         if ((is_dir && !match_dirs) || (!is_dir && match_dirs)) rejected = 1;
178         free(item_path);
179         if (rejected) return;
180     }
181     if (items && *i < num_items)
182     {
183         items[*i] = (char*) realloc(items[*i], 1 + strlen(name));
184         if (items[*i]) strcpy(items[*i], name);
185     }
186     ++(*i);
187 }
188 
189 
get_items(const char * dir_path,const char * wildcard,int match_files,int match_dirs,int num_items,char ** items)190 static int get_items(const char* dir_path, const char* wildcard,
191         int match_files, int match_dirs, int num_items, char** items)
192 {
193     int i = 0;
194 #ifndef OSKAR_OS_WIN
195     DIR *d = 0;
196     struct dirent *t = 0;
197     if (!(d = opendir(dir_path))) return 0;
198     while ((t = readdir(d)) != 0)
199     {
200         item(dir_path, t->d_name, wildcard, match_files, match_dirs,
201                 &i, num_items, items);
202     }
203     (void) closedir(d);
204 #else
205     WIN32_FIND_DATA f;
206     HANDLE h = 0;
207     char* buffer = 0;
208     buffer = (char*) malloc(3 + strlen(dir_path));
209     if (!buffer) return 0;
210     (void) sprintf(buffer, "%s\\*", dir_path);
211     if ((h = FindFirstFile(buffer, &f)) == INVALID_HANDLE_VALUE)
212     {
213         free(buffer);
214         return 0;
215     }
216     do
217     {
218         item(dir_path, f.cFileName, wildcard, match_files, match_dirs,
219                 &i, num_items, items);
220     }
221     while (FindNextFile(h, &f));
222     FindClose(h);
223     free(buffer);
224 #endif
225     return i;
226 }
227 
228 
oskar_dir_items(const char * dir_path,const char * wildcard,int match_files,int match_dirs,int * num_items,char *** items)229 void oskar_dir_items(const char* dir_path, const char* wildcard,
230         int match_files, int match_dirs, int* num_items, char*** items)
231 {
232     int i = 0, old_num_items = *num_items;
233 
234     /* Count the number of items. */
235     *num_items = get_items(dir_path, wildcard, match_files, match_dirs, 0, 0);
236 
237     /* Get the sorted list of names if required. */
238     if (items)
239     {
240         for (i = *num_items; i < old_num_items; ++i) free((*items)[i]);
241         *items = (char**) realloc(*items, *num_items * sizeof(char**));
242         if (!*items) return;
243         for (i = old_num_items; i < *num_items; ++i) (*items)[i] = 0;
244         (void) get_items(dir_path, wildcard, match_files, match_dirs,
245                 *num_items, *items);
246         qsort(*items, *num_items, sizeof(char*), name_cmp);
247     }
248 }
249 
250 
oskar_dir_leafname(const char * path)251 const char* oskar_dir_leafname(const char* path)
252 {
253     const char* leafname = strrchr(path, (int)oskar_dir_separator());
254     return !leafname ? path : leafname + 1;
255 }
256 
257 
oskar_dir_mkdir(const char * dir_path)258 int oskar_dir_mkdir(const char* dir_path)
259 {
260 #ifndef OSKAR_OS_WIN
261     struct stat s;
262     if (stat(dir_path, &s) != 0)
263     {
264         /* Item does not exist. Try to create the directory. */
265         if (mkdir(dir_path, 0755) != 0 && errno != EEXIST) return 0;
266     }
267     else if (!S_ISDIR(s.st_mode))
268     {
269         return 0;
270     }
271 #else
272     int attrib;
273     if ((attrib = GetFileAttributes(dir_path)) == INVALID_FILE_ATTRIBUTES)
274     {
275         /* Item does not exist. Try to create the directory. */
276         if (!CreateDirectory(dir_path, 0) &&
277                 GetLastError() != ERROR_ALREADY_EXISTS)
278         {
279             return 0;
280         }
281     }
282     else if (!(attrib & FILE_ATTRIBUTE_DIRECTORY))
283     {
284         return 0;
285     }
286 #endif
287     return 1;
288 }
289 
290 
oskar_dir_mkpath(const char * dir_path)291 int oskar_dir_mkpath(const char* dir_path)
292 {
293     char *start = 0, *sep = 0, *dir_path_p = 0;
294     int error = 0;
295 
296     /* Copy the input path string so it can be modified. */
297     const size_t path_len = 1 + strlen(dir_path);
298     dir_path_p = (char*) malloc(path_len);
299     if (!dir_path_p) return 0;
300     memcpy(dir_path_p, dir_path, path_len);
301 
302     /* Loop over directories in path to ensure they all exist. */
303     start = dir_path_p;
304     while (!error && (sep = strchr(start, oskar_dir_separator())))
305     {
306         if (sep != start && *(sep - 1) != ':')
307         {
308             *sep = '\0'; /* Temporarily truncate to ensure this dir exists. */
309             error = !oskar_dir_mkdir(dir_path_p);
310             *sep = oskar_dir_separator(); /* Restore full path. */
311         }
312         start = sep + 1;
313     }
314     free(dir_path_p);
315     return !error ? oskar_dir_mkdir(dir_path) : 0;
316 }
317 
318 
oskar_dir_remove(const char * dir_path)319 int oskar_dir_remove(const char* dir_path)
320 {
321     int error = 0, i = 0, num_items = 0;
322     char **items = 0, *path = 0;
323     if (!oskar_dir_exists(dir_path) ||
324             !strcmp(dir_path, ".") ||
325             !strcmp(dir_path, "./") ||
326             !strcmp(dir_path, ".\\"))
327     {
328         return 0;
329     }
330 
331     /* Get names of all items in the directory. */
332     oskar_dir_items(dir_path, NULL, 1, 1, &num_items, &items);
333     for (i = 0; i < num_items; ++i)
334     {
335         /* Get full path of the item. */
336         path = oskar_dir_get_path(dir_path, items[i]);
337 
338         /* Remove files and directories recursively. */
339         if (!oskar_dir_exists(path))
340         {
341             error = remove(path);
342         }
343         else
344         {
345             error = !oskar_dir_remove(path);
346         }
347         free(path);
348         if (error) break;
349     }
350     for (i = 0; i < num_items; ++i) free(items[i]);
351     free(items);
352     if (error) return 0;
353 
354     /* Remove the empty directory. */
355 #ifdef OSKAR_OS_WIN
356     return RemoveDirectory(dir_path);
357 #else
358     return remove(dir_path) ? 0 : 1;
359 #endif
360 }
361 
362 
oskar_dir_separator(void)363 char oskar_dir_separator(void)
364 {
365 #ifdef OSKAR_OS_WIN
366     return '\\';
367 #else
368     return '/';
369 #endif
370 }
371 
372 
373 #ifdef __cplusplus
374 }
375 #endif
376