Fix concurrency issue in handling references
[src/app-framework-binder.git] / src / locale-root.c
1 /*
2  Copyright 2015 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
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
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
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.
17 */
18
19 #define _GNU_SOURCE
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <assert.h>
27 #include <limits.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <dirent.h>
33
34 #include "locale-root.h"
35
36 /*
37  * Implementation of folder based localisation as described here:
38  *
39  *    https://www.w3.org/TR/widgets/#folder-based-localization
40  */
41
42 #define LRU_COUNT 3
43
44 static const char locales[] = "locales/";
45
46 struct locale_folder {
47         struct locale_folder *parent;
48         size_t length;
49         char name[1];
50 };
51
52 struct locale_container {
53         size_t maxlength;
54         size_t count;
55         struct locale_folder **folders;
56 };
57
58 struct locale_search_node {
59         struct locale_search_node *next;
60         struct locale_folder *folder;
61 };
62
63 struct locale_root;
64
65 struct locale_search {
66         struct locale_root *root;
67         struct locale_search_node *head;
68         int refcount;
69         char definition[1];
70 };
71
72 struct locale_root {
73         int refcount;
74         int intcount;
75         int rootfd;
76         struct locale_container container;
77         struct locale_search *lru[LRU_COUNT];
78         struct locale_search *default_search;
79 };
80
81 /* a valid subpath is a relative path not looking deeper than root using .. */
82 static int validsubpath(const char *subpath)
83 {
84         int l = 0, i = 0;
85
86         /* absolute path is not valid */
87         if (subpath[i] == '/')
88                 return 0;
89
90         /* inspect the path */
91         while(subpath[i]) {
92                 switch(subpath[i++]) {
93                 case '.':
94                         if (!subpath[i])
95                                 break;
96                         if (subpath[i] == '/') {
97                                 i++;
98                                 break;
99                         }
100                         if (subpath[i++] == '.') {
101                                 if (!subpath[i]) {
102                                         l--;
103                                         break;
104                                 }
105                                 if (subpath[i++] == '/') {
106                                         l--;
107                                         break;
108                                 }
109                         }
110                 default:
111                         while(subpath[i] && subpath[i] != '/')
112                                 i++;
113                         if (l >= 0)
114                                 l++;
115                 case '/':
116                         break;
117                 }
118         }
119         return l >= 0;
120 }
121
122 /*
123  * Normalizes and checks the 'subpath'.
124  * Removes any starting '/' and checks that 'subpath'
125  * does not contains sequence of '..' going deeper than
126  * root.
127  * Returns the normalized subpath or NULL in case of
128  * invalid subpath.
129  */
130 static const char *normalsubpath(const char *subpath)
131 {
132         while(*subpath == '/')
133                 subpath++;
134         return validsubpath(subpath) ? subpath : NULL;
135 }
136
137 /*
138  * Clear a container content
139  */
140 static void clear_container(struct locale_container *container)
141 {
142         while(container->count)
143                 free(container->folders[--container->count]);
144         free(container->folders);
145 }
146
147 /*
148  * Adds a folder of name for the container
149  */
150 static int add_folder(struct locale_container *container, const char *name)
151 {
152         size_t count, length;
153         struct locale_folder **folders;
154
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;
168                         return 0;
169                 }
170         }
171         clear_container(container);
172         errno = ENOMEM;
173         return -1;
174 }
175
176 /*
177  * Compare two folders for qsort
178  */
179 static int compare_folders_for_qsort(const void *a, const void *b)
180 {
181         const struct locale_folder * const *fa = a, * const *fb = b;
182         return strcasecmp((*fa)->name, (*fb)->name);
183 }
184
185 /*
186  * Search for a folder
187  */
188 static struct locale_folder *search_folder(struct locale_container *container, const char *name, size_t length)
189 {
190         size_t low, high, mid;
191         struct locale_folder *f;
192         int c;
193
194         low = 0;
195         high = container->count;
196         while (low < high) {
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)
201                         return f;
202                 if (c >= 0)
203                         high = mid;
204                 else    
205                         low = mid + 1;
206         }
207         return NULL;
208 }
209
210 /*
211  * Init a container
212  */
213 static int init_container(struct locale_container *container, int dirfd)
214 {
215         int rc, sfd;
216         DIR *dir;
217         struct dirent *dent;
218         struct stat st;
219         size_t i, j;
220         struct locale_folder *f;
221
222         /* init the container */
223         container->maxlength = 0;
224         container->count = 0;
225         container->folders = NULL;
226
227         /* scan the subdirs */
228         sfd = openat(dirfd, locales, O_DIRECTORY|O_RDONLY);
229         if (sfd == -1)
230                 return (errno == ENOENT) - 1;
231
232         /* get the directory data */
233         dir = fdopendir(sfd);
234         if (dir == NULL) {
235                 close(sfd);
236                 return -1;
237         }
238
239         /* enumerate the entries */
240         for(;;) {
241                 /* next entry */
242                 errno = 0;
243                 dent = readdir(dir);
244                 if (dent == NULL) {
245                         /* end of entries */
246                         closedir(dir);
247                         if (errno != 0)
248                                 return -1;
249                         break;
250                 }
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 */
255                         } else {
256                                 rc = add_folder(container, dent->d_name);
257                                 if (rc < 0) {
258                                         closedir(dir);
259                                         return rc;
260                                 }
261                         }
262                 }
263         }
264
265         /* sort the folders */
266         qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
267
268         /* build the parents links */
269         i = container->count;
270         while (i != 0) {
271                 f = container->folders[--i];
272                 j = strlen(f->name);
273                 while (j != 0 && f->parent == NULL) {
274                         if (f->name[--j] == '-')
275                                 f->parent = search_folder(container, f->name, j);
276                 }
277         }
278
279         return rc;
280 }
281
282 /*
283  * Creates a locale root handler and returns it or return NULL
284  * in case of memory depletion.
285  */
286 struct locale_root *locale_root_create(int dirfd)
287 {
288         struct locale_root *root;
289         size_t i;
290
291         root = calloc(1, sizeof * root);
292         if (root == NULL)
293                 errno = ENOMEM;
294         else {
295                 if (init_container(&root->container, dirfd) == 0) {
296                         root->rootfd = dirfd;
297                         root->refcount = 1;
298                         root->intcount = 1;
299                         for(i = 0 ; i < LRU_COUNT ; i++)
300                                 root->lru[i] = NULL;
301                         root->default_search = NULL;
302                         return root;
303                 }
304                 free(root);
305         }
306         return NULL;
307 }
308
309 /*
310  * Creates a locale root handler and returns it or return NULL
311  * in case of memory depletion.
312  */
313 struct locale_root *locale_root_create_at(int dirfd, const char *path)
314 {
315         int fd;
316         struct locale_root *root;
317
318         fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
319         if (fd < 0)
320                 root =  NULL;
321         else {
322                 root = locale_root_create(fd);
323                 if (root == NULL)
324                         close(fd);
325         }
326         return root;
327 }
328
329 /*
330  * Adds a reference to 'root'
331  */
332 struct locale_root *locale_root_addref(struct locale_root *root)
333 {
334         __atomic_add_fetch(&root->refcount, 1, __ATOMIC_RELAXED);
335         return root;
336 }
337
338 /*
339  * Drops an internal reference to 'root' and destroys it
340  * if not more referenced
341  */
342 static void internal_unref(struct locale_root *root)
343 {
344         if (!__atomic_sub_fetch(&root->intcount, 1, __ATOMIC_RELAXED)) {
345                 clear_container(&root->container);
346                 close(root->rootfd);
347                 free(root);
348         }
349 }
350
351 /*
352  * Drops a reference to 'root' and destroys it
353  * if not more referenced
354  */
355 void locale_root_unref(struct locale_root *root)
356 {
357         size_t i;
358
359         if (root && !__atomic_sub_fetch(&root->refcount, 1, __ATOMIC_RELAXED)) {
360                 /* clear circular references through searchs */
361                 for (i = 0 ; i < LRU_COUNT ; i++)
362                         locale_search_unref(root->lru[i]);
363                 /* finalize if needed */
364                 internal_unref(root);
365         }
366 }
367
368 /*
369  * Get the filedescriptor for the 'root' directory
370  */
371 int locale_root_get_dirfd(struct locale_root *root)
372 {
373         return root->rootfd;
374 }
375
376 /*
377  * append, if needed, a folder to the search
378  */
379 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
380 {
381         struct locale_search_node **p, *n;
382
383         /* search an existing node */
384         p = &search->head;
385         n = search->head;
386         while(n != NULL) {
387                 if (n->folder == folder)
388                         return 0;
389                 p = &n->next;
390                 n = n->next;
391         }
392
393         /* allocates a new node */
394         n = malloc(sizeof *n);
395         if (n == NULL) {
396                 errno = ENOMEM;
397                 return -1;
398         }
399
400         /* init the node */
401         *p = n;
402         n->folder = folder;
403         n->next = NULL;
404         return 1;
405 }
406
407 /*
408  * construct a search for the given root and definition of length
409  */
410 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
411 {
412         struct locale_search *search;
413         size_t stop, back;
414         struct locale_folder *folder;
415         struct locale_search_node *node;
416
417         /* allocate the structure */
418         search = malloc(sizeof *search + length);
419         if (search == NULL) {
420                 errno = ENOMEM;
421         } else {
422                 /* init */
423                 __atomic_add_fetch(&root->intcount, 1, __ATOMIC_RELAXED);
424                 search->root = root;
425                 search->head = NULL;
426                 search->refcount = 1;
427                 memcpy(search->definition, definition, length);
428                 search->definition[length] = 0;
429
430                 /* build the search from the definition */
431                 while(length > 0) {
432                         stop = 0;
433                         while(stop < length && definition[stop] != ',' && definition[stop] != ';')
434                                 stop++;
435                         back = stop;
436                         while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
437                                 back--;
438                         while (back > 0) {
439                                 folder = search_folder(&root->container, definition, back);
440                                 if (folder) {
441                                         if (search_append_folder(search, folder) < 0) {
442                                                 locale_search_unref(search);
443                                                 return NULL;
444                                         }
445                                         if (!immediate)
446                                                 break;
447                                 }
448                                 while(back > 0 && definition[--back] != '-');
449                                 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
450                         }
451                         while (stop < length && definition[stop] != ',')
452                                 stop++;
453                         while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
454                                 stop++;
455                         definition += stop;
456                         length -= stop;
457                 }
458
459                 /* fullfills the search */
460                 node = search->head;
461                 while(node != NULL) {
462                         folder = node->folder->parent;
463                         if (folder != NULL) {
464                                 if (search_append_folder(search, node->folder->parent) < 0) {
465                                         locale_search_unref(search);
466                                         return NULL;
467                                 }
468                         }
469                         node = node->next;
470                 }
471         }
472         return search;
473 }
474
475 /*
476  * Check if a possibly NUUL search matches the definition of length
477  */
478 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
479 {
480         return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
481 }
482
483 /*
484  * Get an instance of search for the given root and definition
485  * The flag immediate affects how the search is built.
486  * For example, if the definition is "en-US,en-GB,en", the result differs depending on
487  * immediate or not:
488  *  when immediate==0 the search becomes "en-US,en-GB,en"
489  *  when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
490  */
491 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
492 {
493         char c;
494         size_t i, length;
495         struct locale_search *search;
496
497         /* normalize the definition */
498         c = definition != NULL ? *definition : 0;
499         while (c == ' ' || c == '\t' || c == ',')
500                 c = *++definition;
501         length = 0;
502         while (c)
503                 c = definition[++length];
504         if (length) {
505                 c = definition[length - 1];
506                 while ((c == ' ' || c == '\t' || c == ',') && length)
507                         c = definition[--length - 1];
508         }
509
510         /* search lru entry */
511         i = 0;
512         while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
513                 i++;
514
515         /* get the entry */
516         if (i < LRU_COUNT) {
517                 /* use an existing one */
518                 search = root->lru[i];
519         } else {
520                 /* create a new one */
521                 search = create_search(root, definition, length, immediate);
522                 if (search == NULL)
523                         return NULL;
524                 /* drop the oldest reference and update i */
525                 locale_search_unref(root->lru[--i]);
526         }
527
528         /* manage the LRU */
529         while (i > 0) {
530                 root->lru[i] = root->lru[i - 1];
531                 i = i - 1;
532         }
533         root->lru[0] = search;
534
535         /* returns a new instance */
536         return locale_search_addref(search);
537 }
538
539 /*
540  * Adds a reference to the search
541  */
542 struct locale_search *locale_search_addref(struct locale_search *search)
543 {
544         __atomic_add_fetch(&search->refcount, 1, __ATOMIC_RELAXED);
545         return search;
546 }
547
548 /*
549  * Removes a reference from the search
550  */
551 void locale_search_unref(struct locale_search *search)
552 {
553         struct locale_search_node *it, *nx;
554
555         if (search && !__atomic_sub_fetch(&search->refcount, 1, __ATOMIC_RELAXED)) {
556                 it = search->head;
557                 while(it != NULL) {
558                         nx = it->next;
559                         free(it);
560                         it = nx;
561                 }
562                 internal_unref(search->root);
563                 free(search);
564         }
565 }
566
567 /*
568  * Set the default search of 'root' to 'search'.
569  * This search is used as fallback when other search are failing.
570  */
571 void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
572 {
573         struct locale_search *older;
574
575         assert(search == NULL || search->root == root);
576
577         older = root->default_search;
578         root->default_search = search ? locale_search_addref(search) : NULL;
579         locale_search_unref(older);
580 }
581
582 /*
583  * Opens 'filename' for 'search' and 'root'.
584  *
585  * Returns the file descriptor as returned by openat
586  * system call or -1 in case of error.
587  */
588 static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
589 {
590         size_t maxlength, length;
591         char *buffer, *p;
592         struct locale_search_node *node;
593         struct locale_folder *folder;
594         int rootfd, fd;
595
596         /* check the path and normalize it */
597         filename = normalsubpath(filename);
598         if (filename == NULL)
599                 goto inval;
600
601         /* no creation flags accepted */
602         if ((flags & O_CREAT) != 0)
603                 goto inval;
604
605         /* search for folders */
606         rootfd = root->rootfd;
607         node = search ? search->head : NULL;
608         if (node != NULL) {
609                 /* allocates a buffer big enough */
610                 maxlength = root->container.maxlength;
611                 length = strlen(filename);
612                 if (length > PATH_MAX)
613                         goto inval;
614
615                 /* initialise the end of the buffer */
616                 buffer = alloca(length + maxlength + sizeof locales + 1);
617                 buffer[maxlength + sizeof locales - 1] = '/';
618                 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
619
620                 /* iterate the searched folder */
621                 while (node != NULL) {
622                         folder = node->folder;
623                         p = buffer + maxlength - folder->length;
624                         memcpy(p, locales, sizeof locales - 1);
625                         memcpy(p + sizeof locales - 1, folder->name, folder->length);
626                         fd = openat(rootfd, p, flags);
627                         if (fd >= 0)
628                                 return fd;
629                         node = node->next;
630                         if (node == NULL && search != root->default_search) {
631                                 search = root->default_search;
632                                 node = search ? search->head : NULL;
633                         }
634                 }
635         }
636
637         /* root search */
638         return openat(rootfd, filename, flags);
639
640 inval:
641         errno = EINVAL;
642         return -1;
643 }
644
645 /*
646  * Opens 'filename' after search.
647  *
648  * Returns the file descriptor as returned by openat
649  * system call or -1 in case of error.
650  */
651 int locale_search_open(struct locale_search *search, const char *filename, int flags)
652 {
653         return do_open(search, filename, flags, search->root);
654 }
655
656 /*
657  * Opens 'filename' for root with default search.
658  *
659  * Returns the file descriptor as returned by openat
660  * system call or -1 in case of error.
661  */
662 int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
663 {
664         int result;
665         struct locale_search *search;
666
667         search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
668         result = do_open(search ? : root->default_search, filename, flags, root);
669         locale_search_unref(search);
670         return result;
671 }
672
673 /*
674  * Resolves 'filename' for 'root' and 'search'.
675  *
676  * returns a copy of the filename after search or NULL if not found.
677  * the returned string MUST be freed by the caller (using free).
678  */
679 static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
680 {
681         size_t maxlength, length;
682         char *buffer, *p;
683         struct locale_search_node *node;
684         struct locale_folder *folder;
685         int rootfd;
686
687         /* check the path and normalize it */
688         filename = normalsubpath(filename);
689         if (filename == NULL)
690                 goto inval;
691
692         /* search for folders */
693         rootfd = root->rootfd;
694         node = search ? search->head : NULL;
695         if (node != NULL) {
696                 /* allocates a buffer big enough */
697                 maxlength = root->container.maxlength;
698                 length = strlen(filename);
699                 if (length > PATH_MAX)
700                         goto inval;
701
702                 /* initialise the end of the buffer */
703                 buffer = alloca(length + maxlength + sizeof locales + 1);
704                 buffer[maxlength + sizeof locales - 1] = '/';
705                 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
706
707                 /* iterate the searched folder */
708                 while (node != NULL) {
709                         folder = node->folder;
710                         p = buffer + maxlength - folder->length;
711                         memcpy(p, locales, sizeof locales - 1);
712                         memcpy(p + sizeof locales - 1, folder->name, folder->length);
713                         if (0 == faccessat(rootfd, p, F_OK, 0)) {
714                                 filename = p;
715                                 goto found;
716                         }
717                         node = node->next;
718                         if (node == NULL && search != root->default_search) {
719                                 search = root->default_search;
720                                 node = search ? search->head : NULL;
721                         }
722                 }
723         }
724
725         /* root search */
726         if (0 != faccessat(rootfd, filename, F_OK, 0)) {
727                 errno = ENOENT;
728                 return NULL;
729         }
730
731 found:
732         p = strdup(filename);
733         if (p == NULL)
734                 errno = ENOMEM;
735         return p;
736
737 inval:
738         errno = EINVAL;
739         return NULL;
740 }
741
742 /*
743  * Resolves 'filename' at 'root' after default search.
744  *
745  * returns a copy of the filename after search or NULL if not found.
746  * the returned string MUST be freed by the caller (using free).
747  */
748 char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
749 {
750         char *result;
751         struct locale_search *search;
752
753         search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
754         result = do_resolve(search ? : root->default_search, filename, root);
755         locale_search_unref(search);
756         return result;
757 }
758
759 /*
760  * Resolves 'filename' after 'search'.
761  *
762  * returns a copy of the filename after search or NULL if not found.
763  * the returned string MUST be freed by the caller (using free).
764  */
765 char *locale_search_resolve(struct locale_search *search, const char *filename)
766 {
767         return do_resolve(search, filename, search->root);
768 }
769
770 #if defined(TEST_locale_root_validsubpath)
771 #include <stdio.h>
772 void t(const char *subpath, int validity) {
773   printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
774 }
775 int main() {
776   t("/",0);
777   t("..",0);
778   t(".",1);
779   t("../a",0);
780   t("a/..",1);
781   t("a/../////..",0);
782   t("a/../b/..",1);
783   t("a/b/c/..",1);
784   t("a/b/c/../..",1);
785   t("a/b/c/../../..",1);
786   t("a/b/c/../../../.",1);
787   t("./..a/././..b/..c/./.././.././../.",1);
788   t("./..a/././..b/..c/./.././.././.././..",0);
789   t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
790   return 0;
791 }
792 #endif
793
794 #if defined(TEST_locale_root)
795 int main(int ac,char**av)
796 {
797         struct locale_root *root = locale_root_create(AT_FDCWD);
798         struct locale_search *search = NULL;
799         int fd, rc, i;
800         char buffer[256];
801         char *subpath;
802         while (*++av) {
803                 if (**av == '@') {
804                         locale_search_unref(search);
805                         search = NULL;
806                         locale_root_unref(root);
807                         root = locale_root_create(AT_FDCWD, *av + 1);
808                         if (root == NULL)
809                                 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
810                         else
811                                 printf("root: %s\n", *av + 1);
812                 } else {
813                         if (root == NULL) {
814                                 fprintf(stderr, "no valid root for %s\n", *av);
815                         } else if (**av == '-' || **av == '+') {
816                                 locale_search_unref(search);
817                                 search = locale_root_search(root, *av + 1, **av == '+');
818                                 if (search == NULL)
819                                         fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
820                                 else
821                                         printf("search: %s\n", *av + 1);
822                         } else if (search == NULL) {
823                                 fprintf(stderr, "no valid search for %s\n", *av);
824                         } else {
825                                 fd = locale_search_open(search, *av, O_RDONLY);
826                                 if (fd < 0)
827                                         fprintf(stderr, "can't open file %s: %m\n", *av);
828                                 else {
829                                         subpath = locale_search_resolve(search, *av);
830                                         if (subpath == NULL)
831                                                 fprintf(stderr, "can't resolve file %s: %m\n", *av);
832                                         else {
833                                                 rc = (int)read(fd, buffer, sizeof buffer - 1);
834                                                 if (rc < 0)
835                                                         fprintf(stderr, "can't read file %s: %m\n", *av);
836                                                 else {
837                                                         buffer[rc] = 0;
838                                                         *strchrnul(buffer, '\n') = 0;
839                                                         printf("%s -> %s [%s]\n", *av, subpath, buffer);
840                                                 }
841                                                 free(subpath);
842                                         }
843                                         close(fd);
844                                 }
845                         }
846                 }
847         }
848         locale_search_unref(search); search = NULL;
849         locale_root_unref(root); root = NULL;
850 }
851 #endif
852