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"
38 * Implementation of folder based localisation as described here:
40 * https://www.w3.org/TR/widgets/#folder-based-localization
45 static const char locales[] = "locales/";
47 struct locale_folder {
48 struct locale_folder *parent;
53 struct locale_container {
56 struct locale_folder **folders;
59 struct locale_search_node {
60 struct locale_search_node *next;
61 struct locale_folder *folder;
66 struct locale_search {
67 struct locale_root *root;
68 struct locale_search_node *head;
77 struct locale_container container;
78 struct locale_search *lru[LRU_COUNT];
79 struct locale_search *default_search;
83 * Clear a container content
85 static void clear_container(struct locale_container *container)
87 while(container->count)
88 free(container->folders[--container->count]);
89 free(container->folders);
93 * Adds a folder of name for the container
95 static int add_folder(struct locale_container *container, const char *name)
98 struct locale_folder **folders;
100 count = container->count;
101 folders = realloc(container->folders, (1 + count) * sizeof *folders);
102 if (folders != NULL) {
103 container->folders = folders;
104 length = strlen(name);
105 folders[count] = malloc(sizeof **folders + length);
106 if (folders[count] != NULL) {
107 folders[count]->parent = NULL;
108 folders[count]->length = length;
109 memcpy(folders[count]->name, name, 1 + length);
110 container->count = count + 1;
111 if (length > container->maxlength)
112 container->maxlength = length;
116 clear_container(container);
122 * Compare two folders for qsort
124 static int compare_folders_for_qsort(const void *a, const void *b)
126 const struct locale_folder * const *fa = a, * const *fb = b;
127 return strcasecmp((*fa)->name, (*fb)->name);
131 * Search for a folder
133 static struct locale_folder *search_folder(struct locale_container *container, const char *name, size_t length)
135 size_t low, high, mid;
136 struct locale_folder *f;
140 high = container->count;
142 mid = (low + high) >> 1;
143 f = container->folders[mid];
144 c = strncasecmp(f->name, name, length);
145 if (c == 0 && f->name[length] == 0)
158 static int init_container(struct locale_container *container, int dirfd)
165 struct locale_folder *f;
167 /* init the container */
168 container->maxlength = 0;
169 container->count = 0;
170 container->folders = NULL;
172 /* scan the subdirs */
173 sfd = openat(dirfd, locales, O_DIRECTORY|O_RDONLY);
175 return (errno == ENOENT) - 1;
177 /* get the directory data */
178 dir = fdopendir(sfd);
184 /* enumerate the entries */
196 if (dent->d_type == DT_DIR || (dent->d_type == DT_UNKNOWN && fstatat(sfd, dent->d_name, &st, 0) == 0 && S_ISDIR(st.st_mode))) {
197 /* directory aka folder */
198 if (dent->d_name[0] == '.' && (dent->d_name[1] == 0 || (dent->d_name[1] == '.' && dent->d_name[2] == 0))) {
199 /* nothing to do for special directories, basic detection, improves if needed */
201 rc = add_folder(container, dent->d_name);
210 /* sort the folders */
211 if (container->count)
212 qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
214 /* build the parents links */
215 i = container->count;
217 f = container->folders[--i];
219 while (j != 0 && f->parent == NULL) {
220 if (f->name[--j] == '-')
221 f->parent = search_folder(container, f->name, j);
229 * Creates a locale root handler and returns it or return NULL
230 * in case of memory depletion.
232 struct locale_root *locale_root_create(int dirfd)
234 struct locale_root *root;
237 root = calloc(1, sizeof * root);
241 if (init_container(&root->container, dirfd) == 0) {
242 root->rootfd = dirfd;
245 for(i = 0 ; i < LRU_COUNT ; i++)
247 root->default_search = NULL;
256 * Creates a locale root handler and returns it or return NULL
257 * in case of memory depletion.
259 struct locale_root *locale_root_create_at(int dirfd, const char *path)
262 struct locale_root *root;
264 fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
268 root = locale_root_create(fd);
276 * Adds a reference to 'root'
278 struct locale_root *locale_root_addref(struct locale_root *root)
280 __atomic_add_fetch(&root->refcount, 1, __ATOMIC_RELAXED);
285 * Drops an internal reference to 'root' and destroys it
286 * if not more referenced
288 static void internal_unref(struct locale_root *root)
290 if (!__atomic_sub_fetch(&root->intcount, 1, __ATOMIC_RELAXED)) {
291 clear_container(&root->container);
298 * Drops a reference to 'root' and destroys it
299 * if not more referenced
301 void locale_root_unref(struct locale_root *root)
305 if (root && !__atomic_sub_fetch(&root->refcount, 1, __ATOMIC_RELAXED)) {
306 /* clear circular references through searchs */
307 for (i = 0 ; i < LRU_COUNT ; i++)
308 locale_search_unref(root->lru[i]);
309 /* finalize if needed */
310 internal_unref(root);
315 * Get the filedescriptor for the 'root' directory
317 int locale_root_get_dirfd(struct locale_root *root)
323 * append, if needed, a folder to the search
325 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
327 struct locale_search_node **p, *n;
329 /* search an existing node */
333 if (n->folder == folder)
339 /* allocates a new node */
340 n = malloc(sizeof *n);
354 * construct a search for the given root and definition of length
356 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
358 struct locale_search *search;
360 struct locale_folder *folder;
361 struct locale_search_node *node;
363 /* allocate the structure */
364 search = malloc(sizeof *search + length);
365 if (search == NULL) {
369 __atomic_add_fetch(&root->intcount, 1, __ATOMIC_RELAXED);
372 search->refcount = 1;
373 memcpy(search->definition, definition, length);
374 search->definition[length] = 0;
376 /* build the search from the definition */
379 while(stop < length && definition[stop] != ',' && definition[stop] != ';')
382 while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
385 folder = search_folder(&root->container, definition, back);
387 if (search_append_folder(search, folder) < 0) {
388 locale_search_unref(search);
394 while(back > 0 && definition[--back] != '-');
395 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
397 while (stop < length && definition[stop] != ',')
399 while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
405 /* fullfills the search */
407 while(node != NULL) {
408 folder = node->folder->parent;
409 if (folder != NULL) {
410 if (search_append_folder(search, node->folder->parent) < 0) {
411 locale_search_unref(search);
422 * Check if a possibly NUUL search matches the definition of length
424 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
426 return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
430 * Get an instance of search for the given root and definition
431 * The flag immediate affects how the search is built.
432 * For example, if the definition is "en-US,en-GB,en", the result differs depending on
434 * when immediate==0 the search becomes "en-US,en-GB,en"
435 * when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
437 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
441 struct locale_search *search;
443 /* normalize the definition */
444 c = definition != NULL ? *definition : 0;
445 while (c == ' ' || c == '\t' || c == ',')
449 c = definition[++length];
451 c = definition[length - 1];
452 while ((c == ' ' || c == '\t' || c == ',') && length)
453 c = definition[--length - 1];
456 /* search lru entry */
458 while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
463 /* use an existing one */
464 search = root->lru[i];
466 /* create a new one */
467 search = create_search(root, definition, length, immediate);
470 /* drop the oldest reference and update i */
471 locale_search_unref(root->lru[--i]);
476 root->lru[i] = root->lru[i - 1];
479 root->lru[0] = search;
481 /* returns a new instance */
482 return locale_search_addref(search);
486 * Adds a reference to the search
488 struct locale_search *locale_search_addref(struct locale_search *search)
490 __atomic_add_fetch(&search->refcount, 1, __ATOMIC_RELAXED);
495 * Removes a reference from the search
497 void locale_search_unref(struct locale_search *search)
499 struct locale_search_node *it, *nx;
501 if (search && !__atomic_sub_fetch(&search->refcount, 1, __ATOMIC_RELAXED)) {
508 internal_unref(search->root);
514 * Set the default search of 'root' to 'search'.
515 * This search is used as fallback when other search are failing.
517 void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
519 struct locale_search *older;
521 assert(search == NULL || search->root == root);
523 older = root->default_search;
524 root->default_search = search ? locale_search_addref(search) : NULL;
525 locale_search_unref(older);
529 * Opens 'filename' for 'search' and 'root'.
531 * Returns the file descriptor as returned by openat
532 * system call or -1 in case of error.
534 static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
536 size_t maxlength, length;
538 struct locale_search_node *node;
539 struct locale_folder *folder;
542 /* check the path and normalize it */
543 filename = subpath_force(filename);
544 if (filename == NULL)
547 /* no creation flags accepted */
548 if ((flags & O_CREAT) != 0)
551 /* search for folders */
552 rootfd = root->rootfd;
553 node = search ? search->head : NULL;
555 /* allocates a buffer big enough */
556 maxlength = root->container.maxlength;
557 length = strlen(filename);
558 if (length > PATH_MAX)
561 /* initialise the end of the buffer */
562 buffer = alloca(length + maxlength + sizeof locales + 1);
563 buffer[maxlength + sizeof locales - 1] = '/';
564 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
566 /* iterate the searched folder */
567 while (node != NULL) {
568 folder = node->folder;
569 p = buffer + maxlength - folder->length;
570 memcpy(p, locales, sizeof locales - 1);
571 memcpy(p + sizeof locales - 1, folder->name, folder->length);
572 fd = openat(rootfd, p, flags);
576 if (node == NULL && search != root->default_search) {
577 search = root->default_search;
578 node = search ? search->head : NULL;
584 return openat(rootfd, filename, flags);
592 * Opens 'filename' after search.
594 * Returns the file descriptor as returned by openat
595 * system call or -1 in case of error.
597 int locale_search_open(struct locale_search *search, const char *filename, int flags)
599 return do_open(search, filename, flags, search->root);
603 * Opens 'filename' for root with default search.
605 * Returns the file descriptor as returned by openat
606 * system call or -1 in case of error.
608 int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
611 struct locale_search *search;
613 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
614 result = do_open(search ? : root->default_search, filename, flags, root);
615 locale_search_unref(search);
620 * Resolves 'filename' for 'root' and 'search'.
622 * returns a copy of the filename after search or NULL if not found.
623 * the returned string MUST be freed by the caller (using free).
625 static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
627 size_t maxlength, length;
629 struct locale_search_node *node;
630 struct locale_folder *folder;
633 /* check the path and normalize it */
634 filename = subpath_force(filename);
635 if (filename == NULL)
638 /* search for folders */
639 rootfd = root->rootfd;
640 node = search ? search->head : NULL;
642 /* allocates a buffer big enough */
643 maxlength = root->container.maxlength;
644 length = strlen(filename);
645 if (length > PATH_MAX)
648 /* initialise the end of the buffer */
649 buffer = alloca(length + maxlength + sizeof locales + 1);
650 buffer[maxlength + sizeof locales - 1] = '/';
651 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
653 /* iterate the searched folder */
654 while (node != NULL) {
655 folder = node->folder;
656 p = buffer + maxlength - folder->length;
657 memcpy(p, locales, sizeof locales - 1);
658 memcpy(p + sizeof locales - 1, folder->name, folder->length);
659 if (0 == faccessat(rootfd, p, F_OK, 0)) {
664 if (node == NULL && search != root->default_search) {
665 search = root->default_search;
666 node = search ? search->head : NULL;
672 if (0 != faccessat(rootfd, filename, F_OK, 0)) {
678 p = strdup(filename);
689 * Resolves 'filename' at 'root' after default search.
691 * returns a copy of the filename after search or NULL if not found.
692 * the returned string MUST be freed by the caller (using free).
694 char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
697 struct locale_search *search;
699 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
700 result = do_resolve(search ? : root->default_search, filename, root);
701 locale_search_unref(search);
706 * Resolves 'filename' after 'search'.
708 * returns a copy of the filename after search or NULL if not found.
709 * the returned string MUST be freed by the caller (using free).
711 char *locale_search_resolve(struct locale_search *search, const char *filename)
713 return do_resolve(search, filename, search->root);
716 #if defined(TEST_locale_root)
717 int main(int ac,char**av)
719 struct locale_root *root = locale_root_create(AT_FDCWD);
720 struct locale_search *search = NULL;
726 locale_search_unref(search);
728 locale_root_unref(root);
729 root = locale_root_create(AT_FDCWD, *av + 1);
731 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
733 printf("root: %s\n", *av + 1);
736 fprintf(stderr, "no valid root for %s\n", *av);
737 } else if (**av == '-' || **av == '+') {
738 locale_search_unref(search);
739 search = locale_root_search(root, *av + 1, **av == '+');
741 fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
743 printf("search: %s\n", *av + 1);
744 } else if (search == NULL) {
745 fprintf(stderr, "no valid search for %s\n", *av);
747 fd = locale_search_open(search, *av, O_RDONLY);
749 fprintf(stderr, "can't open file %s: %m\n", *av);
751 subpath = locale_search_resolve(search, *av);
753 fprintf(stderr, "can't resolve file %s: %m\n", *av);
755 rc = (int)read(fd, buffer, sizeof buffer - 1);
757 fprintf(stderr, "can't read file %s: %m\n", *av);
760 *strchrnul(buffer, '\n') = 0;
761 printf("%s -> %s [%s]\n", *av, subpath, buffer);
770 locale_search_unref(search); search = NULL;
771 locale_root_unref(root); root = NULL;