Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions source/MaterialXFormat/File.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdlib.h>
#endif

#if defined(__linux__)
Expand All @@ -32,6 +33,10 @@
#include <cerrno>
#include <cstring>

#if defined(_WIN32)
#include <random>
#endif

MATERIALX_NAMESPACE_BEGIN

const string VALID_SEPARATORS = "/\\";
Expand All @@ -49,6 +54,15 @@ const string PATH_LIST_SEPARATOR = ":";
#endif
const string MATERIALX_SEARCH_PATH_ENV_VAR = "MATERIALX_SEARCH_PATH";

const string TEMPORARY_DIRECTORY_PREFIX = "materialx_tmp_";

#if defined(_WIN32)
const int TEMPORARY_DIRECTORY_MAX_ATTEMPTS = 1000;
#else
const StringVec TEMPORARY_DIRECTORY_ENV_VARS = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" };
const string TEMPORARY_DIRECTORY_DEFAULT = "/tmp";
#endif

inline bool hasWindowsDriveSpecifier(const string& val)
{
return (val.length() > 1 && std::isalpha((unsigned char) val[0]) && (val[1] == ':'));
Expand Down Expand Up @@ -301,6 +315,77 @@ void FilePath::createDirectory(bool recursive) const
#endif
}

bool FilePath::removeDirectory(bool recursive) const
{
if (recursive)
{
// Remove the contents of the directory, taking care not to follow symbolic
// links to locations outside of this directory.
#if defined(_WIN32)
WIN32_FIND_DATAA fd;
HANDLE hFind = FindFirstFileA((*this / "*").asString().c_str(), &fd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
string entryName = fd.cFileName;
if (entryName == CURRENT_PATH_STRING || entryName == PARENT_PATH_STRING)
{
continue;
}

FilePath entryPath = *this / entryName;
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
!(fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
{
entryPath.removeDirectory(true);
}
else if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
RemoveDirectoryA(entryPath.asString().c_str());
}
else
{
DeleteFileA(entryPath.asString().c_str());
}
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
}
#else
DIR* dir = opendir(asString().c_str());
if (dir)
{
while (struct dirent* entry = readdir(dir))
{
string entryName = entry->d_name;
if (entryName == CURRENT_PATH_STRING || entryName == PARENT_PATH_STRING)
{
continue;
}

FilePath entryPath = *this / entryName;
struct stat sb;
if (lstat(entryPath.asString().c_str(), &sb) == 0 && S_ISDIR(sb.st_mode))
{
entryPath.removeDirectory(true);
}
else
{
unlink(entryPath.asString().c_str());
}
}
closedir(dir);
}
#endif
}

#if defined(_WIN32)
return (RemoveDirectoryA(asString().c_str()) != 0);
#else
return (rmdir(asString().c_str()) == 0);
#endif
}

bool FilePath::setCurrentPath()
{
#if defined(_WIN32)
Expand Down Expand Up @@ -385,6 +470,75 @@ FilePath FilePath::getModulePath()
#endif
}

FilePath FilePath::createTemporaryDirectory(const FilePath& parentDir)
{
FilePath tempParent = parentDir.isEmpty() ? getSystemTemporaryDirectory() : parentDir;
tempParent.createDirectory(true);
if (!tempParent.isDirectory())
{
throw Exception("Error in createTemporaryDirectory: parent path is not a directory: " + tempParent.asString());
}

#if defined(_WIN32)
// Windows provides no atomic equivalent of mkdtemp, so generate candidate names
// until CreateDirectory succeeds. Because CreateDirectory fails rather than
// succeeding when the path already exists, no other process can be given the
// same directory.
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dis(100000, 999999);

for (int attempt = 0; attempt < TEMPORARY_DIRECTORY_MAX_ATTEMPTS; attempt++)
{
FilePath tempDir = tempParent / (TEMPORARY_DIRECTORY_PREFIX + std::to_string(dis(gen)));
if (CreateDirectoryA(tempDir.asString().c_str(), nullptr))
{
return tempDir;
}
if (GetLastError() != ERROR_ALREADY_EXISTS)
{
throw Exception("Error in createTemporaryDirectory: " + std::to_string(GetLastError()));
}
}

throw Exception("Error in createTemporaryDirectory: no unique name found in " + tempParent.asString());
#else
// Create the directory with mkdtemp, which generates a unique name, creates the
// directory atomically, and restricts access to the current user.
string nameTemplate = (tempParent / (TEMPORARY_DIRECTORY_PREFIX + "XXXXXX")).asString();
vector<char> buf(nameTemplate.begin(), nameTemplate.end());
buf.push_back('\0');
if (!mkdtemp(buf.data()))
{
throw Exception("Error in createTemporaryDirectory: " + string(strerror(errno)));
}
return FilePath(buf.data());
#endif
}

FilePath FilePath::getSystemTemporaryDirectory()
{
#if defined(_WIN32)
std::array<char, MAX_PATH + 1> buf;
DWORD length = GetTempPathA((DWORD) buf.size(), buf.data());
if (!length || length > buf.size())
{
throw Exception("Error in getSystemTemporaryDirectory: " + std::to_string(GetLastError()));
}
return FilePath(string(buf.data(), length));
#else
for (const string& envVar : TEMPORARY_DIRECTORY_ENV_VARS)
{
string tempDir = getEnviron(envVar);
if (!tempDir.empty())
{
return FilePath(tempDir);
}
}
return FilePath(TEMPORARY_DIRECTORY_DEFAULT);
#endif
}

FileSearchPath getEnvironmentPath(const string& sep)
{
string searchPathEnv = getEnviron(MATERIALX_SEARCH_PATH_ENV_VAR);
Expand Down
22 changes: 22 additions & 0 deletions source/MaterialXFormat/File.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ class MX_FORMAT_API FilePath
/// If recursive is true, any missing parent directories will be created as well.
void createDirectory(bool recursive = false) const;

/// Remove the directory on the file system at the given path.
/// @param recursive If true, the contents of the directory will be removed as
/// well; otherwise the directory is only removed if it is empty. Symbolic
/// links within the directory are removed without following their targets.
/// @return True if the directory was removed.
bool removeDirectory(bool recursive = false) const;

/// Set the current working directory of the file system.
bool setCurrentPath();

Expand All @@ -209,6 +216,21 @@ class MX_FORMAT_API FilePath
/// Return the directory containing the executable module.
static FilePath getModulePath();

/// Create a temporary directory with a unique name.
/// @param parentDir The parent directory for the temporary directory, which
/// will be created if it does not already exist. If empty, the system's
/// default temporary directory is used.
/// @return A FilePath to the created temporary directory, which is accessible
/// only to the current user on platforms that support this restriction.
/// @throws Exception if the temporary directory cannot be created.
static FilePath createTemporaryDirectory(const FilePath& parentDir = FilePath());

/// Return the system's default temporary directory. This method does not
/// create the directory, and its existence is not guaranteed.
/// @return A FilePath to the system temporary directory.
/// @throws Exception if the system temporary directory cannot be determined.
static FilePath getSystemTemporaryDirectory();

private:
StringVec _vec;
Type _type;
Expand Down
8 changes: 8 additions & 0 deletions source/MaterialXFormat/XmlIo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,15 @@ void writeToXmlStream(DocumentPtr doc, std::ostream& stream, const XmlWriteOptio

void writeToXmlFile(DocumentPtr doc, const FilePath& filename, const XmlWriteOptions* writeOptions)
{
if (writeOptions && writeOptions->createDirectories)
{
filename.getParentPath().createDirectory(true);
}
std::ofstream ofs(filename.asString());
if (!ofs)
{
throw ExceptionFileMissing("Failed to open file for writing: " + filename.asString());
}
writeToXmlStream(doc, ofs, writeOptions);
}

Expand Down
5 changes: 5 additions & 0 deletions source/MaterialXFormat/XmlIo.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class MX_FORMAT_API XmlWriteOptions
/// If provided, this function will be used to exclude specific elements
/// (those returning false) from the write operation. Defaults to nullptr.
ElementPredicate elementPredicate;

/// If true, any necessary directories will be created to write the
/// file.
bool createDirectories = false;
};

/// @class ExceptionParseError
Expand Down Expand Up @@ -170,6 +174,7 @@ MX_FORMAT_API void writeToXmlStream(DocumentPtr doc, std::ostream& stream, const
/// @param writeOptions An optional pointer to an XmlWriteOptions object.
/// If provided, then the given options will affect the behavior of the
/// write function. Defaults to a null pointer.
/// @throws ExceptionFileMissing if the file cannot be opened for writing.
MX_FORMAT_API void writeToXmlFile(DocumentPtr doc, const FilePath& filename, const XmlWriteOptions* writeOptions = nullptr);

/// Write a Document as XML to a new string, returned by value.
Expand Down
15 changes: 14 additions & 1 deletion source/MaterialXGenOsl/LibsToOso.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,20 @@ int main(int argc, char* const argv[])
{
// We only write the MaterialX document containing the implementations out if requested.
mx::FilePath implMtlxDocFilePath = outputMtlxPath / "genoslnetwork_impl.mtlx";
mx::writeToXmlFile(implMtlxDoc, implMtlxDocFilePath);
try
{
mx::writeToXmlFile(implMtlxDoc, implMtlxDocFilePath);
}
// Catch any file writing related exceptions.
catch (mx::Exception& exc)
{
std::cerr << "Encountered an exception while writing the implementation document to the "
"following path: "
<< implMtlxDocFilePath.asString() << std::endl;
std::cerr << exc.what() << std::endl;

hasFailed = true;
}
}

// If something went wrong, return an appropriate error code.
Expand Down
9 changes: 8 additions & 1 deletion source/MaterialXGraphEditor/Graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5074,5 +5074,12 @@ void Graph::saveDocument(mx::FilePath filePath)

mx::XmlWriteOptions writeOptions;
writeOptions.elementPredicate = getElementPredicate();
mx::writeToXmlFile(writeDoc, filePath, &writeOptions);
try
{
mx::writeToXmlFile(writeDoc, filePath, &writeOptions);
}
catch (mx::Exception& e)
{
std::cerr << "Failed to write file: " << filePath.asString() << ": \"" << std::string(e.what()) << "\"" << std::endl;
}
}
Loading
Loading