// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Os-specific function wrapper // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2025 by Wilson Snyder. This program is free software; you // can redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // clang-format off #if defined(_WIN32) || defined(__MINGW32__) # ifndef PSAPI_VERSION # define PSAPI_VERSION 1 // Needed for compatibility with Windows 7 # endif #endif #if defined(__MINGW32__) # define MINGW_HAS_SECURE_API 1 // Needed to expose a "secure" POSIX-like API #endif // clang-format on #include "config_build.h" #include "verilatedos.h" // Limited V3 headers here - this is a base class for Vlc etc #include "V3Os.h" #include "V3String.h" #ifndef V3ERROR_NO_GLOBAL_ #include "V3Global.h" VL_DEFINE_DEBUG_FUNCTIONS; #endif #include #include // PATH_MAX (especially on FreeBSD) #include #ifdef _MSC_VER #include // C++17 #define PATH_MAX MAX_PATH #else #include #include #endif #include #include #include #include #include #ifdef HAVE_TCMALLOC #include #endif // clang-format off #if defined(_WIN32) || defined(__MINGW32__) # include // LONG for bcrypt.h on MINGW # include // replace # include // BCryptGenRandom # include # include // mkdir # include // open, read, write, close # include // These macros taken from gdbsupport/gdb_wait.h in binutils-gdb # ifndef WIFEXITED # ifdef __MINGW32__ # define WIFEXITED(w) (((w) & 0xC0000000) == 0) # define WEXITSTATUS(w) ((w) & ~0xC0000000) # else # define WIFEXITED(w) (((w) & 0377) == 0) # define WEXITSTATUS(w) (((w) >> 8) & 0377) # endif # endif #else # include # include // Needed on FreeBSD for WIFEXITED # include // usleep #endif // clang-format on #define VL_ALLOW_VERILATEDOS_C #include "verilatedos_c.h" //###################################################################### // Environment string V3Os::getenvStr(const string& envvar, const string& defaultValue) { return VString::escapeStringForPath(VlOs::getenvStr(envvar, defaultValue)); } void V3Os::setenvStr(const string& envvar, const string& value, const string& why) { if (why != "") { UINFO(1, "export " << envvar << "='" << value << "' # " << why); } else { UINFO(1, "export " << envvar << "='" << value << "'"); } #if defined(_WIN32) || defined(__MINGW32__) _putenv_s(envvar.c_str(), value.c_str()); #elif defined(_BSD_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) setenv(envvar.c_str(), value.c_str(), true); #else // setenv() replaced by putenv() in Solaris environment. Prototype is different // putenv() requires NAME=VALUE format const string vareq = envvar + "=" + value; putenv(strdup(vareq.c_str())); // will leak if setting the same variable again #endif } //###################################################################### // Generic filename utilities #if defined(_WIN32) || defined(__MINGW32__) static constexpr char V3OS_SLASH = '\\'; #else static constexpr char V3OS_SLASH = '/'; #endif static bool isSlash(char ch) VL_PURE { #if defined(_WIN32) || defined(__MINGW32__) return ch == '/' || ch == '\\'; #else return ch == '/'; #endif } string V3Os::filenameCleanup(const string& filename) VL_PURE { string str; str.reserve(filename.length()); bool lastIsSlash = false; for (const char ch : filename) { const bool lastIsSlashOld = lastIsSlash; lastIsSlash = isSlash(ch); if (lastIsSlash && lastIsSlashOld) continue; str += ch; } if (str.size() > 1 && isSlash(str.back())) str.pop_back(); while (str.size() > 2 && str[0] == '.' && isSlash(str[1])) str.erase(0, 2); return str; } string V3Os::filenameJoin(std::initializer_list paths) VL_PURE { string fullpath; for (const auto& item : paths) { if (item.empty() || item == ".") { continue; } else { if (!fullpath.empty()) fullpath += V3OS_SLASH; fullpath += item; } } return fullpath; } string V3Os::filenameDir(const string& filename) VL_PURE { // std::filesystem::path::parent_path auto it = filename.rbegin(); for (; it != filename.rend(); ++it) { if (isSlash(*it)) break; } if (it.base() == filename.begin()) { return "."; } else { return string{filename.begin(), (++it).base()}; } } string V3Os::filenameExt(const string& filename) VL_PURE { string base = filenameNonDir(filename); string::size_type pos; if ((pos = base.find('.')) != string::npos) { base.erase(0, pos); return base; } else { return ""; } } string V3Os::filenameNonDir(const string& filename) VL_PURE { // std::filesystem::path::filename auto it = filename.rbegin(); for (; it != filename.rend(); ++it) { if (isSlash(*it)) break; } return string{it.base(), filename.end()}; } string V3Os::filenameNonDirExt(const string& filename) VL_PURE { string base = filenameNonDir(filename); string::size_type pos; if ((pos = base.find('.')) != string::npos) base.erase(pos); return base; } string V3Os::filenameSubstitute(const string& filename) { string result; // cppcheck-has-bug-suppress unusedLabel enum : uint8_t { NONE, PAREN, CURLY } brackets = NONE; for (string::size_type pos = 0; pos < filename.length(); ++pos) { if ((filename[pos] == '$') && (pos + 1 < filename.length())) { switch (filename[pos + 1]) { case '{': brackets = CURLY; break; case '(': brackets = PAREN; break; default: brackets = NONE; break; } if (brackets != NONE) pos = pos + 1; string::size_type endpos = pos + 1; while (((endpos + 1) < filename.length()) && (((brackets == NONE) && (std::isalnum(filename[endpos + 1]) || filename[endpos + 1] == '_')) || ((brackets == CURLY) && (filename[endpos + 1] != '}')) || ((brackets == PAREN) && (filename[endpos + 1] != ')')))) ++endpos; // Catch bracket errors if (((brackets == CURLY) && (filename[endpos + 1] != '}')) || ((brackets == PAREN) && (filename[endpos + 1] != ')'))) { v3fatal("Unmatched brackets in variable substitution in file: " + filename); } const string envvar = filename.substr(pos + 1, endpos - pos); string envvalue; if (!envvar.empty()) envvalue = getenvStr(envvar, ""); if (!envvalue.empty()) { result += envvalue; if (brackets == NONE) { pos = endpos; } else { pos = endpos + 1; } } else { result += filename[pos]; // *pos == '$' } } else { result += filename[pos]; } } return result; } string V3Os::filenameRealPath(const string& filename) VL_PURE { // Get rid of all the ../ behavior in the middle of the paths. // If there is a ../ that goes down from the 'root' of this path it is preserved. char retpath[PATH_MAX]; if ( #if defined(_WIN32) || defined(__MINGW32__) _fullpath(retpath, filename.c_str(), PATH_MAX) #else realpath(filename.c_str(), retpath) #endif ) { return std::string{retpath}; } else { return filename; } } string V3Os::filenameRelativePath(const string& filename, const string& base) VL_PURE { const string a = V3Os::filenameRealPath(filename); const string b = V3Os::filenameRealPath(base); string result; if (a == b) return "."; auto aIt = a.begin(); auto bIt = b.begin(); while (aIt != a.end() && bIt != b.end()) { // UINFO(9, "fnrp scan " << (aIt - a.begin()) << " " << a.substr(aIt - a.begin())); // UINFO(9, "fnrp scan " << (bIt - b.begin()) << " " << b.substr(bIt - b.begin())); auto aWordIt = aIt; // position of next slash for (; aWordIt != a.end(); ++aWordIt) { if (isSlash(*aWordIt)) break; } auto bWordIt = bIt; // position of next slash for (; bWordIt != b.end(); ++bWordIt) { if (isSlash(*bWordIt)) break; } const string aWord = a.substr(aIt - a.begin(), aWordIt - aIt); const string bWord = b.substr(bIt - b.begin(), bWordIt - bIt); if (aWord != bWord) break; aIt = aWordIt; bIt = bWordIt; if (aIt != a.end()) ++aIt; // Skip slash if (bIt != b.end()) ++bIt; // Skip slash } while (bIt != b.end()) { for (; bIt != b.end(); ++bIt) { if (isSlash(*bIt)) { ++bIt; break; } } if (!result.empty()) result += "/"; result += ".."; } const string aLeft = a.substr(aIt - a.begin()); if (!aLeft.empty()) { if (!result.empty()) result += "/"; result += aLeft; } return filenameCleanup(result); } bool V3Os::filenameIsRel(const string& filename) VL_PURE { #if defined(_MSC_VER) return std::filesystem::path(filename).is_relative(); #else return (filename.length() > 0 && filename[0] != '/'); #endif } string V3Os::filenameSlashPath(const string& path) VL_PURE { #if defined(_WIN32) || defined(__MINGW32__) string slashedPath = path; std::replace(slashedPath.begin(), slashedPath.end(), '\\', '/'); return slashedPath; #else return path; #endif } //###################################################################### // File utilities string V3Os::getline(std::istream& is, char delim) { string line; #if defined(__CYGWIN__) // Work around buggy implementation of getline char buf[65536]; is.getline(buf, 65535, delim); line = buf; #else std::getline(is, line, delim); #endif return line; } //###################################################################### // Directory utilities void V3Os::createDir(const string& dirname) { #if defined(_WIN32) || defined(__MINGW32__) _mkdir(dirname.c_str()); #else mkdir(dirname.c_str(), 0777); #endif } void V3Os::filesystemFlush(const string& dirname) { // NFS caches stat() calls so to get up-to-date information must // do a open or opendir on the filename. #if defined(_MSC_VER) || defined(__MINGW32__) if (int fd = ::open(dirname.c_str(), O_RDONLY)) { // LCOV_EXCL_BR_LINE if (fd > 0) ::close(fd); } #else // Faster to just try both rather than check if a file is a dir. if (DIR* const dirp = opendir(dirname.c_str())) { // LCOV_EXCL_BR_LINE closedir(dirp); // LCOV_EXCL_LINE } else if (int fd = ::open(dirname.c_str(), O_RDONLY)) { // LCOV_EXCL_BR_LINE if (fd > 0) ::close(fd); } #endif } void V3Os::filesystemFlushBuildDir(const string& dirname) { // Attempt to force out written directory, for NFS like file systems. #if !defined(_MSC_VER) && !defined(__MINGW32__) // Linux kernel may not reread from NFS unless timestamp modified const int err = utimes(dirname.c_str(), nullptr); // Not an error if (err != 0) UINFO(1, "-Info: File not utimed: " << dirname); #endif filesystemFlush(dirname); } void V3Os::unlinkRegexp(const string& dir, const string& regexp) { #ifdef _MSC_VER try { for (const auto& dirEntry : std::filesystem::directory_iterator(dir.c_str())) { if (VString::wildmatch(dirEntry.path().filename().string(), regexp.c_str())) { const string fullname = dir + "/" + dirEntry.path().filename().string(); _unlink(fullname.c_str()); } } } catch (std::filesystem::filesystem_error const& ex) {} #else if (DIR* const dirp = opendir(dir.c_str())) { while (struct dirent* const direntp = readdir(dirp)) { if (VString::wildmatch(direntp->d_name, regexp.c_str())) { const string fullname = dir + "/" + std::string{direntp->d_name}; #if defined(_WIN32) || defined(__MINGW32__) _unlink(fullname.c_str()); #else unlink(fullname.c_str()); #endif } } closedir(dirp); } #endif } //###################################################################### // METHODS (memory) void V3Os::releaseMemory() { #ifdef HAVE_TCMALLOC MallocExtension::instance()->ReleaseFreeMemory(); #endif } //###################################################################### // METHODS (random) uint64_t V3Os::rand64(std::array& stater) { // Xoroshiro128+ algorithm const uint64_t result = stater[0] + stater[1]; stater[1] ^= stater[0]; stater[0] = (((stater[0] << 55) | (stater[0] >> 9)) ^ stater[1] ^ (stater[1] << 14)); stater[1] = (stater[1] << 36) | (stater[1] >> 28); return result; } string V3Os::trueRandom(size_t size) VL_MT_SAFE { string result(size, '\xFF'); char* const data = const_cast(result.data()); // Note: std::string.data() returns a non-const Char* from C++17 onwards. // For pre-C++17, this cast is OK in practice, even though it's UB. #if defined(_WIN32) || defined(__MINGW32__) const NTSTATUS hr = BCryptGenRandom(nullptr, reinterpret_cast(data), size, BCRYPT_USE_SYSTEM_PREFERRED_RNG); if (VL_UNCOVERABLE(!BCRYPT_SUCCESS(hr))) { v3fatal("Could not acquire random data. Try specifying a key instead."); // LCOV_EXCL_LINE } #else std::ifstream is{"/dev/urandom", std::ios::in | std::ios::binary}; // This read uses the size of the buffer. // Flawfinder: ignore if (VL_UNCOVERABLE(!is.read(data, size))) { v3fatal("Could not open /dev/urandom, no source of randomness. " // LCOV_EXCL_LINE "Try specifying a key instead."); } #endif return result; } //###################################################################### // METHODS (performance) uint64_t V3Os::timeUsecs() { #if defined(_WIN32) || defined(__MINGW32__) // Microseconds between 1601-01-01 00:00:00 UTC and 1970-01-01 00:00:00 UTC static const uint64_t EPOCH_DIFFERENCE_USECS = 11644473600000000ULL; FILETIME ft; // contains number of 0.1us intervals since the beginning of 1601 UTC. GetSystemTimeAsFileTime(&ft); const uint64_t us = ((static_cast(ft.dwHighDateTime) << 32) + ft.dwLowDateTime + 5ULL) / 10ULL; return us - EPOCH_DIFFERENCE_USECS; #else // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) timeval tv; if (gettimeofday(&tv, nullptr) < 0) return 0; return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; #endif } void V3Os::u_sleep(int64_t usec) { #if defined(_WIN32) || defined(__MINGW32__) std::this_thread::sleep_for(std::chrono::microseconds(usec)); #else // cppcheck-suppress obsoleteFunctionsusleep // Flawfinder: ignore ::usleep(usec); #endif } //###################################################################### // METHODS (sub command) int V3Os::system(const string& command) { UINFO(1, "Running system: " << command); const int ret = ::system(command.c_str()); if (VL_UNCOVERABLE(ret == -1)) { v3fatal("Failed to execute command:" // LCOV_EXCL_LINE << command << " " << std::strerror(errno)); return -1; // LCOV_EXCL_LINE } else { UASSERT(WIFEXITED(ret), "system(" << command << ") returned unexpected value of " << ret); const int exit_code = WEXITSTATUS(ret); UINFO(1, command << " returned exit code of " << exit_code); UASSERT(exit_code >= 0, "exit code must not be negative"); return exit_code; } } void V3Os::selfTest() { #ifdef VL_DEBUG UASSERT_SELFTEST(string, filenameCleanup(""), ""); UASSERT_SELFTEST(string, filenameCleanup("."), "."); UASSERT_SELFTEST(string, filenameCleanup(".."), ".."); UASSERT_SELFTEST(string, filenameCleanup("/"), "/"); UASSERT_SELFTEST(string, filenameCleanup("../"), ".."); UASSERT_SELFTEST(string, filenameCleanup("//"), "/"); UASSERT_SELFTEST(string, filenameCleanup("//."), "/."); UASSERT_SELFTEST(string, filenameCleanup("./"), "."); UASSERT_SELFTEST(string, filenameCleanup("././"), "."); UASSERT_SELFTEST(string, filenameCleanup(".///"), "."); UASSERT_SELFTEST(string, filenameCleanup("a"), "a"); UASSERT_SELFTEST(string, filenameCleanup("a/"), "a"); UASSERT_SELFTEST(string, filenameCleanup("a/b"), "a/b"); UASSERT_SELFTEST(string, filenameCleanup("././//./a/b"), "a/b"); UASSERT_SELFTEST(string, filenameCleanup(".//./a///"), "a"); UASSERT_SELFTEST(string, filenameCleanup("///a/./b///."), "/a/./b/."); UASSERT_SELFTEST(string, filenameCleanup("aaa/bbb/ccc/"), "aaa/bbb/ccc"); UASSERT_SELFTEST(string, filenameCleanup("./aaa/bbb/ccc/"), "aaa/bbb/ccc"); UASSERT_SELFTEST(string, filenameCleanup("../aaa/bbb/ccc/"), "../aaa/bbb/ccc"); UASSERT_SELFTEST(string, filenameDir("a.a/b.b/f.e"), "a.a/b.b"); UASSERT_SELFTEST(string, filenameExt("a.a/b.b/f"), ""); UASSERT_SELFTEST(string, filenameExt("a.a/b.b/f.e"), ".e"); UASSERT_SELFTEST(string, filenameNonDirExt("a.a/b.b/f.e"), "f"); UASSERT_SELFTEST(string, filenameRelativePath("/a/b", "/a/b"), "."); UASSERT_SELFTEST(string, filenameRelativePath("/a/b", "/a/b/c"), ".."); UASSERT_SELFTEST(string, filenameRelativePath("/a/b", "/a/b/c/d"), "../.."); UASSERT_SELFTEST(string, filenameRelativePath("/a/b/x", "/a/b/c/d"), "../../x"); UASSERT_SELFTEST(string, filenameRelativePath("/a/b/x/y", "/"), "a/b/x/y"); UASSERT_SELFTEST(string, filenameRelativePath("/a/b/x/y", "/a/b"), "x/y"); UASSERT_SELFTEST(string, filenameRelativePath("/a/b/x/y", "/a/q"), "../b/x/y"); UASSERT_SELFTEST(string, filenameRelativePath("a/b", "a/b"), "."); UASSERT_SELFTEST(string, filenameRelativePath("a/b", "a/b/c"), ".."); UASSERT_SELFTEST(string, filenameRelativePath("a/b", "a/b/c/d"), "../.."); UASSERT_SELFTEST(string, filenameRelativePath("a/b/x", "a/b/c/d"), "../../x"); UASSERT_SELFTEST(string, filenameRelativePath("a/b/x/y", ""), "a/b/x/y"); UASSERT_SELFTEST(string, filenameRelativePath("a/b/x/y", "a/b"), "x/y"); UASSERT_SELFTEST(string, filenameRelativePath("a/b/x/y", "a/q"), "../b/x/y"); #endif }