X-Git-Url: http://git.efficios.com/?p=lttng-tools.git;a=blobdiff_plain;f=src%2Fcommon%2Fcompat%2Fdirectory-handle.c;h=e29d6540b56c648242afafc5c065e1ca0fe7ec9c;hp=7077378e0a3e8aa40911eee665c6b972c4ef9168;hb=2463b7879c00298daa79744cdaae82ac061a4ed8;hpb=18710679a8ac57fda5dbd26cf16bb180dce9e286 diff --git a/src/common/compat/directory-handle.c b/src/common/compat/directory-handle.c index 7077378e0..e29d6540b 100644 --- a/src/common/compat/directory-handle.c +++ b/src/common/compat/directory-handle.c @@ -1,18 +1,8 @@ /* - * Copyright (C) 2019 - Jérémie Galarneau + * Copyright (C) 2019 Jérémie Galarneau * - * 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 @@ -21,16 +11,20 @@ #include #include #include +#include #include #include #include #include #include +#include -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, @@ -41,60 +35,222 @@ int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path, 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 +struct lttng_directory_handle *lttng_directory_handle_copy( + const struct lttng_directory_handle *handle) +{ + struct lttng_directory_handle *new_handle = NULL; + + if (handle->dirfd == AT_FDCWD) { + new_handle = lttng_directory_handle_create_from_dirfd(AT_FDCWD); + } else { + 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"); + } + } +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, @@ -104,8 +260,38 @@ int lttng_directory_handle_mkdir( } 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); } @@ -117,209 +303,598 @@ int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle, return run_as_mkdirat_recursive(handle->dirfd, path, mode, uid, 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) +{ + return renameat(old_handle->dirfd, old_name, + new_handle->dirfd, 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) +{ + 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) +{ + DIR *dir_stream = NULL; + int fd = openat(handle->dirfd, path, O_RDONLY); + + 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; + } + +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; + } + 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; +} + LTTNG_HIDDEN -int lttng_directory_handle_init(struct lttng_directory_handle *handle, +struct lttng_directory_handle *lttng_directory_handle_create( 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; + 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, + }; + + 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; + } + 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; + } + 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; + } + /* Takes ownership of new_path. */ + new_handle = _lttng_directory_handle_create(new_path); + new_path = NULL; + goto end; + } + + 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); + goto end; + } + new_path = zmalloc(handle_path_len); + if (!new_path) { + PERROR("Failed to initialize directory handle"); + goto end; + } + + 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; +} + +static +int _run_as_unlink(const struct lttng_directory_handle *handle, + const char *filename, 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_unlink(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 ret; + char fullpath[LTTNG_PATH_MAX]; - 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; - } - cwd_len = strlen(cwd); - if (cwd_len == 0) { - ERR("Failed to initialize directory handle to \"%s\": getcwd() returned an empty string", - path); - ret = -1; + ret = get_full_path(handle, path, fullpath, sizeof(fullpath)); + if (ret) { + errno = ENOMEM; goto end; } - if (cwd[cwd_len - 1] != '/') { - add_slash = true; - } - 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; - } + ret = run_as_mkdir_recursive(fullpath, mode, uid, gid); +end: + return ret; +} - /* - * 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); - goto end; - } - if (!S_ISDIR(stat_buf.st_mode)) { - ERR("Failed to initialize directory handle to \"%s\": not a directory", - path); - ret = -1; - goto end; - } - if (*path == '/') { - handle->base_path = strdup(path); - if (!handle->base_path) { - ret = -1; - } - /* Not an error. */ - goto end; - } - } else { - path = ""; - path_len = 0; - add_slash = false; - } +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) +{ + int ret; + char old_fullpath[LTTNG_PATH_MAX]; + char new_fullpath[LTTNG_PATH_MAX]; - handle_path_len = cwd_len + path_len + !!add_slash + 2; - 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; + ret = get_full_path(old_handle, old_name, old_fullpath, + sizeof(old_fullpath)); + if (ret) { + errno = ENOMEM; goto end; } - handle->base_path = zmalloc(handle_path_len); - if (!handle->base_path) { - PERROR("Failed to initialize directory handle"); - ret = -1; + ret = get_full_path(new_handle, new_name, new_fullpath, + sizeof(new_fullpath)); + if (ret) { + errno = ENOMEM; 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; - goto end; - } + ret = rename(old_fullpath, new_fullpath); end: return ret; } -LTTNG_HIDDEN -int lttng_directory_handle_init_from_dirfd( - struct lttng_directory_handle *handle, int dirfd) -{ - assert(dirfd == AT_FDCWD); - return lttng_directory_handle_init(handle, NULL); -} - -LTTNG_HIDDEN -void lttng_directory_handle_fini(struct lttng_directory_handle *handle) -{ - free(handle->base_path); -} - 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 @@ -347,6 +922,8 @@ int create_directory_check_exists(const struct lttng_directory_handle *handle, ret = -1; goto end; } + } else if (errno != ENOENT) { + goto end; } /* @@ -358,7 +935,6 @@ end: return ret; } -/* Common implementation. */ static int create_directory_recursive(const struct lttng_directory_handle *handle, const char *path, mode_t mode) @@ -414,11 +990,27 @@ error: 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, const char *subdirectory, - mode_t mode, struct lttng_credentials *creds) + mode_t mode, const struct lttng_credentials *creds) { int ret; @@ -427,8 +1019,9 @@ int lttng_directory_handle_create_subdirectory_as_user( ret = create_directory_check_exists(handle, subdirectory, mode); } else { - ret = _run_as_mkdir(handle, subdirectory, - mode, creds->uid, creds->gid); + ret = _run_as_mkdir(handle, subdirectory, mode, + lttng_credentials_get_uid(creds), + lttng_credentials_get_gid(creds)); } return ret; @@ -438,7 +1031,7 @@ LTTNG_HIDDEN int lttng_directory_handle_create_subdirectory_recursive_as_user( const struct lttng_directory_handle *handle, const char *subdirectory_path, - mode_t mode, struct lttng_credentials *creds) + mode_t mode, const struct lttng_credentials *creds) { int ret; @@ -448,7 +1041,7 @@ int lttng_directory_handle_create_subdirectory_recursive_as_user( subdirectory_path, mode); } else { ret = _run_as_mkdir_recursive(handle, subdirectory_path, - mode, creds->uid, creds->gid); + mode, lttng_credentials_get_uid(creds), lttng_credentials_get_gid(creds)); } return ret; @@ -473,3 +1066,352 @@ int lttng_directory_handle_create_subdirectory_recursive( 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, + lttng_credentials_get_uid(creds), lttng_credentials_get_gid(creds)); + } + 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, lttng_credentials_get_uid(creds), lttng_credentials_get_gid(creds)); + } + 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, lttng_credentials_get_uid(creds), lttng_credentials_get_gid(creds)); + } + 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, lttng_credentials_get_uid(creds), lttng_credentials_get_gid(creds)); + } + 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, lttng_credentials_get_uid(creds), + lttng_credentials_get_gid(creds), flags); + } + return ret; +}