]> git.ipfire.org Git - ipfire-2.x.git/blob - src/patches/dnsmasq/0038-Expand-inotify-code-to-dhcp-hostsdir-dhcp-optsdir-an.patch
dnsmasq: Import more patches from upstream
[ipfire-2.x.git] / src / patches / dnsmasq / 0038-Expand-inotify-code-to-dhcp-hostsdir-dhcp-optsdir-an.patch
1 From 70d1873dd9e70041ed4bb88c69d5b886b7cc634c Mon Sep 17 00:00:00 2001
2 From: Simon Kelley <simon@thekelleys.org.uk>
3 Date: Sat, 31 Jan 2015 19:59:29 +0000
4 Subject: [PATCH 38/78] Expand inotify code to dhcp-hostsdir, dhcp-optsdir and
5 hostsdir.
6
7 ---
8 src/cache.c | 81 +++++++++++++++++---------
9 src/dnsmasq.c | 9 ++-
10 src/dnsmasq.h | 14 +++--
11 src/inotify.c | 179 +++++++++++++++++++++++++++++-----------------------------
12 src/option.c | 37 +++++++++---
13 5 files changed, 187 insertions(+), 133 deletions(-)
14
15 diff --git a/src/cache.c b/src/cache.c
16 index 09b6dbf8087a..abaf25ec0f18 100644
17 --- a/src/cache.c
18 +++ b/src/cache.c
19 @@ -835,27 +835,42 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl
20 Only insert each unique address once into this hashing structure.
21
22 This complexity avoids O(n^2) divergent CPU use whilst reading
23 - large (10000 entry) hosts files. */
24 -
25 - /* hash address */
26 - for (j = 0, i = 0; i < addrlen; i++)
27 - j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
28 -
29 - for (lookup = rhash[j]; lookup; lookup = lookup->next)
30 - if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
31 - memcmp(&lookup->addr.addr, addr, addrlen) == 0)
32 - {
33 - cache->flags &= ~F_REVERSE;
34 - break;
35 - }
36 + large (10000 entry) hosts files.
37 +
38 + Note that we only do this process when bulk-reading hosts files,
39 + for incremental reads, rhash is NULL, and we use cache lookups
40 + instead.
41 + */
42
43 - /* maintain address hash chain, insert new unique address */
44 - if (!lookup)
45 + if (rhash)
46 {
47 - cache->next = rhash[j];
48 - rhash[j] = cache;
49 + /* hash address */
50 + for (j = 0, i = 0; i < addrlen; i++)
51 + j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
52 +
53 + for (lookup = rhash[j]; lookup; lookup = lookup->next)
54 + if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
55 + memcmp(&lookup->addr.addr, addr, addrlen) == 0)
56 + {
57 + cache->flags &= ~F_REVERSE;
58 + break;
59 + }
60 +
61 + /* maintain address hash chain, insert new unique address */
62 + if (!lookup)
63 + {
64 + cache->next = rhash[j];
65 + rhash[j] = cache;
66 + }
67 }
68 -
69 + else
70 + {
71 + /* incremental read, lookup in cache */
72 + lookup = cache_find_by_addr(NULL, addr, 0, cache->flags & (F_IPV4 | F_IPV6));
73 + if (lookup && lookup->flags & F_HOSTS)
74 + cache->flags &= ~F_REVERSE;
75 + }
76 +
77 cache->uid = index;
78 memcpy(&cache->addr.addr, addr, addrlen);
79 cache_hash(cache);
80 @@ -912,7 +927,7 @@ static int gettok(FILE *f, char *token)
81 }
82 }
83
84 -static int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz)
85 +int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz)
86 {
87 FILE *f = fopen(filename, "r");
88 char *token = daemon->namebuff, *domain_suffix = NULL;
89 @@ -958,7 +973,7 @@ static int read_hostsfile(char *filename, unsigned int index, int cache_size, st
90 addr_count++;
91
92 /* rehash every 1000 names. */
93 - if ((name_count - cache_size) > 1000)
94 + if (rhash && ((name_count - cache_size) > 1000))
95 {
96 rehash(name_count);
97 cache_size = name_count;
98 @@ -1005,10 +1020,13 @@ static int read_hostsfile(char *filename, unsigned int index, int cache_size, st
99 }
100
101 fclose(f);
102 - rehash(name_count);
103 -
104 - my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count);
105
106 + if (rhash)
107 + {
108 + rehash(name_count);
109 + my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count);
110 + }
111 +
112 return name_count;
113 }
114
115 @@ -1118,14 +1136,19 @@ void cache_reload(void)
116 my_syslog(LOG_INFO, _("cleared cache"));
117 return;
118 }
119 -
120 +
121 if (!option_bool(OPT_NO_HOSTS))
122 total_size = read_hostsfile(HOSTSFILE, SRC_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
123 -
124 +
125 daemon->addn_hosts = expand_filelist(daemon->addn_hosts);
126 for (ah = daemon->addn_hosts; ah; ah = ah->next)
127 if (!(ah->flags & AH_INACTIVE))
128 total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz);
129 +
130 +#ifdef HAVE_INOTIFY
131 + set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
132 +#endif
133 +
134 }
135
136 #ifdef HAVE_DHCP
137 @@ -1505,7 +1528,13 @@ char *record_source(unsigned int index)
138 for (ah = daemon->addn_hosts; ah; ah = ah->next)
139 if (ah->index == index)
140 return ah->fname;
141 -
142 +
143 +#ifdef HAVE_INOTIFY
144 + for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
145 + if (ah->index == index)
146 + return ah->fname;
147 +#endif
148 +
149 return "<unknown>";
150 }
151
152 diff --git a/src/dnsmasq.c b/src/dnsmasq.c
153 index bc4f47170705..2c629fe422aa 100644
154 --- a/src/dnsmasq.c
155 +++ b/src/dnsmasq.c
156 @@ -145,8 +145,8 @@ int main (int argc, char **argv)
157 #endif
158
159 #ifndef HAVE_INOTIFY
160 - if (daemon->inotify_hosts)
161 - die(_("dhcp-hostsdir not supported on this platform"), NULL, EC_BADCONF);
162 + if (daemon->dynamic_dirs)
163 + die(_("dhcp-hostsdir, dhcp-optsdir and hostsdir are not supported on this platform"), NULL, EC_BADCONF);
164 #endif
165
166 if (option_bool(OPT_DNSSEC_VALID))
167 @@ -324,8 +324,7 @@ int main (int argc, char **argv)
168 }
169
170 #ifdef HAVE_INOTIFY
171 - if ((!option_bool(OPT_NO_POLL) && daemon->port != 0) ||
172 - daemon->dhcp || daemon->doing_dhcp6)
173 + if (daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6)
174 inotify_dnsmasq_init();
175 else
176 daemon->inotifyfd = -1;
177 @@ -1400,7 +1399,7 @@ void clear_cache_and_reload(time_t now)
178 dhcp_read_ethers();
179 reread_dhcp();
180 #ifdef HAVE_INOTIFY
181 - set_dhcp_inotify();
182 + set_dynamic_inotify(AH_DHCP_HST | AH_DHCP_OPT, 0, NULL, 0);
183 #endif
184 dhcp_update_configs(daemon->dhcp_conf);
185 lease_update_from_configs();
186 diff --git a/src/dnsmasq.h b/src/dnsmasq.h
187 index 8091634f69db..0c322a93993e 100644
188 --- a/src/dnsmasq.h
189 +++ b/src/dnsmasq.h
190 @@ -554,6 +554,9 @@ struct resolvc {
191 #define AH_DIR 1
192 #define AH_INACTIVE 2
193 #define AH_WD_DONE 4
194 +#define AH_HOSTS 8
195 +#define AH_DHCP_HST 16
196 +#define AH_DHCP_OPT 32
197 struct hostsfile {
198 struct hostsfile *next;
199 int flags;
200 @@ -965,7 +968,7 @@ extern struct daemon {
201 int doing_ra, doing_dhcp6;
202 struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names;
203 struct dhcp_netid_list *force_broadcast, *bootp_dynamic;
204 - struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *inotify_hosts;
205 + struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs;
206 int dhcp_max, tftp_max;
207 int dhcp_server_port, dhcp_client_port;
208 int start_tftp_port, end_tftp_port;
209 @@ -1071,6 +1074,8 @@ int cache_make_stat(struct txt_record *t);
210 char *cache_get_name(struct crec *crecp);
211 char *cache_get_cname_target(struct crec *crecp);
212 struct crec *cache_enumerate(int init);
213 +int read_hostsfile(char *filename, unsigned int index, int cache_size,
214 + struct crec **rhash, int hashsz);
215
216 /* blockdata.c */
217 #ifdef HAVE_DNSSEC
218 @@ -1204,7 +1209,8 @@ void reset_option_bool(unsigned int opt);
219 struct hostsfile *expand_filelist(struct hostsfile *list);
220 char *parse_server(char *arg, union mysockaddr *addr,
221 union mysockaddr *source_addr, char *interface, int *flags);
222 -int option_read_hostsfile(char *file);
223 +int option_read_dynfile(char *file, int flags);
224 +
225 /* forward.c */
226 void reply_query(int fd, int family, time_t now);
227 void receive_query(struct listener *listen, time_t now);
228 @@ -1494,7 +1500,5 @@ int detect_loop(char *query, int type);
229 #ifdef HAVE_INOTIFY
230 void inotify_dnsmasq_init();
231 int inotify_check(time_t now);
232 -# ifdef HAVE_DHCP
233 -void set_dhcp_inotify(void);
234 -# endif
235 +void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz);
236 #endif
237 diff --git a/src/inotify.c b/src/inotify.c
238 index 818fe8eddda4..c537f4c1562a 100644
239 --- a/src/inotify.c
240 +++ b/src/inotify.c
241 @@ -19,11 +19,6 @@
242
243 #include <sys/inotify.h>
244
245 -#ifdef HAVE_DHCP
246 -static void check_for_dhcp_inotify(struct inotify_event *in, time_t now);
247 -#endif
248 -
249 -
250 /* the strategy is to set a inotify on the directories containing
251 resolv files, for any files in the directory which are close-write
252 or moved into the directory.
253 @@ -82,57 +77,28 @@ void inotify_dnsmasq_init()
254 }
255 }
256
257 -int inotify_check(time_t now)
258 +
259 +/* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */
260 +void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz)
261 {
262 - int hit = 0;
263 + struct hostsfile *ah;
264
265 - while (1)
266 + for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
267 {
268 - int rc;
269 - char *p;
270 - struct resolvc *res;
271 - struct inotify_event *in;
272 -
273 - while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
274 -
275 - if (rc <= 0)
276 - break;
277 -
278 - for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len)
279 + DIR *dir_stream = NULL;
280 + struct dirent *ent;
281 + struct stat buf;
282 +
283 + if (!(ah->flags & flag))
284 + continue;
285 +
286 + if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
287 {
288 - in = (struct inotify_event*)p;
289 -
290 - for (res = daemon->resolv_files; res; res = res->next)
291 - if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0)
292 - hit = 1;
293 -
294 -#ifdef HAVE_DHCP
295 - if (daemon->dhcp || daemon->doing_dhcp6)
296 - check_for_dhcp_inotify(in, now);
297 -#endif
298 + my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"),
299 + ah->fname, strerror(errno));
300 + continue;
301 }
302 - }
303 - return hit;
304 -}
305 -
306 -#ifdef HAVE_DHCP
307 -/* initialisation for dhcp-hostdir. Set inotify watch for each directory, and read pre-existing files */
308 -void set_dhcp_inotify(void)
309 -{
310 - struct hostsfile *ah;
311 -
312 - for (ah = daemon->inotify_hosts; ah; ah = ah->next)
313 - {
314 - DIR *dir_stream = NULL;
315 - struct dirent *ent;
316 - struct stat buf;
317 -
318 - if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
319 - {
320 - my_syslog(LOG_ERR, _("bad directory in dhcp-hostsdir %s"), ah->fname);
321 - continue;
322 - }
323 -
324 +
325 if (!(ah->flags & AH_WD_DONE))
326 {
327 ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO);
328 @@ -142,7 +108,8 @@ void set_dhcp_inotify(void)
329 a race which misses files being added as we start */
330 if (ah->wd == -1 || !(dir_stream = opendir(ah->fname)))
331 {
332 - my_syslog(LOG_ERR, _("failed to create inotify for %s"), ah->fname);
333 + my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"),
334 + ah->fname, strerror(errno));
335 continue;
336 }
337
338 @@ -167,54 +134,90 @@ void set_dhcp_inotify(void)
339
340 /* ignore non-regular files */
341 if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
342 - option_read_hostsfile(path);
343 -
344 + {
345 + if (ah->flags & AH_HOSTS)
346 + total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz);
347 +#ifdef HAVE_DHCP
348 + else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT))
349 + option_read_dynfile(path, ah->flags);
350 +#endif
351 + }
352 +
353 free(path);
354 }
355 }
356 }
357 }
358
359 -static void check_for_dhcp_inotify(struct inotify_event *in, time_t now)
360 +int inotify_check(time_t now)
361 {
362 + int hit = 0;
363 struct hostsfile *ah;
364
365 - /* ignore emacs backups and dotfiles */
366 - if (in->len == 0 ||
367 - in->name[in->len - 1] == '~' ||
368 - (in->name[0] == '#' && in->name[in->len - 1] == '#') ||
369 - in->name[0] == '.')
370 - return;
371 -
372 - for (ah = daemon->inotify_hosts; ah; ah = ah->next)
373 - if (ah->wd == in->wd)
374 - {
375 - size_t lendir = strlen(ah->fname);
376 - char *path;
377 -
378 - if ((path = whine_malloc(lendir + in->len + 2)))
379 - {
380 - strcpy(path, ah->fname);
381 - strcat(path, "/");
382 - strcat(path, in->name);
383 -
384 - if (option_read_hostsfile(path))
385 + while (1)
386 + {
387 + int rc;
388 + char *p;
389 + struct resolvc *res;
390 + struct inotify_event *in;
391 +
392 + while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
393 +
394 + if (rc <= 0)
395 + break;
396 +
397 + for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len)
398 + {
399 + in = (struct inotify_event*)p;
400 +
401 + for (res = daemon->resolv_files; res; res = res->next)
402 + if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0)
403 + hit = 1;
404 +
405 + /* ignore emacs backups and dotfiles */
406 + if (in->len == 0 ||
407 + in->name[in->len - 1] == '~' ||
408 + (in->name[0] == '#' && in->name[in->len - 1] == '#') ||
409 + in->name[0] == '.')
410 + continue;
411 +
412 + for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
413 + if (ah->wd == in->wd)
414 {
415 - /* Propogate the consequences of loading a new dhcp-host */
416 - dhcp_update_configs(daemon->dhcp_conf);
417 - lease_update_from_configs();
418 - lease_update_file(now);
419 - lease_update_dns(1);
420 + size_t lendir = strlen(ah->fname);
421 + char *path;
422 +
423 + if ((path = whine_malloc(lendir + in->len + 2)))
424 + {
425 + strcpy(path, ah->fname);
426 + strcat(path, "/");
427 + strcat(path, in->name);
428 +
429 + if (ah->flags & AH_HOSTS)
430 + read_hostsfile(path, ah->index, 0, NULL, 0);
431 +#ifdef HAVE_DHCP
432 + else if (ah->flags & AH_DHCP_HST)
433 + {
434 + if (option_read_dynfile(path, AH_DHCP_HST))
435 + {
436 + /* Propogate the consequences of loading a new dhcp-host */
437 + dhcp_update_configs(daemon->dhcp_conf);
438 + lease_update_from_configs();
439 + lease_update_file(now);
440 + lease_update_dns(1);
441 + }
442 + }
443 + else if (ah->flags & AH_DHCP_OPT)
444 + option_read_dynfile(path, AH_DHCP_OPT);
445 +#endif
446 +
447 + free(path);
448 + }
449 }
450 -
451 - free(path);
452 - }
453 -
454 - return;
455 - }
456 + }
457 + }
458 + return hit;
459 }
460
461 -#endif /* DHCP */
462 -
463 #endif /* INOTIFY */
464
465 diff --git a/src/option.c b/src/option.c
466 index 22e11c37d374..6ef80117cc8c 100644
467 --- a/src/option.c
468 +++ b/src/option.c
469 @@ -150,6 +150,8 @@ struct myoption {
470 #define LOPT_IGNORE_ADDR 338
471 #define LOPT_MINCTTL 339
472 #define LOPT_DHCP_INOTIFY 340
473 +#define LOPT_DHOPT_INOTIFY 341
474 +#define LOPT_HOST_INOTIFY 342
475
476 #ifdef HAVE_GETOPT_LONG
477 static const struct option opts[] =
478 @@ -200,6 +202,7 @@ static const struct myoption opts[] =
479 { "local-ttl", 1, 0, 'T' },
480 { "no-negcache", 0, 0, 'N' },
481 { "addn-hosts", 1, 0, 'H' },
482 + { "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
483 { "query-port", 1, 0, 'Q' },
484 { "except-interface", 1, 0, 'I' },
485 { "no-dhcp-interface", 1, 0, '2' },
486 @@ -249,6 +252,7 @@ static const struct myoption opts[] =
487 { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST },
488 { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS },
489 { "dhcp-hostsdir", 1, 0, LOPT_DHCP_INOTIFY },
490 + { "dhcp-optsdir", 1, 0, LOPT_DHOPT_INOTIFY },
491 { "dhcp-no-override", 0, 0, LOPT_OVERRIDE },
492 { "tftp-port-range", 1, 0, LOPT_TFTPPORTS },
493 { "stop-dns-rebind", 0, 0, LOPT_REBIND },
494 @@ -338,9 +342,11 @@ static struct {
495 { LOPT_DHCP_HOST, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from file."), NULL },
496 { LOPT_DHCP_OPTS, ARG_DUP, "<path>", gettext_noop("Read DHCP option specs from file."), NULL },
497 { LOPT_DHCP_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from a directory."), NULL },
498 + { LOPT_DHOPT_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP options from a directory."), NULL },
499 { LOPT_TAG_IF, ARG_DUP, "tag-expression", gettext_noop("Evaluate conditional tag expression."), NULL },
500 { 'h', OPT_NO_HOSTS, NULL, gettext_noop("Do NOT load %s file."), HOSTSFILE },
501 { 'H', ARG_DUP, "<path>", gettext_noop("Specify a hosts file to be read in addition to %s."), HOSTSFILE },
502 + { LOPT_HOST_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read hosts files from a directory."), NULL },
503 { 'i', ARG_DUP, "<interface>", gettext_noop("Specify interface(s) to listen on."), NULL },
504 { 'I', ARG_DUP, "<interface>", gettext_noop("Specify interface(s) NOT to listen on.") , NULL },
505 { 'j', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP user class to tag."), NULL },
506 @@ -1712,10 +1718,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
507 break;
508 #endif /* HAVE_DHCP */
509
510 - case LOPT_DHCP_HOST: /* --dhcp-hostsfile */
511 - case LOPT_DHCP_OPTS: /* --dhcp-optsfile */
512 - case LOPT_DHCP_INOTIFY: /* dhcp-hostsdir */
513 - case 'H': /* --addn-hosts */
514 + case LOPT_DHCP_HOST: /* --dhcp-hostsfile */
515 + case LOPT_DHCP_OPTS: /* --dhcp-optsfile */
516 + case LOPT_DHCP_INOTIFY: /* --dhcp-hostsdir */
517 + case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */
518 + case LOPT_HOST_INOTIFY: /* --hostsdir */
519 + case 'H': /* --addn-hosts */
520 {
521 struct hostsfile *new = opt_malloc(sizeof(struct hostsfile));
522 static unsigned int hosts_index = SRC_AH;
523 @@ -1737,10 +1745,16 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
524 new->next = daemon->dhcp_opts_file;
525 daemon->dhcp_opts_file = new;
526 }
527 - else if (option == LOPT_DHCP_INOTIFY)
528 + else
529 {
530 - new->next = daemon->inotify_hosts;
531 - daemon->inotify_hosts = new;
532 + new->next = daemon->dynamic_dirs;
533 + daemon->dynamic_dirs = new;
534 + if (option == LOPT_DHCP_INOTIFY)
535 + new->flags |= AH_DHCP_HST;
536 + else if (option == LOPT_DHOPT_INOTIFY)
537 + new->flags |= AH_DHCP_OPT;
538 + else if (option == LOPT_HOST_INOTIFY)
539 + new->flags |= AH_HOSTS;
540 }
541
542 break;
543 @@ -4052,9 +4066,14 @@ static void read_file(char *file, FILE *f, int hard_opt)
544 }
545
546 #ifdef HAVE_DHCP
547 -int option_read_hostsfile(char *file)
548 +int option_read_dynfile(char *file, int flags)
549 {
550 - return one_file(file, LOPT_BANK);
551 + if (flags & AH_DHCP_HST)
552 + return one_file(file, LOPT_BANK);
553 + else if (flags & AH_DHCP_OPT)
554 + return one_file(file, LOPT_OPTS);
555 +
556 + return 0;
557 }
558 #endif
559
560 --
561 2.1.0
562