Add mkdirat utils and runas wrappers
authorJérémie Galarneau <jeremie.galarneau@efficios.com>
Wed, 17 Apr 2019 20:55:27 +0000 (16:55 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Wed, 24 Apr 2019 23:30:47 +0000 (19:30 -0400)
The lttng_directory_handle allows its user to keep a handle to
a directory and to create subdirectories relative to it.

On platforms implementing POSIX.2008, a directory file descriptor
is used to maintain a handle to an existing directory and used
in conjunction with mkdirat() to create subdirectories.

Derelict platforms (such as Solaris 10) use an alternative
implementation which carries the location of the root directory
and builds subdirectory paths on creation.

The existing mkdir utils are re-implemented using this new
interface (using the special AT_FDCWD file descriptor value, when
applicable) to limit code duplication.

The implementation of the directory handle and its users is
automatically selected based on the presence of the dirfd() function,
but can also be explicitly chosen using the --enable/disable-dirfd
configuration option.

Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
configure.ac
src/common/Makefile.am
src/common/compat/Makefile.am
src/common/compat/directory-handle.c [new file with mode: 0644]
src/common/compat/directory-handle.h [new file with mode: 0644]
src/common/credentials.h [new file with mode: 0644]
src/common/runas.c
src/common/runas.h
src/common/utils.c
src/common/utils.h

index 67214731eea80be86d7bebcf99fa958644674604..06e6e10e645a08e79443ae0df470a116feafaff6 100644 (file)
@@ -639,6 +639,17 @@ AX_CONFIG_FEATURE(
 )
 AM_CONDITIONAL([COMPAT_EPOLL], [ test "$enable_epoll" = "yes" ])
 
+AS_IF([test "x$ac_cv_func_dirfd" = "xyes"],
+       [AX_CONFIG_FEATURE_ENABLE(dirfd)],
+       [AX_CONFIG_FEATURE_DISABLE(dirfd)]
+)
+AX_CONFIG_FEATURE(
+       [dirfd], [Use directory file descriptors],
+       [COMPAT_DIRFD], [This platform supports directory file descriptors.],
+       [enable_dirfd="yes"], [enable_dirfd="no"]
+)
+AM_CONDITIONAL([COMPAT_DIRFD], [ test "$enable_dirfd" = "yes" ])
+
 AM_CONDITIONAL([TEST_JAVA_JUL_AGENT], [test "x$test_java_agent_jul" = "xyes"])
 AM_CONDITIONAL([TEST_JAVA_LOG4J_AGENT], [test "x$test_java_agent_log4j" = "xyes"])
 
index dae067df6a619f824907ad99408271cacb6b145e..3e816b26a27e5d977d4be0380376e4083a0c7bc8 100644 (file)
@@ -28,7 +28,7 @@ libcommon_la_SOURCES = error.h error.c utils.c utils.h runas.h runas.c \
                        location.c \
                        waiter.h waiter.c \
                        userspace-probe.c event.c time.c \
-                       session-descriptor.c
+                       session-descriptor.c credentials.h
 
 if HAVE_ELF_H
 libcommon_la_SOURCES += lttng-elf.h lttng-elf.c
@@ -36,6 +36,7 @@ endif
 
 libcommon_la_LIBADD = \
                $(top_builddir)/src/common/config/libconfig.la \
+               $(top_builddir)/src/common/compat/libcompat.la \
                $(UUID_LIBS)
 
 if BUILD_LIB_COMPAT
index c5418baebec2c738ccc3448c285246b642e7e227..b31158f0d4b9d9b7f2963201e223f49bdf6ff9c9 100644 (file)
@@ -9,4 +9,4 @@ endif
 libcompat_la_SOURCES = poll.h fcntl.h endian.h mman.h dirent.h \
                socket.h compat-fcntl.c uuid.h tid.h \
                getenv.h string.h prctl.h paths.h netdb.h $(COMPAT) \
-               time.h
+               time.h directory-handle.h 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);
+}
diff --git a/src/common/compat/directory-handle.h b/src/common/compat/directory-handle.h
new file mode 100644 (file)
index 0000000..a24bbd8
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#ifndef _COMPAT_DIRECTORY_HANDLE_H
+#define _COMPAT_DIRECTORY_HANDLE_H
+
+#include <common/macros.h>
+#include <common/credentials.h>
+
+/*
+ * Some platforms, such as Solaris 10, do not support directory file descriptors
+ * and their associated functions (*at(...)), which are defined in POSIX.2008.
+ *
+ * This wrapper provides a handle that is either a copy of a directory's path
+ * or a directory file descriptors, depending on the platform's capabilities.
+ */
+#ifdef COMPAT_DIRFD
+struct lttng_directory_handle {
+       int dirfd;
+};
+#else
+struct lttng_directory_handle {
+       char *base_path;
+};
+#endif
+
+/*
+ * Initialize a directory handle to the provided path. Passing a NULL path
+ * returns a handle to the current working directory. The working directory
+ * is not sampled; it will be accessed at the time of use of the functions
+ * of this API.
+ *
+ * An initialized directory handle must be finalized using
+ * lttng_directory_handle_fini().
+ */
+LTTNG_HIDDEN
+int lttng_directory_handle_init(struct lttng_directory_handle *handle,
+               const char *path);
+
+LTTNG_HIDDEN
+int lttng_directory_handle_init_from_dirfd(
+               struct lttng_directory_handle *handle, int dirfd);
+
+/*
+ * Release the resources of a directory handle.
+ */
+LTTNG_HIDDEN
+void lttng_directory_handle_fini(struct lttng_directory_handle *handle);
+
+/*
+ * Create a subdirectory relative to a directory handle.
+ */
+LTTNG_HIDDEN
+int lttng_directory_handle_create_subdirectory(
+               const struct lttng_directory_handle *handle,
+               const char *subdirectory,
+               mode_t mode);
+
+/*
+ * Create a subdirectory relative to a directory handle
+ * as a given user.
+ */
+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);
+
+/*
+ * Recursively create a directory relative to a directory handle.
+ */
+LTTNG_HIDDEN
+int lttng_directory_handle_create_subdirectory_recursive(
+               const struct lttng_directory_handle *handle,
+               const char *subdirectory_path,
+               mode_t mode);
+
+/*
+ * Recursively create a directory relative to a directory handle
+ * as a given user.
+ */
+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);
+
+#endif /* _COMPAT_PATH_HANDLE_H */
diff --git a/src/common/credentials.h b/src/common/credentials.h
new file mode 100644 (file)
index 0000000..11aeba9
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#ifndef LTTNG_CREDENTIALS_H
+#define LTTNG_CREDENTIALS_H
+
+#include <sys/types.h>
+
+struct lttng_credentials {
+       uid_t uid;
+       gid_t gid;
+};
+
+#endif /* LTTNG_CREDENTIALS_H */
index c5cf09bfccbe2cd40d819cbc467d42f371cb4d80..43b6704b75d02055483d76bd93bce9eafedc2264 100644 (file)
@@ -49,7 +49,7 @@ struct run_as_data;
 struct run_as_ret;
 typedef int (*run_as_fct)(struct run_as_data *data, struct run_as_ret *ret_value);
 
-struct run_as_mkdir_data {
+struct run_as_mkdirat_data {
        char path[PATH_MAX];
        mode_t mode;
 };
@@ -77,7 +77,7 @@ struct run_as_extract_sdt_probe_offsets_data {
        char provider_name[LTTNG_SYMBOL_NAME_LEN];
 };
 
-struct run_as_mkdir_ret {
+struct run_as_mkdirat_ret {
        int ret;
 };
 
@@ -104,10 +104,12 @@ struct run_as_extract_sdt_probe_offsets_ret {
 
 enum run_as_cmd {
        RUN_AS_MKDIR,
+       RUN_AS_MKDIRAT,
+       RUN_AS_MKDIR_RECURSIVE,
+       RUN_AS_MKDIRAT_RECURSIVE,
        RUN_AS_OPEN,
        RUN_AS_UNLINK,
        RUN_AS_RMDIR_RECURSIVE,
-       RUN_AS_MKDIR_RECURSIVE,
        RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET,
        RUN_AS_EXTRACT_SDT_PROBE_OFFSETS,
 };
@@ -116,7 +118,7 @@ struct run_as_data {
        enum run_as_cmd cmd;
        int fd;
        union {
-               struct run_as_mkdir_data mkdir;
+               struct run_as_mkdirat_data mkdirat;
                struct run_as_open_data open;
                struct run_as_unlink_data unlink;
                struct run_as_rmdir_recursive_data rmdir_recursive;
@@ -145,7 +147,7 @@ struct run_as_data {
 struct run_as_ret {
        int fd;
        union {
-               struct run_as_mkdir_ret mkdir;
+               struct run_as_mkdirat_ret mkdirat;
                struct run_as_open_ret open;
                struct run_as_unlink_ret unlink;
                struct run_as_rmdir_recursive_ret rmdir_recursive;
@@ -181,35 +183,49 @@ int use_clone(void)
 }
 #endif
 
-LTTNG_HIDDEN
-int _utils_mkdir_recursive_unsafe(const char *path, mode_t mode);
-
 /*
  * Create recursively directory using the FULL path.
  */
 static
-int _mkdir_recursive(struct run_as_data *data, struct run_as_ret *ret_value)
+int _mkdirat_recursive(struct run_as_data *data, struct run_as_ret *ret_value)
 {
        const char *path;
        mode_t mode;
+       struct lttng_directory_handle handle;
 
-       path = data->u.mkdir.path;
-       mode = data->u.mkdir.mode;
+       path = data->u.mkdirat.path;
+       mode = data->u.mkdirat.mode;
 
+       (void) lttng_directory_handle_init_from_dirfd(&handle, data->fd);
        /* Safe to call as we have transitioned to the requested uid/gid. */
-       ret_value->u.mkdir.ret = _utils_mkdir_recursive_unsafe(path, mode);
+       ret_value->u.mkdirat.ret =
+                       lttng_directory_handle_create_subdirectory_recursive(
+                                       &handle, path, mode);
        ret_value->_errno = errno;
-       ret_value->_error = (ret_value->u.mkdir.ret) ? true : false;
-       return ret_value->u.mkdir.ret;
+       ret_value->_error = (ret_value->u.mkdirat.ret) ? true : false;
+       lttng_directory_handle_fini(&handle);
+       return ret_value->u.mkdirat.ret;
 }
 
 static
-int _mkdir(struct run_as_data *data, struct run_as_ret *ret_value)
+int _mkdirat(struct run_as_data *data, struct run_as_ret *ret_value)
 {
-       ret_value->u.mkdir.ret = mkdir(data->u.mkdir.path, data->u.mkdir.mode);
+       const char *path;
+       mode_t mode;
+       struct lttng_directory_handle handle;
+
+       path = data->u.mkdirat.path;
+       mode = data->u.mkdirat.mode;
+
+       (void) lttng_directory_handle_init_from_dirfd(&handle, data->fd);
+       /* Safe to call as we have transitioned to the requested uid/gid. */
+       ret_value->u.mkdirat.ret =
+                       lttng_directory_handle_create_subdirectory(
+                                       &handle, path, mode);
        ret_value->_errno = errno;
-       ret_value->_error = (ret_value->u.mkdir.ret) ? true : false;
-       return ret_value->u.mkdir.ret;
+       ret_value->_error = (ret_value->u.mkdirat.ret) ? true : false;
+       lttng_directory_handle_fini(&handle);
+       return ret_value->u.mkdirat.ret;
 }
 
 static
@@ -322,15 +338,17 @@ run_as_fct run_as_enum_to_fct(enum run_as_cmd cmd)
 {
        switch (cmd) {
        case RUN_AS_MKDIR:
-               return _mkdir;
+       case RUN_AS_MKDIRAT:
+               return _mkdirat;
+       case RUN_AS_MKDIR_RECURSIVE:
+       case RUN_AS_MKDIRAT_RECURSIVE:
+               return _mkdirat_recursive;
        case RUN_AS_OPEN:
                return _open;
        case RUN_AS_UNLINK:
                return _unlink;
        case RUN_AS_RMDIR_RECURSIVE:
                return _rmdir_recursive;
-       case RUN_AS_MKDIR_RECURSIVE:
-               return _mkdir_recursive;
        case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET:
                return _extract_elf_symbol_offset;
        case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS:
@@ -390,6 +408,8 @@ int send_fd_to_worker(struct run_as_worker *worker, enum run_as_cmd cmd, int fd)
        switch (cmd) {
        case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET:
        case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS:
+       case RUN_AS_MKDIRAT:
+       case RUN_AS_MKDIRAT_RECURSIVE:
                break;
        default:
                return 0;
@@ -468,7 +488,13 @@ int recv_fd_from_master(struct run_as_worker *worker, enum run_as_cmd cmd, int *
        switch (cmd) {
        case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET:
        case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS:
+       case RUN_AS_MKDIRAT:
+       case RUN_AS_MKDIRAT_RECURSIVE:
                break;
+       case RUN_AS_MKDIR:
+       case RUN_AS_MKDIR_RECURSIVE:
+               *fd = AT_FDCWD;
+               /* fall-through */
        default:
                return 0;
        }
@@ -490,6 +516,8 @@ int cleanup_received_fd(enum run_as_cmd cmd, int fd)
        switch (cmd) {
        case RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET:
        case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS:
+       case RUN_AS_MKDIRAT:
+       case RUN_AS_MKDIRAT_RECURSIVE:
                break;
        default:
                return 0;
@@ -1102,39 +1130,74 @@ err:
 LTTNG_HIDDEN
 int run_as_mkdir_recursive(const char *path, mode_t mode, uid_t uid, gid_t gid)
 {
+       return run_as_mkdirat_recursive(AT_FDCWD, path, mode, uid, gid);
+}
+
+LTTNG_HIDDEN
+int run_as_mkdirat_recursive(int dirfd, const char *path, mode_t mode,
+               uid_t uid, gid_t gid)
+{
+       int ret;
        struct run_as_data data;
-       struct run_as_ret ret;
+       struct run_as_ret run_as_ret;
 
        memset(&data, 0, sizeof(data));
-       memset(&ret, 0, sizeof(ret));
-       DBG3("mkdir() recursive %s with mode %d for uid %d and gid %d",
+       memset(&run_as_ret, 0, sizeof(run_as_ret));
+       DBG3("mkdirat() recursive fd = %d%s, path = %s, mode = %d, uid = %d, gid = %d",
+                       dirfd, dirfd == AT_FDCWD ? " (AT_FDCWD)" : "",
                        path, (int) mode, (int) uid, (int) gid);
-       strncpy(data.u.mkdir.path, path, PATH_MAX - 1);
-       data.u.mkdir.path[PATH_MAX - 1] = '\0';
-       data.u.mkdir.mode = mode;
-
-       run_as(RUN_AS_MKDIR_RECURSIVE, &data, &ret, uid, gid);
-       errno = ret._errno;
-       return ret.u.mkdir.ret;
+       ret = lttng_strncpy(data.u.mkdirat.path, path,
+                       sizeof(data.u.mkdirat.path));
+       if (ret) {
+               ERR("Failed to copy path argument of mkdirat recursive command");
+               goto error;
+       }
+       data.u.mkdirat.path[PATH_MAX - 1] = '\0';
+       data.u.mkdirat.mode = mode;
+       data.fd = dirfd;
+       run_as(dirfd == AT_FDCWD ? RUN_AS_MKDIR_RECURSIVE : RUN_AS_MKDIRAT_RECURSIVE,
+                       &data, &run_as_ret, uid, gid);
+       errno = run_as_ret._errno;
+       ret = run_as_ret.u.mkdirat.ret;
+error:
+       return ret;
 }
 
 LTTNG_HIDDEN
 int run_as_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid)
 {
+       return run_as_mkdirat(AT_FDCWD, path, mode, uid, gid);
+}
+
+LTTNG_HIDDEN
+int run_as_mkdirat(int dirfd, const char *path, mode_t mode,
+               uid_t uid, gid_t gid)
+{
+       int ret;
        struct run_as_data data;
-       struct run_as_ret ret;
+       struct run_as_ret run_as_ret;
 
        memset(&data, 0, sizeof(data));
-       memset(&ret, 0, sizeof(ret));
+       memset(&run_as_ret, 0, sizeof(run_as_ret));
 
-       DBG3("mkdir() %s with mode %d for uid %d and gid %d",
+       DBG3("mkdirat() recursive fd = %d%s, path = %s, mode = %d, uid = %d, gid = %d",
+                       dirfd, dirfd == AT_FDCWD ? " (AT_FDCWD)" : "",
                        path, (int) mode, (int) uid, (int) gid);
-       strncpy(data.u.mkdir.path, path, PATH_MAX - 1);
-       data.u.mkdir.path[PATH_MAX - 1] = '\0';
-       data.u.mkdir.mode = mode;
-       run_as(RUN_AS_MKDIR, &data, &ret, uid, gid);
-       errno = ret._errno;
-       return ret.u.mkdir.ret;
+       ret = lttng_strncpy(data.u.mkdirat.path, path,
+                       sizeof(data.u.mkdirat.path));
+       if (ret) {
+               ERR("Failed to copy path argument of mkdirat command");
+               goto error;
+       }
+       data.u.mkdirat.path[PATH_MAX - 1] = '\0';
+       data.u.mkdirat.mode = mode;
+       data.fd = dirfd;
+       run_as(dirfd == AT_FDCWD ? RUN_AS_MKDIR : RUN_AS_MKDIRAT,
+                       &data, &run_as_ret, uid, gid);
+       errno = run_as_ret._errno;
+       ret = run_as_ret._errno;
+error:
+       return ret;
 }
 
 LTTNG_HIDDEN
index 4d5639c4b6b77035a071255de313f303df7a35e1..82737177485b43c381c3834a2c577c367113f61a 100644 (file)
@@ -41,8 +41,14 @@ typedef int (*post_fork_cleanup_cb)(void *user_data);
 LTTNG_HIDDEN
 int run_as_mkdir_recursive(const char *path, mode_t mode, uid_t uid, gid_t gid);
 LTTNG_HIDDEN
+int run_as_mkdirat_recursive(int dirfd, const char *path, mode_t mode,
+               uid_t uid, gid_t gid);
+LTTNG_HIDDEN
 int run_as_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid);
 LTTNG_HIDDEN
+int run_as_mkdirat(int dirfd, const char *path, mode_t mode,
+               uid_t uid, gid_t gid);
+LTTNG_HIDDEN
 int run_as_open(const char *path, int flags, mode_t mode, uid_t uid, gid_t gid);
 LTTNG_HIDDEN
 int run_as_unlink(const char *path, uid_t uid, gid_t gid);
index 4c000e9b400864591a5bbd7715c2db7e3a1aef26..5be67c11f1e1dc896fa2e5d5e9cb7a6299903c73 100644 (file)
@@ -38,6 +38,7 @@
 #include <common/compat/getenv.h>
 #include <common/compat/string.h>
 #include <common/compat/dirent.h>
+#include <common/compat/directory-handle.h>
 #include <lttng/constant.h>
 
 #include "utils.h"
@@ -667,44 +668,6 @@ error:
        return fd;
 }
 
-/*
- * 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", ...).
- *
- * Performing a stat(...) on the path to check for existence allows us to
- * work around this behaviour.
- */
-static
-int mkdir_check_exists(const char *path, mode_t mode)
-{
-       int ret = 0;
-       struct stat st;
-
-       ret = stat(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 = mkdir(path, mode);
-end:
-       return ret;
-}
-
 /*
  * Create directory using the given path and mode.
  *
@@ -714,82 +677,17 @@ LTTNG_HIDDEN
 int utils_mkdir(const char *path, mode_t mode, int uid, int gid)
 {
        int ret;
-
-       if (uid < 0 || gid < 0) {
-               ret = mkdir_check_exists(path, mode);
-       } else {
-               ret = run_as_mkdir(path, mode, uid, gid);
-       }
-       if (ret < 0) {
-               if (errno != EEXIST) {
-                       PERROR("mkdir %s, uid %d, gid %d", path ? path : "NULL",
-                                       uid, gid);
-               } else {
-                       ret = 0;
-               }
-       }
-
-       return ret;
-}
-
-/*
- * Internal version of mkdir_recursive. Runs as the current user.
- * Don't call directly; use utils_mkdir_recursive().
- *
- * This function is ominously marked as "unsafe" since it should only
- * be called by a caller that has transitioned to the uid and gid under which
- * the directory creation should occur.
- */
-LTTNG_HIDDEN
-int _utils_mkdir_recursive_unsafe(const char *path, mode_t mode)
-{
-       char *p, tmp[PATH_MAX];
-       size_t len;
-       int ret;
-
-       assert(path);
-
-       ret = snprintf(tmp, sizeof(tmp), "%s", path);
-       if (ret < 0) {
-               PERROR("snprintf mkdir");
-               goto error;
-       }
-
-       len = ret;
-       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 = mkdir_check_exists(tmp, mode);
-                       if (ret < 0) {
-                               if (errno != EACCES) {
-                                       PERROR("mkdir recursive");
-                                       ret = -errno;
-                                       goto error;
-                               }
-                       }
-                       *p = '/';
-               }
-       }
-
-       ret = mkdir_check_exists(tmp, mode);
-       if (ret < 0) {
-               PERROR("mkdir recursive last element");
-               ret = -errno;
-       }
-
-error:
+       struct lttng_directory_handle handle;
+       struct lttng_credentials creds = {
+               .uid = (uid_t) uid,
+               .gid = (gid_t) gid,
+       };
+
+       (void) lttng_directory_handle_init(&handle, NULL);
+       ret = lttng_directory_handle_create_subdirectory_as_user(
+                       &handle, path, mode,
+                       (uid >= 0 || gid >= 0) ? &creds : NULL);
+       lttng_directory_handle_fini(&handle);
        return ret;
 }
 
@@ -803,18 +701,17 @@ LTTNG_HIDDEN
 int utils_mkdir_recursive(const char *path, mode_t mode, int uid, int gid)
 {
        int ret;
-
-       if (uid < 0 || gid < 0) {
-               /* Run as current user. */
-               ret = _utils_mkdir_recursive_unsafe(path, mode);
-       } else {
-               ret = run_as_mkdir_recursive(path, mode, uid, gid);
-       }
-       if (ret < 0) {
-               PERROR("mkdir %s, uid %d, gid %d", path ? path : "NULL",
-                               uid, gid);
-       }
-
+       struct lttng_directory_handle handle;
+       struct lttng_credentials creds = {
+               .uid = (uid_t) uid,
+               .gid = (gid_t) gid,
+       };
+
+       (void) lttng_directory_handle_init(&handle, NULL);
+       ret = lttng_directory_handle_create_subdirectory_recursive_as_user(
+                       &handle, path, mode,
+                       (uid >= 0 || gid >= 0) ? &creds : NULL);
+       lttng_directory_handle_fini(&handle);
        return ret;
 }
 
index 9e3fa60cdc4d1f5385e464328d7ae56eade4be07..f372bf46404ff6a5a587df3d513f0cb3a95af6ae 100644 (file)
@@ -23,6 +23,8 @@
 #include <stdint.h>
 #include <getopt.h>
 
+#include <common/compat/directory-handle.h>
+
 #define KIBI_LOG2 10
 #define MEBI_LOG2 20
 #define GIBI_LOG2 30
This page took 0.041223 seconds and 4 git commands to generate.