lib:util: Add directory_create_or_exists_recursive()
authorAndreas Schneider <asn@samba.org>
Mon, 21 Dec 2020 09:35:51 +0000 (10:35 +0100)
committerKarolin Seeger <kseeger@samba.org>
Wed, 13 Jan 2021 12:41:13 +0000 (12:41 +0000)
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14601

Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
from commit bf7b165877bdfd07eb84ecafdc87bd7a6d945f09)

lib/util/samba_util.h
lib/util/tests/test_util.c
lib/util/util.c

index f0aa42e7271f4fb3974c2f4e59a45fbb8d6e3903..d32765bf6d16c48e721c307f634beefea5845796 100644 (file)
@@ -451,6 +451,20 @@ _PUBLIC_ bool file_check_permissions(const char *fname,
  */
 _PUBLIC_ bool directory_create_or_exist(const char *dname, mode_t dir_perms);
 
+/**
+ * @brief Try to create a specified directory and the parent directory if they
+ *        don't exist.
+ *
+ * @param[in]  dname     The directory path to create.
+ *
+ * @param[in]  dir_perms The permission of the directories.
+ *
+ * @return true on success, false otherwise.
+ */
+_PUBLIC_ bool directory_create_or_exists_recursive(
+               const char *dname,
+               mode_t dir_perms);
+
 _PUBLIC_ bool directory_create_or_exist_strict(const char *dname,
                                               uid_t uid,
                                               mode_t dir_perms);
index eebba39e70ca777ba4087868dfd13c926b5db660..a893e6175c2508bc52959d1b8aa6eced7fd49483 100644 (file)
@@ -4,6 +4,7 @@
  *  Unit test for util.c
  *
  *  Copyright (C) Christof Schmitt 2020
+ *  Copyright (C) Andreas Schneider 2020
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "lib/util/util.c"
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
 #include <cmocka.h>
 
+#include "lib/replace/replace.h"
+#include "system/dir.h"
+
+#include "lib/util/util.c"
+
 struct test_paths {
        char testdir[PATH_MAX];
        char none[PATH_MAX];
        char dir[PATH_MAX];
+       char dir_recursive[PATH_MAX];
        mode_t dir_mode;
        char file[PATH_MAX];
        mode_t file_mode;
@@ -59,6 +69,12 @@ static int group_setup(void **state)
        ret = mkdir(paths->dir, paths->dir_mode);
        assert_return_code(ret, errno);
 
+       strlcpy(paths->dir_recursive, testdir, sizeof(paths->dir));
+       strlcat(paths->dir_recursive, "/dir_recursive", sizeof(paths->dir));
+       paths->dir_mode = 0750;
+       ret = mkdir(paths->dir_recursive, paths->dir_mode);
+       assert_return_code(ret, errno);
+
        strlcpy(paths->file, testdir, sizeof(paths->file));
        strlcat(paths->file, "/file", sizeof(paths->file));
        paths->file_mode = 0640;
@@ -89,16 +105,79 @@ static int group_setup(void **state)
        return 0;
 }
 
+static int torture_rmdirs(const char *path)
+{
+       DIR *d;
+       struct dirent *dp;
+       struct stat sb;
+       char *fname;
+
+       if ((d = opendir(path)) != NULL) {
+               while(stat(path, &sb) == 0) {
+                       /* if we can remove the directory we're done */
+                       if (rmdir(path) == 0) {
+                               break;
+                       }
+                       switch (errno) {
+                               case ENOTEMPTY:
+                               case EEXIST:
+                               case EBADF:
+                                       break; /* continue */
+                               default:
+                                       closedir(d);
+                                       return 0;
+                       }
+
+                       while ((dp = readdir(d)) != NULL) {
+                               size_t len;
+                               /* skip '.' and '..' */
+                               if (dp->d_name[0] == '.' &&
+                                               (dp->d_name[1] == '\0' ||
+                                                (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) {
+                                       continue;
+                               }
+
+                               len = strlen(path) + strlen(dp->d_name) + 2;
+                               fname = malloc(len);
+                               if (fname == NULL) {
+                                       closedir(d);
+                                       return -1;
+                               }
+                               snprintf(fname, len, "%s/%s", path, dp->d_name);
+
+                               /* stat the file */
+                               if (lstat(fname, &sb) != -1) {
+                                       if (S_ISDIR(sb.st_mode) && !S_ISLNK(sb.st_mode)) {
+                                               if (rmdir(fname) < 0) { /* can't be deleted */
+                                                       if (errno == EACCES) {
+                                                               closedir(d);
+                                                               SAFE_FREE(fname);
+                                                               return -1;
+                                                       }
+                                                       torture_rmdirs(fname);
+                                               }
+                                       } else {
+                                               unlink(fname);
+                                       }
+                               } /* lstat */
+                               SAFE_FREE(fname);
+                       } /* readdir */
+
+                       rewinddir(d);
+               }
+       } else {
+               return -1;
+       }
+
+       closedir(d);
+       return 0;
+}
+
 static int group_teardown(void **state)
 {
        struct test_paths *paths = *state;
        int ret;
 
-       return 0;
-
-       ret = rmdir(paths->dir);
-       assert_return_code(ret, errno);
-
        ret = unlink(paths->file);
        assert_return_code(ret, errno);
 
@@ -111,7 +190,7 @@ static int group_teardown(void **state)
        ret = unlink(paths->symlink_file);
        assert_return_code(ret, errno);
 
-       ret = unlink(paths->testdir);
+       ret = torture_rmdirs(paths->testdir);
        assert_return_code(ret, errno);
 
        free(paths);
@@ -217,6 +296,30 @@ static void test_directory_create_or_exists_symlink_file(void **state)
        assert_true(S_ISLNK(sbuf.st_mode));
 }
 
+static void test_directory_create_or_exists_recursive(void **state)
+{
+       struct test_paths *paths = *state;
+       char recursive_testdir[PATH_MAX] = {0};
+       struct stat sbuf = {0};
+       bool ok;
+       int ret;
+
+       ret = snprintf(recursive_testdir,
+                      sizeof(recursive_testdir),
+                      "%s/wurst/brot",
+                      paths->dir_recursive);
+       assert_int_not_equal(ret, -1);
+
+       ok = directory_create_or_exists_recursive(recursive_testdir,
+                                                 0700);
+       assert_true(ok);
+
+       ret = lstat(recursive_testdir, &sbuf);
+       assert_return_code(ret, errno);
+       assert_int_equal(sbuf.st_mode & 0777, 0700);
+       assert_true(S_ISDIR(sbuf.st_mode));
+}
+
 int main(int argc, char **argv)
 {
        const struct CMUnitTest tests[] = {
@@ -226,6 +329,7 @@ int main(int argc, char **argv)
                cmocka_unit_test(test_directory_create_or_exists_symlink_none),
                cmocka_unit_test(test_directory_create_or_exists_symlink_dir),
                cmocka_unit_test(test_directory_create_or_exists_symlink_file),
+               cmocka_unit_test(test_directory_create_or_exists_recursive),
        };
 
        cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
index 52fc61a3e810483e69bd2e09bfa54e637de8ed2e..0c6a06e4ff22f240f50ada8318ff817191e9f7a8 100644 (file)
@@ -35,6 +35,7 @@
 #include "debug.h"
 #include "samba_util.h"
 #include "lib/util/select.h"
+#include <libgen.h>
 
 #undef malloc
 #undef strcasecmp
@@ -394,6 +395,45 @@ _PUBLIC_ bool directory_create_or_exist(const char *dname,
        return true;
 }
 
+_PUBLIC_ bool directory_create_or_exists_recursive(
+               const char *dname,
+               mode_t dir_perms)
+{
+       bool ok;
+
+       ok = directory_create_or_exist(dname, dir_perms);
+       if (!ok) {
+               if (!directory_exist(dname)) {
+                       char tmp[PATH_MAX] = {0};
+                       char *parent = NULL;
+                       size_t n;
+
+                       /* Use the null context */
+                       n = strlcpy(tmp, dname, sizeof(tmp));
+                       if (n < strlen(dname)) {
+                               DBG_ERR("Path too long!\n");
+                               return false;
+                       }
+
+                       parent = dirname(tmp);
+                       if (parent == NULL) {
+                               DBG_ERR("Failed to create dirname!\n");
+                               return false;
+                       }
+
+                       ok = directory_create_or_exists_recursive(parent,
+                                                                 dir_perms);
+                       if (!ok) {
+                               return false;
+                       }
+
+                       ok = directory_create_or_exist(dname, dir_perms);
+               }
+       }
+
+       return ok;
+}
+
 /**
  * @brief Try to create a specified directory if it doesn't exist.
  *