From 8e774579a07b8520919205889a98dce4aecd5bc4 Mon Sep 17 00:00:00 2001 From: Benjamin Lerman Date: Wed, 9 Jul 2014 15:09:00 +0200 Subject: [PATCH] crash-reporter: Replace the crash_sender script by a service daemon. BUG=chromium:391887 TEST=Integration tests CQ-DEPEND=I02ce7593fcfae4ba1d7d3ebdf3912901e635f1c9 CQ-DEPEND=I00315ad47657cebd9e8a4a0121ecb54114a7e200 Change-Id: I2f4546c82fb3769b5f3da5d22949551412096b10 Reviewed-on: https://chromium-review.googlesource.com/208671 Tested-by: Benjamin Lerman Reviewed-by: Ben Chan --- crash_reporter/crash-reporter.gyp | 40 +- crash_reporter/crash_sender | 685 -------------------- crash_reporter/crash_sender.conf | 28 + crash_reporter/crash_sender_daemon.cc | 101 +++ crash_reporter/crash_sender_daemon.h | 43 ++ crash_reporter/crash_sender_service.cc | 853 +++++++++++++++++++++++++ crash_reporter/crash_sender_service.h | 114 ++++ crash_reporter/init/crash-sender.conf | 11 +- crash_reporter/libproxies.cc | 57 ++ crash_reporter/libproxies.h | 28 + crash_reporter/list_proxies.cc | 257 -------- crash_reporter/proxy_resolver.cc | 41 ++ crash_reporter/proxy_resolver.h | 45 ++ 13 files changed, 1353 insertions(+), 950 deletions(-) delete mode 100755 crash_reporter/crash_sender create mode 100644 crash_reporter/crash_sender.conf create mode 100644 crash_reporter/crash_sender_daemon.cc create mode 100644 crash_reporter/crash_sender_daemon.h create mode 100644 crash_reporter/crash_sender_service.cc create mode 100644 crash_reporter/crash_sender_service.h create mode 100644 crash_reporter/libproxies.cc create mode 100644 crash_reporter/libproxies.h delete mode 100644 crash_reporter/list_proxies.cc create mode 100644 crash_reporter/proxy_resolver.cc create mode 100644 crash_reporter/proxy_resolver.h diff --git a/crash_reporter/crash-reporter.gyp b/crash_reporter/crash-reporter.gyp index 79faf97e5..f36d1d0f1 100644 --- a/crash_reporter/crash-reporter.gyp +++ b/crash_reporter/crash-reporter.gyp @@ -63,17 +63,51 @@ ], }, { - 'target_name': 'list_proxies', + 'target_name': 'libproxies', + 'type': 'static_library', + 'variables': { + 'exported_deps': [ + 'libchrome-<(libbase_ver)', + ], + 'deps': ['<@(exported_deps)'], + }, + 'all_dependent_settings': { + 'variables': { + 'deps': [ + '<@(exported_deps)', + ], + }, + }, + 'sources': [ + 'libproxies.cc', + 'libproxies.h', + ], + }, + { + 'target_name': 'crash_sender', 'type': 'executable', 'variables': { 'deps': [ 'dbus-1', - 'dbus-glib-1', 'libchrome-<(libbase_ver)', + 'libchromeos-<(libbase_ver)', + 'libcurl', + 'libmetrics-<(libbase_ver)', ], }, + 'dependencies': [ + 'libproxies', + ], + 'libraries': [ + '-lvboot_host', + ], 'sources': [ - 'list_proxies.cc', + 'crash_sender_daemon.cc', + 'crash_sender_daemon.h', + 'crash_sender_service.cc', + 'crash_sender_service.h', + 'proxy_resolver.cc', + 'proxy_resolver.h', ], }, { diff --git a/crash_reporter/crash_sender b/crash_reporter/crash_sender deleted file mode 100755 index c83a12ae2..000000000 --- a/crash_reporter/crash_sender +++ /dev/null @@ -1,685 +0,0 @@ -#!/bin/sh - -# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -set -e - -# Default product ID in crash report (used if GOOGLE_CRASH_* is undefined). -CHROMEOS_PRODUCT=ChromeOS - -# File whose existence implies crash reports may be sent, and whose -# contents includes our machine's anonymized guid. -CONSENT_ID="/home/chronos/Consent To Send Stats" - -# Crash sender lock in case the sender is already running. -CRASH_SENDER_LOCK="/var/lock/crash_sender" - -# Path to file that indicates a crash test is currently running. -CRASH_TEST_IN_PROGRESS_FILE="/tmp/crash-test-in-progress" - -# Path to find which is required for computing the crash rate. -FIND="/usr/bin/find" - -# Set this to 1 in the environment to allow uploading crash reports -# for unofficial versions. -FORCE_OFFICIAL=${FORCE_OFFICIAL:-0} - -# Path to hardware class description. -HWCLASS_PATH="/sys/devices/platform/chromeos_acpi/HWID" - -# Path to file that indicates this is a developer image. -LEAVE_CORE_FILE="/root/.leave_core" - -# Path to list_proxies. -LIST_PROXIES="/usr/bin/list_proxies" - -# Maximum crashes to send per day. -MAX_CRASH_RATE=${MAX_CRASH_RATE:-32} - -# Path to metrics_client. -METRICS_CLIENT="/usr/bin/metrics_client" - -# File whose existence mocks crash sending. If empty we pretend the -# crash sending was successful, otherwise unsuccessful. -MOCK_CRASH_SENDING="/tmp/mock-crash-sending" - -# Set this to 1 in the environment to pretend to have booted in developer -# mode. This is used by autotests. -MOCK_DEVELOPER_MODE=${MOCK_DEVELOPER_MODE:-0} - -# Ignore PAUSE_CRASH_SENDING file if set. -OVERRIDE_PAUSE_SENDING=${OVERRIDE_PAUSE_SENDING:-0} - -# File whose existence causes crash sending to be delayed (for testing). -# Must be stateful to enable testing kernel crashes. -PAUSE_CRASH_SENDING="/var/lib/crash_sender_paused" - -# URL to send official build crash reports to. -REPORT_UPLOAD_PROD_URL="https://clients2.google.com/cr/report" - -# Path to a directory of restricted certificates which includes -# a certificate for ${REPORT_UPLOAD_PROD_URL}. -RESTRICTED_CERTIFICATES_PATH="/usr/share/chromeos-ca-certificates" - -# File whose existence implies we're running and not to start again. -RUN_FILE="/var/run/crash_sender.pid" - -# Maximum time to sleep between sends. -SECONDS_SEND_SPREAD=${SECONDS_SEND_SPREAD:-600} - -# The syslog tag for all logging we emit. -TAG="$(basename $0)[$$]" - -# Directory to store timestamp files indicating the uploads in the past 24 -# hours. -TIMESTAMPS_DIR="/var/lib/crash_sender" - -# Temp directory for this process. -TMP_DIR="" - -# Chrome's crash report log file. -CHROME_CRASH_LOG="/var/log/chrome/Crash Reports/uploads.log" - -lecho() { - logger -t "${TAG}" "$@" -} - -# Returns true if mock is enabled. -is_mock() { - [ -f "${MOCK_CRASH_SENDING}" ] && return 0 - return 1 -} - -is_mock_successful() { - local mock_in=$(cat "${MOCK_CRASH_SENDING}") - [ "${mock_in}" = "" ] && return 0 # empty file means success - return 1 -} - -cleanup() { - if [ -n "${TMP_DIR}" ]; then - rm -rf "${TMP_DIR}" - fi - rm -f "${RUN_FILE}" - crash_done -} - -crash_done() { - if is_mock; then - # For testing purposes, emit a message to log so that we - # know when the test has received all the messages from this run. - lecho "crash_sender done." - fi -} - -is_official_image() { - [ ${FORCE_OFFICIAL} -ne 0 ] && return 0 - grep ^CHROMEOS_RELEASE_DESCRIPTION /etc/lsb-release | grep -q Official -} - -# Returns 0 if the a crash test is currently running. NOTE: Mirrors -# crash_collector.cc:CrashCollector::IsCrashTestInProgress(). -is_crash_test_in_progress() { - [ -f "${CRASH_TEST_IN_PROGRESS_FILE}" ] && return 0 - return 1 -} - -# Returns 0 if we should consider ourselves to be running on a developer -# image. NOTE: Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage(). -is_developer_image() { - # If we're testing crash reporter itself, we don't want to special-case - # for developer images. - is_crash_test_in_progress && return 1 - [ -f "${LEAVE_CORE_FILE}" ] && return 0 - return 1 -} - -# Returns 0 if we should consider ourselves to be running on a test image. -is_test_image() { - # If we're testing crash reporter itself, we don't want to special-case - # for test images. - is_crash_test_in_progress && return 1 - case $(get_channel) in - test*) return 0;; - esac - return 1 -} - -# Returns 0 if the machine booted up in developer mode. -is_developer_mode() { - [ ${MOCK_DEVELOPER_MODE} -ne 0 ] && return 0 - # If we're testing crash reporter itself, we don't want to special-case - # for developer mode. - is_crash_test_in_progress && return 1 - crossystem "devsw_boot?1" # exit status will be accurate -} - -# Generate a uniform random number in 0..max-1. -generate_uniform_random() { - local max=$1 - local random="$(od -An -N4 -tu /dev/urandom)" - echo $((random % max)) -} - -# Check if sending a crash now does not exceed the maximum 24hr rate and -# commit to doing so, if not. -check_rate() { - mkdir -p ${TIMESTAMPS_DIR} - # Only consider minidumps written in the past 24 hours by removing all older. - ${FIND} "${TIMESTAMPS_DIR}" -mindepth 1 -mmin +$((24 * 60)) \ - -exec rm -- '{}' ';' - local sends_in_24hrs=$(echo "${TIMESTAMPS_DIR}"/* | wc -w) - lecho "Current send rate: ${sends_in_24hrs}sends/24hrs" - if [ ${sends_in_24hrs} -ge ${MAX_CRASH_RATE} ]; then - lecho "Cannot send more crashes:" - lecho " current ${sends_in_24hrs}send/24hrs >= " \ - "max ${MAX_CRASH_RATE}send/24hrs" - return 1 - fi - mktemp "${TIMESTAMPS_DIR}"/XXXX > /dev/null - return 0 -} - -# Gets the base part of a crash report file, such as name.01234.5678.9012 from -# name.01234.5678.9012.meta or name.01234.5678.9012.log.tar.xz. We make sure -# "name" is sanitized in CrashCollector::Sanitize to not include any periods. -get_base() { - echo "$1" | cut -d. -f-4 -} - -get_extension() { - local extension="${1##*.}" - local filename="${1%.*}" - # For gzipped file, we ignore .gz and get the real extension - if [ "${extension}" = "gz" ]; then - echo "${filename##*.}" - else - echo "${extension}" - fi -} - -# Return which kind of report the given metadata file relates to -get_kind() { - local payload="$(get_key_value "$1" "payload")" - if [ ! -r "${payload}" ]; then - lecho "Missing payload: ${payload}" - echo "undefined" - return - fi - local kind="$(get_extension "${payload}")" - if [ "${kind}" = "dmp" ]; then - echo "minidump" - return - fi - echo "${kind}" -} - -get_key_value() { - local file="$1" key="$2" value - - if [ -f "${file}" ]; then - # Return the first entry. There shouldn't be more than one anyways. - # Substr at length($1) + 2 skips past the key and following = sign (awk - # uses 1-based indexes), but preserves embedded = characters. - value=$(sed -n "/^${key}[[:space:]]*=/{s:^[^=]*=::p;q}" "${file}") - fi - - echo "${value:-undefined}" -} - -get_keys() { - local file="$1" regex="$2" - - awk -F'[[:space:]=]' -vregex="${regex}" \ - 'match($1, regex) { print $1 }' "${file}" -} - -# Return the board name. -get_board() { - get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_BOARD" -} - -# Return the channel name (sans "-channel" suffix). -get_channel() { - get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_TRACK" | - sed 's:-channel$::' -} - -# Return the hardware class or "undefined". -get_hardware_class() { - if [ -r "${HWCLASS_PATH}" ]; then - cat "${HWCLASS_PATH}" - elif crossystem hwid > /dev/null 2>&1; then - echo "$(crossystem hwid)" - else - echo "undefined" - fi -} - -send_crash() { - local meta_path="$1" - local report_payload="$(get_key_value "${meta_path}" "payload")" - local kind="$(get_kind "${meta_path}")" - local exec_name="$(get_key_value "${meta_path}" "exec_name")" - local url="${REPORT_UPLOAD_PROD_URL}" - local chromeos_version="$(get_key_value "${meta_path}" "ver")" - local board="$(get_board)" - local hwclass="$(get_hardware_class)" - local write_payload_size="$(get_key_value "${meta_path}" "payload_size")" - local log="$(get_key_value "${meta_path}" "log")" - local sig="$(get_key_value "${meta_path}" "sig")" - local send_payload_size="$(stat --printf=%s "${report_payload}" 2>/dev/null)" - local product="$(get_key_value "${meta_path}" "upload_var_prod")" - local version="$(get_key_value "${meta_path}" "upload_var_ver")" - local upload_prefix="$(get_key_value "${meta_path}" "upload_prefix")" - local guid - - set -- \ - -F "write_payload_size=${write_payload_size}" \ - -F "send_payload_size=${send_payload_size}" - if [ "${sig}" != "undefined" ]; then - set -- "$@" \ - -F "sig=${sig}" \ - -F "sig2=${sig}" - fi - if [ -r "${report_payload}" ]; then - set -- "$@" \ - -F "upload_file_${kind}=@${report_payload}" - fi - if [ "${log}" != "undefined" -a -r "${log}" ]; then - set -- "$@" \ - -F "log=@${log}" - fi - - if [ "${upload_prefix}" = "undefined" ]; then - upload_prefix="" - fi - - # Grab any variable that begins with upload_. - local v - for k in $(get_keys "${meta_path}" "^upload_"); do - v="$(get_key_value "${meta_path}" "${k}")" - case ${k} in - # Product & version are handled separately. - upload_var_prod) ;; - upload_var_ver) ;; - upload_var_*) - set -- "$@" -F "${upload_prefix}${k#upload_var_}=${v}" - ;; - upload_file_*) - if [ -r "${v}" ]; then - set -- "$@" -F "${upload_prefix}${k#upload_file_}=@${v}" - fi - ;; - esac - done - - # When uploading Chrome reports we need to report the right product and - # version. If the meta file does not specify it, use GOOGLE_CRASH_ID - # as the product and GOOGLE_CRASH_VERSION_ID as the version. - if [ "${product}" = "undefined" ]; then - product="$(get_key_value /etc/os-release 'GOOGLE_CRASH_ID')" - fi - if [ "${version}" = "undefined" ]; then - version="$(get_key_value /etc/os-release 'GOOGLE_CRASH_VERSION_ID')" - fi - - # If GOOGLE_CRASH_* is undefined, we look for ID and VERSION_ID in - # /etc/os-release. - if [ "${product}" = "undefined" ]; then - product="$(get_key_value /etc/os-release 'ID')" - fi - if [ "${version}" = "undefined" ]; then - version="$(get_key_value /etc/os-release 'VERSION_ID')" - fi - - # If ID or VERSION_ID is undefined, we use the default product name - # and CHROMEOS_RELEASE_VERSION from /etc/lsb-release. - if [ "${product}" = "undefined" ]; then - product="${CHROMEOS_PRODUCT}" - fi - if [ "${version}" = "undefined" ]; then - version="${chromeos_version}" - fi - - local image_type - if is_test_image; then - image_type="test" - elif is_developer_image; then - image_type="dev" - elif [ ${FORCE_OFFICIAL} -ne 0 ]; then - image_type="force-official" - elif is_mock && ! is_mock_successful; then - image_type="mock-fail" - fi - - local boot_mode - if ! crossystem "cros_debug" > /dev/null 2>&1; then - # Sanity-check failed that makes sure crossystem exists. - lecho "Cannot determine boot mode due to error running crossystem command" - boot_mode="missing-crossystem" - elif is_developer_mode; then - boot_mode="dev" - fi - - # Need to strip dashes ourselves as Chrome preserves it in the file - # nowadays. This is also what the Chrome breakpad client does. - guid=$(tr -d '-' < "${CONSENT_ID}") - - local error_type="$(get_key_value "${meta_path}" "error_type")" - [ "${error_type}" = "undefined" ] && error_type= - - lecho "Sending crash:" - if [ "${product}" != "${CHROMEOS_PRODUCT}" ]; then - lecho " Sending crash report on behalf of ${product}" - fi - lecho " Metadata: ${meta_path} (${kind})" - lecho " Payload: ${report_payload}" - lecho " Version: ${version}" - [ -n "${image_type}" ] && lecho " Image type: ${image_type}" - [ -n "${boot_mode}" ] && lecho " Boot mode: ${boot_mode}" - if is_mock; then - lecho " Product: ${product}" - lecho " URL: ${url}" - lecho " Board: ${board}" - lecho " HWClass: ${hwclass}" - lecho " write_payload_size: ${write_payload_size}" - lecho " send_payload_size: ${send_payload_size}" - if [ "${log}" != "undefined" ]; then - lecho " log: @${log}" - fi - if [ "${sig}" != "undefined" ]; then - lecho " sig: ${sig}" - fi - fi - lecho " Exec name: ${exec_name}" - [ -n "${error_type}" ] && lecho " Error type: ${error_type}" - if is_mock; then - if ! is_mock_successful; then - lecho "Mocking unsuccessful send" - return 1 - fi - lecho "Mocking successful send" - return 0 - fi - - # Read in the first proxy, if any, for a given URL. NOTE: The - # double-quotes are necessary due to a bug in dash with the "local" - # builtin command and values that have spaces in them (see - # "https://bugs.launchpad.net/ubuntu/+source/dash/+bug/139097"). - local proxy="`${LIST_PROXIES} -quiet "${url}" | head -1`" - # if a direct connection should be used, unset the proxy variable. - [ "${proxy}" = "direct://" ] && proxy= - local report_id="${TMP_DIR}/report_id" - local curl_stderr="${TMP_DIR}/curl_stderr" - - set +e - curl "${url}" -v ${proxy:+--proxy "$proxy"} \ - --capath "${RESTRICTED_CERTIFICATES_PATH}" --ciphers HIGH \ - -F "prod=${product}" \ - -F "ver=${version}" \ - -F "board=${board}" \ - -F "hwclass=${hwclass}" \ - -F "exec_name=${exec_name}" \ - ${image_type:+-F "image_type=${image_type}"} \ - ${boot_mode:+-F "boot_mode=${boot_mode}"} \ - ${error_type:+-F "error_type=${error_type}"} \ - -F "guid=${guid}" \ - -o "${report_id}" \ - "$@" \ - 2>"${curl_stderr}" - curl_result=$? - set -e - - if [ ${curl_result} -eq 0 ]; then - local id="$(cat "${report_id}")" - local product_name - local timestamp="$(date +%s)" - case ${product} in - Chrome_ChromeOS) - if is_official_image; then - product_name="Chrome" - else - product_name="Chromium" - fi - ;; - *) - if is_official_image; then - product_name="ChromeOS" - else - product_name="ChromiumOS" - fi - ;; - esac - printf '%s,%s,%s\n' \ - "${timestamp}" "${id}" "${product_name}" >> "${CHROME_CRASH_LOG}" - lecho "Crash report receipt ID ${id}" - else - lecho "Crash sending failed with exit code ${curl_result}: " \ - "$(cat "${curl_stderr}")" - fi - - rm -f "${report_id}" - - return ${curl_result} -} - -# *.meta files always end with done=1 so we can tell if they are complete. -is_complete_metadata() { - grep -q "done=1" "$1" -} - -# Remove the given report path. -remove_report() { - local base="${1%.*}" - rm -f -- "${base}".* -} - -# Send all crashes from the given directory. This applies even when we're on a -# 3G connection (see crosbug.com/3304 for discussion). -send_crashes() { - local dir="$1" - - if [ ! -d "${dir}" ]; then - return - fi - - # Consider any old files which still have no corresponding meta file - # as orphaned, and remove them. - for old_file in $(${FIND} "${dir}" -mindepth 1 \ - -mmin +$((24 * 60)) -type f); do - if [ ! -e "$(get_base "${old_file}").meta" ]; then - lecho "Removing old orphaned file: ${old_file}." - rm -f -- "${old_file}" - fi - done - - # Look through all metadata (*.meta) files, oldest first. That way, the rate - # limit does not stall old crashes if there's a high amount of new crashes - # coming in. - # For each crash report, first evaluate conditions that might lead to its - # removal to honor user choice and to free disk space as soon as possible, - # then decide whether it should be sent right now or kept for later sending. - for meta_path in $(ls -1tr "${dir}"/*.meta 2>/dev/null); do - lecho "Considering metadata ${meta_path}." - - local kind=$(get_kind "${meta_path}") - if [ "${kind}" != "minidump" ] && \ - [ "${kind}" != "kcrash" ] && \ - [ "${kind}" != "log" ]; then - lecho "Unknown report kind ${kind}. Removing report." - remove_report "${meta_path}" - continue - fi - - if ! is_complete_metadata "${meta_path}"; then - # This report is incomplete, so if it's old, just remove it. - local old_meta=$(${FIND} "${dir}" -mindepth 1 -name \ - $(basename "${meta_path}") -mmin +$((24 * 60)) -type f) - if [ -n "${old_meta}" ]; then - lecho "Removing old incomplete metadata." - remove_report "${meta_path}" - else - lecho "Ignoring recent incomplete metadata." - fi - continue - fi - - if ! is_mock && ! is_official_image; then - lecho "Not an official OS version. Removing crash." - remove_report "${meta_path}" - continue - fi - - # Don't send crash reports from previous sessions while we're in guest mode - # to avoid the impression that crash reporting was enabled, which it isn't. - # (Don't exit right now because subsequent reports may be candidates for - # deletion.) - if ${METRICS_CLIENT} -g; then - lecho "Guest mode has been entered. Delaying crash sending until exited." - continue - fi - - # Remove existing crashes in case user consent has not (yet) been given or - # has been revoked. This must come after the guest mode check because - # ${METRICS_CLIENT} always returns "not consented" in guest mode. - if ! ${METRICS_CLIENT} -c; then - lecho "Crash reporting is disabled. Removing crash." - remove_report "${meta_path}" - continue - fi - - # Skip report if the upload rate is exceeded. (Don't exit right now because - # subsequent reports may be candidates for deletion.) - if ! check_rate; then - lecho "Sending ${meta_path} would exceed rate. Leaving for later." - continue - fi - - # The .meta file should be written *after* all to-be-uploaded files that it - # references. Nevertheless, as a safeguard, a hold-off time of thirty - # seconds after writing the .meta file is ensured. Also, sending of crash - # reports is spread out randomly by up to SECONDS_SEND_SPREAD. Thus, for - # the sleep call the greater of the two delays is used. - local now=$(date +%s) - local holdoff_time=$(($(stat --format=%Y "${meta_path}") + 30 - ${now})) - local spread_time=$(generate_uniform_random "${SECONDS_SEND_SPREAD}") - local sleep_time - if [ ${spread_time} -gt ${holdoff_time} ]; then - sleep_time="${spread_time}" - else - sleep_time="${holdoff_time}" - fi - lecho "Scheduled to send in ${sleep_time}s." - if ! is_mock; then - if ! sleep "${sleep_time}"; then - lecho "Sleep failed" - return 1 - fi - fi - - # Try to upload. - if ! send_crash "${meta_path}"; then - lecho "Problem sending ${meta_path}, not removing." - continue - fi - - # Send was successful, now remove. - lecho "Successfully sent crash ${meta_path} and removing." - remove_report "${meta_path}" - done -} - -usage() { - cat <= Set env |var| to |val| (only some vars) -EOF - exit ${1:-1} -} - -parseargs() { - # Parse the command line arguments. - while [ $# -gt 0 ]; do - case $1 in - -e) - shift - case $1 in - FORCE_OFFICIAL=*|\ - MAX_CRASH_RATE=*|\ - MOCK_DEVELOPER_MODE=*|\ - OVERRIDE_PAUSE_SENDING=*|\ - SECONDS_SEND_SPREAD=*) - export "$1" - ;; - *) - lecho "Unknown var passed to -e: $1" - exit 1 - ;; - esac - ;; - -h) - usage 0 - ;; - *) - lecho "Unknown options: $*" - exit 1 - ;; - esac - shift - done -} - -main() { - trap cleanup EXIT INT TERM - - parseargs "$@" - - if [ -e "${PAUSE_CRASH_SENDING}" ] && \ - [ ${OVERRIDE_PAUSE_SENDING} -eq 0 ]; then - lecho "Exiting early due to ${PAUSE_CRASH_SENDING}." - exit 1 - fi - - if is_test_image; then - lecho "Exiting early due to test image." - exit 1 - fi - - # We don't perform checks on this because we have a master lock with the - # CRASH_SENDER_LOCK file. This pid file is for the system to keep track - # (like with autotests) that we're still running. - echo $$ > "${RUN_FILE}" - - for dependency in "${FIND}" "${METRICS_CLIENT}" \ - "${RESTRICTED_CERTIFICATES_PATH}"; do - if [ ! -x "${dependency}" ]; then - lecho "Fatal: Crash sending disabled: ${dependency} not found." - exit 1 - fi - done - - TMP_DIR="$(mktemp -d /tmp/crash_sender.XXXXXX)" - - # Send system-wide crashes - send_crashes "/var/spool/crash" - - # Send user-specific crashes - local d - for d in /home/chronos/crash /home/chronos/u-*/crash; do - send_crashes "${d}" - done -} - -( -if ! flock -n 9; then - lecho "Already running; quitting." - crash_done - exit 1 -fi -main "$@" -) 9>"${CRASH_SENDER_LOCK}" diff --git a/crash_reporter/crash_sender.conf b/crash_reporter/crash_sender.conf new file mode 100644 index 000000000..5611c100c --- /dev/null +++ b/crash_reporter/crash_sender.conf @@ -0,0 +1,28 @@ +# Copyright 2014 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can +# be found in the LICENSE file. + +# This file has the format: +# NAME=value + +# Product ID in crash report. +CHROMEOS_PRODUCT=ChromeOS + +# Set this to 1 to allow uploading crash reports for unofficial versions. +FORCE_OFFICIAL=0 + +# Maximum crashes to send per day. +MAX_CRASH_RATE=32 + +# Set this to 1 to pretend to have booted in developer mode. This is used by +# autotests. +MOCK_DEVELOPER_MODE=0 + +# Ignore PAUSE_CRASH_SENDING file if set. +OVERRIDE_PAUSE_SENDING=0 + +# URL to send official build crash reports to. +REPORT_UPLOAD_PROD_URL=https://clients2.google.com/cr/report + +# Maximum time to sleep between sends. +SECONDS_SEND_SPREAD=600 diff --git a/crash_reporter/crash_sender_daemon.cc b/crash_reporter/crash_sender_daemon.cc new file mode 100644 index 000000000..f477bdb59 --- /dev/null +++ b/crash_reporter/crash_sender_daemon.cc @@ -0,0 +1,101 @@ +// Copyright 2014 The Chromium OS 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 "crash-reporter/crash_sender_daemon.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +// Parameter to specify a custom config file. +const char kSwitchCustomConfigFile[] = "config"; + +const char kDefaultConfigFile[] = "/etc/crash_sender.conf"; + +const int kTerminationSignals[] = { SIGTERM, SIGINT }; +const int kNumTerminationSignals = arraysize(kTerminationSignals); +} // namespace + + +namespace crash_reporter { +CrashSenderDaemon::CrashSenderDaemon(const base::FilePath& config_file) + : config_file_(config_file) {} + +CrashSenderDaemon::~CrashSenderDaemon() { +} + +void CrashSenderDaemon::Run() { + base::RunLoop loop; + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + options.disconnected_callback = loop.QuitClosure(); + + scoped_refptr bus = new dbus::Bus(options); + CrashSenderConfiguration config = + CrashSenderService::ParseConfiguration(config_file_); + scoped_ptr impl( + new DbusCrashSenderServiceImpl(config)); + + CHECK(impl->Start(bus)) << "Failed to start crash sender service"; + crash_sender_service_ = impl.Pass(); + + for (size_t i = 0; i < kNumTerminationSignals; ++i) { + async_signal_handler_.RegisterHandler( + kTerminationSignals[i], + base::Bind(&CrashSenderDaemon::Shutdown, base::Unretained(this))); + } + async_signal_handler_.RegisterHandler( + SIGHUP, base::Bind(&CrashSenderDaemon::Restart, base::Unretained(this))); + async_signal_handler_.Init(); + + loop.Run(); + + bus->ShutdownAndBlock(); +} + +bool CrashSenderDaemon::Shutdown(const struct signalfd_siginfo& info) { + loop_.PostTask(FROM_HERE, loop_.QuitClosure()); + // Unregister the signal handler. + return true; +} + +bool CrashSenderDaemon::Restart(const struct signalfd_siginfo& info) { + CrashSenderConfiguration config = + CrashSenderService::ParseConfiguration(config_file_); + crash_sender_service_->Restart(config); + // Keep listening to the signal. + return false; +} + +} // namespace crash_reporter + +int main(int argc, char** argv) { + CommandLine::Init(argc, argv); + CommandLine* args = CommandLine::ForCurrentProcess(); + + // Some libchrome calls need this. + base::AtExitManager at_exit_manager; + + chromeos::InitLog(chromeos::kLogToSyslog | chromeos::kLogToStderr); + + base::FilePath config_file = + args->GetSwitchValuePath(kSwitchCustomConfigFile); + if (config_file.empty()) { + config_file = base::FilePath(FILE_PATH_LITERAL(kDefaultConfigFile)); + } else { + LOG(INFO) << "Using crash configuration at: " << config_file.value(); + } + + crash_reporter::CrashSenderDaemon daemon(config_file); + daemon.Run(); + return 0; +} diff --git a/crash_reporter/crash_sender_daemon.h b/crash_reporter/crash_sender_daemon.h new file mode 100644 index 000000000..462a53d27 --- /dev/null +++ b/crash_reporter/crash_sender_daemon.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium OS 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 CRASH_REPORTER_CRASH_SENDER_DAEMON_H_ +#define CRASH_REPORTER_CRASH_SENDER_DAEMON_H_ + +#include +#include +#include +#include + +#include "crash-reporter/crash_sender_service.h" + +namespace crash_reporter { + +class CrashSenderDaemon { + public: + // |config_file| specifies the config file for the crash sender. + explicit CrashSenderDaemon(const base::FilePath& config_file); + ~CrashSenderDaemon(); + + // Does all the work. Blocks until the daemon is finished. + void Run(); + + private: + base::MessageLoopForIO loop_; + base::FilePath config_file_; + scoped_ptr crash_sender_service_; + chromeos::AsynchronousSignalHandler async_signal_handler_; + + // Shutdown the sender. + bool Shutdown(const signalfd_siginfo& info); + + // Restart the service, reading the configuration file again. + bool Restart(const signalfd_siginfo& info); + + DISALLOW_COPY_AND_ASSIGN(CrashSenderDaemon); +}; + +} // namespace crash_reporter + +#endif // CRASH_REPORTER_CRASH_SENDER_DAEMON_H_ diff --git a/crash_reporter/crash_sender_service.cc b/crash_reporter/crash_sender_service.cc new file mode 100644 index 000000000..04fb23cab --- /dev/null +++ b/crash_reporter/crash_sender_service.cc @@ -0,0 +1,853 @@ +// Copyright 2014 The Chromium OS 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 "crash-reporter/crash_sender_service.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vboot/crossystem.h" + +namespace { + +// Default product ID in crash report (used if GOOGLE_CRASH_* is undefined). +const char kDefaultProduct[] = "ChromeOS"; + +// File whose existence implies crash reports may be sent, and whose +// contents includes our machine's anonymized guid. +const char kConsentIdPath[] = "/home/chronos/Consent To Send Stats"; + +// Crash sender lock in case the sender is already running. +const char kCrashSenderLockPath[] = "/var/lock/crash_sender"; + +// Crash sender lock in case the sender is already running for tests. +const char kCrashSenderLockForTestsPath[] = "/var/lock/crash_sender_test"; + +// Path to file that indicates a crash test is currently running. +const char kCrashTestInProgressPath[] = "/tmp/crash-test-in-progress"; + +// Path to hardware class description. +const char kHWClassPath[] = "/sys/devices/platform/chromeos_acpi/HWID"; + +// Path to file that indicates this is a developer image. +const char kLeaveCorePath[] = "/root/.leave_core"; + +// File whose existence causes crash sending to be delayed (for testing). +// Must be stateful to enable testing kernel crashes. +const char kPauseCrashSendingPath[] = "/var/lib/crash_sender_paused"; + +// Path to a directory of restricted certificates which includes +// a certificate for ${REPORT_UPLOAD_PROD_URL}. +const char kRestrictedCertificatesPath[] = + "/usr/share/chromeos-ca-certificates"; + +// File whose existence implies we're running and not to start again. +const char kRunFilePath[] = "/var/run/crash_sender.pid"; + +// Directory to store timestamp files indicating the uploads in the past 24 +// hours. +const char kTimestampsDirPath[] = "/var/lib/crash_sender"; + +// Chrome's crash report log file. +const char kChromeCrashLogPath[] = "/var/log/chrome/Crash Reports/uploads.log"; + +// File whose existence mocks crash sending. If empty we pretend the crash +// sending was successful, otherwise unsuccessful. +const char kMockCrashSendingPath[] = "/tmp/mock-crash-sending"; + +// Configuration keys. +const char kForceOfficial[] = "FORCE_OFFICIAL"; +const char kMaxCrashRate[] = "MAX_CRASH_RATE"; +const char kMockDeveloperMode[] = "MOCK_DEVELOPER_MODE"; +const char kOverridePauseSending[] = "OVERRIDE_PAUSE_SENDING"; +const char kReportUploadProdUrl[] = "REPORT_UPLOAD_PROD_URL"; +const char kSecondsSendSpread[] = "SECONDS_SEND_SPREAD"; + +// Owns a curl handle. +class ScopedCurl { + public: + explicit ScopedCurl(bool mock) : mock_(mock) { + if (!mock) + curl_ = curl_easy_init(); + } + + ~ScopedCurl() { + if (mock_) + return; + + if (post_) + curl_formfree(post_); + + curl_easy_cleanup(curl_); + } + + CURL* curl() const { return curl_; } + + void AddMultipartContent(std::string key, std::string value) { + LOG(INFO) << key << ": " << value; + if (mock_) + return; + curl_formadd(&post_, &last_, + CURLFORM_COPYNAME, key.c_str(), + CURLFORM_COPYCONTENTS, value.c_str(), + CURLFORM_END); + } + + void AddFile(std::string key, base::FilePath file) { + LOG(INFO) << key << ": " << file.value(); + if (mock_) + return; + curl_formadd(&post_, &last_, + CURLFORM_COPYNAME, key.c_str(), + CURLFORM_FILE, file.value().c_str(), + CURLFORM_END); + } + + CURLcode perform() { + CHECK(!mock_); + curl_easy_setopt(curl_, CURLOPT_HTTPPOST, post_); + return curl_easy_perform(curl_); + } + + private: + bool mock_; + CURL* curl_ = nullptr; + struct curl_httppost* post_ = nullptr; + struct curl_httppost* last_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ScopedCurl); +}; + +// Comparison function. +bool order_meta_files(const crash_reporter::MetaFile& f1, + const crash_reporter::MetaFile& f2) { + return std::make_tuple(f1.modification_time, f1.path.value()) < + std::make_tuple(f2.modification_time, f2.path.value()); +} + +// Return the list of directories containing crashes. +std::vector GetCrashDirectories() { + std::vector result; + base::FilePath system_wide("/var/spool/crash"); + if (base::DirectoryExists(system_wide)) + result.push_back(system_wide); + + base::FilePath main_user("/home/chronos/crash"); + if (base::DirectoryExists(main_user)) + result.push_back(main_user); + + base::FileEnumerator enumCrashDirectories( + base::FilePath("/home/chronos"), false, base::FileEnumerator::DIRECTORIES, + FILE_PATH_LITERAL("u-*")); + for (base::FilePath dir = enumCrashDirectories.Next(); !dir.empty(); + dir = enumCrashDirectories.Next()) { + base::FilePath crash_dir = dir.Append("crash"); + if (base::DirectoryExists(crash_dir)) + result.push_back(crash_dir); + } + return result; +} + +// Returns all the files in a given directory with the time of last +// modification. +std::vector> GetOrderedFiles( + const base::FilePath& dir) { + std::vector> files; + base::FileEnumerator enumFiles(dir, false, base::FileEnumerator::FILES); + for (base::FilePath file = enumFiles.Next(); !file.empty(); + file = enumFiles.Next()) { + files.push_back( + std::make_pair(enumFiles.GetInfo().GetLastModifiedTime(), file)); + } + return files; +} + +// Parse a file containing key value of the form: +// KEY=VALUE +std::map ParseKeyValueFile( + const base::FilePath& file) { + std::map result; + std::string content; + if (!base::ReadFileToString(file, &content)) { + LOG(WARNING) << "Unable to read key values from: " << file.value(); + return result; + } + base::StringPairs pairs; + base::SplitStringIntoKeyValuePairs(content, '=', '\n', &pairs); + for (base::StringPairs::const_iterator pair = pairs.begin(); + pair != pairs.end(); ++pair) { + if (!pair->first.empty() && pair->first[0] != '#') + result[pair->first] = pair->second; + } + + return result; +} + +// Returns the value associated to |key| in |map|, or |default_value| if |map| +// doesn't contain |key|. +std::string GetValue(const std::map& map, + const std::string& key, const std::string& default_value) { + std::map::const_iterator it = map.find(key); + if (it != map.end()) + return it->second; + + return default_value; +} + +// As |GetValue| for values of type |base::FilePath|. +base::FilePath GetPathValue(const std::map& map, + const std::string& key, + const base::FilePath& default_value) { + std::map::const_iterator it = map.find(key); + if (it != map.end()) + return base::FilePath(it->second); + + return default_value; +} + +// As |GetValue| for values of type |int|. +int GetIntValue(const std::map& map, + const std::string& key, int default_value) { + std::map::const_iterator it = map.find(key); + if (it != map.end()) { + int result; + if (base::StringToInt(it->second, &result)) { + return result; + } + } + return default_value; +} + +// Remove the report for the given |meta_file|. +void RemoveReport(const base::FilePath& meta_file) { + LOG(INFO) << "Removing report: " << meta_file.value(); + base::FilePath directory = meta_file.DirName(); + base::FilePath template_value = meta_file.ReplaceExtension(".*").BaseName(); + + base::FileEnumerator filesToDelete( + directory, false, base::FileEnumerator::FILES, template_value.value()); + for (base::FilePath file = filesToDelete.Next(); !file.empty(); + file = filesToDelete.Next()) { + if (!base::DeleteFile(file, false)) + LOG(WARNING) << "Unable to delete " << file.value(); + } +} + +// Returns the extenstion of the given file, stripping .gz if the file is +// compressed. +std::string GetExtension(const base::FilePath& file) { + std::string extension = file.FinalExtension(); + if (extension == ".gz") + extension = file.RemoveFinalExtension().FinalExtension(); + + if (!extension.empty()) { + DCHECK_EQ(extension[0], '.'); + extension = extension.substr(1); + } + return extension; +} + +// Returns the report kind. +std::string GetKind(const crash_reporter::MetaFile& meta_file) { + base::FilePath payload = + GetPathValue(meta_file.meta_information, "payload", base::FilePath()); + if (payload.value().empty() || !base::PathExists(payload)) { + LOG(WARNING) << "Missing payload on file: " << meta_file.path.value(); + return ""; + } + std::string kind = GetExtension(payload); + if (kind == "dmp") { + return "minidump"; + } + return kind; +} + +// Callback function for curl. It delegates to a callback passed as additional +// data. +size_t CurlWriteData(void* buffer, size_t size, size_t nmemb, void* data) { + base::Callback* callback = + static_cast*>(data); + return callback->Run(buffer, size * nmemb); +} + +size_t AppendDataToString(std::string* data, const void* buffer, size_t size) { + data->append(reinterpret_cast(buffer), size); + return size; +} + + +} // namespace + +namespace crash_reporter { + +CrashSenderService::CrashSenderService(const CrashSenderConfiguration& config) + : config_(config) { + metrics_lib_.Init(); +} + +CrashSenderService::~CrashSenderService() {} + +bool CrashSenderService::Start(ProxyResolver* proxy_resolver) { + proxy_resolver_ = proxy_resolver; + std::map lsb_release_values = + ParseKeyValueFile(base::FilePath("/etc/lsb-release")); + + board_ = lsb_release_values["CHROMEOS_RELEASE_BOARD"]; + if (board_.empty()) { + LOG(ERROR) << "Unable to retrieve board information."; + return false; + } + + channel_ = lsb_release_values["CHROMEOS_RELEASE_TRACK"]; + const char kChannelSuffix[] = "-channel"; + if (EndsWith(channel_, kChannelSuffix, true)) + channel_ = + channel_.substr(0, channel_.size() - arraysize(kChannelSuffix) + 1); + + if (channel_.empty()) { + LOG(ERROR) << "Unable to retrieve channel information."; + return false; + } + + official_ = + (lsb_release_values["CHROMEOS_RELEASE_DESCRIPTION"].find("Official") != + std::string::npos); + + std::map os_release_values = + ParseKeyValueFile(base::FilePath("/etc/os-release")); + + default_product_ = GetValue(os_release_values, "GOOGLE_CRASH_ID", ""); + if (default_product_.empty()) + default_product_ = GetValue(os_release_values, "ID", ""); + + default_version_ = GetValue(os_release_values, "GOOGLE_CRASH_VERSION_ID", ""); + if (default_version_.empty()) + default_version_ = GetValue(os_release_values, "VERSION_ID", ""); + + return ReapplyConfig(config_); +} + +void CrashSenderService::Restart(const CrashSenderConfiguration& config) { + CrashSenderConfiguration old_config = config_; + config_ = config; + if (!ReapplyConfig(config_)) { + LOG(ERROR) << "Restarting failed. Reapplying old configuration."; + config_ = old_config; + CHECK(ReapplyConfig(config_)); + } +} + +CrashSenderConfiguration CrashSenderService::ParseConfiguration( + const base::FilePath& config_file) { + CrashSenderConfiguration result; + std::map key_values = + ParseKeyValueFile(config_file); + + result.force_official = GetIntValue(key_values, kForceOfficial, false); + result.max_crash_rate = GetIntValue(key_values, kMaxCrashRate, 32); + result.mock_developer_mode = + GetIntValue(key_values, kMockDeveloperMode, false); + result.override_pause_sending = + GetIntValue(key_values, kOverridePauseSending, false); + result.report_upload_prod_url = + GetValue(key_values, kReportUploadProdUrl, + "https://clients2.google.com/cr/report"); + result.seconds_send_spread = GetIntValue(key_values, kSecondsSendSpread, 600); + + return result; +} + +bool CrashSenderService::ReapplyConfig(const CrashSenderConfiguration& config) { + bool test_run = IsMock(); + const char* lock_path = + test_run ? kCrashSenderLockForTestsPath : kCrashSenderLockPath; + lock_file_.reset( + new base::File(base::FilePath(lock_path), base::File::FLAG_OPEN_ALWAYS | + base::File::FLAG_READ | + base::File::FLAG_WRITE)); + if (lock_file_->Lock() != base::File::FILE_OK) { + LOG(ERROR) << "Already running; quitting."; + return false; + } + base::FilePath run_file(kRunFilePath); + run_file_deleter_.Reset( + base::Bind(base::IgnoreResult(&base::DeleteFile), run_file, false)); + std::string pid = base::IntToString(getpid()) + "\n"; + base::WriteFile(run_file, pid.data(), pid.length()); + + CollectAllCrashes(); + if (test_run) { + base::MessageLoop::current()->PostTask(FROM_HERE, + base::MessageLoop::QuitClosure()); + LOG(INFO) << "crash_sender done."; + } + return true; +} + +bool CrashSenderService::IsCrashTestInProgress() const { + return base::PathExists(base::FilePath(kCrashTestInProgressPath)); +} + +bool CrashSenderService::IsTestImage() const { + if (IsCrashTestInProgress()) + return false; + + return StartsWithASCII(channel_, "test", true); +} + +bool CrashSenderService::IsMock() const { + return base::PathExists(base::FilePath(kMockCrashSendingPath)); +} + +bool CrashSenderService::IsMockSuccessful() const { + std::string content; + if (base::ReadFileToString(base::FilePath(kMockCrashSendingPath), &content)) + return content.empty(); + + return false; +} + +bool CrashSenderService::IsOfficialImage() const { + return config_.force_official || official_; +} + +bool CrashSenderService::IsDeveloperMode() const { + if (config_.mock_developer_mode) + return true; + + if (IsCrashTestInProgress()) + return false; + + return VbGetSystemPropertyInt("devsw_boot"); +} + +bool CrashSenderService::IsDeveloperImage() const { + // Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage(). + if (IsCrashTestInProgress()) + return false; + + return base::PathExists(base::FilePath(kLeaveCorePath)); +} + +std::string CrashSenderService::GetHardwareClass() const { + std::string content; + if (base::ReadFileToString(base::FilePath(kHWClassPath), &content)) + return content; + + char buffer[VB_MAX_STRING_PROPERTY]; + const char* hwid = + VbGetSystemPropertyString("hwid", buffer, VB_MAX_STRING_PROPERTY); + if (hwid) + return hwid; + + return "undefined"; +} + +std::string CrashSenderService::GetConsentId() const { + std::string content; + if (base::ReadFileToString(base::FilePath(kConsentIdPath), &content)) { + content.erase(std::remove(content.begin(), content.end(), '-'), + content.end()); + return content; + } + + return "undefined"; +} + +void CrashSenderService::CollectCrashes(const base::FilePath& dir) { + std::vector> files = + GetOrderedFiles(dir); + base::Time now = base::Time::Now(); + for (const std::pair& file : files) { + if (file.second.FinalExtension() == ".meta") { + MetaFile info; + info.modification_time = file.first; + info.path = file.second; + info.meta_information = ParseKeyValueFile(info.path); + switch (FilterCrashes(info)) { + case CAN_UPLOAD: + current_crashes_.push_back(info); + break; + case DELETE: + RemoveReport(info.path); + break; + case WAIT: + // Nothing + break; + } + } else if ((now - file.first >= base::TimeDelta::FromDays(1)) && + !base::PathExists(file.second.ReplaceExtension(".meta"))) { + if (base::DeleteFile(file.second, false)) { + LOG(INFO) << "Removing old orphaned file: " << file.second.value(); + } else { + LOG(WARNING) << "Unable to delete: " << file.second.value(); + } + } + } +} + +void CrashSenderService::CollectAllCrashes() { + current_crashes_.clear(); + + std::vector crash_directories = GetCrashDirectories(); + for (const base::FilePath& path : crash_directories) { + CrashSenderService::CollectCrashes(path); + } + std::sort(current_crashes_.begin(), current_crashes_.end(), + &order_meta_files); + + if (current_crashes_.empty()) { + // If no crash is present, wait for an hour. + ScheduleNext(); + return; + } + + PrepareToSendNextCrash(); +} + +CrashSenderService::FileStatus CrashSenderService::FilterCrashes( + const MetaFile& file) { + if (!metrics_lib_.AreMetricsEnabled()) { + LOG(INFO) << "Crash reporting is disabled. Removing crash."; + return DELETE; + } + + if (!IsMock() && !IsOfficialImage()) { + LOG(INFO) << "Not an official OS version. Removing crash."; + return DELETE; + } + + if (GetValue(file.meta_information, "done", "") != "1") { + // This report is incomplete, so if it's old, just remove it + if (base::Time::Now() - file.modification_time >= + base::TimeDelta::FromDays(1)) { + LOG(INFO) << "Removing old incomplete metadata."; + return DELETE; + } else { + LOG(INFO) << "Ignoring recent incomplete metadata."; + return WAIT; + } + } + + std::string report_kind = GetKind(file); + if (report_kind != "minidump" && report_kind != "kcrash" && + report_kind != "log") { + LOG(INFO) << "Unknown report kind " << report_kind << ". Removing report."; + return DELETE; + } + + return CAN_UPLOAD; +} + +bool CrashSenderService::MustThrottle() const { + base::FilePath timestamps_dir(kTimestampsDirPath); + if (!base::CreateDirectoryAndGetError(timestamps_dir, nullptr)) { + LOG(WARNING) << "Unable to create directory: " << timestamps_dir.value(); + return true; + } + + base::Time now = base::Time::Now(); + base::FileEnumerator timestamps(timestamps_dir, false, + base::FileEnumerator::FILES); + int sends_in_24hrs = 0; + for (base::FilePath file = timestamps.Next(); !file.empty(); + file = timestamps.Next()) { + if (now - timestamps.GetInfo().GetLastModifiedTime() >= + base::TimeDelta::FromDays(1)) { + base::DeleteFile(file, false); + } else { + ++sends_in_24hrs; + } + } + LOG(INFO) << "Current send rate: " << sends_in_24hrs << "sends/24hrs"; + if (sends_in_24hrs >= config_.max_crash_rate) { + LOG(INFO) << "Cannot send more crashes: current " << sends_in_24hrs + << "send/24hrs >= max " << config_.max_crash_rate << "send/24hrs"; + return true; + } + base::FilePath tmp_file; + if (!base::CreateTemporaryFileInDir(timestamps_dir, &tmp_file)) { + LOG(WARNING) << "Unable to create a file in " << timestamps_dir.value(); + return true; + } + return false; +} + +void CrashSenderService::PrepareToSendNextCrash() { + // If we cannot send any crashes, wait one hour. + if (!CanSendNextCrash()) { + ScheduleNext(); + return; + } + + // If there is no crash to send, collect crashes and return. + if (current_crashes_.empty()) { + CollectAllCrashes(); + return; + } + + const MetaFile& file = current_crashes_.front(); + base::TimeDelta time_to_wait = + std::max(file.modification_time + base::TimeDelta::FromSeconds(30) - + base::Time::Now(), + base::TimeDelta::FromSeconds( + base::RandInt(1, config_.seconds_send_spread))); + LOG(INFO) << "Scheduled to send " << file.path.value() << " in " + << time_to_wait.InSeconds() << "s."; + + if (IsMock()) { + SendNextCrash(); + } else { + timer_.Start(FROM_HERE, time_to_wait, this, + &CrashSenderService::SendNextCrash); + } +} + +bool CrashSenderService::CanSendNextCrash() { + // Handle pause crash sending + base::FilePath pause_crash_sending(kPauseCrashSendingPath); + if (base::PathExists(pause_crash_sending) && + !config_.override_pause_sending) { + LOG(INFO) << "Not sending crashes due to " << pause_crash_sending.value(); + return false; + } + + // Handle is test image + if (IsTestImage()) { + LOG(INFO) << "Not sending crashes due to test image."; + return false; + } + + // Handle certificate path + base::FilePath restricted_certificates_path(kRestrictedCertificatesPath); + if (!base::DirectoryExists(restricted_certificates_path)) { + LOG(INFO) << "Not sending crashes due to " + << restricted_certificates_path.value() << " missing."; + return false; + } + + // Guest mode. + if (metrics_lib_.IsGuestMode()) { + LOG(INFO) + << "Guest mode has been entered. Delaying crash sending until exited."; + return false; + } + + return true; +} + +void CrashSenderService::SendNextCrash() { + // Ensure the timer will be called again if this is exited early due to + // exceptional conditions. + ScheduleNext(); + + if (!CanSendNextCrash()) + return; + + // Check uploads rate + if (MustThrottle()) { + LOG(INFO) << "Sending a report would exceed rate. Leaving for later."; + return; + } + + const MetaFile file = current_crashes_[0]; + current_crashes_.erase(current_crashes_.begin()); + + // Trying to send a crash. Preparing next crash. + base::ScopedClosureRunner send_next_crash(base::Bind( + &CrashSenderService::PrepareToSendNextCrash, base::Unretained(this))); + // Delete the report whatever the result. + base::ScopedClosureRunner report_delete(base::Bind(&RemoveReport, file.path)); + + ScopedCurl curl(IsMock()); + + LOG(INFO) << "Sending crash:"; + std::string product = GetValue(file.meta_information, "upload_var_prod", ""); + if (product.empty()) + product = default_product_; + + if (product.empty()) + product = kDefaultProduct; + + curl.AddMultipartContent("prod", product); + if (product != kDefaultProduct) + LOG(INFO) << "Sending crash report on behalf of " << product; + + LOG(INFO) << "Metadata: " << file.path.value() << " (" << GetKind(file) + << ")"; + + std::string version = GetValue(file.meta_information, "upload_var_ver", ""); + if (version.empty()) + version = default_version_; + if (version.empty()) + version = GetValue(file.meta_information, "ver", ""); + + curl.AddMultipartContent("ver", version); + curl.AddMultipartContent("board", board_); + curl.AddMultipartContent("hwclass", GetHardwareClass()); + curl.AddMultipartContent( + "exec_name", GetValue(file.meta_information, "exec_name", "undefined")); + + std::string image_type; + if (IsTestImage()) { + image_type = "test"; + } else if (IsDeveloperImage()) { + image_type = "dev"; + } else if (config_.force_official) { + image_type = "force-official"; + } else if (IsMock() && !IsMockSuccessful()) { + image_type = "mock-fail"; + } + if (!image_type.empty()) + curl.AddMultipartContent("image_type", image_type); + + if (VbGetSystemPropertyInt("cros_debug") && IsDeveloperMode()) + curl.AddMultipartContent("boot_mode", "dev"); + + std::string error_type = GetValue(file.meta_information, "error_type", ""); + if (!error_type.empty()) + curl.AddMultipartContent("error_type", error_type); + + curl.AddMultipartContent("guid", GetConsentId()); + curl.AddMultipartContent( + "write_payload_size", + GetValue(file.meta_information, "payload_size", "undefined")); + + base::FilePath payload = + GetPathValue(file.meta_information, "payload", base::FilePath()); + if (!payload.value().empty()) { + int64 file_size; + if (base::GetFileSize(payload, &file_size)) { + curl.AddMultipartContent("send_payload_size", + base::Int64ToString(file_size)); + curl.AddFile("upload_file_" + GetKind(file), payload); + } + } + + std::string signature = GetValue(file.meta_information, "sig", ""); + if (!signature.empty()) { + curl.AddMultipartContent("sig", signature); + curl.AddMultipartContent("sig2", signature); + } + + base::FilePath log = + GetPathValue(file.meta_information, "log", base::FilePath()); + if (base::PathExists(log)) + curl.AddFile("log", log); + + std::string upload_prefix = + GetValue(file.meta_information, "upload_prefix", ""); + + const char kUploadVarPrefix[] = "upload_var_"; + const char kUploadFilePrefix[] = "upload_file_"; + for (const auto& pair : file.meta_information) { + if (StartsWithASCII(pair.first, kUploadVarPrefix, true)) { + curl.AddMultipartContent( + upload_prefix + pair.first.substr(arraysize(kUploadVarPrefix) - 1), + pair.second); + } + if (StartsWithASCII(pair.first, kUploadFilePrefix, true)) { + curl.AddFile( + upload_prefix + pair.first.substr(arraysize(kUploadFilePrefix) - 1), + base::FilePath(pair.second)); + } + } + + if (IsMock()) { + if (IsMockSuccessful()) { + LOG(INFO) << "Mocking successful send"; + } else { + LOG(INFO) << "Mocking unsuccessful send"; + } + return; + } + + curl_easy_setopt(curl.curl(), CURLOPT_URL, + config_.report_upload_prod_url.c_str()); + curl_easy_setopt(curl.curl(), CURLOPT_POST, 1L); + std::vector proxies = proxy_resolver_->GetProxiesForUrl( + config_.report_upload_prod_url, base::TimeDelta::FromSeconds(5)); + if (proxies.size() && proxies[0] != "direct://") + curl_easy_setopt(curl.curl(), CURLOPT_PROXY, proxies[0].c_str()); + + // TODO(qsr) Remove + curl_easy_setopt(curl.curl(), CURLOPT_PROXY, "http://192.168.45.1:8888"); + + std::string received_data = ""; + base::Callback callback = + base::Bind(&AppendDataToString, &received_data); + curl_easy_setopt(curl.curl(), CURLOPT_WRITEFUNCTION, &CurlWriteData); + curl_easy_setopt(curl.curl(), CURLOPT_WRITEDATA, &callback); + + CURLcode success = curl.perform(); + + if (success != 0 || received_data.size() > 20) { + LOG(ERROR) << "Unable to upload crash report. Error code: " << success; + return; + } + + std::string product_name; + if (product == "Chrome_ChromeOS") { + if (IsOfficialImage()) { + product_name = "Chrome"; + } else { + product_name = "Chromium"; + } + } else { + if (IsOfficialImage()) { + product_name = "ChromeOS"; + } else { + product_name = "ChromiumOS"; + } + } + std::string log_string = base::StringPrintf( + "%" PRIu64 ",%s,%s\n", static_cast(base::Time::Now().ToTimeT()), + received_data.c_str(), product_name.c_str()); + if (base::AppendToFile(base::FilePath(kChromeCrashLogPath), log_string.data(), + log_string.size()) == -1) { + LOG(ERROR) << "Unable to update crash log."; + } +} + +void CrashSenderService::ScheduleNext() { + timer_.Start(FROM_HERE, base::TimeDelta::FromHours(1), this, + &CrashSenderService::PrepareToSendNextCrash); +} + +DbusCrashSenderServiceImpl::DbusCrashSenderServiceImpl( + const CrashSenderConfiguration& config) + : CrashSenderService(config) {} + +DbusCrashSenderServiceImpl::~DbusCrashSenderServiceImpl() {} + +bool DbusCrashSenderServiceImpl::Start(dbus::Bus* bus) { + if (!bus || !bus->Connect()) { + LOG(ERROR) << "Failed to connect to DBus"; + return false; + } + + bus_ = bus; + proxy_resolver_.reset(new DBusProxyResolver(bus_)); + proxy_resolver_->Init(); + return CrashSenderService::Start(proxy_resolver_.get()); +} + +} // namespace crash_reporter diff --git a/crash_reporter/crash_sender_service.h b/crash_reporter/crash_sender_service.h new file mode 100644 index 000000000..bc2b35c94 --- /dev/null +++ b/crash_reporter/crash_sender_service.h @@ -0,0 +1,114 @@ +// Copyright 2014 The Chromium OS 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 CRASH_REPORTER_CRASH_SENDER_SERVICE_H_ +#define CRASH_REPORTER_CRASH_SENDER_SERVICE_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "crash-reporter/proxy_resolver.h" +#include "metrics/metrics_library.h" + +namespace dbus { +class Bus; +} // namespace dbus + +namespace crash_reporter { + +// The configuration for the crash sender. See |crash_sender.conf| for details. +struct CrashSenderConfiguration { + bool force_official; + int max_crash_rate; + bool mock_developer_mode; + bool override_pause_sending; + std::string report_upload_prod_url; + int seconds_send_spread; +}; + +// The information about a crash report, which is obtained from the associated +// meta file. +struct MetaFile { + base::Time modification_time; + base::FilePath path; + std::map meta_information; +}; + +class CrashSenderService { + public: + explicit CrashSenderService(const CrashSenderConfiguration& config); + virtual ~CrashSenderService(); + + bool Start(ProxyResolver* proxy_resolver); + + void Restart(const CrashSenderConfiguration& config); + + static CrashSenderConfiguration ParseConfiguration( + const base::FilePath& config_file); + + private: + enum FileStatus { + CAN_UPLOAD, + WAIT, + DELETE, + }; + + bool ReapplyConfig(const CrashSenderConfiguration& config); + bool IsCrashTestInProgress() const; + bool IsTestImage() const; + bool IsMock() const; + bool IsMockSuccessful() const; + bool IsOfficialImage() const; + bool IsDeveloperMode() const; + bool IsDeveloperImage() const; + std::string GetHardwareClass() const; + std::string GetConsentId() const; + void CollectCrashes(const base::FilePath& dir); + void CollectAllCrashes(); + FileStatus FilterCrashes(const MetaFile& file); + bool MustThrottle() const; + void PrepareToSendNextCrash(); + bool CanSendNextCrash(); + void SendNextCrash(); + void ScheduleNext(); + + ProxyResolver* proxy_resolver_ = nullptr; + CrashSenderConfiguration config_; + MetricsLibrary metrics_lib_; + base::OneShotTimer timer_; + base::ScopedClosureRunner run_file_deleter_; + scoped_ptr lock_file_; + std::string channel_; + std::string board_; + std::string default_product_; + std::string default_version_; + bool official_ = false; + std::vector current_crashes_; + + DISALLOW_COPY_AND_ASSIGN(CrashSenderService); +}; + +class DbusCrashSenderServiceImpl : public CrashSenderService { + public: + explicit DbusCrashSenderServiceImpl(const CrashSenderConfiguration& config); + virtual ~DbusCrashSenderServiceImpl(); + + bool Start(dbus::Bus* bus); + + private: + dbus::Bus* bus_ = nullptr; + scoped_ptr proxy_resolver_; + + DISALLOW_COPY_AND_ASSIGN(DbusCrashSenderServiceImpl); +}; +} // namespace crash_reporter + +#endif // CRASH_REPORTER_CRASH_SENDER_SERVICE_H_ diff --git a/crash_reporter/init/crash-sender.conf b/crash_reporter/init/crash-sender.conf index 892186f91..11b7e4fd1 100644 --- a/crash_reporter/init/crash-sender.conf +++ b/crash_reporter/init/crash-sender.conf @@ -1,11 +1,12 @@ -# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +# Copyright 2014 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -description "Run the crash sender periodically" -author "chromium-os-dev@chromium.org" +description "Runs a daemon which send collected crash reports." +author "chromium-os-dev@chromium.org" -start on starting system-services +start on started system-services stop on stopping system-services +respawn -exec periodic_scheduler 3600 14400 crash_sender /sbin/crash_sender +exec crash_sender diff --git a/crash_reporter/libproxies.cc b/crash_reporter/libproxies.cc new file mode 100644 index 000000000..cb77295ab --- /dev/null +++ b/crash_reporter/libproxies.cc @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium OS 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 "crash-reporter/libproxies.h" + +#include + +#include +#include +#include + +namespace crash_reporter { + +const char kLibCrosProxyResolveSignalInterface[] = + "org.chromium.CrashReporterLibcrosProxyResolvedInterface"; +const char kLibCrosProxyResolveName[] = "ProxyResolved"; +const char kLibCrosServiceInterface[] = "org.chromium.LibCrosServiceInterface"; +const char kLibCrosServiceName[] = "org.chromium.LibCrosService"; +const char kLibCrosServicePath[] = "/org/chromium/LibCrosService"; +const char kLibCrosServiceResolveNetworkProxyMethodName[] = + "ResolveNetworkProxy"; +const char kNoProxy[] = "direct://"; + +std::vector ParseProxyString(const std::string& input) { + std::vector ret; + // Some of this code taken from + // https://chromium.googlesource.com/chromium/chromium/+/master/net/proxy + for (const std::string& token : chromeos::string_utils::Split(input, ';')) { + auto space = + std::find_if(token.begin(), token.end(), IsAsciiWhitespace); + std::string scheme(token.begin(), space); + base::StringToLowerASCII(&scheme); + // Chrome uses "socks" to mean socks4 and "proxy" to mean http. + if (scheme == "socks") { + scheme += "4"; + } else if (scheme == "proxy") { + scheme = "http"; + } else if (scheme != "https" && scheme != "socks4" && scheme != "socks5" && + scheme != "direct") { + continue; // Invalid proxy scheme + } + + std::string host_and_port = std::string(space, token.end()); + base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port); + if (scheme != "direct" && host_and_port.empty()) + continue; // Must supply host/port when non-direct proxy used. + + ret.push_back(scheme + "://" + host_and_port); + } + if (ret.empty() || ret.back() != kNoProxy) + ret.push_back(kNoProxy); + + return ret; +} + +} // namespace crash_reporter diff --git a/crash_reporter/libproxies.h b/crash_reporter/libproxies.h new file mode 100644 index 000000000..ed32f34d0 --- /dev/null +++ b/crash_reporter/libproxies.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium OS 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 CRASH_REPORTER_LIBPROXIES_H_ +#define CRASH_REPORTER_LIBPROXIES_H_ + +#include +#include + +namespace crash_reporter { + +extern const char kLibCrosProxyResolveSignalInterface[]; +extern const char kLibCrosProxyResolveName[]; +extern const char kLibCrosServiceInterface[]; +extern const char kLibCrosServiceName[]; +extern const char kLibCrosServicePath[]; +extern const char kLibCrosServiceResolveNetworkProxyMethodName[]; +extern const char kNoProxy[]; + +// Copied from src/update_engine/chrome_browser_proxy_resolver.cc +// Parses the browser's answer for resolved proxies. It returns a +// list of strings, each of which is a resolved proxy. +std::vector ParseProxyString(const std::string& input); + +} // namespace crash_reporter + +#endif // CRASH_REPORTER_LIBPROXIES_H_ diff --git a/crash_reporter/list_proxies.cc b/crash_reporter/list_proxies.cc deleted file mode 100644 index 282c6ae02..000000000 --- a/crash_reporter/list_proxies.cc +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) 2011 The Chromium OS 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 // for isatty() - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -const char kLibCrosProxyResolveSignalInterface[] = - "org.chromium.CrashReporterLibcrosProxyResolvedInterface"; -const char kLibCrosProxyResolveName[] = "ProxyResolved"; -const char kLibCrosServiceInterface[] = "org.chromium.LibCrosServiceInterface"; -const char kLibCrosServiceName[] = "org.chromium.LibCrosService"; -const char kLibCrosServicePath[] = "/org/chromium/LibCrosService"; -const char kLibCrosServiceResolveNetworkProxyMethodName[] = - "ResolveNetworkProxy"; -const char kNoProxy[] = "direct://"; - -namespace switches { - -const unsigned kTimeoutDefault = 5; - -const char kHelp[] = "help"; -const char kQuiet[] = "quiet"; -const char kTimeout[] = "timeout"; -const char kVerbose[] = "verbose"; -// Help message to show when the --help command line switch is specified. -const char kHelpMessage[] = - "Chromium OS Crash helper: proxy lister\n" - "\n" - "Available Switches:\n" - " --quiet Only print the proxies\n" - " --verbose Print additional messages even when not run from a TTY\n" - " --timeout=N Set timeout for browser resolving proxies (default is 5)\n" - " --help Show this help.\n"; - -} // namespace switches - -static const char *GetGErrorMessage(const GError *error) { - if (!error) - return "Unknown error."; - return error->message; -} - -// Copied from src/update_engine/chrome_browser_proxy_resolver.cc -// Parses the browser's answer for resolved proxies. It returns a -// list of strings, each of which is a resolved proxy. -std::deque ParseProxyString(const std::string &input) { - std::deque ret; - // Some of this code taken from - // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and - // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc - base::StringTokenizer entry_tok(input, ";"); - while (entry_tok.GetNext()) { - std::string token = entry_tok.token(); - base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token); - - // Start by finding the first space (if any). - std::string::iterator space; - for (space = token.begin(); space != token.end(); ++space) { - if (IsAsciiWhitespace(*space)) { - break; - } - } - - std::string scheme = std::string(token.begin(), space); - base::StringToLowerASCII(&scheme); - // Chrome uses "socks" to mean socks4 and "proxy" to mean http. - if (scheme == "socks") - scheme += "4"; - else if (scheme == "proxy") - scheme = "http"; - else if (scheme != "https" && - scheme != "socks4" && - scheme != "socks5" && - scheme != "direct") - continue; // Invalid proxy scheme - - std::string host_and_port = std::string(space, token.end()); - base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port); - if (scheme != "direct" && host_and_port.empty()) - continue; // Must supply host/port when non-direct proxy used. - ret.push_back(scheme + "://" + host_and_port); - } - if (ret.empty() || *ret.rbegin() != kNoProxy) - ret.push_back(kNoProxy); - return ret; -} - -// Define a signal-watcher class to handle the D-Bus signal sent to us when -// the browser answers our request to resolve proxies. -class BrowserProxyResolvedSignalWatcher : public chromeos::dbus::SignalWatcher { - public: - explicit BrowserProxyResolvedSignalWatcher(GMainLoop *main_loop, - std::deque *proxies) - : main_loop_(main_loop), proxies_(proxies) { } - - void OnSignal(DBusMessage *message) override { - // Get args - char *source_url = NULL; - char *proxy_list = NULL; - char *error = NULL; - DBusError arg_error; - dbus_error_init(&arg_error); - if (!dbus_message_get_args(message, &arg_error, - DBUS_TYPE_STRING, &source_url, - DBUS_TYPE_STRING, &proxy_list, - DBUS_TYPE_STRING, &error, - DBUS_TYPE_INVALID)) { - LOG(ERROR) << "Error reading D-Bus signal"; - return; - } - if (!source_url || !proxy_list) { - LOG(ERROR) << "Error getting url, proxy list from D-Bus signal"; - return; - } - - const std::deque &proxies = ParseProxyString(proxy_list); - for (std::deque::const_iterator it = proxies.begin(); - it != proxies.end(); ++it) { - LOG(INFO) << "Found proxy via browser signal: " << (*it).c_str(); - proxies_->push_back(*it); - } - - g_main_loop_quit(main_loop_); - } - - private: - GMainLoop *main_loop_; - std::deque *proxies_; -}; - -static gboolean HandleBrowserTimeout(void *data) { - GMainLoop *main_loop = reinterpret_cast(data); - LOG(ERROR) << "Timeout while waiting for browser to resolve proxy"; - g_main_loop_quit(main_loop); - return false; // only call once -} - -static bool ShowBrowserProxies(std::string url, unsigned timeout) { - GMainLoop *main_loop = g_main_loop_new(NULL, false); - - chromeos::dbus::BusConnection dbus = chromeos::dbus::GetSystemBusConnection(); - if (!dbus.HasConnection()) { - LOG(ERROR) << "Error connecting to system D-Bus"; - return false; - } - chromeos::dbus::Proxy browser_proxy(dbus, - kLibCrosServiceName, - kLibCrosServicePath, - kLibCrosServiceInterface); - if (!browser_proxy) { - LOG(ERROR) << "Error creating D-Bus proxy to interface " - << "'" << kLibCrosServiceName << "'"; - return false; - } - - // Watch for a proxy-resolved signal sent to us - std::deque proxies; - BrowserProxyResolvedSignalWatcher proxy_resolver(main_loop, &proxies); - proxy_resolver.StartMonitoring(kLibCrosProxyResolveSignalInterface, - kLibCrosProxyResolveName); - - // Request the proxies for our URL. The answer is sent to us via a - // proxy-resolved signal. - GError *gerror = NULL; - if (!dbus_g_proxy_call(browser_proxy.gproxy(), - kLibCrosServiceResolveNetworkProxyMethodName, - &gerror, - G_TYPE_STRING, url.c_str(), - G_TYPE_STRING, kLibCrosProxyResolveSignalInterface, - G_TYPE_STRING, kLibCrosProxyResolveName, - G_TYPE_INVALID, G_TYPE_INVALID)) { - LOG(ERROR) << "Error performing D-Bus proxy call " - << "'" << kLibCrosServiceResolveNetworkProxyMethodName << "'" - << ": " << GetGErrorMessage(gerror); - return false; - } - - // Setup a timeout in case the browser doesn't respond with our signal - g_timeout_add_seconds(timeout, &HandleBrowserTimeout, main_loop); - - // Loop until we either get the proxy-resolved signal, or until the - // timeout is reached. - g_main_loop_run(main_loop); - - // If there are no proxies, then we failed to get the proxy-resolved - // signal (e.g. timeout was reached). - if (proxies.empty()) - return false; - - for (std::deque::const_iterator it = proxies.begin(); - it != proxies.end(); ++it) { - printf("%s\n", (*it).c_str()); - } - return true; -} - -int main(int argc, char *argv[]) { - CommandLine::Init(argc, argv); - CommandLine* cl = CommandLine::ForCurrentProcess(); - - if (cl->HasSwitch(switches::kHelp)) { - LOG(INFO) << switches::kHelpMessage; - return 0; - } - - bool quiet = cl->HasSwitch(switches::kQuiet); - bool verbose = cl->HasSwitch(switches::kVerbose); - - unsigned timeout = switches::kTimeoutDefault; - std::string str_timeout = cl->GetSwitchValueASCII(switches::kTimeout); - if (!str_timeout.empty() && !base::StringToUint(str_timeout, &timeout)) { - LOG(ERROR) << "Invalid timeout value: " << str_timeout; - return 1; - } - - // Default to logging to syslog. - int init_flags = chromeos::kLogToSyslog; - // Log to stderr if a TTY (and "-quiet" wasn't passed), or if "-verbose" - // was passed. - - if ((!quiet && isatty(STDERR_FILENO)) || verbose) - init_flags |= chromeos::kLogToStderr; - chromeos::InitLog(init_flags); - - ::g_type_init(); - - std::string url; - CommandLine::StringVector urls = cl->GetArgs(); - if (!urls.empty()) { - url = urls[0]; - LOG(INFO) << "Resolving proxies for URL: " << url; - } else { - LOG(INFO) << "Resolving proxies without URL"; - } - - if (!ShowBrowserProxies(url, timeout)) { - LOG(ERROR) << "Error resolving proxies via the browser"; - LOG(INFO) << "Assuming direct proxy"; - printf("%s\n", kNoProxy); - } - - return 0; -} diff --git a/crash_reporter/proxy_resolver.cc b/crash_reporter/proxy_resolver.cc new file mode 100644 index 000000000..a4cc73247 --- /dev/null +++ b/crash_reporter/proxy_resolver.cc @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium OS 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 "crash-reporter/proxy_resolver.h" + +#include + +#include "crash-reporter/libproxies.h" + +namespace crash_reporter { + +DBusProxyResolver::DBusProxyResolver(dbus::Bus* bus) : bus_(bus) {} + +void DBusProxyResolver::Init() { + lib_cros_service_proxy_ = bus_->GetObjectProxy( + kLibCrosServiceName, dbus::ObjectPath(kLibCrosServicePath)); + if (!lib_cros_service_proxy_) { + LOG(WARNING) << "Unable to connect to LibCrosService."; + } +} + +std::vector DBusProxyResolver::GetProxiesForUrl( + const std::string& url, const base::TimeDelta& timeout) { + if (!lib_cros_service_proxy_) return {kNoProxy}; + + auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout( + timeout.InMilliseconds(), lib_cros_service_proxy_, + kLibCrosProxyResolveSignalInterface, + kLibCrosServiceResolveNetworkProxyMethodName, url); + if (response) { + std::string returned_message; + if (chromeos::dbus_utils::ExtractMethodCallResults(response.get(), nullptr, + &returned_message)) { + return ParseProxyString(returned_message); + } + } + return {kNoProxy}; +} + +} // namespace crash_reporter diff --git a/crash_reporter/proxy_resolver.h b/crash_reporter/proxy_resolver.h new file mode 100644 index 000000000..a6d1989ca --- /dev/null +++ b/crash_reporter/proxy_resolver.h @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium OS 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 CRASH_REPORTER_PROXY_RESOLVER_H_ +#define CRASH_REPORTER_PROXY_RESOLVER_H_ + +#include +#include + +#include +#include + +namespace base { +class TimeDelta; +} // namespace base + +namespace crash_reporter { + +class ProxyResolver { + public: + virtual ~ProxyResolver() {} + + virtual std::vector GetProxiesForUrl( + const std::string& url, const base::TimeDelta& timeout) = 0; +}; + +class DBusProxyResolver : public ProxyResolver { + public: + explicit DBusProxyResolver(dbus::Bus* bus); + ~DBusProxyResolver() override = default; + + void Init(); + + std::vector GetProxiesForUrl( + const std::string& url, const base::TimeDelta& timeout) override; + + private: + scoped_refptr bus_; + scoped_refptr lib_cros_service_proxy_; +}; + +} // namespace crash_reporter + +#endif // CRASH_REPORTER_PROXY_RESOLVER_H_