/*
* CrashHandler.cpp
*
* Copyright (C) 2019 by RStudio, PBC
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
#include
#include
#include
#include
#include
#include
#include
#ifndef _WIN32
#include
#endif
#include "config.h"
#ifndef RSTUDIO_CRASHPAD_ENABLED
namespace rstudio {
namespace core {
namespace crash_handler {
Error initialize(ProgramMode programMode)
{
return Success();
}
ConfigSource configSource()
{
return ConfigSource::Default;
}
bool isHandlerEnabled()
{
return false;
}
Error setUserHandlerEnabled(bool)
{
return Success();
}
bool hasUserBeenPromptedForPermission()
{
return true;
}
Error setUserHasBeenPromptedForPermission()
{
return Success();
}
} // namespace crash_handler
} // namespace core
} // namespace rstudio
#else
#include
#include
#include
#define kCrashHandlingEnabled "crash-handling-enabled"
#define kCrashHandlingEnabledDefault false
#define kCrashDatabasePath "crash-db-path"
#define kCrashDatabasePathDefault ""
#define kUploadUrl "upload-url"
#define kUploadUrlDefault "https://sentry.io/api/1379214/minidump/?sentry_key=4e76b8d2cffb49419fec1b431e09247c"
#define kUploadDumps "uploads-enabled"
#define kUploadDumpsDefault true
#define kUploadProxy "upload-proxy"
#define kUploadProxyDefault ""
namespace rstudio {
namespace core {
namespace crash_handler {
namespace {
boost::shared_ptr s_crashpadClient;
boost::shared_ptr s_settings;
ProgramMode s_programMode;
FilePath adminConfFile()
{
#ifndef _WIN32
return FilePath("/etc/rstudio/crash-handler.conf");
#else
return core::system::systemSettingsPath("RStudio", false).completePath("crash-handler.conf");
#endif
}
FilePath userConfFile()
{
return core::system::userSettingsPath(
core::system::userHomePath(),
"R",
true).completePath("crash-handler.conf");
}
void readOptions()
{
s_settings.reset(new Settings());
FilePath optionsFile = adminConfFile();
if (s_programMode == ProgramMode::Desktop && !optionsFile.exists())
{
// admin-level file does not exist - check for existence of user file
// this is only done for desktop mode, as only the admin file should be
// respected in server mode
optionsFile = userConfFile();
// if the user options file does not explicitly exist, we will forcefully create it
// this is to ensure that there is an actual file backing for the settings
// as multiple user clients can update it simultaneously, so we need to have it
// backed to file as opposed to just hanging around in memory so that all clients
// can properly see the state at all times
if (!optionsFile.exists())
{
Error error = optionsFile.getParent().ensureDirectory();
if (!error)
error = optionsFile.ensureFile();
if (error)
LOG_ERROR(error);
}
}
if (optionsFile.exists())
{
Error error = s_settings->initialize(optionsFile);
if (error)
LOG_ERROR(error);
}
}
base::FilePath googleFilePath(const std::string& str)
{
#ifdef _WIN32
std::string utf8Str = core::string_utils::systemToUtf8(str);
std::wstring wideStr = core::string_utils::utf8ToWide(utf8Str);
return base::FilePath(wideStr);
#else
return base::FilePath(str);
#endif
}
void logClientCreation(const base::FilePath& handlerPath,
const base::FilePath& databasePath,
const std::string& uploadUrl)
{
#ifdef _WIN32
std::wstringstream msg;
std::wstring uploadUrlStr = core::string_utils::utf8ToWide(uploadUrl);
#else
std::stringstream msg;
const std::string& uploadUrlStr = uploadUrl;
#endif
msg SetUploadsEnabled(uploadDumps);
else
return systemError(boost::system::errc::no_such_file_or_directory, ERROR_LOCATION);
logClientCreation(handlerPath, databasePath, uploadUrl);
// initialize and start crashpad client
s_crashpadClient.reset(new crashpad::CrashpadClient());
std::map annotations;
annotations["sentry[release]"] = RSTUDIO_VERSION;
std::vector args {"--no-rate-limit"};
#ifdef __linux__
// export proxy environment variable if set
// if not set, the default (system-wide setting) or any existing proxy env var is used instead
// see https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html for more info
std::string proxy = s_settings->get(kUploadProxy, kUploadProxyDefault);
if (!proxy.empty())
core::system::setenv("ALL_PROXY", proxy);
bool success = s_crashpadClient->StartHandlerAtCrash(handlerPath,
databasePath,
base::FilePath(),
uploadUrl,
annotations,
args);
#else
bool success = s_crashpadClient->StartHandler(handlerPath,
databasePath,
base::FilePath(),
uploadUrl,
annotations,
args,
true,
false);
#endif
return success ? Success() : systemError(boost::system::errc::invalid_argument, ERROR_LOCATION);
}
ConfigSource configSource()
{
FilePath settingsPath = s_settings->filePath();
if (settingsPath.isEmpty())
return ConfigSource::Default;
if (settingsPath == adminConfFile())
return ConfigSource::Admin;
else if (settingsPath == userConfFile())
return ConfigSource::User;
else
return ConfigSource::Default;
}
bool isHandlerEnabled()
{
return s_settings->getBool(kCrashHandlingEnabled, kCrashHandlingEnabledDefault);
}
Error setUserHandlerEnabled(bool handlerEnabled)
{
ConfigSource source = configSource();
if (source == ConfigSource::Admin)
{
// the admin setting is in effect so there's nothing to change
return Success();
}
if (source == ConfigSource::Default)
{
FilePath userFile = userConfFile();
Error error = userFile.ensureFile();
if (error)
return error;
Settings settings;
error = settings.initialize(userFile);
if (error)
return error;
settings.set(kCrashHandlingEnabled, handlerEnabled);
}
else
{
// we already have the user file open - simply update the settings directly
s_settings->set(kCrashHandlingEnabled, handlerEnabled);
}
return Success();
}
bool hasUserBeenPromptedForPermission()
{
if (!permissionFile().exists())
{
// if for some reason the parent directory is not writeable
// we will just treat the user as if they have been prompted
// to prevent indefinite repeated promptings
if (!file_utils::isDirectoryWriteable(permissionFile().getParent()))
return true;
else
return false;
}
else
return true;
}
Error setUserHasBeenPromptedForPermission()
{
return permissionFile().ensureFile();
}
} // namespace crash_handler
} // namespace core
} // namespace rstudio
#endif