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)
217 struct dirent dent, *e;
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 */
242 rc = readdir_r(dir, &dent, &e);
253 if (dent.d_type == DT_DIR || (dent.d_type == DT_UNKNOWN && fstatat(sfd, dent.d_name, &st, 0) == 0 && S_ISDIR(st.st_mode))) {
254 /* directory aka folder */
255 if (dent.d_name[0] == '.' && (dent.d_name[1] == 0 || (dent.d_name[1] == '.' && dent.d_name[2] == 0))) {
256 /* nothing to do for special directories, basic detection, improves if needed */
258 rc = add_folder(container, dent.d_name);
267 /* sort the folders */
268 qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
270 /* build the parents links */
271 i = container->count;
273 f = container->folders[--i];
275 while (j != 0 && f->parent == NULL) {
276 if (f->name[--j] == '-')
277 f->parent = search_folder(container, f->name, j);
285 * Creates a locale root handler and returns it or return NULL
286 * in case of memory depletion.
288 struct locale_root *locale_root_create(int dirfd)
290 struct locale_root *root;
293 root = calloc(1, sizeof * root);
297 if (init_container(&root->container, dirfd) == 0) {
298 root->rootfd = dirfd;
301 for(i = 0 ; i < LRU_COUNT ; i++)
303 root->default_search = NULL;
312 * Creates a locale root handler and returns it or return NULL
313 * in case of memory depletion.
315 struct locale_root *locale_root_create_at(int dirfd, const char *path)
318 struct locale_root *root;
320 fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
324 root = locale_root_create(fd);
332 * Adds a reference to 'root'
334 struct locale_root *locale_root_addref(struct locale_root *root)
341 * Drops an internal reference to 'root' and destroys it
342 * if not more referenced
344 static void internal_unref(struct locale_root *root)
346 if (!--root->intcount) {
347 clear_container(&root->container);
354 * Drops a reference to 'root' and destroys it
355 * if not more referenced
357 void locale_root_unref(struct locale_root *root)
361 if (root != NULL && !--root->refcount) {
362 /* clear circular references through searchs */
363 for (i = 0 ; i < LRU_COUNT ; i++)
364 locale_search_unref(root->lru[i]);
365 /* finalize if needed */
366 internal_unref(root);
371 * Get the filedescriptor for the 'root' directory
373 int locale_root_get_dirfd(struct locale_root *root)
379 * append, if needed, a folder to the search
381 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
383 struct locale_search_node **p, *n;
385 /* search an existing node */
389 if (n->folder == folder)
395 /* allocates a new node */
396 n = malloc(sizeof *n);
410 * construct a search for the given root and definition of length
412 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
414 struct locale_search *search;
416 struct locale_folder *folder;
417 struct locale_search_node *node;
419 /* allocate the structure */
420 search = malloc(sizeof *search + length);
421 if (search == NULL) {
428 search->refcount = 1;
429 memcpy(search->definition, definition, length);
430 search->definition[length] = 0;
432 /* build the search from the definition */
435 while(stop < length && definition[stop] != ',' && definition[stop] != ';')
438 while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
441 folder = search_folder(&root->container, definition, back);
443 if (search_append_folder(search, folder) < 0) {
444 locale_search_unref(search);
450 while(back > 0 && definition[--back] != '-');
451 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
453 while (stop < length && definition[stop] != ',')
455 while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
461 /* fullfills the search */
463 while(node != NULL) {
464 folder = node->folder->parent;
465 if (folder != NULL) {
466 if (search_append_folder(search, node->folder->parent) < 0) {
467 locale_search_unref(search);
478 * Check if a possibly NUUL search matches the definition of length
480 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
482 return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
486 * Get an instance of search for the given root and definition
487 * The flag immediate affects how the search is built.
488 * For example, if the definition is "en-US,en-GB,en", the result differs depending on
490 * when immediate==0 the search becomes "en-US,en-GB,en"
491 * when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
493 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
497 struct locale_search *search;
499 /* normalize the definition */
500 c = definition != NULL ? *definition : 0;
501 while (c == ' ' || c == '\t' || c == ',')
505 c = definition[++length];
507 c = definition[length - 1];
508 while ((c == ' ' || c == '\t' || c == ',') && length)
509 c = definition[--length - 1];
512 /* search lru entry */
514 while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
519 /* use an existing one */
520 search = root->lru[i];
522 /* create a new one */
523 search = create_search(root, definition, length, immediate);
526 /* drop the oldest reference and update i */
527 locale_search_unref(root->lru[--i]);
532 root->lru[i] = root->lru[i - 1];
535 root->lru[0] = search;
537 /* returns a new instance */
538 return locale_search_addref(search);
542 * Adds a reference to the search
544 struct locale_search *locale_search_addref(struct locale_search *search)
551 * Removes a reference from the search
553 void locale_search_unref(struct locale_search *search)
555 struct locale_search_node *it, *nx;
557 if (search && !--search->refcount) {
564 internal_unref(search->root);
570 * Set the default search of 'root' to 'search'.
571 * This search is used as fallback when other search are failing.
573 void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
575 struct locale_search *older;
577 assert(search == NULL || search->root == root);
579 older = root->default_search;
580 root->default_search = search ? locale_search_addref(search) : NULL;
581 locale_search_unref(older);
585 * Opens 'filename' for 'search' and 'root'.
587 * Returns the file descriptor as returned by openat
588 * system call or -1 in case of error.
590 static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
592 size_t maxlength, length;
594 struct locale_search_node *node;
595 struct locale_folder *folder;
598 /* check the path and normalize it */
599 filename = normalsubpath(filename);
600 if (filename == NULL)
603 /* no creation flags accepted */
604 if ((flags & O_CREAT) != 0)
607 /* search for folders */
608 rootfd = root->rootfd;
609 node = search ? search->head : NULL;
611 /* allocates a buffer big enough */
612 maxlength = root->container.maxlength;
613 length = strlen(filename);
614 if (length > PATH_MAX)
617 /* initialise the end of the buffer */
618 buffer = alloca(length + maxlength + sizeof locales + 1);
619 buffer[maxlength + sizeof locales - 1] = '/';
620 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
622 /* iterate the searched folder */
623 while (node != NULL) {
624 folder = node->folder;
625 p = buffer + maxlength - folder->length;
626 memcpy(p, locales, sizeof locales - 1);
627 memcpy(p + sizeof locales - 1, folder->name, folder->length);
628 fd = openat(rootfd, p, flags);
632 if (node == NULL && search != root->default_search) {
633 search = root->default_search;
634 node = search ? search->head : NULL;
640 return openat(rootfd, filename, flags);
648 * Opens 'filename' after search.
650 * Returns the file descriptor as returned by openat
651 * system call or -1 in case of error.
653 int locale_search_open(struct locale_search *search, const char *filename, int flags)
655 return do_open(search, filename, flags, search->root);
659 * Opens 'filename' for root with default search.
661 * Returns the file descriptor as returned by openat
662 * system call or -1 in case of error.
664 int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
667 struct locale_search *search;
669 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
670 result = do_open(search ? : root->default_search, filename, flags, root);
671 locale_search_unref(search);
676 * Resolves 'filename' for 'root' and 'search'.
678 * returns a copy of the filename after search or NULL if not found.
679 * the returned string MUST be freed by the caller (using free).
681 static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
683 size_t maxlength, length;
685 struct locale_search_node *node;
686 struct locale_folder *folder;
689 /* check the path and normalize it */
690 filename = normalsubpath(filename);
691 if (filename == NULL)
694 /* search for folders */
695 rootfd = root->rootfd;
696 node = search ? search->head : NULL;
698 /* allocates a buffer big enough */
699 maxlength = root->container.maxlength;
700 length = strlen(filename);
701 if (length > PATH_MAX)
704 /* initialise the end of the buffer */
705 buffer = alloca(length + maxlength + sizeof locales + 1);
706 buffer[maxlength + sizeof locales - 1] = '/';
707 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
709 /* iterate the searched folder */
710 while (node != NULL) {
711 folder = node->folder;
712 p = buffer + maxlength - folder->length;
713 memcpy(p, locales, sizeof locales - 1);
714 memcpy(p + sizeof locales - 1, folder->name, folder->length);
715 if (0 == faccessat(rootfd, p, F_OK, 0)) {
720 if (node == NULL && search != root->default_search) {
721 search = root->default_search;
722 node = search ? search->head : NULL;
728 if (0 != faccessat(rootfd, filename, F_OK, 0)) {
734 p = strdup(filename);
745 * Resolves 'filename' at 'root' after default search.
747 * returns a copy of the filename after search or NULL if not found.
748 * the returned string MUST be freed by the caller (using free).
750 char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
753 struct locale_search *search;
755 search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
756 result = do_resolve(search ? : root->default_search, filename, root);
757 locale_search_unref(search);
762 * Resolves 'filename' after 'search'.
764 * returns a copy of the filename after search or NULL if not found.
765 * the returned string MUST be freed by the caller (using free).
767 char *locale_search_resolve(struct locale_search *search, const char *filename)
769 return do_resolve(search, filename, search->root);
772 #if defined(TEST_locale_root_validsubpath)
774 void t(const char *subpath, int validity) {
775 printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
787 t("a/b/c/../../..",1);
788 t("a/b/c/../../../.",1);
789 t("./..a/././..b/..c/./.././.././../.",1);
790 t("./..a/././..b/..c/./.././.././.././..",0);
791 t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
796 #if defined(TEST_locale_root)
797 int main(int ac,char**av)
799 struct locale_root *root = locale_root_create(AT_FDCWD);
800 struct locale_search *search = NULL;
806 locale_search_unref(search);
808 locale_root_unref(root);
809 root = locale_root_create(AT_FDCWD, *av + 1);
811 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
813 printf("root: %s\n", *av + 1);
816 fprintf(stderr, "no valid root for %s\n", *av);
817 } else if (**av == '-' || **av == '+') {
818 locale_search_unref(search);
819 search = locale_root_search(root, *av + 1, **av == '+');
821 fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
823 printf("search: %s\n", *av + 1);
824 } else if (search == NULL) {
825 fprintf(stderr, "no valid search for %s\n", *av);
827 fd = locale_search_open(search, *av, O_RDONLY);
829 fprintf(stderr, "can't open file %s: %m\n", *av);
831 subpath = locale_search_resolve(search, *av);
833 fprintf(stderr, "can't resolve file %s: %m\n", *av);
835 rc = (int)read(fd, buffer, sizeof buffer - 1);
837 fprintf(stderr, "can't read file %s: %m\n", *av);
840 *strchrnul(buffer, '\n') = 0;
841 printf("%s -> %s [%s]\n", *av, subpath, buffer);
850 locale_search_unref(search); search = NULL;
851 locale_root_unref(root); root = NULL;