Gitweb: https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=fededfbbbc5ed79c509a34... Commit: fededfbbbc5ed79c509a34a0f8a681fa27e49533 Parent: 0524829af69935070b7b15c1bede8f7f4afc9a13 Author: Joe Thornber ejt@redhat.com AuthorDate: Thu Jun 14 14:27:19 2018 +0100 Committer: Joe Thornber ejt@redhat.com CommitterDate: Thu Jun 14 14:27:19 2018 +0100
dmfilemapd: Move to libdm/dm-tools
No longer uses any lvm code. --- configure | 3 +- configure.ac | 1 - daemons/Makefile.in | 6 +- daemons/dmfilemapd/.gitignore | 1 - daemons/dmfilemapd/Makefile.in | 65 --- daemons/dmfilemapd/dmfilemapd.c | 836 --------------------------------------- libdm/dm-tools/.gitignore | 1 + libdm/dm-tools/Makefile.in | 42 ++- libdm/dm-tools/dmfilemapd.c | 835 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 876 insertions(+), 914 deletions(-)
diff --git a/configure b/configure index ed3a315..4536b03 100755 --- a/configure +++ b/configure @@ -13611,7 +13611,7 @@ _ACEOF
################################################################################ -ac_config_files="$ac_config_files Makefile make.tmpl libdm/make.tmpl daemons/Makefile daemons/cmirrord/Makefile daemons/dmeventd/Makefile daemons/dmeventd/libdevmapper-event.pc daemons/dmeventd/plugins/Makefile daemons/dmeventd/plugins/lvm2/Makefile daemons/dmeventd/plugins/raid/Makefile daemons/dmeventd/plugins/mirror/Makefile daemons/dmeventd/plugins/snapshot/Makefile daemons/dmeventd/plugins/thin/Makefile daemons/dmfilemapd/Makefile daemons/lvmdbusd/Makefile daemons/lvmdbusd/lvmdbusd daemons/lvmdbusd/lvmdb.py daemons/lvmdbusd/lvm_shell_proxy.py daemons/lvmdbusd/path.py daemons/lvmetad/Makefile daemons/lvmpolld/Makefile daemons/lvmlockd/Makefile conf/Makefile conf/example.conf conf/lvmlocal.conf conf/command_profile_template.profile conf/metadata_profile_template.profile include/Makefile lib/Makefile include/lvm-version.h libdaemon/Makefile libdaemon/client/Makefile libdaemon/server/Makefile libdm/Makefile libdm/dm-tools/Makefile libdm/libdevmapper.pc man/Makefile po/Makefile scri pts/blkdeactivate.sh scripts/blk_availability_init_red_hat scripts/blk_availability_systemd_red_hat.service scripts/cmirrord_init_red_hat scripts/com.redhat.lvmdbus1.service scripts/dm_event_systemd_red_hat.service scripts/dm_event_systemd_red_hat.socket scripts/lvm2_cmirrord_systemd_red_hat.service scripts/lvm2_lvmdbusd_systemd_red_hat.service scripts/lvm2_lvmetad_init_red_hat scripts/lvm2_lvmetad_systemd_red_hat.service scripts/lvm2_lvmetad_systemd_red_hat.socket scripts/lvm2_lvmpolld_init_red_hat scripts/lvm2_lvmpolld_systemd_red_hat.service scripts/lvm2_lvmpolld_systemd_red_hat.socket scripts/lvm2_lvmlockd_systemd_red_hat.service scripts/lvm2_monitoring_init_red_hat scripts/lvm2_monitoring_systemd_red_hat.service scripts/lvm2_pvscan_systemd_red_hat@.service scripts/lvm2_tmpfiles_red_hat.conf scripts/lvmdump.sh scripts/Makefile test/Makefile tools/Makefile udev/Makefile" +ac_config_files="$ac_config_files Makefile make.tmpl libdm/make.tmpl daemons/Makefile daemons/cmirrord/Makefile daemons/dmeventd/Makefile daemons/dmeventd/libdevmapper-event.pc daemons/dmeventd/plugins/Makefile daemons/dmeventd/plugins/lvm2/Makefile daemons/dmeventd/plugins/raid/Makefile daemons/dmeventd/plugins/mirror/Makefile daemons/dmeventd/plugins/snapshot/Makefile daemons/dmeventd/plugins/thin/Makefile daemons/lvmdbusd/Makefile daemons/lvmdbusd/lvmdbusd daemons/lvmdbusd/lvmdb.py daemons/lvmdbusd/lvm_shell_proxy.py daemons/lvmdbusd/path.py daemons/lvmetad/Makefile daemons/lvmpolld/Makefile daemons/lvmlockd/Makefile conf/Makefile conf/example.conf conf/lvmlocal.conf conf/command_profile_template.profile conf/metadata_profile_template.profile include/Makefile lib/Makefile include/lvm-version.h libdaemon/Makefile libdaemon/client/Makefile libdaemon/server/Makefile libdm/Makefile libdm/dm-tools/Makefile libdm/libdevmapper.pc man/Makefile po/Makefile scripts/blkdeactivate.sh scripts /blk_availability_init_red_hat scripts/blk_availability_systemd_red_hat.service scripts/cmirrord_init_red_hat scripts/com.redhat.lvmdbus1.service scripts/dm_event_systemd_red_hat.service scripts/dm_event_systemd_red_hat.socket scripts/lvm2_cmirrord_systemd_red_hat.service scripts/lvm2_lvmdbusd_systemd_red_hat.service scripts/lvm2_lvmetad_init_red_hat scripts/lvm2_lvmetad_systemd_red_hat.service scripts/lvm2_lvmetad_systemd_red_hat.socket scripts/lvm2_lvmpolld_init_red_hat scripts/lvm2_lvmpolld_systemd_red_hat.service scripts/lvm2_lvmpolld_systemd_red_hat.socket scripts/lvm2_lvmlockd_systemd_red_hat.service scripts/lvm2_monitoring_init_red_hat scripts/lvm2_monitoring_systemd_red_hat.service scripts/lvm2_pvscan_systemd_red_hat@.service scripts/lvm2_tmpfiles_red_hat.conf scripts/lvmdump.sh scripts/Makefile test/Makefile tools/Makefile udev/Makefile"
cat >confcache <<_ACEOF # This file is a shell script that caches the results of configure @@ -14319,7 +14319,6 @@ do "daemons/dmeventd/plugins/mirror/Makefile") CONFIG_FILES="$CONFIG_FILES daemons/dmeventd/plugins/mirror/Makefile" ;; "daemons/dmeventd/plugins/snapshot/Makefile") CONFIG_FILES="$CONFIG_FILES daemons/dmeventd/plugins/snapshot/Makefile" ;; "daemons/dmeventd/plugins/thin/Makefile") CONFIG_FILES="$CONFIG_FILES daemons/dmeventd/plugins/thin/Makefile" ;; - "daemons/dmfilemapd/Makefile") CONFIG_FILES="$CONFIG_FILES daemons/dmfilemapd/Makefile" ;; "daemons/lvmdbusd/Makefile") CONFIG_FILES="$CONFIG_FILES daemons/lvmdbusd/Makefile" ;; "daemons/lvmdbusd/lvmdbusd") CONFIG_FILES="$CONFIG_FILES daemons/lvmdbusd/lvmdbusd" ;; "daemons/lvmdbusd/lvmdb.py") CONFIG_FILES="$CONFIG_FILES daemons/lvmdbusd/lvmdb.py" ;; diff --git a/configure.ac b/configure.ac index 42f19fd..05b13e0 100644 --- a/configure.ac +++ b/configure.ac @@ -1764,7 +1764,6 @@ daemons/dmeventd/plugins/raid/Makefile daemons/dmeventd/plugins/mirror/Makefile daemons/dmeventd/plugins/snapshot/Makefile daemons/dmeventd/plugins/thin/Makefile -daemons/dmfilemapd/Makefile daemons/lvmdbusd/Makefile daemons/lvmdbusd/lvmdbusd daemons/lvmdbusd/lvmdb.py diff --git a/daemons/Makefile.in b/daemons/Makefile.in index 8edaefa..5226a5e 100644 --- a/daemons/Makefile.in +++ b/daemons/Makefile.in @@ -44,12 +44,8 @@ ifeq ("@BUILD_LVMDBUSD@", "yes") SUBDIRS += lvmdbusd endif
-ifeq ("@BUILD_DMFILEMAPD@", "yes") - SUBDIRS += dmfilemapd -endif - ifeq ($(MAKECMDGOALS),distclean) - SUBDIRS = cmirrord dmeventd lvmetad lvmpolld lvmlockd lvmdbusd dmfilemapd + SUBDIRS = cmirrord dmeventd lvmetad lvmpolld lvmlockd lvmdbusd endif
include $(top_builddir)/make.tmpl diff --git a/daemons/dmfilemapd/.gitignore b/daemons/dmfilemapd/.gitignore deleted file mode 100644 index 6dcde30..0000000 --- a/daemons/dmfilemapd/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dmfilemapd diff --git a/daemons/dmfilemapd/Makefile.in b/daemons/dmfilemapd/Makefile.in deleted file mode 100644 index 1afd6b8..0000000 --- a/daemons/dmfilemapd/Makefile.in +++ /dev/null @@ -1,65 +0,0 @@ -# -# Copyright (C) 2016 Red Hat, Inc. All rights reserved. -# -# This file is part of the device-mapper userspace tools. -# -# This copyrighted material is made available to anyone wishing to use, -# modify, copy, or redistribute it subject to the terms and conditions -# of the GNU Lesser General Public License v.2.1. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -top_builddir = @top_builddir@ - -SOURCES = dmfilemapd.c - -TARGETS = dmfilemapd - -.PHONY: install_dmfilemapd install_dmfilemapd_static - -INSTALL_DMFILEMAPD_TARGETS = install_dmfilemapd_dynamic - -CLEAN_TARGETS = dmfilemapd.static - -CFLOW_LIST = $(SOURCES) -CFLOW_LIST_TARGET = $(LIB_NAME).cflow -CFLOW_TARGET = dmfilemapd - -include $(top_builddir)/make.tmpl - -all: device-mapper -device-mapper: $(TARGETS) - -CFLAGS_dmfilemapd.o += $(EXTRA_EXEC_CFLAGS) - -dmfilemapd: $(LIB_SHARED) dmfilemapd.o - $(CC) $(CFLAGS) $(LDFLAGS) $(EXTRA_EXEC_LDFLAGS) $(ELDFLAGS) \ - -o $@ dmfilemapd.o $(DL_LIBS) $(LIBS) - -dmfilemapd.static: $(LIB_STATIC) dmfilemapd.o - $(CC) $(CFLAGS) $(LDFLAGS) $(ELDFLAGS) -static -L$(interfacebuilddir) \ - -o $@ dmfilemapd.o $(DL_LIBS) $(LIBS) $(STATIC_LIBS) - -ifneq ("$(CFLOW_CMD)", "") -CFLOW_SOURCES = $(addprefix $(srcdir)/, $(SOURCES)) --include $(top_builddir)/libdm/libdevmapper.cflow --include $(top_builddir)/lib/liblvm-internal.cflow --include $(top_builddir)/lib/liblvm2cmd.cflow --include $(top_builddir)/daemons/dmfilemapd/$(LIB_NAME).cflow -endif - -install_dmfilemapd_dynamic: dmfilemapd - $(INSTALL_PROGRAM) -D $< $(sbindir)/$(<F) - -install_dmfilemapd_static: dmfilemapd.static - $(INSTALL_PROGRAM) -D $< $(staticdir)/$(<F) - -install_dmfilemapd: $(INSTALL_DMFILEMAPD_TARGETS) - -install: install_dmfilemapd - -install_device-mapper: install_dmfilemapd diff --git a/daemons/dmfilemapd/dmfilemapd.c b/daemons/dmfilemapd/dmfilemapd.c deleted file mode 100644 index 726ad6e..0000000 --- a/daemons/dmfilemapd/dmfilemapd.c +++ /dev/null @@ -1,836 +0,0 @@ -/* - * Copyright (C) 2016 Red Hat, Inc. All rights reserved. - * - * This file is part of the device-mapper userspace tools. - * - * It includes tree drawing code based on pstree: http://psmisc.sourceforge.net/ - * - * This copyrighted material is made available to anyone wishing to use, - * modify, copy, or redistribute it subject to the terms and conditions - * of the GNU General Public License v.2. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "tools/tool.h" - -#include "device_mapper/misc/dm-logging.h" - -#include "lib/config/defaults.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/inotify.h> -#include <dirent.h> -#include <ctype.h> - -#ifdef __linux__ -# include "kdev_t.h" -#else -# define MAJOR(x) major((x)) -# define MINOR(x) minor((x)) -# define MKDEV(x,y) makedev((x),(y)) -#endif - -/* limit to two updates/sec */ -#define FILEMAPD_WAIT_USECS 500000 - -/* how long to wait for unlinked files */ -#define FILEMAPD_NOFILE_WAIT_USECS 100000 -#define FILEMAPD_NOFILE_WAIT_TRIES 10 - -struct filemap_monitor { - dm_filemapd_mode_t mode; - const char *program_id; - uint64_t group_id; - char *path; - int fd; - - int inotify_fd; - int inotify_watch_fd; - - /* monitoring heuristics */ - int64_t blocks; /* allocated blocks, from stat.st_blocks */ - uint64_t nr_regions; - int deleted; -}; - -static int _foreground; -static int _verbose; - -const char *const _usage = "dmfilemapd <fd> <group_id> <abs_path> <mode> " - "[<foreground>[<log_level>]]"; - -/* - * Daemon logging. By default, all messages are thrown away: messages - * are only written to the terminal if the daemon is run in the foreground. - */ -__attribute__((format(printf, 5, 0))) -static void _dmfilemapd_log_line(int level, - const char *file __attribute__((unused)), - int line __attribute__((unused)), - int dm_errno_or_class, - const char *f, va_list ap) -{ - static int _abort_on_internal_errors = -1; - FILE *out = log_stderr(level) ? stderr : stdout; - - level = log_level(level); - - if (level <= _LOG_WARN || _verbose) { - if (level < _LOG_WARN) - out = stderr; - vfprintf(out, f, ap); - fputc('\n', out); - } - - if (_abort_on_internal_errors < 0) - /* Set when env DM_ABORT_ON_INTERNAL_ERRORS is not "0" */ - _abort_on_internal_errors = - strcmp(getenv("DM_ABORT_ON_INTERNAL_ERRORS") ? : "0", "0"); - - if (_abort_on_internal_errors && - !strncmp(f, INTERNAL_ERROR, sizeof(INTERNAL_ERROR) - 1)) - abort(); -} - -__attribute__((format(printf, 5, 6))) -static void _dmfilemapd_log_with_errno(int level, - const char *file, int line, - int dm_errno_or_class, - const char *f, ...) -{ - va_list ap; - - va_start(ap, f); - _dmfilemapd_log_line(level, file, line, dm_errno_or_class, f, ap); - va_end(ap); -} - -/* - * Only used for reporting errors before daemonise(). - */ -__attribute__((format(printf, 1, 2))) -static void _early_log(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); - va_end(ap); -} - -static void _setup_logging(void) -{ - dm_log_init_verbose(_verbose - 1); - dm_log_with_errno_init(_dmfilemapd_log_with_errno); -} - -#define PROC_FD_DELETED_STR "(deleted)" -/* - * Scan the /proc/<pid>/fd directory for pid and check for an fd - * symlink whose contents match path. - */ -static int _is_open_in_pid(pid_t pid, const char *path) -{ - char deleted_path[PATH_MAX + sizeof(PROC_FD_DELETED_STR)]; - struct dirent *pid_dp = NULL; - char path_buf[PATH_MAX]; - char link_buf[PATH_MAX]; - DIR *pid_d = NULL; - ssize_t len; - - if (pid == getpid()) - return 0; - - if (dm_snprintf(path_buf, sizeof(path_buf), - DEFAULT_PROC_DIR "%d/fd", pid) < 0) { - log_error("Could not format pid path."); - return 0; - } - - /* - * Test for the kernel 'file (deleted)' form when scanning. - */ - if (dm_snprintf(deleted_path, sizeof(deleted_path), "%s %s", - path, PROC_FD_DELETED_STR) < 0) { - log_error("Could not format check path."); - return 0; - } - - pid_d = opendir(path_buf); - if (!pid_d) { - log_error("Could not open proc path: %s.", path_buf); - return 0; - } - - while ((pid_dp = readdir(pid_d)) != NULL) { - if (pid_dp->d_name[0] == '.') - continue; - if ((len = readlinkat(dirfd(pid_d), pid_dp->d_name, link_buf, - sizeof(link_buf))) < 0) { - log_error("readlink failed for " DEFAULT_PROC_DIR - "/%d/fd/.", pid); - goto bad; - } - link_buf[len] = '\0'; - if (!strcmp(deleted_path, link_buf)) { - if (closedir(pid_d)) - log_sys_error("closedir", path_buf); - return 1; - } - } - -bad: - if (closedir(pid_d)) - log_sys_error("closedir", path_buf); - - return 0; -} - -/* - * Attempt to determine whether a file is open by any process by - * scanning symbolic links in /proc/<pid>/fd. - * - * This is a heuristic since it cannot guarantee to detect brief - * access in all cases: a process that opens and then closes the - * file rapidly may never be seen by the scan. - * - * The method will also give false-positives if a process exists - * that has a deleted file open that had the same path, but a - * different inode number, to the file being monitored. - * - * For this reason the daemon only uses _is_open() for unlinked - * files when the mode is DM_FILEMAPD_FOLLOW_INODE, since these - * files can no longer be newly opened by processes. - * - * In this situation !is_open(path) provides an indication that - * the daemon should shut down: the file has been unlinked from - * the file system and we appear to hold the final reference. - */ -static int _is_open(const char *path) -{ - struct dirent *proc_dp = NULL; - DIR *proc_d = NULL; - pid_t pid; - - proc_d = opendir(DEFAULT_PROC_DIR); - if (!proc_d) - return 0; - while ((proc_dp = readdir(proc_d)) != NULL) { - if (!isdigit(proc_dp->d_name[0])) - continue; - errno = 0; - pid = (pid_t) strtol(proc_dp->d_name, NULL, 10); - if (errno || !pid) - continue; - if (_is_open_in_pid(pid, path)) { - if (closedir(proc_d)) - log_sys_error("closedir", DEFAULT_PROC_DIR); - return 1; - } - } - - if (closedir(proc_d)) - log_sys_error("closedir", DEFAULT_PROC_DIR); - - return 0; -} - -static void _filemap_monitor_wait(uint64_t usecs) -{ - if (_verbose) { - if (usecs == FILEMAPD_WAIT_USECS) - log_very_verbose("Waiting for check interval"); - if (usecs == FILEMAPD_NOFILE_WAIT_USECS) - log_very_verbose("Waiting for unlinked path"); - } - usleep((useconds_t) usecs); -} - -static int _parse_args(int argc, char **argv, struct filemap_monitor *fm) -{ - char *endptr; - - /* we don't care what is in argv[0]. */ - argc--; - argv++; - - if (argc < 5) { - _early_log("Wrong number of arguments."); - _early_log("usage: %s", _usage); - return 0; - } - - /* - * We don't know the true nr_regions at daemon start time, - * and it is not worth a dm_stats_list()/group walk to count: - * we can assume that there is at least one region or the - * daemon would not have been started. - * - * A correct value will be obtained following the first update - * of the group's regions. - */ - fm->nr_regions = 1; - - /* parse <fd> */ - errno = 0; - fm->fd = (int) strtol(argv[0], &endptr, 10); - if (errno || *endptr) { - _early_log("Could not parse file descriptor: %s", argv[0]); - return 0; - } - - argc--; - argv++; - - /* parse <group_id> */ - errno = 0; - fm->group_id = strtoull(argv[0], &endptr, 10); - if (*endptr || errno) { - _early_log("Could not parse group identifier: %s", argv[0]); - return 0; - } - - argc--; - argv++; - - /* parse <path> */ - if (!argv[0] || !strlen(argv[0])) { - _early_log("Path argument is required."); - return 0; - } - - if (*argv[0] != '/') { - _early_log("Path argument must specify an absolute path."); - return 0; - } - - fm->path = strdup(argv[0]); - if (!fm->path) { - _early_log("Could not allocate memory for path argument."); - return 0; - } - - argc--; - argv++; - - /* parse <mode> */ - if (!argv[0] || !strlen(argv[0])) { - _early_log("Mode argument is required."); - return 0; - } - - fm->mode = dm_filemapd_mode_from_string(argv[0]); - if (fm->mode == DM_FILEMAPD_FOLLOW_NONE) - return 0; - - argc--; - argv++; - - /* parse [<foreground>[<verbose>]] */ - if (argc) { - errno = 0; - _foreground = (int) strtol(argv[0], &endptr, 10); - if (errno || *endptr) { - _early_log("Could not parse debug argument: %s.", - argv[0]); - return 0; - } - argc--; - argv++; - if (argc) { - errno = 0; - _verbose = (int) strtol(argv[0], &endptr, 10); - if (errno || *endptr) { - _early_log("Could not parse verbose " - "argument: %s", argv[0]); - return 0; - } - if (_verbose < 0 || _verbose > 3) { - _early_log("Verbose argument out of range: %d.", - _verbose); - return 0; - } - } - } - return 1; -} - -static int _filemap_fd_update_blocks(struct filemap_monitor *fm) -{ - struct stat buf; - - if (fm->fd < 0) { - log_error("Filemap fd is not open."); - return 0; - } - - if (fstat(fm->fd, &buf)) { - log_error("Failed to fstat filemap file descriptor."); - return 0; - } - - fm->blocks = buf.st_blocks; - - return 1; -} - -static int _filemap_fd_check_changed(struct filemap_monitor *fm) -{ - int64_t old_blocks; - - old_blocks = fm->blocks; - - if (!_filemap_fd_update_blocks(fm)) - return -1; - - return (fm->blocks != old_blocks); -} - -static void _filemap_monitor_close_fd(struct filemap_monitor *fm) -{ - if (close(fm->fd)) - log_error("Error closing file descriptor."); - fm->fd = -1; -} - -static void _filemap_monitor_end_notify(struct filemap_monitor *fm) -{ - inotify_rm_watch(fm->inotify_fd, fm->inotify_watch_fd); -} - -static int _filemap_monitor_set_notify(struct filemap_monitor *fm) -{ - int inotify_fd, watch_fd; - - /* - * Set IN_NONBLOCK since we do not want to block in event read() - * calls. Do not set IN_CLOEXEC as dmfilemapd is single-threaded - * and does not fork or exec. - */ - if ((inotify_fd = inotify_init1(IN_NONBLOCK)) < 0) { - log_sys_error("inotify_init1", "IN_NONBLOCK"); - return 0; - } - - if ((watch_fd = inotify_add_watch(inotify_fd, fm->path, - IN_MODIFY | IN_DELETE_SELF)) < 0) { - log_sys_error("inotify_add_watch", fm->path); - return 0; - } - fm->inotify_fd = inotify_fd; - fm->inotify_watch_fd = watch_fd; - return 1; -} - -static int _filemap_monitor_reopen_fd(struct filemap_monitor *fm) -{ - int tries = FILEMAPD_NOFILE_WAIT_TRIES; - - /* - * In DM_FILEMAPD_FOLLOW_PATH mode, inotify watches must be - * re-established whenever the file at the watched path is - * changed. - * - * FIXME: stat file and skip if inode is unchanged. - */ - if (fm->fd > 0) - log_error("Filemap file descriptor already open."); - - while ((fm->fd < 0) && --tries) - if (((fm->fd = open(fm->path, O_RDONLY)) < 0) && tries) - _filemap_monitor_wait(FILEMAPD_NOFILE_WAIT_USECS); - - if (!tries && (fm->fd < 0)) { - log_error("Could not re-open file descriptor."); - return 0; - } - - return _filemap_monitor_set_notify(fm); -} - -static int _filemap_monitor_get_events(struct filemap_monitor *fm) -{ - /* alignment as per man(7) inotify */ - char buf[sizeof(struct inotify_event) + NAME_MAX + 1] - __attribute__ ((aligned(__alignof__(struct inotify_event)))); - - struct inotify_event *event; - int check = 0; - ssize_t len; - char *ptr; - - /* - * Close the file descriptor for the file being monitored here - * when mode=path: this will allow the inode to be de-allocated, - * and an IN_DELETE_SELF event generated in the case that the - * daemon is holding the last open reference to the file. - */ - if (fm->mode == DM_FILEMAPD_FOLLOW_PATH) { - _filemap_monitor_end_notify(fm); - _filemap_monitor_close_fd(fm); - } - - len = read(fm->inotify_fd, (void *) &buf, sizeof(buf)); - - /* no events to read? */ - if (len < 0 && (errno == EAGAIN)) - goto out; - - /* interrupted by signal? */ - if (len < 0 && (errno == EINTR)) - goto out; - - if (len < 0) - return -1; - - if (!len) - goto out; - - for (ptr = buf; ptr < buf + len; ptr += sizeof(*event) + event->len) { - event = (struct inotify_event *) ptr; - if (event->mask & IN_DELETE_SELF) - fm->deleted = 1; - if (event->mask & IN_MODIFY) - check = 1; - /* - * Event IN_IGNORED is generated when a file has been deleted - * and IN_DELETE_SELF generated, and indicates that the file - * watch has been automatically removed. - * - * This can only happen for the DM_FILEMAPD_FOLLOW_PATH mode, - * since inotify IN_DELETE events are generated at the time - * the inode is destroyed: DM_FILEMAPD_FOLLOW_INODE will hold - * the file descriptor open, meaning that the event will not - * be generated until after the daemon closes the file. - * - * The event is ignored here since inotify monitoring will - * be reestablished (or the daemon will terminate) following - * deletion of a DM_FILEMAPD_FOLLOW_PATH monitored file. - */ - if (event->mask & IN_IGNORED) - log_very_verbose("Inotify watch removed: IN_IGNORED " - "in event->mask"); - } - -out: - /* - * Re-open file descriptor if required and log disposition. - */ - if (fm->mode == DM_FILEMAPD_FOLLOW_PATH) - if (!_filemap_monitor_reopen_fd(fm)) - return -1; - - log_very_verbose("exiting _filemap_monitor_get_events() with " - "deleted=%d, check=%d", fm->deleted, check); - return check; -} - -static void _filemap_monitor_destroy(struct filemap_monitor *fm) -{ - if (fm->fd > 0) { - _filemap_monitor_end_notify(fm); - _filemap_monitor_close_fd(fm); - } - free((void *) fm->program_id); - free(fm->path); -} - -static int _filemap_monitor_check_same_file(int fd1, int fd2) -{ - struct stat buf1, buf2; - - if ((fd1 < 0) || (fd2 < 0)) - return 0; - - if (fstat(fd1, &buf1)) { - log_error("Failed to fstat file descriptor %d", fd1); - return -1; - } - - if (fstat(fd2, &buf2)) { - log_error("Failed to fstat file descriptor %d", fd2); - return -1; - } - - return ((buf1.st_dev == buf2.st_dev) && (buf1.st_ino == buf2.st_ino)); -} - -static int _filemap_monitor_check_file_unlinked(struct filemap_monitor *fm) -{ - char path_buf[PATH_MAX]; - char link_buf[PATH_MAX]; - int same, fd; - ssize_t len; - - fm->deleted = 0; - same = 0; - - if ((fd = open(fm->path, O_RDONLY)) < 0) - goto check_unlinked; - - same = _filemap_monitor_check_same_file(fm->fd, fd); - - if (close(fd)) - log_error("Error closing fd %d", fd); - - if (same < 0) - return 0; - - if (same) - return 1; - -check_unlinked: - /* - * The file has been unlinked from its original location: test - * whether it is still reachable in the filesystem, or if it is - * unlinked and anonymous. - */ - if (dm_snprintf(path_buf, sizeof(path_buf), DEFAULT_PROC_DIR - "/%d/fd/%d", getpid(), fm->fd) < 0) { - log_error("Could not format pid path."); - return 0; - } - if ((len = readlink(path_buf, link_buf, sizeof(link_buf) - 1)) < 0) { - log_error("readlink failed for " DEFAULT_PROC_DIR "/%d/fd/%d.", - getpid(), fm->fd); - return 0; - } - link_buf[len] = '\0'; - - /* - * Try to re-open the file, from the path now reported in /proc/pid/fd. - */ - if ((fd = open(link_buf, O_RDONLY)) < 0) - fm->deleted = 1; - else - same = _filemap_monitor_check_same_file(fm->fd, fd); - - if ((fd >= 0) && close(fd)) - log_error("Error closing fd %d", fd); - - if (same < 0) - return 0; - - /* Should not happen with normal /proc. */ - if ((fd > 0) && !same) { - log_error("File descriptor mismatch: %d and %s (read from %s) " - "are not the same file!", fm->fd, link_buf, path_buf); - return 0; - } - return 1; -} - -static int _daemonise(struct filemap_monitor *fm) -{ - pid_t pid = 0, sid; - int fd; - - if (!(sid = setsid())) { - _early_log("setsid failed."); - return 0; - } - - if ((pid = fork()) < 0) { - _early_log("Failed to fork daemon process."); - return 0; - } - - if (pid > 0) { - if (_verbose) - _early_log("Started dmfilemapd with pid=%d", pid); - exit(0); - } - - if (chdir("/")) { - _early_log("Failed to change directory."); - return 0; - } - - if (!_verbose) { - if (close(STDIN_FILENO)) - _early_log("Error closing stdin"); - if (close(STDOUT_FILENO)) - _early_log("Error closing stdout"); - if (close(STDERR_FILENO)) - _early_log("Error closing stderr"); - if ((open("/dev/null", O_RDONLY) < 0) || - (open("/dev/null", O_WRONLY) < 0) || - (open("/dev/null", O_WRONLY) < 0)) { - _early_log("Error opening stdio streams."); - return 0; - } - } - /* TODO: Use libdaemon/server/daemon-server.c _daemonise() */ - for (fd = (int) sysconf(_SC_OPEN_MAX) - 1; fd > STDERR_FILENO; fd--) { - if (fd == fm->fd) - continue; - (void) close(fd); - } - - return 1; -} - -static int _update_regions(struct dm_stats *dms, struct filemap_monitor *fm) -{ - uint64_t *regions = NULL, *region, nr_regions = 0; - - regions = dm_stats_update_regions_from_fd(dms, fm->fd, fm->group_id); - if (!regions) { - log_error("Failed to update filemap regions for group_id=" - FMTu64 ".", fm->group_id); - return 0; - } - - for (region = regions; *region != DM_STATS_REGIONS_ALL; region++) - nr_regions++; - - if (!nr_regions) - log_warn("File contains no extents: exiting."); - - if (nr_regions && (regions[0] != fm->group_id)) { - log_warn("group_id changed from " FMTu64 " to " FMTu64, - fm->group_id, regions[0]); - fm->group_id = regions[0]; - } - free(regions); - fm->nr_regions = nr_regions; - return 1; -} - -static int _dmfilemapd(struct filemap_monitor *fm) -{ - int running = 1, check = 0, open = 0; - const char *program_id; - struct dm_stats *dms; - - /* - * The correct program_id is retrieved from the group leader - * following the call to dm_stats_list(). - */ - if (!(dms = dm_stats_create(NULL))) - goto_bad; - - if (!dm_stats_bind_from_fd(dms, fm->fd)) { - log_error("Could not bind dm_stats handle to file descriptor " - "%d", fm->fd); - goto bad; - } - - if (!_filemap_monitor_set_notify(fm)) - goto bad; - - if (!_filemap_fd_update_blocks(fm)) - goto bad; - - if (!dm_stats_list(dms, DM_STATS_ALL_PROGRAMS)) { - log_error("Failed to list stats handle."); - goto bad; - } - - /* - * Take the program_id for new regions (created by calls to - * dm_stats_update_regions_from_fd()) from the value used by - * the group leader. - */ - program_id = dm_stats_get_region_program_id(dms, fm->group_id); - if (program_id) - fm->program_id = strdup(program_id); - else - fm->program_id = NULL; - dm_stats_set_program_id(dms, 1, program_id); - - do { - if (!dm_stats_group_present(dms, fm->group_id)) { - log_info("Filemap group removed: exiting."); - running = 0; - continue; - } - - if ((check = _filemap_monitor_get_events(fm)) < 0) - goto bad; - - if (!check) - goto wait; - - if ((check = _filemap_fd_check_changed(fm)) < 0) - goto bad; - - if (check && !_update_regions(dms, fm)) - goto bad; - - running = !!fm->nr_regions; - if (!running) - continue; - -wait: - _filemap_monitor_wait(FILEMAPD_WAIT_USECS); - - /* mode=inode termination condions */ - if (fm->mode == DM_FILEMAPD_FOLLOW_INODE) { - if (!_filemap_monitor_check_file_unlinked(fm)) - goto bad; - if (fm->deleted && !(open = _is_open(fm->path))) { - log_info("File unlinked and closed: exiting."); - running = 0; - } else if (fm->deleted && open) - log_verbose("File unlinked and open: " - "continuing."); - } - - if (!dm_stats_list(dms, NULL)) { - log_error("Failed to list stats handle."); - goto bad; - } - - } while (running); - - _filemap_monitor_destroy(fm); - dm_stats_destroy(dms); - return 0; - -bad: - _filemap_monitor_destroy(fm); - dm_stats_destroy(dms); - log_error("Exiting"); - return 1; -} - -static const char * const _mode_names[] = { - "inode", - "path" -}; - -/* - * dmfilemapd <fd> <group_id> <path> <mode> [<foreground>[<log_level>]] - */ -int main(int argc, char **argv) -{ - struct filemap_monitor fm; - - memset(&fm, 0, sizeof(fm)); - - if (!_parse_args(argc, argv, &fm)) { - free(fm.path); - return 1; - } - - _setup_logging(); - - log_info("Starting dmfilemapd with fd=%d, group_id=" FMTu64 " " - "mode=%s, path=%s", fm.fd, fm.group_id, - _mode_names[fm.mode], fm.path); - - if (!_foreground && !_daemonise(&fm)) { - free(fm.path); - return 1; - } - - return _dmfilemapd(&fm); -} diff --git a/libdm/dm-tools/.gitignore b/libdm/dm-tools/.gitignore index c42a5e0..5274cda 100644 --- a/libdm/dm-tools/.gitignore +++ b/libdm/dm-tools/.gitignore @@ -1 +1,2 @@ dmsetup +dmfilemapd diff --git a/libdm/dm-tools/Makefile.in b/libdm/dm-tools/Makefile.in index b8d83ac..39d411d 100644 --- a/libdm/dm-tools/Makefile.in +++ b/libdm/dm-tools/Makefile.in @@ -16,8 +16,6 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ top_builddir = @top_builddir@
-SOURCES=\ - SOURCES2=\ dmsetup.c
@@ -36,8 +34,6 @@ CLEAN_TARGETS = $(TARGETS_DM) \
include $(top_builddir)/libdm/make.tmpl
-device-mapper: $(TARGETS_DM) - CFLAGS_dmsetup.o += $(UDEV_CFLAGS) $(EXTRA_EXEC_CFLAGS)
dmsetup: dmsetup.o @@ -67,3 +63,41 @@ install_dmsetup_static: dmsetup.static install_device-mapper: $(INSTALL_DMSETUP_TARGETS)
install: install_device-mapper + +# dmfilemapd support +ifeq ("@BUILD_DMFILEMAPD@", "yes") + SOURCES += dmfilemapd.c + TARGETS_DM += dmfilemapd + .PHONY: install_dmfilemapd install_dmfilemapd_static + INSTALL_DMFILEMAPD_TARGETS = install_dmfilemapd_dynamic + ifeq ("@STATIC_LINK@", "yes") + TARGETS_DM += dmfilemapd.static + else + TARGETS_DM += dmfilemapd + endif + CLEAN_TARGETS+= dmfilemapd.static + + CFLAGS_dmfilemapd.o += $(EXTRA_EXEC_CFLAGS) + + dmfilemapd: $(LIB_SHARED) dmfilemapd.o + $(CC) $(CFLAGS) $(LDFLAGS) $(EXTRA_EXEC_LDFLAGS) $(ELDFLAGS) \ + -o $@ dmfilemapd.o -L$(top_builddir)/libdm -ldevmapper $(DL_LIBS) $(LIBS) + + dmfilemapd.static: $(LIB_STATIC) dmfilemapd.o + $(CC) $(CFLAGS) $(LDFLAGS) $(ELDFLAGS) -static -L$(interfacebuilddir) \ + -o $@ dmfilemapd.o $(DL_LIBS) $(LIBS) $(STATIC_LIBS) + + install_dmfilemapd_dynamic: dmfilemapd + $(INSTALL_PROGRAM) -D $< $(sbindir)/$(<F) + + install_dmfilemapd_static: dmfilemapd.static + $(INSTALL_PROGRAM) -D $< $(staticdir)/$(<F) + + install_dmfilemapd: $(INSTALL_DMFILEMAPD_TARGETS) + + install_device-mapper: install_dmfilemapd + + install: install_dmfilemapd +endif + +device-mapper: $(TARGETS_DM) diff --git a/libdm/dm-tools/dmfilemapd.c b/libdm/dm-tools/dmfilemapd.c new file mode 100644 index 0000000..0b17c2b --- /dev/null +++ b/libdm/dm-tools/dmfilemapd.c @@ -0,0 +1,835 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. All rights reserved. + * + * This file is part of the device-mapper userspace tools. + * + * It includes tree drawing code based on pstree: http://psmisc.sourceforge.net/ + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License v.2. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "util.h" +#include "libdm/misc/dm-logging.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/inotify.h> +#include <dirent.h> +#include <ctype.h> + +#ifdef __linux__ +# include "libdm/misc/kdev_t.h" +#else +# define MAJOR(x) major((x)) +# define MINOR(x) minor((x)) +# define MKDEV(x,y) makedev((x),(y)) +#endif + +#define DEFAULT_PROC_DIR "/proc" + +/* limit to two updates/sec */ +#define FILEMAPD_WAIT_USECS 500000 + +/* how long to wait for unlinked files */ +#define FILEMAPD_NOFILE_WAIT_USECS 100000 +#define FILEMAPD_NOFILE_WAIT_TRIES 10 + +struct filemap_monitor { + dm_filemapd_mode_t mode; + const char *program_id; + uint64_t group_id; + char *path; + int fd; + + int inotify_fd; + int inotify_watch_fd; + + /* monitoring heuristics */ + int64_t blocks; /* allocated blocks, from stat.st_blocks */ + uint64_t nr_regions; + int deleted; +}; + +static int _foreground; +static int _verbose; + +const char *const _usage = "dmfilemapd <fd> <group_id> <abs_path> <mode> " + "[<foreground>[<log_level>]]"; + +/* + * Daemon logging. By default, all messages are thrown away: messages + * are only written to the terminal if the daemon is run in the foreground. + */ +__attribute__((format(printf, 5, 0))) +static void _dmfilemapd_log_line(int level, + const char *file __attribute__((unused)), + int line __attribute__((unused)), + int dm_errno_or_class, + const char *f, va_list ap) +{ + static int _abort_on_internal_errors = -1; + FILE *out = log_stderr(level) ? stderr : stdout; + + level = log_level(level); + + if (level <= _LOG_WARN || _verbose) { + if (level < _LOG_WARN) + out = stderr; + vfprintf(out, f, ap); + fputc('\n', out); + } + + if (_abort_on_internal_errors < 0) + /* Set when env DM_ABORT_ON_INTERNAL_ERRORS is not "0" */ + _abort_on_internal_errors = + strcmp(getenv("DM_ABORT_ON_INTERNAL_ERRORS") ? : "0", "0"); + + if (_abort_on_internal_errors && + !strncmp(f, INTERNAL_ERROR, sizeof(INTERNAL_ERROR) - 1)) + abort(); +} + +__attribute__((format(printf, 5, 6))) +static void _dmfilemapd_log_with_errno(int level, + const char *file, int line, + int dm_errno_or_class, + const char *f, ...) +{ + va_list ap; + + va_start(ap, f); + _dmfilemapd_log_line(level, file, line, dm_errno_or_class, f, ap); + va_end(ap); +} + +/* + * Only used for reporting errors before daemonise(). + */ +__attribute__((format(printf, 1, 2))) +static void _early_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); +} + +static void _setup_logging(void) +{ + dm_log_init_verbose(_verbose - 1); + dm_log_with_errno_init(_dmfilemapd_log_with_errno); +} + +#define PROC_FD_DELETED_STR "(deleted)" +/* + * Scan the /proc/<pid>/fd directory for pid and check for an fd + * symlink whose contents match path. + */ +static int _is_open_in_pid(pid_t pid, const char *path) +{ + char deleted_path[PATH_MAX + sizeof(PROC_FD_DELETED_STR)]; + struct dirent *pid_dp = NULL; + char path_buf[PATH_MAX]; + char link_buf[PATH_MAX]; + DIR *pid_d = NULL; + ssize_t len; + + if (pid == getpid()) + return 0; + + if (dm_snprintf(path_buf, sizeof(path_buf), + DEFAULT_PROC_DIR "%d/fd", pid) < 0) { + log_error("Could not format pid path."); + return 0; + } + + /* + * Test for the kernel 'file (deleted)' form when scanning. + */ + if (dm_snprintf(deleted_path, sizeof(deleted_path), "%s %s", + path, PROC_FD_DELETED_STR) < 0) { + log_error("Could not format check path."); + return 0; + } + + pid_d = opendir(path_buf); + if (!pid_d) { + log_error("Could not open proc path: %s.", path_buf); + return 0; + } + + while ((pid_dp = readdir(pid_d)) != NULL) { + if (pid_dp->d_name[0] == '.') + continue; + if ((len = readlinkat(dirfd(pid_d), pid_dp->d_name, link_buf, + sizeof(link_buf))) < 0) { + log_error("readlink failed for " DEFAULT_PROC_DIR + "/%d/fd/.", pid); + goto bad; + } + link_buf[len] = '\0'; + if (!strcmp(deleted_path, link_buf)) { + if (closedir(pid_d)) + log_sys_error("closedir", path_buf); + return 1; + } + } + +bad: + if (closedir(pid_d)) + log_sys_error("closedir", path_buf); + + return 0; +} + +/* + * Attempt to determine whether a file is open by any process by + * scanning symbolic links in /proc/<pid>/fd. + * + * This is a heuristic since it cannot guarantee to detect brief + * access in all cases: a process that opens and then closes the + * file rapidly may never be seen by the scan. + * + * The method will also give false-positives if a process exists + * that has a deleted file open that had the same path, but a + * different inode number, to the file being monitored. + * + * For this reason the daemon only uses _is_open() for unlinked + * files when the mode is DM_FILEMAPD_FOLLOW_INODE, since these + * files can no longer be newly opened by processes. + * + * In this situation !is_open(path) provides an indication that + * the daemon should shut down: the file has been unlinked from + * the file system and we appear to hold the final reference. + */ +static int _is_open(const char *path) +{ + struct dirent *proc_dp = NULL; + DIR *proc_d = NULL; + pid_t pid; + + proc_d = opendir(DEFAULT_PROC_DIR); + if (!proc_d) + return 0; + while ((proc_dp = readdir(proc_d)) != NULL) { + if (!isdigit(proc_dp->d_name[0])) + continue; + errno = 0; + pid = (pid_t) strtol(proc_dp->d_name, NULL, 10); + if (errno || !pid) + continue; + if (_is_open_in_pid(pid, path)) { + if (closedir(proc_d)) + log_sys_error("closedir", DEFAULT_PROC_DIR); + return 1; + } + } + + if (closedir(proc_d)) + log_sys_error("closedir", DEFAULT_PROC_DIR); + + return 0; +} + +static void _filemap_monitor_wait(uint64_t usecs) +{ + if (_verbose) { + if (usecs == FILEMAPD_WAIT_USECS) + log_very_verbose("Waiting for check interval"); + if (usecs == FILEMAPD_NOFILE_WAIT_USECS) + log_very_verbose("Waiting for unlinked path"); + } + usleep((useconds_t) usecs); +} + +static int _parse_args(int argc, char **argv, struct filemap_monitor *fm) +{ + char *endptr; + + /* we don't care what is in argv[0]. */ + argc--; + argv++; + + if (argc < 5) { + _early_log("Wrong number of arguments."); + _early_log("usage: %s", _usage); + return 0; + } + + /* + * We don't know the true nr_regions at daemon start time, + * and it is not worth a dm_stats_list()/group walk to count: + * we can assume that there is at least one region or the + * daemon would not have been started. + * + * A correct value will be obtained following the first update + * of the group's regions. + */ + fm->nr_regions = 1; + + /* parse <fd> */ + errno = 0; + fm->fd = (int) strtol(argv[0], &endptr, 10); + if (errno || *endptr) { + _early_log("Could not parse file descriptor: %s", argv[0]); + return 0; + } + + argc--; + argv++; + + /* parse <group_id> */ + errno = 0; + fm->group_id = strtoull(argv[0], &endptr, 10); + if (*endptr || errno) { + _early_log("Could not parse group identifier: %s", argv[0]); + return 0; + } + + argc--; + argv++; + + /* parse <path> */ + if (!argv[0] || !strlen(argv[0])) { + _early_log("Path argument is required."); + return 0; + } + + if (*argv[0] != '/') { + _early_log("Path argument must specify an absolute path."); + return 0; + } + + fm->path = strdup(argv[0]); + if (!fm->path) { + _early_log("Could not allocate memory for path argument."); + return 0; + } + + argc--; + argv++; + + /* parse <mode> */ + if (!argv[0] || !strlen(argv[0])) { + _early_log("Mode argument is required."); + return 0; + } + + fm->mode = dm_filemapd_mode_from_string(argv[0]); + if (fm->mode == DM_FILEMAPD_FOLLOW_NONE) + return 0; + + argc--; + argv++; + + /* parse [<foreground>[<verbose>]] */ + if (argc) { + errno = 0; + _foreground = (int) strtol(argv[0], &endptr, 10); + if (errno || *endptr) { + _early_log("Could not parse debug argument: %s.", + argv[0]); + return 0; + } + argc--; + argv++; + if (argc) { + errno = 0; + _verbose = (int) strtol(argv[0], &endptr, 10); + if (errno || *endptr) { + _early_log("Could not parse verbose " + "argument: %s", argv[0]); + return 0; + } + if (_verbose < 0 || _verbose > 3) { + _early_log("Verbose argument out of range: %d.", + _verbose); + return 0; + } + } + } + return 1; +} + +static int _filemap_fd_update_blocks(struct filemap_monitor *fm) +{ + struct stat buf; + + if (fm->fd < 0) { + log_error("Filemap fd is not open."); + return 0; + } + + if (fstat(fm->fd, &buf)) { + log_error("Failed to fstat filemap file descriptor."); + return 0; + } + + fm->blocks = buf.st_blocks; + + return 1; +} + +static int _filemap_fd_check_changed(struct filemap_monitor *fm) +{ + int64_t old_blocks; + + old_blocks = fm->blocks; + + if (!_filemap_fd_update_blocks(fm)) + return -1; + + return (fm->blocks != old_blocks); +} + +static void _filemap_monitor_close_fd(struct filemap_monitor *fm) +{ + if (close(fm->fd)) + log_error("Error closing file descriptor."); + fm->fd = -1; +} + +static void _filemap_monitor_end_notify(struct filemap_monitor *fm) +{ + inotify_rm_watch(fm->inotify_fd, fm->inotify_watch_fd); +} + +static int _filemap_monitor_set_notify(struct filemap_monitor *fm) +{ + int inotify_fd, watch_fd; + + /* + * Set IN_NONBLOCK since we do not want to block in event read() + * calls. Do not set IN_CLOEXEC as dmfilemapd is single-threaded + * and does not fork or exec. + */ + if ((inotify_fd = inotify_init1(IN_NONBLOCK)) < 0) { + log_sys_error("inotify_init1", "IN_NONBLOCK"); + return 0; + } + + if ((watch_fd = inotify_add_watch(inotify_fd, fm->path, + IN_MODIFY | IN_DELETE_SELF)) < 0) { + log_sys_error("inotify_add_watch", fm->path); + return 0; + } + fm->inotify_fd = inotify_fd; + fm->inotify_watch_fd = watch_fd; + return 1; +} + +static int _filemap_monitor_reopen_fd(struct filemap_monitor *fm) +{ + int tries = FILEMAPD_NOFILE_WAIT_TRIES; + + /* + * In DM_FILEMAPD_FOLLOW_PATH mode, inotify watches must be + * re-established whenever the file at the watched path is + * changed. + * + * FIXME: stat file and skip if inode is unchanged. + */ + if (fm->fd > 0) + log_error("Filemap file descriptor already open."); + + while ((fm->fd < 0) && --tries) + if (((fm->fd = open(fm->path, O_RDONLY)) < 0) && tries) + _filemap_monitor_wait(FILEMAPD_NOFILE_WAIT_USECS); + + if (!tries && (fm->fd < 0)) { + log_error("Could not re-open file descriptor."); + return 0; + } + + return _filemap_monitor_set_notify(fm); +} + +static int _filemap_monitor_get_events(struct filemap_monitor *fm) +{ + /* alignment as per man(7) inotify */ + char buf[sizeof(struct inotify_event) + NAME_MAX + 1] + __attribute__ ((aligned(__alignof__(struct inotify_event)))); + + struct inotify_event *event; + int check = 0; + ssize_t len; + char *ptr; + + /* + * Close the file descriptor for the file being monitored here + * when mode=path: this will allow the inode to be de-allocated, + * and an IN_DELETE_SELF event generated in the case that the + * daemon is holding the last open reference to the file. + */ + if (fm->mode == DM_FILEMAPD_FOLLOW_PATH) { + _filemap_monitor_end_notify(fm); + _filemap_monitor_close_fd(fm); + } + + len = read(fm->inotify_fd, (void *) &buf, sizeof(buf)); + + /* no events to read? */ + if (len < 0 && (errno == EAGAIN)) + goto out; + + /* interrupted by signal? */ + if (len < 0 && (errno == EINTR)) + goto out; + + if (len < 0) + return -1; + + if (!len) + goto out; + + for (ptr = buf; ptr < buf + len; ptr += sizeof(*event) + event->len) { + event = (struct inotify_event *) ptr; + if (event->mask & IN_DELETE_SELF) + fm->deleted = 1; + if (event->mask & IN_MODIFY) + check = 1; + /* + * Event IN_IGNORED is generated when a file has been deleted + * and IN_DELETE_SELF generated, and indicates that the file + * watch has been automatically removed. + * + * This can only happen for the DM_FILEMAPD_FOLLOW_PATH mode, + * since inotify IN_DELETE events are generated at the time + * the inode is destroyed: DM_FILEMAPD_FOLLOW_INODE will hold + * the file descriptor open, meaning that the event will not + * be generated until after the daemon closes the file. + * + * The event is ignored here since inotify monitoring will + * be reestablished (or the daemon will terminate) following + * deletion of a DM_FILEMAPD_FOLLOW_PATH monitored file. + */ + if (event->mask & IN_IGNORED) + log_very_verbose("Inotify watch removed: IN_IGNORED " + "in event->mask"); + } + +out: + /* + * Re-open file descriptor if required and log disposition. + */ + if (fm->mode == DM_FILEMAPD_FOLLOW_PATH) + if (!_filemap_monitor_reopen_fd(fm)) + return -1; + + log_very_verbose("exiting _filemap_monitor_get_events() with " + "deleted=%d, check=%d", fm->deleted, check); + return check; +} + +static void _filemap_monitor_destroy(struct filemap_monitor *fm) +{ + if (fm->fd > 0) { + _filemap_monitor_end_notify(fm); + _filemap_monitor_close_fd(fm); + } + free((void *) fm->program_id); + free(fm->path); +} + +static int _filemap_monitor_check_same_file(int fd1, int fd2) +{ + struct stat buf1, buf2; + + if ((fd1 < 0) || (fd2 < 0)) + return 0; + + if (fstat(fd1, &buf1)) { + log_error("Failed to fstat file descriptor %d", fd1); + return -1; + } + + if (fstat(fd2, &buf2)) { + log_error("Failed to fstat file descriptor %d", fd2); + return -1; + } + + return ((buf1.st_dev == buf2.st_dev) && (buf1.st_ino == buf2.st_ino)); +} + +static int _filemap_monitor_check_file_unlinked(struct filemap_monitor *fm) +{ + char path_buf[PATH_MAX]; + char link_buf[PATH_MAX]; + int same, fd; + ssize_t len; + + fm->deleted = 0; + same = 0; + + if ((fd = open(fm->path, O_RDONLY)) < 0) + goto check_unlinked; + + same = _filemap_monitor_check_same_file(fm->fd, fd); + + if (close(fd)) + log_error("Error closing fd %d", fd); + + if (same < 0) + return 0; + + if (same) + return 1; + +check_unlinked: + /* + * The file has been unlinked from its original location: test + * whether it is still reachable in the filesystem, or if it is + * unlinked and anonymous. + */ + if (dm_snprintf(path_buf, sizeof(path_buf), DEFAULT_PROC_DIR + "/%d/fd/%d", getpid(), fm->fd) < 0) { + log_error("Could not format pid path."); + return 0; + } + if ((len = readlink(path_buf, link_buf, sizeof(link_buf) - 1)) < 0) { + log_error("readlink failed for " DEFAULT_PROC_DIR "/%d/fd/%d.", + getpid(), fm->fd); + return 0; + } + link_buf[len] = '\0'; + + /* + * Try to re-open the file, from the path now reported in /proc/pid/fd. + */ + if ((fd = open(link_buf, O_RDONLY)) < 0) + fm->deleted = 1; + else + same = _filemap_monitor_check_same_file(fm->fd, fd); + + if ((fd >= 0) && close(fd)) + log_error("Error closing fd %d", fd); + + if (same < 0) + return 0; + + /* Should not happen with normal /proc. */ + if ((fd > 0) && !same) { + log_error("File descriptor mismatch: %d and %s (read from %s) " + "are not the same file!", fm->fd, link_buf, path_buf); + return 0; + } + return 1; +} + +static int _daemonise(struct filemap_monitor *fm) +{ + pid_t pid = 0, sid; + int fd; + + if (!(sid = setsid())) { + _early_log("setsid failed."); + return 0; + } + + if ((pid = fork()) < 0) { + _early_log("Failed to fork daemon process."); + return 0; + } + + if (pid > 0) { + if (_verbose) + _early_log("Started dmfilemapd with pid=%d", pid); + exit(0); + } + + if (chdir("/")) { + _early_log("Failed to change directory."); + return 0; + } + + if (!_verbose) { + if (close(STDIN_FILENO)) + _early_log("Error closing stdin"); + if (close(STDOUT_FILENO)) + _early_log("Error closing stdout"); + if (close(STDERR_FILENO)) + _early_log("Error closing stderr"); + if ((open("/dev/null", O_RDONLY) < 0) || + (open("/dev/null", O_WRONLY) < 0) || + (open("/dev/null", O_WRONLY) < 0)) { + _early_log("Error opening stdio streams."); + return 0; + } + } + /* TODO: Use libdaemon/server/daemon-server.c _daemonise() */ + for (fd = (int) sysconf(_SC_OPEN_MAX) - 1; fd > STDERR_FILENO; fd--) { + if (fd == fm->fd) + continue; + (void) close(fd); + } + + return 1; +} + +static int _update_regions(struct dm_stats *dms, struct filemap_monitor *fm) +{ + uint64_t *regions = NULL, *region, nr_regions = 0; + + regions = dm_stats_update_regions_from_fd(dms, fm->fd, fm->group_id); + if (!regions) { + log_error("Failed to update filemap regions for group_id=" + FMTu64 ".", fm->group_id); + return 0; + } + + for (region = regions; *region != DM_STATS_REGIONS_ALL; region++) + nr_regions++; + + if (!nr_regions) + log_warn("File contains no extents: exiting."); + + if (nr_regions && (regions[0] != fm->group_id)) { + log_warn("group_id changed from " FMTu64 " to " FMTu64, + fm->group_id, regions[0]); + fm->group_id = regions[0]; + } + free(regions); + fm->nr_regions = nr_regions; + return 1; +} + +static int _dmfilemapd(struct filemap_monitor *fm) +{ + int running = 1, check = 0, open = 0; + const char *program_id; + struct dm_stats *dms; + + /* + * The correct program_id is retrieved from the group leader + * following the call to dm_stats_list(). + */ + if (!(dms = dm_stats_create(NULL))) + goto_bad; + + if (!dm_stats_bind_from_fd(dms, fm->fd)) { + log_error("Could not bind dm_stats handle to file descriptor " + "%d", fm->fd); + goto bad; + } + + if (!_filemap_monitor_set_notify(fm)) + goto bad; + + if (!_filemap_fd_update_blocks(fm)) + goto bad; + + if (!dm_stats_list(dms, DM_STATS_ALL_PROGRAMS)) { + log_error("Failed to list stats handle."); + goto bad; + } + + /* + * Take the program_id for new regions (created by calls to + * dm_stats_update_regions_from_fd()) from the value used by + * the group leader. + */ + program_id = dm_stats_get_region_program_id(dms, fm->group_id); + if (program_id) + fm->program_id = strdup(program_id); + else + fm->program_id = NULL; + dm_stats_set_program_id(dms, 1, program_id); + + do { + if (!dm_stats_group_present(dms, fm->group_id)) { + log_info("Filemap group removed: exiting."); + running = 0; + continue; + } + + if ((check = _filemap_monitor_get_events(fm)) < 0) + goto bad; + + if (!check) + goto wait; + + if ((check = _filemap_fd_check_changed(fm)) < 0) + goto bad; + + if (check && !_update_regions(dms, fm)) + goto bad; + + running = !!fm->nr_regions; + if (!running) + continue; + +wait: + _filemap_monitor_wait(FILEMAPD_WAIT_USECS); + + /* mode=inode termination condions */ + if (fm->mode == DM_FILEMAPD_FOLLOW_INODE) { + if (!_filemap_monitor_check_file_unlinked(fm)) + goto bad; + if (fm->deleted && !(open = _is_open(fm->path))) { + log_info("File unlinked and closed: exiting."); + running = 0; + } else if (fm->deleted && open) + log_verbose("File unlinked and open: " + "continuing."); + } + + if (!dm_stats_list(dms, NULL)) { + log_error("Failed to list stats handle."); + goto bad; + } + + } while (running); + + _filemap_monitor_destroy(fm); + dm_stats_destroy(dms); + return 0; + +bad: + _filemap_monitor_destroy(fm); + dm_stats_destroy(dms); + log_error("Exiting"); + return 1; +} + +static const char * const _mode_names[] = { + "inode", + "path" +}; + +/* + * dmfilemapd <fd> <group_id> <path> <mode> [<foreground>[<log_level>]] + */ +int main(int argc, char **argv) +{ + struct filemap_monitor fm; + + memset(&fm, 0, sizeof(fm)); + + if (!_parse_args(argc, argv, &fm)) { + free(fm.path); + return 1; + } + + _setup_logging(); + + log_info("Starting dmfilemapd with fd=%d, group_id=" FMTu64 " " + "mode=%s, path=%s", fm.fd, fm.group_id, + _mode_names[fm.mode], fm.path); + + if (!_foreground && !_daemonise(&fm)) { + free(fm.path); + return 1; + } + + return _dmfilemapd(&fm); +}
lvm2-commits@lists.fedorahosted.org