| /* | |
| * Copyright (C) 2018 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/file_utils.h" | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <algorithm> | |
| #include <deque> | |
| #include <limits> | |
| #include <optional> | |
| #include <string> | |
| #include <vector> | |
| #include "perfetto/base/build_config.h" | |
| #include "perfetto/base/compiler.h" | |
| #include "perfetto/base/logging.h" | |
| #include "perfetto/base/platform_handle.h" | |
| #include "perfetto/base/status.h" | |
| #include "perfetto/base/task_runner.h" | |
| #include "perfetto/ext/base/platform.h" | |
| #include "perfetto/ext/base/scoped_file.h" | |
| #include "perfetto/ext/base/string_utils.h" | |
| #include "perfetto/ext/base/utils.h" | |
| #include "perfetto/ext/base/weak_ptr.h" | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| #include <windows.h> | |
| #include <direct.h> | |
| #include <io.h> | |
| #include <stringapiset.h> | |
| #else | |
| #include <dirent.h> | |
| #include <unistd.h> | |
| #endif | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ | |
| PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \ | |
| PERFETTO_BUILDFLAG(PERFETTO_OS_FREEBSD) || \ | |
| PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE) | |
| #define PERFETTO_SET_FILE_PERMISSIONS | |
| #include <fcntl.h> | |
| #include <grp.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #endif | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ | |
| PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) | |
| // For inotify. QNX doesn't have full support for inotify_init1(). | |
| #include <sys/inotify.h> | |
| #endif | |
| namespace perfetto { | |
| namespace base { | |
| namespace { | |
| constexpr size_t kBufSize = 2048; | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| // Wrap FindClose to: (1) make the return unix-style; (2) deal with stdcall. | |
| int CloseFindHandle(HANDLE h) { | |
| return FindClose(h) ? 0 : -1; | |
| } | |
| std::optional<std::wstring> ToUtf16(const std::string& str) { | |
| int len = MultiByteToWideChar(CP_UTF8, 0, str.data(), | |
| static_cast<int>(str.size()), nullptr, 0); | |
| if (len < 0) { | |
| return std::nullopt; | |
| } | |
| std::vector<wchar_t> tmp; | |
| tmp.resize(static_cast<std::vector<wchar_t>::size_type>(len)); | |
| len = | |
| MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.size()), | |
| tmp.data(), static_cast<int>(tmp.size())); | |
| if (len < 0) { | |
| return std::nullopt; | |
| } | |
| PERFETTO_CHECK(static_cast<std::vector<wchar_t>::size_type>(len) == | |
| tmp.size()); | |
| return std::wstring(tmp.data(), tmp.size()); | |
| } | |
| #endif | |
| } // namespace | |
| ssize_t Read(int fd, void* dst, size_t dst_size) { | |
| ssize_t ret; | |
| platform::BeforeMaybeBlockingSyscall(); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| ret = _read(fd, dst, static_cast<unsigned>(dst_size)); | |
| #else | |
| ret = PERFETTO_EINTR(read(fd, dst, dst_size)); | |
| #endif | |
| platform::AfterMaybeBlockingSyscall(); | |
| return ret; | |
| } | |
| bool ReadFileDescriptor(int fd, std::string* out) { | |
| // Do not override existing data in string. | |
| size_t i = out->size(); | |
| struct stat buf{}; | |
| if (fstat(fd, &buf) != -1) { | |
| if (buf.st_size > 0) | |
| out->resize(i + static_cast<size_t>(buf.st_size)); | |
| } | |
| ssize_t bytes_read; | |
| for (;;) { | |
| if (out->size() < i + kBufSize) | |
| out->resize(out->size() + kBufSize); | |
| bytes_read = Read(fd, &((*out)[i]), kBufSize); | |
| if (bytes_read > 0) { | |
| i += static_cast<size_t>(bytes_read); | |
| } else { | |
| out->resize(i); | |
| return bytes_read == 0; | |
| } | |
| } | |
| } | |
| bool ReadPlatformHandle(PlatformHandle h, std::string* out) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| // Do not override existing data in string. | |
| size_t i = out->size(); | |
| for (;;) { | |
| if (out->size() < i + kBufSize) | |
| out->resize(out->size() + kBufSize); | |
| DWORD bytes_read = 0; | |
| auto res = ::ReadFile(h, &((*out)[i]), kBufSize, &bytes_read, nullptr); | |
| if (res && bytes_read > 0) { | |
| i += static_cast<size_t>(bytes_read); | |
| } else { | |
| out->resize(i); | |
| const bool is_eof = res && bytes_read == 0; | |
| auto err = res ? 0 : GetLastError(); | |
| // The "Broken pipe" error on Windows is slightly different than Unix: | |
| // On Unix: a "broken pipe" error can happen only on the writer side. On | |
| // the reader there is no broken pipe, just a EOF. | |
| // On windows: the reader also sees a broken pipe error. | |
| // Here we normalize on the Unix behavior, treating broken pipe as EOF. | |
| return is_eof || err == ERROR_BROKEN_PIPE; | |
| } | |
| } | |
| #else | |
| return ReadFileDescriptor(h, out); | |
| #endif | |
| } | |
| bool ReadFileStream(FILE* f, std::string* out) { | |
| return ReadFileDescriptor(fileno(f), out); | |
| } | |
| bool ReadFile(const std::string& path, std::string* out) { | |
| base::ScopedFile fd = base::OpenFile(path, O_RDONLY); | |
| if (!fd) | |
| return false; | |
| return ReadFileDescriptor(*fd, out); | |
| } | |
| ssize_t WriteAll(int fd, const void* buf, size_t count) { | |
| size_t written = 0; | |
| while (written < count) { | |
| // write() on windows takes an unsigned int size. | |
| uint32_t bytes_left = static_cast<uint32_t>( | |
| std::min(count - written, static_cast<size_t>(UINT32_MAX))); | |
| platform::BeforeMaybeBlockingSyscall(); | |
| ssize_t wr = PERFETTO_EINTR( | |
| write(fd, static_cast<const char*>(buf) + written, bytes_left)); | |
| platform::AfterMaybeBlockingSyscall(); | |
| if (wr == 0) | |
| break; | |
| if (wr < 0) | |
| return wr; | |
| written += static_cast<size_t>(wr); | |
| } | |
| return static_cast<ssize_t>(written); | |
| } | |
| base::Status CopyFileContents(int fd_in, int fd_out) { | |
| off_t original_offset = lseek(fd_in, 0, SEEK_CUR); | |
| if (original_offset == -1) { | |
| return base::ErrStatus( | |
| "Can't get offset in 'fd_in', lseek error: %s (errno: %d)", | |
| strerror(errno), errno); | |
| } | |
| if (lseek(fd_in, 0, SEEK_SET) == -1) { | |
| return base::ErrStatus( | |
| "Can't change the offset in 'fd_in', lseek error: %s (errno: %d)", | |
| strerror(errno), errno); | |
| } | |
| auto restore_offset_on_exit = OnScopeExit([fd_in, original_offset] { | |
| // 'lseek' should never fail here, but if it fails, we crash, to prevent | |
| // possible data loss/overwrite in the 'fd_in'. | |
| PERFETTO_CHECK(lseek(fd_in, original_offset, SEEK_SET) >= 0); | |
| }); | |
| // Use bigger buffer when copy files. | |
| constexpr size_t kCopyFileBufSize = 32 * 1024; // 32KB. | |
| static_assert(kCopyFileBufSize > kBufSize); | |
| // Don't allocate that much memory on stack. | |
| std::vector<char> buffer(kCopyFileBufSize); | |
| for (;;) { | |
| ssize_t bytes_read = Read(fd_in, buffer.data(), buffer.size()); | |
| if (bytes_read == 0) | |
| break; | |
| if (bytes_read < 0) { | |
| return base::ErrStatus("Read failed: %s (errno: %d)", strerror(errno), | |
| errno); | |
| } | |
| ssize_t written = | |
| WriteAll(fd_out, buffer.data(), static_cast<size_t>(bytes_read)); | |
| if (written != bytes_read) { | |
| return base::ErrStatus("Write failed: %s (errno: %d)", strerror(errno), | |
| errno); | |
| } | |
| } | |
| return base::OkStatus(); | |
| } | |
| ssize_t WriteAllHandle(PlatformHandle h, const void* buf, size_t count) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| DWORD wsize = 0; | |
| if (::WriteFile(h, buf, static_cast<DWORD>(count), &wsize, nullptr)) { | |
| return wsize; | |
| } else { | |
| return -1; | |
| } | |
| #else | |
| return WriteAll(h, buf, count); | |
| #endif | |
| } | |
| bool FlushFile(int fd) { | |
| PERFETTO_DCHECK(fd != 0); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \ | |
| PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) | |
| return !PERFETTO_EINTR(fdatasync(fd)); | |
| #elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| return !PERFETTO_EINTR(_commit(fd)); | |
| #else | |
| return !PERFETTO_EINTR(fsync(fd)); | |
| #endif | |
| } | |
| bool SeekFile(int fd, uint64_t offset) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| if (fd < 0) { | |
| errno = EBADF; | |
| return false; | |
| } | |
| if (offset > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) { | |
| errno = EOVERFLOW; | |
| return false; | |
| } | |
| return _lseeki64(fd, static_cast<int64_t>(offset), SEEK_SET) != -1; | |
| #else | |
| if (offset > static_cast<uint64_t>(std::numeric_limits<off_t>::max())) { | |
| errno = EOVERFLOW; | |
| return false; | |
| } | |
| return lseek(fd, static_cast<off_t>(offset), SEEK_SET) != | |
| static_cast<off_t>(-1); | |
| #endif | |
| } | |
| bool TruncateFile(int fd, uint64_t size) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| if (fd < 0) { | |
| errno = EBADF; | |
| return false; | |
| } | |
| if (size > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) { | |
| errno = EOVERFLOW; | |
| return false; | |
| } | |
| int result = _chsize_s(fd, static_cast<int64_t>(size)); | |
| if (result != 0) { | |
| errno = result; | |
| return false; | |
| } | |
| return true; | |
| #else | |
| if (size > static_cast<uint64_t>(std::numeric_limits<off_t>::max())) { | |
| errno = EOVERFLOW; | |
| return false; | |
| } | |
| return PERFETTO_EINTR(ftruncate(fd, static_cast<off_t>(size))) == 0; | |
| #endif | |
| } | |
| bool Mkdir(const std::string& path, uint32_t mode) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| base::ignore_result(mode); | |
| return _mkdir(path.c_str()) == 0; | |
| #else | |
| return mkdir(path.c_str(), mode) == 0; | |
| #endif | |
| } | |
| bool Rmdir(const std::string& path) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| return _rmdir(path.c_str()) == 0; | |
| #else | |
| return rmdir(path.c_str()) == 0; | |
| #endif | |
| } | |
| bool Unlink(const char* path) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| return _unlink(path) == 0; | |
| #else | |
| return unlink(path) == 0; | |
| #endif | |
| } | |
| int CloseFile(int fd) { | |
| return close(fd); | |
| } | |
| ScopedFile OpenFile(const std::string& path, int flags, FileOpenMode mode) { | |
| // If a new file might be created, ensure that the permissions for the new | |
| // file are explicitly specified. | |
| PERFETTO_CHECK((flags & O_CREAT) == 0 || mode != kFileModeInvalid); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| // Always use O_BINARY on Windows, to avoid silly EOL translations. | |
| ScopedFile fd(_open(path.c_str(), flags | O_BINARY, mode)); | |
| #else | |
| // Always open a ScopedFile with O_CLOEXEC so we can safely fork and exec. | |
| ScopedFile fd(open(path.c_str(), flags | O_CLOEXEC, mode)); | |
| #endif | |
| return fd; | |
| } | |
| ScopedFstream OpenFstream(const std::string& path, const std::string& mode) { | |
| ScopedFstream file; | |
| std::string s_mode(mode); | |
| // Windows supports non-standard mode extension that sets encoding in text | |
| // mode. If you need to open a FILE* in text mode, use the fopen API directly. | |
| bool is_text_mode = Contains(s_mode, "ccs=") || Contains(s_mode, "t"); | |
| PERFETTO_CHECK(!is_text_mode); | |
| bool is_binary_mode = Contains(s_mode, 'b'); | |
| if (!is_binary_mode) | |
| s_mode += 'b'; | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| // On Windows fopen interprets filename using the ANSI or OEM codepage but | |
| // sqlite3_value_text returns a UTF-8 string. To make sure we interpret the | |
| // filename correctly we use _wfopen and a UTF-16 string on windows. | |
| // | |
| // On Windows fopen also open files in the text mode by default, but we want | |
| // to open them in the binary mode, to avoid silly EOL translations (and to be | |
| // consistent with base::OpenFile). So we check the mode first and append 'b' | |
| // mode only when it makes sense. | |
| auto w_path = ToUtf16(path); | |
| auto w_mode = ToUtf16(s_mode); | |
| if (w_path && w_mode) { | |
| file.reset(_wfopen(w_path->c_str(), w_mode->c_str())); | |
| } | |
| #else | |
| file.reset(fopen(path.c_str(), s_mode.c_str())); | |
| #endif | |
| return file; | |
| } | |
| bool FileExists(const std::string& path) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| return _access(path.c_str(), 0) == 0; | |
| #else | |
| return access(path.c_str(), F_OK) == 0; | |
| #endif | |
| } | |
| bool DirectoryExists(const std::string& path) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| DWORD attrs = GetFileAttributesA(path.c_str()); | |
| return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY); | |
| #else | |
| struct stat st; | |
| if (stat(path.c_str(), &st) != 0) { | |
| return false; | |
| } | |
| // MSan's stat() interceptor on glibc 2.35+ does not mark the output buffer | |
| // as initialized (the syscall goes through statx). | |
| PERFETTO_MSAN_UNPOISON(&st, sizeof(st)); | |
| return S_ISDIR(st.st_mode); | |
| #endif | |
| } | |
| // Declared in base/platform_handle.h. | |
| int ClosePlatformHandle(PlatformHandle handle) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| // Make the return value UNIX-style. | |
| return CloseHandle(handle) ? 0 : -1; | |
| #else | |
| return close(handle); | |
| #endif | |
| } | |
| base::Status ListFilesRecursive(const std::string& dir_path, | |
| std::vector<std::string>& output) { | |
| std::string root_dir_path = dir_path; | |
| if (root_dir_path.back() == '\\') { | |
| root_dir_path.back() = '/'; | |
| } else if (root_dir_path.back() != '/') { | |
| root_dir_path.push_back('/'); | |
| } | |
| // dir_queue contains full paths to the directories. The paths include the | |
| // root_dir_path at the beginning and the trailing slash at the end. | |
| std::deque<std::string> dir_queue; | |
| dir_queue.push_back(root_dir_path); | |
| while (!dir_queue.empty()) { | |
| const std::string cur_dir = std::move(dir_queue.front()); | |
| dir_queue.pop_front(); | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) | |
| return base::ErrStatus("ListFilesRecursive not supported yet"); | |
| #elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| std::string glob_path = cur_dir + "*"; | |
| // + 1 because we also have to count the NULL terminator. | |
| if (glob_path.length() + 1 > MAX_PATH) | |
| return base::ErrStatus("Directory path %s is too long", dir_path.c_str()); | |
| WIN32_FIND_DATAA ffd; | |
| base::ScopedResource<HANDLE, CloseFindHandle, nullptr, false, | |
| base::PlatformHandleChecker> | |
| hFind(FindFirstFileA(glob_path.c_str(), &ffd)); | |
| if (!hFind) { | |
| // For empty directories, there should be at least one entry '.'. | |
| // If FindFirstFileA returns INVALID_HANDLE_VALUE, this means directory | |
| // couldn't be accessed. | |
| return base::ErrStatus("Failed to open directory %s", cur_dir.c_str()); | |
| } | |
| do { | |
| if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) | |
| continue; | |
| if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | |
| std::string subdir_path = cur_dir + ffd.cFileName + '/'; | |
| dir_queue.push_back(subdir_path); | |
| } else { | |
| const std::string full_path = cur_dir + ffd.cFileName; | |
| PERFETTO_CHECK(full_path.length() > root_dir_path.length()); | |
| output.push_back(full_path.substr(root_dir_path.length())); | |
| } | |
| } while (FindNextFileA(*hFind, &ffd)); | |
| #else | |
| ScopedDir dir = ScopedDir(opendir(cur_dir.c_str())); | |
| if (!dir) { | |
| return base::ErrStatus("Failed to open directory %s", cur_dir.c_str()); | |
| } | |
| for (auto* dirent = readdir(dir.get()); dirent != nullptr; | |
| dirent = readdir(dir.get())) { | |
| if (strcmp(dirent->d_name, ".") == 0 || | |
| strcmp(dirent->d_name, "..") == 0) { | |
| continue; | |
| } | |
| struct stat dirstat; | |
| std::string full_path = cur_dir + dirent->d_name; | |
| PERFETTO_CHECK(stat(full_path.c_str(), &dirstat) == 0); | |
| // MSan's stat() interceptor on glibc 2.35+ does not mark the output | |
| // buffer as initialized (the syscall goes through statx). | |
| PERFETTO_MSAN_UNPOISON(&dirstat, sizeof(dirstat)); | |
| if (S_ISDIR(dirstat.st_mode)) { | |
| dir_queue.push_back(full_path + '/'); | |
| } else if (S_ISREG(dirstat.st_mode)) { | |
| PERFETTO_CHECK(full_path.length() > root_dir_path.length()); | |
| output.push_back(full_path.substr(root_dir_path.length())); | |
| } | |
| } | |
| #endif | |
| } | |
| return base::OkStatus(); | |
| } | |
| base::Status ListDirectories(const std::string& dir_path, | |
| std::vector<std::string>& output) { | |
| std::string normalized_path = dir_path; | |
| if (!normalized_path.empty() && normalized_path.back() != '/' && | |
| normalized_path.back() != '\\') { | |
| normalized_path.push_back('/'); | |
| } | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) | |
| return base::ErrStatus("ListDirectories not supported yet"); | |
| #elif PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| std::string glob_path = normalized_path + "*"; | |
| if (glob_path.length() + 1 > MAX_PATH) { | |
| return base::ErrStatus("Directory path %s is too long", dir_path.c_str()); | |
| } | |
| WIN32_FIND_DATAA ffd; | |
| base::ScopedResource<HANDLE, CloseFindHandle, nullptr, false, | |
| base::PlatformHandleChecker> | |
| hFind(FindFirstFileA(glob_path.c_str(), &ffd)); | |
| if (!hFind) { | |
| return base::ErrStatus("Failed to open directory %s", | |
| normalized_path.c_str()); | |
| } | |
| do { | |
| if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) { | |
| continue; | |
| } | |
| if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | |
| output.push_back(ffd.cFileName); | |
| } | |
| } while (FindNextFileA(*hFind, &ffd)); | |
| #else | |
| ScopedDir dir = ScopedDir(opendir(normalized_path.c_str())); | |
| if (!dir) { | |
| return base::ErrStatus("Failed to open directory %s", | |
| normalized_path.c_str()); | |
| } | |
| for (auto* dirent = readdir(dir.get()); dirent != nullptr; | |
| dirent = readdir(dir.get())) { | |
| if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) { | |
| continue; | |
| } | |
| std::string full_path = normalized_path + dirent->d_name; | |
| struct stat dirstat; | |
| if (stat(full_path.c_str(), &dirstat) == 0 && S_ISDIR(dirstat.st_mode)) { | |
| output.push_back(dirent->d_name); | |
| } | |
| } | |
| #endif | |
| return base::OkStatus(); | |
| } | |
| std::string GetFileExtension(const std::string& filename) { | |
| auto ext_idx = filename.rfind('.'); | |
| if (ext_idx == std::string::npos) | |
| return std::string(); | |
| return filename.substr(ext_idx); | |
| } | |
| std::string Basename(const std::string& path) { | |
| // Handle empty path | |
| if (path.empty()) | |
| return "."; | |
| // Make a copy to work with | |
| std::string p = path; | |
| // Strip trailing slashes (both / and \) | |
| while (p.size() > 1 && (p.back() == '/' || p.back() == '\\')) { | |
| p.pop_back(); | |
| } | |
| // If the path is now empty or just a single slash, return it | |
| if (p.empty() || p == "/" || p == "\\") | |
| return p.empty() ? "/" : p; | |
| // Find the last directory separator (either / or \) | |
| size_t last_sep = p.find_last_of("/\\"); | |
| if (last_sep == std::string::npos) { | |
| // No separator found, the whole path is the basename | |
| return p; | |
| } | |
| // Return everything after the last separator | |
| return p.substr(last_sep + 1); | |
| } | |
| std::string Dirname(const std::string& path) { | |
| // Handle empty path | |
| if (path.empty()) | |
| return "."; | |
| // Make a copy to work with | |
| std::string p = path; | |
| // Strip trailing slashes (both / and \) | |
| while (p.size() > 1 && (p.back() == '/' || p.back() == '\\')) { | |
| p.pop_back(); | |
| } | |
| // If the path is now just a single slash, return it | |
| if (p == "/" || p == "\\") | |
| return p; | |
| // Find the last directory separator (either / or \) | |
| size_t last_sep = p.find_last_of("/\\"); | |
| if (last_sep == std::string::npos) { | |
| // No separator found, return "." | |
| return "."; | |
| } | |
| // If the separator is at position 0, return the root | |
| if (last_sep == 0) | |
| return p.substr(0, 1); // Return "/" or "\" | |
| // Strip trailing slashes from the dirname part | |
| while (last_sep > 0 && (p[last_sep - 1] == '/' || p[last_sep - 1] == '\\')) { | |
| --last_sep; | |
| } | |
| // If we've consumed all characters, return the root | |
| if (last_sep == 0) | |
| return p.substr(0, 1); | |
| // Return everything up to (but not including) the last separator | |
| return p.substr(0, last_sep); | |
| } | |
| size_t PathRootPrefixLength(const std::string& path) { | |
| size_t pos = 0; | |
| // Skip over a leading drive letter, e.g. "C:\foo". | |
| bool has_drive_letter = path.size() >= 2 && path[1] == ':' && | |
| ((path[0] >= 'a' && path[0] <= 'z') || | |
| (path[0] >= 'A' && path[0] <= 'Z')); | |
| if (has_drive_letter) | |
| pos = 2; | |
| size_t end = path.find_first_not_of("/\\", pos); | |
| end = end == std::string::npos ? path.size() : end; | |
| // A drive letter not followed by a separator ("C:foo") is relative to the | |
| // current directory of that drive, not absolute. | |
| return end > pos ? end : 0; | |
| } | |
| bool IsAbsolutePath(const std::string& path) { | |
| return PathRootPrefixLength(path) != 0; | |
| } | |
| base::Status SetFilePermissions(const std::string& file_path, | |
| const std::string& group_name_or_id, | |
| const std::string& mode_bits) { | |
| #ifdef PERFETTO_SET_FILE_PERMISSIONS | |
| PERFETTO_CHECK(!file_path.empty()); | |
| PERFETTO_CHECK(!group_name_or_id.empty()); | |
| // Default |group_id| to -1 for not changing the group ownership. | |
| gid_t group_id = static_cast<gid_t>(-1); | |
| auto maybe_group_id = base::StringToUInt32(group_name_or_id); | |
| if (maybe_group_id) { // A numerical group ID. | |
| group_id = *maybe_group_id; | |
| } else { // A group name. | |
| struct group* file_group = nullptr; | |
| // Query the group ID of |group|. | |
| do { | |
| file_group = getgrnam(group_name_or_id.c_str()); | |
| } while (file_group == nullptr && errno == EINTR); | |
| if (file_group == nullptr) { | |
| return base::ErrStatus("Failed to get group information of %s ", | |
| group_name_or_id.c_str()); | |
| } | |
| group_id = file_group->gr_gid; | |
| } | |
| if (PERFETTO_EINTR(chown(file_path.c_str(), geteuid(), group_id))) { | |
| return base::ErrStatus("Failed to chown %s ", file_path.c_str()); | |
| } | |
| // |mode| accepts values like "0660" as "rw-rw----" mode bits. | |
| auto mode_value = base::StringToInt32(mode_bits, 8); | |
| if (!(mode_bits.size() == 4 && mode_value.has_value())) { | |
| return base::ErrStatus( | |
| "The chmod mode bits must be a 4-digit octal number, e.g. 0660"); | |
| } | |
| if (PERFETTO_EINTR( | |
| chmod(file_path.c_str(), static_cast<mode_t>(mode_value.value())))) { | |
| return base::ErrStatus("Failed to chmod %s", file_path.c_str()); | |
| } | |
| return base::OkStatus(); | |
| #else | |
| base::ignore_result(file_path); | |
| base::ignore_result(group_name_or_id); | |
| base::ignore_result(mode_bits); | |
| return base::ErrStatus( | |
| "Setting file permissions is not supported on this platform"); | |
| #endif | |
| } | |
| std::optional<uint64_t> GetFileSize(const std::string& file_path) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| // This does not use base::OpenFile to avoid getting an exclusive lock. | |
| base::ScopedPlatformHandle fd( | |
| CreateFileA(file_path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ, | |
| nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); | |
| #else | |
| base::ScopedFile fd(base::OpenFile(file_path, O_RDONLY | O_CLOEXEC)); | |
| #endif | |
| if (!fd) { | |
| return std::nullopt; | |
| } | |
| return GetFileSize(*fd); | |
| } | |
| std::optional<uint64_t> GetFileSize(PlatformHandle fd) { | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| LARGE_INTEGER file_size; | |
| file_size.QuadPart = 0; | |
| if (!GetFileSizeEx(fd, &file_size)) { | |
| return std::nullopt; | |
| } | |
| static_assert(sizeof(decltype(file_size.QuadPart)) <= sizeof(uint64_t)); | |
| return static_cast<uint64_t>(file_size.QuadPart); | |
| #else | |
| struct stat buf{}; | |
| if (fstat(fd, &buf) == -1) { | |
| return std::nullopt; | |
| } | |
| static_assert(sizeof(decltype(buf.st_size)) <= sizeof(uint64_t)); | |
| return static_cast<uint64_t>(buf.st_size); | |
| #endif | |
| } | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| std::optional<uint64_t> GetFileSize(int fd) { | |
| return GetFileSize(reinterpret_cast<PlatformHandle>(_get_osfhandle(fd))); | |
| } | |
| #endif | |
| // LinuxFileWatch | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ | |
| PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) | |
| namespace { | |
| // Implementation class extends LinuxFileWatch | |
| class FileWatchImpl : public LinuxFileWatch { | |
| public: | |
| FileWatchImpl(TaskRunner* tr, | |
| std::string fn, | |
| ScopedFile ifd, | |
| std::function<void()> cb) | |
| : task_runner_(tr), | |
| file_base_name_(std::move(fn)), | |
| inotify_fd_(std::move(ifd)), | |
| callback_(std::move(cb)), | |
| weak_ptr_factory_(this) { | |
| task_runner_->AddFileDescriptorWatch( | |
| *inotify_fd_, [weak_handle = weak_ptr_factory_.GetWeakPtr()] { | |
| if (!weak_handle) | |
| return; | |
| alignas(struct inotify_event) char buf[4096]; | |
| ssize_t rsize = | |
| base::Read(*weak_handle->inotify_fd_, buf, sizeof(buf)); | |
| if (rsize <= 0) | |
| return; | |
| for (ssize_t i = 0; i < rsize;) { | |
| auto* evt = reinterpret_cast<struct inotify_event*>(&buf[i]); | |
| i += static_cast<ssize_t>(sizeof(inotify_event) + evt->len); | |
| if (evt->len > 0 && (evt->mask & IN_CREATE)) { | |
| if (weak_handle->file_base_name_ == evt->name) { | |
| weak_handle->task_runner_->PostTask(weak_handle->callback_); | |
| return; | |
| } | |
| } | |
| } // for(evt); | |
| }); | |
| } | |
| ~FileWatchImpl() override { | |
| if (!inotify_fd_) | |
| return; | |
| task_runner_->RemoveFileDescriptorWatch(*inotify_fd_); | |
| inotify_fd_.reset(); | |
| } | |
| private: | |
| TaskRunner* task_runner_ = nullptr; | |
| std::string file_base_name_; // Only the name without the path. | |
| ScopedFile inotify_fd_; | |
| std::function<void()> callback_; | |
| WeakPtrFactory<FileWatchImpl> weak_ptr_factory_; // Keep last. | |
| }; | |
| } // namespace | |
| std::unique_ptr<LinuxFileWatch> LinuxFileWatch::WatchFileCreation( | |
| TaskRunner* task_runner, | |
| const char* path, | |
| std::function<void()> callback) { | |
| if (!path || path[0] == '\0') { | |
| // We can add a inotify watch only for valid filesystem paths. | |
| return nullptr; | |
| } | |
| ScopedFile inotify_fd(inotify_init1(IN_CLOEXEC | IN_NONBLOCK)); | |
| if (!inotify_fd) { | |
| PERFETTO_DLOG("inotify_init() failed"); | |
| return nullptr; | |
| } | |
| // Here we watch the parent dir, not the file itself. We cannot watch for | |
| // a file that doesn't exist yet, because the kernel internally attaches the | |
| // watch to an inode, and a non-existing file doesn't have inodes. | |
| // We are not interested in IN_MOVED_TO because we don't mv files. | |
| std::string file_dir = Dirname(path); | |
| if (inotify_add_watch(*inotify_fd, file_dir.c_str(), IN_CREATE) < 0) { | |
| PERFETTO_DLOG("inotify_add_watch(%s) failed", file_dir.c_str()); | |
| return nullptr; | |
| } | |
| std::string file_base_name = Basename(path); | |
| return std::unique_ptr<LinuxFileWatch>( | |
| new FileWatchImpl(task_runner, std::move(file_base_name), | |
| std::move(inotify_fd), std::move(callback))); | |
| } | |
| LinuxFileWatch::~LinuxFileWatch() = default; | |
| #else | |
| std::unique_ptr<LinuxFileWatch> LinuxFileWatch::WatchFileCreation( | |
| TaskRunner*, | |
| const char*, | |
| std::function<void()>) { | |
| return nullptr; // Not supported on other platforms. | |
| } | |
| LinuxFileWatch::~LinuxFileWatch() = default; | |
| #endif // OS_LINUX || OS_ANDROID | |
| } // namespace base | |
| } // namespace perfetto |