| /* |
| * Copyright (C) 2026 The Android Open Source Project |
| * |
| * 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 |
| * |
| * http://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 "perfetto/ext/base/atomic_file.h" |
| |
| #include <cerrno> |
| #include <cstdio> |
| #include <cstring> |
| |
| #include "perfetto/base/build_config.h" |
| #include "perfetto/base/compiler.h" |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/file_utils.h" |
| #include "perfetto/ext/base/uuid.h" |
| |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) |
| #include <io.h> |
| #include <windows.h> |
| #else |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #endif |
| |
| namespace perfetto::base { |
| |
| AtomicFile::AtomicFile(const std::string& destination) |
| : destination_(destination), |
| temp_path_(destination + ".tmp." + Uuidv4().ToPrettyString()) {} |
| |
| AtomicFile::~AtomicFile() { |
| fd_.reset(); |
| if (owns_temp_file_) |
| Unlink(temp_path_.c_str()); |
| } |
| |
| base::Status AtomicFile::Open() { |
| PERFETTO_CHECK(!fd_ && !owns_temp_file_); |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) |
| DWORD attributes = GetFileAttributesA(destination_.c_str()); |
| if (attributes != INVALID_FILE_ATTRIBUTES && |
| (attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))) |
| return base::ErrStatus( |
| "output must be a regular file, not a directory or link"); |
| HANDLE handle = |
| CreateFileA(temp_path_.c_str(), GENERIC_READ | GENERIC_WRITE, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); |
| if (handle == INVALID_HANDLE_VALUE) |
| return base::ErrStatus( |
| "cannot create temporary output beside '%s' (Windows error %lu)", |
| destination_.c_str(), GetLastError()); |
| owns_temp_file_ = true; |
| fd_.reset(_open_osfhandle(reinterpret_cast<intptr_t>(handle), _O_BINARY)); |
| if (!fd_) |
| CloseHandle(handle); |
| #else |
| struct stat output{}; |
| bool exists = lstat(destination_.c_str(), &output) == 0; |
| PERFETTO_MSAN_UNPOISON(&output, sizeof(output)); |
| if (exists && !S_ISREG(output.st_mode)) |
| return base::ErrStatus( |
| "output must be a regular file, not a directory or link: '%s'. Specify " |
| "the target path directly.", |
| destination_.c_str()); |
| fd_ = base::OpenFile(temp_path_, O_CREAT | O_EXCL | O_RDWR, 0644); |
| owns_temp_file_ = static_cast<bool>(fd_); |
| if (fd_ && exists && fchmod(*fd_, output.st_mode & 0777) != 0) |
| return base::ErrStatus("cannot preserve output permissions: %s", |
| strerror(errno)); |
| #endif |
| if (!fd_) |
| return base::ErrStatus( |
| "cannot create temporary output beside '%s': %s. Check that the parent " |
| "directory exists and is writable.", |
| destination_.c_str(), strerror(errno)); |
| return base::OkStatus(); |
| } |
| |
| base::ScopedFile AtomicFile::DuplicateFD() const { |
| PERFETTO_CHECK(fd_); |
| return DupFile(*fd_); |
| } |
| |
| base::Status AtomicFile::Commit() && { |
| PERFETTO_CHECK(fd_); |
| if (!base::FlushFile(*fd_)) |
| return base::ErrStatus("failed to flush output: %s", strerror(errno)); |
| fd_.reset(); |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) |
| if (!MoveFileExA(temp_path_.c_str(), destination_.c_str(), |
| MOVEFILE_REPLACE_EXISTING)) |
| return base::ErrStatus("failed to publish '%s' (Windows error %lu)", |
| destination_.c_str(), GetLastError()); |
| #else |
| if (rename(temp_path_.c_str(), destination_.c_str()) != 0) |
| return base::ErrStatus("failed to publish '%s': %s", destination_.c_str(), |
| strerror(errno)); |
| #endif |
| owns_temp_file_ = false; |
| return base::OkStatus(); |
| } |
| } // namespace perfetto::base |