summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/libssh/misc.h3
-rw-r--r--src/misc.c75
-rw-r--r--tests/unittests/torture_misc.c93
3 files changed, 166 insertions, 5 deletions
diff --git a/include/libssh/misc.h b/include/libssh/misc.h
index a6108fcc..543de04f 100644
--- a/include/libssh/misc.h
+++ b/include/libssh/misc.h
@@ -85,4 +85,7 @@ int ssh_match_group(const char *group, const char *object);
void uint64_inc(unsigned char *counter);
void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len);
+
+int ssh_mkdirs(const char *pathname, mode_t mode);
+
#endif /* MISC_H_ */
diff --git a/src/misc.c b/src/misc.c
index 19bf28c9..8bd378f3 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -950,16 +950,81 @@ char *ssh_basename (const char *path) {
*
* @return 0 on success, < 0 on error with errno set.
*/
-int ssh_mkdir(const char *pathname, mode_t mode) {
- int r;
+int ssh_mkdir(const char *pathname, mode_t mode)
+{
+ int r;
+#ifdef _WIN32
+ r = _mkdir(pathname);
+#else
+ r = mkdir(pathname, mode);
+#endif
+ return r;
+}
+
+/**
+ * @brief Attempts to create a directory with the given pathname. The missing
+ * directories in the given pathname are created recursively.
+ *
+ * @param[in] pathname The path name to create the directory.
+ *
+ * @param[in] mode The permissions to use.
+ *
+ * @return 0 on success, < 0 on error with errno set.
+ *
+ * @note mode is ignored on Windows systems.
+ */
+int ssh_mkdirs(const char *pathname, mode_t mode)
+{
+ int rc = 0;
+ char *parent = NULL;
+
+ if (pathname == NULL ||
+ pathname[0] == '\0' ||
+ !strcmp(pathname, "/") ||
+ !strcmp(pathname, "."))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ errno = 0;
+
+#ifdef _WIN32
+ rc = _mkdir(pathname);
+#else
+ rc = mkdir(pathname, mode);
+#endif
+
+ if (rc < 0) {
+ /* If a directory was missing, try to create the parent */
+ if (errno == ENOENT) {
+ parent = ssh_dirname(pathname);
+ if (parent == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ rc = ssh_mkdirs(parent, mode);
+ if (rc < 0) {
+ /* We could not create the parent */
+ SAFE_FREE(parent);
+ return -1;
+ }
+
+ SAFE_FREE(parent);
+
+ /* Try again */
+ errno = 0;
#ifdef _WIN32
- r = _mkdir(pathname);
+ rc = _mkdir(pathname);
#else
- r = mkdir(pathname, mode);
+ rc = mkdir(pathname, mode);
#endif
+ }
+ }
- return r;
+ return rc;
}
/**
diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c
index 1f003321..2d628359 100644
--- a/tests/unittests/torture_misc.c
+++ b/tests/unittests/torture_misc.c
@@ -409,6 +409,98 @@ static void torture_ssh_dir_writeable(UNUSED_PARAM(void **state))
SAFE_FREE(tmp_dir);
}
+static void torture_ssh_mkdirs(UNUSED_PARAM(void **state))
+{
+ char *tmp_dir = NULL;
+ char *cwd = NULL;
+ char buffer[256];
+
+ ssize_t count = 0;
+
+ int rc;
+
+ /* Get current working directory */
+ cwd = torture_get_current_working_dir();
+ assert_non_null(cwd);
+
+ /* Create a base disposable directory */
+ tmp_dir = torture_make_temp_dir(template);
+ assert_non_null(tmp_dir);
+
+ /* Create a single directory */
+ count = snprintf(buffer, sizeof(buffer), "%s/a", tmp_dir);
+ assert_return_code(count, errno);
+
+ rc = ssh_mkdirs(buffer, 0700);
+ assert_return_code(rc, errno);
+
+ rc = ssh_dir_writeable(buffer);
+ assert_int_equal(rc, 1);
+
+ /* Create directories recursively */
+ count = snprintf(buffer, sizeof(buffer), "%s/b/c/d", tmp_dir);
+ assert_return_code(count, errno);
+
+ rc = ssh_mkdirs(buffer, 0700);
+ assert_return_code(rc, errno);
+
+ rc = ssh_dir_writeable(buffer);
+ assert_int_equal(rc, 1);
+
+ /* Change directory */
+ rc = torture_change_dir(tmp_dir);
+ assert_return_code(rc, errno);
+
+ /* Create single local directory */
+ rc = ssh_mkdirs("e", 0700);
+ assert_return_code(rc, errno);
+
+ rc = ssh_dir_writeable("e");
+ assert_int_equal(rc, 1);
+
+ /* Create local directories recursively */
+ rc = ssh_mkdirs("f/g/h", 0700);
+ assert_return_code(rc, errno);
+
+ rc = ssh_dir_writeable("f/g/h");
+ assert_int_equal(rc, 1);
+
+ /* Negative test for creating "." directory */
+ rc = ssh_mkdirs(".", 0700);
+ assert_int_equal(rc, -1);
+ assert_int_equal(errno, EINVAL);
+
+ /* Negative test for creating "/" directory */
+ rc = ssh_mkdirs("/", 0700);
+ assert_int_equal(rc, -1);
+ assert_int_equal(errno, EINVAL);
+
+ /* Negative test for creating "" directory */
+ rc = ssh_mkdirs("", 0700);
+ assert_int_equal(rc, -1);
+ assert_int_equal(errno, EINVAL);
+
+ /* Negative test for creating NULL directory */
+ rc = ssh_mkdirs(NULL, 0700);
+ assert_int_equal(rc, -1);
+ assert_int_equal(errno, EINVAL);
+
+ /* Negative test for creating existing directory */
+ rc = ssh_mkdirs("a", 0700);
+ assert_int_equal(rc, -1);
+ assert_int_equal(errno, EEXIST);
+
+ /* Return to original directory */
+ rc = torture_change_dir(cwd);
+ assert_return_code(rc, errno);
+
+ /* Cleanup */
+ torture_rmdirs(tmp_dir);
+
+ SAFE_FREE(tmp_dir);
+ SAFE_FREE(cwd);
+}
+
int torture_run_tests(void) {
int rc;
struct CMUnitTest tests[] = {
@@ -428,6 +520,7 @@ int torture_run_tests(void) {
cmocka_unit_test(torture_timeout_update),
cmocka_unit_test(torture_ssh_analyze_banner),
cmocka_unit_test(torture_ssh_dir_writeable),
+ cmocka_unit_test(torture_ssh_mkdirs),
};
ssh_init();