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