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];
80 /* a valid subpath is a relative path not looking deeper than root using .. */
81 static int validsubpath(const char *subpath)
85 /* absolute path is not valid */
86 if (subpath[i] == '/')
89 /* inspect the path */
91 switch(subpath[i++]) {
95 if (subpath[i] == '/') {
99 if (subpath[i++] == '.') {
104 if (subpath[i++] == '/') {
110 while(subpath[i] && subpath[i] != '/')
122 * Normalizes and checks the 'subpath'.
123 * Removes any starting '/' and checks that 'subpath'
124 * does not contains sequence of '..' going deeper than
126 * Returns the normalized subpath or NULL in case of
129 static const char *normalsubpath(const char *subpath)
131 while(*subpath == '/')
133 return validsubpath(subpath) ? subpath : NULL;
137 * Clear a container content
139 static void clear_container(struct locale_container *container)
141 while(container->count)
142 free(container->folders[--container->count]);
143 free(container->folders);
147 * Adds a folder of name for the container
149 static int add_folder(struct locale_container *container, const char *name)
151 size_t count, length;
152 struct locale_folder **folders;
154 count = container->count;
155 folders = realloc(container->folders, (1 + count) * sizeof *folders);
156 if (folders != NULL) {
157 container->folders = folders;
158 length = strlen(name);
159 folders[count] = malloc(sizeof **folders + length);
160 if (folders[count] != NULL) {
161 folders[count]->parent = NULL;
162 folders[count]->length = length;
163 memcpy(folders[count]->name, name, 1 + length);
164 container->count = count + 1;
165 if (length > container->maxlength)
166 container->maxlength = length;
170 clear_container(container);
176 * Compare two folders for qsort
178 static int compare_folders_for_qsort(const void *a, const void *b)
180 const struct locale_folder * const *fa = a, * const *fb = b;
181 return strcasecmp((*fa)->name, (*fb)->name);
185 * Search for a folder
187 static struct locale_folder *search_folder(struct locale_container *container, const char *name, size_t length)
189 size_t low, high, mid;
190 struct locale_folder *f;
194 high = container->count;
196 mid = (low + high) >> 1;
197 f = container->folders[mid];
198 c = strncasecmp(f->name, name, length);
199 if (c == 0 && f->name[length] == 0)
212 static int init_container(struct locale_container *container, int dirfd)
216 struct dirent dent, *e;
219 struct locale_folder *f;
221 /* init the container */
222 container->maxlength = 0;
223 container->count = 0;
224 container->folders = NULL;
226 /* scan the subdirs */
227 sfd = openat(dirfd, locales, O_DIRECTORY|O_RDONLY);
229 return (errno == ENOENT) - 1;
231 /* get the directory data */
232 dir = fdopendir(sfd);
238 /* enumerate the entries */
241 rc = readdir_r(dir, &dent, &e);
252 if (dent.d_type == DT_DIR || (dent.d_type == DT_UNKNOWN && fstatat(sfd, dent.d_name, &st, 0) == 0 && S_ISDIR(st.st_mode))) {
253 /* directory aka folder */
254 if (dent.d_name[0] == '.' && (dent.d_name[1] == 0 || (dent.d_name[1] == '.' && dent.d_name[2] == 0))) {
255 /* nothing to do for special directories, basic detection, improves if needed */
257 rc = add_folder(container, dent.d_name);
266 /* sort the folders */
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, const char *pathname)
290 struct locale_root *root;
293 rfd = (pathname && *pathname) ? openat(dirfd, pathname, O_PATH|O_DIRECTORY) : dirfd >= 0 ? dup(dirfd) : dirfd;
294 if (rfd >= 0 || (!(pathname && *pathname) && dirfd < 0)) {
295 root = calloc(1, sizeof * root);
299 if (init_container(&root->container, rfd) == 0) {
303 for(i = 0 ; i < LRU_COUNT ; i++)
315 * Adds a reference to 'root'
317 struct locale_root *locale_root_addref(struct locale_root *root)
324 * Drops a reference to 'root' and destroys it
325 * if not more referenced
327 static void locale_root_unref2(struct locale_root *root)
329 if (!--root->refcount2) {
330 clear_container(&root->container);
337 * Drops a reference to 'root' and destroys it
338 * if not more referenced
340 void locale_root_unref(struct locale_root *root)
344 if (root != NULL && !--root->refcount) {
345 /* clear circular references through searchs */
346 for (i = 0 ; i < LRU_COUNT ; i++)
347 locale_search_unref(root->lru[i]);
348 /* finalize if needed */
349 locale_root_unref2(root);
354 * append, if needed, a folder to the search
356 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
358 struct locale_search_node **p, *n;
360 /* search an existing node */
364 if (n->folder == folder)
370 /* allocates a new node */
371 n = malloc(sizeof *n);
385 * construct a search for the given root and definition of length
387 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
389 struct locale_search *search;
391 struct locale_folder *folder;
392 struct locale_search_node *node;
394 /* allocate the structure */
395 search = malloc(sizeof *search + length);
396 if (search != NULL) {
401 search->refcount = 1;
402 memcpy(search->definition, definition, length);
403 search->definition[length] = 0;
405 /* build the search from the definition */
408 while(stop < length && definition[stop] != ',' && definition[stop] != ';')
411 while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
414 folder = search_folder(&root->container, definition, back);
416 if (search_append_folder(search, folder) < 0) {
417 locale_search_unref(search);
423 while(back > 0 && definition[--back] != '-');
424 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
426 while (stop < length && definition[stop] != ',')
428 while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
434 /* fullfills the search */
436 while(node != NULL) {
437 folder = node->folder->parent;
438 if (folder != NULL) {
439 if (search_append_folder(search, node->folder->parent) < 0) {
440 locale_search_unref(search);
451 * Check if a possibly NUUL search matches the definition of length
453 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
455 return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
459 * Get an instance of search for the given root and definition
460 * The flag immediate affects how the search is built.
461 * For example, if the definition is "en-US,en-GB,en", the result differs depending on
463 * when immediate==0 the search becomes "en-US,en-GB,en"
464 * when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
466 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
470 struct locale_search *search;
472 /* normalize the definition */
473 c = definition != NULL ? *definition : 0;
474 while (c == ' ' || c == '\t' || c == ',')
478 c = definition[++length];
480 c = definition[length - 1];
481 while ((c == ' ' || c == '\t' || c == ',') && length)
482 c = definition[--length - 1];
485 /* search lru entry */
487 while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
492 /* use an existing one */
493 search = root->lru[i];
495 /* create a new one */
496 search = create_search(root, definition, length, immediate);
499 /* drop the oldest reference and update i */
500 locale_search_unref(root->lru[--i]);
505 root->lru[i] = root->lru[i - 1];
508 root->lru[0] = search;
510 /* returns a new instance */
511 return locale_search_addref(search);
515 * Adds a reference to the search
517 struct locale_search *locale_search_addref(struct locale_search *search)
524 * Removes a reference from the search
526 void locale_search_unref(struct locale_search *search)
528 struct locale_search_node *it, *nx;
530 if (search && !--search->refcount) {
537 locale_root_unref2(search->root);
543 * Opens 'filename' after search.
545 * Returns the file descriptor as returned by openat
546 * system call or -1 in case of error.
548 int locale_search_open(struct locale_search *search, const char *filename, int mode)
550 size_t maxlength, length;
552 struct locale_search_node *node;
553 struct locale_folder *folder;
556 /* no creation mode accepted */
557 if ((mode & O_CREAT) != 0)
560 /* check the path and normalize it */
561 filename = normalsubpath(filename);
562 if (filename == NULL)
565 /* search for folders */
566 rootfd = search->root->rootfd;
569 /* allocates a buffer big enough */
570 maxlength = search->root->container.maxlength;
571 length = strlen(filename);
572 if (length > PATH_MAX)
575 /* initialise the end of the buffer */
576 buffer = alloca(length + maxlength + sizeof locales + 1);
577 buffer[maxlength + sizeof locales - 1] = '/';
578 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
580 /* iterate the searched folder */
581 while (node != NULL) {
582 folder = node->folder;
583 p = buffer + maxlength - folder->length;
584 memcpy(p, locales, sizeof locales - 1);
585 memcpy(p + sizeof locales - 1, folder->name, folder->length);
586 fd = openat(rootfd, p, mode);
594 return openat(rootfd, filename, mode);
602 * Resolves 'filename' after search.
604 * returns a copy of the filename after search or NULL if not found.
605 * the returned string MUST be freed by the caller (using free).
607 char *locale_search_resolve(struct locale_search *search, const char *filename)
609 size_t maxlength, length;
611 struct locale_search_node *node;
612 struct locale_folder *folder;
615 /* check the path and normalize it */
616 filename = normalsubpath(filename);
617 if (filename == NULL)
620 /* search for folders */
621 rootfd = search->root->rootfd;
624 /* allocates a buffer big enough */
625 maxlength = search->root->container.maxlength;
626 length = strlen(filename);
627 if (length > PATH_MAX)
630 /* initialise the end of the buffer */
631 buffer = alloca(length + maxlength + sizeof locales + 1);
632 buffer[maxlength + sizeof locales - 1] = '/';
633 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
635 /* iterate the searched folder */
636 while (node != NULL) {
637 folder = node->folder;
638 p = buffer + maxlength - folder->length;
639 memcpy(p, locales, sizeof locales - 1);
640 memcpy(p + sizeof locales - 1, folder->name, folder->length);
641 if (0 == faccessat(rootfd, p, F_OK, 0)) {
650 if (0 != faccessat(rootfd, filename, F_OK, 0)) {
656 p = strdup(filename);
666 #if defined(TEST_locale_root_validsubpath)
668 void t(const char *subpath, int validity) {
669 printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
681 t("a/b/c/../../..",1);
682 t("a/b/c/../../../.",1);
683 t("./..a/././..b/..c/./.././.././../.",1);
684 t("./..a/././..b/..c/./.././.././.././..",0);
685 t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
690 #if defined(TEST_locale_root)
691 int main(int ac,char**av)
693 struct locale_root *root = locale_root_create(AT_FDCWD, NULL);
694 struct locale_search *search = NULL;
700 locale_search_unref(search);
702 locale_root_unref(root);
703 root = locale_root_create(AT_FDCWD, *av + 1);
705 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
707 printf("root: %s\n", *av + 1);
710 fprintf(stderr, "no valid root for %s\n", *av);
711 } else if (**av == '-' || **av == '+') {
712 locale_search_unref(search);
713 search = locale_root_search(root, *av + 1, **av == '+');
715 fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
717 printf("search: %s\n", *av + 1);
718 } else if (search == NULL) {
719 fprintf(stderr, "no valid search for %s\n", *av);
721 fd = locale_search_open(search, *av, O_RDONLY);
723 fprintf(stderr, "can't open file %s: %m\n", *av);
725 subpath = locale_search_resolve(search, *av);
727 fprintf(stderr, "can't resolve file %s: %m\n", *av);
729 rc = (int)read(fd, buffer, sizeof buffer - 1);
731 fprintf(stderr, "can't read file %s: %m\n", *av);
734 *strchrnul(buffer, '\n') = 0;
735 printf("%s -> %s [%s]\n", *av, subpath, buffer);
744 locale_search_unref(search); search = NULL;
745 locale_root_unref(root); root = NULL;