1 /* -*- coding: utf-8 -*-
2  * ----------------------------------------------------------------------
3  * Copyright © 2013-2014, RedJack, LLC.
4  * All rights reserved.
5  *
6  * Please see the COPYING file in this distribution for license details.
7  * ----------------------------------------------------------------------
8  */
9 
10 #ifdef __GNU__
11 #define _GNU_SOURCE
12 #endif
13 #include <assert.h>
14 #include <dirent.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 #include <unistd.h>
21 
22 #include "libcork/core/attributes.h"
23 #include "libcork/core/error.h"
24 #include "libcork/core/types.h"
25 #include "libcork/ds/array.h"
26 #include "libcork/ds/buffer.h"
27 #include "libcork/helpers/errors.h"
28 #include "libcork/helpers/posix.h"
29 #include "libcork/helpers/mingw.h"
30 #include "libcork/os/files.h"
31 #include "libcork/os/subprocess.h"
32 
33 
34 #if !defined(CORK_DEBUG_FILES)
35 #define CORK_DEBUG_FILES  0
36 #endif
37 
38 #if CORK_DEBUG_FILES
39 #include <stdio.h>
40 #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
41 #else
42 #define DEBUG(...) /* no debug messages */
43 #endif
44 
45 
46 /*-----------------------------------------------------------------------
47  * Paths
48  */
49 
50 struct cork_path {
51     struct cork_buffer  given;
52 };
53 
54 static struct cork_path *
cork_path_new_internal(const char * str,size_t length)55 cork_path_new_internal(const char *str, size_t length)
56 {
57     struct cork_path  *path = cork_new(struct cork_path);
58     cork_buffer_init(&path->given);
59     if (length == 0) {
60         cork_buffer_ensure_size(&path->given, 16);
61         cork_buffer_set(&path->given, "", 0);
62     } else {
63         cork_buffer_set(&path->given, str, length);
64     }
65     return path;
66 }
67 
68 struct cork_path *
cork_path_new(const char * source)69 cork_path_new(const char *source)
70 {
71     return cork_path_new_internal(source, source == NULL? 0: strlen(source));
72 }
73 
74 struct cork_path *
cork_path_clone(const struct cork_path * other)75 cork_path_clone(const struct cork_path *other)
76 {
77     return cork_path_new_internal(other->given.buf, other->given.size);
78 }
79 
80 void
cork_path_free(struct cork_path * path)81 cork_path_free(struct cork_path *path)
82 {
83     cork_buffer_done(&path->given);
84     cork_delete(struct cork_path, path);
85 }
86 
87 
88 void
cork_path_set(struct cork_path * path,const char * content)89 cork_path_set(struct cork_path *path, const char *content)
90 {
91     if (content == NULL) {
92         cork_buffer_clear(&path->given);
93     } else {
94         cork_buffer_set_string(&path->given, content);
95     }
96 }
97 
98 const char *
cork_path_get(const struct cork_path * path)99 cork_path_get(const struct cork_path *path)
100 {
101     return path->given.buf;
102 }
103 
104 #define cork_path_get(path) ((const char *) (path)->given.buf)
105 #define cork_path_size(path)  ((path)->given.size)
106 #define cork_path_truncate(path, size) \
107     (cork_buffer_truncate(&(path)->given, (size)))
108 
109 
110 int
cork_path_set_cwd(struct cork_path * path)111 cork_path_set_cwd(struct cork_path *path)
112 {
113 #ifdef __GNU__
114     char *dirname = get_current_dir_name();
115     rip_check_posix(dirname);
116     cork_buffer_set(&path->given, dirname, strlen(dirname));
117     free(dirname);
118 #else
119     cork_buffer_ensure_size(&path->given, PATH_MAX);
120     rip_check_posix(getcwd(path->given.buf, PATH_MAX));
121     path->given.size = strlen(path->given.buf);
122 #endif
123     return 0;
124 }
125 
126 struct cork_path *
cork_path_cwd(void)127 cork_path_cwd(void)
128 {
129     struct cork_path  *path = cork_path_new(NULL);
130     ei_check(cork_path_set_cwd(path));
131     return path;
132 
133 error:
134     cork_path_free(path);
135     return NULL;
136 }
137 
138 
139 int
cork_path_set_absolute(struct cork_path * path)140 cork_path_set_absolute(struct cork_path *path)
141 {
142     struct cork_buffer  buf;
143 
144     if (path->given.size > 0 &&
145         cork_buffer_char(&path->given, 0) == '/') {
146         /* The path is already absolute. */
147         return 0;
148     }
149 
150 #ifdef __GNU__
151     char *dirname;
152     dirname = get_current_dir_name();
153     ep_check_posix(dirname);
154     cork_buffer_init(&buf);
155     cork_buffer_set(&buf, dirname, strlen(dirname));
156     free(dirname);
157 #else
158     cork_buffer_init(&buf);
159     cork_buffer_ensure_size(&buf, PATH_MAX);
160     ep_check_posix(getcwd(buf.buf, PATH_MAX));
161     buf.size = strlen(buf.buf);
162 #endif
163     cork_buffer_append(&buf, "/", 1);
164     cork_buffer_append_copy(&buf, &path->given);
165     cork_buffer_done(&path->given);
166     path->given = buf;
167     return 0;
168 
169 error:
170     cork_buffer_done(&buf);
171     return -1;
172 }
173 
174 struct cork_path *
cork_path_absolute(const struct cork_path * other)175 cork_path_absolute(const struct cork_path *other)
176 {
177     struct cork_path  *path = cork_path_clone(other);
178     ei_check(cork_path_set_absolute(path));
179     return path;
180 
181 error:
182     cork_path_free(path);
183     return NULL;
184 }
185 
186 
187 void
cork_path_append(struct cork_path * path,const char * more)188 cork_path_append(struct cork_path *path, const char *more)
189 {
190     if (more == NULL || more[0] == '\0') {
191         return;
192     }
193 
194     if (more[0] == '/') {
195         /* If more starts with a "/", then it's absolute, and should replace
196          * the contents of the current path. */
197         cork_buffer_set_string(&path->given, more);
198     } else {
199         /* Otherwise, more is relative, and should be appended to the current
200          * path.  If the current given path doesn't end in a "/", then we need
201          * to add one to keep the path well-formed. */
202 
203         if (path->given.size > 0 &&
204             cork_buffer_char(&path->given, path->given.size - 1) != '/') {
205             cork_buffer_append(&path->given, "/", 1);
206         }
207 
208         cork_buffer_append_string(&path->given, more);
209     }
210 }
211 
212 struct cork_path *
cork_path_join(const struct cork_path * other,const char * more)213 cork_path_join(const struct cork_path *other, const char *more)
214 {
215     struct cork_path  *path = cork_path_clone(other);
216     cork_path_append(path, more);
217     return path;
218 }
219 
220 void
cork_path_append_path(struct cork_path * path,const struct cork_path * more)221 cork_path_append_path(struct cork_path *path, const struct cork_path *more)
222 {
223     cork_path_append(path, more->given.buf);
224 }
225 
226 struct cork_path *
cork_path_join_path(const struct cork_path * other,const struct cork_path * more)227 cork_path_join_path(const struct cork_path *other, const struct cork_path *more)
228 {
229     struct cork_path  *path = cork_path_clone(other);
230     cork_path_append_path(path, more);
231     return path;
232 }
233 
234 
235 void
cork_path_set_basename(struct cork_path * path)236 cork_path_set_basename(struct cork_path *path)
237 {
238     char  *given = path->given.buf;
239     const char  *last_slash = strrchr(given, '/');
240     if (last_slash != NULL) {
241         size_t  offset = last_slash - given;
242         size_t  basename_length = path->given.size - offset - 1;
243         memmove(given, last_slash + 1, basename_length);
244         given[basename_length] = '\0';
245         path->given.size = basename_length;
246     }
247 }
248 
249 struct cork_path *
cork_path_basename(const struct cork_path * other)250 cork_path_basename(const struct cork_path *other)
251 {
252     struct cork_path  *path = cork_path_clone(other);
253     cork_path_set_basename(path);
254     return path;
255 }
256 
257 
258 void
cork_path_set_dirname(struct cork_path * path)259 cork_path_set_dirname(struct cork_path *path)
260 {
261     const char  *given = path->given.buf;
262     const char  *last_slash = strrchr(given, '/');
263     if (last_slash == NULL) {
264         cork_buffer_clear(&path->given);
265     } else {
266         size_t  offset = last_slash - given;
267         if (offset == 0) {
268             /* A special case for the immediate subdirectories of "/" */
269             cork_buffer_truncate(&path->given, 1);
270         } else {
271             cork_buffer_truncate(&path->given, offset);
272         }
273     }
274 }
275 
276 struct cork_path *
cork_path_dirname(const struct cork_path * other)277 cork_path_dirname(const struct cork_path *other)
278 {
279     struct cork_path  *path = cork_path_clone(other);
280     cork_path_set_dirname(path);
281     return path;
282 }
283 
284 
285 /*-----------------------------------------------------------------------
286  * Lists of paths
287  */
288 
289 struct cork_path_list {
290     cork_array(struct cork_path *)  array;
291     struct cork_buffer  string;
292 };
293 
294 struct cork_path_list *
cork_path_list_new_empty(void)295 cork_path_list_new_empty(void)
296 {
297     struct cork_path_list  *list = cork_new(struct cork_path_list);
298     cork_array_init(&list->array);
299     cork_buffer_init(&list->string);
300     return list;
301 }
302 
303 void
cork_path_list_free(struct cork_path_list * list)304 cork_path_list_free(struct cork_path_list *list)
305 {
306     size_t  i;
307     for (i = 0; i < cork_array_size(&list->array); i++) {
308         struct cork_path  *path = cork_array_at(&list->array, i);
309         cork_path_free(path);
310     }
311     cork_array_done(&list->array);
312     cork_buffer_done(&list->string);
313     cork_delete(struct cork_path_list, list);
314 }
315 
316 const char *
cork_path_list_to_string(const struct cork_path_list * list)317 cork_path_list_to_string(const struct cork_path_list *list)
318 {
319     return list->string.buf;
320 }
321 
322 void
cork_path_list_add(struct cork_path_list * list,struct cork_path * path)323 cork_path_list_add(struct cork_path_list *list, struct cork_path *path)
324 {
325     cork_array_append(&list->array, path);
326     if (cork_array_size(&list->array) > 1) {
327         cork_buffer_append(&list->string, ":", 1);
328     }
329     cork_buffer_append_string(&list->string, cork_path_get(path));
330 }
331 
332 size_t
cork_path_list_size(const struct cork_path_list * list)333 cork_path_list_size(const struct cork_path_list *list)
334 {
335     return cork_array_size(&list->array);
336 }
337 
338 const struct cork_path *
cork_path_list_get(const struct cork_path_list * list,size_t index)339 cork_path_list_get(const struct cork_path_list *list, size_t index)
340 {
341     return cork_array_at(&list->array, index);
342 }
343 
344 static void
cork_path_list_append_string(struct cork_path_list * list,const char * str)345 cork_path_list_append_string(struct cork_path_list *list, const char *str)
346 {
347     struct cork_path  *path;
348     const char  *curr = str;
349     const char  *next;
350 
351     while ((next = strchr(curr, ':')) != NULL) {
352         size_t  size = next - curr;
353         path = cork_path_new_internal(curr, size);
354         cork_path_list_add(list, path);
355         curr = next + 1;
356     }
357 
358     path = cork_path_new(curr);
359     cork_path_list_add(list, path);
360 }
361 
362 struct cork_path_list *
cork_path_list_new(const char * str)363 cork_path_list_new(const char *str)
364 {
365     struct cork_path_list  *list = cork_path_list_new_empty();
366     cork_path_list_append_string(list, str);
367     return list;
368 }
369 
370 
371 /*-----------------------------------------------------------------------
372  * Files
373  */
374 
375 struct cork_file {
376     struct cork_path  *path;
377     struct stat  stat;
378     enum cork_file_type  type;
379     bool  has_stat;
380 };
381 
382 static void
cork_file_init(struct cork_file * file,struct cork_path * path)383 cork_file_init(struct cork_file *file, struct cork_path *path)
384 {
385     file->path = path;
386     file->has_stat = false;
387 }
388 
389 struct cork_file *
cork_file_new(const char * path)390 cork_file_new(const char *path)
391 {
392     return cork_file_new_from_path(cork_path_new(path));
393 }
394 
395 struct cork_file *
cork_file_new_from_path(struct cork_path * path)396 cork_file_new_from_path(struct cork_path *path)
397 {
398     struct cork_file  *file = cork_new(struct cork_file);
399     cork_file_init(file, path);
400     return file;
401 }
402 
403 static void
cork_file_reset(struct cork_file * file)404 cork_file_reset(struct cork_file *file)
405 {
406     file->has_stat = false;
407 }
408 
409 static void
cork_file_done(struct cork_file * file)410 cork_file_done(struct cork_file *file)
411 {
412     cork_path_free(file->path);
413 }
414 
415 void
cork_file_free(struct cork_file * file)416 cork_file_free(struct cork_file *file)
417 {
418     cork_file_done(file);
419     cork_delete(struct cork_file, file);
420 }
421 
422 const struct cork_path *
cork_file_path(struct cork_file * file)423 cork_file_path(struct cork_file *file)
424 {
425     return file->path;
426 }
427 
428 static int
cork_file_stat(struct cork_file * file)429 cork_file_stat(struct cork_file *file)
430 {
431     if (file->has_stat) {
432         return 0;
433     } else {
434         int  rc;
435         rc = stat(cork_path_get(file->path), &file->stat);
436 
437         if (rc == -1) {
438             if (errno == ENOENT || errno == ENOTDIR) {
439                 file->type = CORK_FILE_MISSING;
440                 file->has_stat = true;
441                 return 0;
442             } else {
443                 cork_system_error_set();
444                 return -1;
445             }
446         }
447 
448         if (S_ISREG(file->stat.st_mode)) {
449             file->type = CORK_FILE_REGULAR;
450         } else if (S_ISDIR(file->stat.st_mode)) {
451             file->type = CORK_FILE_DIRECTORY;
452         } else if (S_ISLNK(file->stat.st_mode)) {
453             file->type = CORK_FILE_SYMLINK;
454         } else {
455             file->type = CORK_FILE_UNKNOWN;
456         }
457 
458         file->has_stat = true;
459         return 0;
460     }
461 }
462 
463 int
cork_file_exists(struct cork_file * file,bool * exists)464 cork_file_exists(struct cork_file *file, bool *exists)
465 {
466     rii_check(cork_file_stat(file));
467     *exists = (file->type != CORK_FILE_MISSING);
468     return 0;
469 }
470 
471 int
cork_file_type(struct cork_file * file,enum cork_file_type * type)472 cork_file_type(struct cork_file *file, enum cork_file_type *type)
473 {
474     rii_check(cork_file_stat(file));
475     *type = file->type;
476     return 0;
477 }
478 
479 
480 struct cork_file *
cork_path_list_find_file(const struct cork_path_list * list,const char * rel_path)481 cork_path_list_find_file(const struct cork_path_list *list,
482                          const char *rel_path)
483 {
484     size_t  i;
485     size_t  count = cork_path_list_size(list);
486     struct cork_file  *file;
487 
488     for (i = 0; i < count; i++) {
489         const struct cork_path  *path = cork_path_list_get(list, i);
490         struct cork_path  *joined = cork_path_join(path, rel_path);
491         bool  exists;
492         file = cork_file_new_from_path(joined);
493         ei_check(cork_file_exists(file, &exists));
494         if (exists) {
495             return file;
496         } else {
497             cork_file_free(file);
498         }
499     }
500 
501     cork_error_set_printf
502         (ENOENT, "%s not found in %s",
503          rel_path, cork_path_list_to_string(list));
504     return NULL;
505 
506 error:
507     cork_file_free(file);
508     return NULL;
509 }
510 
511 
512 /*-----------------------------------------------------------------------
513  * Directories
514  */
515 
516 int
cork_file_iterate_directory(struct cork_file * file,cork_file_directory_iterator iterator,void * user_data)517 cork_file_iterate_directory(struct cork_file *file,
518                             cork_file_directory_iterator iterator,
519                             void *user_data)
520 {
521     DIR  *dir = NULL;
522     struct dirent  *entry;
523     size_t  dir_path_size;
524     struct cork_path  *child_path;
525     struct cork_file  child_file;
526 
527     rip_check_posix(dir = opendir(cork_path_get(file->path)));
528     child_path = cork_path_clone(file->path);
529     cork_file_init(&child_file, child_path);
530     dir_path_size = cork_path_size(child_path);
531 
532     errno = 0;
533     while ((entry = readdir(dir)) != NULL) {
534         /* Skip the "." and ".." entries */
535         if (strcmp(entry->d_name, ".") == 0 ||
536             strcmp(entry->d_name, "..") == 0) {
537             continue;
538         }
539 
540         cork_path_append(child_path, entry->d_name);
541         ei_check(cork_file_stat(&child_file));
542 
543         /* If the entry is a subdirectory, recurse into it. */
544         ei_check(iterator(&child_file, entry->d_name, user_data));
545 
546         /* Remove this entry name from the path buffer. */
547         cork_path_truncate(child_path, dir_path_size);
548         cork_file_reset(&child_file);
549 
550         /* We have to reset errno to 0 because of the ambiguous way readdir uses
551          * a return value of NULL.  Other functions may return normally yet set
552          * errno to a non-zero value.  dlopen on Mac OS X is an ogreish example.
553          * Since an error readdir is indicated by returning NULL and setting
554          * errno to indicate the error, then we need to reset it to zero before
555          * each call.  We shall assume, perhaps to our great misery, that
556          * functions within this loop do proper error checking and act
557          * accordingly. */
558         errno = 0;
559     }
560 
561     /* Check errno immediately after the while loop terminates */
562     if (CORK_UNLIKELY(errno != 0)) {
563         cork_system_error_set();
564         goto error;
565     }
566 
567     cork_file_done(&child_file);
568     rii_check_posix(closedir(dir));
569     return 0;
570 
571 error:
572     cork_file_done(&child_file);
573     rii_check_posix(closedir(dir));
574     return -1;
575 }
576 
577 static int
cork_file_mkdir_one(struct cork_file * file,cork_file_mode mode,unsigned int flags)578 cork_file_mkdir_one(struct cork_file *file, cork_file_mode mode,
579                     unsigned int flags)
580 {
581     DEBUG("mkdir %s\n", cork_path_get(file->path));
582 
583     /* First check if the directory already exists. */
584     rii_check(cork_file_stat(file));
585     if (file->type == CORK_FILE_DIRECTORY) {
586         DEBUG("  Already exists!\n");
587         if (!(flags & CORK_FILE_PERMISSIVE)) {
588             cork_system_error_set_explicit(EEXIST);
589             return -1;
590         } else {
591             return 0;
592         }
593     } else if (file->type != CORK_FILE_MISSING) {
594         DEBUG("  Exists and not a directory!\n");
595         cork_system_error_set_explicit(EEXIST);
596         return -1;
597     }
598 
599     /* If the caller asked for a recursive mkdir, then make sure the parent
600      * directory exists. */
601     if (flags & CORK_FILE_RECURSIVE) {
602         struct cork_path  *parent = cork_path_dirname(file->path);
603         DEBUG("  Checking parent %s\n", cork_path_get(parent));
604         if (parent->given.size == 0) {
605             /* There is no parent; we're either at the filesystem root (for an
606              * absolute path) or the current directory (for a relative one).
607              * Either way, we can assume it already exists. */
608             cork_path_free(parent);
609         } else {
610             int  rc;
611             struct cork_file  parent_file;
612             cork_file_init(&parent_file, parent);
613             rc = cork_file_mkdir_one
614                 (&parent_file, mode, flags | CORK_FILE_PERMISSIVE);
615             cork_file_done(&parent_file);
616             rii_check(rc);
617         }
618     }
619 
620     /* Create the directory already! */
621     DEBUG("  Creating %s\n", cork_path_get(file->path));
622     rii_check_posix(mkdir(cork_path_get(file->path), mode));
623     return 0;
624 }
625 
626 int
cork_file_mkdir(struct cork_file * file,cork_file_mode mode,unsigned int flags)627 cork_file_mkdir(struct cork_file *file, cork_file_mode mode,
628                 unsigned int flags)
629 {
630     return cork_file_mkdir_one(file, mode, flags);
631 }
632 
633 static int
cork_file_remove_iterator(struct cork_file * file,const char * rel_name,void * user_data)634 cork_file_remove_iterator(struct cork_file *file, const char *rel_name,
635                           void *user_data)
636 {
637     unsigned int  *flags = user_data;
638     return cork_file_remove(file, *flags);
639 }
640 
641 int
cork_file_remove(struct cork_file * file,unsigned int flags)642 cork_file_remove(struct cork_file *file, unsigned int flags)
643 {
644     DEBUG("rm %s\n", cork_path_get(file->path));
645     rii_check(cork_file_stat(file));
646 
647     if (file->type == CORK_FILE_MISSING) {
648         if (flags & CORK_FILE_PERMISSIVE) {
649             return 0;
650         } else {
651             cork_system_error_set_explicit(ENOENT);
652             return -1;
653         }
654     } else if (file->type == CORK_FILE_DIRECTORY) {
655         if (flags & CORK_FILE_RECURSIVE) {
656             /* The user asked that we delete the contents of the directory
657              * first. */
658             rii_check(cork_file_iterate_directory
659                       (file, cork_file_remove_iterator, &flags));
660         }
661 
662         rii_check_posix(rmdir(cork_path_get(file->path)));
663         return 0;
664     } else {
665         rii_check(unlink(cork_path_get(file->path)));
666         return 0;
667     }
668 }
669 
670 
671 /*-----------------------------------------------------------------------
672  * Lists of files
673  */
674 
675 struct cork_file_list {
676     cork_array(struct cork_file *)  array;
677 };
678 
679 struct cork_file_list *
cork_file_list_new_empty(void)680 cork_file_list_new_empty(void)
681 {
682     struct cork_file_list  *list = cork_new(struct cork_file_list);
683     cork_array_init(&list->array);
684     return list;
685 }
686 
687 void
cork_file_list_free(struct cork_file_list * list)688 cork_file_list_free(struct cork_file_list *list)
689 {
690     size_t  i;
691     for (i = 0; i < cork_array_size(&list->array); i++) {
692         struct cork_file  *file = cork_array_at(&list->array, i);
693         cork_file_free(file);
694     }
695     cork_array_done(&list->array);
696     cork_delete(struct cork_file_list, list);
697 }
698 
699 void
cork_file_list_add(struct cork_file_list * list,struct cork_file * file)700 cork_file_list_add(struct cork_file_list *list, struct cork_file *file)
701 {
702     cork_array_append(&list->array, file);
703 }
704 
705 size_t
cork_file_list_size(struct cork_file_list * list)706 cork_file_list_size(struct cork_file_list *list)
707 {
708     return cork_array_size(&list->array);
709 }
710 
711 struct cork_file *
cork_file_list_get(struct cork_file_list * list,size_t index)712 cork_file_list_get(struct cork_file_list *list, size_t index)
713 {
714     return cork_array_at(&list->array, index);
715 }
716 
717 struct cork_file_list *
cork_file_list_new(struct cork_path_list * path_list)718 cork_file_list_new(struct cork_path_list *path_list)
719 {
720     struct cork_file_list  *list = cork_file_list_new_empty();
721     size_t  count = cork_path_list_size(path_list);
722     size_t  i;
723 
724     for (i = 0; i < count; i++) {
725         const struct cork_path  *path = cork_path_list_get(path_list, i);
726         struct cork_file  *file = cork_file_new(cork_path_get(path));
727         cork_array_append(&list->array, file);
728     }
729 
730     return list;
731 }
732 
733 
734 struct cork_file_list *
cork_path_list_find_files(const struct cork_path_list * path_list,const char * rel_path)735 cork_path_list_find_files(const struct cork_path_list *path_list,
736                           const char *rel_path)
737 {
738     size_t  i;
739     size_t  count = cork_path_list_size(path_list);
740     struct cork_file_list  *list = cork_file_list_new_empty();
741     struct cork_file  *file;
742 
743     for (i = 0; i < count; i++) {
744         const struct cork_path  *path = cork_path_list_get(path_list, i);
745         struct cork_path  *joined = cork_path_join(path, rel_path);
746         bool  exists;
747         file = cork_file_new_from_path(joined);
748         ei_check(cork_file_exists(file, &exists));
749         if (exists) {
750             cork_file_list_add(list, file);
751         } else {
752             cork_file_free(file);
753         }
754     }
755 
756     return list;
757 
758 error:
759     cork_file_list_free(list);
760     cork_file_free(file);
761     return NULL;
762 }
763 
764 
765 /*-----------------------------------------------------------------------
766  * Standard paths and path lists
767  */
768 
769 #define empty_string(str)  ((str) == NULL || (str)[0] == '\0')
770 
771 struct cork_path *
cork_path_home(void)772 cork_path_home(void)
773 {
774     const char  *path = cork_env_get(NULL, "HOME");
775     if (empty_string(path)) {
776         cork_undefined("Cannot determine home directory");
777         return NULL;
778     } else {
779         return cork_path_new(path);
780     }
781 }
782 
783 
784 struct cork_path_list *
cork_path_config_paths(void)785 cork_path_config_paths(void)
786 {
787     struct cork_path_list  *list = cork_path_list_new_empty();
788     const char  *var;
789     struct cork_path  *path;
790 
791     /* The first entry should be the user's configuration directory.  This is
792      * specified by $XDG_CONFIG_HOME, with $HOME/.config as the default. */
793     var = cork_env_get(NULL, "XDG_CONFIG_HOME");
794     if (empty_string(var)) {
795         ep_check(path = cork_path_home());
796         cork_path_append(path, ".config");
797         cork_path_list_add(list, path);
798     } else {
799         path = cork_path_new(var);
800         cork_path_list_add(list, path);
801     }
802 
803     /* The remaining entries should be the system-wide configuration
804      * directories.  These are specified by $XDG_CONFIG_DIRS, with /etc/xdg as
805      * the default. */
806     var = cork_env_get(NULL, "XDG_CONFIG_DIRS");
807     if (empty_string(var)) {
808         path = cork_path_new("/etc/xdg");
809         cork_path_list_add(list, path);
810     } else {
811         cork_path_list_append_string(list, var);
812     }
813 
814     return list;
815 
816 error:
817     cork_path_list_free(list);
818     return NULL;
819 }
820 
821 struct cork_path_list *
cork_path_data_paths(void)822 cork_path_data_paths(void)
823 {
824     struct cork_path_list  *list = cork_path_list_new_empty();
825     const char  *var;
826     struct cork_path  *path;
827 
828     /* The first entry should be the user's data directory.  This is specified
829      * by $XDG_DATA_HOME, with $HOME/.local/share as the default. */
830     var = cork_env_get(NULL, "XDG_DATA_HOME");
831     if (empty_string(var)) {
832         ep_check(path = cork_path_home());
833         cork_path_append(path, ".local/share");
834         cork_path_list_add(list, path);
835     } else {
836         path = cork_path_new(var);
837         cork_path_list_add(list, path);
838     }
839 
840     /* The remaining entries should be the system-wide configuration
841      * directories.  These are specified by $XDG_DATA_DIRS, with
842      * /usr/local/share:/usr/share as the the default. */
843     var = cork_env_get(NULL, "XDG_DATA_DIRS");
844     if (empty_string(var)) {
845         path = cork_path_new("/usr/local/share");
846         cork_path_list_add(list, path);
847         path = cork_path_new("/usr/share");
848         cork_path_list_add(list, path);
849     } else {
850         cork_path_list_append_string(list, var);
851     }
852 
853     return list;
854 
855 error:
856     cork_path_list_free(list);
857     return NULL;
858 }
859 
860 struct cork_path *
cork_path_user_cache_path(void)861 cork_path_user_cache_path(void)
862 {
863     const char  *var;
864     struct cork_path  *path;
865 
866     /* The user's cache directory is specified by $XDG_CACHE_HOME, with
867      * $HOME/.cache as the default. */
868     var = cork_env_get(NULL, "XDG_CACHE_HOME");
869     if (empty_string(var)) {
870         rpp_check(path = cork_path_home());
871         cork_path_append(path, ".cache");
872         return path;
873     } else {
874         return cork_path_new(var);
875     }
876 }
877 
878 struct cork_path *
cork_path_user_runtime_path(void)879 cork_path_user_runtime_path(void)
880 {
881     const char  *var;
882 
883     /* The user's cache directory is specified by $XDG_RUNTIME_DIR, with
884      * no default given by the spec. */
885     var = cork_env_get(NULL, "XDG_RUNTIME_DIR");
886     if (empty_string(var)) {
887         cork_undefined("Cannot determine user-specific runtime directory");
888         return NULL;
889     } else {
890         return cork_path_new(var);
891     }
892 }
893