Add mkdirat utils and runas wrappers
[lttng-tools.git] / src / common / compat / directory-handle.c
diff --git a/src/common/compat/directory-handle.c b/src/common/compat/directory-handle.c
new file mode 100644 (file)
index 0000000..7077378
--- /dev/null
@@ -0,0 +1,475 @@
+/*
+ * 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.
+ *
+ * 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/error.h>
+#include <common/macros.h>
+#include <common/runas.h>
+#include <common/credentials.h>
+#include <lttng/constant.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+static
+int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
+               const char *path, struct stat *st);
+static
+int lttng_directory_handle_mkdir(
+               const struct lttng_directory_handle *handle,
+               const char *path, mode_t mode);
+static
+int _run_as_mkdir(const struct lttng_directory_handle *handle, const char *path,
+               mode_t mode, uid_t uid, gid_t gid);
+static
+int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
+               const char *path, mode_t mode, uid_t uid, gid_t gid);
+
+#ifdef COMPAT_DIRFD
+
+LTTNG_HIDDEN
+int lttng_directory_handle_init(struct lttng_directory_handle *handle,
+               const char *path)
+{
+       int ret;
+
+       if (!path) {
+               handle->dirfd = AT_FDCWD;
+               ret = 0;
+               goto end;
+       }
+       ret = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+       if (ret == -1) {
+               PERROR("Failed to initialize directory handle to \"%s\"", path);
+               goto end;
+       }
+       handle->dirfd = ret;
+       ret = 0;
+end:
+       return ret;
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_init_from_dirfd(
+               struct lttng_directory_handle *handle, int dirfd)
+{
+       handle->dirfd = dirfd;
+       return 0;
+}
+
+LTTNG_HIDDEN
+void lttng_directory_handle_fini(struct lttng_directory_handle *handle)
+{
+       int ret;
+
+       if (handle->dirfd == AT_FDCWD) {
+               return;
+       }
+       ret = close(handle->dirfd);
+       if (ret == -1) {
+               PERROR("Failed to close directory file descriptor of directory handle");
+       }
+}
+
+static
+int lttng_directory_handle_stat(const struct lttng_directory_handle *handle,
+               const char *path, struct stat *st)
+{
+       return fstatat(handle->dirfd, path, st, 0);
+}
+
+static
+int lttng_directory_handle_mkdir(
+               const struct lttng_directory_handle *handle,
+               const char *path, mode_t mode)
+{
+       return mkdirat(handle->dirfd, path, mode);
+}
+
+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);
+}
+
+static
+int _run_as_mkdir_recursive(const struct lttng_directory_handle *handle,
+               const char *path, mode_t mode, uid_t uid, gid_t gid)
+{
+       return run_as_mkdirat_recursive(handle->dirfd, path, mode, uid, gid);
+}
+
+#else /* COMPAT_DIRFD */
+
+LTTNG_HIDDEN
+int lttng_directory_handle_init(struct lttng_directory_handle *handle,
+               const char *path)
+{
+       int ret;
+       size_t cwd_len, path_len, handle_path_len;
+       char cwd_buf[LTTNG_PATH_MAX];
+       const char *cwd;
+       bool add_slash = false;
+       struct stat stat_buf;
+
+       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;
+               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;
+               }
+
+               /*
+                * 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;
+       }
+
+       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;
+               goto end;
+       }
+       handle->base_path = zmalloc(handle_path_len);
+       if (!handle->base_path) {
+               PERROR("Failed to initialize directory handle");
+               ret = -1;
+               goto end;
+       }
+
+       ret = sprintf(handle->base_path, "%s%s%s/", cwd,
+                       add_slash ? "/" : "", path);
+       if (ret == -1 || ret >= handle_path_len) {
+               ERR("Failed to initialize directory handle: path formatting failed");
+               ret = -1;
+               goto end;
+       }
+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 ret;
+
+       /*
+        * 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 = 0;
+       return ret;
+}
+
+static
+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;
+}
+
+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 _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_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];
+
+       ret = get_full_path(handle, path, fullpath, sizeof(fullpath));
+       if (ret) {
+               errno = ENOMEM;
+               goto end;
+       }
+
+       ret = run_as_mkdir_recursive(fullpath, mode, uid, gid);
+end:
+       return ret;
+}
+
+#endif /* COMPAT_DIRFD */
+
+/*
+ * 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
+ * where "/home/" is a mounted NFS share, and running as an unpriviledged user,
+ * recursively creating a path of the form "/home/my_user/trace/" will fail with
+ * EACCES on mkdir("/home", ...).
+ *
+ * Checking the path for existence allows us to work around this behaviour.
+ */
+static
+int create_directory_check_exists(const struct lttng_directory_handle *handle,
+               const char *path, mode_t mode)
+{
+       int ret = 0;
+       struct stat st;
+
+       ret = lttng_directory_handle_stat(handle, path, &st);
+       if (ret == 0) {
+               if (S_ISDIR(st.st_mode)) {
+                       /* Directory exists, skip. */
+                       goto end;
+               } else {
+                       /* Exists, but is not a directory. */
+                       errno = ENOTDIR;
+                       ret = -1;
+                       goto end;
+               }
+       }
+
+       /*
+        * Let mkdir handle other errors as the caller expects mkdir
+        * semantics.
+        */
+       ret = lttng_directory_handle_mkdir(handle, path, mode);
+end:
+       return ret;
+}
+
+/* Common implementation. */
+static
+int create_directory_recursive(const struct lttng_directory_handle *handle,
+               const char *path, mode_t mode)
+{
+       char *p, tmp[LTTNG_PATH_MAX];
+       size_t len;
+       int ret;
+
+       assert(path);
+
+       ret = lttng_strncpy(tmp, path, sizeof(tmp));
+       if (ret) {
+               ERR("Failed to create directory: provided path's length (%zu bytes) exceeds the maximal allowed length (%zu bytes)",
+                               strlen(path) + 1, sizeof(tmp));
+               goto error;
+       }
+
+       len = strlen(path);
+       if (tmp[len - 1] == '/') {
+               tmp[len - 1] = 0;
+       }
+
+       for (p = tmp + 1; *p; p++) {
+               if (*p == '/') {
+                       *p = 0;
+                       if (tmp[strlen(tmp) - 1] == '.' &&
+                                       tmp[strlen(tmp) - 2] == '.' &&
+                                       tmp[strlen(tmp) - 3] == '/') {
+                               ERR("Using '/../' is not permitted in the trace path (%s)",
+                                               tmp);
+                               ret = -1;
+                               goto error;
+                       }
+                       ret = create_directory_check_exists(handle, tmp, mode);
+                       if (ret < 0) {
+                               if (errno != EACCES) {
+                                       PERROR("Failed to create directory \"%s\"",
+                                                       path);
+                                       ret = -errno;
+                                       goto error;
+                               }
+                       }
+                       *p = '/';
+               }
+       }
+
+       ret = create_directory_check_exists(handle, tmp, mode);
+       if (ret < 0) {
+               PERROR("mkdirat recursive last element");
+               ret = -errno;
+       }
+error:
+       return ret;
+}
+
+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)
+{
+       int ret;
+
+       if (!creds) {
+               /* Run as current user. */
+               ret = create_directory_check_exists(handle,
+                               subdirectory, mode);
+       } else {
+               ret = _run_as_mkdir(handle, subdirectory,
+                               mode, creds->uid, creds->gid);
+       }
+
+       return ret;
+}
+
+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)
+{
+       int ret;
+
+       if (!creds) {
+               /* Run as current user. */
+               ret = create_directory_recursive(handle,
+                               subdirectory_path, mode);
+       } else {
+               ret = _run_as_mkdir_recursive(handle, subdirectory_path,
+                               mode, creds->uid, creds->gid);
+       }
+
+       return ret;
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_create_subdirectory(
+               const struct lttng_directory_handle *handle,
+               const char *subdirectory,
+               mode_t mode)
+{
+       return lttng_directory_handle_create_subdirectory_as_user(
+                       handle, subdirectory, mode, NULL);
+}
+
+LTTNG_HIDDEN
+int lttng_directory_handle_create_subdirectory_recursive(
+               const struct lttng_directory_handle *handle,
+               const char *subdirectory_path,
+               mode_t mode)
+{
+       return lttng_directory_handle_create_subdirectory_recursive_as_user(
+                       handle, subdirectory_path, mode, NULL);
+}
This page took 0.02774 seconds and 5 git commands to generate.