/* Copyright (c) 2023, Google Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "file_util.h" #include #if defined(OPENSSL_WINDOWS) OPENSSL_MSVC_PRAGMA(warning(push, 3)) #include OPENSSL_MSVC_PRAGMA(warning(pop)) #else #include #include #include #endif #include #include "test_util.h" BSSL_NAMESPACE_BEGIN #if defined(OPENSSL_WINDOWS) static void PrintLastError(const char *s) { DWORD error = GetLastError(); char *buffer; DWORD len = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, error, 0, reinterpret_cast(&buffer), 0, nullptr); std::string msg = "unknown error"; if (len > 0) { msg.assign(buffer, len); while (!msg.empty() && (msg.back() == '\r' || msg.back() == '\n')) { msg.resize(msg.size() - 1); } } LocalFree(buffer); fprintf(stderr, "%s: %s (0x%lx)\n", s, msg.c_str(), error); } #endif // OPENSSL_WINDOWS // GetTempDir returns the path to the temporary directory, or the empty string // on error. On success, the result will include the directory separator. static std::string GetTempDir() { #if defined(OPENSSL_WINDOWS) char buf[MAX_PATH + 1]; DWORD len = GetTempPathA(sizeof(buf), buf); return std::string(buf, len); #else const char *tmpdir = getenv("TMPDIR"); if (tmpdir != nullptr && *tmpdir != '\0') { std::string ret = tmpdir; if (ret.back() != '/') { ret.push_back('/'); } return ret; } #if defined(OPENSSL_ANDROID) return "/data/local/tmp/"; #else return "/tmp/"; #endif #endif } bool SkipTempFileTests() { #if defined(OPENSSL_ANDROID) // When running in an APK context, /data/local/tmp is unreadable. Android // versions before https://android-review.googlesource.com/c/1821337 do not // set TMPDIR to a suitable replacement. if (getenv("TMPDIR") == nullptr) { static bool should_skip = [] { TemporaryFile file; return !file.Init(); }(); if (should_skip) { fprintf(stderr, "Skipping tests with temporary files.\n"); return true; } } #endif return false; } TemporaryFile::~TemporaryFile() { #if defined(OPENSSL_WINDOWS) if (!path_.empty() && !DeleteFileA(path_.c_str())) { PrintLastError("Could not delete file"); } #else if (!path_.empty() && unlink(path_.c_str()) != 0) { perror("Could not delete file"); } #endif } bool TemporaryFile::Init(bssl::Span content) { std::string temp_dir = GetTempDir(); if (temp_dir.empty()) { return false; } #if defined(OPENSSL_WINDOWS) char path[MAX_PATH]; if (GetTempFileNameA(temp_dir.c_str(), "bssl", /*uUnique=*/0, path) == 0) { PrintLastError("Could not create temporary"); return false; } path_ = path; #else std::string path = temp_dir + "bssl_tmp_file.XXXXXX"; // TODO(davidben): Use |path.data()| when we require C++17. int fd = mkstemp(&path[0]); if (fd < 0) { perror("Could not create temporary file"); return false; } close(fd); path_ = std::move(path); #endif ScopedFILE file = Open("wb"); if (file == nullptr) { perror("Could not open temporary file"); return false; } if (!content.empty() && fwrite(content.data(), content.size(), /*nitems=*/1, file.get()) != 1) { perror("Could not write temporary file"); return false; } return true; } ScopedFILE TemporaryFile::Open(const char *mode) const { if (path_.empty()) { return nullptr; } return ScopedFILE(fopen(path_.c_str(), mode)); } ScopedFD TemporaryFile::OpenFD(int flags) const { if (path_.empty()) { return ScopedFD(); } #if defined(OPENSSL_WINDOWS) return ScopedFD(_open(path_.c_str(), flags)); #else return ScopedFD(open(path_.c_str(), flags)); #endif } TemporaryDirectory::~TemporaryDirectory() { if (path_.empty()) { return; } #if defined(OPENSSL_WINDOWS) for (const auto &file : files_) { if (!DeleteFileA(GetFilePath(file).c_str())) { PrintLastError("Could not delete file"); } } if (!RemoveDirectoryA(path_.c_str())) { PrintLastError("Could not delete directory"); } #else for (const auto &file : files_) { if (unlink(GetFilePath(file).c_str()) != 0) { perror("Could not delete file"); } } if (rmdir(path_.c_str()) != 0) { perror("Could not delete directory"); } #endif } bool TemporaryDirectory::Init() { std::string path = GetTempDir(); if (path.empty()) { return false; } #if defined(OPENSSL_WINDOWS) // For simplicity, just try the first candidate and assume the directory // doesn't exist. 128 bits of cryptographically secure randomness is plenty. uint8_t buf[16]; RAND_bytes(buf, sizeof(buf)); path += "bssl_tmp_dir." + EncodeHex(buf); if (!CreateDirectoryA(path.c_str(), /*lpSecurityAttributes=*/nullptr)) { perror("Could not make temporary directory"); return false; } #else path += "bssl_tmp_dir.XXXXXX"; // TODO(davidben): Use |path.data()| when we require C++17. if (mkdtemp(&path[0]) == nullptr) { perror("Could not make temporary directory"); return false; } #endif path_ = std::move(path); return true; } bool TemporaryDirectory::AddFile(const std::string &filename, bssl::Span content) { if (path_.empty()) { return false; } ScopedFILE file(fopen(GetFilePath(filename).c_str(), "wb")); if (file == nullptr) { perror("Could not open temporary file"); return false; } if (!content.empty() && fwrite(content.data(), content.size(), /*nitems=*/1, file.get()) != 1) { perror("Could not write temporary file"); return false; } files_.insert(filename); return true; } BSSL_NAMESPACE_END