]> git.ipfire.org Git - people/ms/dnsmasq.git/blame - src/inotify.c
Fix inotify code to handle dangling symlinks better.
[people/ms/dnsmasq.git] / src / inotify.c
CommitLineData
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
36static 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 */
43static 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
86void 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 */
133void 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 204int 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