From 600d4967269d994babe1c9078a3e2560d7f59798 Mon Sep 17 00:00:00 2001 From: Yaqi Wang Date: Mon, 23 Nov 2020 16:36:31 -0700 Subject: [PATCH] create directory recursively when making dir for profiling files, patch for #16277 --- framework/include/utils/MooseUtils.h | 20 +++++ framework/src/base/MooseApp.C | 9 +- framework/src/utils/MooseUtils.C | 120 +++++++++++++++++++++++++++ unit/src/MooseUtilsTest.C | 48 +++++++++++ 4 files changed, 192 insertions(+), 5 deletions(-) diff --git a/framework/include/utils/MooseUtils.h b/framework/include/utils/MooseUtils.h index 213560eae8b7..cdcac17455f7 100644 --- a/framework/include/utils/MooseUtils.h +++ b/framework/include/utils/MooseUtils.h @@ -193,6 +193,26 @@ std::string stripExtension(const std::string & s); */ std::pair splitFileName(std::string full_file); +/** + * Recursively make directories + * @param dir_name A complete path + * @param throw_on_failure True to throw instead of error out when creating a directory is failed. + * + * The path can be relative like 'a/b/c' or absolute like '/a/b/c'. + * The path is allowed to contain '.' or '..'. + */ +void makedirs(const std::string & dir_name, bool throw_on_failure = false); + +/** + * Recursively remove directories from inner-most when the directories are empty + * @param dir_name A complete path + * @param throw_on_failure True to throw instead of error out when deleting a directory is failed. + * + * The path can be relative like 'a/b/c' or absolute like '/a/b/c'. + * The path is allowed to contain '.' or '..'. + */ +void removedirs(const std::string & dir_name, bool throw_on_failure = false); + /** * Function for converting a camel case name to a name containing underscores. * @param camel_case_name A string containing camel casing diff --git a/framework/src/base/MooseApp.C b/framework/src/base/MooseApp.C index 738fd1c796c7..9edea0f79967 100644 --- a/framework/src/base/MooseApp.C +++ b/framework/src/base/MooseApp.C @@ -54,7 +54,6 @@ #include "libmesh/string_to_enum.h" #include "libmesh/checkpoint_io.h" #include "libmesh/mesh_base.h" -#include "libmesh/utility.h" // System include for dynamic library methods #ifdef LIBMESH_HAVE_DLOPEN @@ -360,8 +359,8 @@ MooseApp::MooseApp(InputParameters parameters) _cpu_profiling = true; auto name = MooseUtils::splitFileName(profile_file); - if (!name.first.empty()) - Utility::mkdir(name.first.c_str()); + if (!name.first.empty() && processor_id() == 0) + MooseUtils::makedirs(name.first.c_str()); if (!ProfilerStart(profile_file.c_str())) mooseError("CPU profiler is not started properly"); @@ -376,8 +375,8 @@ MooseApp::MooseApp(InputParameters parameters) _heap_profiling = true; auto name = MooseUtils::splitFileName(profile_file); - if (!name.first.empty()) - Utility::mkdir(name.first.c_str()); + if (!name.first.empty() && processor_id() == 0) + MooseUtils::makedirs(name.first.c_str()); HeapProfilerStart(profile_file.c_str()); if (!IsHeapProfilerRunning()) diff --git a/framework/src/utils/MooseUtils.C b/framework/src/utils/MooseUtils.C index ef71319e2ac9..0061d1f2a1b8 100644 --- a/framework/src/utils/MooseUtils.C +++ b/framework/src/utils/MooseUtils.C @@ -16,6 +16,7 @@ #include "ExecFlagEnum.h" #include "InfixIterator.h" +#include "libmesh/utility.h" #include "libmesh/elem.h" // External includes @@ -337,6 +338,125 @@ splitFileName(std::string full_file) return std::pair(path, file); } +void +makedirs(const std::string & dir_name, bool throw_on_failure) +{ + // split path into directories with delimiter '/' + std::vector split_dir_names; + MooseUtils::tokenize(dir_name, split_dir_names); + + auto n = split_dir_names.size(); + + // remove '.' and '..' when possible + auto i = n; + i = 0; + while (i != n) + { + if (split_dir_names[i] == ".") + { + for (auto j = i + 1; j < n; ++j) + split_dir_names[j - 1] = split_dir_names[j]; + --n; + } + else if (i > 0 && split_dir_names[i] == ".." && split_dir_names[i - 1] != "..") + { + for (auto j = i + 1; j < n; ++j) + split_dir_names[j - 2] = split_dir_names[j]; + n -= 2; + --i; + } + else + ++i; + } + if (n == 0) + return; + + split_dir_names.resize(n); + + // start creating directories recursively + std::string cur_dir = dir_name[0] == '/' ? "" : "."; + for (auto & dir : split_dir_names) + { + cur_dir += "/" + dir; + + if (!pathExists(cur_dir)) + { + auto code = Utility::mkdir(cur_dir.c_str()); + if (code != 0) + { + std::string msg = "Failed creating directory " + dir_name; + if (throw_on_failure) + throw std::invalid_argument(msg); + else + mooseError(msg); + } + } + } +} + +void +removedirs(const std::string & dir_name, bool throw_on_failure) +{ + // split path into directories with delimiter '/' + std::vector split_dir_names; + MooseUtils::tokenize(dir_name, split_dir_names); + + auto n = split_dir_names.size(); + + // remove '.' and '..' when possible + auto i = n; + i = 0; + while (i != n) + { + if (split_dir_names[i] == ".") + { + for (auto j = i + 1; j < n; ++j) + split_dir_names[j - 1] = split_dir_names[j]; + --n; + } + else if (i > 0 && split_dir_names[i] == ".." && split_dir_names[i - 1] != "..") + { + for (auto j = i + 1; j < n; ++j) + split_dir_names[j - 2] = split_dir_names[j]; + n -= 2; + --i; + } + else + ++i; + } + if (n == 0) + return; + + split_dir_names.resize(n); + + // start removing directories recursively + std::string base_dir = dir_name[0] == '/' ? "" : "."; + for (i = n; i > 0; --i) + { + std::string cur_dir = base_dir; + auto j = i; + for (j = 0; j < i; ++j) + cur_dir += "/" + split_dir_names[j]; + + // listDir should return at least '.' and '..' + if (pathExists(cur_dir) && listDir(cur_dir).size() == 2) + { + auto code = rmdir(cur_dir.c_str()); + if (code != 0) + { + std::string msg = "Failed removing directory " + dir_name; + if (throw_on_failure) + throw std::invalid_argument(msg); + else + mooseError(msg); + } + } + else + // stop removing + break; + } +} + std::string camelCaseToUnderscore(const std::string & camel_case_name) { diff --git a/unit/src/MooseUtilsTest.C b/unit/src/MooseUtilsTest.C index f1ff4b12d8e0..5eba06f7e98b 100644 --- a/unit/src/MooseUtilsTest.C +++ b/unit/src/MooseUtilsTest.C @@ -363,3 +363,51 @@ TEST(MooseUtils, realpath) // ok as long mooseError is not triggered MooseUtils::realpath("data/example_file"); } + +TEST(MooseUtils, directory) +{ + std::string path; + + path = "a/b/c"; + MooseUtils::makedirs(path); + EXPECT_TRUE(MooseUtils::pathExists(path)); + MooseUtils::removedirs(path); + EXPECT_FALSE(MooseUtils::pathExists(path)); + + // mkdir for an existing directory + path = "a/b/c"; + MooseUtils::makedirs(path); + MooseUtils::makedirs(path); + EXPECT_TRUE(MooseUtils::pathExists(path)); + MooseUtils::removedirs(path); + EXPECT_FALSE(MooseUtils::pathExists(path)); + MooseUtils::removedirs(path); + EXPECT_FALSE(MooseUtils::pathExists(path)); + + // test .. + path = "no_dir_name_like_this/../../b/c"; + MooseUtils::makedirs(path); + EXPECT_TRUE(MooseUtils::pathExists("../b/c")); + MooseUtils::removedirs(path); + EXPECT_FALSE(MooseUtils::pathExists("../b/c")); + EXPECT_FALSE(MooseUtils::pathExists(path)); + + // test . + path = "./b/c"; + MooseUtils::makedirs(path); + EXPECT_TRUE(MooseUtils::pathExists("b/c")); + MooseUtils::removedirs(path); + EXPECT_FALSE(MooseUtils::pathExists("b/c")); + EXPECT_FALSE(MooseUtils::pathExists(path)); + + // test absolute path + path = "a/b/c"; + MooseUtils::makedirs(path); + std::string rpath = MooseUtils::realpath(path); + MooseUtils::removedirs(path); + MooseUtils::makedirs(rpath); + EXPECT_TRUE(MooseUtils::pathExists(path)); + MooseUtils::removedirs(rpath); + EXPECT_FALSE(MooseUtils::pathExists(path)); + EXPECT_THROW(MooseUtils::makedirs("/should_not_access", true), std::invalid_argument); +}