Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / src / inotify.c
1 /* dnsmasq is Copyright (c) 2000-2022 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 an 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
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, 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             {
58               free(buf);
59               return NULL;
60             }
61           else
62             die(_("cannot access path %s: %s"), path, EC_MISC);
63         }
64       else if (rc < size-1)
65         {
66           char *d;
67           
68           buf[rc] = 0;
69           if (buf[0] != '/' && ((d = strrchr(path, '/'))))
70             {
71               /* Add path to relative link */
72               char *new_buf = safe_malloc((d - path) + strlen(buf) + 2);
73               *(d+1) = 0;
74               strcpy(new_buf, path);
75               strcat(new_buf, buf);
76               free(buf);
77               buf = new_buf;
78             }
79           return buf;
80         }
81
82       /* Buffer too small, increase and retry */
83       size += 64;
84       free(buf);
85     }
86 }
87
88 void inotify_dnsmasq_init()
89 {
90   struct resolvc *res;
91   inotify_buffer = safe_malloc(INOTIFY_SZ);
92   daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
93   
94   if (daemon->inotifyfd == -1)
95     die(_("failed to create inotify: %s"), NULL, EC_MISC);
96
97   if (option_bool(OPT_NO_RESOLV))
98     return;
99   
100   for (res = daemon->resolv_files; res; res = res->next)
101     {
102       char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1);
103       int links = MAXSYMLINKS;
104
105       strcpy(path, res->name);
106
107       /* Follow symlinks until we reach a non-symlink, or a non-existent file. */
108       while ((new_path = my_readlink(path)))
109         {
110           if (links-- == 0)
111             die(_("too many symlinks following %s"), res->name, EC_MISC);
112           free(path);
113           path = new_path;
114         }
115
116       res->wd = -1;
117
118       if ((d = strrchr(path, '/')))
119         {
120           *d = 0; /* make path just directory */
121           res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO);
122
123           res->file = d+1; /* pointer to filename */
124           *d = '/';
125           
126           if (res->wd == -1 && errno == ENOENT)
127             die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC);
128         }         
129          
130       if (res->wd == -1)
131         die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
132         
133     }
134 }
135
136 static struct hostsfile *dyndir_addhosts(struct dyndir *dd, char *path)
137 {
138   /* Check if this file is already known in dd->files */
139   struct hostsfile *ah = NULL;
140   for(ah = dd->files; ah; ah = ah->next)
141     if(ah && ah->fname && strcmp(path, ah->fname) == 0)
142       return ah;
143
144   /* Not known, create new hostsfile record for this dyndir */
145   struct hostsfile *newah = NULL;
146   if(!(newah = whine_malloc(sizeof(struct hostsfile))))
147     return NULL;
148
149   /* Add this file to the tip of the linked list */
150   newah->next = dd->files;
151   dd->files = newah;
152
153   /* Copy flags, set index and the full file path */
154   newah->flags = dd->flags;
155   newah->index = daemon->host_index++;
156   newah->fname = path;
157
158   return newah;
159 }
160
161
162 /* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */
163 void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz)
164 {
165   struct dyndir *dd;
166
167   for (dd = daemon->dynamic_dirs; dd; dd = dd->next)
168     {
169       DIR *dir_stream = NULL;
170       struct dirent *ent;
171       struct stat buf;
172
173       if (!(dd->flags & flag))
174         continue;
175
176       if (stat(dd->dname, &buf) == -1)
177         {
178           my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), 
179                     dd->dname, strerror(errno));
180           continue;
181         }
182
183       if (!(S_ISDIR(buf.st_mode)))
184         {
185           my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), 
186                     dd->dname, _("not a directory"));
187           continue;
188         }
189
190        if (!(dd->flags & AH_WD_DONE))
191          {
192            dd->wd = inotify_add_watch(daemon->inotifyfd, dd->dname, IN_CLOSE_WRITE | IN_MOVED_TO | IN_DELETE);
193            dd->flags |= AH_WD_DONE;
194          }
195
196        /* Read contents of dir _after_ calling add_watch, in the hope of avoiding
197           a race which misses files being added as we start */
198        if (dd->wd == -1 || !(dir_stream = opendir(dd->dname)))
199          {
200            my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"),
201                      dd->dname, strerror(errno));
202            continue;
203          }
204
205        while ((ent = readdir(dir_stream)))
206          {
207            size_t lendir = strlen(dd->dname);
208            size_t lenfile = strlen(ent->d_name);
209            char *path;
210
211            /* ignore emacs backups and dotfiles */
212            if (lenfile == 0 || 
213                ent->d_name[lenfile - 1] == '~' ||
214                (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') ||
215                ent->d_name[0] == '.')
216              continue;
217
218            if ((path = whine_malloc(lendir + lenfile + 2)))
219              {
220                struct hostsfile *ah;
221
222                strcpy(path, dd->dname);
223                strcat(path, "/");
224                strcat(path, ent->d_name);
225
226                if (!(ah = dyndir_addhosts(dd, path)))
227                  {
228                    free(path);
229                    continue;
230                  }
231                
232                /* ignore non-regular files */
233                if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
234                  {
235                    if (dd->flags & AH_HOSTS)
236                      total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz);
237 #ifdef HAVE_DHCP
238                    else if (dd->flags & (AH_DHCP_HST | AH_DHCP_OPT))
239                      option_read_dynfile(path, dd->flags);
240 #endif             
241                  }
242              }
243          }
244
245        closedir(dir_stream);
246     }
247 }
248
249 int inotify_check(time_t now)
250 {
251   int hit = 0;
252   struct dyndir *dd;
253
254   while (1)
255     {
256       int rc;
257       char *p;
258       struct resolvc *res;
259       struct inotify_event *in;
260
261       while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
262       
263       if (rc <= 0)
264         break;
265       
266       for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) 
267         {
268           size_t namelen;
269
270           in = (struct inotify_event*)p;
271           
272           /* ignore emacs backups and dotfiles */
273           if (in->len == 0 || (namelen = strlen(in->name)) == 0 ||
274               in->name[namelen - 1] == '~' ||
275               (in->name[0] == '#' && in->name[namelen - 1] == '#') ||
276               in->name[0] == '.')
277             continue;
278
279           for (res = daemon->resolv_files; res; res = res->next)
280             if (res->wd == in->wd && strcmp(res->file, in->name) == 0)
281               hit = 1;
282
283           for (dd = daemon->dynamic_dirs; dd; dd = dd->next)
284             if (dd->wd == in->wd)
285               {
286                 size_t lendir = strlen(dd->dname);
287                 char *path;
288                                 
289                 if ((path = whine_malloc(lendir + in->len + 2)))
290                   {
291                     struct hostsfile *ah = NULL;
292
293                     strcpy(path, dd->dname);
294                     strcat(path, "/");
295                     strcat(path, in->name);
296
297                     /* Is this is a deletion event? */
298                     if (in->mask & IN_DELETE)
299                       my_syslog(LOG_INFO, _("inotify: %s removed"), path);
300                     else 
301                       my_syslog(LOG_INFO, _("inotify: %s new or modified"), path);
302
303                     if (dd->flags & AH_HOSTS)
304                       {
305                         if ((ah = dyndir_addhosts(dd, path)))
306                           {
307                             const unsigned int removed = cache_remove_uid(ah->index);
308                             if (removed > 0)
309                               my_syslog(LOG_INFO, _("inotify: flushed %u names read from %s"), removed, path);
310
311                             /* (Re-)load hostsfile only if this event isn't triggered by deletion */
312                             if (!(in->mask & IN_DELETE))
313                               read_hostsfile(path, ah->index, 0, NULL, 0);
314 #ifdef HAVE_DHCP
315                             if (daemon->dhcp || daemon->doing_dhcp6) 
316                               {
317                                 /* Propagate the consequences of loading a new dhcp-host */
318                                 dhcp_update_configs(daemon->dhcp_conf);
319                                 lease_update_from_configs(); 
320                                 lease_update_file(now); 
321                                 lease_update_dns(1);
322                               }
323 #endif
324                           }
325                       }
326 #ifdef HAVE_DHCP
327                     else if (dd->flags & AH_DHCP_HST)
328                       {
329                         if (option_read_dynfile(path, AH_DHCP_HST))
330                           {
331                             /* Propagate the consequences of loading a new dhcp-host */
332                             dhcp_update_configs(daemon->dhcp_conf);
333                             lease_update_from_configs(); 
334                             lease_update_file(now); 
335                             lease_update_dns(1);
336                           }
337                       }
338                     else if (dd->flags & AH_DHCP_OPT)
339                       option_read_dynfile(path, AH_DHCP_OPT);
340 #endif
341                     
342                     if (!ah)
343                       free(path);
344                   }
345               }
346         }
347     }
348   return hit;
349 }
350
351 #endif  /* INOTIFY */