/*
- * Copyright (C) 2019 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2 only,
- * as published by the Free Software Foundation.
+ * SPDX-License-Identifier: GPL-2.0-only
*
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <common/compat/directory-handle.h>
#include <common/runas.h>
#include <common/credentials.h>
#include <lttng/constant.h>
+#include <common/dynamic-array.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
+#include <dirent.h>
-static
-int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
- const char *path, struct stat *st);
+/*
+ * This compatibility layer shares a common "base" that is implemented
+ * in terms of an internal API. This file contains two implementations
+ * of the internal API below.
+ */
static
int lttng_directory_handle_mkdir(
const struct lttng_directory_handle *handle,
static
int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
const char *path, mode_t mode, uid_t uid, gid_t gid);
+static
+int lttng_directory_handle_open(const struct lttng_directory_handle *handle,
+ const char *filename, int flags, mode_t mode);
+static
+int _run_as_open(const struct lttng_directory_handle *handle,
+ const char *filename,
+ int flags, mode_t mode, uid_t uid, gid_t gid);
+static
+int lttng_directory_handle_unlink(
+ const struct lttng_directory_handle *handle,
+ const char *filename);
+static
+int _run_as_unlink(const struct lttng_directory_handle *handle,
+ const char *filename, uid_t uid, gid_t gid);
+static
+int _lttng_directory_handle_rename(
+ const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name);
+static
+int _run_as_rename(const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name, uid_t uid, gid_t gid);
+static
+DIR *lttng_directory_handle_opendir(const struct lttng_directory_handle *handle,
+ const char *path);
+static
+int lttng_directory_handle_rmdir(
+ const struct lttng_directory_handle *handle, const char *name);
+static
+int _run_as_rmdir(const struct lttng_directory_handle *handle,
+ const char *name, uid_t uid, gid_t gid);
+static
+int _run_as_rmdir_recursive(
+ const struct lttng_directory_handle *handle, const char *name,
+ uid_t uid, gid_t gid, int flags);
+static
+void lttng_directory_handle_invalidate(struct lttng_directory_handle *handle);
+static
+void lttng_directory_handle_release(struct urcu_ref *ref);
#ifdef COMPAT_DIRFD
+/*
+ * Special inode number reserved to represent the "current working directory".
+ * ino_t is spec'ed as being an unsigned integral type.
+ */
+#define RESERVED_AT_FDCWD_INO \
+ ({ \
+ uint64_t reserved_val; \
+ switch (sizeof(ino_t)) { \
+ case 4: \
+ reserved_val = UINT32_MAX; \
+ break; \
+ case 8: \
+ reserved_val = UINT64_MAX; \
+ break; \
+ default: \
+ abort(); \
+ } \
+ (ino_t) reserved_val; \
+ })
+
LTTNG_HIDDEN
-int lttng_directory_handle_init(struct lttng_directory_handle *handle,
- const char *path)
+struct lttng_directory_handle *lttng_directory_handle_create(const char *path)
{
- int ret;
+ const struct lttng_directory_handle cwd_handle = {
+ .dirfd = AT_FDCWD,
+ };
+
+ /* Open a handle to the CWD if NULL is passed. */
+ return lttng_directory_handle_create_from_handle(path, &cwd_handle);
+}
+
+LTTNG_HIDDEN
+struct lttng_directory_handle *lttng_directory_handle_create_from_handle(
+ const char *path,
+ const struct lttng_directory_handle *ref_handle)
+{
+ int dirfd;
+ struct lttng_directory_handle *handle = NULL;
if (!path) {
- handle->dirfd = AT_FDCWD;
- ret = 0;
+ handle = lttng_directory_handle_copy(ref_handle);
goto end;
}
- ret = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
- if (ret == -1) {
+ if (!*path) {
+ ERR("Failed to initialize directory handle: provided path is an empty string");
+ goto end;
+ }
+
+ dirfd = openat(ref_handle->dirfd, path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (dirfd == -1) {
PERROR("Failed to initialize directory handle to \"%s\"", path);
goto end;
}
- handle->dirfd = ret;
- ret = 0;
+
+ handle = lttng_directory_handle_create_from_dirfd(dirfd);
+ if (!handle) {
+ goto error_close;
+ }
end:
- return ret;
+ return handle;
+error_close:
+ if (close(dirfd)) {
+ PERROR("Failed to close directory file descriptor");
+ }
+ return NULL;
}
LTTNG_HIDDEN
-int lttng_directory_handle_init_from_dirfd(
- struct lttng_directory_handle *handle, int dirfd)
+struct lttng_directory_handle *lttng_directory_handle_create_from_dirfd(
+ int dirfd)
{
+ int ret;
+ struct lttng_directory_handle *handle = zmalloc(sizeof(*handle));
+ struct stat stat_buf;
+
+ if (!handle) {
+ goto end;
+ }
+
+ if (dirfd != AT_FDCWD) {
+ ret = fstat(dirfd, &stat_buf);
+ if (ret) {
+ PERROR("Failed to fstat directory file descriptor %i", dirfd);
+ lttng_directory_handle_release(&handle->ref);
+ handle = NULL;
+ goto end;
+ }
+ } else {
+ handle->directory_inode = RESERVED_AT_FDCWD_INO;
+ }
handle->dirfd = dirfd;
- return 0;
+ urcu_ref_init(&handle->ref);
+end:
+ return handle;
}
-LTTNG_HIDDEN
-void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
+static
+void lttng_directory_handle_release(struct urcu_ref *ref)
{
int ret;
+ struct lttng_directory_handle *handle =
+ container_of(ref, struct lttng_directory_handle, ref);
- if (handle->dirfd == AT_FDCWD) {
- return;
+ if (handle->destroy_cb) {
+ handle->destroy_cb(handle, handle->destroy_cb_data);
+ }
+
+ if (handle->dirfd == AT_FDCWD || handle->dirfd == -1) {
+ goto end;
}
ret = close(handle->dirfd);
if (ret == -1) {
PERROR("Failed to close directory file descriptor of directory handle");
}
+end:
+ lttng_directory_handle_invalidate(handle);
+ free(handle);
}
LTTNG_HIDDEN
-int lttng_directory_handle_copy(const struct lttng_directory_handle *handle,
- struct lttng_directory_handle *new_copy)
+struct lttng_directory_handle *lttng_directory_handle_copy(
+ const struct lttng_directory_handle *handle)
{
- int ret = 0;
+ struct lttng_directory_handle *new_handle = NULL;
if (handle->dirfd == AT_FDCWD) {
- new_copy->dirfd = handle->dirfd;
+ new_handle = lttng_directory_handle_create_from_dirfd(AT_FDCWD);
} else {
- new_copy->dirfd = dup(handle->dirfd);
- if (new_copy->dirfd == -1) {
- PERROR("Failed to duplicate directory fd of directory handle");
- ret = -1;
+ const int new_dirfd = dup(handle->dirfd);
+
+ if (new_dirfd == -1) {
+ PERROR("Failed to duplicate directory file descriptor of directory handle");
+ goto end;
+ }
+ new_handle = lttng_directory_handle_create_from_dirfd(
+ new_dirfd);
+ if (!new_handle && close(new_dirfd)) {
+ PERROR("Failed to close directory file descriptor of directory handle");
}
}
- return ret;
+end:
+ return new_handle;
+}
+
+LTTNG_HIDDEN
+bool lttng_directory_handle_equals(const struct lttng_directory_handle *lhs,
+ const struct lttng_directory_handle *rhs)
+{
+ return lhs->directory_inode == rhs->directory_inode;
}
static
+void lttng_directory_handle_invalidate(struct lttng_directory_handle *handle)
+{
+ handle->dirfd = -1;
+}
+
+LTTNG_HIDDEN
int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
const char *path, struct stat *st)
{
return fstatat(handle->dirfd, path, st, 0);
}
+LTTNG_HIDDEN
+bool lttng_directory_handle_uses_fd(
+ const struct lttng_directory_handle *handle)
+{
+ return handle->dirfd != AT_FDCWD;
+}
+
static
int lttng_directory_handle_mkdir(
const struct lttng_directory_handle *handle,
}
static
-int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
- mode_t mode, uid_t uid, gid_t gid)
+int lttng_directory_handle_open(const struct lttng_directory_handle *handle,
+ const char *filename, int flags, mode_t mode)
+{
+ return openat(handle->dirfd, filename, flags, mode);
+}
+
+static
+int _run_as_open(const struct lttng_directory_handle *handle,
+ const char *filename,
+ int flags, mode_t mode, uid_t uid, gid_t gid)
+{
+ return run_as_openat(handle->dirfd, filename, flags, mode, uid, gid);
+}
+
+static
+int _run_as_unlink(const struct lttng_directory_handle *handle,
+ const char *filename, uid_t uid, gid_t gid)
+{
+ return run_as_unlinkat(handle->dirfd, filename, uid, gid);
+}
+
+static
+int lttng_directory_handle_unlink(
+ const struct lttng_directory_handle *handle,
+ const char *filename)
+{
+ return unlinkat(handle->dirfd, filename, 0);
+}
+
+static
+int _run_as_mkdir(const struct lttng_directory_handle *handle,
+ const char *path, mode_t mode, uid_t uid, gid_t gid)
{
return run_as_mkdirat(handle->dirfd, path, mode, uid, gid);
}
return run_as_mkdirat_recursive(handle->dirfd, path, mode, uid, gid);
}
-#else /* COMPAT_DIRFD */
+static
+int _lttng_directory_handle_rename(
+ const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name)
+{
+ return renameat(old_handle->dirfd, old_name,
+ new_handle->dirfd, new_name);
+}
-LTTNG_HIDDEN
-int lttng_directory_handle_init(struct lttng_directory_handle *handle,
+static
+int _run_as_rename(const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name, uid_t uid, gid_t gid)
+{
+ return run_as_renameat(old_handle->dirfd, old_name, new_handle->dirfd,
+ new_name, uid, gid);
+}
+
+static
+DIR *lttng_directory_handle_opendir(const struct lttng_directory_handle *handle,
const char *path)
{
- int ret;
- size_t cwd_len, path_len, handle_path_len;
- char cwd_buf[LTTNG_PATH_MAX];
- const char *cwd;
- bool add_slash = false;
- struct stat stat_buf;
+ DIR *dir_stream = NULL;
+ int fd = openat(handle->dirfd, path, O_RDONLY);
- cwd = getcwd(cwd_buf, sizeof(cwd_buf));
- if (!cwd) {
- PERROR("Failed to initialize directory handle, can't get current working directory");
- ret = -1;
+ if (fd < 0) {
+ goto end;
+ }
+
+ dir_stream = fdopendir(fd);
+ if (!dir_stream) {
+ int ret;
+
+ PERROR("Failed to open directory stream");
+ ret = close(fd);
+ if (ret) {
+ PERROR("Failed to close file descriptor to %s", path);
+ }
goto end;
}
- cwd_len = strlen(cwd);
- if (cwd_len == 0) {
- ERR("Failed to initialize directory handle to \"%s\": getcwd() returned an empty string",
- path);
+
+end:
+ return dir_stream;
+}
+
+static
+int lttng_directory_handle_rmdir(
+ const struct lttng_directory_handle *handle, const char *name)
+{
+ int ret = unlinkat(handle->dirfd, name, AT_REMOVEDIR);
+ if (ret) {
+ PERROR("Failed to remove directory `%s`", name);
+ }
+
+ return ret;
+}
+
+static
+int _run_as_rmdir(const struct lttng_directory_handle *handle,
+ const char *name, uid_t uid, gid_t gid)
+{
+ return run_as_rmdirat(handle->dirfd, name, uid, gid);
+}
+
+static
+int _run_as_rmdir_recursive(
+ const struct lttng_directory_handle *handle, const char *name,
+ uid_t uid, gid_t gid, int flags)
+{
+ return run_as_rmdirat_recursive(handle->dirfd, name, uid, gid, flags);
+}
+
+#else /* COMPAT_DIRFD */
+
+static
+int get_full_path(const struct lttng_directory_handle *handle,
+ const char *subdirectory, char *fullpath, size_t size)
+{
+ int ret;
+ const bool subdirectory_is_absolute =
+ subdirectory && *subdirectory == '/';
+ const char * const base = subdirectory_is_absolute ?
+ subdirectory : handle->base_path;
+ const char * const end = subdirectory && !subdirectory_is_absolute ?
+ subdirectory : NULL;
+ const size_t base_len = strlen(base);
+ const size_t end_len = end ? strlen(end) : 0;
+ const bool add_separator_slash = end && base[base_len - 1] != '/';
+ const bool add_trailing_slash = end && end[end_len - 1] != '/';
+
+ ret = snprintf(fullpath, size, "%s%s%s%s",
+ base,
+ add_separator_slash ? "/" : "",
+ end ? end : "",
+ add_trailing_slash ? "/" : "");
+ if (ret == -1 || ret >= size) {
+ ERR("Failed to format subdirectory from directory handle");
ret = -1;
goto end;
}
- if (cwd[cwd_len - 1] != '/') {
- add_slash = true;
+ ret = 0;
+end:
+ return ret;
+}
+
+static
+struct lttng_directory_handle *_lttng_directory_handle_create(char *path)
+{
+ struct lttng_directory_handle *handle = zmalloc(sizeof(*handle));
+
+ if (!handle) {
+ goto end;
}
+ urcu_ref_init(&handle->ref);
+ handle->base_path = path;
+end:
+ return handle;
+}
- if (path) {
- path_len = strlen(path);
- if (path_len == 0) {
- ERR("Failed to initialize directory handle: provided path is an empty string");
- ret = -1;
- goto end;
- }
+LTTNG_HIDDEN
+struct lttng_directory_handle *lttng_directory_handle_create(
+ const char *path)
+{
+ int ret;
+ const char *cwd = "";
+ size_t cwd_len, path_len;
+ char cwd_buf[LTTNG_PATH_MAX] = {};
+ char handle_buf[LTTNG_PATH_MAX] = {};
+ struct lttng_directory_handle *new_handle = NULL;
+ bool add_cwd_slash = false, add_trailing_slash = false;
+ const struct lttng_directory_handle cwd_handle = {
+ .base_path = handle_buf,
+ };
- /*
- * Ensure that 'path' is a directory. There is a race
- * (TOCTOU) since the directory could be removed/replaced/renamed,
- * but this is inevitable on platforms that don't provide dirfd support.
- */
- ret = stat(path, &stat_buf);
- if (ret == -1) {
- PERROR("Failed to initialize directory handle to \"%s\", stat() failed",
- path);
+ path_len = path ? strlen(path) : 0;
+ add_trailing_slash = path && path[path_len - 1] != '/';
+ if (!path || (path && *path != '/')) {
+ cwd = getcwd(cwd_buf, sizeof(cwd_buf));
+ if (!cwd) {
+ PERROR("Failed to initialize directory handle, can't get current working directory");
+ ret = -1;
goto end;
}
- if (!S_ISDIR(stat_buf.st_mode)) {
- ERR("Failed to initialize directory handle to \"%s\": not a directory",
- path);
+ cwd_len = strlen(cwd);
+ if (cwd_len == 0) {
+ ERR("Failed to initialize directory handle, current working directory path has a length of 0");
ret = -1;
goto end;
}
- if (*path == '/') {
- handle->base_path = strdup(path);
- if (!handle->base_path) {
- ret = -1;
- }
- /* Not an error. */
+ add_cwd_slash = cwd[cwd_len - 1] != '/';
+ }
+
+ ret = snprintf(handle_buf, sizeof(handle_buf), "%s%s%s%s",
+ cwd,
+ add_cwd_slash ? "/" : "",
+ path ? : "",
+ add_trailing_slash ? "/" : "");
+ if (ret == -1 || ret >= LTTNG_PATH_MAX) {
+ ERR("Failed to initialize directory handle, failed to format directory path");
+ goto end;
+ }
+
+ new_handle = lttng_directory_handle_create_from_handle(path, &cwd_handle);
+end:
+ return new_handle;
+}
+
+LTTNG_HIDDEN
+struct lttng_directory_handle *lttng_directory_handle_create_from_handle(
+ const char *path,
+ const struct lttng_directory_handle *ref_handle)
+{
+ int ret;
+ size_t path_len, handle_path_len;
+ bool add_trailing_slash;
+ struct stat stat_buf;
+ struct lttng_directory_handle *new_handle = NULL;
+ char *new_path = NULL;
+
+ assert(ref_handle && ref_handle->base_path);
+
+ ret = lttng_directory_handle_stat(ref_handle, path, &stat_buf);
+ if (ret == -1) {
+ PERROR("Failed to create directory handle");
+ goto end;
+ } else if (!S_ISDIR(stat_buf.st_mode)) {
+ char full_path[LTTNG_PATH_MAX];
+
+ /* Best effort for logging purposes. */
+ ret = get_full_path(ref_handle, path, full_path,
+ sizeof(full_path));
+ if (ret) {
+ full_path[0] = '\0';
+ }
+
+ ERR("Failed to initialize directory handle to \"%s\": not a directory",
+ full_path);
+ goto end;
+ }
+ if (!path) {
+ new_handle = lttng_directory_handle_copy(ref_handle);
+ goto end;
+ }
+
+ path_len = strlen(path);
+ if (path_len == 0) {
+ ERR("Failed to initialize directory handle: provided path is an empty string");
+ ret = -1;
+ goto end;
+ }
+ if (*path == '/') {
+ new_path = strdup(path);
+ if (!new_path) {
goto end;
}
- } else {
- path = "";
- path_len = 0;
- add_slash = false;
+ /* Takes ownership of new_path. */
+ new_handle = _lttng_directory_handle_create(new_path);
+ new_path = NULL;
+ goto end;
}
- handle_path_len = cwd_len + path_len + !!add_slash + 2;
+ add_trailing_slash = path[path_len - 1] != '/';
+
+ handle_path_len = strlen(ref_handle->base_path) + path_len +
+ !!add_trailing_slash;
if (handle_path_len >= LTTNG_PATH_MAX) {
ERR("Failed to initialize directory handle as the resulting path's length (%zu bytes) exceeds the maximal allowed length (%d bytes)",
handle_path_len, LTTNG_PATH_MAX);
- ret = -1;
goto end;
}
- handle->base_path = zmalloc(handle_path_len);
- if (!handle->base_path) {
+ new_path = zmalloc(handle_path_len);
+ if (!new_path) {
PERROR("Failed to initialize directory handle");
- ret = -1;
goto end;
}
- ret = sprintf(handle->base_path, "%s%s%s/", cwd,
- add_slash ? "/" : "", path);
- if (ret == -1 || ret >= handle_path_len) {
- ERR("Failed to initialize directory handle: path formatting failed");
- ret = -1;
+ ret = sprintf(new_handle->base_path, "%s%s%s",
+ ref_handle->base_path,
+ path,
+ add_trailing_slash ? "/" : "");
+ if (ret == -1 || ret >= handle_path_len) {
+ ERR("Failed to initialize directory handle: path formatting failed");
+ goto end;
+ }
+ new_handle = _lttng_directory_handle_create(new_path);
+ new_path = NULL;
+end:
+ free(new_path);
+ return new_handle;
+}
+
+LTTNG_HIDDEN
+struct lttng_directory_handle *lttng_directory_handle_create_from_dirfd(
+ int dirfd)
+{
+ assert(dirfd == AT_FDCWD);
+ return lttng_directory_handle_create(NULL);
+}
+
+static
+void lttng_directory_handle_release(struct urcu_ref *ref)
+{
+ struct lttng_directory_handle *handle =
+ container_of(ref, struct lttng_directory_handle, ref);
+
+ free(handle->base_path);
+ lttng_directory_handle_invalidate(handle);
+ free(handle);
+}
+
+LTTNG_HIDDEN
+struct lttng_directory_handle *lttng_directory_handle_copy(
+ const struct lttng_directory_handle *handle)
+{
+ struct lttng_directory_handle *new_handle = NULL;
+ char *new_path = NULL;
+
+ if (handle->base_path) {
+ new_path = strdup(handle->base_path);
+ if (!new_path) {
+ goto end;
+ }
+ }
+ new_handle = _lttng_directory_handle_create(new_path);
+end:
+ return new_handle;
+}
+
+LTTNG_HIDDEN
+bool lttng_directory_handle_equals(const struct lttng_directory_handle *lhs,
+ const struct lttng_directory_handle *rhs)
+{
+ return strcmp(lhs->base_path, rhs->base_path) == 0;
+}
+
+static
+void lttng_directory_handle_invalidate(struct lttng_directory_handle *handle)
+{
+ handle->base_path = NULL;
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
+ const char *subdirectory, struct stat *st)
+{
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = stat(fullpath, st);
+end:
+ return ret;
+}
+
+LTTNG_HIDDEN
+bool lttng_directory_handle_uses_fd(
+ const struct lttng_directory_handle *handle)
+{
+ return false;
+}
+
+static
+int lttng_directory_handle_mkdir(const struct lttng_directory_handle *handle,
+ const char *subdirectory, mode_t mode)
+{
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = mkdir(fullpath, mode);
+end:
+ return ret;
+}
+
+static
+int lttng_directory_handle_open(const struct lttng_directory_handle *handle,
+ const char *filename, int flags, mode_t mode)
+{
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = open(fullpath, flags, mode);
+end:
+ return ret;
+}
+
+static
+int lttng_directory_handle_unlink(
+ const struct lttng_directory_handle *handle,
+ const char *filename)
+{
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = unlink(fullpath);
+end:
+ return ret;
+}
+
+static
+int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
+ mode_t mode, uid_t uid, gid_t gid)
+{
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = run_as_mkdir(fullpath, mode, uid, gid);
+end:
+ return ret;
+}
+
+static
+int _run_as_open(const struct lttng_directory_handle *handle,
+ const char *filename,
+ int flags, mode_t mode, uid_t uid, gid_t gid)
+{
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
goto end;
}
+
+ ret = run_as_open(fullpath, flags, mode, uid, gid);
end:
return ret;
}
-LTTNG_HIDDEN
-int lttng_directory_handle_init_from_dirfd(
- struct lttng_directory_handle *handle, int dirfd)
+static
+int _run_as_unlink(const struct lttng_directory_handle *handle,
+ const char *filename, uid_t uid, gid_t gid)
{
- assert(dirfd == AT_FDCWD);
- return lttng_directory_handle_init(handle, NULL);
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, filename, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = run_as_unlink(fullpath, uid, gid);
+end:
+ return ret;
}
-LTTNG_HIDDEN
-void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
+static
+int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
+ const char *path, mode_t mode, uid_t uid, gid_t gid)
{
- free(handle->base_path);
+ int ret;
+ char fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = run_as_mkdir_recursive(fullpath, mode, uid, gid);
+end:
+ return ret;
}
-LTTNG_HIDDEN
-int lttng_directory_handle_copy(const struct lttng_directory_handle *handle,
- struct lttng_directory_handle *new_copy)
+static
+int _lttng_directory_handle_rename(
+ const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name)
{
- new_copy->base_path = strdup(handle->base_path);
- return new_copy->base_path ? 0 : -1;
+ int ret;
+ char old_fullpath[LTTNG_PATH_MAX];
+ char new_fullpath[LTTNG_PATH_MAX];
+
+ ret = get_full_path(old_handle, old_name, old_fullpath,
+ sizeof(old_fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+ ret = get_full_path(new_handle, new_name, new_fullpath,
+ sizeof(new_fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = rename(old_fullpath, new_fullpath);
+end:
+ return ret;
}
static
-int get_full_path(const struct lttng_directory_handle *handle,
- const char *subdirectory, char *fullpath, size_t size)
+int _run_as_rename(const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name, uid_t uid, gid_t gid)
{
int ret;
+ char old_fullpath[LTTNG_PATH_MAX];
+ char new_fullpath[LTTNG_PATH_MAX];
- /*
- * Don't include the base path if subdirectory is absolute.
- * This is the same behaviour than mkdirat.
- */
- ret = snprintf(fullpath, size, "%s%s",
- *subdirectory != '/' ? handle->base_path : "",
- subdirectory);
- if (ret == -1 || ret >= size) {
- ERR("Failed to format subdirectory from directory handle");
- ret = -1;
+ ret = get_full_path(old_handle, old_name, old_fullpath,
+ sizeof(old_fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
}
- ret = 0;
+ ret = get_full_path(new_handle, new_name, new_fullpath,
+ sizeof(new_fullpath));
+ if (ret) {
+ errno = ENOMEM;
+ goto end;
+ }
+
+ ret = run_as_rename(old_fullpath, new_fullpath, uid, gid);
+end:
return ret;
}
static
-int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
- const char *subdirectory, struct stat *st)
+DIR *lttng_directory_handle_opendir(const struct lttng_directory_handle *handle,
+ const char *path)
{
int ret;
+ DIR *dir_stream = NULL;
char fullpath[LTTNG_PATH_MAX];
- ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
+ ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
if (ret) {
errno = ENOMEM;
goto end;
}
- ret = stat(fullpath, st);
+ dir_stream = opendir(fullpath);
end:
- return ret;
+ return dir_stream;
}
static
-int lttng_directory_handle_mkdir(const struct lttng_directory_handle *handle,
- const char *subdirectory, mode_t mode)
+int lttng_directory_handle_rmdir(
+ const struct lttng_directory_handle *handle, const char *name)
{
int ret;
char fullpath[LTTNG_PATH_MAX];
- ret = get_full_path(handle, subdirectory, fullpath, sizeof(fullpath));
+ ret = get_full_path(handle, name, fullpath, sizeof(fullpath));
if (ret) {
errno = ENOMEM;
goto end;
}
- ret = mkdir(fullpath, mode);
+ ret = rmdir(fullpath);
end:
return ret;
}
static
-int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
- mode_t mode, uid_t uid, gid_t gid)
+int _run_as_rmdir(const struct lttng_directory_handle *handle,
+ const char *name, uid_t uid, gid_t gid)
{
int ret;
char fullpath[LTTNG_PATH_MAX];
- ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
+ ret = get_full_path(handle, name, fullpath, sizeof(fullpath));
if (ret) {
errno = ENOMEM;
goto end;
}
- ret = run_as_mkdir(fullpath, mode, uid, gid);
+ ret = run_as_rmdir(fullpath, uid, gid);
end:
return ret;
}
static
-int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
- const char *path, mode_t mode, uid_t uid, gid_t gid)
+int _run_as_rmdir_recursive(
+ const struct lttng_directory_handle *handle, const char *name,
+ uid_t uid, gid_t gid, int flags)
{
int ret;
char fullpath[LTTNG_PATH_MAX];
- ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
+ ret = get_full_path(handle, name, fullpath, sizeof(fullpath));
if (ret) {
errno = ENOMEM;
goto end;
}
- ret = run_as_mkdir_recursive(fullpath, mode, uid, gid);
+ ret = run_as_rmdir_recursive(fullpath, uid, gid, flags);
end:
return ret;
}
#endif /* COMPAT_DIRFD */
+/* Common implementation. */
+
/*
* On some filesystems (e.g. nfs), mkdir will validate access rights before
* checking for the existence of the path element. This means that on a setup
ret = -1;
goto end;
}
+ } else if (errno != ENOENT) {
+ goto end;
}
/*
return ret;
}
-/* Common implementation. */
static
int create_directory_recursive(const struct lttng_directory_handle *handle,
const char *path, mode_t mode)
return ret;
}
+LTTNG_HIDDEN
+bool lttng_directory_handle_get(struct lttng_directory_handle *handle)
+{
+ return urcu_ref_get_unless_zero(&handle->ref);
+}
+
+LTTNG_HIDDEN
+void lttng_directory_handle_put(struct lttng_directory_handle *handle)
+{
+ if (!handle) {
+ return;
+ }
+ assert(handle->ref.refcount);
+ urcu_ref_put(&handle->ref, lttng_directory_handle_release);
+}
+
LTTNG_HIDDEN
int lttng_directory_handle_create_subdirectory_as_user(
const struct lttng_directory_handle *handle,
return lttng_directory_handle_create_subdirectory_recursive_as_user(
handle, subdirectory_path, mode, NULL);
}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_open_file_as_user(
+ const struct lttng_directory_handle *handle,
+ const char *filename,
+ int flags, mode_t mode,
+ const struct lttng_credentials *creds)
+{
+ int ret;
+
+ if (!creds) {
+ /* Run as current user. */
+ ret = lttng_directory_handle_open(handle, filename, flags,
+ mode);
+ } else {
+ ret = _run_as_open(handle, filename, flags, mode,
+ creds->uid, creds->gid);
+ }
+ return ret;
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_open_file(
+ const struct lttng_directory_handle *handle,
+ const char *filename,
+ int flags, mode_t mode)
+{
+ return lttng_directory_handle_open_file_as_user(handle, filename, flags,
+ mode, NULL);
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_unlink_file_as_user(
+ const struct lttng_directory_handle *handle,
+ const char *filename,
+ const struct lttng_credentials *creds)
+{
+ int ret;
+
+ if (!creds) {
+ /* Run as current user. */
+ ret = lttng_directory_handle_unlink(handle, filename);
+ } else {
+ ret = _run_as_unlink(handle, filename, creds->uid, creds->gid);
+ }
+ return ret;
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_unlink_file(
+ const struct lttng_directory_handle *handle,
+ const char *filename)
+{
+ return lttng_directory_handle_unlink_file_as_user(handle,
+ filename, NULL);
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_rename(
+ const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name)
+{
+ return lttng_directory_handle_rename_as_user(old_handle, old_name,
+ new_handle, new_name, NULL);
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_rename_as_user(
+ const struct lttng_directory_handle *old_handle,
+ const char *old_name,
+ const struct lttng_directory_handle *new_handle,
+ const char *new_name,
+ const struct lttng_credentials *creds)
+{
+ int ret;
+
+ if (!creds) {
+ /* Run as current user. */
+ ret = _lttng_directory_handle_rename(old_handle,
+ old_name, new_handle, new_name);
+ } else {
+ ret = _run_as_rename(old_handle, old_name, new_handle,
+ new_name, creds->uid, creds->gid);
+ }
+ return ret;
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_remove_subdirectory(
+ const struct lttng_directory_handle *handle,
+ const char *name)
+{
+ return lttng_directory_handle_remove_subdirectory_as_user(handle, name,
+ NULL);
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_remove_subdirectory_as_user(
+ const struct lttng_directory_handle *handle,
+ const char *name,
+ const struct lttng_credentials *creds)
+{
+ int ret;
+
+ if (!creds) {
+ /* Run as current user. */
+ ret = lttng_directory_handle_rmdir(handle, name);
+ } else {
+ ret = _run_as_rmdir(handle, name, creds->uid, creds->gid);
+ }
+ return ret;
+}
+
+struct rmdir_frame {
+ ssize_t parent_frame_idx;
+ DIR *dir;
+ bool empty;
+ /* Size including '\0'. */
+ size_t path_size;
+};
+
+static
+void rmdir_frame_fini(void *data)
+{
+ int ret;
+ struct rmdir_frame *frame = data;
+
+ ret = closedir(frame->dir);
+ if (ret == -1) {
+ PERROR("Failed to close directory stream");
+ }
+}
+
+static
+int remove_directory_recursive(const struct lttng_directory_handle *handle,
+ const char *path, int flags)
+{
+ int ret;
+ struct lttng_dynamic_array frames;
+ size_t current_frame_idx = 0;
+ struct rmdir_frame initial_frame = {
+ .parent_frame_idx = -1,
+ .dir = lttng_directory_handle_opendir(handle, path),
+ .empty = true,
+ .path_size = strlen(path) + 1,
+ };
+ struct lttng_dynamic_buffer current_path;
+ const char separator = '/';
+
+ lttng_dynamic_buffer_init(¤t_path);
+ lttng_dynamic_array_init(&frames, sizeof(struct rmdir_frame),
+ rmdir_frame_fini);
+
+ if (flags & ~(LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG |
+ LTTNG_DIRECTORY_HANDLE_FAIL_NON_EMPTY_FLAG)) {
+ ERR("Unknown flags %d", flags);
+ ret = -1;
+ goto end;
+ }
+
+ if (!initial_frame.dir) {
+ if (flags & LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG &&
+ errno == ENOENT) {
+ DBG("Cannot rmdir \"%s\": root does not exist", path);
+ ret = 0;
+ goto end;
+ } else {
+ PERROR("Failed to rmdir \"%s\"", path);
+ ret = -1;
+ goto end;
+ }
+ }
+
+ ret = lttng_dynamic_array_add_element(&frames, &initial_frame);
+ if (ret) {
+ ERR("Failed to push context frame during recursive directory removal");
+ rmdir_frame_fini(&initial_frame);
+ goto end;
+ }
+
+ ret = lttng_dynamic_buffer_append(
+ ¤t_path, path, initial_frame.path_size);
+ if (ret) {
+ ERR("Failed to set initial path during recursive directory removal");
+ ret = -1;
+ goto end;
+ }
+
+ while (lttng_dynamic_array_get_count(&frames) > 0) {
+ struct dirent *entry;
+ struct rmdir_frame *current_frame =
+ lttng_dynamic_array_get_element(
+ &frames, current_frame_idx);
+
+ assert(current_frame->dir);
+ ret = lttng_dynamic_buffer_set_size(
+ ¤t_path, current_frame->path_size);
+ assert(!ret);
+ current_path.data[current_path.size - 1] = '\0';
+
+ while ((entry = readdir(current_frame->dir))) {
+ struct stat st;
+
+ if (!strcmp(entry->d_name, ".") ||
+ !strcmp(entry->d_name, "..")) {
+ continue;
+ }
+
+ /* Set current_path to the entry's path. */
+ ret = lttng_dynamic_buffer_set_size(
+ ¤t_path, current_path.size - 1);
+ assert(!ret);
+ ret = lttng_dynamic_buffer_append(¤t_path,
+ &separator, sizeof(separator));
+ if (ret) {
+ goto end;
+ }
+ ret = lttng_dynamic_buffer_append(¤t_path,
+ entry->d_name,
+ strlen(entry->d_name) + 1);
+ if (ret) {
+ goto end;
+ }
+
+ if (lttng_directory_handle_stat(
+ handle, current_path.data, &st)) {
+ if ((flags & LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG) &&
+ errno == ENOENT) {
+ break;
+ }
+ PERROR("Failed to stat \"%s\"",
+ current_path.data);
+ ret = -1;
+ goto end;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ if (flags & LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG) {
+ current_frame->empty = false;
+ break;
+ } else {
+ /* Not empty, abort. */
+ DBG("Directory \"%s\" is not empty; refusing to remove directory",
+ current_path.data);
+ ret = -1;
+ goto end;
+ }
+ } else {
+ struct rmdir_frame new_frame = {
+ .path_size = current_path.size,
+ .dir = lttng_directory_handle_opendir(
+ handle,
+ current_path.data),
+ .empty = true,
+ .parent_frame_idx = current_frame_idx,
+ };
+
+ if (!new_frame.dir) {
+ if (flags & LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG &&
+ errno == ENOENT) {
+ DBG("Non-existing directory stream during recursive directory removal");
+ break;
+ } else {
+ PERROR("Failed to open directory stream during recursive directory removal");
+ ret = -1;
+ goto end;
+ }
+ }
+ ret = lttng_dynamic_array_add_element(
+ &frames, &new_frame);
+ if (ret) {
+ ERR("Failed to push context frame during recursive directory removal");
+ rmdir_frame_fini(&new_frame);
+ goto end;
+ }
+ current_frame_idx++;
+ /* We break iteration on readdir. */
+ break;
+ }
+ }
+ if (entry) {
+ continue;
+ }
+
+ /* Pop rmdir frame. */
+ if (current_frame->empty) {
+ ret = lttng_directory_handle_rmdir(
+ handle, current_path.data);
+ if (ret) {
+ if ((flags & LTTNG_DIRECTORY_HANDLE_FAIL_NON_EMPTY_FLAG) ||
+ errno != ENOENT) {
+ PERROR("Failed to remove \"%s\" during recursive directory removal",
+ current_path.data);
+ goto end;
+ }
+ DBG("Non-existing directory stream during recursive directory removal");
+ }
+ } else if (current_frame->parent_frame_idx >= 0) {
+ struct rmdir_frame *parent_frame;
+
+ parent_frame = lttng_dynamic_array_get_element(&frames,
+ current_frame->parent_frame_idx);
+ assert(parent_frame);
+ parent_frame->empty = false;
+ }
+ ret = lttng_dynamic_array_remove_element(
+ &frames, current_frame_idx);
+ if (ret) {
+ ERR("Failed to pop context frame during recursive directory removal");
+ goto end;
+ }
+ current_frame_idx--;
+ }
+end:
+ lttng_dynamic_array_reset(&frames);
+ lttng_dynamic_buffer_reset(¤t_path);
+ return ret;
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_remove_subdirectory_recursive(
+ const struct lttng_directory_handle *handle,
+ const char *name,
+ int flags)
+{
+ return lttng_directory_handle_remove_subdirectory_recursive_as_user(
+ handle, name, NULL, flags);
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_remove_subdirectory_recursive_as_user(
+ const struct lttng_directory_handle *handle,
+ const char *name,
+ const struct lttng_credentials *creds,
+ int flags)
+{
+ int ret;
+
+ if (!creds) {
+ /* Run as current user. */
+ ret = remove_directory_recursive(handle, name, flags);
+ } else {
+ ret = _run_as_rmdir_recursive(handle, name, creds->uid,
+ creds->gid, flags);
+ }
+ return ret;
+}