From: Simon Marchi Date: Wed, 22 Sep 2021 01:06:30 +0000 (-0400) Subject: common: compile libfd-tracker as C++ X-Git-Url: https://git.lttng.org./?a=commitdiff_plain;h=e032c6fd0419a73188f7143b18625b43a8d8f7c3;p=lttng-tools.git common: compile libfd-tracker as C++ Change-Id: I6eed13d24dd8a306f4aa474f7d739931f0750635 Signed-off-by: Simon Marchi Signed-off-by: Jérémie Galarneau --- diff --git a/src/common/fd-tracker/Makefile.am b/src/common/fd-tracker/Makefile.am index 05ca8e84b..68e63d42b 100644 --- a/src/common/fd-tracker/Makefile.am +++ b/src/common/fd-tracker/Makefile.am @@ -2,7 +2,11 @@ noinst_LTLIBRARIES = libfd-tracker.la -libfd_tracker_la_SOURCES = fd-tracker.h fd-tracker.c \ - utils.h utils.c \ - inode.h inode.c \ - utils-poll.c +libfd_tracker_la_SOURCES = \ + fd-tracker.cpp \ + fd-tracker.h \ + inode.cpp \ + inode.h \ + utils.cpp \ + utils.h \ + utils-poll.cpp diff --git a/src/common/fd-tracker/fd-tracker.c b/src/common/fd-tracker/fd-tracker.c deleted file mode 100644 index a9921fa84..000000000 --- a/src/common/fd-tracker/fd-tracker.c +++ /dev/null @@ -1,946 +0,0 @@ -/* - * Copyright (C) 2018-2020 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "fd-tracker.h" -#include "inode.h" - -/* Tracker lock must be taken by the user. */ -#define TRACKED_COUNT(tracker) \ - (tracker->count.suspendable.active + \ - tracker->count.suspendable.suspended + \ - tracker->count.unsuspendable) - -/* Tracker lock must be taken by the user. */ -#define ACTIVE_COUNT(tracker) \ - (tracker->count.suspendable.active + tracker->count.unsuspendable) - -/* Tracker lock must be taken by the user. */ -#define SUSPENDED_COUNT(tracker) (tracker->count.suspendable.suspended) - -/* Tracker lock must be taken by the user. */ -#define SUSPENDABLE_COUNT(tracker) \ - (tracker->count.suspendable.active + \ - tracker->count.suspendable.suspended) - -/* Tracker lock must be taken by the user. */ -#define UNSUSPENDABLE_COUNT(tracker) (tracker->count.unsuspendable) - -struct fd_tracker { - pthread_mutex_t lock; - struct { - struct { - unsigned int active; - unsigned int suspended; - } suspendable; - unsigned int unsuspendable; - } count; - unsigned int capacity; - struct { - uint64_t uses; - uint64_t misses; - /* Failures to suspend or restore fs handles. */ - uint64_t errors; - } stats; - /* - * The head of the active_handles list is always the least recently - * used active handle. When an handle is used, it is removed from the - * list and added to the end. When a file has to be suspended, the - * first element in the list is "popped", suspended, and added to the - * list of suspended handles. - */ - struct cds_list_head active_handles; - struct cds_list_head suspended_handles; - struct cds_lfht *unsuspendable_fds; - struct lttng_inode_registry *inode_registry; - /* Unlinked files are moved in this directory under a unique name. */ - struct lttng_directory_handle *unlink_directory_handle; - struct lttng_unlinked_file_pool *unlinked_file_pool; -}; - -struct open_properties { - int flags; - LTTNG_OPTIONAL(mode_t) mode; -}; - -/* - * A fs_handle_tracked is not ref-counted. Therefore, it is assumed that a - * handle is never in-use while it is being reclaimed. It can be - * shared by multiple threads, but external synchronization is required - * to ensure it is not still being used when it is reclaimed (close method). - * In this respect, it is not different from a regular file descriptor. - * - * The fs_handle lock always nests _within_ the tracker's lock. - */ -struct fs_handle_tracked { - struct fs_handle parent; - pthread_mutex_t lock; - /* - * Weak reference to the tracker. All fs_handles are assumed to have - * been closed at the moment of the destruction of the fd_tracker. - */ - struct fd_tracker *tracker; - struct open_properties properties; - struct lttng_inode *inode; - int fd; - /* inode number of the file at the time of the handle's creation. */ - uint64_t ino; - bool in_use; - /* Offset to which the file should be restored. */ - off_t offset; - struct cds_list_head handles_list_node; -}; - -struct unsuspendable_fd { - /* - * Accesses are only performed through the tracker, which is protected - * by its own lock. - */ - int fd; - char *name; - struct cds_lfht_node tracker_node; - struct rcu_head rcu_head; -}; - -static struct { - pthread_mutex_t lock; - bool initialized; - unsigned long value; -} seed = { - .lock = PTHREAD_MUTEX_INITIALIZER, -}; - -static int match_fd(struct cds_lfht_node *node, const void *key); -static void unsuspendable_fd_destroy(struct unsuspendable_fd *entry); -static struct unsuspendable_fd *unsuspendable_fd_create( - const char *name, int fd); -static int open_from_properties(const struct lttng_directory_handle *dir_handle, - const char *path, struct open_properties *properties); - -static void fs_handle_tracked_log(struct fs_handle_tracked *handle); -static int fs_handle_tracked_suspend(struct fs_handle_tracked *handle); -static int fs_handle_tracked_restore(struct fs_handle_tracked *handle); -static int fs_handle_tracked_get_fd(struct fs_handle *_handle); -static void fs_handle_tracked_put_fd(struct fs_handle *_handle); -static int fs_handle_tracked_unlink(struct fs_handle *_handle); -static int fs_handle_tracked_close(struct fs_handle *_handle); - -static void fd_tracker_track( - struct fd_tracker *tracker, struct fs_handle_tracked *handle); -static void fd_tracker_untrack( - struct fd_tracker *tracker, struct fs_handle_tracked *handle); -static int fd_tracker_suspend_handles( - struct fd_tracker *tracker, unsigned int count); -static int fd_tracker_restore_handle( - struct fd_tracker *tracker, struct fs_handle_tracked *handle); - -/* Match function of the tracker's unsuspendable_fds hash table. */ -static int match_fd(struct cds_lfht_node *node, const void *key) -{ - struct unsuspendable_fd *entry = caa_container_of( - node, struct unsuspendable_fd, tracker_node); - - return hash_match_key_ulong( - (void *) (unsigned long) entry->fd, (void *) key); -} - -static void delete_unsuspendable_fd(struct rcu_head *head) -{ - struct unsuspendable_fd *fd = caa_container_of( - head, struct unsuspendable_fd, rcu_head); - - free(fd->name); - free(fd); -} - -static void unsuspendable_fd_destroy(struct unsuspendable_fd *entry) -{ - if (!entry) { - return; - } - call_rcu(&entry->rcu_head, delete_unsuspendable_fd); -} - -static struct unsuspendable_fd *unsuspendable_fd_create( - const char *name, int fd) -{ - struct unsuspendable_fd *entry = zmalloc(sizeof(*entry)); - - if (!entry) { - goto error; - } - if (name) { - entry->name = strdup(name); - if (!entry->name) { - goto error; - } - } - cds_lfht_node_init(&entry->tracker_node); - entry->fd = fd; - return entry; -error: - unsuspendable_fd_destroy(entry); - return NULL; -} - -static void fs_handle_tracked_log(struct fs_handle_tracked *handle) -{ - const char *path; - - pthread_mutex_lock(&handle->lock); - lttng_inode_borrow_location(handle->inode, NULL, &path); - - if (handle->fd >= 0) { - DBG_NO_LOC(" %s [active, fd %d%s]", path, handle->fd, - handle->in_use ? ", in use" : ""); - } else { - DBG_NO_LOC(" %s [suspended]", path); - } - pthread_mutex_unlock(&handle->lock); -} - -/* Tracker lock must be held by the caller. */ -static int fs_handle_tracked_suspend(struct fs_handle_tracked *handle) -{ - int ret = 0; - struct stat fs_stat; - const char *path; - const struct lttng_directory_handle *node_directory_handle; - - pthread_mutex_lock(&handle->lock); - lttng_inode_borrow_location( - handle->inode, &node_directory_handle, &path); - LTTNG_ASSERT(handle->fd >= 0); - if (handle->in_use) { - /* This handle can't be suspended as it is currently in use. */ - ret = -EAGAIN; - goto end; - } - - ret = lttng_directory_handle_stat( - node_directory_handle, path, &fs_stat); - if (ret) { - PERROR("Filesystem handle to %s cannot be suspended as stat() failed", - path); - ret = -errno; - goto end; - } - - if (fs_stat.st_ino != handle->ino) { - /* Don't suspend as the handle would not be restorable. */ - WARN("Filesystem handle to %s cannot be suspended as its inode changed", - path); - ret = -ENOENT; - goto end; - } - - handle->offset = lseek(handle->fd, 0, SEEK_CUR); - if (handle->offset == -1) { - WARN("Filesystem handle to %s cannot be suspended as lseek() failed to sample its current position", - path); - ret = -errno; - goto end; - } - - ret = close(handle->fd); - if (ret) { - PERROR("Filesystem handle to %s cannot be suspended as close() failed", - path); - ret = -errno; - goto end; - } - DBG("Suspended filesystem handle to %s (fd %i) at position %" PRId64, - path, handle->fd, handle->offset); - handle->fd = -1; -end: - if (ret) { - handle->tracker->stats.errors++; - } - pthread_mutex_unlock(&handle->lock); - return ret; -} - -/* Caller must hold the tracker and handle's locks. */ -static int fs_handle_tracked_restore(struct fs_handle_tracked *handle) -{ - int ret, fd = -1; - const char *path; - const struct lttng_directory_handle *node_directory_handle; - - lttng_inode_borrow_location( - handle->inode, &node_directory_handle, &path); - - LTTNG_ASSERT(handle->fd == -1); - LTTNG_ASSERT(path); - ret = open_from_properties( - node_directory_handle, path, &handle->properties); - if (ret < 0) { - PERROR("Failed to restore filesystem handle to %s, open() failed", - path); - ret = -errno; - goto end; - } - fd = ret; - - ret = lseek(fd, handle->offset, SEEK_SET); - if (ret < 0) { - PERROR("Failed to restore filesystem handle to %s, lseek() failed", - path); - ret = -errno; - goto end; - } - DBG("Restored filesystem handle to %s (fd %i) at position %" PRId64, - path, fd, handle->offset); - ret = 0; - handle->fd = fd; - fd = -1; -end: - if (fd >= 0) { - (void) close(fd); - } - return ret; -} - -static int open_from_properties(const struct lttng_directory_handle *dir_handle, - const char *path, struct open_properties *properties) -{ - int ret; - - /* - * open() ignores the 'flags' parameter unless the O_CREAT or O_TMPFILE - * flags are set. O_TMPFILE would not make sense in the context of a - * suspendable fs_handle as it would not be restorable (see OPEN(2)), - * thus it is ignored here. - */ - if ((properties->flags & O_CREAT) && properties->mode.is_set) { - ret = lttng_directory_handle_open_file(dir_handle, path, - properties->flags, properties->mode.value); - } else { - ret = lttng_directory_handle_open_file(dir_handle, path, - properties->flags, 0); - } - /* - * Some flags should not be used beyond the initial open() of a - * restorable file system handle. O_CREAT and O_TRUNC must - * be cleared since it would be unexpected to re-use them - * when the handle is retored: - * - O_CREAT should not be needed as the file has been created - * on the initial call to open(), - * - O_TRUNC would destroy the file's contents by truncating it - * to length 0. - */ - properties->flags &= ~(O_CREAT | O_TRUNC); - if (ret < 0) { - ret = -errno; - goto end; - } -end: - return ret; -} - -struct fd_tracker *fd_tracker_create(const char *unlinked_file_path, - unsigned int capacity) -{ - struct fd_tracker *tracker = zmalloc(sizeof(struct fd_tracker)); - - if (!tracker) { - goto end; - } - - pthread_mutex_lock(&seed.lock); - if (!seed.initialized) { - seed.value = (unsigned long) time(NULL); - seed.initialized = true; - } - pthread_mutex_unlock(&seed.lock); - - CDS_INIT_LIST_HEAD(&tracker->active_handles); - CDS_INIT_LIST_HEAD(&tracker->suspended_handles); - tracker->capacity = capacity; - tracker->unsuspendable_fds = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!tracker->unsuspendable_fds) { - ERR("Failed to create fd-tracker's unsuspendable_fds hash table"); - goto error; - } - tracker->inode_registry = lttng_inode_registry_create(); - if (!tracker->inode_registry) { - ERR("Failed to create fd-tracker's inode registry"); - goto error; - } - - tracker->unlinked_file_pool = - lttng_unlinked_file_pool_create(unlinked_file_path); - if (!tracker->unlinked_file_pool) { - goto error; - } - DBG("File descriptor tracker created with a limit of %u simultaneously-opened FDs", - capacity); -end: - return tracker; -error: - fd_tracker_destroy(tracker); - return NULL; -} - -void fd_tracker_log(struct fd_tracker *tracker) -{ - struct fs_handle_tracked *handle; - struct unsuspendable_fd *unsuspendable_fd; - struct cds_lfht_iter iter; - - pthread_mutex_lock(&tracker->lock); - DBG_NO_LOC("File descriptor tracker"); - DBG_NO_LOC(" Stats:"); - DBG_NO_LOC(" uses: %" PRIu64, tracker->stats.uses); - DBG_NO_LOC(" misses: %" PRIu64, tracker->stats.misses); - DBG_NO_LOC(" errors: %" PRIu64, tracker->stats.errors); - DBG_NO_LOC(" Tracked: %u", TRACKED_COUNT(tracker)); - DBG_NO_LOC(" active: %u", ACTIVE_COUNT(tracker)); - DBG_NO_LOC(" suspendable: %u", SUSPENDABLE_COUNT(tracker)); - DBG_NO_LOC(" unsuspendable: %u", UNSUSPENDABLE_COUNT(tracker)); - DBG_NO_LOC(" suspended: %u", SUSPENDED_COUNT(tracker)); - DBG_NO_LOC(" capacity: %u", tracker->capacity); - - DBG_NO_LOC(" Tracked suspendable file descriptors"); - cds_list_for_each_entry( - handle, &tracker->active_handles, handles_list_node) { - fs_handle_tracked_log(handle); - } - cds_list_for_each_entry(handle, &tracker->suspended_handles, - handles_list_node) { - fs_handle_tracked_log(handle); - } - if (!SUSPENDABLE_COUNT(tracker)) { - DBG_NO_LOC(" None"); - } - - DBG_NO_LOC(" Tracked unsuspendable file descriptors"); - rcu_read_lock(); - cds_lfht_for_each_entry(tracker->unsuspendable_fds, &iter, - unsuspendable_fd, tracker_node) { - DBG_NO_LOC(" %s [active, fd %d]", - unsuspendable_fd->name ?: "Unnamed", - unsuspendable_fd->fd); - } - rcu_read_unlock(); - if (!UNSUSPENDABLE_COUNT(tracker)) { - DBG_NO_LOC(" None"); - } - - pthread_mutex_unlock(&tracker->lock); -} - -int fd_tracker_destroy(struct fd_tracker *tracker) -{ - int ret = 0; - - if (!tracker) { - goto end; - } - /* - * Refuse to destroy the tracker as fs_handles may still old - * weak references to the tracker. - */ - pthread_mutex_lock(&tracker->lock); - if (TRACKED_COUNT(tracker)) { - ERR("A file descriptor leak has been detected: %u tracked file descriptors are still being tracked", - TRACKED_COUNT(tracker)); - pthread_mutex_unlock(&tracker->lock); - fd_tracker_log(tracker); - ret = -1; - goto end; - } - pthread_mutex_unlock(&tracker->lock); - - if (tracker->unsuspendable_fds) { - ret = cds_lfht_destroy(tracker->unsuspendable_fds, NULL); - LTTNG_ASSERT(!ret); - } - - lttng_inode_registry_destroy(tracker->inode_registry); - lttng_unlinked_file_pool_destroy(tracker->unlinked_file_pool); - pthread_mutex_destroy(&tracker->lock); - free(tracker); -end: - return ret; -} - -struct fs_handle *fd_tracker_open_fs_handle(struct fd_tracker *tracker, - struct lttng_directory_handle *directory, - const char *path, - int flags, - mode_t *mode) -{ - int ret; - struct fs_handle_tracked *handle = NULL; - struct stat fd_stat; - struct open_properties properties = { - .flags = flags, - .mode.is_set = !!mode, - .mode.value = mode ? *mode : 0, - }; - - pthread_mutex_lock(&tracker->lock); - if (ACTIVE_COUNT(tracker) == tracker->capacity) { - if (tracker->count.suspendable.active > 0) { - ret = fd_tracker_suspend_handles(tracker, 1); - if (ret) { - goto end; - } - } else { - /* - * There are not enough active suspendable file - * descriptors to open a new fd and still accommodate - * the tracker's capacity. - */ - WARN("Cannot open file system handle, too many unsuspendable file descriptors are opened (%u)", - tracker->count.unsuspendable); - goto end; - } - } - - handle = zmalloc(sizeof(*handle)); - if (!handle) { - goto end; - } - handle->parent = (typeof(handle->parent)) { - .get_fd = fs_handle_tracked_get_fd, - .put_fd = fs_handle_tracked_put_fd, - .unlink = fs_handle_tracked_unlink, - .close = fs_handle_tracked_close, - }; - - handle->tracker = tracker; - - ret = pthread_mutex_init(&handle->lock, NULL); - if (ret) { - PERROR("Failed to initialize handle mutex while creating fs handle"); - goto error_mutex_init; - } - - handle->fd = open_from_properties(directory, path, &properties); - if (handle->fd < 0) { - PERROR("Failed to open fs handle to %s, open() returned", path); - goto error; - } - - handle->properties = properties; - - handle->inode = lttng_inode_registry_get_inode(tracker->inode_registry, - directory, path, handle->fd, - tracker->unlinked_file_pool); - if (!handle->inode) { - ERR("Failed to get lttng_inode corresponding to file %s", path); - goto error; - } - - if (fstat(handle->fd, &fd_stat)) { - PERROR("Failed to retrieve file descriptor inode while creating fs handle, fstat() returned"); - goto error; - } - handle->ino = fd_stat.st_ino; - - fd_tracker_track(tracker, handle); -end: - pthread_mutex_unlock(&tracker->lock); - return handle ? &handle->parent : NULL; -error: - if (handle->inode) { - lttng_inode_put(handle->inode); - } - pthread_mutex_destroy(&handle->lock); -error_mutex_init: - free(handle); - handle = NULL; - goto end; -} - -/* Caller must hold the tracker's lock. */ -static int fd_tracker_suspend_handles( - struct fd_tracker *tracker, unsigned int count) -{ - unsigned int left_to_close = count; - unsigned int attempts_left = tracker->count.suspendable.active; - struct fs_handle_tracked *handle, *tmp; - - cds_list_for_each_entry_safe(handle, tmp, &tracker->active_handles, - handles_list_node) { - int ret; - - fd_tracker_untrack(tracker, handle); - ret = fs_handle_tracked_suspend(handle); - fd_tracker_track(tracker, handle); - if (!ret) { - left_to_close--; - } - attempts_left--; - - if (left_to_close == 0 || attempts_left == 0) { - break; - } - } - return left_to_close ? -EMFILE : 0; -} - -int fd_tracker_open_unsuspendable_fd(struct fd_tracker *tracker, - int *out_fds, - const char **names, - unsigned int fd_count, - fd_open_cb open, - void *user_data) -{ - int ret, user_ret, i, fds_to_suspend; - unsigned int active_fds; - struct unsuspendable_fd **entries; - - entries = zmalloc(fd_count * sizeof(*entries)); - if (!entries) { - ret = -1; - goto end; - } - - pthread_mutex_lock(&tracker->lock); - - active_fds = ACTIVE_COUNT(tracker); - fds_to_suspend = (int) active_fds + (int) fd_count - - (int) tracker->capacity; - if (fds_to_suspend > 0) { - if (fds_to_suspend <= tracker->count.suspendable.active) { - ret = fd_tracker_suspend_handles( - tracker, fds_to_suspend); - if (ret) { - goto end_unlock; - } - } else { - /* - * There are not enough active suspendable file - * descriptors to open a new fd and still accommodates the - * tracker's capacity. - */ - WARN("Cannot open unsuspendable fd, too many unsuspendable file descriptors are opened (%u)", - tracker->count.unsuspendable); - ret = -EMFILE; - goto end_unlock; - } - } - - user_ret = open(user_data, out_fds); - if (user_ret) { - ret = user_ret; - goto end_unlock; - } - - /* - * Add the fds returned by the user's callback to the hashtable - * of unsuspendable fds. - */ - for (i = 0; i < fd_count; i++) { - struct unsuspendable_fd *entry = unsuspendable_fd_create( - names ? names[i] : NULL, out_fds[i]); - - if (!entry) { - ret = -1; - goto end_free_entries; - } - entries[i] = entry; - } - - rcu_read_lock(); - for (i = 0; i < fd_count; i++) { - struct cds_lfht_node *node; - struct unsuspendable_fd *entry = entries[i]; - - node = cds_lfht_add_unique(tracker->unsuspendable_fds, - hash_key_ulong((void *) (unsigned long) - out_fds[i], - seed.value), - match_fd, (void *) (unsigned long) out_fds[i], - &entry->tracker_node); - - if (node != &entry->tracker_node) { - ret = -EEXIST; - rcu_read_unlock(); - goto end_free_entries; - } - entries[i] = NULL; - } - tracker->count.unsuspendable += fd_count; - rcu_read_unlock(); - ret = user_ret; -end_unlock: - pthread_mutex_unlock(&tracker->lock); -end: - free(entries); - return ret; -end_free_entries: - for (i = 0; i < fd_count; i++) { - unsuspendable_fd_destroy(entries[i]); - } - goto end_unlock; -} - -int fd_tracker_close_unsuspendable_fd(struct fd_tracker *tracker, - int *fds_in, - unsigned int fd_count, - fd_close_cb close, - void *user_data) -{ - int i, ret, user_ret; - int *fds = NULL; - - /* - * Maintain a local copy of fds_in as the user's callback may modify its - * contents (e.g. setting the fd(s) to -1 after close). - */ - fds = malloc(sizeof(*fds) * fd_count); - if (!fds) { - ret = -1; - goto end; - } - memcpy(fds, fds_in, sizeof(*fds) * fd_count); - - pthread_mutex_lock(&tracker->lock); - rcu_read_lock(); - - /* Let the user close the file descriptors. */ - user_ret = close(user_data, fds_in); - if (user_ret) { - ret = user_ret; - goto end_unlock; - } - - /* Untrack the fds that were just closed by the user's callback. */ - for (i = 0; i < fd_count; i++) { - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - struct unsuspendable_fd *entry; - - cds_lfht_lookup(tracker->unsuspendable_fds, - hash_key_ulong((void *) (unsigned long) fds[i], - seed.value), - match_fd, (void *) (unsigned long) fds[i], - &iter); - node = cds_lfht_iter_get_node(&iter); - if (!node) { - /* Unknown file descriptor. */ - WARN("Untracked file descriptor %d passed to fd_tracker_close_unsuspendable_fd()", - fds[i]); - ret = -EINVAL; - goto end_unlock; - } - entry = caa_container_of( - node, struct unsuspendable_fd, tracker_node); - - cds_lfht_del(tracker->unsuspendable_fds, node); - unsuspendable_fd_destroy(entry); - fds[i] = -1; - } - - tracker->count.unsuspendable -= fd_count; - ret = 0; -end_unlock: - rcu_read_unlock(); - pthread_mutex_unlock(&tracker->lock); - free(fds); -end: - return ret; -} - -/* Caller must have taken the tracker's and handle's locks. */ -static void fd_tracker_track( - struct fd_tracker *tracker, struct fs_handle_tracked *handle) -{ - if (handle->fd >= 0) { - tracker->count.suspendable.active++; - cds_list_add_tail(&handle->handles_list_node, - &tracker->active_handles); - } else { - tracker->count.suspendable.suspended++; - cds_list_add_tail(&handle->handles_list_node, - &tracker->suspended_handles); - } -} - -/* Caller must have taken the tracker's and handle's locks. */ -static void fd_tracker_untrack( - struct fd_tracker *tracker, struct fs_handle_tracked *handle) -{ - if (handle->fd >= 0) { - tracker->count.suspendable.active--; - } else { - tracker->count.suspendable.suspended--; - } - cds_list_del(&handle->handles_list_node); -} - -/* Caller must have taken the tracker's and handle's locks. */ -static int fd_tracker_restore_handle( - struct fd_tracker *tracker, struct fs_handle_tracked *handle) -{ - int ret; - - fd_tracker_untrack(tracker, handle); - if (ACTIVE_COUNT(tracker) >= tracker->capacity) { - ret = fd_tracker_suspend_handles(tracker, 1); - if (ret) { - goto end; - } - } - ret = fs_handle_tracked_restore(handle); -end: - fd_tracker_track(tracker, handle); - return ret ? ret : handle->fd; -} - -static int fs_handle_tracked_get_fd(struct fs_handle *_handle) -{ - int ret; - struct fs_handle_tracked *handle = - container_of(_handle, struct fs_handle_tracked, parent); - - /* - * TODO This should be optimized as it is a fairly hot path. - * The fd-tracker's lock should only be taken when a fs_handle is - * restored (slow path). On the fast path (fs_handle is active), - * the only effect on the fd_tracker is marking the handle as the - * most recently used. Currently, it is done by a call to the - * track/untrack helpers, but it should be done atomically. - * - * Note that the lock's nesting order must still be respected here. - * The handle's lock nests inside the tracker's lock. - */ - pthread_mutex_lock(&handle->tracker->lock); - pthread_mutex_lock(&handle->lock); - LTTNG_ASSERT(!handle->in_use); - - handle->tracker->stats.uses++; - if (handle->fd >= 0) { - ret = handle->fd; - /* Mark as most recently used. */ - fd_tracker_untrack(handle->tracker, handle); - fd_tracker_track(handle->tracker, handle); - } else { - handle->tracker->stats.misses++; - ret = fd_tracker_restore_handle(handle->tracker, handle); - if (ret < 0) { - handle->tracker->stats.errors++; - goto end; - } - } - handle->in_use = true; -end: - pthread_mutex_unlock(&handle->lock); - pthread_mutex_unlock(&handle->tracker->lock); - return ret; -} - -static void fs_handle_tracked_put_fd(struct fs_handle *_handle) -{ - struct fs_handle_tracked *handle = - container_of(_handle, struct fs_handle_tracked, parent); - - pthread_mutex_lock(&handle->lock); - handle->in_use = false; - pthread_mutex_unlock(&handle->lock); -} - -static int fs_handle_tracked_unlink(struct fs_handle *_handle) -{ - int ret; - struct fs_handle_tracked *handle = - container_of(_handle, struct fs_handle_tracked, parent); - - pthread_mutex_lock(&handle->tracker->lock); - pthread_mutex_lock(&handle->lock); - ret = lttng_inode_unlink(handle->inode); - pthread_mutex_unlock(&handle->lock); - pthread_mutex_unlock(&handle->tracker->lock); - return ret; -} - -static int fs_handle_tracked_close(struct fs_handle *_handle) -{ - int ret = 0; - const char *path = NULL; - struct fs_handle_tracked *handle = - container_of(_handle, struct fs_handle_tracked, parent); - struct lttng_directory_handle *inode_directory_handle = NULL; - - if (!handle) { - ret = -EINVAL; - goto end; - } - - pthread_mutex_lock(&handle->tracker->lock); - pthread_mutex_lock(&handle->lock); - if (handle->inode) { - lttng_inode_borrow_location(handle->inode, NULL, &path); - /* - * Here a reference to the inode's directory handle is acquired - * to prevent the last reference to it from being released while - * the tracker's lock is taken. - * - * If this wasn't done, the directory handle could attempt to - * close its underlying directory file descriptor, which would - * attempt to lock the tracker's lock, resulting in a deadlock. - * - * Since a new reference to the directory handle is taken within - * the scope of this function, it is not possible for the last - * reference to the inode's location directory handle to be - * released during the call to lttng_inode_put(). - * - * We wait until the tracker's lock is released to release the - * reference. Hence, the call to the tracker is delayed just - * enough to not attempt to recursively acquire the tracker's - * lock twice. - */ - inode_directory_handle = - lttng_inode_get_location_directory_handle( - handle->inode); - } - fd_tracker_untrack(handle->tracker, handle); - if (handle->fd >= 0) { - /* - * The return value of close() is not propagated as there - * isn't much the user can do about it. - */ - if (close(handle->fd)) { - PERROR("Failed to close the file descriptor (%d) of fs handle to %s, close() returned", - handle->fd, path ? path : "Unknown"); - } - handle->fd = -1; - } - if (handle->inode) { - lttng_inode_put(handle->inode); - } - pthread_mutex_unlock(&handle->lock); - pthread_mutex_destroy(&handle->lock); - pthread_mutex_unlock(&handle->tracker->lock); - free(handle); - lttng_directory_handle_put(inode_directory_handle); -end: - return ret; -} diff --git a/src/common/fd-tracker/fd-tracker.cpp b/src/common/fd-tracker/fd-tracker.cpp new file mode 100644 index 000000000..f1058aa52 --- /dev/null +++ b/src/common/fd-tracker/fd-tracker.cpp @@ -0,0 +1,948 @@ +/* + * Copyright (C) 2018-2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "fd-tracker.h" +#include "inode.h" + +/* Tracker lock must be taken by the user. */ +#define TRACKED_COUNT(tracker) \ + (tracker->count.suspendable.active + \ + tracker->count.suspendable.suspended + \ + tracker->count.unsuspendable) + +/* Tracker lock must be taken by the user. */ +#define ACTIVE_COUNT(tracker) \ + (tracker->count.suspendable.active + tracker->count.unsuspendable) + +/* Tracker lock must be taken by the user. */ +#define SUSPENDED_COUNT(tracker) (tracker->count.suspendable.suspended) + +/* Tracker lock must be taken by the user. */ +#define SUSPENDABLE_COUNT(tracker) \ + (tracker->count.suspendable.active + \ + tracker->count.suspendable.suspended) + +/* Tracker lock must be taken by the user. */ +#define UNSUSPENDABLE_COUNT(tracker) (tracker->count.unsuspendable) + +struct fd_tracker { + pthread_mutex_t lock; + struct { + struct { + unsigned int active; + unsigned int suspended; + } suspendable; + unsigned int unsuspendable; + } count; + unsigned int capacity; + struct { + uint64_t uses; + uint64_t misses; + /* Failures to suspend or restore fs handles. */ + uint64_t errors; + } stats; + /* + * The head of the active_handles list is always the least recently + * used active handle. When an handle is used, it is removed from the + * list and added to the end. When a file has to be suspended, the + * first element in the list is "popped", suspended, and added to the + * list of suspended handles. + */ + struct cds_list_head active_handles; + struct cds_list_head suspended_handles; + struct cds_lfht *unsuspendable_fds; + struct lttng_inode_registry *inode_registry; + /* Unlinked files are moved in this directory under a unique name. */ + struct lttng_directory_handle *unlink_directory_handle; + struct lttng_unlinked_file_pool *unlinked_file_pool; +}; + +struct open_properties { + int flags; + LTTNG_OPTIONAL(mode_t) mode; +}; + +/* + * A fs_handle_tracked is not ref-counted. Therefore, it is assumed that a + * handle is never in-use while it is being reclaimed. It can be + * shared by multiple threads, but external synchronization is required + * to ensure it is not still being used when it is reclaimed (close method). + * In this respect, it is not different from a regular file descriptor. + * + * The fs_handle lock always nests _within_ the tracker's lock. + */ +struct fs_handle_tracked { + struct fs_handle parent; + pthread_mutex_t lock; + /* + * Weak reference to the tracker. All fs_handles are assumed to have + * been closed at the moment of the destruction of the fd_tracker. + */ + struct fd_tracker *tracker; + struct open_properties properties; + struct lttng_inode *inode; + int fd; + /* inode number of the file at the time of the handle's creation. */ + uint64_t ino; + bool in_use; + /* Offset to which the file should be restored. */ + off_t offset; + struct cds_list_head handles_list_node; +}; + +struct unsuspendable_fd { + /* + * Accesses are only performed through the tracker, which is protected + * by its own lock. + */ + int fd; + char *name; + struct cds_lfht_node tracker_node; + struct rcu_head rcu_head; +}; + +static struct { + pthread_mutex_t lock; + bool initialized; + unsigned long value; +} seed = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +static int match_fd(struct cds_lfht_node *node, const void *key); +static void unsuspendable_fd_destroy(struct unsuspendable_fd *entry); +static struct unsuspendable_fd *unsuspendable_fd_create( + const char *name, int fd); +static int open_from_properties(const struct lttng_directory_handle *dir_handle, + const char *path, struct open_properties *properties); + +static void fs_handle_tracked_log(struct fs_handle_tracked *handle); +static int fs_handle_tracked_suspend(struct fs_handle_tracked *handle); +static int fs_handle_tracked_restore(struct fs_handle_tracked *handle); +static int fs_handle_tracked_get_fd(struct fs_handle *_handle); +static void fs_handle_tracked_put_fd(struct fs_handle *_handle); +static int fs_handle_tracked_unlink(struct fs_handle *_handle); +static int fs_handle_tracked_close(struct fs_handle *_handle); + +static void fd_tracker_track( + struct fd_tracker *tracker, struct fs_handle_tracked *handle); +static void fd_tracker_untrack( + struct fd_tracker *tracker, struct fs_handle_tracked *handle); +static int fd_tracker_suspend_handles( + struct fd_tracker *tracker, unsigned int count); +static int fd_tracker_restore_handle( + struct fd_tracker *tracker, struct fs_handle_tracked *handle); + +/* Match function of the tracker's unsuspendable_fds hash table. */ +static int match_fd(struct cds_lfht_node *node, const void *key) +{ + struct unsuspendable_fd *entry = caa_container_of( + node, struct unsuspendable_fd, tracker_node); + + return hash_match_key_ulong( + (void *) (unsigned long) entry->fd, (void *) key); +} + +static void delete_unsuspendable_fd(struct rcu_head *head) +{ + struct unsuspendable_fd *fd = caa_container_of( + head, struct unsuspendable_fd, rcu_head); + + free(fd->name); + free(fd); +} + +static void unsuspendable_fd_destroy(struct unsuspendable_fd *entry) +{ + if (!entry) { + return; + } + call_rcu(&entry->rcu_head, delete_unsuspendable_fd); +} + +static struct unsuspendable_fd *unsuspendable_fd_create( + const char *name, int fd) +{ + struct unsuspendable_fd *entry = (unsuspendable_fd *) zmalloc(sizeof(*entry)); + + if (!entry) { + goto error; + } + if (name) { + entry->name = strdup(name); + if (!entry->name) { + goto error; + } + } + cds_lfht_node_init(&entry->tracker_node); + entry->fd = fd; + return entry; +error: + unsuspendable_fd_destroy(entry); + return NULL; +} + +static void fs_handle_tracked_log(struct fs_handle_tracked *handle) +{ + const char *path; + + pthread_mutex_lock(&handle->lock); + lttng_inode_borrow_location(handle->inode, NULL, &path); + + if (handle->fd >= 0) { + DBG_NO_LOC(" %s [active, fd %d%s]", path, handle->fd, + handle->in_use ? ", in use" : ""); + } else { + DBG_NO_LOC(" %s [suspended]", path); + } + pthread_mutex_unlock(&handle->lock); +} + +/* Tracker lock must be held by the caller. */ +static int fs_handle_tracked_suspend(struct fs_handle_tracked *handle) +{ + int ret = 0; + struct stat fs_stat; + const char *path; + const struct lttng_directory_handle *node_directory_handle; + + pthread_mutex_lock(&handle->lock); + lttng_inode_borrow_location( + handle->inode, &node_directory_handle, &path); + LTTNG_ASSERT(handle->fd >= 0); + if (handle->in_use) { + /* This handle can't be suspended as it is currently in use. */ + ret = -EAGAIN; + goto end; + } + + ret = lttng_directory_handle_stat( + node_directory_handle, path, &fs_stat); + if (ret) { + PERROR("Filesystem handle to %s cannot be suspended as stat() failed", + path); + ret = -errno; + goto end; + } + + if (fs_stat.st_ino != handle->ino) { + /* Don't suspend as the handle would not be restorable. */ + WARN("Filesystem handle to %s cannot be suspended as its inode changed", + path); + ret = -ENOENT; + goto end; + } + + handle->offset = lseek(handle->fd, 0, SEEK_CUR); + if (handle->offset == -1) { + WARN("Filesystem handle to %s cannot be suspended as lseek() failed to sample its current position", + path); + ret = -errno; + goto end; + } + + ret = close(handle->fd); + if (ret) { + PERROR("Filesystem handle to %s cannot be suspended as close() failed", + path); + ret = -errno; + goto end; + } + DBG("Suspended filesystem handle to %s (fd %i) at position %" PRId64, + path, handle->fd, handle->offset); + handle->fd = -1; +end: + if (ret) { + handle->tracker->stats.errors++; + } + pthread_mutex_unlock(&handle->lock); + return ret; +} + +/* Caller must hold the tracker and handle's locks. */ +static int fs_handle_tracked_restore(struct fs_handle_tracked *handle) +{ + int ret, fd = -1; + const char *path; + const struct lttng_directory_handle *node_directory_handle; + + lttng_inode_borrow_location( + handle->inode, &node_directory_handle, &path); + + LTTNG_ASSERT(handle->fd == -1); + LTTNG_ASSERT(path); + ret = open_from_properties( + node_directory_handle, path, &handle->properties); + if (ret < 0) { + PERROR("Failed to restore filesystem handle to %s, open() failed", + path); + ret = -errno; + goto end; + } + fd = ret; + + ret = lseek(fd, handle->offset, SEEK_SET); + if (ret < 0) { + PERROR("Failed to restore filesystem handle to %s, lseek() failed", + path); + ret = -errno; + goto end; + } + DBG("Restored filesystem handle to %s (fd %i) at position %" PRId64, + path, fd, handle->offset); + ret = 0; + handle->fd = fd; + fd = -1; +end: + if (fd >= 0) { + (void) close(fd); + } + return ret; +} + +static int open_from_properties(const struct lttng_directory_handle *dir_handle, + const char *path, struct open_properties *properties) +{ + int ret; + + /* + * open() ignores the 'flags' parameter unless the O_CREAT or O_TMPFILE + * flags are set. O_TMPFILE would not make sense in the context of a + * suspendable fs_handle as it would not be restorable (see OPEN(2)), + * thus it is ignored here. + */ + if ((properties->flags & O_CREAT) && properties->mode.is_set) { + ret = lttng_directory_handle_open_file(dir_handle, path, + properties->flags, properties->mode.value); + } else { + ret = lttng_directory_handle_open_file(dir_handle, path, + properties->flags, 0); + } + /* + * Some flags should not be used beyond the initial open() of a + * restorable file system handle. O_CREAT and O_TRUNC must + * be cleared since it would be unexpected to re-use them + * when the handle is retored: + * - O_CREAT should not be needed as the file has been created + * on the initial call to open(), + * - O_TRUNC would destroy the file's contents by truncating it + * to length 0. + */ + properties->flags &= ~(O_CREAT | O_TRUNC); + if (ret < 0) { + ret = -errno; + goto end; + } +end: + return ret; +} + +struct fd_tracker *fd_tracker_create(const char *unlinked_file_path, + unsigned int capacity) +{ + struct fd_tracker *tracker = (fd_tracker *) zmalloc(sizeof(struct fd_tracker)); + + if (!tracker) { + goto end; + } + + pthread_mutex_lock(&seed.lock); + if (!seed.initialized) { + seed.value = (unsigned long) time(NULL); + seed.initialized = true; + } + pthread_mutex_unlock(&seed.lock); + + CDS_INIT_LIST_HEAD(&tracker->active_handles); + CDS_INIT_LIST_HEAD(&tracker->suspended_handles); + tracker->capacity = capacity; + tracker->unsuspendable_fds = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!tracker->unsuspendable_fds) { + ERR("Failed to create fd-tracker's unsuspendable_fds hash table"); + goto error; + } + tracker->inode_registry = lttng_inode_registry_create(); + if (!tracker->inode_registry) { + ERR("Failed to create fd-tracker's inode registry"); + goto error; + } + + tracker->unlinked_file_pool = + lttng_unlinked_file_pool_create(unlinked_file_path); + if (!tracker->unlinked_file_pool) { + goto error; + } + DBG("File descriptor tracker created with a limit of %u simultaneously-opened FDs", + capacity); +end: + return tracker; +error: + fd_tracker_destroy(tracker); + return NULL; +} + +void fd_tracker_log(struct fd_tracker *tracker) +{ + struct fs_handle_tracked *handle; + struct unsuspendable_fd *unsuspendable_fd; + struct cds_lfht_iter iter; + + pthread_mutex_lock(&tracker->lock); + DBG_NO_LOC("File descriptor tracker"); + DBG_NO_LOC(" Stats:"); + DBG_NO_LOC(" uses: %" PRIu64, tracker->stats.uses); + DBG_NO_LOC(" misses: %" PRIu64, tracker->stats.misses); + DBG_NO_LOC(" errors: %" PRIu64, tracker->stats.errors); + DBG_NO_LOC(" Tracked: %u", TRACKED_COUNT(tracker)); + DBG_NO_LOC(" active: %u", ACTIVE_COUNT(tracker)); + DBG_NO_LOC(" suspendable: %u", SUSPENDABLE_COUNT(tracker)); + DBG_NO_LOC(" unsuspendable: %u", UNSUSPENDABLE_COUNT(tracker)); + DBG_NO_LOC(" suspended: %u", SUSPENDED_COUNT(tracker)); + DBG_NO_LOC(" capacity: %u", tracker->capacity); + + DBG_NO_LOC(" Tracked suspendable file descriptors"); + cds_list_for_each_entry( + handle, &tracker->active_handles, handles_list_node) { + fs_handle_tracked_log(handle); + } + cds_list_for_each_entry(handle, &tracker->suspended_handles, + handles_list_node) { + fs_handle_tracked_log(handle); + } + if (!SUSPENDABLE_COUNT(tracker)) { + DBG_NO_LOC(" None"); + } + + DBG_NO_LOC(" Tracked unsuspendable file descriptors"); + rcu_read_lock(); + cds_lfht_for_each_entry(tracker->unsuspendable_fds, &iter, + unsuspendable_fd, tracker_node) { + DBG_NO_LOC(" %s [active, fd %d]", + unsuspendable_fd->name ?: "Unnamed", + unsuspendable_fd->fd); + } + rcu_read_unlock(); + if (!UNSUSPENDABLE_COUNT(tracker)) { + DBG_NO_LOC(" None"); + } + + pthread_mutex_unlock(&tracker->lock); +} + +int fd_tracker_destroy(struct fd_tracker *tracker) +{ + int ret = 0; + + if (!tracker) { + goto end; + } + /* + * Refuse to destroy the tracker as fs_handles may still old + * weak references to the tracker. + */ + pthread_mutex_lock(&tracker->lock); + if (TRACKED_COUNT(tracker)) { + ERR("A file descriptor leak has been detected: %u tracked file descriptors are still being tracked", + TRACKED_COUNT(tracker)); + pthread_mutex_unlock(&tracker->lock); + fd_tracker_log(tracker); + ret = -1; + goto end; + } + pthread_mutex_unlock(&tracker->lock); + + if (tracker->unsuspendable_fds) { + ret = cds_lfht_destroy(tracker->unsuspendable_fds, NULL); + LTTNG_ASSERT(!ret); + } + + lttng_inode_registry_destroy(tracker->inode_registry); + lttng_unlinked_file_pool_destroy(tracker->unlinked_file_pool); + pthread_mutex_destroy(&tracker->lock); + free(tracker); +end: + return ret; +} + +struct fs_handle *fd_tracker_open_fs_handle(struct fd_tracker *tracker, + struct lttng_directory_handle *directory, + const char *path, + int flags, + mode_t *mode) +{ + int ret; + struct fs_handle_tracked *handle = NULL; + struct stat fd_stat; + struct open_properties properties = { + .flags = flags, + .mode = { + .is_set = !!mode, + .value = mode ? *mode : 0, + } + }; + + pthread_mutex_lock(&tracker->lock); + if (ACTIVE_COUNT(tracker) == tracker->capacity) { + if (tracker->count.suspendable.active > 0) { + ret = fd_tracker_suspend_handles(tracker, 1); + if (ret) { + goto end; + } + } else { + /* + * There are not enough active suspendable file + * descriptors to open a new fd and still accommodate + * the tracker's capacity. + */ + WARN("Cannot open file system handle, too many unsuspendable file descriptors are opened (%u)", + tracker->count.unsuspendable); + goto end; + } + } + + handle = (fs_handle_tracked *) zmalloc(sizeof(*handle)); + if (!handle) { + goto end; + } + handle->parent = (typeof(handle->parent)) { + .get_fd = fs_handle_tracked_get_fd, + .put_fd = fs_handle_tracked_put_fd, + .unlink = fs_handle_tracked_unlink, + .close = fs_handle_tracked_close, + }; + + handle->tracker = tracker; + + ret = pthread_mutex_init(&handle->lock, NULL); + if (ret) { + PERROR("Failed to initialize handle mutex while creating fs handle"); + goto error_mutex_init; + } + + handle->fd = open_from_properties(directory, path, &properties); + if (handle->fd < 0) { + PERROR("Failed to open fs handle to %s, open() returned", path); + goto error; + } + + handle->properties = properties; + + handle->inode = lttng_inode_registry_get_inode(tracker->inode_registry, + directory, path, handle->fd, + tracker->unlinked_file_pool); + if (!handle->inode) { + ERR("Failed to get lttng_inode corresponding to file %s", path); + goto error; + } + + if (fstat(handle->fd, &fd_stat)) { + PERROR("Failed to retrieve file descriptor inode while creating fs handle, fstat() returned"); + goto error; + } + handle->ino = fd_stat.st_ino; + + fd_tracker_track(tracker, handle); +end: + pthread_mutex_unlock(&tracker->lock); + return handle ? &handle->parent : NULL; +error: + if (handle->inode) { + lttng_inode_put(handle->inode); + } + pthread_mutex_destroy(&handle->lock); +error_mutex_init: + free(handle); + handle = NULL; + goto end; +} + +/* Caller must hold the tracker's lock. */ +static int fd_tracker_suspend_handles( + struct fd_tracker *tracker, unsigned int count) +{ + unsigned int left_to_close = count; + unsigned int attempts_left = tracker->count.suspendable.active; + struct fs_handle_tracked *handle, *tmp; + + cds_list_for_each_entry_safe(handle, tmp, &tracker->active_handles, + handles_list_node) { + int ret; + + fd_tracker_untrack(tracker, handle); + ret = fs_handle_tracked_suspend(handle); + fd_tracker_track(tracker, handle); + if (!ret) { + left_to_close--; + } + attempts_left--; + + if (left_to_close == 0 || attempts_left == 0) { + break; + } + } + return left_to_close ? -EMFILE : 0; +} + +int fd_tracker_open_unsuspendable_fd(struct fd_tracker *tracker, + int *out_fds, + const char **names, + unsigned int fd_count, + fd_open_cb open, + void *user_data) +{ + int ret, user_ret, i, fds_to_suspend; + unsigned int active_fds; + struct unsuspendable_fd **entries; + + entries = (unsuspendable_fd **) zmalloc(fd_count * sizeof(*entries)); + if (!entries) { + ret = -1; + goto end; + } + + pthread_mutex_lock(&tracker->lock); + + active_fds = ACTIVE_COUNT(tracker); + fds_to_suspend = (int) active_fds + (int) fd_count - + (int) tracker->capacity; + if (fds_to_suspend > 0) { + if (fds_to_suspend <= tracker->count.suspendable.active) { + ret = fd_tracker_suspend_handles( + tracker, fds_to_suspend); + if (ret) { + goto end_unlock; + } + } else { + /* + * There are not enough active suspendable file + * descriptors to open a new fd and still accommodates the + * tracker's capacity. + */ + WARN("Cannot open unsuspendable fd, too many unsuspendable file descriptors are opened (%u)", + tracker->count.unsuspendable); + ret = -EMFILE; + goto end_unlock; + } + } + + user_ret = open(user_data, out_fds); + if (user_ret) { + ret = user_ret; + goto end_unlock; + } + + /* + * Add the fds returned by the user's callback to the hashtable + * of unsuspendable fds. + */ + for (i = 0; i < fd_count; i++) { + struct unsuspendable_fd *entry = unsuspendable_fd_create( + names ? names[i] : NULL, out_fds[i]); + + if (!entry) { + ret = -1; + goto end_free_entries; + } + entries[i] = entry; + } + + rcu_read_lock(); + for (i = 0; i < fd_count; i++) { + struct cds_lfht_node *node; + struct unsuspendable_fd *entry = entries[i]; + + node = cds_lfht_add_unique(tracker->unsuspendable_fds, + hash_key_ulong((void *) (unsigned long) + out_fds[i], + seed.value), + match_fd, (void *) (unsigned long) out_fds[i], + &entry->tracker_node); + + if (node != &entry->tracker_node) { + ret = -EEXIST; + rcu_read_unlock(); + goto end_free_entries; + } + entries[i] = NULL; + } + tracker->count.unsuspendable += fd_count; + rcu_read_unlock(); + ret = user_ret; +end_unlock: + pthread_mutex_unlock(&tracker->lock); +end: + free(entries); + return ret; +end_free_entries: + for (i = 0; i < fd_count; i++) { + unsuspendable_fd_destroy(entries[i]); + } + goto end_unlock; +} + +int fd_tracker_close_unsuspendable_fd(struct fd_tracker *tracker, + int *fds_in, + unsigned int fd_count, + fd_close_cb close, + void *user_data) +{ + int i, ret, user_ret; + int *fds = NULL; + + /* + * Maintain a local copy of fds_in as the user's callback may modify its + * contents (e.g. setting the fd(s) to -1 after close). + */ + fds = (int *) malloc(sizeof(*fds) * fd_count); + if (!fds) { + ret = -1; + goto end; + } + memcpy(fds, fds_in, sizeof(*fds) * fd_count); + + pthread_mutex_lock(&tracker->lock); + rcu_read_lock(); + + /* Let the user close the file descriptors. */ + user_ret = close(user_data, fds_in); + if (user_ret) { + ret = user_ret; + goto end_unlock; + } + + /* Untrack the fds that were just closed by the user's callback. */ + for (i = 0; i < fd_count; i++) { + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct unsuspendable_fd *entry; + + cds_lfht_lookup(tracker->unsuspendable_fds, + hash_key_ulong((void *) (unsigned long) fds[i], + seed.value), + match_fd, (void *) (unsigned long) fds[i], + &iter); + node = cds_lfht_iter_get_node(&iter); + if (!node) { + /* Unknown file descriptor. */ + WARN("Untracked file descriptor %d passed to fd_tracker_close_unsuspendable_fd()", + fds[i]); + ret = -EINVAL; + goto end_unlock; + } + entry = caa_container_of( + node, struct unsuspendable_fd, tracker_node); + + cds_lfht_del(tracker->unsuspendable_fds, node); + unsuspendable_fd_destroy(entry); + fds[i] = -1; + } + + tracker->count.unsuspendable -= fd_count; + ret = 0; +end_unlock: + rcu_read_unlock(); + pthread_mutex_unlock(&tracker->lock); + free(fds); +end: + return ret; +} + +/* Caller must have taken the tracker's and handle's locks. */ +static void fd_tracker_track( + struct fd_tracker *tracker, struct fs_handle_tracked *handle) +{ + if (handle->fd >= 0) { + tracker->count.suspendable.active++; + cds_list_add_tail(&handle->handles_list_node, + &tracker->active_handles); + } else { + tracker->count.suspendable.suspended++; + cds_list_add_tail(&handle->handles_list_node, + &tracker->suspended_handles); + } +} + +/* Caller must have taken the tracker's and handle's locks. */ +static void fd_tracker_untrack( + struct fd_tracker *tracker, struct fs_handle_tracked *handle) +{ + if (handle->fd >= 0) { + tracker->count.suspendable.active--; + } else { + tracker->count.suspendable.suspended--; + } + cds_list_del(&handle->handles_list_node); +} + +/* Caller must have taken the tracker's and handle's locks. */ +static int fd_tracker_restore_handle( + struct fd_tracker *tracker, struct fs_handle_tracked *handle) +{ + int ret; + + fd_tracker_untrack(tracker, handle); + if (ACTIVE_COUNT(tracker) >= tracker->capacity) { + ret = fd_tracker_suspend_handles(tracker, 1); + if (ret) { + goto end; + } + } + ret = fs_handle_tracked_restore(handle); +end: + fd_tracker_track(tracker, handle); + return ret ? ret : handle->fd; +} + +static int fs_handle_tracked_get_fd(struct fs_handle *_handle) +{ + int ret; + struct fs_handle_tracked *handle = + container_of(_handle, struct fs_handle_tracked, parent); + + /* + * TODO This should be optimized as it is a fairly hot path. + * The fd-tracker's lock should only be taken when a fs_handle is + * restored (slow path). On the fast path (fs_handle is active), + * the only effect on the fd_tracker is marking the handle as the + * most recently used. Currently, it is done by a call to the + * track/untrack helpers, but it should be done atomically. + * + * Note that the lock's nesting order must still be respected here. + * The handle's lock nests inside the tracker's lock. + */ + pthread_mutex_lock(&handle->tracker->lock); + pthread_mutex_lock(&handle->lock); + LTTNG_ASSERT(!handle->in_use); + + handle->tracker->stats.uses++; + if (handle->fd >= 0) { + ret = handle->fd; + /* Mark as most recently used. */ + fd_tracker_untrack(handle->tracker, handle); + fd_tracker_track(handle->tracker, handle); + } else { + handle->tracker->stats.misses++; + ret = fd_tracker_restore_handle(handle->tracker, handle); + if (ret < 0) { + handle->tracker->stats.errors++; + goto end; + } + } + handle->in_use = true; +end: + pthread_mutex_unlock(&handle->lock); + pthread_mutex_unlock(&handle->tracker->lock); + return ret; +} + +static void fs_handle_tracked_put_fd(struct fs_handle *_handle) +{ + struct fs_handle_tracked *handle = + container_of(_handle, struct fs_handle_tracked, parent); + + pthread_mutex_lock(&handle->lock); + handle->in_use = false; + pthread_mutex_unlock(&handle->lock); +} + +static int fs_handle_tracked_unlink(struct fs_handle *_handle) +{ + int ret; + struct fs_handle_tracked *handle = + container_of(_handle, struct fs_handle_tracked, parent); + + pthread_mutex_lock(&handle->tracker->lock); + pthread_mutex_lock(&handle->lock); + ret = lttng_inode_unlink(handle->inode); + pthread_mutex_unlock(&handle->lock); + pthread_mutex_unlock(&handle->tracker->lock); + return ret; +} + +static int fs_handle_tracked_close(struct fs_handle *_handle) +{ + int ret = 0; + const char *path = NULL; + struct fs_handle_tracked *handle = + container_of(_handle, struct fs_handle_tracked, parent); + struct lttng_directory_handle *inode_directory_handle = NULL; + + if (!handle) { + ret = -EINVAL; + goto end; + } + + pthread_mutex_lock(&handle->tracker->lock); + pthread_mutex_lock(&handle->lock); + if (handle->inode) { + lttng_inode_borrow_location(handle->inode, NULL, &path); + /* + * Here a reference to the inode's directory handle is acquired + * to prevent the last reference to it from being released while + * the tracker's lock is taken. + * + * If this wasn't done, the directory handle could attempt to + * close its underlying directory file descriptor, which would + * attempt to lock the tracker's lock, resulting in a deadlock. + * + * Since a new reference to the directory handle is taken within + * the scope of this function, it is not possible for the last + * reference to the inode's location directory handle to be + * released during the call to lttng_inode_put(). + * + * We wait until the tracker's lock is released to release the + * reference. Hence, the call to the tracker is delayed just + * enough to not attempt to recursively acquire the tracker's + * lock twice. + */ + inode_directory_handle = + lttng_inode_get_location_directory_handle( + handle->inode); + } + fd_tracker_untrack(handle->tracker, handle); + if (handle->fd >= 0) { + /* + * The return value of close() is not propagated as there + * isn't much the user can do about it. + */ + if (close(handle->fd)) { + PERROR("Failed to close the file descriptor (%d) of fs handle to %s, close() returned", + handle->fd, path ? path : "Unknown"); + } + handle->fd = -1; + } + if (handle->inode) { + lttng_inode_put(handle->inode); + } + pthread_mutex_unlock(&handle->lock); + pthread_mutex_destroy(&handle->lock); + pthread_mutex_unlock(&handle->tracker->lock); + free(handle); + lttng_directory_handle_put(inode_directory_handle); +end: + return ret; +} diff --git a/src/common/fd-tracker/inode.c b/src/common/fd-tracker/inode.c deleted file mode 100644 index 88e392bde..000000000 --- a/src/common/fd-tracker/inode.c +++ /dev/null @@ -1,543 +0,0 @@ -/* - * Copyright (C) 2020 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "inode.h" - -struct inode_id { - dev_t device; - ino_t inode; -}; - -struct lttng_inode_registry { - /* Hashtable of inode_id to lttng_inode. */ - struct cds_lfht *inodes; -}; - -struct lttng_inode { - struct inode_id id; - /* Node in the lttng_inode_registry's ht. */ - struct cds_lfht_node registry_node; - /* Weak reference to ht containing the node. */ - struct cds_lfht *registry_ht; - struct urcu_ref ref; - struct rcu_head rcu_head; - /* Location from which this file can be opened. */ - struct { - struct lttng_directory_handle *directory_handle; - char *path; - } location; - /* Unlink the underlying file at the release of the inode. */ - bool unlink_pending; - LTTNG_OPTIONAL(unsigned int) unlinked_id; - /* Weak reference. */ - struct lttng_unlinked_file_pool *unlinked_file_pool; -}; - -struct lttng_unlinked_file_pool { - struct lttng_directory_handle *unlink_directory_handle; - char *unlink_directory_path; - unsigned int file_count; - unsigned int next_id; -}; - -static struct { - pthread_mutex_t lock; - bool initialized; - unsigned long value; -} seed = { - .lock = PTHREAD_MUTEX_INITIALIZER, -}; - -static unsigned long lttng_inode_id_hash(const struct inode_id *id) -{ - uint64_t device = id->device, inode_no = id->inode; - - return hash_key_u64(&device, seed.value) ^ - hash_key_u64(&inode_no, seed.value); -} - -static int lttng_inode_match(struct cds_lfht_node *node, const void *key) -{ - const struct inode_id *id = key; - const struct lttng_inode *inode = caa_container_of( - node, struct lttng_inode, registry_node); - - return inode->id.device == id->device && inode->id.inode == id->inode; -} - -static void lttng_inode_free(struct rcu_head *head) -{ - struct lttng_inode *inode = - caa_container_of(head, struct lttng_inode, rcu_head); - - free(inode); -} - -static int lttng_unlinked_file_pool_add_inode( - struct lttng_unlinked_file_pool *pool, - struct lttng_inode *inode) -{ - int ret; - const unsigned int unlinked_id = pool->next_id++; - char *inode_unlinked_name; - bool reference_acquired; - - DBG("Adding inode of %s to unlinked file pool as id %u", - inode->location.path, unlinked_id); - ret = asprintf(&inode_unlinked_name, "%u", unlinked_id); - if (ret < 0) { - ERR("Failed to format unlinked inode name"); - ret = -1; - goto end; - } - - if (pool->file_count == 0) { - DBG("Creating unlinked files directory at %s", - pool->unlink_directory_path); - LTTNG_ASSERT(!pool->unlink_directory_handle); - ret = utils_mkdir(pool->unlink_directory_path, - S_IRWXU | S_IRWXG, -1, -1); - if (ret) { - if (errno == EEXIST) { - /* - * Unexpected (previous crash?), but not an - * error. - */ - DBG("Unlinked file directory \"%s\" already exists", - pool->unlink_directory_path); - } else { - PERROR("Failed to create unlinked files directory at %s", - pool->unlink_directory_path); - goto end; - } - } - pool->unlink_directory_handle = lttng_directory_handle_create( - pool->unlink_directory_path); - if (!pool->unlink_directory_handle) { - ERR("Failed to create directory handle to unlinked file pool at %s", - pool->unlink_directory_path); - ret = -1; - goto end; - } - } - - ret = lttng_directory_handle_rename(inode->location.directory_handle, - inode->location.path, pool->unlink_directory_handle, - inode_unlinked_name); - if (ret) { - goto end; - } - - lttng_directory_handle_put(inode->location.directory_handle); - inode->location.directory_handle = NULL; - reference_acquired = lttng_directory_handle_get( - pool->unlink_directory_handle); - LTTNG_ASSERT(reference_acquired); - inode->location.directory_handle = pool->unlink_directory_handle; - - free(inode->location.path); - inode->location.path = inode_unlinked_name; - inode_unlinked_name = NULL; - LTTNG_OPTIONAL_SET(&inode->unlinked_id, unlinked_id); - pool->file_count++; -end: - free(inode_unlinked_name); - return ret; -} - -static int lttng_unlinked_file_pool_remove_inode( - struct lttng_unlinked_file_pool *pool, - struct lttng_inode *inode) -{ - int ret; - - DBG("Removing inode with unlinked id %u from unlinked file pool", - LTTNG_OPTIONAL_GET(inode->unlinked_id)); - - ret = lttng_directory_handle_unlink_file( - inode->location.directory_handle, inode->location.path); - if (ret) { - PERROR("Failed to unlink file %s from unlinked file directory", - inode->location.path); - goto end; - } - free(inode->location.path); - inode->location.path = NULL; - lttng_directory_handle_put(inode->location.directory_handle); - inode->location.directory_handle = NULL; - - pool->file_count--; - if (pool->file_count == 0) { - ret = utils_recursive_rmdir(pool->unlink_directory_path); - if (ret) { - /* - * There is nothing the caller can do, don't report an - * error except through logging. - */ - PERROR("Failed to remove unlinked files directory at %s", - pool->unlink_directory_path); - } - lttng_directory_handle_put(pool->unlink_directory_handle); - pool->unlink_directory_handle = NULL; - } -end: - return ret; -} - -static void lttng_inode_destroy(struct lttng_inode *inode) -{ - if (!inode) { - return; - } - - rcu_read_lock(); - cds_lfht_del(inode->registry_ht, &inode->registry_node); - rcu_read_unlock(); - - if (inode->unlink_pending) { - int ret; - - LTTNG_ASSERT(inode->location.directory_handle); - LTTNG_ASSERT(inode->location.path); - DBG("Removing %s from unlinked file pool", - inode->location.path); - ret = lttng_unlinked_file_pool_remove_inode(inode->unlinked_file_pool, inode); - if (ret) { - PERROR("Failed to unlink %s", inode->location.path); - } - } - - lttng_directory_handle_put( - inode->location.directory_handle); - inode->location.directory_handle = NULL; - free(inode->location.path); - inode->location.path = NULL; - call_rcu(&inode->rcu_head, lttng_inode_free); -} - -static void lttng_inode_release(struct urcu_ref *ref) -{ - lttng_inode_destroy(caa_container_of(ref, struct lttng_inode, ref)); -} - -static void lttng_inode_get(struct lttng_inode *inode) -{ - urcu_ref_get(&inode->ref); -} - -struct lttng_unlinked_file_pool *lttng_unlinked_file_pool_create( - const char *path) -{ - struct lttng_unlinked_file_pool *pool = zmalloc(sizeof(*pool)); - - if (!pool) { - goto error; - } - - if (!path || *path != '/') { - ERR("Unlinked file pool must be created with an absolute path, path = \"%s\"", - path ? path : "NULL"); - goto error; - } - - pool->unlink_directory_path = strdup(path); - if (!pool->unlink_directory_path) { - PERROR("Failed to allocation unlinked file pool path"); - goto error; - } - DBG("Unlinked file pool created at: %s", path); - return pool; -error: - lttng_unlinked_file_pool_destroy(pool); - return NULL; -} - -void lttng_unlinked_file_pool_destroy( - struct lttng_unlinked_file_pool *pool) -{ - if (!pool) { - return; - } - - LTTNG_ASSERT(pool->file_count == 0); - lttng_directory_handle_put(pool->unlink_directory_handle); - free(pool->unlink_directory_path); - free(pool); -} - -void lttng_inode_put(struct lttng_inode *inode) -{ - urcu_ref_put(&inode->ref, lttng_inode_release); -} - -struct lttng_directory_handle * -lttng_inode_get_location_directory_handle( - struct lttng_inode *inode) -{ - if (inode->location.directory_handle) { - const bool reference_acquired = lttng_directory_handle_get( - inode->location.directory_handle); - - LTTNG_ASSERT(reference_acquired); - } - return inode->location.directory_handle; -} - -void lttng_inode_borrow_location(struct lttng_inode *inode, - const struct lttng_directory_handle **out_directory_handle, - const char **out_path) -{ - if (out_directory_handle) { - *out_directory_handle = inode->location.directory_handle; - } - if (out_path) { - *out_path = inode->location.path; - } -} - -int lttng_inode_rename( - struct lttng_inode *inode, - struct lttng_directory_handle *old_directory_handle, - const char *old_path, - struct lttng_directory_handle *new_directory_handle, - const char *new_path, - bool overwrite) -{ - int ret = 0; - char *new_path_copy = strdup(new_path); - bool reference_acquired; - - DBG("Performing rename of inode from %s to %s with %s directory handles", - old_path, new_path, - lttng_directory_handle_equals(old_directory_handle, - new_directory_handle) ? - "identical" : - "different"); - - if (!new_path_copy) { - ret = -ENOMEM; - goto end; - } - - if (inode->unlink_pending) { - WARN("An attempt to rename an unlinked file from %s to %s has been performed", - old_path, new_path); - ret = -ENOENT; - goto end; - } - - if (!overwrite) { - /* Verify that file doesn't exist. */ - struct stat statbuf; - - ret = lttng_directory_handle_stat( - new_directory_handle, new_path, &statbuf); - if (ret == 0) { - ERR("Refusing to rename %s as the destination already exists", - old_path); - ret = -EEXIST; - goto end; - } else if (ret < 0 && errno != ENOENT) { - PERROR("Failed to stat() %s", new_path); - ret = -errno; - goto end; - } - } - - ret = lttng_directory_handle_rename(old_directory_handle, old_path, - new_directory_handle, new_path); - if (ret) { - PERROR("Failed to rename file %s to %s", old_path, new_path); - ret = -errno; - goto end; - } - - reference_acquired = lttng_directory_handle_get(new_directory_handle); - LTTNG_ASSERT(reference_acquired); - lttng_directory_handle_put(inode->location.directory_handle); - free(inode->location.path); - inode->location.directory_handle = new_directory_handle; - /* Ownership transferred. */ - inode->location.path = new_path_copy; - new_path_copy = NULL; -end: - free(new_path_copy); - return ret; -} - -int lttng_inode_unlink(struct lttng_inode *inode) -{ - int ret = 0; - - DBG("Attempting unlink of inode %s", inode->location.path); - - if (inode->unlink_pending) { - WARN("An attempt to re-unlink %s has been performed, ignoring.", - inode->location.path); - ret = -ENOENT; - goto end; - } - - /* - * Move to the temporary "deleted" directory until all - * references are released. - */ - ret = lttng_unlinked_file_pool_add_inode( - inode->unlinked_file_pool, inode); - if (ret) { - PERROR("Failed to add inode \"%s\" to the unlinked file pool", - inode->location.path); - goto end; - } - inode->unlink_pending = true; -end: - return ret; -} - -static struct lttng_inode *lttng_inode_create(const struct inode_id *id, - struct cds_lfht *ht, - struct lttng_unlinked_file_pool *unlinked_file_pool, - struct lttng_directory_handle *directory_handle, - const char *path) -{ - struct lttng_inode *inode = NULL; - char *path_copy; - bool reference_acquired; - - path_copy = strdup(path); - if (!path_copy) { - goto end; - } - - reference_acquired = lttng_directory_handle_get(directory_handle); - LTTNG_ASSERT(reference_acquired); - - inode = zmalloc(sizeof(*inode)); - if (!inode) { - goto end; - } - - urcu_ref_init(&inode->ref); - cds_lfht_node_init(&inode->registry_node); - inode->id = *id; - inode->registry_ht = ht; - inode->unlinked_file_pool = unlinked_file_pool; - /* Ownership of path copy is transferred to inode. */ - inode->location.path = path_copy; - path_copy = NULL; - inode->location.directory_handle = directory_handle; -end: - free(path_copy); - return inode; -} - -struct lttng_inode_registry *lttng_inode_registry_create(void) -{ - struct lttng_inode_registry *registry = zmalloc(sizeof(*registry)); - - if (!registry) { - goto end; - } - - pthread_mutex_lock(&seed.lock); - if (!seed.initialized) { - seed.value = (unsigned long) time(NULL); - seed.initialized = true; - } - pthread_mutex_unlock(&seed.lock); - - registry->inodes = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!registry->inodes) { - goto error; - } -end: - return registry; -error: - lttng_inode_registry_destroy(registry); - return NULL; -} - -void lttng_inode_registry_destroy( - struct lttng_inode_registry *registry) -{ - if (!registry) { - return; - } - if (registry->inodes) { - int ret = cds_lfht_destroy(registry->inodes, NULL); - - LTTNG_ASSERT(!ret); - } - free(registry); -} - -struct lttng_inode *lttng_inode_registry_get_inode( - struct lttng_inode_registry *registry, - struct lttng_directory_handle *handle, - const char *path, - int fd, - struct lttng_unlinked_file_pool *unlinked_file_pool) -{ - int ret; - struct stat statbuf; - struct inode_id id; - struct cds_lfht_iter iter; - struct cds_lfht_node *node; - struct lttng_inode *inode = NULL; - - ret = fstat(fd, &statbuf); - if (ret < 0) { - PERROR("stat() failed on fd %i", fd); - goto end; - } - - id.device = statbuf.st_dev; - id.inode = statbuf.st_ino; - - rcu_read_lock(); - cds_lfht_lookup(registry->inodes, lttng_inode_id_hash(&id), - lttng_inode_match, &id, &iter); - node = cds_lfht_iter_get_node(&iter); - if (node) { - inode = caa_container_of( - node, struct lttng_inode, registry_node); - lttng_inode_get(inode); - goto end_unlock; - } - - inode = lttng_inode_create(&id, registry->inodes, unlinked_file_pool, - handle, path); - if (!inode) { - goto end_unlock; - } - - node = cds_lfht_add_unique(registry->inodes, - lttng_inode_id_hash(&inode->id), lttng_inode_match, - &inode->id, &inode->registry_node); - LTTNG_ASSERT(node == &inode->registry_node); -end_unlock: - rcu_read_unlock(); -end: - return inode; -} diff --git a/src/common/fd-tracker/inode.cpp b/src/common/fd-tracker/inode.cpp new file mode 100644 index 000000000..404ba360b --- /dev/null +++ b/src/common/fd-tracker/inode.cpp @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inode.h" + +struct inode_id { + dev_t device; + ino_t inode; +}; + +struct lttng_inode_registry { + /* Hashtable of inode_id to lttng_inode. */ + struct cds_lfht *inodes; +}; + +struct lttng_inode { + struct inode_id id; + /* Node in the lttng_inode_registry's ht. */ + struct cds_lfht_node registry_node; + /* Weak reference to ht containing the node. */ + struct cds_lfht *registry_ht; + struct urcu_ref ref; + struct rcu_head rcu_head; + /* Location from which this file can be opened. */ + struct { + struct lttng_directory_handle *directory_handle; + char *path; + } location; + /* Unlink the underlying file at the release of the inode. */ + bool unlink_pending; + LTTNG_OPTIONAL(unsigned int) unlinked_id; + /* Weak reference. */ + struct lttng_unlinked_file_pool *unlinked_file_pool; +}; + +struct lttng_unlinked_file_pool { + struct lttng_directory_handle *unlink_directory_handle; + char *unlink_directory_path; + unsigned int file_count; + unsigned int next_id; +}; + +static struct { + pthread_mutex_t lock; + bool initialized; + unsigned long value; +} seed = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +static unsigned long lttng_inode_id_hash(const struct inode_id *id) +{ + uint64_t device = id->device, inode_no = id->inode; + + return hash_key_u64(&device, seed.value) ^ + hash_key_u64(&inode_no, seed.value); +} + +static int lttng_inode_match(struct cds_lfht_node *node, const void *key) +{ + const struct inode_id *id = (inode_id *) key; + const struct lttng_inode *inode = caa_container_of( + node, struct lttng_inode, registry_node); + + return inode->id.device == id->device && inode->id.inode == id->inode; +} + +static void lttng_inode_free(struct rcu_head *head) +{ + struct lttng_inode *inode = + caa_container_of(head, struct lttng_inode, rcu_head); + + free(inode); +} + +static int lttng_unlinked_file_pool_add_inode( + struct lttng_unlinked_file_pool *pool, + struct lttng_inode *inode) +{ + int ret; + const unsigned int unlinked_id = pool->next_id++; + char *inode_unlinked_name; + bool reference_acquired; + + DBG("Adding inode of %s to unlinked file pool as id %u", + inode->location.path, unlinked_id); + ret = asprintf(&inode_unlinked_name, "%u", unlinked_id); + if (ret < 0) { + ERR("Failed to format unlinked inode name"); + ret = -1; + goto end; + } + + if (pool->file_count == 0) { + DBG("Creating unlinked files directory at %s", + pool->unlink_directory_path); + LTTNG_ASSERT(!pool->unlink_directory_handle); + ret = utils_mkdir(pool->unlink_directory_path, + S_IRWXU | S_IRWXG, -1, -1); + if (ret) { + if (errno == EEXIST) { + /* + * Unexpected (previous crash?), but not an + * error. + */ + DBG("Unlinked file directory \"%s\" already exists", + pool->unlink_directory_path); + } else { + PERROR("Failed to create unlinked files directory at %s", + pool->unlink_directory_path); + goto end; + } + } + pool->unlink_directory_handle = lttng_directory_handle_create( + pool->unlink_directory_path); + if (!pool->unlink_directory_handle) { + ERR("Failed to create directory handle to unlinked file pool at %s", + pool->unlink_directory_path); + ret = -1; + goto end; + } + } + + ret = lttng_directory_handle_rename(inode->location.directory_handle, + inode->location.path, pool->unlink_directory_handle, + inode_unlinked_name); + if (ret) { + goto end; + } + + lttng_directory_handle_put(inode->location.directory_handle); + inode->location.directory_handle = NULL; + reference_acquired = lttng_directory_handle_get( + pool->unlink_directory_handle); + LTTNG_ASSERT(reference_acquired); + inode->location.directory_handle = pool->unlink_directory_handle; + + free(inode->location.path); + inode->location.path = inode_unlinked_name; + inode_unlinked_name = NULL; + LTTNG_OPTIONAL_SET(&inode->unlinked_id, unlinked_id); + pool->file_count++; +end: + free(inode_unlinked_name); + return ret; +} + +static int lttng_unlinked_file_pool_remove_inode( + struct lttng_unlinked_file_pool *pool, + struct lttng_inode *inode) +{ + int ret; + + DBG("Removing inode with unlinked id %u from unlinked file pool", + LTTNG_OPTIONAL_GET(inode->unlinked_id)); + + ret = lttng_directory_handle_unlink_file( + inode->location.directory_handle, inode->location.path); + if (ret) { + PERROR("Failed to unlink file %s from unlinked file directory", + inode->location.path); + goto end; + } + free(inode->location.path); + inode->location.path = NULL; + lttng_directory_handle_put(inode->location.directory_handle); + inode->location.directory_handle = NULL; + + pool->file_count--; + if (pool->file_count == 0) { + ret = utils_recursive_rmdir(pool->unlink_directory_path); + if (ret) { + /* + * There is nothing the caller can do, don't report an + * error except through logging. + */ + PERROR("Failed to remove unlinked files directory at %s", + pool->unlink_directory_path); + } + lttng_directory_handle_put(pool->unlink_directory_handle); + pool->unlink_directory_handle = NULL; + } +end: + return ret; +} + +static void lttng_inode_destroy(struct lttng_inode *inode) +{ + if (!inode) { + return; + } + + rcu_read_lock(); + cds_lfht_del(inode->registry_ht, &inode->registry_node); + rcu_read_unlock(); + + if (inode->unlink_pending) { + int ret; + + LTTNG_ASSERT(inode->location.directory_handle); + LTTNG_ASSERT(inode->location.path); + DBG("Removing %s from unlinked file pool", + inode->location.path); + ret = lttng_unlinked_file_pool_remove_inode(inode->unlinked_file_pool, inode); + if (ret) { + PERROR("Failed to unlink %s", inode->location.path); + } + } + + lttng_directory_handle_put( + inode->location.directory_handle); + inode->location.directory_handle = NULL; + free(inode->location.path); + inode->location.path = NULL; + call_rcu(&inode->rcu_head, lttng_inode_free); +} + +static void lttng_inode_release(struct urcu_ref *ref) +{ + lttng_inode_destroy(caa_container_of(ref, struct lttng_inode, ref)); +} + +static void lttng_inode_get(struct lttng_inode *inode) +{ + urcu_ref_get(&inode->ref); +} + +struct lttng_unlinked_file_pool *lttng_unlinked_file_pool_create( + const char *path) +{ + struct lttng_unlinked_file_pool *pool = (lttng_unlinked_file_pool *) zmalloc(sizeof(*pool)); + + if (!pool) { + goto error; + } + + if (!path || *path != '/') { + ERR("Unlinked file pool must be created with an absolute path, path = \"%s\"", + path ? path : "NULL"); + goto error; + } + + pool->unlink_directory_path = strdup(path); + if (!pool->unlink_directory_path) { + PERROR("Failed to allocation unlinked file pool path"); + goto error; + } + DBG("Unlinked file pool created at: %s", path); + return pool; +error: + lttng_unlinked_file_pool_destroy(pool); + return NULL; +} + +void lttng_unlinked_file_pool_destroy( + struct lttng_unlinked_file_pool *pool) +{ + if (!pool) { + return; + } + + LTTNG_ASSERT(pool->file_count == 0); + lttng_directory_handle_put(pool->unlink_directory_handle); + free(pool->unlink_directory_path); + free(pool); +} + +void lttng_inode_put(struct lttng_inode *inode) +{ + urcu_ref_put(&inode->ref, lttng_inode_release); +} + +struct lttng_directory_handle * +lttng_inode_get_location_directory_handle( + struct lttng_inode *inode) +{ + if (inode->location.directory_handle) { + const bool reference_acquired = lttng_directory_handle_get( + inode->location.directory_handle); + + LTTNG_ASSERT(reference_acquired); + } + return inode->location.directory_handle; +} + +void lttng_inode_borrow_location(struct lttng_inode *inode, + const struct lttng_directory_handle **out_directory_handle, + const char **out_path) +{ + if (out_directory_handle) { + *out_directory_handle = inode->location.directory_handle; + } + if (out_path) { + *out_path = inode->location.path; + } +} + +int lttng_inode_rename( + struct lttng_inode *inode, + struct lttng_directory_handle *old_directory_handle, + const char *old_path, + struct lttng_directory_handle *new_directory_handle, + const char *new_path, + bool overwrite) +{ + int ret = 0; + char *new_path_copy = strdup(new_path); + bool reference_acquired; + + DBG("Performing rename of inode from %s to %s with %s directory handles", + old_path, new_path, + lttng_directory_handle_equals(old_directory_handle, + new_directory_handle) ? + "identical" : + "different"); + + if (!new_path_copy) { + ret = -ENOMEM; + goto end; + } + + if (inode->unlink_pending) { + WARN("An attempt to rename an unlinked file from %s to %s has been performed", + old_path, new_path); + ret = -ENOENT; + goto end; + } + + if (!overwrite) { + /* Verify that file doesn't exist. */ + struct stat statbuf; + + ret = lttng_directory_handle_stat( + new_directory_handle, new_path, &statbuf); + if (ret == 0) { + ERR("Refusing to rename %s as the destination already exists", + old_path); + ret = -EEXIST; + goto end; + } else if (ret < 0 && errno != ENOENT) { + PERROR("Failed to stat() %s", new_path); + ret = -errno; + goto end; + } + } + + ret = lttng_directory_handle_rename(old_directory_handle, old_path, + new_directory_handle, new_path); + if (ret) { + PERROR("Failed to rename file %s to %s", old_path, new_path); + ret = -errno; + goto end; + } + + reference_acquired = lttng_directory_handle_get(new_directory_handle); + LTTNG_ASSERT(reference_acquired); + lttng_directory_handle_put(inode->location.directory_handle); + free(inode->location.path); + inode->location.directory_handle = new_directory_handle; + /* Ownership transferred. */ + inode->location.path = new_path_copy; + new_path_copy = NULL; +end: + free(new_path_copy); + return ret; +} + +int lttng_inode_unlink(struct lttng_inode *inode) +{ + int ret = 0; + + DBG("Attempting unlink of inode %s", inode->location.path); + + if (inode->unlink_pending) { + WARN("An attempt to re-unlink %s has been performed, ignoring.", + inode->location.path); + ret = -ENOENT; + goto end; + } + + /* + * Move to the temporary "deleted" directory until all + * references are released. + */ + ret = lttng_unlinked_file_pool_add_inode( + inode->unlinked_file_pool, inode); + if (ret) { + PERROR("Failed to add inode \"%s\" to the unlinked file pool", + inode->location.path); + goto end; + } + inode->unlink_pending = true; +end: + return ret; +} + +static struct lttng_inode *lttng_inode_create(const struct inode_id *id, + struct cds_lfht *ht, + struct lttng_unlinked_file_pool *unlinked_file_pool, + struct lttng_directory_handle *directory_handle, + const char *path) +{ + struct lttng_inode *inode = NULL; + char *path_copy; + bool reference_acquired; + + path_copy = strdup(path); + if (!path_copy) { + goto end; + } + + reference_acquired = lttng_directory_handle_get(directory_handle); + LTTNG_ASSERT(reference_acquired); + + inode = (lttng_inode *) zmalloc(sizeof(*inode)); + if (!inode) { + goto end; + } + + urcu_ref_init(&inode->ref); + cds_lfht_node_init(&inode->registry_node); + inode->id = *id; + inode->registry_ht = ht; + inode->unlinked_file_pool = unlinked_file_pool; + /* Ownership of path copy is transferred to inode. */ + inode->location.path = path_copy; + path_copy = NULL; + inode->location.directory_handle = directory_handle; +end: + free(path_copy); + return inode; +} + +struct lttng_inode_registry *lttng_inode_registry_create(void) +{ + struct lttng_inode_registry *registry = (lttng_inode_registry *) zmalloc(sizeof(*registry)); + + if (!registry) { + goto end; + } + + pthread_mutex_lock(&seed.lock); + if (!seed.initialized) { + seed.value = (unsigned long) time(NULL); + seed.initialized = true; + } + pthread_mutex_unlock(&seed.lock); + + registry->inodes = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!registry->inodes) { + goto error; + } +end: + return registry; +error: + lttng_inode_registry_destroy(registry); + return NULL; +} + +void lttng_inode_registry_destroy( + struct lttng_inode_registry *registry) +{ + if (!registry) { + return; + } + if (registry->inodes) { + int ret = cds_lfht_destroy(registry->inodes, NULL); + + LTTNG_ASSERT(!ret); + } + free(registry); +} + +struct lttng_inode *lttng_inode_registry_get_inode( + struct lttng_inode_registry *registry, + struct lttng_directory_handle *handle, + const char *path, + int fd, + struct lttng_unlinked_file_pool *unlinked_file_pool) +{ + int ret; + struct stat statbuf; + struct inode_id id; + struct cds_lfht_iter iter; + struct cds_lfht_node *node; + struct lttng_inode *inode = NULL; + + ret = fstat(fd, &statbuf); + if (ret < 0) { + PERROR("stat() failed on fd %i", fd); + goto end; + } + + id.device = statbuf.st_dev; + id.inode = statbuf.st_ino; + + rcu_read_lock(); + cds_lfht_lookup(registry->inodes, lttng_inode_id_hash(&id), + lttng_inode_match, &id, &iter); + node = cds_lfht_iter_get_node(&iter); + if (node) { + inode = caa_container_of( + node, struct lttng_inode, registry_node); + lttng_inode_get(inode); + goto end_unlock; + } + + inode = lttng_inode_create(&id, registry->inodes, unlinked_file_pool, + handle, path); + if (!inode) { + goto end_unlock; + } + + node = cds_lfht_add_unique(registry->inodes, + lttng_inode_id_hash(&inode->id), lttng_inode_match, + &inode->id, &inode->registry_node); + LTTNG_ASSERT(node == &inode->registry_node); +end_unlock: + rcu_read_unlock(); +end: + return inode; +} diff --git a/src/common/fd-tracker/utils-poll.c b/src/common/fd-tracker/utils-poll.c deleted file mode 100644 index 17349a45f..000000000 --- a/src/common/fd-tracker/utils-poll.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include - -#include "utils.h" - -#if HAVE_EPOLL - -struct create_args { - struct lttng_poll_event *events; - int size; - int flags; -}; - -static int open_epoll(void *data, int *out_fd) -{ - int ret; - struct create_args *args = data; - - ret = lttng_poll_create(args->events, args->size, args->flags); - if (ret < 0) { - goto end; - } - - *out_fd = args->events->epfd; -end: - return ret; -} - -static int close_epoll(void *data, int *in_fd) -{ - /* Will close the epfd. */ - lttng_poll_clean((struct lttng_poll_event *) data); - return 0; -} - -/* - * The epoll variant of the poll compat layer creates an unsuspendable fd which - * must be tracked. - */ -int fd_tracker_util_poll_create(struct fd_tracker *tracker, - const char *name, - struct lttng_poll_event *events, - int size, - int flags) -{ - int out_fd; - struct create_args create_args = { - .events = events, - .size = size, - .flags = flags, - }; - - return fd_tracker_open_unsuspendable_fd( - tracker, &out_fd, &name, 1, open_epoll, &create_args); -} - -int fd_tracker_util_poll_clean( - struct fd_tracker *tracker, struct lttng_poll_event *events) -{ - return fd_tracker_close_unsuspendable_fd( - tracker, &events->epfd, 1, close_epoll, events); -} - -#else /* HAVE_EPOLL */ - -/* - * The epoll variant of the poll compat layer creates an unsuspendable fd which - * must be tracked. - */ -int fd_tracker_util_poll_create(struct fd_tracker *tracker, - const char *name, - struct lttng_poll_event *events, - int size, - int flags) -{ - return lttng_poll_create(events, size, flags); -} - -int fd_tracker_util_poll_clean( - struct fd_tracker *tracker, struct lttng_poll_event *events) -{ - lttng_poll_clean(events); - return 0; -} - -#endif /* !HAVE_EPOLL */ diff --git a/src/common/fd-tracker/utils-poll.cpp b/src/common/fd-tracker/utils-poll.cpp new file mode 100644 index 000000000..4949d8160 --- /dev/null +++ b/src/common/fd-tracker/utils-poll.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include + +#include "utils.h" + +#if HAVE_EPOLL + +struct create_args { + struct lttng_poll_event *events; + int size; + int flags; +}; + +static int open_epoll(void *data, int *out_fd) +{ + int ret; + struct create_args *args = (create_args *) data; + + ret = lttng_poll_create(args->events, args->size, args->flags); + if (ret < 0) { + goto end; + } + + *out_fd = args->events->epfd; +end: + return ret; +} + +static int close_epoll(void *data, int *in_fd) +{ + /* Will close the epfd. */ + lttng_poll_clean((struct lttng_poll_event *) data); + return 0; +} + +/* + * The epoll variant of the poll compat layer creates an unsuspendable fd which + * must be tracked. + */ +int fd_tracker_util_poll_create(struct fd_tracker *tracker, + const char *name, + struct lttng_poll_event *events, + int size, + int flags) +{ + int out_fd; + struct create_args create_args = { + .events = events, + .size = size, + .flags = flags, + }; + + return fd_tracker_open_unsuspendable_fd( + tracker, &out_fd, &name, 1, open_epoll, &create_args); +} + +int fd_tracker_util_poll_clean( + struct fd_tracker *tracker, struct lttng_poll_event *events) +{ + return fd_tracker_close_unsuspendable_fd( + tracker, &events->epfd, 1, close_epoll, events); +} + +#else /* HAVE_EPOLL */ + +/* + * The epoll variant of the poll compat layer creates an unsuspendable fd which + * must be tracked. + */ +int fd_tracker_util_poll_create(struct fd_tracker *tracker, + const char *name, + struct lttng_poll_event *events, + int size, + int flags) +{ + return lttng_poll_create(events, size, flags); +} + +int fd_tracker_util_poll_clean( + struct fd_tracker *tracker, struct lttng_poll_event *events) +{ + lttng_poll_clean(events); + return 0; +} + +#endif /* !HAVE_EPOLL */ diff --git a/src/common/fd-tracker/utils.c b/src/common/fd-tracker/utils.c deleted file mode 100644 index fc307fea9..000000000 --- a/src/common/fd-tracker/utils.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include -#include -#include - -static -int open_pipe_cloexec(void *data, int *fds) -{ - return utils_create_pipe_cloexec(fds); -} - -static -int close_pipe(void *data, int *pipe) -{ - utils_close_pipe(pipe); - pipe[0] = pipe[1] = -1; - return 0; -} - -int fd_tracker_util_close_fd(void *unused, int *fd) -{ - return close(*fd); -} - -int fd_tracker_util_pipe_open_cloexec( - struct fd_tracker *tracker, const char *name, int *pipe) -{ - int ret; - const char *name_prefix; - char *names[2]; - - name_prefix = name ? name : "Unknown pipe"; - ret = asprintf(&names[0], "%s (read end)", name_prefix); - if (ret < 0) { - goto end; - } - ret = asprintf(&names[1], "%s (write end)", name_prefix); - if (ret < 0) { - goto end; - } - - ret = fd_tracker_open_unsuspendable_fd(tracker, pipe, - (const char **) names, 2, open_pipe_cloexec, NULL); - free(names[0]); - free(names[1]); -end: - return ret; -} - -int fd_tracker_util_pipe_close(struct fd_tracker *tracker, int *pipe) -{ - return fd_tracker_close_unsuspendable_fd( - tracker, pipe, 2, close_pipe, NULL); -} - -struct open_directory_handle_args { - const struct lttng_directory_handle *in_handle; - struct lttng_directory_handle *ret_handle; - const char *path; -}; - -static -int open_directory_handle(void *_args, int *out_fds) -{ - int ret = 0; - struct open_directory_handle_args *args = _args; - struct lttng_directory_handle *new_handle = NULL; - - new_handle = args->in_handle ? - lttng_directory_handle_create_from_handle( - args->path, args->in_handle) : - lttng_directory_handle_create(args->path); - if (!new_handle) { - ret = -errno; - goto end; - } - - args->ret_handle = new_handle; - - /* - * Reserved to indicate that the handle does not use a handle; there is - * nothing to track. We want to indicate an error to the fd-tracker so - * that it doesn't attempt to track the file descriptor, but also want - * the caller to retrieve the newly-created handle. - * - * Calling this a hack is a fair assessment. - */ - if (!lttng_directory_handle_uses_fd(new_handle)) { - ret = ENOTSUP; - } else { -#ifdef HAVE_DIRFD - *out_fds = new_handle->dirfd; -#else - abort(); -#endif - - } -end: - return ret; -} - -#ifdef HAVE_DIRFD -static -int fd_close(void *unused, int *in_fds) -{ - const int ret = close(in_fds[0]); - - in_fds[0] = -1; - return ret; -} - -static -void directory_handle_destroy( - struct lttng_directory_handle *handle, void *data) -{ - struct fd_tracker *tracker = data; - const int ret = fd_tracker_close_unsuspendable_fd( - tracker, &handle->dirfd, 1, fd_close, NULL); - - if (ret) { - ERR("Failed to untrack directory handle file descriptor"); - } -} -#endif - -struct lttng_directory_handle *fd_tracker_create_directory_handle( - struct fd_tracker *tracker, const char *path) -{ - return fd_tracker_create_directory_handle_from_handle( - tracker, NULL, path); -} - -struct lttng_directory_handle *fd_tracker_create_directory_handle_from_handle( - struct fd_tracker *tracker, - struct lttng_directory_handle *in_handle, - const char *path) -{ - int ret; - int dirfd = -1; - char *handle_name = NULL; - char cwd_path[LTTNG_PATH_MAX] = "working directory"; - struct lttng_directory_handle *new_handle = NULL; - struct open_directory_handle_args open_args = { - .in_handle = in_handle, - .path = path, - }; - - if (!path) { - if (!getcwd(cwd_path, sizeof(cwd_path))) { - PERROR("Failed to get current working directory to name directory handle"); - goto end; - } - } - - ret = asprintf(&handle_name, "Directory handle to %s", - path ? path : cwd_path); - if (ret < 0) { - PERROR("Failed to format directory handle name"); - goto end; - } - - ret = fd_tracker_open_unsuspendable_fd(tracker, &dirfd, - (const char **) &handle_name, 1, open_directory_handle, - &open_args); - if (ret && ret != ENOTSUP) { - ERR("Failed to open directory handle to %s through the fd tracker", path ? path : cwd_path); - } - new_handle = open_args.ret_handle; - -#ifdef HAVE_DIRFD - new_handle->destroy_cb = directory_handle_destroy; - new_handle->destroy_cb_data = tracker; -#endif -end: - free(handle_name); - return new_handle; -} diff --git a/src/common/fd-tracker/utils.cpp b/src/common/fd-tracker/utils.cpp new file mode 100644 index 000000000..be6c65df6 --- /dev/null +++ b/src/common/fd-tracker/utils.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include +#include +#include + +static +int open_pipe_cloexec(void *data, int *fds) +{ + return utils_create_pipe_cloexec(fds); +} + +static +int close_pipe(void *data, int *pipe) +{ + utils_close_pipe(pipe); + pipe[0] = pipe[1] = -1; + return 0; +} + +int fd_tracker_util_close_fd(void *unused, int *fd) +{ + return close(*fd); +} + +int fd_tracker_util_pipe_open_cloexec( + struct fd_tracker *tracker, const char *name, int *pipe) +{ + int ret; + const char *name_prefix; + char *names[2]; + + name_prefix = name ? name : "Unknown pipe"; + ret = asprintf(&names[0], "%s (read end)", name_prefix); + if (ret < 0) { + goto end; + } + ret = asprintf(&names[1], "%s (write end)", name_prefix); + if (ret < 0) { + goto end; + } + + ret = fd_tracker_open_unsuspendable_fd(tracker, pipe, + (const char **) names, 2, open_pipe_cloexec, NULL); + free(names[0]); + free(names[1]); +end: + return ret; +} + +int fd_tracker_util_pipe_close(struct fd_tracker *tracker, int *pipe) +{ + return fd_tracker_close_unsuspendable_fd( + tracker, pipe, 2, close_pipe, NULL); +} + +struct open_directory_handle_args { + const struct lttng_directory_handle *in_handle; + struct lttng_directory_handle *ret_handle; + const char *path; +}; + +static +int open_directory_handle(void *_args, int *out_fds) +{ + int ret = 0; + struct open_directory_handle_args *args = (open_directory_handle_args *) _args; + struct lttng_directory_handle *new_handle = NULL; + + new_handle = args->in_handle ? + lttng_directory_handle_create_from_handle( + args->path, args->in_handle) : + lttng_directory_handle_create(args->path); + if (!new_handle) { + ret = -errno; + goto end; + } + + args->ret_handle = new_handle; + + /* + * Reserved to indicate that the handle does not use a handle; there is + * nothing to track. We want to indicate an error to the fd-tracker so + * that it doesn't attempt to track the file descriptor, but also want + * the caller to retrieve the newly-created handle. + * + * Calling this a hack is a fair assessment. + */ + if (!lttng_directory_handle_uses_fd(new_handle)) { + ret = ENOTSUP; + } else { +#ifdef HAVE_DIRFD + *out_fds = new_handle->dirfd; +#else + abort(); +#endif + + } +end: + return ret; +} + +#ifdef HAVE_DIRFD +static +int fd_close(void *unused, int *in_fds) +{ + const int ret = close(in_fds[0]); + + in_fds[0] = -1; + return ret; +} + +static +void directory_handle_destroy( + struct lttng_directory_handle *handle, void *data) +{ + struct fd_tracker *tracker = (fd_tracker *) data; + const int ret = fd_tracker_close_unsuspendable_fd( + tracker, &handle->dirfd, 1, fd_close, NULL); + + if (ret) { + ERR("Failed to untrack directory handle file descriptor"); + } +} +#endif + +struct lttng_directory_handle *fd_tracker_create_directory_handle( + struct fd_tracker *tracker, const char *path) +{ + return fd_tracker_create_directory_handle_from_handle( + tracker, NULL, path); +} + +struct lttng_directory_handle *fd_tracker_create_directory_handle_from_handle( + struct fd_tracker *tracker, + struct lttng_directory_handle *in_handle, + const char *path) +{ + int ret; + int dirfd = -1; + char *handle_name = NULL; + char cwd_path[LTTNG_PATH_MAX] = "working directory"; + struct lttng_directory_handle *new_handle = NULL; + open_directory_handle_args open_args {}; + + open_args.in_handle = in_handle; + open_args.path = path; + + if (!path) { + if (!getcwd(cwd_path, sizeof(cwd_path))) { + PERROR("Failed to get current working directory to name directory handle"); + goto end; + } + } + + ret = asprintf(&handle_name, "Directory handle to %s", + path ? path : cwd_path); + if (ret < 0) { + PERROR("Failed to format directory handle name"); + goto end; + } + + ret = fd_tracker_open_unsuspendable_fd(tracker, &dirfd, + (const char **) &handle_name, 1, open_directory_handle, + &open_args); + if (ret && ret != ENOTSUP) { + ERR("Failed to open directory handle to %s through the fd tracker", path ? path : cwd_path); + } + new_handle = open_args.ret_handle; + +#ifdef HAVE_DIRFD + new_handle->destroy_cb = directory_handle_destroy; + new_handle->destroy_cb_data = tracker; +#endif +end: + free(handle_name); + return new_handle; +}