| /* 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 <stdlib.h> |
| |
| #if defined(OPENSSL_WINDOWS) |
| OPENSSL_MSVC_PRAGMA(warning(push, 3)) |
| #include <windows.h> |
| OPENSSL_MSVC_PRAGMA(warning(pop)) |
| #else |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #endif |
| |
| #include <openssl/rand.h> |
| |
| #include "test_util.h" |
| |
| |
| #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<char *>(&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<const uint8_t> 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<const uint8_t> 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; |
| } |