Index: chromecast/crash/linux/synchronized_minidump_manager.cc |
diff --git a/chromecast/crash/linux/synchronized_minidump_manager.cc b/chromecast/crash/linux/synchronized_minidump_manager.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..358427a2dd31a3532e82a9d6703bd78f00f161e2 |
--- /dev/null |
+++ b/chromecast/crash/linux/synchronized_minidump_manager.cc |
@@ -0,0 +1,219 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chromecast/crash/linux/synchronized_minidump_manager.h" |
+ |
+#include <dirent.h> |
+#include <errno.h> |
+#include <fcntl.h> |
+#include <grp.h> |
+#include <string.h> |
+#include <sys/file.h> |
+#include <sys/stat.h> |
+#include <sys/types.h> |
+#include <unistd.h> |
+ |
+#include <fstream> |
+ |
+#include "base/logging.h" |
+#include "chromecast/base/path_utils.h" |
+#include "chromecast/crash/linux/dump_info.h" |
+ |
+namespace chromecast { |
+ |
+namespace { |
+ |
+const mode_t kDirMode = 0770; |
+const mode_t kFileMode = 0660; |
+const char kLockfileName[] = "lockfile"; |
+const char kMinidumpsDir[] = "minidumps"; |
+ |
+} // namespace |
+ |
+SynchronizedMinidumpManager::SynchronizedMinidumpManager() |
+ : non_blocking_(false), lockfile_fd_(-1) { |
+ dump_path_ = GetHomePathASCII(kMinidumpsDir); |
+ lockfile_path_ = dump_path_.Append(kLockfileName).value(); |
+} |
+ |
+SynchronizedMinidumpManager::~SynchronizedMinidumpManager() { |
+ // Release the lock if held. |
+ ReleaseLockFile(); |
+} |
+ |
+// TODO(slan): Move some of this pruning logic to ReleaseLockFile? |
+int SynchronizedMinidumpManager::GetNumDumps(bool delete_all_dumps) { |
+ DIR* dirp; |
+ struct dirent* dptr; |
+ int num_dumps = 0; |
+ |
+ // folder does not exist |
+ dirp = opendir(dump_path_.value().c_str()); |
+ if (dirp == NULL) { |
+ LOG(ERROR) << "Unable to open directory " << dump_path_.value(); |
+ return 0; |
+ } |
+ |
+ while ((dptr = readdir(dirp)) != NULL) { |
+ struct stat buf; |
+ const std::string file_fullname = dump_path_.value() + "/" + dptr->d_name; |
+ if (lstat(file_fullname.c_str(), &buf) == -1 || !S_ISREG(buf.st_mode)) { |
+ // if we cannot lstat this file, it is probably bad, so skip |
+ // if the file is not regular, skip |
+ continue; |
+ } |
+ // 'lockfile' is not counted |
+ if (lockfile_path_ != file_fullname) { |
+ ++num_dumps; |
+ if (delete_all_dumps) { |
+ LOG(INFO) << "Removing " << dptr->d_name |
+ << "which was not in the lockfile"; |
+ if (remove(file_fullname.c_str()) < 0) { |
+ LOG(INFO) << "remove failed. error " << strerror(errno); |
+ } |
+ } |
+ } |
+ } |
+ |
+ closedir(dirp); |
+ return num_dumps; |
+} |
+ |
+int SynchronizedMinidumpManager::AcquireLockAndDoWork() { |
+ int success = -1; |
+ if (AcquireLockFile() >= 0) { |
+ success = DoWork(); |
+ ReleaseLockFile(); |
+ } |
+ return success; |
+} |
+ |
+const ScopedVector<DumpInfo>& SynchronizedMinidumpManager::GetDumpMetadata() { |
+ DCHECK_GE(lockfile_fd_, 0); |
+ if (!dump_metadata_) |
+ ParseLockFile(); |
+ return *dump_metadata_; |
+} |
+ |
+int SynchronizedMinidumpManager::AcquireLockFile() { |
+ DCHECK_LT(lockfile_fd_, 0); |
+ // Make the directory for the minidumps if it does not exist. |
+ if (mkdir(dump_path_.value().c_str(), kDirMode) < 0 && errno != EEXIST) { |
+ LOG(ERROR) << "mkdir for " << dump_path_.value().c_str() |
+ << " failed. error = " << strerror(errno); |
+ return -1; |
+ } |
+ |
+ // Open the lockfile. Create it if it does not exist. |
+ lockfile_fd_ = open(lockfile_path_.c_str(), O_RDWR | O_CREAT, kFileMode); |
+ |
+ // If opening or creating the lockfile failed, we don't want to proceed |
+ // with dump writing for fear of exhausting up system resources. |
+ if (lockfile_fd_ < 0) { |
+ LOG(ERROR) << "open lockfile failed " << lockfile_path_; |
+ return -1; |
+ } |
+ |
+ // Acquire the lock on the file. Whether or not we are in non-blocking mode, |
+ // flock failure means that we did not acquire it and this method should fail. |
+ int operation_mode = non_blocking_ ? (LOCK_EX | LOCK_NB) : LOCK_EX; |
+ if (flock(lockfile_fd_, operation_mode) < 0) { |
+ ReleaseLockFile(); |
+ LOG(INFO) << "flock lockfile failed, error = " << strerror(errno); |
+ return -1; |
+ } |
+ |
+ // The lockfile is open and locked. Parse it to provide subclasses with a |
+ // record of all the current dumps. |
+ if (ParseLockFile() < 0) { |
+ LOG(ERROR) << "Lockfile did not parse correctly. "; |
+ return -1; |
+ } |
+ |
+ // We successfully have acquired the lock. |
+ return 0; |
+} |
+ |
+int SynchronizedMinidumpManager::ParseLockFile() { |
+ DCHECK_GE(lockfile_fd_, 0); |
+ DCHECK(!dump_metadata_); |
+ |
+ scoped_ptr<ScopedVector<DumpInfo> > dumps(new ScopedVector<DumpInfo>()); |
+ std::string entry; |
+ |
+ // Instead of using |lockfile_fd_|, use <fstream> for readability. |
+ std::ifstream in(lockfile_path_); |
+ if (!in.is_open()) { |
+ NOTREACHED(); |
+ LOG(ERROR) << lockfile_path_ << " could not be opened."; |
+ return -1; |
+ } |
+ |
+ // Grab each entry. |
+ while (std::getline(in, entry)) { |
+ scoped_ptr<DumpInfo> info(new DumpInfo(entry)); |
+ if (info->valid() && info->crashed_process_dump().size() > 0) { |
+ dumps->push_back(info.Pass()); |
+ } else { |
+ LOG(WARNING) << "Entry is not valid: " << entry; |
+ return -1; |
+ } |
+ } |
+ |
+ dump_metadata_ = dumps.Pass(); |
+ return 0; |
+} |
+ |
+int SynchronizedMinidumpManager::AddEntryToLockFile(const DumpInfo& dump_info) { |
+ DCHECK_LE(0, lockfile_fd_); |
+ |
+ // Make sure dump_info is valid. |
+ if (!dump_info.valid()) { |
+ LOG(ERROR) << "Entry to be added is invalid"; |
+ return -1; |
+ } |
+ |
+ // Open the file. |
+ std::ofstream out(lockfile_path_, std::ios::app); |
+ if (!out.is_open()) { |
+ NOTREACHED() << "Lockfile would not open."; |
+ return -1; |
+ } |
+ |
+ // Write the string and close the file. |
+ out << dump_info.entry(); |
+ out.close(); |
+ return 0; |
+} |
+ |
+int SynchronizedMinidumpManager::RemoveEntryFromLockFile(int index) { |
+ const auto& entries = GetDumpMetadata(); |
+ if (index < 0 || static_cast<size_t>(index) >= entries.size()) |
+ return -1; |
+ |
+ // Remove the entry and write all remaining entries to file. |
+ dump_metadata_->erase(dump_metadata_->begin() + index); |
+ std::ofstream out(lockfile_path_); |
+ for (auto info : *dump_metadata_) { |
+ out << info->entry(); |
+ } |
+ out.close(); |
+ return 0; |
+} |
+ |
+void SynchronizedMinidumpManager::ReleaseLockFile() { |
+ // flock is associated with the fd entry in the open fd table, so closing |
+ // all fd's will release the lock. To be safe, we explicitly unlock. |
+ if (lockfile_fd_ >= 0) { |
+ flock(lockfile_fd_, LOCK_UN); |
+ close(lockfile_fd_); |
+ |
+ // We may use this object again, so we should reset this. |
+ lockfile_fd_ = -1; |
+ } |
+ |
+ dump_metadata_.reset(); |
+} |
+ |
+} // namespace chromecast |