afb-evt: fix a bug discovered by clang-check
[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         if (container->count)
267                 qsort(container->folders, container->count, sizeof *container->folders, compare_folders_for_qsort);
268
269         /* build the parents links */
270         i = container->count;
271         while (i != 0) {
272                 f = container->folders[--i];
273                 j = strlen(f->name);
274                 while (j != 0 && f->parent == NULL) {
275                         if (f->name[--j] == '-')
276                                 f->parent = search_folder(container, f->name, j);
277                 }
278         }
279
280         return rc;
281 }
282
283 /*
284  * Creates a locale root handler and returns it or return NULL
285  * in case of memory depletion.
286  */
287 struct locale_root *locale_root_create(int dirfd)
288 {
289         struct locale_root *root;
290         size_t i;
291
292         root = calloc(1, sizeof * root);
293         if (root == NULL)
294                 errno = ENOMEM;
295         else {
296                 if (init_container(&root->container, dirfd) == 0) {
297                         root->rootfd = dirfd;
298                         root->refcount = 1;
299                         root->intcount = 1;
300                         for(i = 0 ; i < LRU_COUNT ; i++)
301                                 root->lru[i] = NULL;
302                         root->default_search = NULL;
303                         return root;
304                 }
305                 free(root);
306         }
307         return NULL;
308 }
309
310 /*
311  * Creates a locale root handler and returns it or return NULL
312  * in case of memory depletion.
313  */
314 struct locale_root *locale_root_create_at(int dirfd, const char *path)
315 {
316         int fd;
317         struct locale_root *root;
318
319         fd = openat(dirfd, path, O_PATH|O_DIRECTORY);
320         if (fd < 0)
321                 root =  NULL;
322         else {
323                 root = locale_root_create(fd);
324                 if (root == NULL)
325                         close(fd);
326         }
327         return root;
328 }
329
330 /*
331  * Adds a reference to 'root'
332  */
333 struct locale_root *locale_root_addref(struct locale_root *root)
334 {
335         __atomic_add_fetch(&root->refcount, 1, __ATOMIC_RELAXED);
336         return root;
337 }
338
339 /*
340  * Drops an internal reference to 'root' and destroys it
341  * if not more referenced
342  */
343 static void internal_unref(struct locale_root *root)
344 {
345         if (!__atomic_sub_fetch(&root->intcount, 1, __ATOMIC_RELAXED)) {
346                 clear_container(&root->container);
347                 close(root->rootfd);
348                 free(root);
349         }
350 }
351
352 /*
353  * Drops a reference to 'root' and destroys it
354  * if not more referenced
355  */
356 void locale_root_unref(struct locale_root *root)
357 {
358         size_t i;
359
360         if (root && !__atomic_sub_fetch(&root->refcount, 1, __ATOMIC_RELAXED)) {
361                 /* clear circular references through searchs */
362                 for (i = 0 ; i < LRU_COUNT ; i++)
363                         locale_search_unref(root->lru[i]);
364                 /* finalize if needed */
365                 internal_unref(root);
366         }
367 }
368
369 /*
370  * Get the filedescriptor for the 'root' directory
371  */
372 int locale_root_get_dirfd(struct locale_root *root)
373 {
374         return root->rootfd;
375 }
376
377 /*
378  * append, if needed, a folder to the search
379  */
380 static int search_append_folder(struct locale_search *search, struct locale_folder *folder)
381 {
382         struct locale_search_node **p, *n;
383
384         /* search an existing node */
385         p = &search->head;
386         n = search->head;
387         while(n != NULL) {
388                 if (n->folder == folder)
389                         return 0;
390                 p = &n->next;
391                 n = n->next;
392         }
393
394         /* allocates a new node */
395         n = malloc(sizeof *n);
396         if (n == NULL) {
397                 errno = ENOMEM;
398                 return -1;
399         }
400
401         /* init the node */
402         *p = n;
403         n->folder = folder;
404         n->next = NULL;
405         return 1;
406 }
407
408 /*
409  * construct a search for the given root and definition of length
410  */
411 static struct locale_search *create_search(struct locale_root *root, const char *definition, size_t length, int immediate)
412 {
413         struct locale_search *search;
414         size_t stop, back;
415         struct locale_folder *folder;
416         struct locale_search_node *node;
417
418         /* allocate the structure */
419         search = malloc(sizeof *search + length);
420         if (search == NULL) {
421                 errno = ENOMEM;
422         } else {
423                 /* init */
424                 __atomic_add_fetch(&root->intcount, 1, __ATOMIC_RELAXED);
425                 search->root = root;
426                 search->head = NULL;
427                 search->refcount = 1;
428                 memcpy(search->definition, definition, length);
429                 search->definition[length] = 0;
430
431                 /* build the search from the definition */
432                 while(length > 0) {
433                         stop = 0;
434                         while(stop < length && definition[stop] != ',' && definition[stop] != ';')
435                                 stop++;
436                         back = stop;
437                         while (back > 0 && (definition[back] == ' ' || definition[back] == '\t'))
438                                 back--;
439                         while (back > 0) {
440                                 folder = search_folder(&root->container, definition, back);
441                                 if (folder) {
442                                         if (search_append_folder(search, folder) < 0) {
443                                                 locale_search_unref(search);
444                                                 return NULL;
445                                         }
446                                         if (!immediate)
447                                                 break;
448                                 }
449                                 while(back > 0 && definition[--back] != '-');
450                                 while(back > 0 && definition[back] == '-' && definition[back-1] == '-') back--;
451                         }
452                         while (stop < length && definition[stop] != ',')
453                                 stop++;
454                         while (stop < length && (definition[stop] == ',' || definition[stop] == ' ' || definition[stop] == '\t'))
455                                 stop++;
456                         definition += stop;
457                         length -= stop;
458                 }
459
460                 /* fullfills the search */
461                 node = search->head;
462                 while(node != NULL) {
463                         folder = node->folder->parent;
464                         if (folder != NULL) {
465                                 if (search_append_folder(search, node->folder->parent) < 0) {
466                                         locale_search_unref(search);
467                                         return NULL;
468                                 }
469                         }
470                         node = node->next;
471                 }
472         }
473         return search;
474 }
475
476 /*
477  * Check if a possibly NUUL search matches the definition of length
478  */
479 static inline int search_matches(struct locale_search *search, const char *definition, size_t length)
480 {
481         return search != NULL && strncasecmp(search->definition, definition, length) == 0 && search->definition[length] == '\0';
482 }
483
484 /*
485  * Get an instance of search for the given root and definition
486  * The flag immediate affects how the search is built.
487  * For example, if the definition is "en-US,en-GB,en", the result differs depending on
488  * immediate or not:
489  *  when immediate==0 the search becomes "en-US,en-GB,en"
490  *  when immediate==1 the search becomes "en-US,en,en-GB" because en-US is immediately downgraded to en
491  */
492 struct locale_search *locale_root_search(struct locale_root *root, const char *definition, int immediate)
493 {
494         char c;
495         size_t i, length;
496         struct locale_search *search;
497
498         /* normalize the definition */
499         c = definition != NULL ? *definition : 0;
500         while (c == ' ' || c == '\t' || c == ',')
501                 c = *++definition;
502         length = 0;
503         while (c)
504                 c = definition[++length];
505         if (length) {
506                 c = definition[length - 1];
507                 while ((c == ' ' || c == '\t' || c == ',') && length)
508                         c = definition[--length - 1];
509         }
510
511         /* search lru entry */
512         i = 0;
513         while (i < LRU_COUNT && !search_matches(root->lru[i], definition, length))
514                 i++;
515
516         /* get the entry */
517         if (i < LRU_COUNT) {
518                 /* use an existing one */
519                 search = root->lru[i];
520         } else {
521                 /* create a new one */
522                 search = create_search(root, definition, length, immediate);
523                 if (search == NULL)
524                         return NULL;
525                 /* drop the oldest reference and update i */
526                 locale_search_unref(root->lru[--i]);
527         }
528
529         /* manage the LRU */
530         while (i > 0) {
531                 root->lru[i] = root->lru[i - 1];
532                 i = i - 1;
533         }
534         root->lru[0] = search;
535
536         /* returns a new instance */
537         return locale_search_addref(search);
538 }
539
540 /*
541  * Adds a reference to the search
542  */
543 struct locale_search *locale_search_addref(struct locale_search *search)
544 {
545         __atomic_add_fetch(&search->refcount, 1, __ATOMIC_RELAXED);
546         return search;
547 }
548
549 /*
550  * Removes a reference from the search
551  */
552 void locale_search_unref(struct locale_search *search)
553 {
554         struct locale_search_node *it, *nx;
555
556         if (search && !__atomic_sub_fetch(&search->refcount, 1, __ATOMIC_RELAXED)) {
557                 it = search->head;
558                 while(it != NULL) {
559                         nx = it->next;
560                         free(it);
561                         it = nx;
562                 }
563                 internal_unref(search->root);
564                 free(search);
565         }
566 }
567
568 /*
569  * Set the default search of 'root' to 'search'.
570  * This search is used as fallback when other search are failing.
571  */
572 void locale_root_set_default_search(struct locale_root *root, struct locale_search *search)
573 {
574         struct locale_search *older;
575
576         assert(search == NULL || search->root == root);
577
578         older = root->default_search;
579         root->default_search = search ? locale_search_addref(search) : NULL;
580         locale_search_unref(older);
581 }
582
583 /*
584  * Opens 'filename' for 'search' and 'root'.
585  *
586  * Returns the file descriptor as returned by openat
587  * system call or -1 in case of error.
588  */
589 static int do_open(struct locale_search *search, const char *filename, int flags, struct locale_root *root)
590 {
591         size_t maxlength, length;
592         char *buffer, *p;
593         struct locale_search_node *node;
594         struct locale_folder *folder;
595         int rootfd, fd;
596
597         /* check the path and normalize it */
598         filename = normalsubpath(filename);
599         if (filename == NULL)
600                 goto inval;
601
602         /* no creation flags accepted */
603         if ((flags & O_CREAT) != 0)
604                 goto inval;
605
606         /* search for folders */
607         rootfd = root->rootfd;
608         node = search ? search->head : NULL;
609         if (node != NULL) {
610                 /* allocates a buffer big enough */
611                 maxlength = root->container.maxlength;
612                 length = strlen(filename);
613                 if (length > PATH_MAX)
614                         goto inval;
615
616                 /* initialise the end of the buffer */
617                 buffer = alloca(length + maxlength + sizeof locales + 1);
618                 buffer[maxlength + sizeof locales - 1] = '/';
619                 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
620
621                 /* iterate the searched folder */
622                 while (node != NULL) {
623                         folder = node->folder;
624                         p = buffer + maxlength - folder->length;
625                         memcpy(p, locales, sizeof locales - 1);
626                         memcpy(p + sizeof locales - 1, folder->name, folder->length);
627                         fd = openat(rootfd, p, flags);
628                         if (fd >= 0)
629                                 return fd;
630                         node = node->next;
631                         if (node == NULL && search != root->default_search) {
632                                 search = root->default_search;
633                                 node = search ? search->head : NULL;
634                         }
635                 }
636         }
637
638         /* root search */
639         return openat(rootfd, filename, flags);
640
641 inval:
642         errno = EINVAL;
643         return -1;
644 }
645
646 /*
647  * Opens 'filename' after search.
648  *
649  * Returns the file descriptor as returned by openat
650  * system call or -1 in case of error.
651  */
652 int locale_search_open(struct locale_search *search, const char *filename, int flags)
653 {
654         return do_open(search, filename, flags, search->root);
655 }
656
657 /*
658  * Opens 'filename' for root with default search.
659  *
660  * Returns the file descriptor as returned by openat
661  * system call or -1 in case of error.
662  */
663 int locale_root_open(struct locale_root *root, const char *filename, int flags, const char *locale)
664 {
665         int result;
666         struct locale_search *search;
667
668         search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
669         result = do_open(search ? : root->default_search, filename, flags, root);
670         locale_search_unref(search);
671         return result;
672 }
673
674 /*
675  * Resolves 'filename' for 'root' and 'search'.
676  *
677  * returns a copy of the filename after search or NULL if not found.
678  * the returned string MUST be freed by the caller (using free).
679  */
680 static char *do_resolve(struct locale_search *search, const char *filename, struct locale_root *root)
681 {
682         size_t maxlength, length;
683         char *buffer, *p;
684         struct locale_search_node *node;
685         struct locale_folder *folder;
686         int rootfd;
687
688         /* check the path and normalize it */
689         filename = normalsubpath(filename);
690         if (filename == NULL)
691                 goto inval;
692
693         /* search for folders */
694         rootfd = root->rootfd;
695         node = search ? search->head : NULL;
696         if (node != NULL) {
697                 /* allocates a buffer big enough */
698                 maxlength = root->container.maxlength;
699                 length = strlen(filename);
700                 if (length > PATH_MAX)
701                         goto inval;
702
703                 /* initialise the end of the buffer */
704                 buffer = alloca(length + maxlength + sizeof locales + 1);
705                 buffer[maxlength + sizeof locales - 1] = '/';
706                 memcpy(buffer + sizeof locales + maxlength, filename, length + 1);
707
708                 /* iterate the searched folder */
709                 while (node != NULL) {
710                         folder = node->folder;
711                         p = buffer + maxlength - folder->length;
712                         memcpy(p, locales, sizeof locales - 1);
713                         memcpy(p + sizeof locales - 1, folder->name, folder->length);
714                         if (0 == faccessat(rootfd, p, F_OK, 0)) {
715                                 filename = p;
716                                 goto found;
717                         }
718                         node = node->next;
719                         if (node == NULL && search != root->default_search) {
720                                 search = root->default_search;
721                                 node = search ? search->head : NULL;
722                         }
723                 }
724         }
725
726         /* root search */
727         if (0 != faccessat(rootfd, filename, F_OK, 0)) {
728                 errno = ENOENT;
729                 return NULL;
730         }
731
732 found:
733         p = strdup(filename);
734         if (p == NULL)
735                 errno = ENOMEM;
736         return p;
737
738 inval:
739         errno = EINVAL;
740         return NULL;
741 }
742
743 /*
744  * Resolves 'filename' at 'root' after default search.
745  *
746  * returns a copy of the filename after search or NULL if not found.
747  * the returned string MUST be freed by the caller (using free).
748  */
749 char *locale_root_resolve(struct locale_root *root, const char *filename, const char *locale)
750 {
751         char *result;
752         struct locale_search *search;
753
754         search = locale != NULL ? locale_root_search(root, locale, 0) : NULL;
755         result = do_resolve(search ? : root->default_search, filename, root);
756         locale_search_unref(search);
757         return result;
758 }
759
760 /*
761  * Resolves 'filename' after 'search'.
762  *
763  * returns a copy of the filename after search or NULL if not found.
764  * the returned string MUST be freed by the caller (using free).
765  */
766 char *locale_search_resolve(struct locale_search *search, const char *filename)
767 {
768         return do_resolve(search, filename, search->root);
769 }
770
771 #if defined(TEST_locale_root_validsubpath)
772 #include <stdio.h>
773 void t(const char *subpath, int validity) {
774   printf("%s -> %d = %d, %s\n", subpath, validity, validsubpath(subpath), validsubpath(subpath)==validity ? "ok" : "NOT OK");
775 }
776 int main() {
777   t("/",0);
778   t("..",0);
779   t(".",1);
780   t("../a",0);
781   t("a/..",1);
782   t("a/../////..",0);
783   t("a/../b/..",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/./.././.././../.",1);
789   t("./..a/././..b/..c/./.././.././.././..",0);
790   t("./..a//.//./..b/..c/./.././/./././///.././.././a/a/a/a/a",1);
791   return 0;
792 }
793 #endif
794
795 #if defined(TEST_locale_root)
796 int main(int ac,char**av)
797 {
798         struct locale_root *root = locale_root_create(AT_FDCWD);
799         struct locale_search *search = NULL;
800         int fd, rc, i;
801         char buffer[256];
802         char *subpath;
803         while (*++av) {
804                 if (**av == '@') {
805                         locale_search_unref(search);
806                         search = NULL;
807                         locale_root_unref(root);
808                         root = locale_root_create(AT_FDCWD, *av + 1);
809                         if (root == NULL)
810                                 fprintf(stderr, "can't create root at %s: %m\n", *av + 1);
811                         else
812                                 printf("root: %s\n", *av + 1);
813                 } else {
814                         if (root == NULL) {
815                                 fprintf(stderr, "no valid root for %s\n", *av);
816                         } else if (**av == '-' || **av == '+') {
817                                 locale_search_unref(search);
818                                 search = locale_root_search(root, *av + 1, **av == '+');
819                                 if (search == NULL)
820                                         fprintf(stderr, "can't create search for %s: %m\n", *av + 1);
821                                 else
822                                         printf("search: %s\n", *av + 1);
823                         } else if (search == NULL) {
824                                 fprintf(stderr, "no valid search for %s\n", *av);
825                         } else {
826                                 fd = locale_search_open(search, *av, O_RDONLY);
827                                 if (fd < 0)
828                                         fprintf(stderr, "can't open file %s: %m\n", *av);
829                                 else {
830                                         subpath = locale_search_resolve(search, *av);
831                                         if (subpath == NULL)
832                                                 fprintf(stderr, "can't resolve file %s: %m\n", *av);
833                                         else {
834                                                 rc = (int)read(fd, buffer, sizeof buffer - 1);
835                                                 if (rc < 0)
836                                                         fprintf(stderr, "can't read file %s: %m\n", *av);
837                                                 else {
838                                                         buffer[rc] = 0;
839                                                         *strchrnul(buffer, '\n') = 0;
840                                                         printf("%s -> %s [%s]\n", *av, subpath, buffer);
841                                                 }
842                                                 free(subpath);
843                                         }
844                                         close(fd);
845                                 }
846                         }
847                 }
848         }
849         locale_search_unref(search); search = NULL;
850         locale_root_unref(root); root = NULL;
851 }
852 #endif
853