Revert "crash-reporter: Replace the crash_sender script by a service daemon."
This reverts commit 38c5ad3a94762945456f8e25c78f464d9221bb84.
Was think it will go to the CQ.
Change-Id: Icbe21da107375b0975f6328623fd9cdd38f71b09
Reviewed-on: https://chromium-review.googlesource.com/217740
Reviewed-by: Benjamin Lerman <qsr@chromium.org>
Tested-by: Benjamin Lerman <qsr@chromium.org>
diff --git a/crash_reporter/crash-reporter.gyp b/crash_reporter/crash-reporter.gyp
index f36d1d0..79faf97 100644
--- a/crash_reporter/crash-reporter.gyp
+++ b/crash_reporter/crash-reporter.gyp
@@ -63,51 +63,17 @@
],
},
{
- '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',
+ 'target_name': 'list_proxies',
'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': [
- 'crash_sender_daemon.cc',
- 'crash_sender_daemon.h',
- 'crash_sender_service.cc',
- 'crash_sender_service.h',
- 'proxy_resolver.cc',
- 'proxy_resolver.h',
+ 'list_proxies.cc',
],
},
{
diff --git a/crash_reporter/crash_sender b/crash_reporter/crash_sender
new file mode 100755
index 0000000..c83a12a
--- /dev/null
+++ b/crash_reporter/crash_sender
@@ -0,0 +1,685 @@
+#!/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 <<EOF
+Usage: crash_sender [options]
+
+Options:
+ -e <var>=<val> 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
deleted file mode 100644
index 5611c10..0000000
--- a/crash_reporter/crash_sender.conf
+++ /dev/null
@@ -1,28 +0,0 @@
-# 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
deleted file mode 100644
index f477bdb..0000000
--- a/crash_reporter/crash_sender_daemon.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// 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 <unistd.h>
-
-#include <base/at_exit.h>
-#include <base/bind.h>
-#include <base/command_line.h>
-#include <base/logging.h>
-#include <base/run_loop.h>
-#include <chromeos/syslog_logging.h>
-#include <crash-reporter/crash_sender_service.h>
-#include <dbus/bus.h>
-
-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<dbus::Bus> bus = new dbus::Bus(options);
- CrashSenderConfiguration config =
- CrashSenderService::ParseConfiguration(config_file_);
- scoped_ptr<DbusCrashSenderServiceImpl> 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
deleted file mode 100644
index 462a53d..0000000
--- a/crash_reporter/crash_sender_daemon.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// 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 <base/files/file_path.h>
-#include <base/memory/scoped_ptr.h>
-#include <base/message_loop/message_loop.h>
-#include <chromeos/asynchronous_signal_handler.h>
-
-#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<CrashSenderService> 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
deleted file mode 100644
index 04fb23c..0000000
--- a/crash_reporter/crash_sender_service.cc
+++ /dev/null
@@ -1,853 +0,0 @@
-// 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 <curl/curl.h>
-
-#include <algorithm>
-#include <utility>
-
-#include <base/bind.h>
-#include <base/files/file_enumerator.h>
-#include <base/files/file_path.h>
-#include <base/files/file_util.h>
-#include <base/format_macros.h>
-#include <base/logging.h>
-#include <base/message_loop/message_loop.h>
-#include <base/rand_util.h>
-#include <base/strings/string_number_conversions.h>
-#include <base/strings/stringprintf.h>
-#include <base/strings/string_split.h>
-#include <base/strings/string_util.h>
-#include <base/time/time.h>
-#include <dbus/bus.h>
-
-#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<base::FilePath> GetCrashDirectories() {
- std::vector<base::FilePath> 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<std::pair<base::Time, base::FilePath>> GetOrderedFiles(
- const base::FilePath& dir) {
- std::vector<std::pair<base::Time, base::FilePath>> 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<std::string, std::string> ParseKeyValueFile(
- const base::FilePath& file) {
- std::map<std::string, std::string> 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<std::string, std::string>& map,
- const std::string& key, const std::string& default_value) {
- std::map<std::string, std::string>::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<std::string, std::string>& map,
- const std::string& key,
- const base::FilePath& default_value) {
- std::map<std::string, std::string>::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<std::string, std::string>& map,
- const std::string& key, int default_value) {
- std::map<std::string, std::string>::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<size_t(void*, size_t)>* callback =
- static_cast<base::Callback<size_t(void*, size_t)>*>(data);
- return callback->Run(buffer, size * nmemb);
-}
-
-size_t AppendDataToString(std::string* data, const void* buffer, size_t size) {
- data->append(reinterpret_cast<const char*>(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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::pair<base::Time, base::FilePath>> files =
- GetOrderedFiles(dir);
- base::Time now = base::Time::Now();
- for (const std::pair<base::Time, base::FilePath>& 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<base::FilePath> 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<std::string> 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<size_t(const void*, size_t)> 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<uint64_t>(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
deleted file mode 100644
index bc2b35c..0000000
--- a/crash_reporter/crash_sender_service.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// 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 <map>
-#include <string>
-#include <vector>
-
-#include <base/callback_helpers.h>
-#include <base/files/file.h>
-#include <base/files/file_path.h>
-#include <base/memory/ref_counted.h>
-#include <base/timer/timer.h>
-
-#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<std::string, std::string> 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<CrashSenderService> timer_;
- base::ScopedClosureRunner run_file_deleter_;
- scoped_ptr<base::File> lock_file_;
- std::string channel_;
- std::string board_;
- std::string default_product_;
- std::string default_version_;
- bool official_ = false;
- std::vector<MetaFile> 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<DBusProxyResolver> 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 11b7e4f..892186f 100644
--- a/crash_reporter/init/crash-sender.conf
+++ b/crash_reporter/init/crash-sender.conf
@@ -1,12 +1,11 @@
-# Copyright 2014 The Chromium OS Authors. All rights reserved.
+# Copyright (c) 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 "Runs a daemon which send collected crash reports."
-author "chromium-os-dev@chromium.org"
+description "Run the crash sender periodically"
+author "chromium-os-dev@chromium.org"
-start on started system-services
+start on starting system-services
stop on stopping system-services
-respawn
-exec crash_sender
+exec periodic_scheduler 3600 14400 crash_sender /sbin/crash_sender
diff --git a/crash_reporter/libproxies.cc b/crash_reporter/libproxies.cc
deleted file mode 100644
index cb77295..0000000
--- a/crash_reporter/libproxies.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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 <algorithm>
-
-#include <base/strings/string_tokenizer.h>
-#include <base/strings/string_util.h>
-#include <chromeos/strings/string_utils.h>
-
-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<std::string> ParseProxyString(const std::string& input) {
- std::vector<std::string> 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<char>);
- 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
deleted file mode 100644
index ed32f34..0000000
--- a/crash_reporter/libproxies.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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 <string>
-#include <vector>
-
-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<std::string> 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
new file mode 100644
index 0000000..282c6ae
--- /dev/null
+++ b/crash_reporter/list_proxies.cc
@@ -0,0 +1,257 @@
+// 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 <dbus/dbus-glib-lowlevel.h>
+#include <glib.h>
+#include <unistd.h> // for isatty()
+
+#include <deque>
+#include <string>
+
+#include <base/command_line.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+#include <chromeos/dbus/dbus.h>
+#include <chromeos/syslog_logging.h>
+
+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<std::string> ParseProxyString(const std::string &input) {
+ std::deque<std::string> 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<std::string> *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<std::string> &proxies = ParseProxyString(proxy_list);
+ for (std::deque<std::string>::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<std::string> *proxies_;
+};
+
+static gboolean HandleBrowserTimeout(void *data) {
+ GMainLoop *main_loop = reinterpret_cast<GMainLoop *>(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<std::string> 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<std::string>::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
deleted file mode 100644
index a4cc732..0000000
--- a/crash_reporter/proxy_resolver.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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 <chromeos/dbus/dbus_method_invoker.h>
-
-#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<std::string> 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
deleted file mode 100644
index a6d1989..0000000
--- a/crash_reporter/proxy_resolver.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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 <string>
-#include <vector>
-
-#include <dbus/bus.h>
-#include <dbus/object_proxy.h>
-
-namespace base {
-class TimeDelta;
-} // namespace base
-
-namespace crash_reporter {
-
-class ProxyResolver {
- public:
- virtual ~ProxyResolver() {}
-
- virtual std::vector<std::string> 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<std::string> GetProxiesForUrl(
- const std::string& url, const base::TimeDelta& timeout) override;
-
- private:
- scoped_refptr<dbus::Bus> bus_;
- scoped_refptr<dbus::ObjectProxy> lib_cros_service_proxy_;
-};
-
-} // namespace crash_reporter
-
-#endif // CRASH_REPORTER_PROXY_RESOLVER_H_