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"
13 #include <sys/types.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) \
33 namespace chromecast
{
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
)) {
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
,
74 result
= static_cast<time_t>(strtoll(period_start_str
.c_str(), nullptr, 0));
76 return static_cast<time_t>(-1);
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
,
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
,
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
);
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;
150 const int SynchronizedMinidumpManager::kMaxLockfileDumps
= 5;
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()),
164 SynchronizedMinidumpManager::~SynchronizedMinidumpManager() {
165 // Release the lock if held.
169 // TODO(slan): Move some of this pruning logic to ReleaseLockFile?
170 int SynchronizedMinidumpManager::GetNumDumps(bool delete_all_dumps
) {
175 // folder does not exist
176 dirp
= opendir(dump_path_
.value().c_str());
178 LOG(ERROR
) << "Unable to open directory " << dump_path_
.value();
182 while ((dptr
= readdir(dirp
)) != NULL
) {
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
191 // 'lockfile' and 'metadata' is not counted
192 if (lockfile_path_
!= file_fullname
&& metadata_path_
!= file_fullname
) {
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
);
208 int SynchronizedMinidumpManager::AcquireLockAndDoWork() {
210 if (AcquireLockFile() >= 0) {
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
);
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_
;
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) {
241 LOG(INFO
) << "flock lockfile failed, error = " << strerror(errno
);
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!";
258 // We successfully have acquired the lock.
262 int SynchronizedMinidumpManager::ParseFiles() {
263 DCHECK_GE(lockfile_fd_
, 0);
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());
276 for (const std::string
& line
: lines
) {
277 if (line
.size() == 0)
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();
294 int SynchronizedMinidumpManager::WriteFiles(const base::ListValue
* dumps
,
295 const base::Value
* 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_
),
309 lockfile
.size()) < 0) {
313 return SerializeJsonToFile(base::FilePath(metadata_path_
), *metadata
) ? 0
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_
);
335 // Make sure dump_info is valid.
336 if (!dump_info
.valid()) {
337 LOG(ERROR
) << "Entry to be added is invalid";
341 if (!CanWriteDumps(1)) {
342 LOG(ERROR
) << "Can't Add Dump: Ratelimited";
346 IncrementCurrentPeriodDumps(metadata_
.get(), 1);
347 dumps_
->Append(dump_info
.GetAsValue());
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) {
361 WriteFiles(dumps_
.get(), metadata_
.get());
363 flock(lockfile_fd_
, LOCK_UN
);
366 // We may use this object again, so we should reset this.
374 ScopedVector
<DumpInfo
> SynchronizedMinidumpManager::GetDumps() {
375 ScopedVector
<DumpInfo
> dumps
;
377 for (const base::Value
* elem
: *dumps_
) {
378 dumps
.push_back(new DumpInfo(elem
));
384 int SynchronizedMinidumpManager::SetCurrentDumps(
385 const ScopedVector
<DumpInfo
>& dumps
) {
388 for (DumpInfo
* dump
: dumps
)
389 dumps_
->Append(dump
->GetAsValue());
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
)
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
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