diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn index 6169cf86808637..e5f3b89ee80e91 100644 --- a/chromecast/BUILD.gn +++ b/chromecast/BUILD.gn @@ -16,6 +16,18 @@ component("chromecast") { deps = [ "//chromecast/base", "//chromecast/base/metrics", + "//chromecast/crash", "//chromecast/media", ] } + +group("chromecast_unittests") { + testonly = true + + deps = [ + "//chromecast/app:cast_shell_unittests", + "//chromecast/base:cast_base_unittests", + "//chromecast/crash:cast_crash_unittests", + "//chromecast/media:cast_media_unittests", + ] +} diff --git a/chromecast/android/DEPS b/chromecast/android/DEPS index df718c8e42360e..941b823e6ed8d3 100644 --- a/chromecast/android/DEPS +++ b/chromecast/android/DEPS @@ -1,7 +1,7 @@ include_rules = [ # Includes for JNI. "+chromecast/android", + "+chromecast/app/android", "+chromecast/browser/android", - "+chromecast/crash/android", "+components/external_video_surface", ] diff --git a/chromecast/android/cast_jni_registrar.cc b/chromecast/android/cast_jni_registrar.cc index a91a4f380d87d7..6fb7515161495f 100644 --- a/chromecast/android/cast_jni_registrar.cc +++ b/chromecast/android/cast_jni_registrar.cc @@ -7,11 +7,11 @@ #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" #include "chromecast/android/cast_metrics_helper_android.h" +#include "chromecast/app/android/crash_handler.h" #include "chromecast/base/cast_sys_info_android.h" #include "chromecast/base/chromecast_config_android.h" #include "chromecast/browser/android/cast_window_android.h" #include "chromecast/browser/android/cast_window_manager.h" -#include "chromecast/crash/android/crash_handler.h" #include "components/external_video_surface/component_jni_registrar.h" namespace chromecast { diff --git a/chromecast/app/BUILD.gn b/chromecast/app/BUILD.gn new file mode 100644 index 00000000000000..cf722cb4c6a2e4 --- /dev/null +++ b/chromecast/app/BUILD.gn @@ -0,0 +1,34 @@ +# 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. + +import("//testing/test.gni") + +component("cast_crash_client") { + sources = [ + "android/cast_crash_reporter_client_androic.cc", + "android/cast_crash_reporter_client_android.h", + "linux/cast_crash_reporter_client.cc", + "linux/cast_crash_reporter_client.h", + ] + + deps = [ + "//chromecast/crash", + "//components/crash/app", + "//content/public/common", + ] + + configs += [ "//chromecast:config" ] +} + +test("cast_shell_unittests") { + sources = [ + "linux/cast_crash_reporter_client_unittest.cc", + ] + + deps = [ + ":cast_crash_client", + "//base/test:run_all_unittests", + "//testing/gtest", + ] +} diff --git a/chromecast/app/android/DEPS b/chromecast/app/android/DEPS index 5021862892ef61..c31bf9fbce243b 100644 --- a/chromecast/app/android/DEPS +++ b/chromecast/app/android/DEPS @@ -1,3 +1,4 @@ include_rules = [ + "+breakpad", "+chromecast/android", ] diff --git a/chromecast/crash/android/cast_crash_reporter_client_android.cc b/chromecast/app/android/cast_crash_reporter_client_android.cc similarity index 97% rename from chromecast/crash/android/cast_crash_reporter_client_android.cc rename to chromecast/app/android/cast_crash_reporter_client_android.cc index af5ec8dd843fd5..ba1c6d91c1df53 100644 --- a/chromecast/crash/android/cast_crash_reporter_client_android.cc +++ b/chromecast/app/android/cast_crash_reporter_client_android.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chromecast/crash/android/cast_crash_reporter_client_android.h" +#include "chromecast/app/android/cast_crash_reporter_client_android.h" #include "base/base_paths.h" #include "base/files/file_path.h" diff --git a/chromecast/crash/android/cast_crash_reporter_client_android.h b/chromecast/app/android/cast_crash_reporter_client_android.h similarity index 78% rename from chromecast/crash/android/cast_crash_reporter_client_android.h rename to chromecast/app/android/cast_crash_reporter_client_android.h index 3f296ab63bf919..9267b4c2c938e5 100644 --- a/chromecast/crash/android/cast_crash_reporter_client_android.h +++ b/chromecast/app/android/cast_crash_reporter_client_android.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ -#define CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ +#ifndef CHROMECAST_APP_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ +#define CHROMECAST_APP_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ #include "base/compiler_specific.h" #include "components/crash/app/crash_reporter_client.h" @@ -23,8 +23,7 @@ class CastCrashReporterClientAndroid bool GetCrashDumpLocation(base::FilePath* crash_dir) override; int GetAndroidMinidumpDescriptor() override; bool GetCollectStatsConsent() override; - bool EnableBreakpadForProcess( - const std::string& process_type) override; + bool EnableBreakpadForProcess(const std::string& process_type) override; size_t RegisterCrashKeys() override; private: @@ -35,4 +34,4 @@ class CastCrashReporterClientAndroid } // namespace chromecast -#endif // CHROMECAST_CRASH_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ +#endif // CHROMECAST_APP_ANDROID_CAST_CRASH_REPORTER_CLIENT_ANDROID_H_ diff --git a/chromecast/crash/android/crash_handler.cc b/chromecast/app/android/crash_handler.cc similarity index 86% rename from chromecast/crash/android/crash_handler.cc rename to chromecast/app/android/crash_handler.cc index 12b1acd78d18fc..8a2dc7ac657a65 100644 --- a/chromecast/crash/android/crash_handler.cc +++ b/chromecast/app/android/crash_handler.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chromecast/crash/android/crash_handler.h" +#include "chromecast/app/android/crash_handler.h" #include #include @@ -15,8 +15,8 @@ #include "base/strings/string_number_conversions.h" #include "breakpad/src/client/linux/handler/exception_handler.h" #include "breakpad/src/client/linux/handler/minidump_descriptor.h" +#include "chromecast/app/android/cast_crash_reporter_client_android.h" #include "chromecast/base/version.h" -#include "chromecast/crash/android/cast_crash_reporter_client_android.h" #include "components/crash/app/breakpad_linux.h" #include "components/crash/app/crash_reporter_client.h" #include "content/public/common/content_switches.h" @@ -54,8 +54,8 @@ void CrashHandler::Initialize(const std::string& process_type, // static bool CrashHandler::GetCrashDumpLocation(base::FilePath* crash_dir) { DCHECK(g_crash_handler); - return g_crash_handler->crash_reporter_client_-> - GetCrashDumpLocation(crash_dir); + return g_crash_handler->crash_reporter_client_->GetCrashDumpLocation( + crash_dir); } // static @@ -94,11 +94,12 @@ void CrashHandler::Initialize() { void CrashHandler::InitializeUploader() { JNIEnv* env = base::android::AttachCurrentThread(); base::android::ScopedJavaLocalRef crash_dump_path_java = - base::android::ConvertUTF8ToJavaString(env, - crash_dump_path_.value()); + base::android::ConvertUTF8ToJavaString(env, crash_dump_path_.value()); Java_CastCrashHandler_initializeUploader( - env, base::android::GetApplicationContext(), - crash_dump_path_java.obj(), UploadCrashToStaging()); + env, + base::android::GetApplicationContext(), + crash_dump_path_java.obj(), + UploadCrashToStaging()); } } // namespace chromecast diff --git a/chromecast/crash/android/crash_handler.h b/chromecast/app/android/crash_handler.h similarity index 90% rename from chromecast/crash/android/crash_handler.h rename to chromecast/app/android/crash_handler.h index c018339a5da22e..d4a79754d07389 100644 --- a/chromecast/crash/android/crash_handler.h +++ b/chromecast/app/android/crash_handler.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_ -#define CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_ +#ifndef CHROMECAST_APP_ANDROID_CRASH_HANDLER_H_ +#define CHROMECAST_APP_ANDROID_CRASH_HANDLER_H_ #include #include @@ -54,4 +54,4 @@ class CrashHandler { } // namespace chromecast -#endif // CHROMECAST_CRASH_ANDROID_CRASH_HANDLER_H_ +#endif // CHROMECAST_APP_ANDROID_CRASH_HANDLER_H_ diff --git a/chromecast/app/cast_main_delegate.cc b/chromecast/app/cast_main_delegate.cc index 12d1a79087ca93..b0e28a045fd6be 100644 --- a/chromecast/app/cast_main_delegate.cc +++ b/chromecast/app/cast_main_delegate.cc @@ -16,7 +16,6 @@ #include "chromecast/browser/cast_content_browser_client.h" #include "chromecast/common/cast_resource_delegate.h" #include "chromecast/common/global_descriptors.h" -#include "chromecast/crash/cast_crash_reporter_client.h" #include "chromecast/renderer/cast_content_renderer_client.h" #include "components/crash/app/crash_reporter_client.h" #include "content/public/browser/browser_main_runner.h" @@ -24,7 +23,9 @@ #include "ui/base/resource/resource_bundle.h" #if defined(OS_ANDROID) -#include "chromecast/crash/android/crash_handler.h" +#include "chromecast/app/android/crash_handler.h" +#else +#include "chromecast/app/linux/cast_crash_reporter_client.h" #endif // defined(OS_ANDROID) namespace { diff --git a/chromecast/app/linux/cast_crash_reporter_client.cc b/chromecast/app/linux/cast_crash_reporter_client.cc new file mode 100644 index 00000000000000..0bf2877057e649 --- /dev/null +++ b/chromecast/app/linux/cast_crash_reporter_client.cc @@ -0,0 +1,77 @@ +// Copyright 2014 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/app/linux/cast_crash_reporter_client.h" + +#include "base/time/time.h" +#include "chromecast/base/error_codes.h" +#include "chromecast/crash/linux/crash_util.h" +#include "components/crash/app/breakpad_linux.h" +#include "content/public/common/content_switches.h" + +namespace chromecast { + +namespace { + +std::string* g_process_type = nullptr; +uint64_t g_process_start_time_ms = 0u; + +} // namespace + +// static +void CastCrashReporterClient::InitCrashReporter( + const std::string& process_type) { + DCHECK(!g_process_type); + g_process_start_time_ms = + (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds(); + + // Save the process type (leaked). + g_process_type = new std::string(process_type); + + // Start up breakpad for this process, if applicable. + breakpad::InitCrashReporter(process_type); +} + +// static +const char* CastCrashReporterClient::GetProcessType() { + return g_process_type ? g_process_type->c_str() : nullptr; +} + +// static +uint64_t CastCrashReporterClient::GetProcessStartTime() { + return g_process_start_time_ms; +} + +CastCrashReporterClient::CastCrashReporterClient() { +} +CastCrashReporterClient::~CastCrashReporterClient() { +} + +bool CastCrashReporterClient::EnableBreakpadForProcess( + const std::string& process_type) { + return process_type == switches::kRendererProcess || + process_type == switches::kZygoteProcess || + process_type == switches::kGpuProcess; +} + +bool CastCrashReporterClient::HandleCrashDump(const char* crashdump_filename) { + // Set the initial error code to ERROR_WEB_CONTENT_RENDER_VIEW_GONE to show + // an error message on next cast_shell run. Though the error code is for + // renderer process crash, the actual messages can be used for browser process + // as well. + if (!GetProcessType() || !strcmp(GetProcessType(), "")) + SetInitialErrorCode(ERROR_WEB_CONTENT_RENDER_VIEW_GONE); + + // Upload crash dump. If user didn't opt-in crash report, minidump writer + // instantiated within CrashUtil::RequestUploadCrashDump() does nothing. + CrashUtil::RequestUploadCrashDump(crashdump_filename, + GetProcessType() ? GetProcessType() : "", + GetProcessStartTime()); + + // Always return true to indicate that this crash dump has been processed, + // so that it won't fallback to use chrome's default uploader. + return true; +} + +} // namespace chromecast diff --git a/chromecast/crash/cast_crash_reporter_client.h b/chromecast/app/linux/cast_crash_reporter_client.h similarity index 72% rename from chromecast/crash/cast_crash_reporter_client.h rename to chromecast/app/linux/cast_crash_reporter_client.h index a0916a041ea6c2..07712af233db2d 100644 --- a/chromecast/crash/cast_crash_reporter_client.h +++ b/chromecast/app/linux/cast_crash_reporter_client.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROMECAST_CRASH_CAST_CRASH_REPORTER_CLIENT_H_ -#define CHROMECAST_CRASH_CAST_CRASH_REPORTER_CLIENT_H_ +#ifndef CHROMECAST_APP_LINUX_CAST_CRASH_REPORTER_CLIENT_H_ +#define CHROMECAST_APP_LINUX_CAST_CRASH_REPORTER_CLIENT_H_ #include @@ -20,12 +20,11 @@ class CastCrashReporterClient : public crash_reporter::CrashReporterClient { ~CastCrashReporterClient() override; // crash_reporter::CrashReporterClient implementation: - bool EnableBreakpadForProcess( - const std::string& process_type) override; + bool EnableBreakpadForProcess(const std::string& process_type) override; bool HandleCrashDump(const char* crashdump_filename) override; private: - static char* GetProcessType(); + static const char* GetProcessType(); static uint64_t GetProcessStartTime(); DISALLOW_COPY_AND_ASSIGN(CastCrashReporterClient); @@ -33,4 +32,4 @@ class CastCrashReporterClient : public crash_reporter::CrashReporterClient { } // namespace chromecast -#endif // CHROMECAST_CRASH_CAST_CRASH_REPORTER_CLIENT_H_ +#endif // CHROMECAST_APP_LINUX_CAST_CRASH_REPORTER_CLIENT_H_ diff --git a/chromecast/app/linux/cast_crash_reporter_client_unittest.cc b/chromecast/app/linux/cast_crash_reporter_client_unittest.cc new file mode 100644 index 00000000000000..88cbad9c50c328 --- /dev/null +++ b/chromecast/app/linux/cast_crash_reporter_client_unittest.cc @@ -0,0 +1,116 @@ +// 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 + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/memory/scoped_vector.h" +#include "base/test/scoped_path_override.h" +#include "chromecast/app/linux/cast_crash_reporter_client.h" +#include "chromecast/crash/app_state_tracker.h" +#include "chromecast/crash/linux/crash_util.h" +#include "chromecast/crash/linux/dump_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace { + +const char kFakeDumpstateContents[] = "Dumpstate Contents\nDumpdumpdumpdump\n"; +const char kFakeMinidumpContents[] = "Minidump Contents\nLine1\nLine2\n"; + +int WriteFakeDumpStateFile(const std::string& path) { + // Append the correct extension and write the data to file. + base::File dumpstate(base::FilePath(path).AddExtension(".txt.gz"), + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + dumpstate.Write( + 0, kFakeDumpstateContents, sizeof(kFakeDumpstateContents) - 1); + return 0; +} + +ScopedVector GetCurrentDumps(const std::string& logfile_path) { + ScopedVector dumps; + std::string entry; + + std::ifstream in(logfile_path); + DCHECK(in.is_open()); + while (std::getline(in, entry)) { + scoped_ptr info(new DumpInfo(entry)); + dumps.push_back(info.Pass()); + } + return dumps.Pass(); +} + +} // namespace + +TEST(CastCrashReporterClientTest, EndToEnd) { + // Set up a temporary directory which will be used as our fake home dir. + base::FilePath fake_home_dir; + ASSERT_TRUE(base::CreateNewTempDirectory("", &fake_home_dir)); + base::ScopedPathOverride home(base::DIR_HOME, fake_home_dir); + + // Set a callback to be used in place of the |dumpstate| executable. + CrashUtil::SetDumpStateCbForTest(base::Bind(&WriteFakeDumpStateFile)); + + // "Launch" YouTube. + AppStateTracker::SetLastLaunchedApp("youtube"); + AppStateTracker::SetCurrentApp("youtube"); + + // "Launch" and switch to Pandora. + AppStateTracker::SetLastLaunchedApp("pandora"); + AppStateTracker::SetCurrentApp("pandora"); + + // "Launch" Netflix. + AppStateTracker::SetLastLaunchedApp("netflix"); + // Netflix crashed. + + // A minidump file is created. + base::FilePath minidump_path; + base::CreateTemporaryFile(&minidump_path); + base::File minidump(minidump_path, + base::File::FLAG_OPEN | base::File::FLAG_APPEND); + minidump.Write(0, kFakeMinidumpContents, sizeof(kFakeMinidumpContents) - 1); + minidump.Close(); + + // Handle the crash. + CastCrashReporterClient client; + ASSERT_TRUE(client.HandleCrashDump(minidump_path.value().c_str())); + + // Assert that the original file has been moved. + ASSERT_FALSE(base::PathExists(minidump_path)); + + // Assert that the file has been moved to "minidumps", with the expected + // contents. + std::string contents; + base::FilePath new_minidump = + fake_home_dir.Append("minidumps").Append(minidump_path.BaseName()); + ASSERT_TRUE(base::PathExists(new_minidump)); + ASSERT_TRUE(base::ReadFileToString(new_minidump, &contents)); + ASSERT_EQ(kFakeMinidumpContents, contents); + + // Assert that the dumpstate file has been written with the expected contents. + base::FilePath dumpstate = new_minidump.AddExtension(".txt.gz"); + ASSERT_TRUE(base::PathExists(dumpstate)); + ASSERT_TRUE(base::ReadFileToString(dumpstate, &contents)); + ASSERT_EQ(kFakeDumpstateContents, contents); + + // Assert that the lockfile has logged the correct information. + base::FilePath lockfile = + fake_home_dir.Append("minidumps").Append("lockfile"); + ASSERT_TRUE(base::PathExists(lockfile)); + ScopedVector dumps = GetCurrentDumps(lockfile.value()); + ASSERT_EQ(1u, dumps.size()); + + const DumpInfo& dump_info = *(dumps[0]); + ASSERT_TRUE(dump_info.valid()); + EXPECT_EQ(new_minidump.value(), dump_info.crashed_process_dump()); + EXPECT_EQ(dumpstate.value(), dump_info.logfile()); + EXPECT_EQ("youtube", dump_info.params().previous_app_name); + EXPECT_EQ("pandora", dump_info.params().current_app_name); + EXPECT_EQ("netflix", dump_info.params().last_app_name); +} + +} // namespace chromecast \ No newline at end of file diff --git a/chromecast/base/BUILD.gn b/chromecast/base/BUILD.gn index b105a71926ae94..09ad4ce16e03ac 100644 --- a/chromecast/base/BUILD.gn +++ b/chromecast/base/BUILD.gn @@ -2,11 +2,66 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//chrome/version.gni") # TODO layering violation! +import("//testing/test.gni") + source_set("base") { sources = [ "cast_paths.cc", "cast_paths.h", + "chromecast_switches.cc", + "chromecast_switches.h", + "error_codes.cc", + "error_codes.h", + "metrics/cast_histograms.h", + "metrics/cast_metrics_helper.cc", + "metrics/cast_metrics_helper.h", + "metrics/grouped_histogram.cc", + "metrics/grouped_histogram.h", + "path_utils.cc", + "path_utils.h", + "process_utils.cc", + "process_utils.h", + "serializers.cc", + "serializers.h", + ] + + deps = [ + "//base", ] configs += [ "//chromecast:config" ] } + +test("cast_base_unittests") { + sources = [ + "path_utils_unittest.cc", + "process_utils_unittest.cc", + "serializers_unittest.cc", + ] + + deps = [ + ":base", + "//base/test:run_all_unittests", + "//testing/gtest", + ] +} + +process_version("cast_version") { + template_file = "version.h.in" + output = "$target_gen_dir/version.h" + extra_args = [ + "-e", + "VERSION_FULL=\"%s.%s.%s.%s\"%(MAJOR,MINOR,BUILD,PATCH)", + + # TODO(slan): Populate the fields below with real values + "-e", + "CAST_BUILD_INCREMENTAL=20150608.181153", + "-e", + "CAST_BUILD_RELEASE=1.15", + "-e", + "CAST_IS_DEBUG_BUILD=1", + "-e", + "CAST_PRODUCT_TYPE=0", + ] +} diff --git a/chromecast/base/error_codes.cc b/chromecast/base/error_codes.cc new file mode 100644 index 00000000000000..21fabae4c6c10d --- /dev/null +++ b/chromecast/base/error_codes.cc @@ -0,0 +1,81 @@ +// 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/base/error_codes.h" + +#include + +#include + +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "chromecast/base/path_utils.h" + +namespace chromecast { + +namespace { + +const char kInitialErrorFile[] = "initial_error"; + +base::FilePath GetInitialErrorFilePath() { + return GetHomePathASCII(kInitialErrorFile); +} + +} // namespace + +ErrorCode GetInitialErrorCode() { + std::string initial_error_code_str; + if (!base::ReadFileToString(GetInitialErrorFilePath(), + &initial_error_code_str)) { + return NO_ERROR; + } + + int initial_error_code = 0; + if (base::StringToInt(initial_error_code_str, &initial_error_code) && + initial_error_code >= NO_ERROR && initial_error_code <= ERROR_UNKNOWN) { + VLOG(1) << "Initial error from " << GetInitialErrorFilePath().value() + << ": " << initial_error_code; + return static_cast(initial_error_code); + } + + LOG(ERROR) << "Unknown initial error code: " << initial_error_code_str; + return NO_ERROR; +} + +bool SetInitialErrorCode(ErrorCode initial_error_code) { + // Note: Do not use Chromium IO methods in this function. When cast_shell + // crashes, this function can be called by any thread. + const std::string& error_file_path = GetInitialErrorFilePath().value(); + + if (initial_error_code > NO_ERROR && initial_error_code <= ERROR_UNKNOWN) { + const std::string initial_error_code_str( + base::IntToString(initial_error_code)); + int fd = creat(error_file_path.c_str(), 0640); + if (fd < 0) { + LOG(ERROR) << "Could not open error code file"; + return false; + } + + int written = + write(fd, initial_error_code_str.data(), initial_error_code_str.size()); + close(fd); + + if (written != static_cast(initial_error_code_str.size())) { + LOG(ERROR) << "Could not write error code to file"; + return false; + } + + return true; + } + + // Remove initial error file if no error. + if (unlink(error_file_path.c_str()) == 0 || errno == ENOENT) + return true; + + LOG(ERROR) << "Failed to remove error file"; + return false; +} + +} // namespace chromecast diff --git a/chromecast/base/error_codes.h b/chromecast/base/error_codes.h new file mode 100644 index 00000000000000..80d430f204649a --- /dev/null +++ b/chromecast/base/error_codes.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef CHROMECAST_BASE_ERROR_CODES_H_ +#define CHROMECAST_BASE_ERROR_CODES_H_ + +namespace chromecast { + +enum ErrorCode { + NO_ERROR = 0, + + // web_content [1, 9999] + ERROR_WEB_CONTENT_RENDER_VIEW_GONE = 1, + ERROR_WEB_CONTENT_NAME_NOT_RESOLVED, + ERROR_WEB_CONTENT_INTERNET_DISCONNECTED, + + // reboot [10000, 19999] + // The following error codes do not reset the volume when the page is + // launched. Add codes that do not reset the volume before ERROR_REBOOT_NOW. + ERROR_REBOOT_NOW = 10000, + ERROR_REBOOT_FDR, + ERROR_REBOOT_OTA, + ERROR_REBOOT_IDLE, + // Chromecast WebUI uses 19999 for END_OF_REBOOT_SECTION, so reserve it here. + END_OF_REBOOT_SECTION = 19999, + + // misc [20000, 29999] + ERROR_ABORTED = 20000, + ERROR_LOST_PEER_CONNECTION = 20001, + + ERROR_UNKNOWN = 30000, +}; + +// Gets the error code for the first idle screen. +ErrorCode GetInitialErrorCode(); + +// Sets the error code for the first idle screen. Returns true if set +// successfully. +bool SetInitialErrorCode(ErrorCode initial_error_code); + +} // namespace chromecast + +#endif // CHROMECAST_BASE_ERROR_CODES_H_ diff --git a/chromecast/base/version.h.in b/chromecast/base/version.h.in index faba4899bcce88..62b01d31929776 100644 --- a/chromecast/base/version.h.in +++ b/chromecast/base/version.h.in @@ -9,6 +9,7 @@ #define PRODUCT_VERSION "@VERSION_FULL@" #define CAST_BUILD_INCREMENTAL "@CAST_BUILD_INCREMENTAL@" +#define CAST_BUILD_RELEASE "@CAST_BUILD_RELEASE@" #define CAST_BUILD_REVISION "@CAST_BUILD_RELEASE@.@CAST_BUILD_INCREMENTAL@" #define CAST_IS_DEBUG_BUILD() @CAST_IS_DEBUG_BUILD@ #define CAST_PRODUCT_TYPE @CAST_PRODUCT_TYPE@ diff --git a/chromecast/browser/cast_browser_main_parts.cc b/chromecast/browser/cast_browser_main_parts.cc index 8ade28fa4d4b63..a841a11b168da9 100644 --- a/chromecast/browser/cast_browser_main_parts.cc +++ b/chromecast/browser/cast_browser_main_parts.cc @@ -46,8 +46,8 @@ #include "ui/compositor/compositor_switches.h" #if defined(OS_ANDROID) +#include "chromecast/app/android/crash_handler.h" #include "chromecast/browser/media/cast_media_client_android.h" -#include "chromecast/crash/android/crash_handler.h" #include "components/crash/browser/crash_dump_manager_android.h" #include "media/base/android/media_client_android.h" #include "net/android/network_change_notifier_factory_android.h" diff --git a/chromecast/chromecast.gyp b/chromecast/chromecast.gyp index 18a07cd09697bf..61b5336b9c7e6e 100644 --- a/chromecast/chromecast.gyp +++ b/chromecast/chromecast.gyp @@ -62,6 +62,8 @@ 'base/cast_paths.h', 'base/chromecast_switches.cc', 'base/chromecast_switches.h', + 'base/error_codes.cc', + 'base/error_codes.h', 'base/metrics/cast_histograms.h', 'base/metrics/cast_metrics_helper.cc', 'base/metrics/cast_metrics_helper.h', @@ -76,29 +78,54 @@ ], }, # end of target 'cast_base' { - 'target_name': 'cast_crash_client', + 'target_name': 'cast_crash', 'type': '<(component)', + 'include_dirs': [ + # TODO(gfhuang): we should not need to include this directly, but + # somehow depending on component.gyp:breakpad_component is not + # working as expected. + '../breakpad/src', + ], 'dependencies': [ + 'cast_base', + 'cast_version_header', '../breakpad/breakpad.gyp:breakpad_client', - '../components/components.gyp:crash_component', ], 'sources': [ + 'crash/app_state_tracker.cc', + 'crash/app_state_tracker.h', 'crash/cast_crash_keys.cc', 'crash/cast_crash_keys.h', - 'crash/cast_crash_reporter_client.cc', - 'crash/cast_crash_reporter_client.h', + 'crash/cast_crashdump_uploader.cc', + 'crash/cast_crashdump_uploader.h', + 'crash/linux/crash_util.cc', + 'crash/linux/crash_util.h', + 'crash/linux/dummy_minidump_generator.cc', + 'crash/linux/dummy_minidump_generator.h', + 'crash/linux/dump_info.cc', + 'crash/linux/dump_info.h', + 'crash/linux/minidump_generator.h', + 'crash/linux/synchronized_minidump_manager.cc', + 'crash/linux/synchronized_minidump_manager.h', + 'crash/linux/minidump_params.cc', + 'crash/linux/minidump_params.h', + 'crash/linux/minidump_writer.cc', + 'crash/linux/minidump_writer.h', + ], + }, # end of target 'cast_crash' + { + 'target_name': 'cast_crash_client', + 'type': '<(component)', + 'dependencies': [ + 'cast_crash', + '../components/components.gyp:crash_component', + '../content/content.gyp:content_common', + ], + 'sources' : [ + # TODO(slan): Move android crash_client here as well. + 'app/linux/cast_crash_reporter_client.cc', + 'app/linux/cast_crash_reporter_client.h', ], - 'conditions': [ - ['chromecast_branding=="Chrome"', { - 'dependencies': [ - 'internal/chromecast_internal.gyp:crash_internal', - ], - }, { - 'sources': [ - 'crash/cast_crash_reporter_client_simple.cc', - ], - }], - ] }, # end of target 'cast_crash_client' { 'target_name': 'cast_net', @@ -451,24 +478,24 @@ '../breakpad/src', ], 'sources': [ - 'base/cast_sys_info_android.cc', - 'base/cast_sys_info_android.h', - 'base/chromecast_config_android.cc', - 'base/chromecast_config_android.h', 'android/cast_jni_registrar.cc', 'android/cast_jni_registrar.h', 'android/cast_metrics_helper_android.cc', 'android/cast_metrics_helper_android.h', 'android/platform_jni_loader.h', + 'app/android/cast_crash_reporter_client_android.cc', + 'app/android/cast_crash_reporter_client_android.h', 'app/android/cast_jni_loader.cc', + 'app/android/crash_handler.cc', + 'app/android/crash_handler.h', + 'base/cast_sys_info_android.cc', + 'base/cast_sys_info_android.h', + 'base/chromecast_config_android.cc', + 'base/chromecast_config_android.h', 'browser/android/cast_window_android.cc', 'browser/android/cast_window_android.h', 'browser/android/cast_window_manager.cc', 'browser/android/cast_window_manager.h', - 'crash/android/cast_crash_reporter_client_android.cc', - 'crash/android/cast_crash_reporter_client_android.h', - 'crash/android/crash_handler.cc', - 'crash/android/crash_handler.h', ], 'conditions': [ ['chromecast_branding=="Chrome"', { diff --git a/chromecast/chromecast_tests.gypi b/chromecast/chromecast_tests.gypi index caff44643d562c..7cbd326cfeb146 100644 --- a/chromecast/chromecast_tests.gypi +++ b/chromecast/chromecast_tests.gypi @@ -20,7 +20,21 @@ 'base/process_utils_unittest.cc', 'base/serializers_unittest.cc', ], - }, + }, # end of cast_base_unittests + { + 'target_name': 'cast_crash_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'chromecast.gyp:cast_crash', + '../base/base.gyp:run_all_unittests', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'crash/linux/dump_info_unittest.cc', + 'crash/linux/synchronized_minidump_manager_unittest.cc', + 'crash/linux/minidump_writer_unittest.cc', + ], + }, # end of cast_crash_unittests { 'target_name': 'cast_tests', 'type': 'none', @@ -41,6 +55,7 @@ 'type': 'none', 'dependencies': [ 'cast_base_unittests', + 'cast_crash_unittests', '../base/base.gyp:base_unittests', '../content/content_shell_and_tests.gyp:content_unittests', '../crypto/crypto.gyp:crypto_unittests', @@ -113,6 +128,7 @@ }], ['OS!="android"', { 'dependencies': [ + 'cast_shell_unittests', 'cast_shell_browser_test', 'media/media.gyp:cast_media_unittests', ], @@ -274,6 +290,18 @@ 'browser/test/chromecast_shell_browser_test.cc', ], }, + { + 'target_name': 'cast_shell_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'chromecast.gyp:cast_crash_client', + '../base/base.gyp:run_all_unittests', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'app/linux/cast_crash_reporter_client_unittest.cc', + ], + }, # end of cast_shell_unittests ], # end of targets }], ], # end of conditions diff --git a/chromecast/crash/BUILD.gn b/chromecast/crash/BUILD.gn new file mode 100644 index 00000000000000..56a30daf91edb3 --- /dev/null +++ b/chromecast/crash/BUILD.gn @@ -0,0 +1,51 @@ +# 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. + +import("//testing/test.gni") + +source_set("crash") { + sources = [ + "app_state_tracker.cc", + "app_state_tracker.h", + "cast_crash_keys.cc", + "cast_crash_keys.h", + "cast_crashdump_uploader.cc", + "cast_crashdump_uploader.h", + "linux/crash_util.cc", + "linux/crash_util.h", + "linux/dummy_minidump_generator.cc", + "linux/dummy_minidump_generator.h", + "linux/dump_info.cc", + "linux/dump_info.h", + "linux/minidump_generator.h", + "linux/minidump_params.cc", + "linux/minidump_params.h", + "linux/minidump_writer.cc", + "linux/minidump_writer.h", + "linux/synchronized_minidump_manager.cc", + "linux/synchronized_minidump_manager.h", + ] + + deps = [ + "//breakpad:client", + "//chromecast/base", + "//chromecast/base:cast_version", + ] + + configs += [ "//chromecast:config" ] +} + +test("cast_crash_unittests") { + sources = [ + "linux/dump_info_unittest.cc", + "linux/minidump_writer_unittest.cc", + "linux/synchronized_minidump_manager_unittest.cc", + ] + + deps = [ + ":crash", + "//base/test:run_all_unittests", + "//testing/gtest", + ] +} diff --git a/chromecast/crash/DEPS b/chromecast/crash/DEPS index 4389c51b6d2738..9ebc4972913821 100644 --- a/chromecast/crash/DEPS +++ b/chromecast/crash/DEPS @@ -1,4 +1,3 @@ include_rules = [ "+breakpad", - "+components/crash", ] diff --git a/chromecast/crash/android/DEPS b/chromecast/crash/android/DEPS deleted file mode 100644 index 2443d792c84ae1..00000000000000 --- a/chromecast/crash/android/DEPS +++ /dev/null @@ -1,4 +0,0 @@ -include_rules = [ - "+chromecast/android", - "+chromecast/common", -] diff --git a/chromecast/crash/app_state_tracker.cc b/chromecast/crash/app_state_tracker.cc new file mode 100644 index 00000000000000..b563ece66cdb1c --- /dev/null +++ b/chromecast/crash/app_state_tracker.cc @@ -0,0 +1,63 @@ +// 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/app_state_tracker.h" + +#include "base/lazy_instance.h" +#include "chromecast/crash/cast_crash_keys.h" + +namespace { + +struct CurrentAppState { + std::string previous_app; + std::string current_app; + std::string last_launched_app; +}; + +base::LazyInstance g_app_state = LAZY_INSTANCE_INITIALIZER; + +CurrentAppState* GetAppState() { + return g_app_state.Pointer(); +} + +} // namespace + +namespace chromecast { + +// static +std::string AppStateTracker::GetLastLaunchedApp() { + return GetAppState()->last_launched_app; +} + +// static +std::string AppStateTracker::GetCurrentApp() { + return GetAppState()->current_app; +} + +// static +std::string AppStateTracker::GetPreviousApp() { + return GetAppState()->previous_app; +} + +// static +void AppStateTracker::SetLastLaunchedApp(const std::string& app_id) { + GetAppState()->last_launched_app = app_id; + + // TODO(slan): Currently SetCrashKeyValue is a no-op on chromecast until + // we add call to InitCrashKeys + base::debug::SetCrashKeyValue(crash_keys::kLastApp, app_id); +} + +// static +void AppStateTracker::SetCurrentApp(const std::string& app_id) { + CurrentAppState* app_state = GetAppState(); + app_state->previous_app = app_state->current_app; + app_state->current_app = app_id; + + base::debug::SetCrashKeyValue(crash_keys::kCurrentApp, app_id); + base::debug::SetCrashKeyValue(crash_keys::kPreviousApp, + app_state->previous_app); +} + +} // namespace chromecast diff --git a/chromecast/crash/app_state_tracker.h b/chromecast/crash/app_state_tracker.h new file mode 100644 index 00000000000000..3df8686c951419 --- /dev/null +++ b/chromecast/crash/app_state_tracker.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef CHROMECAST_CRASH_APP_STATE_TRACKER_H_ +#define CHROMECAST_CRASH_APP_STATE_TRACKER_H_ + +#include + +namespace chromecast { + +class AppStateTracker { + public: + // Record |app_id| as the last app that attempted to launch. + static void SetLastLaunchedApp(const std::string& app_id); + + // The current app becomes the previous app, |app_id| becomes the current app. + static void SetCurrentApp(const std::string& app_id); + + // Returns the id of the app that was last attempted to launch. + static std::string GetLastLaunchedApp(); + + // Returns the id of the active app. + static std::string GetCurrentApp(); + + // Returns the id of the app which was previously active. + static std::string GetPreviousApp(); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_APP_STATE_TRACKER_H_ diff --git a/chromecast/crash/cast_crash_reporter_client.cc b/chromecast/crash/cast_crash_reporter_client.cc deleted file mode 100644 index 8edbfa6b7b26eb..00000000000000 --- a/chromecast/crash/cast_crash_reporter_client.cc +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 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/cast_crash_reporter_client.h" - -#include "base/time/time.h" -#include "components/crash/app/breakpad_linux.h" -#include "content/public/common/content_switches.h" - -namespace chromecast { - -namespace { - -char* g_process_type = NULL; -uint64_t g_process_start_time = 0; - -} // namespace - -// static -void CastCrashReporterClient::InitCrashReporter( - const std::string& process_type) { - g_process_start_time = (base::TimeTicks::Now() - - base::TimeTicks()).InMilliseconds(); - - // Save the process type (leaked). - // Note: "browser" process is identified by empty process type string. - const std::string& named_process_type( - process_type.empty() ? "browser" : process_type); - const size_t process_type_len = named_process_type.size() + 1; - g_process_type = new char[process_type_len]; - strncpy(g_process_type, named_process_type.c_str(), process_type_len); - - // Start up breakpad for this process, if applicable. - breakpad::InitCrashReporter(process_type); -} - -// static -char* CastCrashReporterClient::GetProcessType() { - return g_process_type; -} - -// static -uint64_t CastCrashReporterClient::GetProcessStartTime() { - return g_process_start_time; -} - -CastCrashReporterClient::CastCrashReporterClient() {} -CastCrashReporterClient::~CastCrashReporterClient() {} - -bool CastCrashReporterClient::EnableBreakpadForProcess( - const std::string& process_type) { - return process_type == switches::kRendererProcess || - process_type == switches::kZygoteProcess || - process_type == switches::kGpuProcess; -} - -} // namespace chromecast diff --git a/chromecast/crash/cast_crash_reporter_client_simple.cc b/chromecast/crash/cast_crash_reporter_client_simple.cc deleted file mode 100644 index aef4add13c0edd..00000000000000 --- a/chromecast/crash/cast_crash_reporter_client_simple.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2014 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/cast_crash_reporter_client.h" - -#include "base/logging.h" - -namespace chromecast { - -bool CastCrashReporterClient::HandleCrashDump(const char* crashdump_filename) { - LOG(INFO) << "Process " << GetProcessType() << " crashed; minidump in " - << crashdump_filename; - return true; -} - -} // namespace chromecast diff --git a/chromecast/crash/cast_crashdump_uploader.cc b/chromecast/crash/cast_crashdump_uploader.cc new file mode 100644 index 00000000000000..a3582ea60474bb --- /dev/null +++ b/chromecast/crash/cast_crashdump_uploader.cc @@ -0,0 +1,123 @@ +// 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/cast_crashdump_uploader.h" + +#include + +#include "base/logging.h" +// TODO(slan): Find a replacement for LibcurlWrapper in Chromium to remove the +// breakpad dependency. +#include "breakpad/src/common/linux/libcurl_wrapper.h" + +namespace chromecast { +namespace { + +// Keep these in sync with "//breakpad/src/client/mac/sender/uploader.mm" +const char kProdKey[] = "prod"; +const char kVerKey[] = "ver"; +const char kGuidKey[] = "guid"; +const char kPtimeKey[] = "ptime"; +const char kCtimeKey[] = "ctime"; +const char kEmailKey[] = "email"; +const char kCommentsKey[] = "comments"; + +} // namespace + +CastCrashdumpData::CastCrashdumpData() { +} + +CastCrashdumpData::~CastCrashdumpData() { +} + +CastCrashdumpUploader::CastCrashdumpUploader(const CastCrashdumpData& data) + : CastCrashdumpUploader(data, new google_breakpad::LibcurlWrapper()) { + // This instance of libcurlwrapper will leak. +} + +CastCrashdumpUploader::CastCrashdumpUploader( + const CastCrashdumpData& data, + google_breakpad::LibcurlWrapper* http_layer) + : http_layer_(http_layer), data_(data) { + DCHECK(http_layer_); +} + +CastCrashdumpUploader::~CastCrashdumpUploader() { +} + +bool CastCrashdumpUploader::AddAttachment(const std::string& label, + const std::string& filename) { + attachments_[label] = filename; + return true; +} + +bool CastCrashdumpUploader::CheckRequiredParametersArePresent() { + return !(data_.product.empty() || data_.version.empty() || + data_.guid.empty() || data_.minidump_pathname.empty()); +} + +bool CastCrashdumpUploader::Upload(std::string* response) { + if (http_layer_->Init()) { + LOG(ERROR) << "http layer Init failed"; + return false; + } + + if (!CheckRequiredParametersArePresent()) { + LOG(ERROR) << "Missing required parameters"; + return false; + } + + struct stat st; + if (0 != stat(data_.minidump_pathname.c_str(), &st)) { + LOG(ERROR) << data_.minidump_pathname << " does not exist."; + return false; + } + + if (!http_layer_->AddFile(data_.minidump_pathname, "upload_file_minidump")) { + LOG(ERROR) << "Failed to add file: " << data_.minidump_pathname; + return false; + } + + // Populate |parameters_|. + parameters_[kProdKey] = data_.product; + parameters_[kVerKey] = data_.version; + parameters_[kGuidKey] = data_.guid; + parameters_[kPtimeKey] = data_.ptime; + parameters_[kCtimeKey] = data_.ctime; + parameters_[kEmailKey] = data_.email; + parameters_[kCommentsKey] = data_.comments; + + // Add each attachement in |attachments_|. + for (auto iter = attachments_.begin(); iter != attachments_.end(); ++iter) { + // Search for the attachment. + if (0 != stat(iter->second.c_str(), &st)) { + LOG(ERROR) << iter->second << " could not be found"; + return false; + } + + // Add the attachment + if (!http_layer_->AddFile(iter->second, iter->first)) { + LOG(ERROR) << "Failed to add file: " << iter->second + << " with label: " << iter->first; + return false; + } + } + + LOG(INFO) << "Sending request to " << data_.crash_server; + + int http_status_code; + std::string http_header_data; + return http_layer_->SendRequest(data_.crash_server, + parameters_, + &http_status_code, + &http_header_data, + response); +} + +void CastCrashdumpUploader::SetParameter(const std::string& key, + const std::string& value) { + parameters_[key] = value; +} + +} // namespace chromecast diff --git a/chromecast/crash/cast_crashdump_uploader.h b/chromecast/crash/cast_crashdump_uploader.h new file mode 100644 index 00000000000000..acdfd9ad3b3bfa --- /dev/null +++ b/chromecast/crash/cast_crashdump_uploader.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef CHROMECAST_CRASH_CAST_CRASHDUMP_UPLOADER_H_ +#define CHROMECAST_CRASH_CAST_CRASHDUMP_UPLOADER_H_ + +#include +#include + +#include "base/macros.h" + +namespace google_breakpad { +class LibcurlWrapper; +} + +namespace chromecast { + +struct CastCrashdumpData { + CastCrashdumpData(); + ~CastCrashdumpData(); + + std::string product; + std::string version; + std::string guid; + std::string ptime; + std::string ctime; + std::string email; + std::string comments; + std::string minidump_pathname; + std::string crash_server; + std::string proxy_host; + std::string proxy_userpassword; +}; + +class CastCrashdumpUploader { + public: + // Does not take ownership of |http_layer|. + CastCrashdumpUploader(const CastCrashdumpData& data, + google_breakpad::LibcurlWrapper* http_layer); + CastCrashdumpUploader(const CastCrashdumpData& data); + ~CastCrashdumpUploader(); + + bool AddAttachment(const std::string& label, const std::string& filename); + void SetParameter(const std::string& key, const std::string& value); + bool Upload(std::string* response); + + private: + bool CheckRequiredParametersArePresent(); + + google_breakpad::LibcurlWrapper* http_layer_; + CastCrashdumpData data_; + + // Holds the following mapping for attachments: + std::map attachments_; + + // Holds the following mapping for HTTP request params: + std::map parameters_; + + DISALLOW_COPY_AND_ASSIGN(CastCrashdumpUploader); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_CAST_CRASHDUMP_UPLOADER_H_ diff --git a/chromecast/crash/linux/crash_util.cc b/chromecast/crash/linux/crash_util.cc new file mode 100644 index 00000000000000..e80b179c16e35d --- /dev/null +++ b/chromecast/crash/linux/crash_util.cc @@ -0,0 +1,91 @@ +// 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/crash_util.h" + +#include + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/time/time.h" +#include "chromecast/base/path_utils.h" +#include "chromecast/base/version.h" +#include "chromecast/crash/app_state_tracker.h" +#include "chromecast/crash/linux/dummy_minidump_generator.h" +#include "chromecast/crash/linux/minidump_writer.h" + +namespace chromecast { + +namespace { + +// This can be set to a callback for testing. This allows us to inject a fake +// dumpstate routine to avoid calling an executable during an automated test. +// This value should not be mutated through any other function except +// CrashUtil::SetDumpStateCbForTest(). +static base::Callback* g_dumpstate_cb = nullptr; + +} // namespace + +// static +uint64_t CrashUtil::GetCurrentTimeMs() { + return (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds(); +} + +// static +bool CrashUtil::RequestUploadCrashDump( + const std::string& existing_minidump_path, + const std::string& crashed_process_name, + uint64_t crashed_process_start_time_ms) { + LOG(INFO) << "Request to upload crash dump " << existing_minidump_path + << " for process " << crashed_process_name; + + // Note: Do not use Chromium IO methods in this function. When cast_shell + // crashes, this function can be called by any thread, which may not allow IO. + // Use stdlib system calls for IO instead. + uint64_t uptime_ms = GetCurrentTimeMs() - crashed_process_start_time_ms; + MinidumpParams params(crashed_process_name, + uptime_ms, + "", // suffix + AppStateTracker::GetPreviousApp(), + AppStateTracker::GetCurrentApp(), + AppStateTracker::GetLastLaunchedApp(), + CAST_BUILD_RELEASE, + CAST_BUILD_INCREMENTAL); + DummyMinidumpGenerator minidump_generator(existing_minidump_path); + + base::FilePath filename = base::FilePath(existing_minidump_path).BaseName(); + + scoped_ptr writer; + if (g_dumpstate_cb) { + writer.reset(new MinidumpWriter( + &minidump_generator, filename.value(), params, *g_dumpstate_cb)); + } else { + writer.reset( + new MinidumpWriter(&minidump_generator, filename.value(), params)); + } + bool success = false; + writer->set_non_blocking(false); + success = (0 == writer->Write()); // error already logged. + + // In case the file is still in $TEMP, remove it. + if (remove(existing_minidump_path.c_str()) < 0 && errno != ENOENT) { + LOG(ERROR) << "Unable to delete temp minidump file " + << existing_minidump_path << ": " << strerror(errno); + success = false; + } + + // Use std::endl to flush the log stream in case this process exits. + LOG(INFO) << "Request to upload crash dump finished. " + << "Exit now if it is main process that crashed." << std::endl; + + return success; +} + +void CrashUtil::SetDumpStateCbForTest( + const base::Callback& cb) { + DCHECK(!g_dumpstate_cb); + g_dumpstate_cb = new base::Callback(cb); +} + +} // namespace chromecast diff --git a/chromecast/crash/linux/crash_util.h b/chromecast/crash/linux/crash_util.h new file mode 100644 index 00000000000000..04de56516761e3 --- /dev/null +++ b/chromecast/crash/linux/crash_util.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef CHROMECAST_CRASH_LINUX_CRASH_UTIL_H_ +#define CHROMECAST_CRASH_LINUX_CRASH_UTIL_H_ + +#include + +#include + +#include "base/callback.h" + +namespace chromecast { + +class CrashUtil { + public: + // Helper function to request upload an existing minidump file. Returns true + // on success, false otherwise. + static bool RequestUploadCrashDump(const std::string& existing_minidump_path, + const std::string& crashed_process_name, + uint64_t crashed_process_start_time_ms); + + // Util function to get current time in ms. This is used to record + // crashed_process_start_time_ms in client side. + static uint64_t GetCurrentTimeMs(); + + // Call this to set a callback to be used instead of invoking an executable + // in a seperate process. See MinidumpWriter::SetDumpStateCbForTest() for more + // details on this callback's signature. + static void SetDumpStateCbForTest( + const base::Callback& cb); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_LINUX_CRASH_UTIL_H_ diff --git a/chromecast/crash/linux/dummy_minidump_generator.cc b/chromecast/crash/linux/dummy_minidump_generator.cc new file mode 100644 index 00000000000000..b4c7caa69fc7dd --- /dev/null +++ b/chromecast/crash/linux/dummy_minidump_generator.cc @@ -0,0 +1,37 @@ +// 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/dummy_minidump_generator.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" + +namespace chromecast { + +DummyMinidumpGenerator::DummyMinidumpGenerator( + const std::string& existing_minidump_path) + : existing_minidump_path_(existing_minidump_path) { +} + +bool DummyMinidumpGenerator::Generate(const std::string& minidump_path) { + base::FilePath file(existing_minidump_path_); + if (!base::PathExists(file)) { + LOG(ERROR) << file.value() << " is not a valid file path"; + return false; + } + + LOG(INFO) << "Moving minidump from " << existing_minidump_path_ << " to " + << minidump_path << " for further uploading."; + // Use stdlib call here to avoid potential IO restrictions on this thread. + if (rename(file.value().c_str(), minidump_path.c_str()) < 0) { + LOG(ERROR) << "Could not move file: " << file.value() << " " + << strerror(errno); + return false; + } + + return true; +} + +} // namespace chromecast diff --git a/chromecast/crash/linux/dummy_minidump_generator.h b/chromecast/crash/linux/dummy_minidump_generator.h new file mode 100644 index 00000000000000..9e638069230d6a --- /dev/null +++ b/chromecast/crash/linux/dummy_minidump_generator.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef CHROMECAST_CRASH_LINUX_DUMMY_MINIDUMP_GENERATOR_H_ +#define CHROMECAST_CRASH_LINUX_DUMMY_MINIDUMP_GENERATOR_H_ + +#include + +#include "base/macros.h" +#include "chromecast/crash/linux/minidump_generator.h" + +namespace chromecast { + +class DummyMinidumpGenerator : public MinidumpGenerator { + public: + // A dummy minidump generator to move an existing minidump into + // crash_uploader's monitoring path ($HOME/minidumps). The path is monitored + // with file lock-control, so that third process should not write to it + // directly. + explicit DummyMinidumpGenerator(const std::string& existing_minidump_path); + + // MinidumpGenerator implementation: + bool Generate(const std::string& minidump_path) override; + + private: + const std::string existing_minidump_path_; + + DISALLOW_COPY_AND_ASSIGN(DummyMinidumpGenerator); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_LINUX_DUMMY_MINIDUMP_GENERATOR_H_ diff --git a/chromecast/crash/linux/dump_info.cc b/chromecast/crash/linux/dump_info.cc new file mode 100644 index 00000000000000..d96245cfa81a1c --- /dev/null +++ b/chromecast/crash/linux/dump_info.cc @@ -0,0 +1,138 @@ +// 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/dump_info.h" + +#include + +#include + +#include "base/logging.h" +#include "base/strings/string_split.h" + +namespace chromecast { + +namespace { + +const char kDumpTimeFormat[] = "%Y-%m-%d %H:%M:%S"; +const unsigned kDumpTimeMaxLen = 255; + +const int kNumRequiredParams = 5; +} // namespace + +DumpInfo::DumpInfo(const std::string& entry) : valid_(false) { + // TODO(slan): This ctor is doing non-trivial work. Change this. + if ((valid_ = ParseEntry(entry)) == true) { + entry_ = GetEntryAsString(); + } +} + +DumpInfo::DumpInfo(const std::string& crashed_process_dump, + const std::string& logfile, + const time_t& dump_time, + const MinidumpParams& params) + : crashed_process_dump_(crashed_process_dump), + logfile_(logfile), + dump_time_(dump_time), + params_(params), + valid_(false) { + // The format is + // ||||||| + // | + // is in the format of kDumpTimeFormat + + // Validate the time passed in. + struct tm* tm = gmtime(&dump_time); + char buf[kDumpTimeMaxLen]; + int n = strftime(buf, kDumpTimeMaxLen, kDumpTimeFormat, tm); + if (n <= 0) { + LOG(INFO) << "strftime failed"; + return; + } + entry_ = GetEntryAsString(); + valid_ = true; +} + +DumpInfo::~DumpInfo() { +} + +std::string DumpInfo::GetEntryAsString() { + struct tm* tm = gmtime(&dump_time_); + char buf[kDumpTimeMaxLen]; + int n = strftime(buf, kDumpTimeMaxLen, kDumpTimeFormat, tm); + DCHECK_GT(n, 0); + + std::stringstream entrystream; + entrystream << params_.process_name << "|" << buf << "|" + << crashed_process_dump_ << "|" << params_.process_uptime << "|" + << logfile_ << "|" << params_.suffix << "|" + << params_.previous_app_name << "|" << params_.current_app_name + << "|" << params_.last_app_name << "|" + << params_.cast_release_version << "|" + << params_.cast_build_number << std::endl; + return entrystream.str(); +} + +bool DumpInfo::ParseEntry(const std::string& entry) { + // The format is + // ||||{|{|{ + // |{|last_launched_app_name}}}} + // is in the format |kDumpTimeFormat| + std::vector fields; + base::SplitString(entry, '|', &fields); + if (fields.size() < kNumRequiredParams) { + LOG(INFO) << "Invalid entry: Too few fields."; + return false; + } + + // Extract required fields. + params_.process_name = fields[0]; + if (!SetDumpTimeFromString(fields[1])) + return false; + crashed_process_dump_ = fields[2]; + params_.process_uptime = atoll(fields[3].c_str()); + logfile_ = fields[4]; + + // Extract all other optional fields. + for (size_t i = 5; i < fields.size(); ++i) { + const std::string& temp = fields[i]; + switch (i) { + case 5: // Optional field: suffix + params_.suffix = temp; + break; + case 6: // Optional field: prev_app_name + params_.previous_app_name = temp; + break; + case 7: // Optional field: current_app_name + params_.current_app_name = temp; + break; + case 8: // Optional field: last_launched_app_name + params_.last_app_name = temp; + break; + case 9: // extract an optional cast release version + params_.cast_release_version = temp; + break; + case 10: // extract an optional cast build number + params_.cast_build_number = temp; + break; + default: + LOG(INFO) << "Entry has too many fields invalid"; + return false; + } + } + valid_ = true; + return true; +} + +bool DumpInfo::SetDumpTimeFromString(const std::string& timestr) { + struct tm tm; + char* text = strptime(timestr.c_str(), kDumpTimeFormat, &tm); + dump_time_ = mktime(&tm); + if (!text || dump_time_ < 0) { + LOG(INFO) << "Failed to convert dump time invalid"; + return false; + } + return true; +} + +} // namespace chromecast diff --git a/chromecast/crash/linux/dump_info.h b/chromecast/crash/linux/dump_info.h new file mode 100644 index 00000000000000..7425f5d982d447 --- /dev/null +++ b/chromecast/crash/linux/dump_info.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef CHROMECAST_CRASH_LINUX_DUMP_INFO_H_ +#define CHROMECAST_CRASH_LINUX_DUMP_INFO_H_ + +#include + +#include "base/macros.h" +#include "chromecast/crash/linux/minidump_params.h" + +namespace chromecast { + +// Class that encapsulates the construction and parsing of dump entries +// in the log file. +class DumpInfo { + public: + // Attempt to construct a DumpInfo object by parsing the given entry string + // and extracting the contained information and populate the relevant + // fields. + explicit DumpInfo(const std::string& entry); + + // Attempt to construct a DumpInfo object that has the following info: + // + // -crashed_process_dump: the full path of the dump written + // -crashed_process_logfile: the full path of the logfile written + // -dump_time: the time of the dump written + // -params: a structure containing other useful crash information + // + // As a result of construction, the |entry_| will be filled with the + // appropriate string to add to the log file. + DumpInfo(const std::string& crashed_process_dump, + const std::string& crashed_process_logfile, + const time_t& dump_time, + const MinidumpParams& params); + + ~DumpInfo(); + + const std::string& crashed_process_dump() const { + return crashed_process_dump_; + } + const std::string& logfile() const { return logfile_; } + const time_t& dump_time() const { return dump_time_; } + const std::string& entry() const { return entry_; } + const MinidumpParams& params() const { return params_; } + const bool valid() const { return valid_; } + + private: + bool ParseEntry(const std::string& entry); + bool SetDumpTimeFromString(const std::string& timestr); + std::string GetEntryAsString(); + + std::string crashed_process_dump_; + std::string logfile_; + time_t dump_time_; + std::string entry_; + MinidumpParams params_; + bool valid_; + + DISALLOW_COPY_AND_ASSIGN(DumpInfo); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_LINUX_DUMP_INFO_H_ diff --git a/chromecast/crash/linux/dump_info_unittest.cc b/chromecast/crash/linux/dump_info_unittest.cc new file mode 100644 index 00000000000000..32c8d612e9393c --- /dev/null +++ b/chromecast/crash/linux/dump_info_unittest.cc @@ -0,0 +1,134 @@ +// 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 + +#include "chromecast/crash/linux/dump_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { + +TEST(DumpInfoTest, EmptyStringIsNotValid) { + DumpInfo dump_info(""); + ASSERT_FALSE(dump_info.valid()); +} + +TEST(DumpInfoTest, TooFewFieldsIsNotValid) { + DumpInfo dump_info("name|2001-11-12 18:31:01|dump_string"); + ASSERT_FALSE(dump_info.valid()); +} + +TEST(DumpInfoTest, BadTimeStringIsNotValid) { + DumpInfo info("name|Mar 23 2014 01:23:45|dump_string|123456789|logfile.log"); + ASSERT_FALSE(info.valid()); +} + +TEST(DumpInfoTest, AllRequiredFieldsIsValid) { + DumpInfo info("name|2001-11-12 18:31:01|dump_string|123456789|logfile.log"); + struct tm tm = {}; + tm.tm_isdst = 0; + tm.tm_sec = 1; + tm.tm_min = 31; + tm.tm_hour = 18; + tm.tm_mday = 12; + tm.tm_mon = 10; + tm.tm_year = 101; + time_t dump_time = mktime(&tm); + + ASSERT_TRUE(info.valid()); + ASSERT_EQ("name", info.params().process_name); + ASSERT_EQ(dump_time, info.dump_time()); + ASSERT_EQ("dump_string", info.crashed_process_dump()); + ASSERT_EQ(123456789u, info.params().process_uptime); + ASSERT_EQ("logfile.log", info.logfile()); +} + +TEST(DumpInfoTest, EmptyProcessNameIsValid) { + DumpInfo dump_info("|2001-11-12 18:31:01|dump_string|123456789|logfile.log"); + ASSERT_TRUE(dump_info.valid()); +} + +TEST(DumpInfoTest, SomeRequiredFieldsEmptyIsValid) { + // TODO(slan): This test is failing non-deterministically (time result is + // occasionally off by an hour - has something to do with DST). It is a + // concurrency issue with mktime. Investigate. + DumpInfo info("name|2001-11-12 18:31:01|||"); + struct tm tm = {}; + tm.tm_isdst = 0; + tm.tm_sec = 1; + tm.tm_min = 31; + tm.tm_hour = 18; + tm.tm_mday = 12; + tm.tm_mon = 10; + tm.tm_year = 101; + time_t dump_time = mktime(&tm); + + ASSERT_TRUE(info.valid()); + ASSERT_EQ("name", info.params().process_name); + ASSERT_EQ(dump_time, info.dump_time()); + ASSERT_EQ("", info.crashed_process_dump()); + ASSERT_EQ(0u, info.params().process_uptime); + ASSERT_EQ("", info.logfile()); +} + +TEST(DumpInfoTest, AllOptionalFieldsIsValid) { + DumpInfo info( + "name|2001-11-12 18:31:01|dump_string|123456789|logfile.log|" + "suffix|previous_app|current_app|last_app|RELEASE|BUILD_NUMBER"); + struct tm tm = {}; + tm.tm_isdst = 0; + tm.tm_sec = 1; + tm.tm_min = 31; + tm.tm_hour = 18; + tm.tm_mday = 12; + tm.tm_mon = 10; + tm.tm_year = 101; + time_t dump_time = mktime(&tm); + + ASSERT_TRUE(info.valid()); + ASSERT_EQ("name", info.params().process_name); + ASSERT_EQ(dump_time, info.dump_time()); + ASSERT_EQ("dump_string", info.crashed_process_dump()); + ASSERT_EQ(123456789u, info.params().process_uptime); + ASSERT_EQ("logfile.log", info.logfile()); + + ASSERT_EQ("suffix", info.params().suffix); + ASSERT_EQ("previous_app", info.params().previous_app_name); + ASSERT_EQ("current_app", info.params().current_app_name); + ASSERT_EQ("last_app", info.params().last_app_name); +} + +TEST(DumpInfoTest, SomeOptionalFieldsIsValid) { + DumpInfo info( + "name|2001-11-12 18:31:01|dump_string|123456789|logfile.log|" + "suffix|previous_app"); + struct tm tm = {}; + tm.tm_isdst = 0; + tm.tm_sec = 1; + tm.tm_min = 31; + tm.tm_hour = 18; + tm.tm_mday = 12; + tm.tm_mon = 10; + tm.tm_year = 101; + time_t dump_time = mktime(&tm); + + ASSERT_TRUE(info.valid()); + ASSERT_EQ("name", info.params().process_name); + ASSERT_EQ(dump_time, info.dump_time()); + ASSERT_EQ("dump_string", info.crashed_process_dump()); + ASSERT_EQ(123456789u, info.params().process_uptime); + ASSERT_EQ("logfile.log", info.logfile()); + + ASSERT_EQ("suffix", info.params().suffix); + ASSERT_EQ("previous_app", info.params().previous_app_name); +} + +TEST(DumpInfoTest, TooManyFieldsIsNotValid) { + DumpInfo info( + "name|2001-11-12 18:31:01|dump_string|123456789|logfile.log|" + "suffix|previous_app|current_app|last_app|VERSION|BUILD_NUM|extra_field"); + ASSERT_FALSE(info.valid()); +} + +} // chromecast \ No newline at end of file diff --git a/chromecast/crash/linux/minidump_generator.h b/chromecast/crash/linux/minidump_generator.h new file mode 100644 index 00000000000000..9780e430ec7102 --- /dev/null +++ b/chromecast/crash/linux/minidump_generator.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef CHROMECAST_CRASH_LINUX_MINIDUMP_GENERATOR_H_ +#define CHROMECAST_CRASH_LINUX_MINIDUMP_GENERATOR_H_ + +#include + +namespace chromecast { + +class MinidumpGenerator { + public: + virtual ~MinidumpGenerator() {} + + // Interface to generate a minidump file in given path. + // This is called inside MinidumpWriter::DoWorkLocked(). + virtual bool Generate(const std::string& minidump_path) = 0; +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_LINUX_MINIDUMP_GENERATOR_H_ diff --git a/chromecast/crash/linux/minidump_params.cc b/chromecast/crash/linux/minidump_params.cc new file mode 100644 index 00000000000000..3d532830c4847f --- /dev/null +++ b/chromecast/crash/linux/minidump_params.cc @@ -0,0 +1,35 @@ +// 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/minidump_params.h" + +namespace chromecast { + +MinidumpParams::MinidumpParams(const std::string& p_process_name, + const uint64_t p_process_uptime, + const std::string& p_suffix, + const std::string& p_previous_app_name, + const std::string& p_current_app_name, + const std::string& p_last_app_name, + const std::string& p_cast_release_version, + const std::string& p_cast_build_number) + : process_name(p_process_name), + process_uptime(p_process_uptime), + suffix(p_suffix), + previous_app_name(p_previous_app_name), + current_app_name(p_current_app_name), + last_app_name(p_last_app_name), + cast_release_version(p_cast_release_version), + cast_build_number(p_cast_build_number) { +} + +MinidumpParams::MinidumpParams() : process_uptime(0) { +} + +MinidumpParams::MinidumpParams(const MinidumpParams& params) = default; + +MinidumpParams::~MinidumpParams() { +} + +} // namespace chromecast diff --git a/chromecast/crash/linux/minidump_params.h b/chromecast/crash/linux/minidump_params.h new file mode 100644 index 00000000000000..1a1c15518add1b --- /dev/null +++ b/chromecast/crash/linux/minidump_params.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef CHROMECAST_CRASH_LINUX_MINIDUMP_PARAMS_H_ +#define CHROMECAST_CRASH_LINUX_MINIDUMP_PARAMS_H_ + +#include + +namespace chromecast { + +struct MinidumpParams { + MinidumpParams(); + MinidumpParams(const std::string& p_process_name, + const uint64_t p_process_uptime, + const std::string& p_suffix, + const std::string& p_previous_app_name, + const std::string& p_current_app_name, + const std::string& p_last_app_name, + const std::string& p_cast_release_version, + const std::string& p_cast_build_number); + MinidumpParams(const MinidumpParams& params); + ~MinidumpParams(); + + std::string process_name; + uint64_t process_uptime; + std::string suffix; + std::string previous_app_name; + std::string current_app_name; + std::string last_app_name; + // Release version is in the format of "major.minor", such as "1.15". + std::string cast_release_version; + // Build number is numerical string such as "20000". + std::string cast_build_number; +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_LINUX_MINIDUMP_PARAMS_H_ diff --git a/chromecast/crash/linux/minidump_writer.cc b/chromecast/crash/linux/minidump_writer.cc new file mode 100644 index 00000000000000..5f4016e1c4efc1 --- /dev/null +++ b/chromecast/crash/linux/minidump_writer.cc @@ -0,0 +1,135 @@ +// 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/minidump_writer.h" + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "chromecast/base/path_utils.h" +#include "chromecast/base/process_utils.h" +#include "chromecast/crash/linux/dump_info.h" +#include "chromecast/crash/linux/minidump_generator.h" + +namespace chromecast { + +namespace { + +const char kDumpStateSuffix[] = ".txt.gz"; + +const int kDefaultDumpIntervalHours = 24; +const int kDefaultMaxDumps = 5; +const int kDefaultMaxRecentDumps = 5; + +// Fork and run dumpstate, saving results to minidump_name + ".txt.gz". +int DumpState(const std::string& minidump_name) { + std::vector argv; + argv.push_back(GetBinPathASCII("dumpstate").value()); + argv.push_back("-w"); + argv.push_back("crash-request"); + argv.push_back("-z"); + argv.push_back("-o"); + argv.push_back( + minidump_name); // dumpstate appends ".txt.gz" to the filename. + + std::string log; + if (!chromecast::GetAppOutput(argv, &log)) { + LOG(ERROR) << "failed to execute dumpstate"; + return -1; + } + return 0; +} + +} // namespace + +MinidumpWriter::MinidumpWriter(MinidumpGenerator* minidump_generator, + const std::string& minidump_filename, + const MinidumpParams& params, + const DumpStateCallback& dump_state_cb) + : minidump_generator_(minidump_generator), + minidump_path_(minidump_filename), + params_(params), + max_dumps_(kDefaultMaxDumps), + dump_interval_(base::TimeDelta::FromHours(kDefaultDumpIntervalHours)), + max_recent_dumps_(kDefaultMaxRecentDumps), + dump_state_cb_(dump_state_cb) { +} + +MinidumpWriter::MinidumpWriter(MinidumpGenerator* minidump_generator, + const std::string& minidump_filename, + const MinidumpParams& params) + : MinidumpWriter(minidump_generator, + minidump_filename, + params, + base::Bind(&DumpState)) { +} + +MinidumpWriter::~MinidumpWriter() { +} + +int MinidumpWriter::DoWork() { + // If path is not absolute, append it to |dump_path_|. + if (!minidump_path_.value().empty() && minidump_path_.value()[0] != '/') + minidump_path_ = dump_path_.Append(minidump_path_); + + // The path should be a file in the |dump_path_| directory. + if (dump_path_ != minidump_path_.DirName()) { + LOG(INFO) << "The absolute path: " << minidump_path_.value() << " is not" + << "in the correct directory: " << dump_path_.value(); + return -1; + } + + // Query if we are able to write another minidump. + if (!CanWriteDump()) { + LOG(INFO) << "Skipping writing of dump due to limits"; + return -1; + } + + // Generate a minidump at the specified |minidump_path_|. + if (!minidump_generator_->Generate(minidump_path_.value())) { + LOG(ERROR) << "Generate minidump failed " << minidump_path_.value(); + return -1; + } + + // Run the dumpstate callback. + DCHECK(!dump_state_cb_.is_null()); + if (dump_state_cb_.Run(minidump_path_.value()) < 0) { + LOG(ERROR) << "DumpState callback failed."; + return -1; + } + + // Add this entry to the lockfile. + const DumpInfo info(minidump_path_.value(), + minidump_path_.value() + kDumpStateSuffix, + time(NULL), + params_); + if (AddEntryToLockFile(info) < 0) { + LOG(ERROR) << "lockfile logging failed"; + return -1; + } + + return 0; +} + +bool MinidumpWriter::CanWriteDump() { + const auto& dumps = GetDumpMetadata(); + + // If no more dumps can be written, return false. + if (static_cast(dumps.size()) >= max_dumps_) + return false; + + // If too many dumps have been written recently, return false. + time_t cur_time = time(0); + int recent_dumps = 0; + for (auto dump : dumps) { + if (difftime(cur_time, dump->dump_time()) <= dump_interval_.InSecondsF()) { + if (++recent_dumps >= max_recent_dumps_) + return false; + } + } + + return true; +} + +} // namespace crash_manager diff --git a/chromecast/crash/linux/minidump_writer.h b/chromecast/crash/linux/minidump_writer.h new file mode 100644 index 00000000000000..69e6233133cb07 --- /dev/null +++ b/chromecast/crash/linux/minidump_writer.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef CHROMECAST_CRASH_LINUX_MINIDUMP_WRITER_H_ +#define CHROMECAST_CRASH_LINUX_MINIDUMP_WRITER_H_ + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "chromecast/crash/linux/minidump_params.h" +#include "chromecast/crash/linux/synchronized_minidump_manager.h" + +namespace chromecast { + +class MinidumpGenerator; + +// Class for writing a minidump with synchronized access to the minidumps +// directory. +class MinidumpWriter : public SynchronizedMinidumpManager { + public: + typedef base::Callback DumpStateCallback; + + // Constructs a writer for a minidump. If |minidump_filename| is absolute, it + // must be a path to a file in the |dump_path_| directory. Otherwise, it + // should be a filename only, in which case, |minidump_generator| creates + // a minidump at $HOME/minidumps/|minidump_filename|. |params| describes the + // minidump metadata. |dump_state_cb| is Run() to generate a log dump. Please + // see the comments on |dump_state_cb_| below for details about this + // parameter. + // This does not take ownership of |minidump_generator|. + MinidumpWriter(MinidumpGenerator* minidump_generator, + const std::string& minidump_filename, + const MinidumpParams& params, + const base::Callback& dump_state_cb); + + // Like the constructor above, but the default implementation of + // |dump_state_cb_| is used inside DoWork(). + MinidumpWriter(MinidumpGenerator* minidump_generator, + const std::string& minidump_filename, + const MinidumpParams& params); + + ~MinidumpWriter() override; + + // Acquires exclusive access to the minidumps directory and generates a + // minidump and a log to be uploaded later. Returns 0 if successful, -1 + // otherwise. + int Write() { return AcquireLockAndDoWork(); } + + int max_dumps() const { return max_dumps_; } + int max_recent_dumps() const { return max_recent_dumps_; } + const base::TimeDelta& dump_interval() const { return dump_interval_; }; + + protected: + // MinidumpManager implementation: + int DoWork() override; + + private: + // Returns true if we can write another dump, false otherwise. We can write + // another dump if the number of minidumps is strictly less than |max_dumps_| + // and the number of minidumps which occurred within the last |dump_interval_| + // is strictly less than |max_recent_dumps_|. + bool CanWriteDump(); + + MinidumpGenerator* const minidump_generator_; + base::FilePath minidump_path_; + const MinidumpParams params_; + + const int max_dumps_; + const base::TimeDelta dump_interval_; + const int max_recent_dumps_; + + // This callback is Run() to dump a log to |minidump_path_|.txt.gz, taking + // |minidump_path_| as a parameter. It returns 0 on success, and a negative + // integer otherwise. If a callback is not passed in the constructor, the + // default implemementaion is used. + DumpStateCallback dump_state_cb_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpWriter); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_LINUX_MINIDUMP_WRITER_H_ diff --git a/chromecast/crash/linux/minidump_writer_unittest.cc b/chromecast/crash/linux/minidump_writer_unittest.cc new file mode 100644 index 00000000000000..c096f7ae60ec0a --- /dev/null +++ b/chromecast/crash/linux/minidump_writer_unittest.cc @@ -0,0 +1,172 @@ +// 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 + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/test/scoped_path_override.h" +#include "chromecast/crash/linux/minidump_generator.h" +#include "chromecast/crash/linux/minidump_writer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace { + +const char kDumplogFile[] = "dumplog"; +const char kLockfileName[] = "lockfile"; +const char kMinidumpSubdir[] = "minidumps"; + +std::string GetCurrentTimeASCII() { + char cur_time[20]; + time_t now = time(NULL); + struct tm* tm = gmtime(&now); + strftime(cur_time, 20, "%Y-%m-%d %H:%M:%S", tm); + return std::string(cur_time); +} + +class FakeMinidumpGenerator : public MinidumpGenerator { + public: + FakeMinidumpGenerator() {} + ~FakeMinidumpGenerator() override {} + + // MinidumpGenerator implementation: + bool Generate(const std::string& minidump_path) override { return true; } +}; + +int FakeDumpState(const std::string& minidump_path) { + return 0; +} + +} // namespace + +class MinidumpWriterTest : public testing::Test { + protected: + MinidumpWriterTest() {} + ~MinidumpWriterTest() override {} + + void SetUp() override { + // Set up a temporary directory which will be used as our fake home dir. + base::FilePath fake_home_dir; + ASSERT_TRUE(base::CreateNewTempDirectory("", &fake_home_dir)); + home_.reset(new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir)); + minidump_dir_ = fake_home_dir.Append(kMinidumpSubdir); + dumplog_file_ = minidump_dir_.Append(kDumplogFile); + + // Create the minidump directory and lockfile. + ASSERT_TRUE(base::CreateDirectory(minidump_dir_)); + base::File lockfile( + minidump_dir_.Append(kLockfileName), + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + ASSERT_TRUE(lockfile.IsValid()); + } + + FakeMinidumpGenerator fake_generator_; + base::FilePath minidump_dir_; + base::FilePath dumplog_file_; + + private: + scoped_ptr home_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpWriterTest); +}; + +TEST_F(MinidumpWriterTest, Write_FailsWithIncorrectMinidumpPath) { + MinidumpWriter writer(&fake_generator_, + "/path/to/wrong/dir", + MinidumpParams(), + base::Bind(&FakeDumpState)); + + ASSERT_EQ(-1, writer.Write()); +} + +TEST_F(MinidumpWriterTest, Write_FailsWithMultiLevelRelativeMinidumpPath) { + MinidumpWriter writer(&fake_generator_, + "subdir/dumplog", + MinidumpParams(), + base::Bind(&FakeDumpState)); + + ASSERT_EQ(-1, writer.Write()); +} + +TEST_F(MinidumpWriterTest, Write_SucceedsWithSimpleFilename) { + MinidumpWriter writer(&fake_generator_, + "dumplog", + MinidumpParams(), + base::Bind(&FakeDumpState)); + + ASSERT_EQ(0, writer.Write()); +} + +TEST_F(MinidumpWriterTest, Write_SucceedsWithCorrectMinidumpPath) { + MinidumpWriter writer(&fake_generator_, + dumplog_file_.value(), + MinidumpParams(), + base::Bind(&FakeDumpState)); + + ASSERT_EQ(0, writer.Write()); +} + +TEST_F(MinidumpWriterTest, Write_FailsWithSubdirInCorrectPath) { + MinidumpWriter writer(&fake_generator_, + dumplog_file_.Append("subdir/logfile").value(), + MinidumpParams(), + base::Bind(&FakeDumpState)); + ASSERT_EQ(-1, writer.Write()); +} + +TEST_F(MinidumpWriterTest, Write_FailsWhenTooManyDumpsPresent) { + MinidumpWriter writer(&fake_generator_, + dumplog_file_.value(), + MinidumpParams(), + base::Bind(&FakeDumpState)); + + // Write dump logs to the lockfile. + std::ofstream lockfile(minidump_dir_.Append(kLockfileName).value()); + ASSERT_TRUE(lockfile.is_open()); + size_t too_many_dumps = writer.max_dumps() + 1; + for (size_t i = 0; i < too_many_dumps; ++i) { + lockfile << "p|2012-01-01 01:02:03|/dump/path||" << std::endl; + } + lockfile.close(); + + ASSERT_EQ(-1, writer.Write()); +} + +TEST_F(MinidumpWriterTest, Write_FailsWhenTooManyRecentDumpsPresent) { + MinidumpWriter writer(&fake_generator_, + dumplog_file_.value(), + MinidumpParams(), + base::Bind(&FakeDumpState)); + + // Write dump logs to the lockfile. + std::ofstream lockfile(minidump_dir_.Append(kLockfileName).value()); + ASSERT_TRUE(lockfile.is_open()); + size_t too_many_recent_dumps = writer.max_recent_dumps() + 1; + for (size_t i = 0; i < too_many_recent_dumps; ++i) { + lockfile << "|" << GetCurrentTimeASCII() << "|/dump/path||" << std::endl; + } + + ASSERT_EQ(-1, writer.Write()); +} + +TEST_F(MinidumpWriterTest, Write_SucceedsWhenDumpLimitsNotExceeded) { + MinidumpWriter writer(&fake_generator_, + dumplog_file_.value(), + MinidumpParams(), + base::Bind(&FakeDumpState)); + + ASSERT_GT(writer.max_dumps(), 1); + ASSERT_GT(writer.max_recent_dumps(), 0); + + // Write an old dump logs to the lockfile. + std::ofstream lockfile(minidump_dir_.Append(kLockfileName).value()); + ASSERT_TRUE(lockfile.is_open()); + lockfile << "p|2012-01-01 01:02:03|/dump/path||" << std::endl; +} + +} // namespace chromecast diff --git a/chromecast/crash/linux/synchronized_minidump_manager.cc b/chromecast/crash/linux/synchronized_minidump_manager.cc new file mode 100644 index 00000000000000..358427a2dd31a3 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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& 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 > dumps(new ScopedVector()); + std::string entry; + + // Instead of using |lockfile_fd_|, use 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 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(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 diff --git a/chromecast/crash/linux/synchronized_minidump_manager.h b/chromecast/crash/linux/synchronized_minidump_manager.h new file mode 100644 index 00000000000000..4adaa30cfc1af9 --- /dev/null +++ b/chromecast/crash/linux/synchronized_minidump_manager.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef CHROMECAST_CRASH_LINUX_SYNCHRONIZED_MINIDUMP_MANAGER_H_ +#define CHROMECAST_CRASH_LINUX_SYNCHRONIZED_MINIDUMP_MANAGER_H_ + +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/scoped_vector.h" + +namespace chromecast { + +class DumpInfo; + +// Abstract base class for mutually-exclusive minidump handling. Ensures +// synchronized access among instances of this class to the minidumps directory +// using a file lock. The "lockfile" also holds serialized metadata about each +// of the minidumps in the directory. Derived classes should not access the +// lockfile directly. Instead, use protected methods to query and modify the +// metadata, but only within the implementation of DoWork(). +class SynchronizedMinidumpManager { + public: + virtual ~SynchronizedMinidumpManager(); + + // Returns whether this object's file locking method is nonblocking or not. + bool non_blocking() { return non_blocking_; } + + // Sets the file locking mechansim to be nonblocking or not. + void set_non_blocking(bool non_blocking) { non_blocking_ = non_blocking; } + + protected: + SynchronizedMinidumpManager(); + + // Acquires the lock, calls DoWork(), then releases the lock when DoWork() + // returns. Derived classes should expose a method which calls this. Returns + // the status of DoWork(), or -1 if the lock was not successfully acquired. + int AcquireLockAndDoWork(); + + // Derived classes must implement this method. It will be called from + // DoWorkLocked after the lock has been successfully acquired. The lockfile + // shall be accessed and mutated only through the methods below. All other + // files shall be managed as needed by the derived class. + virtual int DoWork() = 0; + + // Access the container holding all the metadata for the dumps. Note that + // the child class must only call this inside DoWork(). This is lazy. If the + // lockfile has not been parsed yet, it will be parsed when this is called. + const ScopedVector& GetDumpMetadata(); + + // Serialize |dump_info| and append it to the lockfile. Note that the child + // class must only call this inside DoWork(). This should be the only method + // used to write to the lockfile. Only call this if the minidump has been + // generated in the minidumps directory successfully. Returns 0 on success, + // -1 otherwise. + int AddEntryToLockFile(const DumpInfo& dump_info); + + // Remove the lockfile entry at |index| in the container returned by + // GetDumpMetadata(). If the index is invalid or an IO error occurred, returns + // -1. Otherwise returns 0. When this function returns, both the in-memory + // containter returned by GetDumpMetadata and the persistent lockfile will be + // current. + int RemoveEntryFromLockFile(int index); + + // Get the number of un-uploaded dumps in the dump_path directory. + // If delete_all_dumps is true, also delete all these files, this is used to + // clean lingering dump files. + int GetNumDumps(bool delete_all_dumps); + + // TODO(slan): Remove this accessor. All I/O on the lockfile in inherited + // classes should be done via GetDumpMetadata(), AddEntryToLockFile(), and + // RemoveEntryFromLockFile(). + const std::string& lockfile_path() { return lockfile_path_; } + + // If true, the flock on the lockfile will be nonblocking + bool non_blocking_; + + // Cached path for the minidumps directory. + base::FilePath dump_path_; + + private: + // Acquire the lock file. Blocks if another process holds it, or if called + // a second time by the same process. Returns the fd of the lockfile if + // successful, or -1 if failed. + int AcquireLockFile(); + + // Parse the lockfile, populating |dumps_| for descendants to use. Return -1 + // if an error occurred. Otherwise, return 0. This must not be called unless + // |this| has acquired the lock. + int ParseLockFile(); + + // Release the lock file with the associated *fd*. + void ReleaseLockFile(); + + std::string lockfile_path_; + int lockfile_fd_; + scoped_ptr > dump_metadata_; + + DISALLOW_COPY_AND_ASSIGN(SynchronizedMinidumpManager); +}; + +} // namespace chromecast + +#endif // CHROMECAST_CRASH_LINUX_SYNCHRONIZED_MINIDUMP_MANAGER_H_ diff --git a/chromecast/crash/linux/synchronized_minidump_manager_unittest.cc b/chromecast/crash/linux/synchronized_minidump_manager_unittest.cc new file mode 100644 index 00000000000000..ea3e908b3e9979 --- /dev/null +++ b/chromecast/crash/linux/synchronized_minidump_manager_unittest.cc @@ -0,0 +1,388 @@ +// 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 +#include +#include +#include // mkdir +#include // +#include // perror +#include + +#include + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/process/launch.h" +#include "base/test/scoped_path_override.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "chromecast/crash/linux/dump_info.h" +#include "chromecast/crash/linux/synchronized_minidump_manager.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace { + +const char kLockfileName[] = "lockfile"; +const char kMinidumpSubdir[] = "minidumps"; + +ScopedVector GetCurrentDumps(const std::string& logfile_path) { + ScopedVector dumps; + std::string entry; + + std::ifstream in(logfile_path); + DCHECK(in.is_open()); + while (std::getline(in, entry)) { + scoped_ptr info(new DumpInfo(entry)); + dumps.push_back(info.Pass()); + } + return dumps.Pass(); +} + +// A trivial implementation of SynchronizedMinidumpManager, which does no work +// to the +// minidump and exposes its protected members for testing. +class SynchronizedMinidumpManagerSimple : public SynchronizedMinidumpManager { + public: + SynchronizedMinidumpManagerSimple() + : SynchronizedMinidumpManager(), + work_done_(false), + add_entry_return_code_(-1), + lockfile_path_(dump_path_.Append(kLockfileName).value()) {} + ~SynchronizedMinidumpManagerSimple() override {} + + void SetDumpInfoToWrite(scoped_ptr dump_info) { + dump_info_ = dump_info.Pass(); + } + + int DoWorkLocked() { return AcquireLockAndDoWork(); } + + // SynchronizedMinidumpManager implementation: + int DoWork() override { + if (dump_info_) + add_entry_return_code_ = AddEntryToLockFile(*dump_info_); + work_done_ = true; + return 0; + } + + // Accessors for testing. + const std::string& dump_path() { return dump_path_.value(); } + const std::string& lockfile_path() { return lockfile_path_; } + bool work_done() { return work_done_; } + int add_entry_return_code() { return add_entry_return_code_; } + + private: + bool work_done_; + int add_entry_return_code_; + std::string lockfile_path_; + scoped_ptr dump_info_; +}; + +void DoWorkLockedTask(SynchronizedMinidumpManagerSimple* manager) { + manager->DoWorkLocked(); +} + +class SleepySynchronizedMinidumpManagerSimple + : public SynchronizedMinidumpManagerSimple { + public: + SleepySynchronizedMinidumpManagerSimple(int sleep_duration_ms) + : SynchronizedMinidumpManagerSimple(), + sleep_duration_ms_(sleep_duration_ms) {} + ~SleepySynchronizedMinidumpManagerSimple() override {} + + // SynchronizedMinidumpManager implementation: + int DoWork() override { + // The lock has been acquired. Fall asleep for |kSleepDurationMs|, then + // write the file. + base::PlatformThread::Sleep( + base::TimeDelta::FromMilliseconds(sleep_duration_ms_)); + return SynchronizedMinidumpManagerSimple::DoWork(); + } + + private: + const int sleep_duration_ms_; +}; + +class SynchronizedMinidumpManagerTest : public testing::Test { + public: + SynchronizedMinidumpManagerTest() {} + ~SynchronizedMinidumpManagerTest() override {} + + void SetUp() override { + // Set up a temporary directory which will be used as our fake home dir. + ASSERT_TRUE(base::CreateNewTempDirectory("", &fake_home_dir_)); + path_override_.reset( + new base::ScopedPathOverride(base::DIR_HOME, fake_home_dir_)); + minidump_dir_ = fake_home_dir_.Append(kMinidumpSubdir); + lockfile_ = minidump_dir_.Append(kLockfileName); + + // Create a minidump directory. + ASSERT_TRUE(base::CreateDirectory(minidump_dir_)); + ASSERT_TRUE(base::IsDirectoryEmpty(minidump_dir_)); + + // Create a lockfile in that directory. + base::File lockfile( + lockfile_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + ASSERT_TRUE(lockfile.IsValid()); + } + + void TearDown() override { + // Remove the temp directory. + path_override_.reset(); + ASSERT_TRUE(base::DeleteFile(fake_home_dir_, true)); + } + + protected: + base::FilePath fake_home_dir_; // Path to the test home directory. + base::FilePath minidump_dir_; // Path the the minidump directory. + base::FilePath lockfile_; // Path to the lockfile in |minidump_dir_|. + + private: + scoped_ptr path_override_; +}; + +} // namespace + +TEST_F(SynchronizedMinidumpManagerTest, FilePathsAreCorrect) { + SynchronizedMinidumpManagerSimple manager; + + // Verify file paths for directory and lock file. + ASSERT_EQ(minidump_dir_.value(), manager.dump_path()); + ASSERT_EQ(lockfile_.value(), manager.lockfile_path()); +} + +TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnNonExistentDirectory) { + // The directory was created in SetUp(). Delete it and its contents. + ASSERT_TRUE(base::DeleteFile(minidump_dir_, true)); + ASSERT_FALSE(base::PathExists(minidump_dir_)); + + SynchronizedMinidumpManagerSimple manager; + ASSERT_EQ(0, manager.DoWorkLocked()); + ASSERT_TRUE(manager.work_done()); + + // Verify the directory and the lockfile both exist. + ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); + ASSERT_TRUE(base::PathExists(lockfile_)); +} + +TEST_F(SynchronizedMinidumpManagerTest, AcquireLockOnExistingEmptyDirectory) { + // The lockfile was created in SetUp(). Delete it. + ASSERT_TRUE(base::DeleteFile(lockfile_, false)); + ASSERT_FALSE(base::PathExists(lockfile_)); + + SynchronizedMinidumpManagerSimple manager; + ASSERT_EQ(0, manager.DoWorkLocked()); + ASSERT_TRUE(manager.work_done()); + + // Verify the directory and the lockfile both exist. + ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); + ASSERT_TRUE(base::PathExists(lockfile_)); +} + +TEST_F(SynchronizedMinidumpManagerTest, + AcquireLockOnExistingDirectoryWithLockfile) { + SynchronizedMinidumpManagerSimple manager; + ASSERT_EQ(0, manager.DoWorkLocked()); + ASSERT_TRUE(manager.work_done()); + + // Verify the directory and the lockfile both exist. + ASSERT_TRUE(base::DirectoryExists(minidump_dir_)); + ASSERT_TRUE(base::PathExists(lockfile_)); +} + +TEST_F(SynchronizedMinidumpManagerTest, + AddEntryToLockFile_FailsWithInvalidEntry) { + // Test that the manager tried to log the entry and failed. + SynchronizedMinidumpManagerSimple manager; + manager.SetDumpInfoToWrite(make_scoped_ptr(new DumpInfo(""))); + ASSERT_EQ(0, manager.DoWorkLocked()); + ASSERT_EQ(-1, manager.add_entry_return_code()); + + // Verify the lockfile is untouched. + ScopedVector dumps = GetCurrentDumps(lockfile_.value()); + ASSERT_EQ(0u, dumps.size()); +} + +TEST_F(SynchronizedMinidumpManagerTest, + AddEntryToLockFile_SucceedsWithValidEntries) { + // Sample parameters. + time_t now = time(0); + MinidumpParams params; + params.process_name = "process"; + + // Write the first entry. + SynchronizedMinidumpManagerSimple manager; + manager.SetDumpInfoToWrite( + make_scoped_ptr(new DumpInfo("dump1", "log1", now, params))); + ASSERT_EQ(0, manager.DoWorkLocked()); + ASSERT_EQ(0, manager.add_entry_return_code()); + + // Test that the manager was successful in logging the entry. + ScopedVector dumps = GetCurrentDumps(lockfile_.value()); + ASSERT_EQ(1u, dumps.size()); + + // Write the second entry. + manager.SetDumpInfoToWrite( + make_scoped_ptr(new DumpInfo("dump2", "log2", now, params))); + ASSERT_EQ(0, manager.DoWorkLocked()); + ASSERT_EQ(0, manager.add_entry_return_code()); + + // Test that the second entry is also valid. + dumps = GetCurrentDumps(lockfile_.value()); + ASSERT_EQ(2u, dumps.size()); + + // TODO(slan): Weird time incosistencies making this fail. + // ASSERT_EQ(dumps[0]->entry(), DumpInfo("dump", "log", now, params).entry()); +} + +TEST_F(SynchronizedMinidumpManagerTest, + AcquireLockFile_FailsWhenNonBlockingAndFileLocked) { + // Lock the lockfile here. Note that the Chromium base::File tools permit + // multiple locks on the same process to succeed, so we must use POSIX system + // calls to accomplish this. + int fd = open(lockfile_.value().c_str(), O_RDWR | O_CREAT, 0660); + ASSERT_GE(fd, 0); + ASSERT_EQ(0, flock(fd, LOCK_EX)); + + SynchronizedMinidumpManagerSimple manager; + manager.set_non_blocking(true); + ASSERT_EQ(-1, manager.DoWorkLocked()); + ASSERT_FALSE(manager.work_done()); + + // Test that the manager was not able to log the crash dump. + ScopedVector dumps = GetCurrentDumps(lockfile_.value()); + ASSERT_EQ(0u, dumps.size()); +} + +TEST_F(SynchronizedMinidumpManagerTest, + AcquireLockFile_WaitsForOtherThreadWhenBlocking) { + // Create some parameters for a minidump. + time_t now = time(0); + MinidumpParams params; + params.process_name = "process"; + + // Create a manager that grabs the lock then sleeps. Post a DoWork task to + // another thread. |sleepy_manager| will grab the lock and hold it for + // |sleep_time_ms|. It will then write a dump and release the lock. + const int sleep_time_ms = 100; + SleepySynchronizedMinidumpManagerSimple sleepy_manager(sleep_time_ms); + sleepy_manager.SetDumpInfoToWrite( + make_scoped_ptr(new DumpInfo("dump", "log", now, params))); + base::Thread sleepy_thread("sleepy"); + sleepy_thread.Start(); + sleepy_thread.task_runner()->PostTask( + FROM_HERE, + base::Bind(&DoWorkLockedTask, base::Unretained(&sleepy_manager))); + + // Meanwhile, this thread should wait brielfy to allow the other thread to + // grab the lock. + const int concurrency_delay = 50; + base::PlatformThread::Sleep( + base::TimeDelta::FromMilliseconds(concurrency_delay)); + + // |sleepy_manager| has the lock by now, but has not released it. Attempt to + // grab it. DoWorkLocked() should block until |manager| has a chance to write + // the dump. + SynchronizedMinidumpManagerSimple manager; + manager.SetDumpInfoToWrite( + make_scoped_ptr(new DumpInfo("dump", "log", now, params))); + manager.set_non_blocking(false); + + EXPECT_EQ(0, manager.DoWorkLocked()); + EXPECT_EQ(0, manager.add_entry_return_code()); + EXPECT_TRUE(manager.work_done()); + + // Check that the other manager was also successful. + EXPECT_EQ(0, sleepy_manager.add_entry_return_code()); + EXPECT_TRUE(sleepy_manager.work_done()); + + // Test that both entries were logged. + ScopedVector dumps = GetCurrentDumps(lockfile_.value()); + EXPECT_EQ(2u, dumps.size()); +} + +// TODO(slan): These tests are passing but forking them is creating duplicates +// of all tests in this thread. Figure out how to lock the file more cleanly +// from another process. +TEST_F(SynchronizedMinidumpManagerTest, + DISABLED_AcquireLockFile_FailsWhenNonBlockingAndLockedFromOtherProcess) { + // Fork the process. + pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr); + if (pid != 0) { + // The child process should instantiate a manager which immediately grabs + // the lock, and falls aleep for some period of time, then writes a dump, + // and finally releases the lock. + SleepySynchronizedMinidumpManagerSimple sleepy_manager(100); + ASSERT_EQ(0, sleepy_manager.DoWorkLocked()); + ASSERT_TRUE(sleepy_manager.work_done()); + return; + } + + // Meanwhile, this process should wait brielfy to allow the other thread to + // grab the lock. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); + + SynchronizedMinidumpManagerSimple manager; + manager.set_non_blocking(true); + ASSERT_EQ(-1, manager.DoWorkLocked()); + ASSERT_FALSE(manager.work_done()); + + // Test that the manager was not able to log the crash dump. + ScopedVector dumps = GetCurrentDumps(lockfile_.value()); + ASSERT_EQ(0u, dumps.size()); +} + +// TODO(slan): These tests are passing but forking them is creating duplicates +// of all tests in this thread. Figure out how to lock the file more cleanly +// from another process. +TEST_F(SynchronizedMinidumpManagerTest, + DISABLED_AcquireLockFile_WaitsForOtherProcessWhenBlocking) { + // Create some parameters for a minidump. + time_t now = time(0); + MinidumpParams params; + params.process_name = "process"; + + // Fork the process. + pid_t pid = base::ForkWithFlags(0u, nullptr, nullptr); + if (pid != 0) { + // The child process should instantiate a manager which immediately grabs + // the lock, and falls aleep for some period of time, then writes a dump, + // and finally releases the lock. + SleepySynchronizedMinidumpManagerSimple sleepy_manager(100); + sleepy_manager.SetDumpInfoToWrite( + make_scoped_ptr(new DumpInfo("dump", "log", now, params))); + ASSERT_EQ(0, sleepy_manager.DoWorkLocked()); + ASSERT_TRUE(sleepy_manager.work_done()); + return; + } + + // Meanwhile, this process should wait brielfy to allow the other thread to + // grab the lock. + const int concurrency_delay = 50; + base::PlatformThread::Sleep( + base::TimeDelta::FromMilliseconds(concurrency_delay)); + + // |sleepy_manager| has the lock by now, but has not released it. Attempt to + // grab it. DoWorkLocked() should block until |manager| has a chance to write + // the dump. + SynchronizedMinidumpManagerSimple manager; + manager.SetDumpInfoToWrite( + make_scoped_ptr(new DumpInfo("dump", "log", now, params))); + manager.set_non_blocking(false); + + EXPECT_EQ(0, manager.DoWorkLocked()); + EXPECT_EQ(0, manager.add_entry_return_code()); + EXPECT_TRUE(manager.work_done()); + + // Test that both entries were logged. + ScopedVector dumps = GetCurrentDumps(lockfile_.value()); + EXPECT_EQ(2u, dumps.size()); +} + +} // namespace chromecast diff --git a/chromecast/media/BUILD.gn b/chromecast/media/BUILD.gn index d99640b89c076c..c927ad63b8f527 100644 --- a/chromecast/media/BUILD.gn +++ b/chromecast/media/BUILD.gn @@ -12,7 +12,7 @@ group("media") { ] } -test("unittests") { +test("cast_media_unittests") { sources = [ "//media/base", "cma/backend/audio_video_pipeline_device_unittest.cc",