From b0e7307b3569a5dad0f2606d2736cc8317851598 Mon Sep 17 00:00:00 2001
From: Dominique Martinet <dominique.martinet@atmark-techno.com>
Date: Wed, 30 Aug 2023 11:46:01 +0900
Subject: [PATCH] utils: add mkdir_recursive

This will be used in the next commit.

A test file for utils has also been added to check mkdir works as
intended.

Signed-off-by: Dominique Martinet <dominique.martinet@atmark-techno.com>
---
 pppd/Makefile.am    |   6 ++
 pppd/pppd-private.h |   1 +
 pppd/utils.c        |  82 ++++++++++++++++++++++++++
 pppd/utils_utest.c  | 139 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 228 insertions(+)
 create mode 100644 pppd/utils_utest.c

diff --git a/pppd/Makefile.am b/pppd/Makefile.am
index 7cb300533..c5fe10776 100644
--- a/pppd/Makefile.am
+++ b/pppd/Makefile.am
@@ -20,6 +20,12 @@ utest_pppcrypt_LDFLAGS =
 
 check_PROGRAMS += utest_crypto
 
+utest_utils_SOURCES = utils.c utils_utest.c
+utest_utils_CPPFLAGS = -DUNIT_TEST
+utest_utils_LDFLAGS =
+
+check_PROGRAMS += utest_utils
+
 if WITH_SRP
 sbin_PROGRAMS += srp-entry
 dist_man8_MANS += srp-entry.8
diff --git a/pppd/pppd-private.h b/pppd/pppd-private.h
index 2883e4622..46ce0c8bd 100644
--- a/pppd/pppd-private.h
+++ b/pppd/pppd-private.h
@@ -437,6 +437,7 @@ int  sifproxyarp(int, u_int32_t);
 int  cifproxyarp(int, u_int32_t);
 				/* Delete proxy ARP entry for peer */
 u_int32_t GetMask(u_int32_t); /* Get appropriate netmask for address */
+int  mkdir_recursive(const char *); /* Recursively create directory */
 int  lock(char *);	/* Create lock file for device */
 int  relock(int);		/* Rewrite lock file with new pid */
 void unlock(void);	/* Delete previously-created lock file */
diff --git a/pppd/utils.c b/pppd/utils.c
index c1bdbbbfe..c47192e67 100644
--- a/pppd/utils.c
+++ b/pppd/utils.c
@@ -781,6 +781,88 @@ complete_read(int fd, void *buf, size_t count)
 }
 #endif
 
+/*
+ * mkdir_check - helper for mkdir_recursive, creates a directory
+ * but do not error on EEXIST if and only if entry is a directory
+ * The caller must check for errno == ENOENT if appropriate.
+ */
+static int
+mkdir_check(const char *path)
+{
+    struct stat statbuf;
+
+    if (mkdir(path, 0755) >= 0)
+	return 0;
+
+    if (errno == EEXIST) {
+	if (stat(path, &statbuf) < 0)
+	    /* got raced? */
+	    return -1;
+
+	if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+	    return 0;
+
+	/* already exists but not a dir, treat as failure */
+	errno = EEXIST;
+	return -1;
+    }
+
+    return -1;
+}
+
+/*
+ * mkdir_parent - helper for mkdir_recursive, modifies the string in place
+ * Assumes mkdir(path) already failed, so it first creates the parent then
+ * full path again.
+ */
+static int
+mkdir_parent(char *path)
+{
+    char *slash;
+    int rc;
+
+    slash = strrchr(path, '/');
+    if (!slash)
+	return -1;
+
+    *slash = 0;
+    if (mkdir_check(path) < 0) {
+	if (errno != ENOENT) {
+	    *slash = '/';
+	    return -1;
+	}
+	if (mkdir_parent(path) < 0) {
+	    *slash = '/';
+	    return -1;
+	}
+    }
+    *slash = '/';
+
+    return mkdir_check(path);
+}
+
+/*
+ * mkdir_recursive - recursively create directory if it didn't exist
+ */
+int
+mkdir_recursive(const char *path)
+{
+    char *copy;
+    int rc;
+
+    // optimistically try on full path first to avoid allocation
+    if (mkdir_check(path) == 0)
+	return 0;
+
+    copy = strdup(path);
+    if (!copy)
+	return -1;
+
+    rc = mkdir_parent(copy);
+    free(copy);
+    return rc;
+}
+
 /* Procedures for locking the serial device using a lock file. */
 static char lock_file[MAXPATHLEN];
 
diff --git a/pppd/utils_utest.c b/pppd/utils_utest.c
new file mode 100644
index 000000000..cdca97e6d
--- /dev/null
+++ b/pppd/utils_utest.c
@@ -0,0 +1,139 @@
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "pppd-private.h"
+
+/* globals used in test.c... */
+int debug = 1;
+int error_count;
+int unsuccess;
+
+/* check if path exists and returns its type */
+static int
+file_type(char *path)
+{
+    struct stat statbuf;
+
+    if (stat(path, &statbuf) < 0)
+	return -1;
+
+    return statbuf.st_mode & S_IFMT;
+}
+
+int
+test_simple() {
+    if (mkdir_recursive("dir"))
+	return -1;
+
+    if (file_type("dir") != S_IFDIR)
+	return -1;
+
+    rmdir("dir");
+    return 0;
+}
+
+int
+test_recurse() {
+    if (mkdir_recursive("dir/subdir/subsubdir"))
+	return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+	return -1;
+
+    rmdir("dir/subdir/subsubdir");
+
+    /* try again with partial existence */
+    if (mkdir_recursive("dir/subdir/subsubdir"))
+	return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+	return -1;
+
+    rmdir("dir/subdir/subsubdir");
+    rmdir("dir/subdir");
+    rmdir("dir");
+    return 0;
+}
+
+int
+test_recurse_multislash() {
+    if (mkdir_recursive("dir/subdir///subsubdir"))
+	return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+	return -1;
+
+    rmdir("dir/subdir/subsubdir");
+    rmdir("dir/subdir");
+
+    /* try again with partial existence */
+    if (mkdir_recursive("dir/subdir/subsubdir///"))
+	return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+	return -1;
+
+    rmdir("dir/subdir/subsubdir");
+    rmdir("dir/subdir");
+    rmdir("dir");
+    return 0;
+}
+
+int
+test_parent_notdir() {
+    int fd = open("file", O_CREAT, 0600);
+    if (fd < 0)
+	return -1;
+    close(fd);
+
+    if (mkdir_recursive("file") == 0)
+	return -1;
+    if (mkdir_recursive("file/dir") == 0)
+	return -1;
+
+    unlink("file");
+    return 0;
+}
+
+int
+main()
+{
+    char *base_dir = strdup("/tmp/ppp_utils_utest.XXXXXX");
+    int failure = 0;
+
+    if (mkdtemp(base_dir) == NULL) {
+	printf("Could not create test directory, aborting\n");
+	return 1;
+    }
+
+    if (chdir(base_dir) < 0) {
+	printf("Could not enter newly created test dir, aborting\n");
+	return 1;
+    }
+
+    if (test_simple()) {
+	printf("Could not create simple directory\n");
+	failure++;
+    }
+
+    if (test_recurse()) {
+	printf("Could not create recursive directory\n");
+	failure++;
+    }
+
+    if (test_recurse_multislash()) {
+	printf("Could not create recursive directory with multiple slashes\n");
+	failure++;
+    }
+
+    if (test_parent_notdir()) {
+	printf("Creating over a file appeared to work?\n");
+	failure++;
+    }
+
+    rmdir(base_dir);
+    free(base_dir);
+    return failure;
+}
