+static
+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);
+}
+
+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);
+}
+
+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 /* HAVE_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;
+}
+
+struct lttng_directory_handle *lttng_directory_handle_create(
+ const char *path)
+{
+ int ret;
+ 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;
+}
+
+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;
+
+ LTTNG_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;
+}
+
+struct lttng_directory_handle *lttng_directory_handle_create_from_dirfd(
+ int dirfd)
+{
+ LTTNG_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);
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+