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 if (container->count)
267 qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
269 /* build the parents links */
270 i = container->count;
272 f = container->folders[--i];
274 while (j != 0 && f->parent == NULL) {
275 if (f->name[--j] == '-')
276 f->parent = search_folder(container, f->name, j);
284 * Creates a locale root handler and returns it or return NULL
285 * in case of memory depletion.
287 struct locale_root *locale_root_create(int dirfd)
289 struct locale_root *root;
292 root = calloc(1, sizeof * root);
296 if (init_container(&root->container, dirfd) == 0) {
297 root->rootfd = dirfd;
300 for(i = 0 ; i < LRU_COUNT ; i++)
302 root->default_search = NULL;
311 * Creates a locale root handler and returns it or return NULL
312 * in case of memory depletion.
314 struct locale_root *locale_root_create_at(int dirfd, const char *path)
317 struct locale_root *root;
319 fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
323 root = locale_root_create(fd);
331 * Adds a reference to 'root'
333 struct locale_root *locale_root_addref(struct locale_root *root)
335 __atomic_add_fetch(&root->refcount, 1, __ATOMIC_RELAXED);
340 * Drops an internal reference to 'root' and destroys it
341 * if not more referenced
343 static void internal_unref(struct locale_root *root)
345 if (!__atomic_sub_fetch(&root->intcount, 1, __ATOMIC_RELAXED)) {
346 clear_container(&root->container);
353 * Drops a reference to 'root' and destroys it
354 * if not more referenced
356 void locale_root_unref(struct locale_root *root)
360 if (root && !__atomic_sub_fetch(&root->refcount, 1, __ATOMIC_RELAXED)) {
361 /* clear circular references through searchs */
362 for (i = 0 ; i < LRU_COUNT ; i++)
363 locale_search_unref(root->lru[i]);
364 /* finalize if needed */
365 internal_unref(root);
370 * Get the filedescriptor for the 'root' directory
372 int locale_root_get_dirfd(struct locale_root *root)
378 * append, if needed, a folder to the search
380 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
382 struct locale_search_node **p, *n;
384 /* search an existing node */
388 if (n->folder == folder)
394 /* allocates a new node */
395 n = malloc(sizeof *n);
409 * construct a search for the given root and definition of length
411 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
413 struct locale_search *search;
415 struct locale_folder *folder;
416 struct locale_search_node *node;
418 /* allocate the structure */
419 search = malloc(sizeof *search + length);
420 if (search == NULL) {
424 __atomic_add_fetch(&root->intcount, 1, __ATOMIC_RELAXED);
427 search->refcount = 1;
428 memcpy(search->definition, definition, length);
429 search->definition[length] = 0;
431 /* build the search from the definition */
434 while(stop < length && definition[stop] != ',' && definition[stop] != ';')
437 while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
440 folder = search_folder(&root->container, definition, back);
442 if (search_append_folder(search, folder) < 0) {
443 locale_search_unref(search);
449 while(back > 0 && definition[--back] != '-');
450 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
452 while (stop < length && definition[stop] != ',')
454 while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
460 /* fullfills the search */
462 while(node != NULL) {
463 folder = node->folder->parent;
464 if (folder != NULL) {
465 if (search_append_folder(search, node->folder->parent) < 0) {
466 locale_search_unref(search);
477 * Check if a possibly NUUL search matches the definition of length
479 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
481 return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
485 * Get an instance of search for the given root and definition
486 * The flag immediate affects how the search is built.
487 * For example, if the definition is "en-US,en-GB,en", the result differs depending on
489 * when immediate==0 the search becomes "en-US,en-GB,en"
490 * when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
492 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
496 struct locale_search *search;
498 /* normalize the definition */
499 c = definition != NULL ? *definition : 0;
500 while (c == ' ' || c == '\t' || c == ',')
504 c = definition[++length];
506 c = definition[length - 1];
507 while ((c == ' ' || c == '\t' || c == ',') && length)
508 c = definition[--length - 1];
511 /* search lru entry */
513 while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
518 /* use an existing one */
519 search = root->lru[i];
521 /* create a new one */
522 search = create_search(root, definition, length, immediate);
525 /* drop the oldest reference and update i */
526 locale_search_unref(root->lru[--i]);
531 root->lru[i] = root->lru[i - 1];
534 root->lru[0] = search;
536 /* returns a new instance */
537 return locale_search_addref(search);
541 * Adds a reference to the search
543 struct locale_search *locale_search_addref(struct locale_search *search)
545 __atomic_add_fetch(&search->refcount, 1, __ATOMIC_RELAXED);
550 * Removes a reference from the search
552 void locale_search_unref(struct locale_search *search)
554 struct locale_search_node *it, *nx;
556 if (search && !__atomic_sub_fetch(&search->refcount, 1, __ATOMIC_RELAXED)) {
563 internal_unref(search->root);
569 * Set the default search of 'root' to 'search'.
570 * This search is used as fallback when other search are failing.
572 void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
574 struct locale_search *older;
576 assert(search == NULL || search->root == root);
578 older = root->default_search;
579 root->default_search = search ? locale_search_addref(search) : NULL;
580 locale_search_unref(older);
584 * Opens 'filename' for 'search' and 'root'.
586 * Returns the file descriptor as returned by openat
587 * system call or -1 in case of error.
589 static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
591 size_t maxlength, length;
593 struct locale_search_node *node;
594 struct locale_folder *folder;
597 /* check the path and normalize it */
598 filename = normalsubpath(filename);
599 if (filename == NULL)
602 /* no creation flags accepted */
603 if ((flags & O_CREAT) != 0)
606 /* search for folders */
607 rootfd = root->rootfd;
608 node = search ? search->head : NULL;
610 /* allocates a buffer big enough */
611 maxlength = root->container.maxlength;
612 length = strlen(filename);
613 if (length > PATH_MAX)
616 /* initialise the end of the buffer */
617 buffer = alloca(length + maxlength + sizeof locales + 1);
618 buffer[maxlength + sizeof locales - 1] = '/';
619 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
621 /* iterate the searched folder */
622 while (node != NULL) {
623 folder = node->folder;
624 p = buffer + maxlength - folder->length;
625 memcpy(p, locales, sizeof locales - 1);
626 memcpy(p + sizeof locales - 1, folder->name, folder->length);
627 fd = openat(rootfd, p, flags);
631 if (node == NULL && search != root->default_search) {
632 search = root->default_search;
633 node = search ? search->head : NULL;
639 return openat(rootfd, filename, flags);
647 * Opens 'filename' after search.
649 * Returns the file descriptor as returned by openat
650 * system call or -1 in case of error.
652 int locale_search_open(struct locale_search *search, const char *filename, int flags)
654 return do_open(search, filename, flags, search->root);
658 * Opens 'filename' for root with default search.
660 * Returns the file descriptor as returned by openat
661 * system call or -1 in case of error.
663 int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
666 struct locale_search *search;
668 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
669 result = do_open(search ? : root->default_search, filename, flags, root);
670 locale_search_unref(search);
675 * Resolves 'filename' for 'root' and 'search'.
677 * returns a copy of the filename after search or NULL if not found.
678 * the returned string MUST be freed by the caller (using free).
680 static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
682 size_t maxlength, length;
684 struct locale_search_node *node;
685 struct locale_folder *folder;
688 /* check the path and normalize it */
689 filename = normalsubpath(filename);
690 if (filename == NULL)
693 /* search for folders */
694 rootfd = root->rootfd;
695 node = search ? search->head : NULL;
697 /* allocates a buffer big enough */
698 maxlength = root->container.maxlength;
699 length = strlen(filename);
700 if (length > PATH_MAX)
703 /* initialise the end of the buffer */
704 buffer = alloca(length + maxlength + sizeof locales + 1);
705 buffer[maxlength + sizeof locales - 1] = '/';
706 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
708 /* iterate the searched folder */
709 while (node != NULL) {
710 folder = node->folder;
711 p = buffer + maxlength - folder->length;
712 memcpy(p, locales, sizeof locales - 1);
713 memcpy(p + sizeof locales - 1, folder->name, folder->length);
714 if (0 == faccessat(rootfd, p, F_OK, 0)) {
719 if (node == NULL && search != root->default_search) {
720 search = root->default_search;
721 node = search ? search->head : NULL;
727 if (0 != faccessat(rootfd, filename, F_OK, 0)) {
733 p = strdup(filename);
744 * Resolves 'filename' at 'root' after default search.
746 * returns a copy of the filename after search or NULL if not found.
747 * the returned string MUST be freed by the caller (using free).
749 char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
752 struct locale_search *search;
754 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
755 result = do_resolve(search ? : root->default_search, filename, root);
756 locale_search_unref(search);
761 * Resolves 'filename' after 'search'.
763 * returns a copy of the filename after search or NULL if not found.
764 * the returned string MUST be freed by the caller (using free).
766 char *locale_search_resolve(struct locale_search *search, const char *filename)
768 return do_resolve(search, filename, search->root);
771 #if defined(TEST_locale_root_validsubpath)
773 void t(const char *subpath, int validity) {
774 printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
786 t("a/b/c/../../..",1);
787 t("a/b/c/../../../.",1);
788 t("./..a/././..b/..c/./.././.././../.",1);
789 t("./..a/././..b/..c/./.././.././.././..",0);
790 t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
795 #if defined(TEST_locale_root)
796 int main(int ac,char**av)
798 struct locale_root *root = locale_root_create(AT_FDCWD);
799 struct locale_search *search = NULL;
805 locale_search_unref(search);
807 locale_root_unref(root);
808 root = locale_root_create(AT_FDCWD, *av + 1);
810 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
812 printf("root: %s\n", *av + 1);
815 fprintf(stderr, "no valid root for %s\n", *av);
816 } else if (**av == '-' || **av == '+') {
817 locale_search_unref(search);
818 search = locale_root_search(root, *av + 1, **av == '+');
820 fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
822 printf("search: %s\n", *av + 1);
823 } else if (search == NULL) {
824 fprintf(stderr, "no valid search for %s\n", *av);
826 fd = locale_search_open(search, *av, O_RDONLY);
828 fprintf(stderr, "can't open file %s: %m\n", *av);
830 subpath = locale_search_resolve(search, *av);
832 fprintf(stderr, "can't resolve file %s: %m\n", *av);
834 rc = (int)read(fd, buffer, sizeof buffer - 1);
836 fprintf(stderr, "can't read file %s: %m\n", *av);
839 *strchrnul(buffer, '\n') = 0;
840 printf("%s -> %s [%s]\n", *av, subpath, buffer);
849 locale_search_unref(search); search = NULL;
850 locale_root_unref(root); root = NULL;