Add rmdirat and renameat to run-as commands
[lttng-tools.git] / src / common / compat / directory-handle.c
index 80e9e118b8c46994751f8b7267d52f0718552f89..3f35f91655b58b6da7287b476293f8504088776b 100644 (file)
 #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>
 
 /*
  * This compatibility layer shares a common "base" that is implemented
@@ -61,6 +63,30 @@ 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);
+static
 void lttng_directory_handle_invalidate(struct lttng_directory_handle *handle);
 
 #ifdef COMPAT_DIRFD
@@ -213,6 +239,76 @@ 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)
+{
+       return unlinkat(handle->dirfd, name, AT_REMOVEDIR);
+}
+
+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)
+{
+       return run_as_rmdirat_recursive(handle->dirfd, name, uid, gid);
+}
+
 #else /* COMPAT_DIRFD */
 
 static
@@ -220,20 +316,29 @@ int get_full_path(const struct lttng_directory_handle *handle,
                const char *subdirectory, char *fullpath, size_t size)
 {
        int ret;
-
-       subdirectory = subdirectory ? : "";
-       /*
-        * 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);
+       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;
 }
 
@@ -242,39 +347,32 @@ int lttng_directory_handle_init(struct lttng_directory_handle *handle,
                const char *path)
 {
        int ret;
-       const char *cwd;
+       const char *cwd = "";
        size_t cwd_len, path_len;
        char cwd_buf[LTTNG_PATH_MAX] = {};
        char handle_buf[LTTNG_PATH_MAX] = {};
-       bool add_cwd_slash, add_trailing_slash;
+       bool add_cwd_slash = false, add_trailing_slash = false;
        const struct lttng_directory_handle cwd_handle = {
                .base_path = handle_buf,
        };
 
-       if (path && *path == '/') {
-               /*
-                * Creation of an handle to an absolute path; no need to sample
-                * the cwd.
-                */
-               goto create;
-       }
        path_len = path ? strlen(path) : 0;
-
-       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] != '/';
        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,
@@ -285,7 +383,7 @@ int lttng_directory_handle_init(struct lttng_directory_handle *handle,
                ERR("Failed to initialize directory handle, failed to format directory path");
                goto end;
        }
-create:
+
        ret = lttng_directory_handle_init_from_handle(handle, path,
                        &cwd_handle);
 end:
@@ -545,8 +643,141 @@ end:
        return ret;
 }
 
+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];
+
+       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 _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];
+
+       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 = run_as_rename(old_fullpath, new_fullpath, uid, gid);
+end:
+       return ret;
+}
+
+static
+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, path, fullpath, sizeof(fullpath));
+       if (ret) {
+               errno = ENOMEM;
+               goto end;
+       }
+
+       dir_stream = opendir(fullpath);
+end:
+       return dir_stream;
+}
+
+static
+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, name, fullpath, sizeof(fullpath));
+       if (ret) {
+               errno = ENOMEM;
+               goto end;
+       }
+
+       ret = rmdir(fullpath);
+end:
+       return ret;
+}
+
+static
+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, name, fullpath, sizeof(fullpath));
+       if (ret) {
+               errno = ENOMEM;
+               goto end;
+       }
+
+       ret = run_as_rmdir(fullpath, uid, gid);
+end:
+       return ret;
+}
+
+static
+int _run_as_rmdir_recursive(
+               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, name, fullpath, sizeof(fullpath));
+       if (ret) {
+               errno = ENOMEM;
+               goto end;
+       }
+
+       ret = run_as_rmdir_recursive(fullpath, uid, gid);
+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
@@ -574,6 +805,8 @@ int create_directory_check_exists(const struct lttng_directory_handle *handle,
                        ret = -1;
                        goto end;
                }
+       } else if (errno != ENOENT) {
+               goto end;
        }
 
        /*
@@ -585,7 +818,6 @@ end:
        return ret;
 }
 
-/* Common implementation. */
 LTTNG_HIDDEN
 struct lttng_directory_handle
 lttng_directory_handle_move(struct lttng_directory_handle *original)
@@ -766,3 +998,228 @@ int lttng_directory_handle_unlink_file(
        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 {
+       DIR *dir;
+       /* Size including '\0'. */
+       size_t path_size;
+};
+
+static
+void rmdir_frame_fini(void *data)
+{
+       struct rmdir_frame *frame = data;
+
+       closedir(frame->dir);
+}
+
+static
+int remove_directory_recursive(const struct lttng_directory_handle *handle,
+               const char *path)
+{
+       int ret;
+       struct lttng_dynamic_array frames;
+       size_t current_frame_idx = 0;
+       struct rmdir_frame initial_frame = {
+               .dir = lttng_directory_handle_opendir(handle, path),
+               .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);
+
+       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);
+
+                if (!current_frame->dir) {
+                       PERROR("Failed to open directory stream during recursive directory removal");
+                       ret = -1;
+                       goto end;
+               }
+               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;
+                       struct rmdir_frame new_frame;
+
+                       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)) {
+                               PERROR("Failed to stat \"%s\"",
+                                               current_path.data);
+                               ret = -1;
+                               goto end;
+                       }
+
+                       if (!S_ISDIR(st.st_mode)) {
+                               /* Not empty, abort. */
+                               DBG("Directory \"%s\" is not empty; refusing to remove directory",
+                                               current_path.data);
+                               ret = -1;
+                               goto end;
+                       }
+
+                       new_frame.path_size = current_path.size;
+                       new_frame.dir = lttng_directory_handle_opendir(handle,
+                                       current_path.data);
+                       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++;
+                       break;
+                }
+               if (!entry) {
+                       ret = lttng_directory_handle_rmdir(handle,
+                                       current_path.data);
+                        if (ret) {
+                               PERROR("Failed to remove \"%s\" during recursive directory removal",
+                                               current_path.data);
+                               goto end;
+                        }
+                       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)
+{
+       return lttng_directory_handle_remove_subdirectory_recursive_as_user(
+                       handle, name, NULL);
+}
+
+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 ret;
+
+       if (!creds) {
+               /* Run as current user. */
+               ret = remove_directory_recursive(handle, name);
+       } else {
+               ret = _run_as_rmdir_recursive(handle, name, creds->uid,
+                               creds->gid);
+       }
+       return ret;
+}
This page took 0.028921 seconds and 4 git commands to generate.