directory-handle: print `errno` reason on `unlinkat()` error
[lttng-tools.git] / src / common / compat / directory-handle.c
index 781f2574b0ea652cadd2d8b8bb0e9e487d891d91..aea4be5fe27ffc54e3c9c873fa36209317cb2ba9 100644 (file)
@@ -1,18 +1,8 @@
 /*
- * 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,
@@ -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,6 +990,22 @@ 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,
@@ -473,3 +1065,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,
+                               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(&current_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(
+                       &current_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(
+                               &current_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(
+                                       &current_path, current_path.size - 1);
+                       assert(!ret);
+                       ret = lttng_dynamic_buffer_append(&current_path,
+                                       &separator, sizeof(separator));
+                       if (ret) {
+                               goto end;
+                       }
+                       ret = lttng_dynamic_buffer_append(&current_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(&current_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;
+}
This page took 0.03763 seconds and 4 git commands to generate.