summaryrefslogtreecommitdiff
path: root/resolv/resolv_conf.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-07-03 21:06:23 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-07-03 21:06:23 +0200
commitaef16cc8a4c670036d45590877d411a97f01e0cd (patch)
tree4403f1962170ce92087a6e1af62dbb92e15d27f6 /resolv/resolv_conf.c
parenta1c4eb8794e789b5055d7ceb13b2b3231abf5e26 (diff)
resolv: Automatically reload a changed /etc/resolv.conf file [BZ #984]
This commit enhances the stub resolver to reload the configuration in the per-thread _res object if the /etc/resolv.conf file has changed. The resolver checks whether the application has modified _res and will not overwrite the _res object in that case. The struct resolv_context mechanism is used to check the configuration file only once per name lookup.
Diffstat (limited to 'resolv/resolv_conf.c')
-rw-r--r--resolv/resolv_conf.c86
1 files changed, 85 insertions, 1 deletions
diff --git a/resolv/resolv_conf.c b/resolv/resolv_conf.c
index dd66523992..9ef59240eb 100644
--- a/resolv/resolv_conf.c
+++ b/resolv/resolv_conf.c
@@ -22,6 +22,7 @@
#include <assert.h>
#include <libc-lock.h>
#include <resolv-internal.h>
+#include <sys/stat.h>
/* _res._u._ext.__glibc_extension_index is used as an index into a
struct resolv_conf_array object. The intent of this construction
@@ -54,6 +55,15 @@ struct resolv_conf_global
the array element is overwritten with NULL. */
struct resolv_conf_array array;
+ /* Cached current configuration object for /etc/resolv.conf. */
+ struct resolv_conf *conf_current;
+
+ /* These properties of /etc/resolv.conf are used to check if the
+ configuration needs reloading. */
+ struct timespec conf_mtime;
+ struct timespec conf_ctime;
+ off64_t conf_size;
+ ino64_t conf_ino;
};
/* Lazily allocated storage for struct resolv_conf_global. */
@@ -100,6 +110,75 @@ conf_decrement (struct resolv_conf *conf)
free (conf);
}
+struct resolv_conf *
+__resolv_conf_get_current (void)
+{
+ struct stat64 st;
+ if (stat64 (_PATH_RESCONF, &st) != 0)
+ {
+ switch (errno)
+ {
+ case EACCES:
+ case EISDIR:
+ case ELOOP:
+ case ENOENT:
+ case ENOTDIR:
+ case EPERM:
+ /* Ignore errors due to file system contents. */
+ memset (&st, 0, sizeof (st));
+ break;
+ default:
+ /* Other errors are fatal. */
+ return NULL;
+ }
+ }
+
+ struct resolv_conf_global *global_copy = get_locked_global ();
+ if (global_copy == NULL)
+ return NULL;
+ struct resolv_conf *conf;
+ if (global_copy->conf_current != NULL
+ && (global_copy->conf_mtime.tv_sec == st.st_mtim.tv_sec
+ && global_copy->conf_mtime.tv_nsec == st.st_mtim.tv_nsec
+ && global_copy->conf_ctime.tv_sec == st.st_ctim.tv_sec
+ && global_copy->conf_ctime.tv_nsec == st.st_ctim.tv_nsec
+ && global_copy->conf_ino == st.st_ino
+ && global_copy->conf_size == st.st_size))
+ /* We can reuse the cached configuration object. */
+ conf = global_copy->conf_current;
+ else
+ {
+ /* Parse configuration while holding the lock. This avoids
+ duplicate work. */
+ conf = __resolv_conf_load (NULL);
+ if (conf != NULL)
+ {
+ if (global_copy->conf_current != NULL)
+ conf_decrement (global_copy->conf_current);
+ global_copy->conf_current = conf; /* Takes ownership. */
+
+ /* Update file modification stamps. The configuration we
+ read could be a newer version of the file, but this does
+ not matter because this will lead to an extraneous reload
+ later. */
+ global_copy->conf_mtime = st.st_mtim;
+ global_copy->conf_ctime = st.st_ctim;
+ global_copy->conf_ino = st.st_ino;
+ global_copy->conf_size = st.st_size;
+ }
+ }
+
+ if (conf != NULL)
+ {
+ /* Return an additional reference. */
+ assert (conf->__refcount > 0);
+ ++conf->__refcount;
+ assert (conf->__refcount > 0);
+ }
+ put_locked_global (global_copy);
+ return conf;
+}
+
/* Internal implementation of __resolv_conf_get, without validation
against *RESP. */
static struct resolv_conf *
@@ -320,7 +399,6 @@ __resolv_conf_allocate (const struct resolv_conf *init)
conf->retry = init->retry;
conf->options = init->options;
conf->ndots = init->ndots;
- conf->initstamp = __res_initstamp;
/* Allocate the arrays with pointers. These must come first because
they have the highets alignment. */
@@ -580,6 +658,12 @@ freeres (void)
if (global == NULL)
return;
+ if (global->conf_current != NULL)
+ {
+ conf_decrement (global->conf_current);
+ global->conf_current = NULL;
+ }
+
/* Note that this frees only the array itself. The pointed-to
configuration objects should have been deallocated by res_nclose
and per-thread cleanup functions. */