]>
Commit | Line | Data |
---|---|---|
aff33962 | 1 | /* dnsmasq is Copyright (c) 2000-2015 Simon Kelley |
193de4ab SK |
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" | |
0491805d | 18 | #ifdef HAVE_INOTIFY |
193de4ab | 19 | |
d310ab7e | 20 | #include <sys/inotify.h> |
362c9303 | 21 | #include <sys/param.h> /* For MAXSYMLINKS */ |
d310ab7e | 22 | |
193de4ab SK |
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 | ||
36 | static char *inotify_buffer; | |
37 | #define INOTIFY_SZ (sizeof(struct inotify_event) + NAME_MAX + 1) | |
38 | ||
362c9303 SK |
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 */ | |
43 | static char *my_readlink(char *path) | |
44 | { | |
45 | ssize_t rc; | |
46 | size_t size = 64; | |
47 | char *buf; | |
48 | ||
49 | while (1) | |
50 | { | |
51 | buf = safe_malloc(size); | |
52 | rc = readlink(path, buf, size); | |
53 | ||
54 | if (rc == -1) | |
55 | { | |
56 | /* Not link or doesn't exist. */ | |
57 | if (errno == EINVAL || errno == ENOENT) | |
58 | return NULL; | |
59 | else | |
60 | die(_("cannot access path %s: %s"), path, EC_MISC); | |
61 | } | |
62 | else if (rc < size-1) | |
63 | { | |
64 | char *d; | |
65 | ||
66 | buf[rc] = 0; | |
67 | if (buf[0] != '/' && ((d = strrchr(path, '/')))) | |
68 | { | |
69 | /* Add path to relative link */ | |
70 | char *new_buf = safe_malloc((d - path) + strlen(buf) + 2); | |
71 | *(d+1) = 0; | |
72 | strcpy(new_buf, path); | |
73 | strcat(new_buf, buf); | |
74 | free(buf); | |
75 | buf = new_buf; | |
76 | } | |
77 | return buf; | |
78 | } | |
79 | ||
80 | /* Buffer too small, increase and retry */ | |
81 | size += 64; | |
82 | free(buf); | |
83 | } | |
84 | } | |
85 | ||
193de4ab SK |
86 | void inotify_dnsmasq_init() |
87 | { | |
88 | struct resolvc *res; | |
193de4ab | 89 | inotify_buffer = safe_malloc(INOTIFY_SZ); |
857973e6 SK |
90 | daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); |
91 | ||
193de4ab SK |
92 | if (daemon->inotifyfd == -1) |
93 | die(_("failed to create inotify: %s"), NULL, EC_MISC); | |
857973e6 | 94 | |
193de4ab SK |
95 | for (res = daemon->resolv_files; res; res = res->next) |
96 | { | |
362c9303 SK |
97 | char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1); |
98 | int links = MAXSYMLINKS; | |
99 | ||
100 | strcpy(path, res->name); | |
101 | ||
102 | /* Follow symlinks until we reach a non-symlink, or a non-existant file. */ | |
103 | while ((new_path = my_readlink(path))) | |
857973e6 | 104 | { |
362c9303 SK |
105 | if (links-- == 0) |
106 | die(_("too many symlinks following %s"), res->name, EC_MISC); | |
107 | free(path); | |
108 | path = new_path; | |
857973e6 | 109 | } |
362c9303 SK |
110 | |
111 | res->wd = -1; | |
112 | ||
857973e6 SK |
113 | if ((d = strrchr(path, '/'))) |
114 | { | |
115 | *d = 0; /* make path just directory */ | |
116 | res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO); | |
5f4dc5c6 | 117 | |
857973e6 SK |
118 | res->file = d+1; /* pointer to filename */ |
119 | *d = '/'; | |
120 | ||
121 | if (res->wd == -1 && errno == ENOENT) | |
122 | die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC); | |
362c9303 SK |
123 | } |
124 | ||
125 | if (res->wd == -1) | |
126 | die(_("failed to create inotify for %s: %s"), res->name, EC_MISC); | |
127 | ||
193de4ab SK |
128 | } |
129 | } | |
130 | ||
70d1873d SK |
131 | |
132 | /* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */ | |
133 | void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz) | |
193de4ab | 134 | { |
70d1873d | 135 | struct hostsfile *ah; |
193de4ab | 136 | |
70d1873d | 137 | for (ah = daemon->dynamic_dirs; ah; ah = ah->next) |
193de4ab | 138 | { |
70d1873d SK |
139 | DIR *dir_stream = NULL; |
140 | struct dirent *ent; | |
141 | struct stat buf; | |
142 | ||
143 | if (!(ah->flags & flag)) | |
144 | continue; | |
145 | ||
146 | if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode))) | |
193de4ab | 147 | { |
70d1873d SK |
148 | my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), |
149 | ah->fname, strerror(errno)); | |
150 | continue; | |
193de4ab | 151 | } |
70d1873d | 152 | |
5f4dc5c6 SK |
153 | if (!(ah->flags & AH_WD_DONE)) |
154 | { | |
155 | ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO); | |
156 | ah->flags |= AH_WD_DONE; | |
157 | } | |
8ff70de6 SK |
158 | |
159 | /* Read contents of dir _after_ calling add_watch, in the hope of avoiding | |
5f4dc5c6 SK |
160 | a race which misses files being added as we start */ |
161 | if (ah->wd == -1 || !(dir_stream = opendir(ah->fname))) | |
162 | { | |
70d1873d SK |
163 | my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"), |
164 | ah->fname, strerror(errno)); | |
5f4dc5c6 SK |
165 | continue; |
166 | } | |
167 | ||
168 | while ((ent = readdir(dir_stream))) | |
169 | { | |
170 | size_t lendir = strlen(ah->fname); | |
171 | size_t lenfile = strlen(ent->d_name); | |
172 | char *path; | |
173 | ||
174 | /* ignore emacs backups and dotfiles */ | |
175 | if (lenfile == 0 || | |
176 | ent->d_name[lenfile - 1] == '~' || | |
177 | (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') || | |
178 | ent->d_name[0] == '.') | |
179 | continue; | |
180 | ||
181 | if ((path = whine_malloc(lendir + lenfile + 2))) | |
182 | { | |
183 | strcpy(path, ah->fname); | |
184 | strcat(path, "/"); | |
185 | strcat(path, ent->d_name); | |
186 | ||
187 | /* ignore non-regular files */ | |
188 | if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode)) | |
70d1873d SK |
189 | { |
190 | if (ah->flags & AH_HOSTS) | |
191 | total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz); | |
192 | #ifdef HAVE_DHCP | |
193 | else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT)) | |
194 | option_read_dynfile(path, ah->flags); | |
195 | #endif | |
196 | } | |
197 | ||
5f4dc5c6 SK |
198 | free(path); |
199 | } | |
200 | } | |
201 | } | |
202 | } | |
203 | ||
70d1873d | 204 | int inotify_check(time_t now) |
5f4dc5c6 | 205 | { |
70d1873d | 206 | int hit = 0; |
5f4dc5c6 SK |
207 | struct hostsfile *ah; |
208 | ||
70d1873d SK |
209 | while (1) |
210 | { | |
211 | int rc; | |
212 | char *p; | |
213 | struct resolvc *res; | |
214 | struct inotify_event *in; | |
215 | ||
216 | while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR); | |
217 | ||
218 | if (rc <= 0) | |
219 | break; | |
220 | ||
221 | for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) | |
222 | { | |
223 | in = (struct inotify_event*)p; | |
224 | ||
225 | for (res = daemon->resolv_files; res; res = res->next) | |
226 | if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0) | |
227 | hit = 1; | |
228 | ||
229 | /* ignore emacs backups and dotfiles */ | |
230 | if (in->len == 0 || | |
231 | in->name[in->len - 1] == '~' || | |
232 | (in->name[0] == '#' && in->name[in->len - 1] == '#') || | |
233 | in->name[0] == '.') | |
234 | continue; | |
235 | ||
236 | for (ah = daemon->dynamic_dirs; ah; ah = ah->next) | |
237 | if (ah->wd == in->wd) | |
5f4dc5c6 | 238 | { |
70d1873d SK |
239 | size_t lendir = strlen(ah->fname); |
240 | char *path; | |
241 | ||
242 | if ((path = whine_malloc(lendir + in->len + 2))) | |
243 | { | |
244 | strcpy(path, ah->fname); | |
245 | strcat(path, "/"); | |
246 | strcat(path, in->name); | |
f9c86370 SK |
247 | |
248 | my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path); | |
249 | ||
70d1873d | 250 | if (ah->flags & AH_HOSTS) |
2941d3ac SK |
251 | { |
252 | read_hostsfile(path, ah->index, 0, NULL, 0); | |
253 | #ifdef HAVE_DHCP | |
254 | if (daemon->dhcp || daemon->doing_dhcp6) | |
255 | { | |
256 | /* Propogate the consequences of loading a new dhcp-host */ | |
257 | dhcp_update_configs(daemon->dhcp_conf); | |
258 | lease_update_from_configs(); | |
259 | lease_update_file(now); | |
260 | lease_update_dns(1); | |
261 | } | |
262 | #endif | |
263 | } | |
70d1873d SK |
264 | #ifdef HAVE_DHCP |
265 | else if (ah->flags & AH_DHCP_HST) | |
266 | { | |
267 | if (option_read_dynfile(path, AH_DHCP_HST)) | |
268 | { | |
269 | /* Propogate the consequences of loading a new dhcp-host */ | |
270 | dhcp_update_configs(daemon->dhcp_conf); | |
271 | lease_update_from_configs(); | |
272 | lease_update_file(now); | |
273 | lease_update_dns(1); | |
274 | } | |
275 | } | |
276 | else if (ah->flags & AH_DHCP_OPT) | |
277 | option_read_dynfile(path, AH_DHCP_OPT); | |
278 | #endif | |
279 | ||
280 | free(path); | |
281 | } | |
5f4dc5c6 | 282 | } |
70d1873d SK |
283 | } |
284 | } | |
285 | return hit; | |
5f4dc5c6 SK |
286 | } |
287 | ||
0491805d | 288 | #endif /* INOTIFY */ |
193de4ab | 289 |