| //===-- sanitizer_file.cpp -----------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===---------------------------------------------------------------------===// |
| // |
| // This file is shared between AddressSanitizer and ThreadSanitizer |
| // run-time libraries. It defines filesystem-related interfaces. This |
| // is separate from sanitizer_common.cpp so that it's simpler to disable |
| // all the filesystem support code for a port that doesn't use it. |
| // |
| //===---------------------------------------------------------------------===// |
| |
| #include "sanitizer_platform.h" |
| |
| #if !SANITIZER_FUCHSIA |
| |
| #include "sanitizer_common.h" |
| #include "sanitizer_file.h" |
| # include "sanitizer_interface_internal.h" |
| |
| namespace __sanitizer { |
| |
| void CatastrophicErrorWrite(const char *buffer, uptr length) { |
| WriteToFile(kStderrFd, buffer, length); |
| } |
| |
| StaticSpinMutex report_file_mu; |
| ReportFile report_file = {&report_file_mu, kStderrFd, "", "", 0}; |
| |
| void RawWrite(const char *buffer) { |
| report_file.Write(buffer, internal_strlen(buffer)); |
| } |
| |
| void ReportFile::ReopenIfNecessary() { |
| mu->CheckLocked(); |
| if (fd == kStdoutFd || fd == kStderrFd) return; |
| |
| uptr pid = internal_getpid(); |
| // If in tracer, use the parent's file. |
| if (pid == stoptheworld_tracer_pid) |
| pid = stoptheworld_tracer_ppid; |
| if (fd != kInvalidFd) { |
| // If the report file is already opened by the current process, |
| // do nothing. Otherwise the report file was opened by the parent |
| // process, close it now. |
| if (fd_pid == pid) |
| return; |
| else |
| CloseFile(fd); |
| } |
| |
| const char *exe_name = GetProcessName(); |
| if (common_flags()->log_exe_name && exe_name) { |
| internal_snprintf(full_path, kMaxPathLength, "%s.%s.%zu", path_prefix, |
| exe_name, pid); |
| } else { |
| internal_snprintf(full_path, kMaxPathLength, "%s.%zu", path_prefix, pid); |
| } |
| if (common_flags()->log_suffix) { |
| internal_strlcat(full_path, common_flags()->log_suffix, kMaxPathLength); |
| } |
| error_t err; |
| fd = OpenFile(full_path, WrOnly, &err); |
| if (fd == kInvalidFd) { |
| const char *ErrorMsgPrefix = "ERROR: Can't open file: "; |
| WriteToFile(kStderrFd, ErrorMsgPrefix, internal_strlen(ErrorMsgPrefix)); |
| WriteToFile(kStderrFd, full_path, internal_strlen(full_path)); |
| char errmsg[100]; |
| internal_snprintf(errmsg, sizeof(errmsg), " (reason: %d)\n", err); |
| WriteToFile(kStderrFd, errmsg, internal_strlen(errmsg)); |
| Die(); |
| } |
| fd_pid = pid; |
| } |
| |
| static void RecursiveCreateParentDirs(char *path) { |
| if (path[0] == '\0') |
| return; |
| for (int i = 1; path[i] != '\0'; ++i) { |
| char save = path[i]; |
| if (!IsPathSeparator(path[i])) |
| continue; |
| path[i] = '\0'; |
| if (!DirExists(path) && !CreateDir(path)) { |
| const char *ErrorMsgPrefix = "ERROR: Can't create directory: "; |
| WriteToFile(kStderrFd, ErrorMsgPrefix, internal_strlen(ErrorMsgPrefix)); |
| WriteToFile(kStderrFd, path, internal_strlen(path)); |
| const char *ErrorMsgSuffix = "\n"; |
| WriteToFile(kStderrFd, ErrorMsgSuffix, internal_strlen(ErrorMsgSuffix)); |
| Die(); |
| } |
| path[i] = save; |
| } |
| } |
| |
| /// Parse the report path \p pattern and copy the parsed path to \p dest. |
| /// |
| /// * `%%` becomes `%` |
| /// * `%H` expands to the environment variable `HOME` |
| /// * `%t` expands to the environment variable `TMPDIR` |
| /// * `%p` expands to the process ID (PID) |
| static void ParseAndSetPath(const char *pattern, char *dest, |
| const uptr dest_size) { |
| CHECK(pattern); |
| CHECK(dest); |
| CHECK_GE(dest_size, 1); |
| dest[0] = '\0'; |
| uptr next_substr_start_idx = 0; |
| for (uptr i = 0; i < internal_strlen(pattern) - 1; i++) { |
| if (pattern[i] != '%') |
| continue; |
| int bytes_to_copy = i - next_substr_start_idx; |
| // Copy over previous substring. |
| CHECK_LT(internal_strlcat(dest, pattern + next_substr_start_idx, |
| internal_strlen(dest) + bytes_to_copy + 1), |
| dest_size); |
| const char *str_to_concat; |
| switch (pattern[++i]) { |
| case '%': |
| str_to_concat = "%"; |
| break; |
| case 'H': |
| str_to_concat = GetEnv("HOME"); |
| break; |
| case 't': |
| str_to_concat = GetEnv("TMPDIR"); |
| break; |
| case 'p': { |
| // Use printf directly to write the PID since it's not a static string. |
| int remaining_capacity = dest_size - internal_strlen(dest); |
| int bytes_copied = |
| internal_snprintf(dest + internal_strlen(dest), remaining_capacity, |
| "%ld", internal_getpid()); |
| CHECK_GT(bytes_copied, 0); |
| CHECK_LT(bytes_copied, remaining_capacity); |
| str_to_concat = ""; |
| break; |
| } |
| default: { |
| // Invalid pattern: fallback to original pattern. |
| const char *message = "ERROR: Unexpected pattern: "; |
| WriteToFile(kStderrFd, message, internal_strlen(message)); |
| WriteToFile(kStderrFd, pattern, internal_strlen(pattern)); |
| WriteToFile(kStderrFd, "\n", internal_strlen("\n")); |
| CHECK_LT(internal_strlcpy(dest, pattern, dest_size), dest_size); |
| return; |
| } |
| } |
| CHECK(str_to_concat); |
| CHECK_LT(internal_strlcat(dest, str_to_concat, dest_size), dest_size); |
| next_substr_start_idx = i + 1; |
| } |
| CHECK_LT(internal_strlcat(dest, pattern + next_substr_start_idx, dest_size), |
| dest_size); |
| } |
| |
| void ReportFile::SetReportPath(const char *path) { |
| if (path) { |
| uptr len = internal_strlen(path); |
| if (len > sizeof(path_prefix) - 100) { |
| const char *message = "ERROR: Path is too long: "; |
| WriteToFile(kStderrFd, message, internal_strlen(message)); |
| WriteToFile(kStderrFd, path, 8); |
| message = "...\n"; |
| WriteToFile(kStderrFd, message, internal_strlen(message)); |
| Die(); |
| } |
| } |
| |
| SpinMutexLock l(mu); |
| if (fd != kStdoutFd && fd != kStderrFd && fd != kInvalidFd) |
| CloseFile(fd); |
| fd = kInvalidFd; |
| if (!path || internal_strcmp(path, "stderr") == 0) { |
| fd = kStderrFd; |
| } else if (internal_strcmp(path, "stdout") == 0) { |
| fd = kStdoutFd; |
| } else { |
| ParseAndSetPath(path, path_prefix, kMaxPathLength); |
| RecursiveCreateParentDirs(path_prefix); |
| } |
| } |
| |
| const char *ReportFile::GetReportPath() { |
| SpinMutexLock l(mu); |
| ReopenIfNecessary(); |
| return full_path; |
| } |
| |
| bool ReadFileToBuffer(const char *file_name, char **buff, uptr *buff_size, |
| uptr *read_len, uptr max_len, error_t *errno_p) { |
| *buff = nullptr; |
| *buff_size = 0; |
| *read_len = 0; |
| if (!max_len) |
| return true; |
| uptr PageSize = GetPageSizeCached(); |
| uptr kMinFileLen = Min(PageSize, max_len); |
| |
| // The files we usually open are not seekable, so try different buffer sizes. |
| for (uptr size = kMinFileLen;; size = Min(size * 2, max_len)) { |
| UnmapOrDie(*buff, *buff_size); |
| *buff = (char*)MmapOrDie(size, __func__); |
| *buff_size = size; |
| fd_t fd = OpenFile(file_name, RdOnly, errno_p); |
| if (fd == kInvalidFd) { |
| UnmapOrDie(*buff, *buff_size); |
| return false; |
| } |
| *read_len = 0; |
| // Read up to one page at a time. |
| bool reached_eof = false; |
| while (*read_len < size) { |
| uptr just_read; |
| if (!ReadFromFile(fd, *buff + *read_len, size - *read_len, &just_read, |
| errno_p)) { |
| UnmapOrDie(*buff, *buff_size); |
| CloseFile(fd); |
| return false; |
| } |
| *read_len += just_read; |
| if (just_read == 0 || *read_len == max_len) { |
| reached_eof = true; |
| break; |
| } |
| } |
| CloseFile(fd); |
| if (reached_eof) // We've read the whole file. |
| break; |
| } |
| return true; |
| } |
| |
| bool ReadFileToVector(const char *file_name, |
| InternalMmapVectorNoCtor<char> *buff, uptr max_len, |
| error_t *errno_p) { |
| buff->clear(); |
| if (!max_len) |
| return true; |
| uptr PageSize = GetPageSizeCached(); |
| fd_t fd = OpenFile(file_name, RdOnly, errno_p); |
| if (fd == kInvalidFd) |
| return false; |
| uptr read_len = 0; |
| while (read_len < max_len) { |
| if (read_len >= buff->size()) |
| buff->resize(Min(Max(PageSize, read_len * 2), max_len)); |
| CHECK_LT(read_len, buff->size()); |
| CHECK_LE(buff->size(), max_len); |
| uptr just_read; |
| if (!ReadFromFile(fd, buff->data() + read_len, buff->size() - read_len, |
| &just_read, errno_p)) { |
| CloseFile(fd); |
| return false; |
| } |
| read_len += just_read; |
| if (!just_read) |
| break; |
| } |
| CloseFile(fd); |
| buff->resize(read_len); |
| return true; |
| } |
| |
| static const char kPathSeparator = SANITIZER_WINDOWS ? ';' : ':'; |
| |
| char *FindPathToBinary(const char *name) { |
| if (FileExists(name)) { |
| return internal_strdup(name); |
| } |
| |
| const char *path = GetEnv("PATH"); |
| if (!path) |
| return nullptr; |
| uptr name_len = internal_strlen(name); |
| InternalMmapVector<char> buffer(kMaxPathLength); |
| const char *beg = path; |
| while (true) { |
| const char *end = internal_strchrnul(beg, kPathSeparator); |
| uptr prefix_len = end - beg; |
| if (prefix_len + name_len + 2 <= kMaxPathLength) { |
| internal_memcpy(buffer.data(), beg, prefix_len); |
| buffer[prefix_len] = '/'; |
| internal_memcpy(&buffer[prefix_len + 1], name, name_len); |
| buffer[prefix_len + 1 + name_len] = '\0'; |
| if (FileExists(buffer.data())) |
| return internal_strdup(buffer.data()); |
| } |
| if (*end == '\0') break; |
| beg = end + 1; |
| } |
| return nullptr; |
| } |
| |
| } // namespace __sanitizer |
| |
| using namespace __sanitizer; |
| |
| extern "C" { |
| void __sanitizer_set_report_path(const char *path) { |
| report_file.SetReportPath(path); |
| } |
| |
| void __sanitizer_set_report_fd(void *fd) { |
| report_file.fd = (fd_t)reinterpret_cast<uptr>(fd); |
| report_file.fd_pid = internal_getpid(); |
| } |
| |
| const char *__sanitizer_get_report_path() { |
| return report_file.GetReportPath(); |
| } |
| } // extern "C" |
| |
| #endif // !SANITIZER_FUCHSIA |