summaryrefslogtreecommitdiff
path: root/glnx-shutil.c
diff options
context:
space:
mode:
authorColin Walters <walters@verbum.org>2015-04-07 12:29:07 -0400
committerColin Walters <walters@verbum.org>2015-04-07 12:29:07 -0400
commit19885b8a20053082543ceb238197315de1cc0bf6 (patch)
tree9565849bdeedce2884ec061a1e973fa7e9708c27 /glnx-shutil.c
parentd59a63e3e650aa75a055e4ede523790d60645435 (diff)
downloadlibglnx-19885b8a20053082543ceb238197315de1cc0bf6.tar.gz
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.
Diffstat (limited to 'glnx-shutil.c')
-rw-r--r--glnx-shutil.c98
1 files changed, 98 insertions, 0 deletions
diff --git a/glnx-shutil.c b/glnx-shutil.c
index 967e364..e4df9ae 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;
+}