From 19885b8a20053082543ceb238197315de1cc0bf6 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 7 Apr 2015 12:29:07 -0400 Subject: [PATCH] shutil: Add mkdir -p API I looked at the systemd code but it didn't have a variant of mkdir_parents that used `*at()`. This is a fresh implementation, with the risk that entails. However I am changing libgsystem to call it now for testing, and libgsystem APIs are covered by ostree usage at least. --- glnx-shutil.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++ glnx-shutil.h | 7 ++++ 2 files changed, 105 insertions(+) diff --git a/glnx-shutil.c b/glnx-shutil.c index 967e3649..e4df9aec 100644 --- a/glnx-shutil.c +++ b/glnx-shutil.c @@ -188,3 +188,101 @@ glnx_shutil_rm_rf_at (int dfd, out: return ret; } + +static gboolean +mkdir_p_at_internal (int dfd, + char *path, + int mode, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gboolean did_recurse = FALSE; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + again: + if (mkdirat (dfd, path, mode) == -1) + { + if (errno == ENOENT) + { + char *lastslash; + + g_assert (!did_recurse); + + lastslash = strrchr (path, '/'); + g_assert (lastslash != NULL); + /* Note we can mutate the buffer as we dup'd it */ + *lastslash = '\0'; + + if (!glnx_shutil_mkdir_p_at (dfd, path, mode, + cancellable, error)) + goto out; + + /* Now restore it for another mkdir attempt */ + *lastslash = '/'; + + did_recurse = TRUE; + goto again; + } + else if (errno == EEXIST) + { + /* Fall through; it may not have been a directory, + * but we'll find that out on the next call up. + */ + } + else + { + glnx_set_error_from_errno (error); + goto out; + } + } + + ret = TRUE; + out: + return ret; +} + +/** + * glnx_shutil_mkdir_p_at: + * @dfd: Directory fd + * @path: Directory path to be created + * @mode: Mode for newly created directories + * @cancellable: Cancellable + * @error: Error + * + * Similar to g_mkdir_with_parents(), except operates relative to the + * directory fd @dfd. + */ +gboolean +glnx_shutil_mkdir_p_at (int dfd, + const char *path, + int mode, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + struct stat stbuf; + + /* Fast path stat to see whether it already exists */ + if (fstatat (dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW) == 0) + { + if (S_ISDIR (stbuf.st_mode)) + { + ret = TRUE; + goto out; + } + } + + { + char *buf = strdupa (path); + + if (!mkdir_p_at_internal (dfd, buf, mode, cancellable, error)) + goto out; + } + + ret = TRUE; + out: + return ret; +} diff --git a/glnx-shutil.h b/glnx-shutil.h index 0c53fd86..8cc732c3 100644 --- a/glnx-shutil.h +++ b/glnx-shutil.h @@ -30,4 +30,11 @@ glnx_shutil_rm_rf_at (int dfd, GCancellable *cancellable, GError **error); +gboolean +glnx_shutil_mkdir_p_at (int dfd, + const char *path, + int mode, + GCancellable *cancellable, + GError **error); + G_END_DECLS