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