|  | // Copyright 2023 The BoringSSL Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #include "file_util.h" | 
|  |  | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #if defined(OPENSSL_WINDOWS) | 
|  | #include <windows.h> | 
|  | #else | 
|  | #include <fcntl.h> | 
|  | #include <sys/stat.h> | 
|  | #include <unistd.h> | 
|  | #endif | 
|  |  | 
|  | #include <openssl/rand.h> | 
|  |  | 
|  | #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<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"; | 
|  | int fd = mkstemp(path.data()); | 
|  | 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"; | 
|  | if (mkdtemp(path.data()) == 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; | 
|  | } | 
|  |  | 
|  | BSSL_NAMESPACE_END |