Update copyright dates
[src/app-framework-binder.git] / src / locale-root.c
index c87ffc5..0cf14ee 100644 (file)
@@ -1,5 +1,5 @@
 /*
- Copyright 2015 IoT.bzh
+ Copyright (C) 2015-2020 "IoT.bzh"
 
  author: José Bollo <jose.bollo@iot.bzh>
 
@@ -32,6 +32,7 @@
 #include <dirent.h>
 
 #include "locale-root.h"
+#include "subpath.h"
 
 /*
  * Implementation of folder based localisation as described here:
@@ -46,7 +47,7 @@ static const char locales[] = "locales/";
 struct locale_folder {
        struct locale_folder *parent;
        size_t length;
-       char name[1];
+       char name[];
 };
 
 struct locale_container {
@@ -66,73 +67,18 @@ struct locale_search {
        struct locale_root *root;
        struct locale_search_node *head;
        int refcount;
-       char definition[1];
+       char definition[];
 };
 
 struct locale_root {
        int refcount;
-       int refcount2;
+       int intcount;
        int rootfd;
        struct locale_container container;
        struct locale_search *lru[LRU_COUNT];
+       struct locale_search *default_search;
 };
 
-/* a valid subpath is a relative path not looking deeper than root using .. */
-static int validsubpath(const char *subpath)
-{
-       int l = 0, i = 0;
-
-       /* absolute path is not valid */
-       if (subpath[i] == '/')
-               return 0;
-
-       /* inspect the path */
-       while(subpath[i]) {
-               switch(subpath[i++]) {
-               case '.':
-                       if (!subpath[i])
-                               break;
-                       if (subpath[i] == '/') {
-                               i++;
-                               break;
-                       }
-                       if (subpath[i++] == '.') {
-                               if (!subpath[i]) {
-                                       l--;
-                                       break;
-                               }
-                               if (subpath[i++] == '/') {
-                                       l--;
-                                       break;
-                               }
-                       }
-               default:
-                       while(subpath[i] && subpath[i] != '/')
-                               i++;
-                       if (l >= 0)
-                               l++;
-               case '/':
-                       break;
-               }
-       }
-       return l >= 0;
-}
-
-/*
- * Normalizes and checks the 'subpath'.
- * Removes any starting '/' and checks that 'subpath'
- * does not contains sequence of '..' going deeper than
- * root.
- * Returns the normalized subpath or NULL in case of
- * invalid subpath.
- */
-static const char *normalsubpath(const char *subpath)
-{
-       while(*subpath == '/')
-               subpath++;
-       return validsubpath(subpath) ? subpath : NULL;
-}
-
 /*
  * Clear a container content
  */
@@ -156,7 +102,7 @@ static int add_folder(struct locale_container *container, const char *name)
        if (folders != NULL) {
                container->folders = folders;
                length = strlen(name);
-               folders[count] = malloc(sizeof **folders + length);
+               folders[count] = malloc(sizeof **folders + 1 + length);
                if (folders[count] != NULL) {
                        folders[count]->parent = NULL;
                        folders[count]->length = length;
@@ -200,7 +146,7 @@ static struct locale_folder *search_folder(struct locale_container *container, c
                        return f;
                if (c >= 0)
                        high = mid;
-               else    
+               else
                        low = mid + 1;
        }
        return NULL;
@@ -211,9 +157,9 @@ static struct locale_folder *search_folder(struct locale_container *container, c
  */
 static int init_container(struct locale_container *container, int dirfd)
 {
-       int rc, sfd;
+       int rc = 0, sfd;
        DIR *dir;
-       struct dirent dent, *e;
+       struct dirent *dent;
        struct stat st;
        size_t i, j;
        struct locale_folder *f;
@@ -238,23 +184,22 @@ static int init_container(struct locale_container *container, int dirfd)
        /* enumerate the entries */
        for(;;) {
                /* next entry */
-               rc = readdir_r(dir, &dent, &e);
-               if (rc < 0) {
-                       /* error */
-                       closedir(dir);
-                       return rc;
-               }
-               if (e == NULL) {
+               errno = 0;
+               dent = readdir(dir);
+               if (dent == NULL) {
                        /* end of entries */
                        closedir(dir);
+                       if (errno != 0)
+                               return -1;
                        break;
                }
-               if (dent.d_type == DT_DIR || (dent.d_type == DT_UNKNOWN && fstatat(sfd, dent.d_name, &st, 0) == 0 && S_ISDIR(st.st_mode))) {
+               rc = fstatat(sfd, dent->d_name, &st, 0);
+               if (rc == 0 && S_ISDIR(st.st_mode)) {
                        /* directory aka folder */
-                       if (dent.d_name[0] == '.' && (dent.d_name[1] == 0 || (dent.d_name[1] == '.' && dent.d_name[2] == 0))) {
+                       if (dent->d_name[0] == '.' && (dent->d_name[1] == 0 || (dent->d_name[1] == '.' && dent->d_name[2] == 0))) {
                                /* nothing to do for special directories, basic detection, improves if needed */
                        } else {
-                               rc = add_folder(container, dent.d_name);
+                               rc = add_folder(container, dent->d_name);
                                if (rc < 0) {
                                        closedir(dir);
                                        return rc;
@@ -264,7 +209,8 @@ static int init_container(struct locale_container *container, int dirfd)
        }
 
        /* sort the folders */
-       qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
+       if (container->count)
+               qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
 
        /* build the parents links */
        i = container->count;
@@ -284,49 +230,65 @@ static int init_container(struct locale_container *container, int dirfd)
  * Creates a locale root handler and returns it or return NULL
  * in case of memory depletion.
  */
-struct locale_root *locale_root_create(int dirfd, const char *pathname)
+struct locale_root *locale_root_create(int dirfd)
 {
-       int rfd;
        struct locale_root *root;
        size_t i;
 
-       rfd = (pathname && *pathname) ? openat(dirfd, pathname, O_PATH|O_DIRECTORY) : dirfd >= 0 ? dup(dirfd) : dirfd;
-       if (rfd >= 0 || (!(pathname && *pathname) && dirfd < 0)) {
-               root = calloc(1, sizeof * root);
-               if (root == NULL)
-                       errno = ENOMEM;
-               else {
-                       if (init_container(&root->container, rfd) == 0) {
-                               root->rootfd = rfd;
-                               root->refcount = 1;
-                               root->refcount2 = 1;
-                               for(i = 0 ; i < LRU_COUNT ; i++)
-                                       root->lru[i] = NULL;
-                               return root;
-                       }
-                       free(root);
+       root = calloc(1, sizeof * root);
+       if (root == NULL)
+               errno = ENOMEM;
+       else {
+               if (init_container(&root->container, dirfd) == 0) {
+                       root->rootfd = dirfd;
+                       root->refcount = 1;
+                       root->intcount = 1;
+                       for(i = 0 ; i < LRU_COUNT ; i++)
+                               root->lru[i] = NULL;
+                       root->default_search = NULL;
+                       return root;
                }
-               close(rfd);
+               free(root);
        }
        return NULL;
 }
 
+/*
+ * Creates a locale root handler and returns it or return NULL
+ * in case of memory depletion.
+ */
+struct locale_root *locale_root_create_at(int dirfd, const char *path)
+{
+       int fd;
+       struct locale_root *root;
+
+       fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
+       if (fd < 0)
+               root =  NULL;
+       else {
+               root = locale_root_create(fd);
+               if (root == NULL)
+                       close(fd);
+       }
+       return root;
+}
+
 /*
  * Adds a reference to 'root'
  */
 struct locale_root *locale_root_addref(struct locale_root *root)
 {
-       root->refcount++;
+       __atomic_add_fetch(&root->refcount, 1, __ATOMIC_RELAXED);
        return root;
 }
 
 /*
- * Drops a reference to 'root' and destroys it
+ * Drops an internal reference to 'root' and destroys it
  * if not more referenced
  */
-static void locale_root_unref2(struct locale_root *root)
+static void internal_unref(struct locale_root *root)
 {
-       if (!--root->refcount2) {
+       if (!__atomic_sub_fetch(&root->intcount, 1, __ATOMIC_RELAXED)) {
                clear_container(&root->container);
                close(root->rootfd);
                free(root);
@@ -341,15 +303,23 @@ void locale_root_unref(struct locale_root *root)
 {
        size_t i;
 
-       if (root != NULL && !--root->refcount) {
+       if (root && !__atomic_sub_fetch(&root->refcount, 1, __ATOMIC_RELAXED)) {
                /* clear circular references through searchs */
                for (i = 0 ; i < LRU_COUNT ; i++)
                        locale_search_unref(root->lru[i]);
                /* finalize if needed */
-               locale_root_unref2(root);
+               internal_unref(root);
        }
 }
 
+/*
+ * Get the filedescriptor for the 'root' directory
+ */
+int locale_root_get_dirfd(struct locale_root *root)
+{
+       return root->rootfd;
+}
+
 /*
  * append, if needed, a folder to the search
  */
@@ -392,10 +362,12 @@ static struct locale_search *create_search(struct locale_root *root, const char
        struct locale_search_node *node;
 
        /* allocate the structure */
-       search = malloc(sizeof *search + length);
-       if (search != NULL) {
+       search = malloc(sizeof *search + 1 + length);
+       if (search == NULL) {
+               errno = ENOMEM;
+       } else {
                /* init */
-               root->refcount2++;
+               __atomic_add_fetch(&root->intcount, 1, __ATOMIC_RELAXED);
                search->root = root;
                search->head = NULL;
                search->refcount = 1;
@@ -516,7 +488,7 @@ struct locale_search *locale_root_search(struct locale_root *root, const char *d
  */
 struct locale_search *locale_search_addref(struct locale_search *search)
 {
-       search->refcount++;
+       __atomic_add_fetch(&search->refcount, 1, __ATOMIC_RELAXED);
        return search;
 }
 
@@ -527,25 +499,40 @@ void locale_search_unref(struct locale_search *search)
 {
        struct locale_search_node *it, *nx;
 
-       if (search && !--search->refcount) {
+       if (search && !__atomic_sub_fetch(&search->refcount, 1, __ATOMIC_RELAXED)) {
                it = search->head;
                while(it != NULL) {
                        nx = it->next;
                        free(it);
                        it = nx;
                }
-               locale_root_unref2(search->root);
+               internal_unref(search->root);
                free(search);
        }
 }
 
 /*
- * Opens 'filename' after search.
+ * Set the default search of 'root' to 'search'.
+ * This search is used as fallback when other search are failing.
+ */
+void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
+{
+       struct locale_search *older;
+
+       assert(search == NULL || search->root == root);
+
+       older = root->default_search;
+       root->default_search = search ? locale_search_addref(search) : NULL;
+       locale_search_unref(older);
+}
+
+/*
+ * Opens 'filename' for 'search' and 'root'.
  *
  * Returns the file descriptor as returned by openat
  * system call or -1 in case of error.
  */
-int locale_search_open(struct locale_search *search, const char *filename, int mode)
+static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
 {
        size_t maxlength, length;
        char *buffer, *p;
@@ -553,21 +540,21 @@ int locale_search_open(struct locale_search *search, const char *filename, int m
        struct locale_folder *folder;
        int rootfd, fd;
 
-       /* no creation mode accepted */
-       if ((mode & O_CREAT) != 0)
-               goto inval;
-
        /* check the path and normalize it */
-       filename = normalsubpath(filename);
+       filename = subpath_force(filename);
        if (filename == NULL)
                goto inval;
 
+       /* no creation flags accepted */
+       if ((flags & O_CREAT) != 0)
+               goto inval;
+
        /* search for folders */
-       rootfd = search->root->rootfd;
-       node = search->head;
+       rootfd = root->rootfd;
+       node = search ? search->head : NULL;
        if (node != NULL) {
                /* allocates a buffer big enough */
-               maxlength = search->root->container.maxlength;
+               maxlength = root->container.maxlength;
                length = strlen(filename);
                if (length > PATH_MAX)
                        goto inval;
@@ -583,15 +570,19 @@ int locale_search_open(struct locale_search *search, const char *filename, int m
                        p = buffer + maxlength - folder->length;
                        memcpy(p, locales, sizeof locales - 1);
                        memcpy(p + sizeof locales - 1, folder->name, folder->length);
-                       fd = openat(rootfd, p, mode);
+                       fd = openat(rootfd, p, flags);
                        if (fd >= 0)
                                return fd;
                        node = node->next;
+                       if (node == NULL && search != root->default_search) {
+                               search = root->default_search;
+                               node = search ? search->head : NULL;
+                       }
                }
        }
 
-       /* default search */
-       return openat(rootfd, filename, mode);
+       /* root search */
+       return openat(rootfd, filename, flags);
 
 inval:
        errno = EINVAL;
@@ -599,12 +590,40 @@ inval:
 }
 
 /*
- * Resolves 'filename' after search.
+ * Opens 'filename' after search.
+ *
+ * Returns the file descriptor as returned by openat
+ * system call or -1 in case of error.
+ */
+int locale_search_open(struct locale_search *search, const char *filename, int flags)
+{
+       return do_open(search, filename, flags, search->root);
+}
+
+/*
+ * Opens 'filename' for root with default search.
+ *
+ * Returns the file descriptor as returned by openat
+ * system call or -1 in case of error.
+ */
+int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
+{
+       int result;
+       struct locale_search *search;
+
+       search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
+       result = do_open(search ? : root->default_search, filename, flags, root);
+       locale_search_unref(search);
+       return result;
+}
+
+/*
+ * Resolves 'filename' for 'root' and 'search'.
  *
  * returns a copy of the filename after search or NULL if not found.
  * the returned string MUST be freed by the caller (using free).
  */
-char *locale_search_resolve(struct locale_search *search, const char *filename)
+static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
 {
        size_t maxlength, length;
        char *buffer, *p;
@@ -613,16 +632,16 @@ char *locale_search_resolve(struct locale_search *search, const char *filename)
        int rootfd;
 
        /* check the path and normalize it */
-       filename = normalsubpath(filename);
+       filename = subpath_force(filename);
        if (filename == NULL)
                goto inval;
 
        /* search for folders */
-       rootfd = search->root->rootfd;
-       node = search->head;
+       rootfd = root->rootfd;
+       node = search ? search->head : NULL;
        if (node != NULL) {
                /* allocates a buffer big enough */
-               maxlength = search->root->container.maxlength;
+               maxlength = root->container.maxlength;
                length = strlen(filename);
                if (length > PATH_MAX)
                        goto inval;
@@ -643,10 +662,14 @@ char *locale_search_resolve(struct locale_search *search, const char *filename)
                                goto found;
                        }
                        node = node->next;
+                       if (node == NULL && search != root->default_search) {
+                               search = root->default_search;
+                               node = search ? search->head : NULL;
+                       }
                }
        }
 
-       /* default search */
+       /* root search */
        if (0 != faccessat(rootfd, filename, F_OK, 0)) {
                errno = ENOENT;
                return NULL;
@@ -663,34 +686,38 @@ inval:
        return NULL;
 }
 
-#if defined(TEST_locale_root_validsubpath)
-#include <stdio.h>
-void t(const char *subpath, int validity) {
-  printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
+/*
+ * Resolves 'filename' at 'root' after default search.
+ *
+ * returns a copy of the filename after search or NULL if not found.
+ * the returned string MUST be freed by the caller (using free).
+ */
+char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
+{
+       char *result;
+       struct locale_search *search;
+
+       search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
+       result = do_resolve(search ? : root->default_search, filename, root);
+       locale_search_unref(search);
+       return result;
 }
-int main() {
-  t("/",0);
-  t("..",0);
-  t(".",1);
-  t("../a",0);
-  t("a/..",1);
-  t("a/../////..",0);
-  t("a/../b/..",1);
-  t("a/b/c/..",1);
-  t("a/b/c/../..",1);
-  t("a/b/c/../../..",1);
-  t("a/b/c/../../../.",1);
-  t("./..a/././..b/..c/./.././.././../.",1);
-  t("./..a/././..b/..c/./.././.././.././..",0);
-  t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
-  return 0;
+
+/*
+ * Resolves 'filename' after 'search'.
+ *
+ * returns a copy of the filename after search or NULL if not found.
+ * the returned string MUST be freed by the caller (using free).
+ */
+char *locale_search_resolve(struct locale_search *search, const char *filename)
+{
+       return do_resolve(search, filename, search->root);
 }
-#endif
 
 #if defined(TEST_locale_root)
 int main(int ac,char**av)
 {
-       struct locale_root *root = locale_root_create(AT_FDCWD, NULL);
+       struct locale_root *root = locale_root_create(AT_FDCWD);
        struct locale_search *search = NULL;
        int fd, rc, i;
        char buffer[256];