Translated using Weblate (Chinese (Simplified))
[oweals/luci.git] / modules / luci-base / src / template_lmo.c
1 /*
2  * lmo - Lua Machine Objects - Base functions
3  *
4  *   Copyright (C) 2009-2010 Jo-Philipp Wich <jow@openwrt.org>
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 #include "template_lmo.h"
20 #include "plural_formula.h"
21
22 /*
23  * Hash function from http://www.azillionmonkeys.com/qed/hash.html
24  * Copyright (C) 2004-2008 by Paul Hsieh
25  */
26
27 uint32_t sfh_hash(const char *data, int len)
28 {
29         uint32_t hash = len, tmp;
30         int rem;
31
32         if (len <= 0 || data == NULL) return 0;
33
34         rem = len & 3;
35         len >>= 2;
36
37         /* Main loop */
38         for (;len > 0; len--) {
39                 hash  += sfh_get16(data);
40                 tmp    = (sfh_get16(data+2) << 11) ^ hash;
41                 hash   = (hash << 16) ^ tmp;
42                 data  += 2*sizeof(uint16_t);
43                 hash  += hash >> 11;
44         }
45
46         /* Handle end cases */
47         switch (rem) {
48                 case 3: hash += sfh_get16(data);
49                         hash ^= hash << 16;
50                         hash ^= (signed char)data[sizeof(uint16_t)] << 18;
51                         hash += hash >> 11;
52                         break;
53                 case 2: hash += sfh_get16(data);
54                         hash ^= hash << 11;
55                         hash += hash >> 17;
56                         break;
57                 case 1: hash += (signed char)*data;
58                         hash ^= hash << 10;
59                         hash += hash >> 1;
60         }
61
62         /* Force "avalanching" of final 127 bits */
63         hash ^= hash << 3;
64         hash += hash >> 5;
65         hash ^= hash << 4;
66         hash += hash >> 17;
67         hash ^= hash << 25;
68         hash += hash >> 6;
69
70         return hash;
71 }
72
73 uint32_t lmo_canon_hash(const char *str, int len,
74                         const char *ctx, int ctxlen, int plural)
75 {
76         char res[4096];
77         char *ptr, *end, prev;
78         int off;
79
80         if (!str)
81                 return 0;
82
83         ptr = res;
84         end = res + sizeof(res);
85
86         if (ctx)
87         {
88                 for (prev = ' ', off = 0; off < ctxlen; prev = *ctx, off++, ctx++)
89                 {
90                         if (ptr >= end)
91                                 return 0;
92
93                         if (isspace(*ctx))
94                         {
95                                 if (!isspace(prev))
96                                         *ptr++ = ' ';
97                         }
98                         else
99                         {
100                                 *ptr++ = *ctx;
101                         }
102                 }
103
104                 if ((ptr > res) && isspace(*(ptr-1)))
105                         ptr--;
106
107                 if (ptr >= end)
108                         return 0;
109
110                 *ptr++ = '\1';
111         }
112
113         for (prev = ' ', off = 0; off < len; prev = *str, off++, str++)
114         {
115                 if (ptr >= end)
116                         return 0;
117
118                 if (isspace(*str))
119                 {
120                         if (!isspace(prev))
121                                 *ptr++ = ' ';
122                 }
123                 else
124                 {
125                         *ptr++ = *str;
126                 }
127         }
128
129         if ((ptr > res) && isspace(*(ptr-1)))
130                 ptr--;
131
132         if (plural > -1)
133         {
134                 if (plural >= 100 || ptr + 3 >= end)
135                         return 0;
136
137                 ptr += snprintf(ptr, 3, "\2%d", plural);
138         }
139
140         return sfh_hash(res, ptr - res);
141 }
142
143 lmo_archive_t * lmo_open(const char *file)
144 {
145         int in = -1;
146         uint32_t idx_offset = 0;
147         struct stat s;
148
149         lmo_archive_t *ar = NULL;
150
151         if (stat(file, &s) == -1)
152                 goto err;
153
154         if ((in = open(file, O_RDONLY)) == -1)
155                 goto err;
156
157         if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
158         {
159                 memset(ar, 0, sizeof(*ar));
160
161                 ar->fd     = in;
162                 ar->size = s.st_size;
163
164                 fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
165
166                 if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
167                         goto err;
168
169                 idx_offset = ntohl(*((const uint32_t *)
170                                      (ar->mmap + ar->size - sizeof(uint32_t))));
171
172                 if (idx_offset >= ar->size)
173                         goto err;
174
175                 ar->index  = (lmo_entry_t *)(ar->mmap + idx_offset);
176                 ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
177                 ar->end    = ar->mmap + ar->size;
178
179                 return ar;
180         }
181
182 err:
183         if (in > -1)
184                 close(in);
185
186         if (ar != NULL)
187         {
188                 if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
189                         munmap(ar->mmap, ar->size);
190
191                 free(ar);
192         }
193
194         return NULL;
195 }
196
197 void lmo_close(lmo_archive_t *ar)
198 {
199         if (ar != NULL)
200         {
201                 if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
202                         munmap(ar->mmap, ar->size);
203
204                 close(ar->fd);
205                 free(ar);
206
207                 ar = NULL;
208         }
209 }
210
211
212 lmo_catalog_t *_lmo_catalogs = NULL;
213 lmo_catalog_t *_lmo_active_catalog = NULL;
214
215 int lmo_load_catalog(const char *lang, const char *dir)
216 {
217         DIR *dh = NULL;
218         char pattern[16];
219         char path[PATH_MAX];
220         struct dirent *de = NULL;
221
222         lmo_archive_t *ar = NULL;
223         lmo_catalog_t *cat = NULL;
224
225         if (!lmo_change_catalog(lang))
226                 return 0;
227
228         if (!dir || !(dh = opendir(dir)))
229                 goto err;
230
231         if (!(cat = malloc(sizeof(*cat))))
232                 goto err;
233
234         memset(cat, 0, sizeof(*cat));
235
236         snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
237         snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
238
239         while ((de = readdir(dh)) != NULL)
240         {
241                 if (!fnmatch(pattern, de->d_name, 0))
242                 {
243                         snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
244                         ar = lmo_open(path);
245
246                         if (ar)
247                         {
248                                 ar->next = cat->archives;
249                                 cat->archives = ar;
250                         }
251                 }
252         }
253
254         closedir(dh);
255
256         cat->next = _lmo_catalogs;
257         _lmo_catalogs = cat;
258
259         if (!_lmo_active_catalog)
260                 _lmo_active_catalog = cat;
261
262         return cat->archives ? 0 : -1;
263
264 err:
265         if (dh) closedir(dh);
266         if (cat) free(cat);
267
268         return -1;
269 }
270
271 int lmo_change_catalog(const char *lang)
272 {
273         lmo_catalog_t *cat;
274
275         for (cat = _lmo_catalogs; cat; cat = cat->next)
276         {
277                 if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
278                 {
279                         _lmo_active_catalog = cat;
280                         return 0;
281                 }
282         }
283
284         return -1;
285 }
286
287 static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
288 {
289         unsigned int m, l, r;
290         uint32_t k;
291
292         l = 0;
293         r = ar->length - 1;
294
295         while (1)
296         {
297                 m = l + ((r - l) / 2);
298
299                 if (r < l)
300                         break;
301
302                 k = ntohl(ar->index[m].key_id);
303
304                 if (k == hash)
305                         return &ar->index[m];
306
307                 if (k > hash)
308                 {
309                         if (!m)
310                                 break;
311
312                         r = m - 1;
313                 }
314                 else
315                 {
316                         l = m + 1;
317                 }
318         }
319
320         return NULL;
321 }
322
323 void *pluralParseAlloc(void *(*)(size_t));
324 void pluralParse(void *, int, int, void *);
325 void pluralParseFree(void *, void (*)(void *));
326
327 static int lmo_eval_plural(const char *expr, int len, int val)
328 {
329         struct { int num; int res; } s = { .num = val, .res = -1 };
330         const char *p = NULL;
331         void *pParser = NULL;
332         int t, n;
333         char c;
334
335         while (len > 7) {
336                 if (*expr == 'p') {
337                         if (!strncmp(expr, "plural=", 7)) {
338                                 p = expr + 7;
339                                 len -= 7;
340                                 break;
341                         }
342                 }
343
344                 expr++;
345                 len--;
346         }
347
348         if (!p)
349                 goto out;
350
351         pParser = pluralParseAlloc(malloc);
352
353         if (!pParser)
354                 goto out;
355
356         while (len-- > 0) {
357                 c = *p++;
358                 t = -1;
359                 n = 0;
360
361                 switch (c) {
362                 case ' ':
363                 case '\t':
364                         continue;
365
366                 case '0': case '1': case '2': case '3': case '4':
367                 case '5': case '6': case '7': case '8': case '9':
368                         t = T_NUM;
369                         n = c - '0';
370
371                         while (*p >= '0' && *p <= '9') {
372                                 n *= 10;
373                                 n += *p - '0';
374                                 p++;
375                         }
376
377                         break;
378
379                 case '=':
380                         if (*p == '=') {
381                                 t = T_EQ;
382                                 p++;
383                         }
384
385                         break;
386
387                 case '!':
388                         if (*p == '=') {
389                                 t = T_NE;
390                                 p++;
391                         }
392                         else {
393                                 t = T_NOT;
394                         }
395
396                         break;
397
398                 case '&':
399                         if (*p == '&') {
400                                 t = T_AND;
401                                 p++;
402                         }
403
404                         break;
405
406                 case '|':
407                         if (*p == '|') {
408                                 t = T_OR;
409                                 p++;
410                         }
411
412                         break;
413
414                 case '<':
415                         if (*p == '=') {
416                                 t = T_LE;
417                                 p++;
418                         }
419                         else {
420                                 t = T_LT;
421                         }
422
423                         break;
424
425                 case '>':
426                         if (*p == '=') {
427                                 t = T_GE;
428                                 p++;
429                         }
430                         else {
431                                 t = T_GT;
432                         }
433
434                         break;
435
436                 case '*':
437                         t = T_MUL;
438                         break;
439
440                 case '/':
441                         t = T_DIV;
442                         break;
443
444                 case '%':
445                         t = T_MOD;
446                         break;
447
448                 case '+':
449                         t = T_ADD;
450                         break;
451
452                 case '-':
453                         t = T_SUB;
454                         break;
455
456                 case 'n':
457                         t = T_N;
458                         break;
459
460                 case '?':
461                         t = T_QMARK;
462                         break;
463
464                 case ':':
465                         t = T_COLON;
466                         break;
467
468                 case '(':
469                         t = T_LPAREN;
470                         break;
471
472                 case ')':
473                         t = T_RPAREN;
474                         break;
475
476                 case ';':
477                 case '\n':
478                 case '\0':
479                         t = 0;
480                         break;
481                 }
482
483                 /* syntax error */
484                 if (t < 0)
485                         goto out;
486
487                 pluralParse(pParser, t, n, &s);
488
489                 /* eof */
490                 if (t == 0)
491                         break;
492         }
493
494         pluralParse(pParser, 0, 0, &s);
495
496 out:
497         pluralParseFree(pParser, free);
498
499         return s.res;
500 }
501
502 int lmo_translate(const char *key, int keylen, char **out, int *outlen)
503 {
504         return lmo_translate_ctxt(key, keylen, NULL, 0, out, outlen);
505 }
506
507 int lmo_translate_ctxt(const char *key, int keylen,
508                        const char *ctx, int ctxlen,
509                        char **out, int *outlen)
510 {
511         uint32_t hash;
512         lmo_entry_t *e;
513         lmo_archive_t *ar;
514
515         if (!key || !_lmo_active_catalog)
516                 return -2;
517
518         hash = lmo_canon_hash(key, keylen, ctx, ctxlen, -1);
519
520         if (hash > 0)
521         {
522                 for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
523                 {
524                         if ((e = lmo_find_entry(ar, hash)) != NULL)
525                         {
526                                 *out = ar->mmap + ntohl(e->offset);
527                                 *outlen = ntohl(e->length);
528                                 return 0;
529                         }
530                 }
531         }
532
533         return -1;
534 }
535
536 int lmo_translate_plural(int n, const char *skey, int skeylen,
537                                 const char *pkey, int pkeylen,
538                                 char **out, int *outlen)
539 {
540         return lmo_translate_plural_ctxt(n, skey, skeylen, pkey, pkeylen,
541                                          NULL, 0, out, outlen);
542 }
543
544 int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen,
545                                      const char *pkey, int pkeylen,
546                                      const char *ctx, int ctxlen,
547                                      char **out, int *outlen)
548 {
549         int pid = -1;
550         uint32_t hash;
551         lmo_entry_t *e;
552         lmo_archive_t *ar;
553         const char *plural_formula;
554
555         if (!skey || !pkey || !_lmo_active_catalog)
556                 return -2;
557
558         for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) {
559                 e = lmo_find_entry(ar, 0);
560
561                 if (e != NULL) {
562                         pid = lmo_eval_plural(ar->mmap + ntohl(e->offset), ntohl(e->length), n);
563                         break;
564                 }
565         }
566
567         if (pid == -1)
568                 pid = (n != 1);
569
570         hash = lmo_canon_hash(skey, skeylen, ctx, ctxlen, pid);
571
572         if (hash == 0)
573                 return -1;
574
575         for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
576         {
577                 if ((e = lmo_find_entry(ar, hash)) != NULL)
578                 {
579                         *out = ar->mmap + ntohl(e->offset);
580                         *outlen = ntohl(e->length);
581                         return 0;
582                 }
583         }
584
585         if (n != 1)
586         {
587                 *out = (char *)pkey;
588                 *outlen = pkeylen;
589         }
590         else
591         {
592                 *out = (char *)skey;
593                 *outlen = skeylen;
594         }
595
596         return 0;
597 }
598
599 void lmo_iterate(lmo_iterate_cb_t cb, void *priv)
600 {
601         unsigned int i;
602         lmo_entry_t *e;
603         lmo_archive_t *ar;
604
605         if (!_lmo_active_catalog)
606                 return;
607
608         for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
609                 for (i = 0, e = &ar->index[0]; i < ar->length; e = &ar->index[++i])
610                         cb(ntohl(e->key_id), ar->mmap + ntohl(e->offset), ntohl(e->length), priv);
611 }
612
613 void lmo_close_catalog(const char *lang)
614 {
615         lmo_archive_t *ar, *next;
616         lmo_catalog_t *cat, *prev;
617
618         for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)
619         {
620                 if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
621                 {
622                         if (prev)
623                                 prev->next = cat->next;
624                         else
625                                 _lmo_catalogs = cat->next;
626
627                         for (ar = cat->archives; ar; ar = next)
628                         {
629                                 next = ar->next;
630                                 lmo_close(ar);
631                         }
632
633                         free(cat);
634                         break;
635                 }
636         }
637 }