Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / chromecast / crash / linux / synchronized_minidump_manager.cc
bloba3322124bd5b4c6d2daed8f6f719eec6ed745162
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chromecast/crash/linux/synchronized_minidump_manager.h"
7 #include <dirent.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <string.h>
11 #include <sys/file.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <time.h>
15 #include <unistd.h>
17 #include "base/files/file_util.h"
18 #include "base/logging.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/stringprintf.h"
21 #include "chromecast/base/path_utils.h"
22 #include "chromecast/base/serializers.h"
23 #include "chromecast/crash/linux/dump_info.h"
25 // if |cond| is false, returns |retval|.
26 #define RCHECK(cond, retval) \
27 do { \
28 if (!(cond)) { \
29 return (retval); \
30 } \
31 } while (0)
33 namespace chromecast {
35 namespace {
37 const mode_t kDirMode = 0770;
38 const mode_t kFileMode = 0660;
39 const char kLockfileName[] = "lockfile";
40 const char kMetadataName[] = "metadata";
41 const char kMinidumpsDir[] = "minidumps";
43 const char kLockfileRatelimitKey[] = "ratelimit";
44 const char kLockfileRatelimitPeriodStartKey[] = "period_start";
45 const char kLockfileRatelimitPeriodDumpsKey[] = "period_dumps";
46 const std::size_t kLockfileNumRatelimitParams = 2;
48 // Gets the ratelimit parameter dictionary given a deserialized |metadata|.
49 // Returns nullptr if invalid.
50 base::DictionaryValue* GetRatelimitParams(base::Value* metadata) {
51 base::DictionaryValue* dict;
52 base::DictionaryValue* ratelimit_params;
53 if (!metadata || !metadata->GetAsDictionary(&dict) ||
54 !dict->GetDictionary(kLockfileRatelimitKey, &ratelimit_params)) {
55 return nullptr;
58 return ratelimit_params;
61 // Returns the time of the current ratelimit period's start in |metadata|.
62 // Returns (time_t)-1 if an error occurs.
63 time_t GetRatelimitPeriodStart(base::Value* metadata) {
64 time_t result = static_cast<time_t>(-1);
65 base::DictionaryValue* ratelimit_params = GetRatelimitParams(metadata);
66 RCHECK(ratelimit_params, result);
68 std::string period_start_str;
69 RCHECK(ratelimit_params->GetString(kLockfileRatelimitPeriodStartKey,
70 &period_start_str),
71 result);
73 errno = 0;
74 result = static_cast<time_t>(strtoll(period_start_str.c_str(), nullptr, 0));
75 if (errno != 0) {
76 return static_cast<time_t>(-1);
79 return result;
82 // Sets the time of the current ratelimit period's start in |metadata| to
83 // |period_start|. Returns 0 on success, < 0 on error.
84 int SetRatelimitPeriodStart(base::Value* metadata, time_t period_start) {
85 DCHECK_GE(period_start, 0);
87 base::DictionaryValue* ratelimit_params = GetRatelimitParams(metadata);
88 RCHECK(ratelimit_params, -1);
90 std::string period_start_str =
91 base::StringPrintf("%lld", static_cast<long long>(period_start));
92 ratelimit_params->SetString(kLockfileRatelimitPeriodStartKey,
93 period_start_str);
94 return 0;
97 // Gets the number of dumps added to |metadata| in the current ratelimit
98 // period. Returns < 0 on error.
99 int GetRatelimitPeriodDumps(base::Value* metadata) {
100 int period_dumps = -1;
102 base::DictionaryValue* ratelimit_params = GetRatelimitParams(metadata);
103 if (!ratelimit_params ||
104 !ratelimit_params->GetInteger(kLockfileRatelimitPeriodDumpsKey,
105 &period_dumps)) {
106 return -1;
109 return period_dumps;
112 // Sets the current ratelimit period's number of dumps in |metadata| to
113 // |period_dumps|. Returns 0 on success, < 0 on error.
114 int SetRatelimitPeriodDumps(base::Value* metadata, int period_dumps) {
115 DCHECK_GE(period_dumps, 0);
117 base::DictionaryValue* ratelimit_params = GetRatelimitParams(metadata);
118 RCHECK(ratelimit_params, -1);
120 ratelimit_params->SetInteger(kLockfileRatelimitPeriodDumpsKey, period_dumps);
122 return 0;
125 // Increment the number of dumps in the current ratelimit period in deserialized
126 // |metadata| by |increment|. Returns 0 on success, < 0 on error.
127 int IncrementCurrentPeriodDumps(base::Value* metadata, int increment) {
128 DCHECK_GE(increment, 0);
129 int last_dumps = GetRatelimitPeriodDumps(metadata);
130 RCHECK(last_dumps >= 0, -1);
132 return SetRatelimitPeriodDumps(metadata, last_dumps + increment);
135 // Returns true if |metadata| contains valid metadata, false otherwise.
136 bool ValidateMetadata(base::Value* metadata) {
137 RCHECK(metadata, false);
139 // Validate ratelimit params
140 base::DictionaryValue* ratelimit_params = GetRatelimitParams(metadata);
142 return ratelimit_params &&
143 ratelimit_params->size() == kLockfileNumRatelimitParams &&
144 GetRatelimitPeriodStart(metadata) >= 0 &&
145 GetRatelimitPeriodDumps(metadata) >= 0;
148 } // namespace
150 const int SynchronizedMinidumpManager::kMaxLockfileDumps = 5;
152 // One day
153 const int SynchronizedMinidumpManager::kRatelimitPeriodSeconds = 24 * 3600;
154 const int SynchronizedMinidumpManager::kRatelimitPeriodMaxDumps = 100;
156 SynchronizedMinidumpManager::SynchronizedMinidumpManager()
157 : non_blocking_(false),
158 dump_path_(GetHomePathASCII(kMinidumpsDir)),
159 lockfile_path_(dump_path_.Append(kLockfileName).value()),
160 metadata_path_(dump_path_.Append(kMetadataName).value()),
161 lockfile_fd_(-1) {
164 SynchronizedMinidumpManager::~SynchronizedMinidumpManager() {
165 // Release the lock if held.
166 ReleaseLockFile();
169 // TODO(slan): Move some of this pruning logic to ReleaseLockFile?
170 int SynchronizedMinidumpManager::GetNumDumps(bool delete_all_dumps) {
171 DIR* dirp;
172 struct dirent* dptr;
173 int num_dumps = 0;
175 // folder does not exist
176 dirp = opendir(dump_path_.value().c_str());
177 if (dirp == NULL) {
178 LOG(ERROR) << "Unable to open directory " << dump_path_.value();
179 return 0;
182 while ((dptr = readdir(dirp)) != NULL) {
183 struct stat buf;
184 const std::string file_fullname = dump_path_.value() + "/" + dptr->d_name;
185 if (lstat(file_fullname.c_str(), &buf) == -1 || !S_ISREG(buf.st_mode)) {
186 // if we cannot lstat this file, it is probably bad, so skip
187 // if the file is not regular, skip
188 continue;
191 // 'lockfile' and 'metadata' is not counted
192 if (lockfile_path_ != file_fullname && metadata_path_ != file_fullname) {
193 ++num_dumps;
194 if (delete_all_dumps) {
195 LOG(INFO) << "Removing " << dptr->d_name
196 << "which was not in the lockfile";
197 if (remove(file_fullname.c_str()) < 0) {
198 LOG(INFO) << "remove failed. error " << strerror(errno);
204 closedir(dirp);
205 return num_dumps;
208 int SynchronizedMinidumpManager::AcquireLockAndDoWork() {
209 int success = -1;
210 if (AcquireLockFile() >= 0) {
211 success = DoWork();
212 ReleaseLockFile();
214 return success;
217 int SynchronizedMinidumpManager::AcquireLockFile() {
218 DCHECK_LT(lockfile_fd_, 0);
219 // Make the directory for the minidumps if it does not exist.
220 if (mkdir(dump_path_.value().c_str(), kDirMode) < 0 && errno != EEXIST) {
221 LOG(ERROR) << "mkdir for " << dump_path_.value().c_str()
222 << " failed. error = " << strerror(errno);
223 return -1;
226 // Open the lockfile. Create it if it does not exist.
227 lockfile_fd_ = open(lockfile_path_.c_str(), O_RDWR | O_CREAT, kFileMode);
229 // If opening or creating the lockfile failed, we don't want to proceed
230 // with dump writing for fear of exhausting up system resources.
231 if (lockfile_fd_ < 0) {
232 LOG(ERROR) << "open lockfile failed " << lockfile_path_;
233 return -1;
236 // Acquire the lock on the file. Whether or not we are in non-blocking mode,
237 // flock failure means that we did not acquire it and this method should fail.
238 int operation_mode = non_blocking_ ? (LOCK_EX | LOCK_NB) : LOCK_EX;
239 if (flock(lockfile_fd_, operation_mode) < 0) {
240 ReleaseLockFile();
241 LOG(INFO) << "flock lockfile failed, error = " << strerror(errno);
242 return -1;
245 // The lockfile is open and locked. Parse it to provide subclasses with a
246 // record of all the current dumps.
247 if (ParseFiles() < 0) {
248 LOG(ERROR) << "Lockfile did not parse correctly. ";
249 if (InitializeFiles() < 0 || ParseFiles() < 0) {
250 LOG(ERROR) << "Failed to create a new lock file!";
251 return -1;
255 DCHECK(dumps_);
256 DCHECK(metadata_);
258 // We successfully have acquired the lock.
259 return 0;
262 int SynchronizedMinidumpManager::ParseFiles() {
263 DCHECK_GE(lockfile_fd_, 0);
264 DCHECK(!dumps_);
265 DCHECK(!metadata_);
267 std::string lockfile;
268 RCHECK(ReadFileToString(base::FilePath(lockfile_path_), &lockfile), -1);
270 std::vector<std::string> lines = base::SplitString(
271 lockfile, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
273 scoped_ptr<base::ListValue> dumps = make_scoped_ptr(new base::ListValue());
275 // Validate dumps
276 for (const std::string& line : lines) {
277 if (line.size() == 0)
278 continue;
279 scoped_ptr<base::Value> dump_info = DeserializeFromJson(line);
280 DumpInfo info(dump_info.get());
281 RCHECK(info.valid(), -1);
282 dumps->Append(dump_info.Pass());
285 scoped_ptr<base::Value> metadata =
286 DeserializeJsonFromFile(base::FilePath(metadata_path_));
287 RCHECK(ValidateMetadata(metadata.get()), -1);
289 dumps_ = dumps.Pass();
290 metadata_ = metadata.Pass();
291 return 0;
294 int SynchronizedMinidumpManager::WriteFiles(const base::ListValue* dumps,
295 const base::Value* metadata) {
296 DCHECK(dumps);
297 DCHECK(metadata);
298 std::string lockfile;
300 for (const base::Value* elem : *dumps) {
301 scoped_ptr<std::string> dump_info = SerializeToJson(*elem);
302 RCHECK(dump_info, -1);
303 lockfile += *dump_info;
304 lockfile += "\n"; // Add line seperatators
307 if (WriteFile(base::FilePath(lockfile_path_),
308 lockfile.c_str(),
309 lockfile.size()) < 0) {
310 return -1;
313 return SerializeJsonToFile(base::FilePath(metadata_path_), *metadata) ? 0
314 : -1;
317 int SynchronizedMinidumpManager::InitializeFiles() {
318 scoped_ptr<base::DictionaryValue> metadata =
319 make_scoped_ptr(new base::DictionaryValue());
321 base::DictionaryValue* ratelimit_fields = new base::DictionaryValue();
322 metadata->Set(kLockfileRatelimitKey, make_scoped_ptr(ratelimit_fields));
323 ratelimit_fields->SetString(kLockfileRatelimitPeriodStartKey, "0");
324 ratelimit_fields->SetInteger(kLockfileRatelimitPeriodDumpsKey, 0);
326 scoped_ptr<base::ListValue> dumps = make_scoped_ptr(new base::ListValue());
328 return WriteFiles(dumps.get(), metadata.get());
331 int SynchronizedMinidumpManager::AddEntryToLockFile(const DumpInfo& dump_info) {
332 DCHECK_LE(0, lockfile_fd_);
333 DCHECK(dumps_);
335 // Make sure dump_info is valid.
336 if (!dump_info.valid()) {
337 LOG(ERROR) << "Entry to be added is invalid";
338 return -1;
341 if (!CanWriteDumps(1)) {
342 LOG(ERROR) << "Can't Add Dump: Ratelimited";
343 return -1;
346 IncrementCurrentPeriodDumps(metadata_.get(), 1);
347 dumps_->Append(dump_info.GetAsValue());
349 return 0;
352 int SynchronizedMinidumpManager::RemoveEntryFromLockFile(int index) {
353 return dumps_->Remove(static_cast<size_t>(index), nullptr) ? 0 : -1;
356 void SynchronizedMinidumpManager::ReleaseLockFile() {
357 // flock is associated with the fd entry in the open fd table, so closing
358 // all fd's will release the lock. To be safe, we explicitly unlock.
359 if (lockfile_fd_ >= 0) {
360 if (dumps_)
361 WriteFiles(dumps_.get(), metadata_.get());
363 flock(lockfile_fd_, LOCK_UN);
364 close(lockfile_fd_);
366 // We may use this object again, so we should reset this.
367 lockfile_fd_ = -1;
370 dumps_.reset();
371 metadata_.reset();
374 ScopedVector<DumpInfo> SynchronizedMinidumpManager::GetDumps() {
375 ScopedVector<DumpInfo> dumps;
377 for (const base::Value* elem : *dumps_) {
378 dumps.push_back(new DumpInfo(elem));
381 return dumps.Pass();
384 int SynchronizedMinidumpManager::SetCurrentDumps(
385 const ScopedVector<DumpInfo>& dumps) {
386 dumps_->Clear();
388 for (DumpInfo* dump : dumps)
389 dumps_->Append(dump->GetAsValue());
391 return 0;
394 bool SynchronizedMinidumpManager::CanWriteDumps(int num_dumps) {
395 const auto dumps(GetDumps());
397 // If no more dumps can be written, return false.
398 if (static_cast<int>(dumps.size()) + num_dumps > kMaxLockfileDumps)
399 return false;
401 // If too many dumps have been written recently, return false.
402 time_t cur_time = time(nullptr);
403 time_t period_start = GetRatelimitPeriodStart(metadata_.get());
404 int period_dumps_count = GetRatelimitPeriodDumps(metadata_.get());
406 // If we're in invalid state, or we passed the period, reset the ratelimit.
407 // When the device reboots, |cur_time| may be incorrectly reported to be a
408 // very small number for a short period of time. So only consider
409 // |period_start| invalid when |cur_time| is less if |cur_time| is not very
410 // close to 0.
411 if (period_dumps_count < 0 ||
412 (cur_time < period_start && cur_time > kRatelimitPeriodSeconds) ||
413 difftime(cur_time, period_start) >= kRatelimitPeriodSeconds) {
414 period_start = cur_time;
415 period_dumps_count = 0;
416 SetRatelimitPeriodStart(metadata_.get(), period_start);
417 SetRatelimitPeriodDumps(metadata_.get(), period_dumps_count);
420 return period_dumps_count + num_dumps <= kRatelimitPeriodMaxDumps;
423 } // namespace chromecast