4 author: José Bollo <jose.bollo@iot.bzh>
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
10 http://www.apache.org/licenses/LICENSE-2.0
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
30 #include <sys/types.h>
34 #include "locale-root.h"
37 * Implementation of folder based localisation as described here:
39 * https://www.w3.org/TR/widgets/#folder-based-localization
44 static const char locales[] = "locales/";
46 struct locale_folder {
47 struct locale_folder *parent;
52 struct locale_container {
55 struct locale_folder **folders;
58 struct locale_search_node {
59 struct locale_search_node *next;
60 struct locale_folder *folder;
65 struct locale_search {
66 struct locale_root *root;
67 struct locale_search_node *head;
76 struct locale_container container;
77 struct locale_search *lru[LRU_COUNT];
78 struct locale_search *default_search;
81 /* a valid subpath is a relative path not looking deeper than root using .. */
82 static int validsubpath(const char *subpath)
86 /* absolute path is not valid */
87 if (subpath[i] == '/')
90 /* inspect the path */
92 switch(subpath[i++]) {
96 if (subpath[i] == '/') {
100 if (subpath[i++] == '.') {
105 if (subpath[i++] == '/') {
111 while(subpath[i] && subpath[i] != '/')
123 * Normalizes and checks the 'subpath'.
124 * Removes any starting '/' and checks that 'subpath'
125 * does not contains sequence of '..' going deeper than
127 * Returns the normalized subpath or NULL in case of
130 static const char *normalsubpath(const char *subpath)
132 while(*subpath == '/')
134 return validsubpath(subpath) ? subpath : NULL;
138 * Clear a container content
140 static void clear_container(struct locale_container *container)
142 while(container->count)
143 free(container->folders[--container->count]);
144 free(container->folders);
148 * Adds a folder of name for the container
150 static int add_folder(struct locale_container *container, const char *name)
152 size_t count, length;
153 struct locale_folder **folders;
155 count = container->count;
156 folders = realloc(container->folders, (1 + count) * sizeof *folders);
157 if (folders != NULL) {
158 container->folders = folders;
159 length = strlen(name);
160 folders[count] = malloc(sizeof **folders + length);
161 if (folders[count] != NULL) {
162 folders[count]->parent = NULL;
163 folders[count]->length = length;
164 memcpy(folders[count]->name, name, 1 + length);
165 container->count = count + 1;
166 if (length > container->maxlength)
167 container->maxlength = length;
171 clear_container(container);
177 * Compare two folders for qsort
179 static int compare_folders_for_qsort(const void *a, const void *b)
181 const struct locale_folder * const *fa = a, * const *fb = b;
182 return strcasecmp((*fa)->name, (*fb)->name);
186 * Search for a folder
188 static struct locale_folder *search_folder(struct locale_container *container, const char *name, size_t length)
190 size_t low, high, mid;
191 struct locale_folder *f;
195 high = container->count;
197 mid = (low + high) >> 1;
198 f = container->folders[mid];
199 c = strncasecmp(f->name, name, length);
200 if (c == 0 && f->name[length] == 0)
213 static int init_container(struct locale_container *container, int dirfd)
220 struct locale_folder *f;
222 /* init the container */
223 container->maxlength = 0;
224 container->count = 0;
225 container->folders = NULL;
227 /* scan the subdirs */
228 sfd = openat(dirfd, locales, O_DIRECTORY|O_RDONLY);
230 return (errno == ENOENT) - 1;
232 /* get the directory data */
233 dir = fdopendir(sfd);
239 /* enumerate the entries */
251 if (dent->d_type == DT_DIR || (dent->d_type == DT_UNKNOWN && fstatat(sfd, dent->d_name, &st, 0) == 0 && S_ISDIR(st.st_mode))) {
252 /* directory aka folder */
253 if (dent->d_name[0] == '.' && (dent->d_name[1] == 0 || (dent->d_name[1] == '.' && dent->d_name[2] == 0))) {
254 /* nothing to do for special directories, basic detection, improves if needed */
256 rc = add_folder(container, dent->d_name);
265 /* sort the folders */
266 qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
268 /* build the parents links */
269 i = container->count;
271 f = container->folders[--i];
273 while (j != 0 && f->parent == NULL) {
274 if (f->name[--j] == '-')
275 f->parent = search_folder(container, f->name, j);
283 * Creates a locale root handler and returns it or return NULL
284 * in case of memory depletion.
286 struct locale_root *locale_root_create(int dirfd)
288 struct locale_root *root;
291 root = calloc(1, sizeof * root);
295 if (init_container(&root->container, dirfd) == 0) {
296 root->rootfd = dirfd;
299 for(i = 0 ; i < LRU_COUNT ; i++)
301 root->default_search = NULL;
310 * Creates a locale root handler and returns it or return NULL
311 * in case of memory depletion.
313 struct locale_root *locale_root_create_at(int dirfd, const char *path)
316 struct locale_root *root;
318 fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
322 root = locale_root_create(fd);
330 * Adds a reference to 'root'
332 struct locale_root *locale_root_addref(struct locale_root *root)
339 * Drops an internal reference to 'root' and destroys it
340 * if not more referenced
342 static void internal_unref(struct locale_root *root)
344 if (!--root->intcount) {
345 clear_container(&root->container);
352 * Drops a reference to 'root' and destroys it
353 * if not more referenced
355 void locale_root_unref(struct locale_root *root)
359 if (root != NULL && !--root->refcount) {
360 /* clear circular references through searchs */
361 for (i = 0 ; i < LRU_COUNT ; i++)
362 locale_search_unref(root->lru[i]);
363 /* finalize if needed */
364 internal_unref(root);
369 * Get the filedescriptor for the 'root' directory
371 int locale_root_get_dirfd(struct locale_root *root)
377 * append, if needed, a folder to the search
379 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
381 struct locale_search_node **p, *n;
383 /* search an existing node */
387 if (n->folder == folder)
393 /* allocates a new node */
394 n = malloc(sizeof *n);
408 * construct a search for the given root and definition of length
410 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
412 struct locale_search *search;
414 struct locale_folder *folder;
415 struct locale_search_node *node;
417 /* allocate the structure */
418 search = malloc(sizeof *search + length);
419 if (search == NULL) {
426 search->refcount = 1;
427 memcpy(search->definition, definition, length);
428 search->definition[length] = 0;
430 /* build the search from the definition */
433 while(stop < length && definition[stop] != ',' && definition[stop] != ';')
436 while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
439 folder = search_folder(&root->container, definition, back);
441 if (search_append_folder(search, folder) < 0) {
442 locale_search_unref(search);
448 while(back > 0 && definition[--back] != '-');
449 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
451 while (stop < length && definition[stop] != ',')
453 while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
459 /* fullfills the search */
461 while(node != NULL) {
462 folder = node->folder->parent;
463 if (folder != NULL) {
464 if (search_append_folder(search, node->folder->parent) < 0) {
465 locale_search_unref(search);
476 * Check if a possibly NUUL search matches the definition of length
478 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
480 return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
484 * Get an instance of search for the given root and definition
485 * The flag immediate affects how the search is built.
486 * For example, if the definition is "en-US,en-GB,en", the result differs depending on
488 * when immediate==0 the search becomes "en-US,en-GB,en"
489 * when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
491 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
495 struct locale_search *search;
497 /* normalize the definition */
498 c = definition != NULL ? *definition : 0;
499 while (c == ' ' || c == '\t' || c == ',')
503 c = definition[++length];
505 c = definition[length - 1];
506 while ((c == ' ' || c == '\t' || c == ',') && length)
507 c = definition[--length - 1];
510 /* search lru entry */
512 while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
517 /* use an existing one */
518 search = root->lru[i];
520 /* create a new one */
521 search = create_search(root, definition, length, immediate);
524 /* drop the oldest reference and update i */
525 locale_search_unref(root->lru[--i]);
530 root->lru[i] = root->lru[i - 1];
533 root->lru[0] = search;
535 /* returns a new instance */
536 return locale_search_addref(search);
540 * Adds a reference to the search
542 struct locale_search *locale_search_addref(struct locale_search *search)
549 * Removes a reference from the search
551 void locale_search_unref(struct locale_search *search)
553 struct locale_search_node *it, *nx;
555 if (search && !--search->refcount) {
562 internal_unref(search->root);
568 * Set the default search of 'root' to 'search'.
569 * This search is used as fallback when other search are failing.
571 void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
573 struct locale_search *older;
575 assert(search == NULL || search->root == root);
577 older = root->default_search;
578 root->default_search = search ? locale_search_addref(search) : NULL;
579 locale_search_unref(older);
583 * Opens 'filename' for 'search' and 'root'.
585 * Returns the file descriptor as returned by openat
586 * system call or -1 in case of error.
588 static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
590 size_t maxlength, length;
592 struct locale_search_node *node;
593 struct locale_folder *folder;
596 /* check the path and normalize it */
597 filename = normalsubpath(filename);
598 if (filename == NULL)
601 /* no creation flags accepted */
602 if ((flags & O_CREAT) != 0)
605 /* search for folders */
606 rootfd = root->rootfd;
607 node = search ? search->head : NULL;
609 /* allocates a buffer big enough */
610 maxlength = root->container.maxlength;
611 length = strlen(filename);
612 if (length > PATH_MAX)
615 /* initialise the end of the buffer */
616 buffer = alloca(length + maxlength + sizeof locales + 1);
617 buffer[maxlength + sizeof locales - 1] = '/';
618 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
620 /* iterate the searched folder */
621 while (node != NULL) {
622 folder = node->folder;
623 p = buffer + maxlength - folder->length;
624 memcpy(p, locales, sizeof locales - 1);
625 memcpy(p + sizeof locales - 1, folder->name, folder->length);
626 fd = openat(rootfd, p, flags);
630 if (node == NULL && search != root->default_search) {
631 search = root->default_search;
632 node = search ? search->head : NULL;
638 return openat(rootfd, filename, flags);
646 * Opens 'filename' after search.
648 * Returns the file descriptor as returned by openat
649 * system call or -1 in case of error.
651 int locale_search_open(struct locale_search *search, const char *filename, int flags)
653 return do_open(search, filename, flags, search->root);
657 * Opens 'filename' for root with default search.
659 * Returns the file descriptor as returned by openat
660 * system call or -1 in case of error.
662 int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
665 struct locale_search *search;
667 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
668 result = do_open(search ? : root->default_search, filename, flags, root);
669 locale_search_unref(search);
674 * Resolves 'filename' for 'root' and 'search'.
676 * returns a copy of the filename after search or NULL if not found.
677 * the returned string MUST be freed by the caller (using free).
679 static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
681 size_t maxlength, length;
683 struct locale_search_node *node;
684 struct locale_folder *folder;
687 /* check the path and normalize it */
688 filename = normalsubpath(filename);
689 if (filename == NULL)
692 /* search for folders */
693 rootfd = root->rootfd;
694 node = search ? search->head : NULL;
696 /* allocates a buffer big enough */
697 maxlength = root->container.maxlength;
698 length = strlen(filename);
699 if (length > PATH_MAX)
702 /* initialise the end of the buffer */
703 buffer = alloca(length + maxlength + sizeof locales + 1);
704 buffer[maxlength + sizeof locales - 1] = '/';
705 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
707 /* iterate the searched folder */
708 while (node != NULL) {
709 folder = node->folder;
710 p = buffer + maxlength - folder->length;
711 memcpy(p, locales, sizeof locales - 1);
712 memcpy(p + sizeof locales - 1, folder->name, folder->length);
713 if (0 == faccessat(rootfd, p, F_OK, 0)) {
718 if (node == NULL && search != root->default_search) {
719 search = root->default_search;
720 node = search ? search->head : NULL;
726 if (0 != faccessat(rootfd, filename, F_OK, 0)) {
732 p = strdup(filename);
743 * Resolves 'filename' at 'root' after default search.
745 * returns a copy of the filename after search or NULL if not found.
746 * the returned string MUST be freed by the caller (using free).
748 char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
751 struct locale_search *search;
753 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
754 result = do_resolve(search ? : root->default_search, filename, root);
755 locale_search_unref(search);
760 * Resolves 'filename' after 'search'.
762 * returns a copy of the filename after search or NULL if not found.
763 * the returned string MUST be freed by the caller (using free).
765 char *locale_search_resolve(struct locale_search *search, const char *filename)
767 return do_resolve(search, filename, search->root);
770 #if defined(TEST_locale_root_validsubpath)
772 void t(const char *subpath, int validity) {
773 printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
785 t("a/b/c/../../..",1);
786 t("a/b/c/../../../.",1);
787 t("./..a/././..b/..c/./.././.././../.",1);
788 t("./..a/././..b/..c/./.././.././.././..",0);
789 t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
794 #if defined(TEST_locale_root)
795 int main(int ac,char**av)
797 struct locale_root *root = locale_root_create(AT_FDCWD);
798 struct locale_search *search = NULL;
804 locale_search_unref(search);
806 locale_root_unref(root);
807 root = locale_root_create(AT_FDCWD, *av + 1);
809 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
811 printf("root: %s\n", *av + 1);
814 fprintf(stderr, "no valid root for %s\n", *av);
815 } else if (**av == '-' || **av == '+') {
816 locale_search_unref(search);
817 search = locale_root_search(root, *av + 1, **av == '+');
819 fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
821 printf("search: %s\n", *av + 1);
822 } else if (search == NULL) {
823 fprintf(stderr, "no valid search for %s\n", *av);
825 fd = locale_search_open(search, *av, O_RDONLY);
827 fprintf(stderr, "can't open file %s: %m\n", *av);
829 subpath = locale_search_resolve(search, *av);
831 fprintf(stderr, "can't resolve file %s: %m\n", *av);
833 rc = (int)read(fd, buffer, sizeof buffer - 1);
835 fprintf(stderr, "can't read file %s: %m\n", *av);
838 *strchrnul(buffer, '\n') = 0;
839 printf("%s -> %s [%s]\n", *av, subpath, buffer);
848 locale_search_unref(search); search = NULL;
849 locale_root_unref(root); root = NULL;