summaryrefslogtreecommitdiffstats
path: root/src/util/file_watch.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/file_watch.c')
-rw-r--r--src/util/file_watch.c326
1 files changed, 326 insertions, 0 deletions
diff --git a/src/util/file_watch.c b/src/util/file_watch.c
new file mode 100644
index 0000000..d19fdcc
--- /dev/null
+++ b/src/util/file_watch.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2022, Red Hat Inc.
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include "util/inotify.h"
+#include "util/util.h"
+#include "util/file_watch.h"
+
+
+
+#define MISSING_FILE_POLL_TIME 10 /* seconds */
+#define FILE_WATCH_POLL_INTERVAL 5 /* seconds */
+
+
+struct file_watch_ctx {
+ struct tevent_context *ev;
+ const char *filename;
+ bool use_inotify;
+
+ struct config_file_inotify_check {
+ struct snotify_ctx *snctx;
+ } inotify_check;
+
+ struct config_file_poll_check {
+ struct tevent_timer *timer;
+ time_t modified;
+ } poll_check;
+
+ fw_callback cb;
+ void *cb_arg;
+};
+
+
+static void poll_watched_file(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr);
+
+static errno_t create_poll_timer(struct file_watch_ctx *fw_ctx)
+{
+ struct timeval tv;
+
+ tv = tevent_timeval_current_ofs(FILE_WATCH_POLL_INTERVAL, 0);
+
+ fw_ctx->poll_check.timer = tevent_add_timer(fw_ctx->ev,
+ fw_ctx,
+ tv,
+ poll_watched_file,
+ fw_ctx);
+ if (!fw_ctx->poll_check.timer) {
+ return EIO;
+ }
+
+ return EOK;
+}
+
+static void poll_watched_file(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ int ret, err;
+ struct stat file_stat;
+ struct file_watch_ctx *fw_ctx;
+
+ fw_ctx = talloc_get_type(ptr, struct file_watch_ctx);
+
+ ret = stat(fw_ctx->filename, &file_stat);
+ if (ret < 0) {
+ err = errno;
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "Could not stat file [%s]. Error [%d:%s]\n",
+ fw_ctx->filename, err, strerror(err));
+ return;
+ }
+
+ if (file_stat.st_mtime != fw_ctx->poll_check.modified) {
+ /* Parse the file and invoke the callback */
+ /* Note: this will fire if the modification time changes into the past
+ * as well as the future.
+ */
+ DEBUG(SSSDBG_TRACE_INTERNAL, "File [%s] changed\n", fw_ctx->filename);
+ fw_ctx->poll_check.modified = file_stat.st_mtime;
+
+ /* Tell the caller the file changed */
+ fw_ctx->cb(fw_ctx->filename, fw_ctx->cb_arg);
+ }
+
+ ret = create_poll_timer(fw_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "Error: File [%s] no longer monitored for changes!\n",
+ fw_ctx->filename);
+ }
+}
+
+
+static int watched_file_inotify_cb(const char *filename,
+ uint32_t flags,
+ void *pvt)
+{
+ static char received[PATH_MAX + 1];
+ static char expected[PATH_MAX + 1];
+ struct file_watch_ctx *fw_ctx;
+ char *res;
+
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Received inotify notification for %s\n", filename);
+
+ fw_ctx = talloc_get_type(pvt, struct file_watch_ctx);
+ if (fw_ctx == NULL) {
+ return EINVAL;
+ }
+
+ res = realpath(fw_ctx->filename, expected);
+ if (res == NULL) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Normalization failed for expected %s. Skipping the callback.\n",
+ fw_ctx->filename);
+ goto done;
+ }
+
+ res = realpath(filename, received);
+ if (res == NULL) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Normalization failed for received %s. Skipping the callback.\n",
+ filename);
+ goto done;
+ }
+
+ if (strcmp(expected, received) == 0) {
+ if (access(received, F_OK) == 0) {
+ fw_ctx->cb(received, fw_ctx->cb_arg);
+ } else {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "File %s is missing. Skipping the callback.\n", filename);
+ }
+ }
+
+done:
+ return EOK;
+}
+
+
+static int try_inotify(struct file_watch_ctx *fw_ctx)
+{
+#ifdef HAVE_INOTIFY
+ struct snotify_ctx *snctx;
+ /* We will queue the file for update in one second.
+ * This way, if there is a script writing to the file
+ * repeatedly, we won't be attempting to update multiple
+ * times.
+ */
+ struct timeval delay = { .tv_sec = 1, .tv_usec = 0 };
+
+ snctx = snotify_create(fw_ctx, fw_ctx->ev, SNOTIFY_WATCH_DIR,
+ fw_ctx->filename, &delay,
+ IN_DELETE_SELF | IN_CLOSE_WRITE | IN_MOVE_SELF | \
+ IN_CREATE | IN_MOVED_TO | IN_IGNORED,
+ watched_file_inotify_cb, fw_ctx);
+ if (snctx == NULL) {
+ return EIO;
+ }
+
+ return EOK;
+#else
+ return EINVAL;
+#endif /* HAVE_INOTIFY */
+}
+
+static errno_t fw_watch_file_poll(struct file_watch_ctx *fw_ctx)
+{
+ struct stat file_stat;
+ int ret, err;
+
+ ret = stat(fw_ctx->filename, &file_stat);
+ if (ret < 0) {
+ err = errno;
+ if (err == ENOENT) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "File [%s] is missing. Will try again later.\n",
+ fw_ctx->filename);
+ } else {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "Could not stat file [%s]. Error [%d:%s]\n",
+ fw_ctx->filename, err, strerror(err));
+ }
+ return err;
+ }
+
+ fw_ctx->poll_check.modified = file_stat.st_mtime;
+
+ if(!fw_ctx->poll_check.timer) {
+ ret = create_poll_timer(fw_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Cannot create poll timer\n");
+ return ret;
+ }
+ }
+
+ return EOK;
+}
+
+
+static int watch_file(struct file_watch_ctx *fw_ctx)
+{
+ int ret = EOK;
+ bool use_inotify;
+
+ use_inotify = fw_ctx->use_inotify;
+ if (use_inotify) {
+ ret = try_inotify(fw_ctx);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Falling back to polling\n");
+ use_inotify = false;
+ }
+ }
+
+ if (!use_inotify) {
+ ret = fw_watch_file_poll(fw_ctx);
+ }
+
+ return ret;
+}
+
+
+static void set_file_watching(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *data)
+{
+ int ret;
+ struct file_watch_ctx *fw_ctx = talloc_get_type(data, struct file_watch_ctx);
+
+ ret = watch_file(fw_ctx);
+ if (ret == EOK) {
+ if (access(fw_ctx->filename, F_OK) == 0) {
+ fw_ctx->cb(fw_ctx->filename, fw_ctx->cb_arg);
+ }
+ } else if (ret == ENOENT) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "%s missing. Waiting for it to appear.\n",
+ fw_ctx->filename);
+ tv = tevent_timeval_current_ofs(MISSING_FILE_POLL_TIME, 0);
+ te = tevent_add_timer(fw_ctx->ev, fw_ctx, tv, set_file_watching, fw_ctx);
+ if (te == NULL) {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "tevent_add_timer failed. %s will be ignored.\n",
+ fw_ctx->filename);
+ }
+ } else {
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "watch_file failed. %s will be ignored: [%i] %s\n",
+ fw_ctx->filename, ret, sss_strerror(ret));
+ }
+}
+
+
+struct file_watch_ctx *fw_watch_file(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *filename,
+ bool use_inotify,
+ fw_callback cb,
+ void *cb_arg)
+{
+ int ret;
+ struct timeval tv;
+ struct file_watch_ctx *fw_ctx;
+
+ if (ev == NULL || filename == NULL || cb == NULL) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Invalid parameter\n");
+ return NULL;
+ }
+
+ fw_ctx = talloc_zero(mem_ctx, struct file_watch_ctx);
+ if (fw_ctx == NULL) {
+ DEBUG(SSSDBG_IMPORTANT_INFO, "Failed to allocate the context\n");
+ return NULL;
+ }
+
+ fw_ctx->ev = ev;
+ fw_ctx->use_inotify = use_inotify;
+ fw_ctx->cb = cb;
+ fw_ctx->cb_arg = cb_arg;
+ fw_ctx->filename = talloc_strdup(fw_ctx, filename);
+ if (fw_ctx->filename == NULL) {
+ DEBUG(SSSDBG_IMPORTANT_INFO, "talloc_strdup() failed\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Watch for changes to the requested file, and retry periodically
+ * if the file does not exist */
+ tv = tevent_timeval_current_ofs(0, 0); // Not actually used
+ set_file_watching(fw_ctx->ev, NULL, tv, fw_ctx);
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ talloc_free(fw_ctx);
+ fw_ctx = NULL;
+ }
+
+ return fw_ctx;
+}