/************************************************************************* ** FileSystem.cpp ** ** ** ** This file is part of dvisvgm -- a fast DVI to SVG converter ** ** Copyright (C) 2005-2024 Martin Gieseking ** ** ** ** 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 the Free Software Foundation; either version 3 of ** ** the License, or (at your option) any later version. ** ** ** ** This program is distributed in the hope that it will be useful, but ** ** WITHOUT ANY WARRANTY; without even the implied warranty of ** ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** ** GNU General Public License for more details. ** ** ** ** You should have received a copy of the GNU General Public License ** ** along with this program; if not, see . ** *************************************************************************/ #include #include #include #include #include #include #include "FileSystem.hpp" #include "utility.hpp" #include "version.hpp" #include "XXHashFunction.hpp" #ifdef HAVE_UNISTD_H #include #endif #ifdef _WIN32 #include #include "windows.hpp" const char *FileSystem::DEVNULL = "nul"; const char FileSystem::PATHSEP = '\\'; #define unlink _unlink #else #include #include #include #include const char *FileSystem::DEVNULL = "/dev/null"; const char FileSystem::PATHSEP = '/'; #endif using namespace std; string FileSystem::TMPDIR; FileSystem::TemporaryDirectory FileSystem::_tmpdir; /** Private wrapper function for mkdir: creates a single folder. * @param[in] dir folder name * @return true on success */ static bool s_mkdir (const string &dirname) { bool success = true; if (!FileSystem::exists(dirname)) { #ifdef _WIN32 success = (_mkdir(dirname.c_str()) == 0); #else success = (::mkdir(dirname.c_str(), 0775) == 0); #endif } return success; } static bool inline s_rmdir (const string &dirname) { #ifdef _WIN32 return (_rmdir(dirname.c_str()) == 0); #else return (::rmdir(dirname.c_str()) == 0); #endif } bool FileSystem::remove (const string &fname) { return unlink(fname.c_str()) == 0; } /** Copies a file. * @param[in] src path of file to copy * @param[in] dest path of target file * @param[in] remove_src remove file 'src' if true * @return true on success */ bool FileSystem::copy (const string &src, const string &dest, bool remove_src) { ifstream ifs(src, ios::in|ios::binary); ofstream ofs(dest, ios::out|ios::binary); if (ifs && ofs) { ofs << ifs.rdbuf(); if (!ifs.fail() && !ofs.fail() && remove_src) { ofs.close(); ifs.close(); return remove(src); } else return !remove_src; } return false; } bool FileSystem::rename (const string &oldname, const string &newname) { return ::rename(oldname.c_str(), newname.c_str()) == 0; } uint64_t FileSystem::filesize (const string &fname) { #ifdef _WIN32 // unfortunately, stat doesn't work properly under Windows // so we have to use this freaky code WIN32_FILE_ATTRIBUTE_DATA attr; GetFileAttributesExA(fname.c_str(), GetFileExInfoStandard, &attr); return (static_cast(attr.nFileSizeHigh) << (8*sizeof(attr.nFileSizeLow))) | attr.nFileSizeLow; #else struct stat attr; return (stat(fname.c_str(), &attr) == 0) ? attr.st_size : 0; #endif } string FileSystem::ensureForwardSlashes (string path) { #ifdef _WIN32 std::replace(path.begin(), path.end(), PATHSEP, '/'); #endif return path; } string FileSystem::ensureSystemSlashes (string path) { #ifdef _WIN32 std::replace(path.begin(), path.end(), '/', PATHSEP); #endif return path; } /** Returns the absolute path of the current working directory. */ string FileSystem::getcwd () { char buf[1024]; #ifdef _WIN32 GetCurrentDirectoryA(1024, buf); return ensureForwardSlashes(buf); #else return ::getcwd(buf, 1024); #endif } #ifdef _WIN32 /** Returns the absolute path of the current directory of a given drive. * Windows keeps a current directory for every drive, i.e. when accessing a drive * without specifying a path (e.g. with "cd z:"), the current directory of that * drive is used. * @param[in] drive letter of drive to get the current directory from * @return absolute path of the directory */ string FileSystem::getcwd (char drive) { string cwd = getcwd(); if (cwd.length() > 1 && cwd[1] == ':' && tolower(cwd[0]) != tolower(drive)) { chdir(string(1, drive)+":"); string cwd2 = cwd; cwd = getcwd(); chdir(string(1, cwd2[0])+":"); } return cwd; } #endif /** Changes the work directory. * @param[in] dir path to new work directory * @return true on success */ bool FileSystem::chdir (const std::string &dirname) { bool success = false; if (const char *cdirname = dirname.c_str()) { #ifdef _WIN32 success = (_chdir(cdirname) == 0); #else success = (::chdir(cdirname) == 0); #endif } return success; } /** Returns the user's home directory. */ const char* FileSystem::userdir () { #ifdef _WIN32 const char *drive=getenv("HOMEDRIVE"); const char *path=getenv("HOMEPATH"); if (drive && path) { static string ret = string(drive)+path; if (!ret.empty()) return ret.c_str(); } return nullptr; #else const char *dir=getenv("HOME"); if (!dir) { if (const char *user=getenv("USER")) { if (struct passwd *pw=getpwnam(user)) dir = pw->pw_dir; } } return dir; #endif } /** Returns the path of the temporary folder. * @param[in] inpace if true, don't create a uniquely named subfolder */ string FileSystem::tmpdir (bool inplace) { if (_tmpdir.path().empty()) { string basedir; if (!TMPDIR.empty()) basedir = ensureForwardSlashes(TMPDIR); else { #ifdef _WIN32 char buf[MAX_PATH]; if (GetTempPath(MAX_PATH, buf)) basedir = ensureForwardSlashes(buf); else basedir = "."; #else if (const char *path = getenv("TMPDIR")) basedir = path; else basedir = "/tmp"; #endif } if (basedir.length() > 2 && string(basedir.end()-2, basedir.end()) == "//") { inplace = true; basedir.pop_back(); } if (basedir.front() != '/' && basedir.back() == '/') basedir.pop_back(); _tmpdir = TemporaryDirectory(basedir, PROGRAM_NAME, inplace); } return _tmpdir.path(); } /** Creates a new folder relative to the current work directory. If necessary, * the parent folders are also created. * @param[in] dir single folder name or path to folder * @return true if folder(s) could be created */ bool FileSystem::mkdir (const string &dirname) { bool success = false; if (const char *cdirname = dirname.c_str()) { success = true; const string dirstr = ensureForwardSlashes(util::trim(cdirname)); for (size_t pos=1; success && (pos = dirstr.find('/', pos)) != string::npos; pos++) success &= s_mkdir(dirstr.substr(0, pos)); success &= s_mkdir(dirstr); } return success; } /** Removes a directory and its contents. * @param[in] dirname path to directory * @return true on success */ bool FileSystem::rmdir (const string &dirname) { bool ok = false; if (isDirectory(dirname)) { ok = true; #ifdef _WIN32 string pattern = dirname + "/*"; WIN32_FIND_DATA data; HANDLE h = FindFirstFile(pattern.c_str(), &data); bool ready = (h == INVALID_HANDLE_VALUE); while (!ready && ok) { const char *fname = data.cFileName; string path = dirname + "/" + fname; if (isDirectory(path)) { if (strcmp(fname, ".") != 0 && strcmp(fname, "..") != 0) ok = rmdir(path) && s_rmdir(path); } else if (isFile(path)) ok = remove(path); else ok = false; ready = !FindNextFile(h, &data); } FindClose(h); #else if (DIR *dir = opendir(dirname.c_str())) { struct dirent *ent; while ((ent = readdir(dir)) && ok) { const char *fname = ent->d_name; string path = dirname + "/" + fname; if (isDirectory(path)) { if (strcmp(fname, ".") != 0 && strcmp(fname, "..") != 0) ok = rmdir(path) && s_rmdir(path); } else if (isFile(path)) ok = remove(path); else ok = false; } closedir(dir); } #endif ok = s_rmdir(dirname); } return ok; } /** Checks if a file or directory exits. */ bool FileSystem::exists (const string &fname) { if (const char *cfname = fname.c_str()) { #ifdef _WIN32 return GetFileAttributes(cfname) != INVALID_FILE_ATTRIBUTES; #else struct stat attr; return stat(cfname, &attr) == 0; #endif } return false; } /** Returns true if 'fname' references a directory. */ bool FileSystem::isDirectory (const string &fname) { if (const char *cfname = fname.c_str()) { #ifdef _WIN32 auto attr = GetFileAttributes(cfname); return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY); #else struct stat attr; return stat(cfname, &attr) == 0 && S_ISDIR(attr.st_mode); #endif } return false; } /** Returns true if 'fname' references a file. */ bool FileSystem::isFile (const string &fname) { if (const char *cfname = fname.c_str()) { #ifdef _WIN32 ifstream ifs(cfname); return (bool)ifs; #else struct stat attr; return stat(cfname, &attr) == 0 && S_ISREG(attr.st_mode); #endif } return false; } int FileSystem::collect (const std::string &dirname, vector &entries) { entries.clear(); #ifdef _WIN32 string pattern = dirname + "/*"; WIN32_FIND_DATA data; HANDLE h = FindFirstFile(pattern.c_str(), &data); bool ready = (h == INVALID_HANDLE_VALUE); while (!ready) { string fname = data.cFileName; string path = dirname+"/"+fname; string typechar = isFile(path) ? "f" : isDirectory(path) ? "d" : "?"; if (fname != "." && fname != "..") entries.emplace_back(typechar+fname); ready = !FindNextFile(h, &data); } FindClose(h); #else if (DIR *dir = opendir(dirname.c_str())) { struct dirent *ent; while ((ent = readdir(dir))) { string fname = ent->d_name; string path = dirname+"/"+fname; string typechar = isFile(path) ? "f" : isDirectory(path) ? "d" : "?"; if (fname != "." && fname != "..") entries.emplace_back(typechar+fname); } closedir(dir); } #endif return entries.size(); } /** Creates a temporary directory in a given folder or treats the given folder as temporary directory. * @param[in] folder folder path in which the directory is to be created * @param[in] prefix initial string of the directory name * @param[in] inplace if true, 'folder' is treated as temporary directory and no subfolder is created */ FileSystem::TemporaryDirectory::TemporaryDirectory (const std::string &folder, string prefix, bool inplace) { if (inplace) { _path = folder; if (!_path.empty() && _path.back() != '/') _path.push_back('/'); } else { using namespace std::chrono; auto now = system_clock::now().time_since_epoch(); auto now_ms = duration_cast(now).count(); auto hash = XXH64HashFunction(to_string(now_ms)).digestValue(); if (!prefix.empty() && prefix.back() != '-') prefix.push_back('-'); for (int i = 0; i < 10 && _path.empty(); i++) { hash++; stringstream oss; oss << folder << '/' << prefix << hex << hash; if (exists(oss.str())) continue; if (s_mkdir(oss.str())) _path = oss.str() + "/"; else break; } } } FileSystem::TemporaryDirectory::~TemporaryDirectory () { if (!_path.empty()) s_rmdir(_path); }