]> git.ipfire.org Git - people/ms/dnsmasq.git/blobdiff - src/inotify.c
Fix inotify code to handle dangling symlinks better.
[people/ms/dnsmasq.git] / src / inotify.c
index 818fe8eddda47bf2b2c7a548a6066a1675de3933..f3e5c3d1689f1e66a95bce7e702c62b805ae8bf6 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
  
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #ifdef HAVE_INOTIFY
 
 #include <sys/inotify.h>
-
-#ifdef HAVE_DHCP
-static void check_for_dhcp_inotify(struct inotify_event *in, time_t now);
-#endif
-
+#include <sys/param.h> /* For MAXSYMLINKS */
 
 /* the strategy is to set a inotify on the directories containing
    resolv files, for any files in the directory which are close-write 
@@ -40,10 +36,56 @@ static void check_for_dhcp_inotify(struct inotify_event *in, time_t now);
 static char *inotify_buffer;
 #define INOTIFY_SZ (sizeof(struct inotify_event) + NAME_MAX + 1)
 
+/* If path is a symbolic link, return the path it
+   points to, made absolute if relative.
+   If path doesn't exist or is not a symlink, return NULL.
+   Return value is malloc'ed */
+static char *my_readlink(char *path)
+{
+  ssize_t rc;
+  size_t size = 64;
+  char *buf;
+
+  while (1)
+    {
+      buf = safe_malloc(size);
+      rc = readlink(path, buf, size);
+      
+      if (rc == -1)
+       {
+         /* Not link or doesn't exist. */
+         if (errno == EINVAL || errno == ENOENT)
+           return NULL;
+         else
+           die(_("cannot access path %s: %s"), path, EC_MISC);
+       }
+      else if (rc < size-1)
+       {
+         char *d;
+         
+         buf[rc] = 0;
+         if (buf[0] != '/' && ((d = strrchr(path, '/'))))
+           {
+             /* Add path to relative link */
+             char *new_buf = safe_malloc((d - path) + strlen(buf) + 2);
+             *(d+1) = 0;
+             strcpy(new_buf, path);
+             strcat(new_buf, buf);
+             free(buf);
+             buf = new_buf;
+           }
+         return buf;
+       }
+
+      /* Buffer too small, increase and retry */
+      size += 64;
+      free(buf);
+    }
+}
+
 void inotify_dnsmasq_init()
 {
   struct resolvc *res;
-
   inotify_buffer = safe_malloc(INOTIFY_SZ);
   daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
   
@@ -52,19 +94,22 @@ void inotify_dnsmasq_init()
   
   for (res = daemon->resolv_files; res; res = res->next)
     {
-      char *d = NULL, *path;
-      
-      if (!(path = realpath(res->name, NULL)))
+      char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1);
+      int links = MAXSYMLINKS;
+
+      strcpy(path, res->name);
+
+      /* Follow symlinks until we reach a non-symlink, or a non-existant file. */
+      while ((new_path = my_readlink(path)))
        {
-         /* realpath will fail if the file doesn't exist, but
-            dnsmasq copes with missing files, so fall back 
-            and assume that symlinks are not in use in that case. */
-         if (errno == ENOENT)
-           path = res->name;
-         else
-           die(_("cannot cannonicalise resolv-file %s: %s"), res->name, EC_MISC); 
+         if (links-- == 0)
+           die(_("too many symlinks following %s"), res->name, EC_MISC);
+         free(path);
+         path = new_path;
        }
-      
+
+      res->wd = -1;
+
       if ((d = strrchr(path, '/')))
        {
          *d = 0; /* make path just directory */
@@ -75,74 +120,48 @@ void inotify_dnsmasq_init()
          
          if (res->wd == -1 && errno == ENOENT)
            die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC);
-         
-         if (res->wd == -1)
-           die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
-       }
+       }         
+        
+      if (res->wd == -1)
+       die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
+       
     }
 }
 
-int inotify_check(time_t now)
+
+/* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */
+void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz)
 {
-  int hit = 0;
+  struct hostsfile *ah;
   
-  while (1)
+  for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
     {
-      int rc;
-      char *p;
-      struct resolvc *res;
-      struct inotify_event *in;
-
-      while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
-      
-      if (rc <= 0)
-       break;
-      
-      for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) 
+      DIR *dir_stream = NULL;
+      struct dirent *ent;
+      struct stat buf;
+     
+      if (!(ah->flags & flag))
+       continue;
+      if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
        {
-         in = (struct inotify_event*)p;
-         
-         for (res = daemon->resolv_files; res; res = res->next)
-           if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0)
-             hit = 1;
-
-#ifdef HAVE_DHCP
-         if (daemon->dhcp || daemon->doing_dhcp6)
-           check_for_dhcp_inotify(in, now);
-#endif
+         my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), 
+                   ah->fname, strerror(errno));
+         continue;
        }
-    }
-  return hit;
-}
-
-#ifdef HAVE_DHCP 
-/* initialisation for dhcp-hostdir. Set inotify watch for each directory, and read pre-existing files */
-void set_dhcp_inotify(void)
-{
-  struct hostsfile *ah;
-
-  for (ah = daemon->inotify_hosts; ah; ah = ah->next)
-    {
-       DIR *dir_stream = NULL;
-       struct dirent *ent;
-       struct stat buf;
-
-       if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
-        {
-          my_syslog(LOG_ERR, _("bad directory in dhcp-hostsdir %s"), ah->fname);
-          continue;
-        }
-
+      
        if (!(ah->flags & AH_WD_DONE))
         {
           ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO);
           ah->flags |= AH_WD_DONE;
         }
-       /* Read contents of dir _after_ calling add_watch, in the ho[e of avoiding
+
+       /* Read contents of dir _after_ calling add_watch, in the hope of avoiding
          a race which misses files being added as we start */
        if (ah->wd == -1 || !(dir_stream = opendir(ah->fname)))
         {
-          my_syslog(LOG_ERR, _("failed to create inotify for %s"), ah->fname);
+          my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"),
+                    ah->fname, strerror(errno));
           continue;
         }
 
@@ -167,54 +186,104 @@ void set_dhcp_inotify(void)
               
               /* ignore non-regular files */
               if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
-                option_read_hostsfile(path);
-              
+                {
+                  if (ah->flags & AH_HOSTS)
+                    total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz);
+#ifdef HAVE_DHCP
+                  else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT))
+                    option_read_dynfile(path, ah->flags);
+#endif            
+                }
+
               free(path);
             }
         }
     }
 }
 
-static void check_for_dhcp_inotify(struct inotify_event *in, time_t now)
+int inotify_check(time_t now)
 {
+  int hit = 0;
   struct hostsfile *ah;
 
-  /* ignore emacs backups and dotfiles */
-  if (in->len == 0 || 
-      in->name[in->len - 1] == '~' ||
-      (in->name[0] == '#' && in->name[in->len - 1] == '#') ||
-      in->name[0] == '.')
-    return;
-
-  for (ah = daemon->inotify_hosts; ah; ah = ah->next)
-    if (ah->wd == in->wd)
-      {
-       size_t lendir = strlen(ah->fname);
-       char *path;
-          
-       if ((path = whine_malloc(lendir + in->len + 2)))
-         {
-           strcpy(path, ah->fname);
-           strcat(path, "/");
-           strcat(path, in->name);
-           
-           if (option_read_hostsfile(path))
+  while (1)
+    {
+      int rc;
+      char *p;
+      struct resolvc *res;
+      struct inotify_event *in;
+
+      while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
+      
+      if (rc <= 0)
+       break;
+      
+      for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) 
+       {
+         in = (struct inotify_event*)p;
+         
+         for (res = daemon->resolv_files; res; res = res->next)
+           if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0)
+             hit = 1;
+
+         /* ignore emacs backups and dotfiles */
+         if (in->len == 0 || 
+             in->name[in->len - 1] == '~' ||
+             (in->name[0] == '#' && in->name[in->len - 1] == '#') ||
+             in->name[0] == '.')
+           continue;
+         
+         for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+           if (ah->wd == in->wd)
              {
-               /* Propogate the consequences of loading a new dhcp-host */
-               dhcp_update_configs(daemon->dhcp_conf);
-               lease_update_from_configs(); 
-               lease_update_file(now); 
-               lease_update_dns(1);
+               size_t lendir = strlen(ah->fname);
+               char *path;
+               
+               if ((path = whine_malloc(lendir + in->len + 2)))
+                 {
+                   strcpy(path, ah->fname);
+                   strcat(path, "/");
+                   strcat(path, in->name);
+                    
+                   my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path);
+
+                   if (ah->flags & AH_HOSTS)
+                     {
+                       read_hostsfile(path, ah->index, 0, NULL, 0);
+#ifdef HAVE_DHCP
+                       if (daemon->dhcp || daemon->doing_dhcp6) 
+                         {
+                           /* Propogate the consequences of loading a new dhcp-host */
+                           dhcp_update_configs(daemon->dhcp_conf);
+                           lease_update_from_configs(); 
+                           lease_update_file(now); 
+                           lease_update_dns(1);
+                         }
+#endif
+                     }
+#ifdef HAVE_DHCP
+                   else if (ah->flags & AH_DHCP_HST)
+                     {
+                       if (option_read_dynfile(path, AH_DHCP_HST))
+                         {
+                           /* Propogate the consequences of loading a new dhcp-host */
+                           dhcp_update_configs(daemon->dhcp_conf);
+                           lease_update_from_configs(); 
+                           lease_update_file(now); 
+                           lease_update_dns(1);
+                         }
+                     }
+                   else if (ah->flags & AH_DHCP_OPT)
+                     option_read_dynfile(path, AH_DHCP_OPT);
+#endif
+                   
+                   free(path);
+                 }
              }
-           
-           free(path);
-         }
-       
-       return;
-      }
+       }
+    }
+  return hit;
 }
 
-#endif /* DHCP */
-
 #endif  /* INOTIFY */