2 Copyright (C) 2015-2018 "IoT.bzh"
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 rc = fstatat(sfd, dent->d_name, &st, 0);
197 if (rc == 0 && S_ISDIR(st.st_mode)) {
198 /* directory aka folder */
199 if (dent->d_name[0] == '.' && (dent->d_name[1] == 0 || (dent->d_name[1] == '.' && dent->d_name[2] == 0))) {
200 /* nothing to do for special directories, basic detection, improves if needed */
202 rc = add_folder(container, dent->d_name);
211 /* sort the folders */
212 if (container->count)
213 qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
215 /* build the parents links */
216 i = container->count;
218 f = container->folders[--i];
220 while (j != 0 && f->parent == NULL) {
221 if (f->name[--j] == '-')
222 f->parent = search_folder(container, f->name, j);
230 * Creates a locale root handler and returns it or return NULL
231 * in case of memory depletion.
233 struct locale_root *locale_root_create(int dirfd)
235 struct locale_root *root;
238 root = calloc(1, sizeof * root);
242 if (init_container(&root->container, dirfd) == 0) {
243 root->rootfd = dirfd;
246 for(i = 0 ; i < LRU_COUNT ; i++)
248 root->default_search = NULL;
257 * Creates a locale root handler and returns it or return NULL
258 * in case of memory depletion.
260 struct locale_root *locale_root_create_at(int dirfd, const char *path)
263 struct locale_root *root;
265 fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
269 root = locale_root_create(fd);
277 * Adds a reference to 'root'
279 struct locale_root *locale_root_addref(struct locale_root *root)
281 __atomic_add_fetch(&root->refcount, 1, __ATOMIC_RELAXED);
286 * Drops an internal reference to 'root' and destroys it
287 * if not more referenced
289 static void internal_unref(struct locale_root *root)
291 if (!__atomic_sub_fetch(&root->intcount, 1, __ATOMIC_RELAXED)) {
292 clear_container(&root->container);
299 * Drops a reference to 'root' and destroys it
300 * if not more referenced
302 void locale_root_unref(struct locale_root *root)
306 if (root && !__atomic_sub_fetch(&root->refcount, 1, __ATOMIC_RELAXED)) {
307 /* clear circular references through searchs */
308 for (i = 0 ; i < LRU_COUNT ; i++)
309 locale_search_unref(root->lru[i]);
310 /* finalize if needed */
311 internal_unref(root);
316 * Get the filedescriptor for the 'root' directory
318 int locale_root_get_dirfd(struct locale_root *root)
324 * append, if needed, a folder to the search
326 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
328 struct locale_search_node **p, *n;
330 /* search an existing node */
334 if (n->folder == folder)
340 /* allocates a new node */
341 n = malloc(sizeof *n);
355 * construct a search for the given root and definition of length
357 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
359 struct locale_search *search;
361 struct locale_folder *folder;
362 struct locale_search_node *node;
364 /* allocate the structure */
365 search = malloc(sizeof *search + length);
366 if (search == NULL) {
370 __atomic_add_fetch(&root->intcount, 1, __ATOMIC_RELAXED);
373 search->refcount = 1;
374 memcpy(search->definition, definition, length);
375 search->definition[length] = 0;
377 /* build the search from the definition */
380 while(stop < length && definition[stop] != ',' && definition[stop] != ';')
383 while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
386 folder = search_folder(&root->container, definition, back);
388 if (search_append_folder(search, folder) < 0) {
389 locale_search_unref(search);
395 while(back > 0 && definition[--back] != '-');
396 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
398 while (stop < length && definition[stop] != ',')
400 while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
406 /* fullfills the search */
408 while(node != NULL) {
409 folder = node->folder->parent;
410 if (folder != NULL) {
411 if (search_append_folder(search, node->folder->parent) < 0) {
412 locale_search_unref(search);
423 * Check if a possibly NUUL search matches the definition of length
425 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
427 return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
431 * Get an instance of search for the given root and definition
432 * The flag immediate affects how the search is built.
433 * For example, if the definition is "en-US,en-GB,en", the result differs depending on
435 * when immediate==0 the search becomes "en-US,en-GB,en"
436 * when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
438 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
442 struct locale_search *search;
444 /* normalize the definition */
445 c = definition != NULL ? *definition : 0;
446 while (c == ' ' || c == '\t' || c == ',')
450 c = definition[++length];
452 c = definition[length - 1];
453 while ((c == ' ' || c == '\t' || c == ',') && length)
454 c = definition[--length - 1];
457 /* search lru entry */
459 while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
464 /* use an existing one */
465 search = root->lru[i];
467 /* create a new one */
468 search = create_search(root, definition, length, immediate);
471 /* drop the oldest reference and update i */
472 locale_search_unref(root->lru[--i]);
477 root->lru[i] = root->lru[i - 1];
480 root->lru[0] = search;
482 /* returns a new instance */
483 return locale_search_addref(search);
487 * Adds a reference to the search
489 struct locale_search *locale_search_addref(struct locale_search *search)
491 __atomic_add_fetch(&search->refcount, 1, __ATOMIC_RELAXED);
496 * Removes a reference from the search
498 void locale_search_unref(struct locale_search *search)
500 struct locale_search_node *it, *nx;
502 if (search && !__atomic_sub_fetch(&search->refcount, 1, __ATOMIC_RELAXED)) {
509 internal_unref(search->root);
515 * Set the default search of 'root' to 'search'.
516 * This search is used as fallback when other search are failing.
518 void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
520 struct locale_search *older;
522 assert(search == NULL || search->root == root);
524 older = root->default_search;
525 root->default_search = search ? locale_search_addref(search) : NULL;
526 locale_search_unref(older);
530 * Opens 'filename' for 'search' and 'root'.
532 * Returns the file descriptor as returned by openat
533 * system call or -1 in case of error.
535 static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
537 size_t maxlength, length;
539 struct locale_search_node *node;
540 struct locale_folder *folder;
543 /* check the path and normalize it */
544 filename = subpath_force(filename);
545 if (filename == NULL)
548 /* no creation flags accepted */
549 if ((flags & O_CREAT) != 0)
552 /* search for folders */
553 rootfd = root->rootfd;
554 node = search ? search->head : NULL;
556 /* allocates a buffer big enough */
557 maxlength = root->container.maxlength;
558 length = strlen(filename);
559 if (length > PATH_MAX)
562 /* initialise the end of the buffer */
563 buffer = alloca(length + maxlength + sizeof locales + 1);
564 buffer[maxlength + sizeof locales - 1] = '/';
565 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
567 /* iterate the searched folder */
568 while (node != NULL) {
569 folder = node->folder;
570 p = buffer + maxlength - folder->length;
571 memcpy(p, locales, sizeof locales - 1);
572 memcpy(p + sizeof locales - 1, folder->name, folder->length);
573 fd = openat(rootfd, p, flags);
577 if (node == NULL && search != root->default_search) {
578 search = root->default_search;
579 node = search ? search->head : NULL;
585 return openat(rootfd, filename, flags);
593 * Opens 'filename' after search.
595 * Returns the file descriptor as returned by openat
596 * system call or -1 in case of error.
598 int locale_search_open(struct locale_search *search, const char *filename, int flags)
600 return do_open(search, filename, flags, search->root);
604 * Opens 'filename' for root with default search.
606 * Returns the file descriptor as returned by openat
607 * system call or -1 in case of error.
609 int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
612 struct locale_search *search;
614 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
615 result = do_open(search ? : root->default_search, filename, flags, root);
616 locale_search_unref(search);
621 * Resolves 'filename' for 'root' and 'search'.
623 * returns a copy of the filename after search or NULL if not found.
624 * the returned string MUST be freed by the caller (using free).
626 static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
628 size_t maxlength, length;
630 struct locale_search_node *node;
631 struct locale_folder *folder;
634 /* check the path and normalize it */
635 filename = subpath_force(filename);
636 if (filename == NULL)
639 /* search for folders */
640 rootfd = root->rootfd;
641 node = search ? search->head : NULL;
643 /* allocates a buffer big enough */
644 maxlength = root->container.maxlength;
645 length = strlen(filename);
646 if (length > PATH_MAX)
649 /* initialise the end of the buffer */
650 buffer = alloca(length + maxlength + sizeof locales + 1);
651 buffer[maxlength + sizeof locales - 1] = '/';
652 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
654 /* iterate the searched folder */
655 while (node != NULL) {
656 folder = node->folder;
657 p = buffer + maxlength - folder->length;
658 memcpy(p, locales, sizeof locales - 1);
659 memcpy(p + sizeof locales - 1, folder->name, folder->length);
660 if (0 == faccessat(rootfd, p, F_OK, 0)) {
665 if (node == NULL && search != root->default_search) {
666 search = root->default_search;
667 node = search ? search->head : NULL;
673 if (0 != faccessat(rootfd, filename, F_OK, 0)) {
679 p = strdup(filename);
690 * Resolves 'filename' at 'root' after default search.
692 * returns a copy of the filename after search or NULL if not found.
693 * the returned string MUST be freed by the caller (using free).
695 char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
698 struct locale_search *search;
700 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
701 result = do_resolve(search ? : root->default_search, filename, root);
702 locale_search_unref(search);
707 * Resolves 'filename' after 'search'.
709 * returns a copy of the filename after search or NULL if not found.
710 * the returned string MUST be freed by the caller (using free).
712 char *locale_search_resolve(struct locale_search *search, const char *filename)
714 return do_resolve(search, filename, search->root);
717 #if defined(TEST_locale_root)
718 int main(int ac,char**av)
720 struct locale_root *root = locale_root_create(AT_FDCWD);
721 struct locale_search *search = NULL;
727 locale_search_unref(search);
729 locale_root_unref(root);
730 root = locale_root_create(AT_FDCWD, *av + 1);
732 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
734 printf("root: %s\n", *av + 1);
737 fprintf(stderr, "no valid root for %s\n", *av);
738 } else if (**av == '-' || **av == '+') {
739 locale_search_unref(search);
740 search = locale_root_search(root, *av + 1, **av == '+');
742 fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
744 printf("search: %s\n", *av + 1);
745 } else if (search == NULL) {
746 fprintf(stderr, "no valid search for %s\n", *av);
748 fd = locale_search_open(search, *av, O_RDONLY);
750 fprintf(stderr, "can't open file %s: %m\n", *av);
752 subpath = locale_search_resolve(search, *av);
754 fprintf(stderr, "can't resolve file %s: %m\n", *av);
756 rc = (int)read(fd, buffer, sizeof buffer - 1);
758 fprintf(stderr, "can't read file %s: %m\n", *av);
761 *strchrnul(buffer, '\n') = 0;
762 printf("%s -> %s [%s]\n", *av, subpath, buffer);
771 locale_search_unref(search); search = NULL;
772 locale_root_unref(root); root = NULL;