logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
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:

Mlibutils/lib_mkdir.c124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mlibutils/symbolize_mode.c2+-
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>