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"
14 #include <sys/types.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) \
31 namespace chromecast
{
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
)) {
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
)) {
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
,
84 result
= static_cast<time_t>(strtoll(period_start_str
.c_str(), nullptr, 0));
86 return static_cast<time_t>(-1);
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);
101 snprintf(buf
, sizeof(buf
), "%lld", static_cast<long long>(period_start
));
102 std::string
period_start_str(buf
);
103 ratelimit_params
->SetString(kLockfileRatelimitPeriodStartKey
,
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
,
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
);
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
);
148 const int SynchronizedMinidumpManager::kMaxLockfileDumps
= 5;
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.
165 // TODO(slan): Move some of this pruning logic to ReleaseLockFile?
166 int SynchronizedMinidumpManager::GetNumDumps(bool delete_all_dumps
) {
171 // folder does not exist
172 dirp
= opendir(dump_path_
.value().c_str());
174 LOG(ERROR
) << "Unable to open directory " << dump_path_
.value();
178 while ((dptr
= readdir(dirp
)) != NULL
) {
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
186 // 'lockfile' is not counted
187 if (lockfile_path_
!= file_fullname
) {
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
);
203 int SynchronizedMinidumpManager::AcquireLockAndDoWork() {
205 if (AcquireLockFile() >= 0) {
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
);
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_
;
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) {
236 LOG(INFO
) << "flock lockfile failed, error = " << strerror(errno
);
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!";
250 DCHECK(lockfile_contents_
);
251 // We successfully have acquired the lock.
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;
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";
300 if (!CanWriteDumps(1)) {
301 LOG(ERROR
) << "Can't Add Dump: Ratelimited";
305 base::ListValue
* entry_list
= GetDumpList(lockfile_contents_
.get());
307 LOG(ERROR
) << "Failed to parse lock file";
311 IncrementCurrentPeriodDumps(lockfile_contents_
.get(), 1);
313 entry_list
->Append(dump_info
.GetAsValue());
318 int SynchronizedMinidumpManager::RemoveEntryFromLockFile(int index
) {
319 base::ListValue
* entry_list
= GetDumpList(lockfile_contents_
.get());
321 LOG(ERROR
) << "Failed to parse lock file";
325 RCHECK(entry_list
->Remove(static_cast<size_t>(index
), nullptr), -1);
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
);
339 // We may use this object again, so we should reset this.
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
));
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";
368 for (DumpInfo
* dump
: dumps
) {
369 dump_list
->Append(dump
->GetAsValue());
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
)
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
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