michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "crashreporter.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "mozilla/NullPtr.h" michael@0: #include "common/linux/http_upload.h" michael@0: #include "crashreporter.h" michael@0: #include "crashreporter_gtk_common.h" michael@0: michael@0: #ifndef GDK_KEY_Escape michael@0: #define GDK_KEY_Escape GDK_Escape michael@0: #endif michael@0: michael@0: using std::string; michael@0: using std::vector; michael@0: michael@0: using namespace CrashReporter; michael@0: michael@0: GtkWidget* gWindow = 0; michael@0: GtkWidget* gSubmitReportCheck = 0; michael@0: GtkWidget* gIncludeURLCheck = 0; michael@0: GtkWidget* gThrobber = 0; michael@0: GtkWidget* gProgressLabel = 0; michael@0: GtkWidget* gCloseButton = 0; michael@0: GtkWidget* gRestartButton = 0; michael@0: michael@0: bool gInitialized = false; michael@0: bool gDidTrySend = false; michael@0: string gDumpFile; michael@0: StringTable gQueryParameters; michael@0: string gHttpProxy; michael@0: string gAuth; michael@0: string gCACertificateFile; michael@0: string gSendURL; michael@0: string gURLParameter; michael@0: vector gRestartArgs; michael@0: GThread* gSendThreadID; michael@0: michael@0: // From crashreporter_linux.cpp michael@0: void SaveSettings(); michael@0: void SendReport(); michael@0: void TryInitGnome(); michael@0: void UpdateSubmit(); michael@0: michael@0: static bool RestartApplication() michael@0: { michael@0: char** argv = reinterpret_cast( michael@0: malloc(sizeof(char*) * (gRestartArgs.size() + 1))); michael@0: michael@0: if (!argv) return false; michael@0: michael@0: unsigned int i; michael@0: for (i = 0; i < gRestartArgs.size(); i++) { michael@0: argv[i] = (char*)gRestartArgs[i].c_str(); michael@0: } michael@0: argv[i] = 0; michael@0: michael@0: pid_t pid = fork(); michael@0: if (pid == -1) michael@0: return false; michael@0: else if (pid == 0) { michael@0: (void)execv(argv[0], argv); michael@0: _exit(1); michael@0: } michael@0: michael@0: free(argv); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Quit the app, used as a timeout callback michael@0: static gboolean CloseApp(gpointer data) michael@0: { michael@0: gtk_main_quit(); michael@0: g_thread_join(gSendThreadID); michael@0: return FALSE; michael@0: } michael@0: michael@0: static gboolean ReportCompleted(gpointer success) michael@0: { michael@0: gtk_widget_hide(gThrobber); michael@0: string str = success ? gStrings[ST_REPORTSUBMITSUCCESS] michael@0: : gStrings[ST_SUBMITFAILED]; michael@0: gtk_label_set_text(GTK_LABEL(gProgressLabel), str.c_str()); michael@0: g_timeout_add(5000, CloseApp, 0); michael@0: return FALSE; michael@0: } michael@0: michael@0: #ifdef MOZ_ENABLE_GCONF michael@0: #define HTTP_PROXY_DIR "/system/http_proxy" michael@0: michael@0: void LoadProxyinfo() michael@0: { michael@0: class GConfClient; michael@0: typedef GConfClient * (*_gconf_default_fn)(); michael@0: typedef gboolean (*_gconf_bool_fn)(GConfClient *, const gchar *, GError **); michael@0: typedef gint (*_gconf_int_fn)(GConfClient *, const gchar *, GError **); michael@0: typedef gchar * (*_gconf_string_fn)(GConfClient *, const gchar *, GError **); michael@0: michael@0: if (getenv ("http_proxy")) michael@0: return; // libcurl can use the value from the environment michael@0: michael@0: static void* gconfLib = dlopen("libgconf-2.so.4", RTLD_LAZY); michael@0: if (!gconfLib) michael@0: return; michael@0: michael@0: _gconf_default_fn gconf_client_get_default = michael@0: (_gconf_default_fn)dlsym(gconfLib, "gconf_client_get_default"); michael@0: _gconf_bool_fn gconf_client_get_bool = michael@0: (_gconf_bool_fn)dlsym(gconfLib, "gconf_client_get_bool"); michael@0: _gconf_int_fn gconf_client_get_int = michael@0: (_gconf_int_fn)dlsym(gconfLib, "gconf_client_get_int"); michael@0: _gconf_string_fn gconf_client_get_string = michael@0: (_gconf_string_fn)dlsym(gconfLib, "gconf_client_get_string"); michael@0: michael@0: if(!(gconf_client_get_default && michael@0: gconf_client_get_bool && michael@0: gconf_client_get_int && michael@0: gconf_client_get_string)) { michael@0: dlclose(gconfLib); michael@0: return; michael@0: } michael@0: michael@0: GConfClient *conf = gconf_client_get_default(); michael@0: michael@0: if (gconf_client_get_bool(conf, HTTP_PROXY_DIR "/use_http_proxy", nullptr)) { michael@0: gint port; michael@0: gchar *host = nullptr, *httpproxy = nullptr; michael@0: michael@0: host = gconf_client_get_string(conf, HTTP_PROXY_DIR "/host", nullptr); michael@0: port = gconf_client_get_int(conf, HTTP_PROXY_DIR "/port", nullptr); michael@0: michael@0: if (port && host && *host != '\0') { michael@0: httpproxy = g_strdup_printf("http://%s:%d/", host, port); michael@0: gHttpProxy = httpproxy; michael@0: } michael@0: michael@0: g_free(host); michael@0: g_free(httpproxy); michael@0: michael@0: if (gconf_client_get_bool(conf, HTTP_PROXY_DIR "/use_authentication", michael@0: nullptr)) { michael@0: gchar *user, *password, *auth = nullptr; michael@0: michael@0: user = gconf_client_get_string(conf, michael@0: HTTP_PROXY_DIR "/authentication_user", michael@0: nullptr); michael@0: password = gconf_client_get_string(conf, michael@0: HTTP_PROXY_DIR michael@0: "/authentication_password", michael@0: nullptr); michael@0: michael@0: if (user && password) { michael@0: auth = g_strdup_printf("%s:%s", user, password); michael@0: gAuth = auth; michael@0: } michael@0: michael@0: g_free(user); michael@0: g_free(password); michael@0: g_free(auth); michael@0: } michael@0: } michael@0: michael@0: g_object_unref(conf); michael@0: michael@0: // Don't dlclose gconfLib as libORBit-2 uses atexit(). michael@0: } michael@0: #endif michael@0: michael@0: gpointer SendThread(gpointer args) michael@0: { michael@0: string response, error; michael@0: long response_code; michael@0: michael@0: bool success = google_breakpad::HTTPUpload::SendRequest michael@0: (gSendURL, michael@0: gQueryParameters, michael@0: gDumpFile, michael@0: "upload_file_minidump", michael@0: gHttpProxy, gAuth, michael@0: gCACertificateFile, michael@0: &response, michael@0: &response_code, michael@0: &error); michael@0: if (success) { michael@0: LogMessage("Crash report submitted successfully"); michael@0: } michael@0: else { michael@0: LogMessage("Crash report submission failed: " + error); michael@0: } michael@0: michael@0: SendCompleted(success, response); michael@0: // Apparently glib is threadsafe, and will schedule this michael@0: // on the main thread, see: michael@0: // http://library.gnome.org/devel/gtk-faq/stable/x499.html michael@0: g_idle_add(ReportCompleted, (gpointer)success); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: gboolean WindowDeleted(GtkWidget* window, michael@0: GdkEvent* event, michael@0: gpointer userData) michael@0: { michael@0: SaveSettings(); michael@0: gtk_main_quit(); michael@0: return TRUE; michael@0: } michael@0: michael@0: gboolean check_escape(GtkWidget* window, michael@0: GdkEventKey* event, michael@0: gpointer userData) michael@0: { michael@0: if (event->keyval == GDK_KEY_Escape) { michael@0: gtk_main_quit(); michael@0: return TRUE; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: static void MaybeSubmitReport() michael@0: { michael@0: if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))) { michael@0: gDidTrySend = true; michael@0: SendReport(); michael@0: } else { michael@0: gtk_main_quit(); michael@0: } michael@0: } michael@0: michael@0: void CloseClicked(GtkButton* button, michael@0: gpointer userData) michael@0: { michael@0: SaveSettings(); michael@0: MaybeSubmitReport(); michael@0: } michael@0: michael@0: void RestartClicked(GtkButton* button, michael@0: gpointer userData) michael@0: { michael@0: SaveSettings(); michael@0: RestartApplication(); michael@0: MaybeSubmitReport(); michael@0: } michael@0: michael@0: static void UpdateURL() michael@0: { michael@0: if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck))) { michael@0: gQueryParameters["URL"] = gURLParameter; michael@0: } else { michael@0: gQueryParameters.erase("URL"); michael@0: } michael@0: } michael@0: michael@0: void SubmitReportChecked(GtkButton* sender, gpointer userData) michael@0: { michael@0: UpdateSubmit(); michael@0: } michael@0: michael@0: void IncludeURLClicked(GtkButton* sender, gpointer userData) michael@0: { michael@0: UpdateURL(); michael@0: } michael@0: michael@0: /* === Crashreporter UI Functions === */ michael@0: michael@0: bool UIInit() michael@0: { michael@0: // breakpad probably left us with blocked signals, unblock them here michael@0: sigset_t signals, old; michael@0: sigfillset(&signals); michael@0: sigprocmask(SIG_UNBLOCK, &signals, &old); michael@0: michael@0: // tell glib we're going to use threads michael@0: g_thread_init(nullptr); michael@0: michael@0: if (gtk_init_check(&gArgc, &gArgv)) { michael@0: gInitialized = true; michael@0: michael@0: if (gStrings.find("isRTL") != gStrings.end() && michael@0: gStrings["isRTL"] == "yes") michael@0: gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void UIShowDefaultUI() michael@0: { michael@0: GtkWidget* errorDialog = michael@0: gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, michael@0: GTK_MESSAGE_ERROR, michael@0: GTK_BUTTONS_CLOSE, michael@0: "%s", gStrings[ST_CRASHREPORTERDEFAULT].c_str()); michael@0: michael@0: gtk_window_set_title(GTK_WINDOW(errorDialog), michael@0: gStrings[ST_CRASHREPORTERTITLE].c_str()); michael@0: gtk_dialog_run(GTK_DIALOG(errorDialog)); michael@0: } michael@0: michael@0: void UIError_impl(const string& message) michael@0: { michael@0: if (!gInitialized) { michael@0: // Didn't initialize, this is the best we can do michael@0: printf("Error: %s\n", message.c_str()); michael@0: return; michael@0: } michael@0: michael@0: GtkWidget* errorDialog = michael@0: gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, michael@0: GTK_MESSAGE_ERROR, michael@0: GTK_BUTTONS_CLOSE, michael@0: "%s", message.c_str()); michael@0: michael@0: gtk_window_set_title(GTK_WINDOW(errorDialog), michael@0: gStrings[ST_CRASHREPORTERTITLE].c_str()); michael@0: gtk_dialog_run(GTK_DIALOG(errorDialog)); michael@0: } michael@0: michael@0: bool UIGetIniPath(string& path) michael@0: { michael@0: path = gArgv[0]; michael@0: path.append(".ini"); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Settings are stored in ~/.vendor/product, or michael@0: * ~/.product if vendor is empty. michael@0: */ michael@0: bool UIGetSettingsPath(const string& vendor, michael@0: const string& product, michael@0: string& settingsPath) michael@0: { michael@0: char* home = getenv("HOME"); michael@0: michael@0: if (!home) michael@0: return false; michael@0: michael@0: settingsPath = home; michael@0: settingsPath += "/."; michael@0: if (!vendor.empty()) { michael@0: string lc_vendor; michael@0: std::transform(vendor.begin(), vendor.end(), back_inserter(lc_vendor), michael@0: (int(*)(int)) std::tolower); michael@0: settingsPath += lc_vendor + "/"; michael@0: } michael@0: string lc_product; michael@0: std::transform(product.begin(), product.end(), back_inserter(lc_product), michael@0: (int(*)(int)) std::tolower); michael@0: settingsPath += lc_product + "/Crash Reports"; michael@0: return true; michael@0: } michael@0: michael@0: bool UIEnsurePathExists(const string& path) michael@0: { michael@0: int ret = mkdir(path.c_str(), S_IRWXU); michael@0: int e = errno; michael@0: if (ret == -1 && e != EEXIST) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool UIFileExists(const string& path) michael@0: { michael@0: struct stat sb; michael@0: int ret = stat(path.c_str(), &sb); michael@0: if (ret == -1 || !(sb.st_mode & S_IFREG)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool UIMoveFile(const string& file, const string& newfile) michael@0: { michael@0: if (!rename(file.c_str(), newfile.c_str())) michael@0: return true; michael@0: if (errno != EXDEV) michael@0: return false; michael@0: michael@0: // use system /bin/mv instead, time to fork michael@0: pid_t pID = vfork(); michael@0: if (pID < 0) { michael@0: // Failed to fork michael@0: return false; michael@0: } michael@0: if (pID == 0) { michael@0: char* const args[4] = { michael@0: "mv", michael@0: strdup(file.c_str()), michael@0: strdup(newfile.c_str()), michael@0: 0 michael@0: }; michael@0: if (args[1] && args[2]) michael@0: execve("/bin/mv", args, 0); michael@0: if (args[1]) michael@0: free(args[1]); michael@0: if (args[2]) michael@0: free(args[2]); michael@0: exit(-1); michael@0: } michael@0: int status; michael@0: waitpid(pID, &status, 0); michael@0: return UIFileExists(newfile); michael@0: } michael@0: michael@0: bool UIDeleteFile(const string& file) michael@0: { michael@0: return (unlink(file.c_str()) != -1); michael@0: } michael@0: michael@0: std::ifstream* UIOpenRead(const string& filename) michael@0: { michael@0: return new std::ifstream(filename.c_str(), std::ios::in); michael@0: } michael@0: michael@0: std::ofstream* UIOpenWrite(const string& filename, bool append) // append=false michael@0: { michael@0: return new std::ofstream(filename.c_str(), michael@0: append ? std::ios::out | std::ios::app michael@0: : std::ios::out); michael@0: }