1/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley 2 3 This program is free software; you can redistribute it and/or modify 4 it under the terms of the GNU General Public License as published by 5 the Free Software Foundation; version 2 dated June, 1991, or 6 (at your option) version 3 dated 29 June, 2007. 7 8 This program is distributed in the hope that it will be useful, 9 but WITHOUT ANY WARRANTY; without even the implied warranty of 10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License 14 along with this program. If not, see <http://www.gnu.org/licenses/>. 15*/ 16 17#include "dnsmasq.h" 18#ifdef HAVE_INOTIFY 19 20#include <sys/inotify.h> 21#include <sys/param.h> /* For MAXSYMLINKS */ 22 23/* the strategy is to set a inotify on the directories containing 24 resolv files, for any files in the directory which are close-write 25 or moved into the directory. 26 27 When either of those happen, we look to see if the file involved 28 is actually a resolv-file, and if so, call poll-resolv with 29 the "force" argument, to ensure it's read. 30 31 This adds one new error condition: the directories containing 32 all specified resolv-files must exist at start-up, even if the actual 33 files don't. 34*/ 35 36static char *inotify_buffer; 37#define INOTIFY_SZ (sizeof(struct inotify_event) + NAME_MAX + 1) 38 39/* If path is a symbolic link, return the path it 40 points to, made absolute if relative. 41 If path doesn't exist or is not a symlink, return NULL. 42 Return value is malloc'ed */ 43static char *my_readlink(char *path) 44{ 45 ssize_t rc, size = 64; 46 char *buf; 47 48 while (1) 49 { 50 buf = safe_malloc(size); 51 rc = readlink(path, buf, (size_t)size); 52 53 if (rc == -1) 54 { 55 /* Not link or doesn't exist. */ 56 if (errno == EINVAL || errno == ENOENT) 57 return NULL; 58 else 59 die(_("cannot access path %s: %s"), path, EC_MISC); 60 } 61 else if (rc < size-1) 62 { 63 char *d; 64 65 buf[rc] = 0; 66 if (buf[0] != '/' && ((d = strrchr(path, '/')))) 67 { 68 /* Add path to relative link */ 69 char *new_buf = safe_malloc((d - path) + strlen(buf) + 2); 70 *(d+1) = 0; 71 strcpy(new_buf, path); 72 strcat(new_buf, buf); 73 free(buf); 74 buf = new_buf; 75 } 76 return buf; 77 } 78 79 /* Buffer too small, increase and retry */ 80 size += 64; 81 free(buf); 82 } 83} 84 85void inotify_dnsmasq_init() 86{ 87 struct resolvc *res; 88 inotify_buffer = safe_malloc(INOTIFY_SZ); 89 daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 90 91 if (daemon->inotifyfd == -1) 92 die(_("failed to create inotify: %s"), NULL, EC_MISC); 93 94 if (option_bool(OPT_NO_RESOLV)) 95 return; 96 97 for (res = daemon->resolv_files; res; res = res->next) 98 { 99 char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1); 100 int links = MAXSYMLINKS; 101 102 strcpy(path, res->name); 103 104 /* Follow symlinks until we reach a non-symlink, or a non-existant file. */ 105 while ((new_path = my_readlink(path))) 106 { 107 if (links-- == 0) 108 die(_("too many symlinks following %s"), res->name, EC_MISC); 109 free(path); 110 path = new_path; 111 } 112 113 res->wd = -1; 114 115 if ((d = strrchr(path, '/'))) 116 { 117 *d = 0; /* make path just directory */ 118 res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO); 119 120 res->file = d+1; /* pointer to filename */ 121 *d = '/'; 122 123 if (res->wd == -1 && errno == ENOENT) 124 die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC); 125 } 126 127 if (res->wd == -1) 128 die(_("failed to create inotify for %s: %s"), res->name, EC_MISC); 129 130 } 131} 132 133 134/* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */ 135void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz) 136{ 137 struct hostsfile *ah; 138 139 for (ah = daemon->dynamic_dirs; ah; ah = ah->next) 140 { 141 DIR *dir_stream = NULL; 142 struct dirent *ent; 143 struct stat buf; 144 145 if (!(ah->flags & flag)) 146 continue; 147 148 if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode))) 149 { 150 my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), 151 ah->fname, strerror(errno)); 152 continue; 153 } 154 155 if (!(ah->flags & AH_WD_DONE)) 156 { 157 ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO); 158 ah->flags |= AH_WD_DONE; 159 } 160 161 /* Read contents of dir _after_ calling add_watch, in the hope of avoiding 162 a race which misses files being added as we start */ 163 if (ah->wd == -1 || !(dir_stream = opendir(ah->fname))) 164 { 165 my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"), 166 ah->fname, strerror(errno)); 167 continue; 168 } 169 170 while ((ent = readdir(dir_stream))) 171 { 172 size_t lendir = strlen(ah->fname); 173 size_t lenfile = strlen(ent->d_name); 174 char *path; 175 176 /* ignore emacs backups and dotfiles */ 177 if (lenfile == 0 || 178 ent->d_name[lenfile - 1] == '~' || 179 (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || 180 ent->d_name[0] == '.') 181 continue; 182 183 if ((path = whine_malloc(lendir + lenfile + 2))) 184 { 185 strcpy(path, ah->fname); 186 strcat(path, "/"); 187 strcat(path, ent->d_name); 188 189 /* ignore non-regular files */ 190 if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode)) 191 { 192 if (ah->flags & AH_HOSTS) 193 total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz); 194#ifdef HAVE_DHCP 195 else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT)) 196 option_read_dynfile(path, ah->flags); 197#endif 198 } 199 200 free(path); 201 } 202 } 203 } 204} 205 206int inotify_check(time_t now) 207{ 208 int hit = 0; 209 struct hostsfile *ah; 210 211 while (1) 212 { 213 int rc; 214 char *p; 215 struct resolvc *res; 216 struct inotify_event *in; 217 218 while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR); 219 220 if (rc <= 0) 221 break; 222 223 for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) 224 { 225 in = (struct inotify_event*)p; 226 227 for (res = daemon->resolv_files; res; res = res->next) 228 if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0) 229 hit = 1; 230 231 /* ignore emacs backups and dotfiles */ 232 if (in->len == 0 || 233 in->name[in->len - 1] == '~' || 234 (in->name[0] == '#' && in->name[in->len - 1] == '#') || 235 in->name[0] == '.') 236 continue; 237 238 for (ah = daemon->dynamic_dirs; ah; ah = ah->next) 239 if (ah->wd == in->wd) 240 { 241 size_t lendir = strlen(ah->fname); 242 char *path; 243 244 if ((path = whine_malloc(lendir + in->len + 2))) 245 { 246 strcpy(path, ah->fname); 247 strcat(path, "/"); 248 strcat(path, in->name); 249 250 my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path); 251 252 if (ah->flags & AH_HOSTS) 253 { 254 read_hostsfile(path, ah->index, 0, NULL, 0); 255#ifdef HAVE_DHCP 256 if (daemon->dhcp || daemon->doing_dhcp6) 257 { 258 /* Propogate the consequences of loading a new dhcp-host */ 259 dhcp_update_configs(daemon->dhcp_conf); 260 lease_update_from_configs(); 261 lease_update_file(now); 262 lease_update_dns(1); 263 } 264#endif 265 } 266#ifdef HAVE_DHCP 267 else if (ah->flags & AH_DHCP_HST) 268 { 269 if (option_read_dynfile(path, AH_DHCP_HST)) 270 { 271 /* Propogate the consequences of loading a new dhcp-host */ 272 dhcp_update_configs(daemon->dhcp_conf); 273 lease_update_from_configs(); 274 lease_update_file(now); 275 lease_update_dns(1); 276 } 277 } 278 else if (ah->flags & AH_DHCP_OPT) 279 option_read_dynfile(path, AH_DHCP_OPT); 280#endif 281 282 free(path); 283 } 284 } 285 } 286 } 287 return hit; 288} 289 290#endif /* INOTIFY */ 291 292