diff options
author | Simon Kelley <simon@thekelleys.org.uk> | 2021-08-12 16:48:54 +0100 |
---|---|---|
committer | Simon Kelley <simon@thekelleys.org.uk> | 2021-08-12 16:48:54 +0100 |
commit | 2f2d59b35c3338ffa20361d409f07ef340987d1b (patch) | |
tree | 6d310fe41484b42054d931eda9f7ea64bfda526d | |
parent | a1729deed3a7d456c5568c0a81450938099e66ae (diff) | |
download | dnsmasq-2f2d59b35c3338ffa20361d409f07ef340987d1b.tar.gz |
Define order of reading files when --addn-hosts given a directory.
Also applies to --dhcp-hostsfile and --dhcp-optsfile though it is
less useful there.
-rw-r--r-- | CHANGELOG | 6 | ||||
-rw-r--r-- | man/dnsmasq.8 | 10 | ||||
-rw-r--r-- | src/option.c | 54 |
3 files changed, 51 insertions, 19 deletions
@@ -86,6 +86,12 @@ version 2.86 conntrack is configured. Thanks to Yick Xie for spotting the lack of this. + When --dhcp-hostsfile --dhcp-optsfile and --addn-hosts are + given a directory as argument, define the order in which + files within that directory are read (alphabetical order + of filename). Thanks to Ed Wildgoose for the initial patch + and motivation for this. + version 2.85 Fix problem with DNS retries in 2.83/2.84. diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 75b5599..4ad125b 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -55,7 +55,8 @@ Don't read the hostnames in /etc/hosts. .B \-H, --addn-hosts=<file> Additional hosts file. Read the specified file as well as /etc/hosts. If \fB--no-hosts\fP is given, read only the specified file. This option may be repeated for more than one -additional hosts file. If a directory is given, then read all the files contained in that directory. +additional hosts file. If a directory is given, then read all the files contained in that directory +in alphabetical order. .TP .B --hostsdir=<path> Read all the hosts files contained in the directory. New or changed files @@ -1165,7 +1166,7 @@ has both wired and wireless interfaces. .TP .B --dhcp-hostsfile=<path> Read DHCP host information from the specified file. If a directory -is given, then read all the files contained in that directory. The file contains +is given, then read all the files contained in that directory in alphabetical order. The file contains information about one host per line. The format of a line is the same as text to the right of '=' in \fB--dhcp-host\fP. The advantage of storing DHCP host information in this file is that it can be changed without re-starting dnsmasq: @@ -1173,7 +1174,7 @@ the file will be re-read when dnsmasq receives SIGHUP. .TP .B --dhcp-optsfile=<path> Read DHCP option information from the specified file. If a directory -is given, then read all the files contained in that directory. The advantage of +is given, then read all the files contained in that directory in alphabetical order. The advantage of using this option is the same as for \fB--dhcp-hostsfile\fP: the \fB--dhcp-optsfile\fP will be re-read when dnsmasq receives SIGHUP. Note that it is possible to encode the information in a @@ -1188,7 +1189,8 @@ directory, and not an individual file. Changed or new files within the directory are read automatically, without the need to send SIGHUP. If a file is deleted or changed after it has been read by dnsmasq, then the host record it contained will remain until dnsmasq receives a SIGHUP, or -is restarted; ie host records are only added dynamically. +is restarted; ie host records are only added dynamically. The order in which the +files in a directory are read is not defined. .TP .B --dhcp-optsdir=<path> This is equivalent to \fB--dhcp-optsfile\fP, with the differences noted for \fB--dhcp-hostsdir\fP. diff --git a/src/option.c b/src/option.c index 14fa537..3a87097 100644 --- a/src/option.c +++ b/src/option.c @@ -5027,15 +5027,33 @@ static int one_file(char *file, int hard_opt) return 1; } +static int file_filter(const struct dirent *ent) +{ + size_t lenfile = strlen(ent->d_name); + + /* ignore emacs backups and dotfiles */ + + if (lenfile == 0 || + ent->d_name[lenfile - 1] == '~' || + (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || + ent->d_name[0] == '.') + return 0; + + return 1; +} /* expand any name which is a directory */ struct hostsfile *expand_filelist(struct hostsfile *list) { unsigned int i; - struct hostsfile *ah; + int entcnt, n; + struct hostsfile *ah, *last, *next, **up; + struct dirent **namelist; /* find largest used index */ for (i = SRC_AH, ah = list; ah; ah = ah->next) { + last = ah; + if (i <= ah->index) i = ah->index + 1; @@ -5051,46 +5069,48 @@ struct hostsfile *expand_filelist(struct hostsfile *list) struct stat buf; if (stat(ah->fname, &buf) != -1 && S_ISDIR(buf.st_mode)) { - DIR *dir_stream; struct dirent *ent; /* don't read this as a file */ ah->flags |= AH_INACTIVE; - if (!(dir_stream = opendir(ah->fname))) + entcnt = scandir(ah->fname, &namelist, file_filter, alphasort); + if (entcnt < 0) my_syslog(LOG_ERR, _("cannot access directory %s: %s"), ah->fname, strerror(errno)); else { - while ((ent = readdir(dir_stream))) + for (n = 0; n < entcnt; n++) { + ent = namelist[n]; size_t lendir = strlen(ah->fname); size_t lenfile = strlen(ent->d_name); struct hostsfile *ah1; char *path; - /* ignore emacs backups and dotfiles */ - if (lenfile == 0 || - ent->d_name[lenfile - 1] == '~' || - (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || - ent->d_name[0] == '.') - continue; - /* see if we have an existing record. dir is ah->fname file is ent->d_name path to match is ah1->fname */ - for (ah1 = list; ah1; ah1 = ah1->next) + for (up = &list, ah1 = list; ah1; ah1 = next) { + next = ah1->next; + if (lendir < strlen(ah1->fname) && strstr(ah1->fname, ah->fname) == ah1->fname && ah1->fname[lendir] == '/' && strcmp(ah1->fname + lendir + 1, ent->d_name) == 0) { ah1->flags &= ~AH_INACTIVE; + /* If found, remove from list to re-insert at the end. + Unless it's already at the end. */ + if (last != ah1) + *up = next; break; } + + up = &ah1->next; } /* make new record */ @@ -5111,17 +5131,21 @@ struct hostsfile *expand_filelist(struct hostsfile *list) ah1->fname = path; ah1->index = i++; ah1->flags = AH_DIR; - ah1->next = list; - list = ah1; } + + /* Edge case, may be the last in the list anyway */ + if (last != ah1) + last->next = ah1; + ah1->next = NULL; + last = ah1; /* inactivate record if not regular file */ if ((ah1->flags & AH_DIR) && stat(ah1->fname, &buf) != -1 && !S_ISREG(buf.st_mode)) ah1->flags |= AH_INACTIVE; } - closedir(dir_stream); } + free(namelist); } } |