Add ICU message format support
[chromium-blink-merge.git] / chromecast / crash / linux / synchronized_minidump_manager.cc
blobc8c1a4d798ce02a00147a851bd0cc2f4756a4f2b
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 <grp.h>
11 #include <string.h>
12 #include <sys/file.h>
13 #include <sys/stat.h>
14 #include <sys/types.h>
15 #include <time.h>
16 #include <unistd.h>
18 #include "base/logging.h"
19 #include "chromecast/base/path_utils.h"
20 #include "chromecast/base/serializers.h"
21 #include "chromecast/crash/linux/dump_info.h"
23 // if |cond| is false, returns |retval|.
24 #define RCHECK(cond, retval) \
25 do { \
26 if (!(cond)) { \
27 return (retval); \
28 } \
29 } while (0)
31 namespace chromecast {
33 namespace {
35 const mode_t kDirMode = 0770;
36 const mode_t kFileMode = 0660;
37 const char kLockfileName[] = "lockfile";
38 const char kMinidumpsDir[] = "minidumps";
40 const char kLockfileDumpKey[] = "dumps";
41 const char kLockfileRatelimitKey[] = "ratelimit";
42 const char kLockfileRatelimitPeriodStartKey[] = "period_start";
43 const char kLockfileRatelimitPeriodDumpsKey[] = "period_dumps";
45 // Gets the list of deserialized DumpInfo given a deserialized |lockfile|.
46 // Returns nullptr if invalid.
47 base::ListValue* GetDumpList(base::Value* lockfile) {
48 base::DictionaryValue* dict;
49 base::ListValue* dump_list;
50 if (!lockfile || !lockfile->GetAsDictionary(&dict) ||
51 !dict->GetList(kLockfileDumpKey, &dump_list)) {
52 return nullptr;
55 return dump_list;
58 // Gets the ratelimit parameter dictionary given a deserialized |lockfile|.
59 // Returns nullptr if invalid.
60 base::DictionaryValue* GetRatelimitParams(base::Value* lockfile) {
61 base::DictionaryValue* dict;
62 base::DictionaryValue* ratelimit_params;
63 if (!lockfile || !lockfile->GetAsDictionary(&dict) ||
64 !dict->GetDictionary(kLockfileRatelimitKey, &ratelimit_params)) {
65 return nullptr;
68 return ratelimit_params;
71 // Returns the time of the current ratelimit period's start in |lockfile|.
72 // Returns (time_t)-1 if an error occurs.
73 time_t GetRatelimitPeriodStart(base::Value* lockfile) {
74 time_t result = static_cast<time_t>(-1);
75 base::DictionaryValue* ratelimit_params = GetRatelimitParams(lockfile);
76 RCHECK(ratelimit_params, result);
78 std::string period_start_str;
79 RCHECK(ratelimit_params->GetString(kLockfileRatelimitPeriodStartKey,
80 &period_start_str),
81 result);
83 errno = 0;
84 result = static_cast<time_t>(strtoll(period_start_str.c_str(), nullptr, 0));
85 if (errno != 0) {
86 return static_cast<time_t>(-1);
89 return result;
92 // Sets the time of the current ratelimit period's start in |lockfile| to
93 // |period_start|. Returns 0 on success, < 0 on error.
94 int SetRatelimitPeriodStart(base::Value* lockfile, time_t period_start) {
95 DCHECK_GE(period_start, 0);
97 base::DictionaryValue* ratelimit_params = GetRatelimitParams(lockfile);
98 RCHECK(ratelimit_params, -1);
100 char buf[128];
101 snprintf(buf, sizeof(buf), "%lld", static_cast<long long>(period_start));
102 std::string period_start_str(buf);
103 ratelimit_params->SetString(kLockfileRatelimitPeriodStartKey,
104 period_start_str);
105 return 0;
108 // Gets the number of dumps added to |lockfile| in the current ratelimit
109 // period. Returns < 0 on error.
110 int GetRatelimitPeriodDumps(base::Value* lockfile) {
111 int period_dumps = -1;
113 base::DictionaryValue* ratelimit_params = GetRatelimitParams(lockfile);
114 if (!ratelimit_params ||
115 !ratelimit_params->GetInteger(kLockfileRatelimitPeriodDumpsKey,
116 &period_dumps)) {
117 return -1;
120 return period_dumps;
123 // Sets the current ratelimit period's number of dumps in |lockfile| to
124 // |period_dumps|. Returns 0 on success, < 0 on error.
125 int SetRatelimitPeriodDumps(base::Value* lockfile, int period_dumps) {
126 DCHECK_GE(period_dumps, 0);
128 base::DictionaryValue* ratelimit_params = GetRatelimitParams(lockfile);
129 RCHECK(ratelimit_params, -1);
131 ratelimit_params->SetInteger(kLockfileRatelimitPeriodDumpsKey, period_dumps);
133 return 0;
136 // Increment the number of dumps in the current ratelimit period in deserialized
137 // |lockfile| by |increment|. Returns 0 on success, < 0 on error.
138 int IncrementCurrentPeriodDumps(base::Value* lockfile, int increment) {
139 DCHECK_GE(increment, 0);
140 int last_dumps = GetRatelimitPeriodDumps(lockfile);
141 RCHECK(last_dumps >= 0, -1);
143 return SetRatelimitPeriodDumps(lockfile, last_dumps + increment);
146 } // namespace
148 const int SynchronizedMinidumpManager::kMaxLockfileDumps = 5;
150 // One day
151 const int SynchronizedMinidumpManager::kRatelimitPeriodSeconds = 24 * 3600;
152 const int SynchronizedMinidumpManager::kRatelimitPeriodMaxDumps = 100;
154 SynchronizedMinidumpManager::SynchronizedMinidumpManager()
155 : non_blocking_(false), lockfile_fd_(-1) {
156 dump_path_ = GetHomePathASCII(kMinidumpsDir);
157 lockfile_path_ = dump_path_.Append(kLockfileName).value();
160 SynchronizedMinidumpManager::~SynchronizedMinidumpManager() {
161 // Release the lock if held.
162 ReleaseLockFile();
165 // TODO(slan): Move some of this pruning logic to ReleaseLockFile?
166 int SynchronizedMinidumpManager::GetNumDumps(bool delete_all_dumps) {
167 DIR* dirp;
168 struct dirent* dptr;
169 int num_dumps = 0;
171 // folder does not exist
172 dirp = opendir(dump_path_.value().c_str());
173 if (dirp == NULL) {
174 LOG(ERROR) << "Unable to open directory " << dump_path_.value();
175 return 0;
178 while ((dptr = readdir(dirp)) != NULL) {
179 struct stat buf;
180 const std::string file_fullname = dump_path_.value() + "/" + dptr->d_name;
181 if (lstat(file_fullname.c_str(), &buf) == -1 || !S_ISREG(buf.st_mode)) {
182 // if we cannot lstat this file, it is probably bad, so skip
183 // if the file is not regular, skip
184 continue;
186 // 'lockfile' is not counted
187 if (lockfile_path_ != file_fullname) {
188 ++num_dumps;
189 if (delete_all_dumps) {
190 LOG(INFO) << "Removing " << dptr->d_name
191 << "which was not in the lockfile";
192 if (remove(file_fullname.c_str()) < 0) {
193 LOG(INFO) << "remove failed. error " << strerror(errno);
199 closedir(dirp);
200 return num_dumps;
203 int SynchronizedMinidumpManager::AcquireLockAndDoWork() {
204 int success = -1;
205 if (AcquireLockFile() >= 0) {
206 success = DoWork();
207 ReleaseLockFile();
209 return success;
212 int SynchronizedMinidumpManager::AcquireLockFile() {
213 DCHECK_LT(lockfile_fd_, 0);
214 // Make the directory for the minidumps if it does not exist.
215 if (mkdir(dump_path_.value().c_str(), kDirMode) < 0 && errno != EEXIST) {
216 LOG(ERROR) << "mkdir for " << dump_path_.value().c_str()
217 << " failed. error = " << strerror(errno);
218 return -1;
221 // Open the lockfile. Create it if it does not exist.
222 lockfile_fd_ = open(lockfile_path_.c_str(), O_RDWR | O_CREAT, kFileMode);
224 // If opening or creating the lockfile failed, we don't want to proceed
225 // with dump writing for fear of exhausting up system resources.
226 if (lockfile_fd_ < 0) {
227 LOG(ERROR) << "open lockfile failed " << lockfile_path_;
228 return -1;
231 // Acquire the lock on the file. Whether or not we are in non-blocking mode,
232 // flock failure means that we did not acquire it and this method should fail.
233 int operation_mode = non_blocking_ ? (LOCK_EX | LOCK_NB) : LOCK_EX;
234 if (flock(lockfile_fd_, operation_mode) < 0) {
235 ReleaseLockFile();
236 LOG(INFO) << "flock lockfile failed, error = " << strerror(errno);
237 return -1;
240 // The lockfile is open and locked. Parse it to provide subclasses with a
241 // record of all the current dumps.
242 if (ParseLockFile() < 0) {
243 LOG(ERROR) << "Lockfile did not parse correctly. ";
244 if (CreateEmptyLockFile() < 0 || ParseLockFile() < 0) {
245 LOG(ERROR) << "Failed to create a new lock file!";
246 return -1;
250 DCHECK(lockfile_contents_);
251 // We successfully have acquired the lock.
252 return 0;
255 int SynchronizedMinidumpManager::ParseLockFile() {
256 DCHECK_GE(lockfile_fd_, 0);
257 DCHECK(!lockfile_contents_);
259 base::FilePath lockfile_path(lockfile_path_);
260 lockfile_contents_ = DeserializeJsonFromFile(lockfile_path);
262 // Verify validity of lockfile
263 if (!GetDumpList(lockfile_contents_.get()) ||
264 GetRatelimitPeriodStart(lockfile_contents_.get()) < 0 ||
265 GetRatelimitPeriodDumps(lockfile_contents_.get()) < 0) {
266 lockfile_contents_ = nullptr;
267 return -1;
270 return 0;
273 int SynchronizedMinidumpManager::WriteLockFile(const base::Value& contents) {
274 base::FilePath lockfile_path(lockfile_path_);
275 return SerializeJsonToFile(lockfile_path, contents) ? 0 : -1;
278 int SynchronizedMinidumpManager::CreateEmptyLockFile() {
279 scoped_ptr<base::DictionaryValue> output(
280 make_scoped_ptr(new base::DictionaryValue()));
281 output->Set(kLockfileDumpKey, make_scoped_ptr(new base::ListValue()));
283 base::DictionaryValue* ratelimit_fields = new base::DictionaryValue();
284 output->Set(kLockfileRatelimitKey, make_scoped_ptr(ratelimit_fields));
285 ratelimit_fields->SetString(kLockfileRatelimitPeriodStartKey, "0");
286 ratelimit_fields->SetInteger(kLockfileRatelimitPeriodDumpsKey, 0);
288 return WriteLockFile(*output);
291 int SynchronizedMinidumpManager::AddEntryToLockFile(const DumpInfo& dump_info) {
292 DCHECK_LE(0, lockfile_fd_);
294 // Make sure dump_info is valid.
295 if (!dump_info.valid()) {
296 LOG(ERROR) << "Entry to be added is invalid";
297 return -1;
300 if (!CanWriteDumps(1)) {
301 LOG(ERROR) << "Can't Add Dump: Ratelimited";
302 return -1;
305 base::ListValue* entry_list = GetDumpList(lockfile_contents_.get());
306 if (!entry_list) {
307 LOG(ERROR) << "Failed to parse lock file";
308 return -1;
311 IncrementCurrentPeriodDumps(lockfile_contents_.get(), 1);
313 entry_list->Append(dump_info.GetAsValue());
315 return 0;
318 int SynchronizedMinidumpManager::RemoveEntryFromLockFile(int index) {
319 base::ListValue* entry_list = GetDumpList(lockfile_contents_.get());
320 if (!entry_list) {
321 LOG(ERROR) << "Failed to parse lock file";
322 return -1;
325 RCHECK(entry_list->Remove(static_cast<size_t>(index), nullptr), -1);
326 return 0;
329 void SynchronizedMinidumpManager::ReleaseLockFile() {
330 // flock is associated with the fd entry in the open fd table, so closing
331 // all fd's will release the lock. To be safe, we explicitly unlock.
332 if (lockfile_fd_ >= 0) {
333 if (lockfile_contents_) {
334 WriteLockFile(*lockfile_contents_);
336 flock(lockfile_fd_, LOCK_UN);
337 close(lockfile_fd_);
339 // We may use this object again, so we should reset this.
340 lockfile_fd_ = -1;
343 lockfile_contents_.reset();
346 ScopedVector<DumpInfo> SynchronizedMinidumpManager::GetDumps() {
347 ScopedVector<DumpInfo> dumps;
348 const base::ListValue* dump_list = GetDumpList(lockfile_contents_.get());
349 RCHECK(dump_list, dumps.Pass());
351 for (const base::Value* elem : *dump_list) {
352 dumps.push_back(new DumpInfo(elem));
355 return dumps.Pass();
358 int SynchronizedMinidumpManager::SetCurrentDumps(
359 const ScopedVector<DumpInfo>& dumps) {
360 base::ListValue* dump_list = GetDumpList(lockfile_contents_.get());
361 if (dump_list == nullptr) {
362 LOG(ERROR) << "Invalid lock file";
363 return -1;
366 dump_list->Clear();
368 for (DumpInfo* dump : dumps) {
369 dump_list->Append(dump->GetAsValue());
372 return 0;
375 bool SynchronizedMinidumpManager::CanWriteDumps(int num_dumps) {
376 const auto dumps(GetDumps());
378 // If no more dumps can be written, return false.
379 if (static_cast<int>(dumps.size()) + num_dumps > kMaxLockfileDumps)
380 return false;
382 // If too many dumps have been written recently, return false.
383 time_t cur_time = time(nullptr);
384 time_t period_start = GetRatelimitPeriodStart(lockfile_contents_.get());
385 int period_dumps_count = GetRatelimitPeriodDumps(lockfile_contents_.get());
387 // If we're in invalid state, or we passed the period, reset the ratelimit.
388 // When the device reboots, |cur_time| may be incorrectly reported to be a
389 // very small number for a short period of time. So only consider
390 // |period_start| invalid when |cur_time| is less if |cur_time| is not very
391 // close to 0.
392 if (period_dumps_count < 0 ||
393 (cur_time < period_start && cur_time > kRatelimitPeriodSeconds) ||
394 difftime(cur_time, period_start) >= kRatelimitPeriodSeconds) {
395 period_start = cur_time;
396 period_dumps_count = 0;
397 SetRatelimitPeriodStart(lockfile_contents_.get(), period_start);
398 SetRatelimitPeriodDumps(lockfile_contents_.get(), period_dumps_count);
401 return period_dumps_count + num_dumps <= kRatelimitPeriodMaxDumps;
404 } // namespace chromecast