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 <qsr@chromium.org> Reviewed-by: Ben Chan <benchan@chromium.org>
This commit is contained in:
parent
ab6cc90503
commit
8e774579a0
13 changed files with 1353 additions and 950 deletions
|
|
@ -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',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 <<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}"
|
||||
28
crash_reporter/crash_sender.conf
Normal file
28
crash_reporter/crash_sender.conf
Normal file
|
|
@ -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
|
||||
101
crash_reporter/crash_sender_daemon.cc
Normal file
101
crash_reporter/crash_sender_daemon.cc
Normal file
|
|
@ -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 <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;
|
||||
}
|
||||
43
crash_reporter/crash_sender_daemon.h
Normal file
43
crash_reporter/crash_sender_daemon.h
Normal file
|
|
@ -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 <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_
|
||||
853
crash_reporter/crash_sender_service.cc
Normal file
853
crash_reporter/crash_sender_service.cc
Normal file
|
|
@ -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 <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
|
||||
114
crash_reporter/crash_sender_service.h
Normal file
114
crash_reporter/crash_sender_service.h
Normal file
|
|
@ -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 <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_
|
||||
|
|
@ -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
|
||||
|
|
|
|||
57
crash_reporter/libproxies.cc
Normal file
57
crash_reporter/libproxies.cc
Normal file
|
|
@ -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 <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
|
||||
28
crash_reporter/libproxies.h
Normal file
28
crash_reporter/libproxies.h
Normal file
|
|
@ -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 <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_
|
||||
|
|
@ -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 <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;
|
||||
}
|
||||
41
crash_reporter/proxy_resolver.cc
Normal file
41
crash_reporter/proxy_resolver.cc
Normal file
|
|
@ -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 <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
|
||||
45
crash_reporter/proxy_resolver.h
Normal file
45
crash_reporter/proxy_resolver.h
Normal file
|
|
@ -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 <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_
|
||||
Loading…
Add table
Reference in a new issue