commit: 28b92a6bd493f0a2543ad6709f3a9f6f116ddb88
parent fe28b8a17d810b3356fc60443584ad1cdb9e34a4
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sun, 4 Jan 2026 11:52:58 +0100
libutils/lib_mkdir: mkdir+chmod when making parents, check filetype after EEXIST
The mkdir(parent, 0) + chmod(parent, mode) is the (strange) flow required
by POSIX since POSIX.1-2013 via
Technical Corrigendum 1 XCU/TC1-2008/0122 [161].
See http://austingroupbugs.net/view.php?id=161
Diffstat:
2 files changed, 104 insertions(+), 22 deletions(-)
diff --git a/libutils/lib_mkdir.c b/libutils/lib_mkdir.c
@@ -7,6 +7,7 @@
#include "./lib_mkdir.h"
#include "./lib_string.h"
+#include "./mode.h"
#include <errno.h>
#include <limits.h> // PATH_MAX
@@ -14,46 +15,127 @@
#include <string.h> // strlen, strerror
#include <sys/stat.h> // mkdir
-int
-mkdir_parents(char *path, mode_t mode)
+static int
+wrap_mkdir(const char *path, mode_t mode)
{
- for(int i = strlen(path) - 1; i >= 0; i--)
+ if(mkdir(path, mode) < 0)
{
- if(path[i] != '/') break;
+ if(errno != EEXIST)
+ {
+ fprintf(
+ stderr, "%s: error: Failed making directory '%s': %s\n", argv0, path, strerror(errno));
+ errno = 0;
+ return -1;
+ }
+
+ errno = 0;
+
+ /*
+ * As seen from GNU. I believe it shouldn't introduce a TOCTOU kind
+ * of problem as it's a check which only invalidates.
+ */
+ struct stat dir_stat;
+ if(stat(path, &dir_stat) < 0)
+ {
+ fprintf(stderr,
+ "%s: error: Failed getting status of existing file or directory at '%s': %s\n",
+ argv0,
+ path,
+ strerror(errno));
+ return -1;
+ }
+
+ if(!S_ISDIR(dir_stat.st_mode))
+ {
+ fprintf(stderr,
+ "%s: error: Location '%s' already used by %s\n",
+ argv0,
+ path,
+ filetype(&dir_stat));
+ return -1;
+ }
- path[i] = 0;
+ errno = EEXIST;
+ return 0;
}
+ else if(mkdir_parents_verbose)
+ fprintf(stderr, "%s: Made directory: %s\n", argv0, path);
- char parent[PATH_MAX] = "";
- lib_strlcpy(parent, path, PATH_MAX);
+ return 0;
+}
+
+static void
+strip_basename(char *parent)
+{
+ size_t i = strlen(parent);
+
+ while(i-- > 0)
+ {
+ if(parent[i] != '/') break;
+
+ parent[i] = '\0';
+ }
- for(int i = strlen(parent) - 1; i >= 0; i--)
+ while(i-- > 0)
{
if(parent[i] == '/') break;
- parent[i] = 0;
+ parent[i] = '\0';
}
+}
+static int
+mkdir_elders(char *path, mode_t mode)
+{
if(path[0] == 0) return 0;
- mode_t parent_mode = (S_IWUSR | S_IXUSR | ~mkdir_parents_filemask) & 0777;
+ char parent[PATH_MAX] = "";
+ lib_strlcpy(parent, path, PATH_MAX);
+ strip_basename(parent);
- if(mkdir_parents(parent, parent_mode) < 0) return -1;
+ if(mkdir_elders(parent, mode) < 0) return -1;
- if(mkdir(path, mode) < 0)
- {
- if(errno == EEXIST)
- {
- errno = 0;
- return 0;
- }
+ errno = 0;
+ if(wrap_mkdir(path, 0) < 0) return -1;
- fprintf(stderr, "%s: error: Failed making directory '%s': %s\n", argv0, path, strerror(errno));
- errno = 0;
+ /*
+ * mkdir()+chmod() flow introduced with http://austingroupbugs.net/view.php?id=161
+ * released in Technical Corrigendum 1 XCU/TC1-2008/0122
+ *
+ * Sadly the October 9, 2009 meeting https://www.opengroup.org/austin/docs/austin_467.txt
+ * doesn't provides more details
+ */
+ if(errno != EEXIST && chmod(path, mode) < 0)
+ {
+ char mode_buf[11] = "";
+ symbolize_mode(mode, mode_buf);
+ fprintf(stderr,
+ "%s: error: Failed applying mode (%07o/%s) on '%s': %s\n",
+ argv0,
+ mode,
+ mode_buf,
+ path,
+ strerror(errno));
return -1;
}
- if(mkdir_parents_verbose) fprintf(stderr, "%s: Made directory: %s\n", argv0, path);
+ return 0;
+}
+
+int
+mkdir_parents(char *path, mode_t mode)
+{
+ if(path[0] == 0) return 0;
+
+ char parent[PATH_MAX] = "";
+ lib_strlcpy(parent, path, PATH_MAX);
+ strip_basename(parent);
+
+ mode_t elders_mode = (S_IWUSR | S_IXUSR | ~mkdir_parents_filemask) & 0777;
+
+ if(mkdir_elders(parent, elders_mode) < 0) return -1;
+
+ if(wrap_mkdir(path, mode) < 0) return -1;
return 0;
}
diff --git a/libutils/symbolize_mode.c b/libutils/symbolize_mode.c
@@ -5,7 +5,7 @@
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700 // S_ISVTX, S_IFMT, S_IFDIR, …
#include "../lib/bitmasks.h"
-#include "mode.h"
+#include "./mode.h"
#include <sys/stat.h>