3
* Copyright © 2011-2013 Canonical Ltd.
4
* Author: Evan Dandrea <evan.dandrea@canonical.com>
6
* This program is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; version 3 of the License.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
27
#include <glib/gstdio.h>
30
#include <curl/curl.h>
32
#include <sys/types.h>
38
#include <sys/capability.h>
39
#include <sys/prctl.h>
40
#include <sys/mount.h>
42
#include <sys/resource.h>
44
#include "bson/bson.h"
47
#include "connectivity.h"
49
#include "identifier.h"
53
/* The length of time to wait before processing outstanding crashes, in seconds
55
#define PROCESS_OUTSTANDING_TIMEOUT 7200
57
/* The maximum allowed size of a report file, if not overridden
59
#define MAX_REPORT_FILESIZE 1000000000
61
/* If true, we have an active Internet connection. True by default in case we
62
* can't bring up GNetworkMonitor */
63
static gboolean online_state = TRUE;
65
/* The URL of the crash database. */
66
static char* crash_db_url = NULL;
68
/* Username we will run under */
69
static const char* username = "whoopsie";
71
/* The database identifier. Either:
72
* - The android serial number, taken from sys, and SHA-512 hashed
73
* - The system UUID, taken from the DMI tables and SHA-512 hashed
74
* - The MAC address of the first non-loopback device, SHA-512 hashed */
75
static char* whoopsie_identifier = NULL;
77
/* Whether or not to verify the ssl peer when submitting crashes. Either:
78
* - 0 verification will NOT occur
79
* - 1 verification will occur */
80
static long verifypeer = 1;
82
/* The URL for sending the initial crash report */
83
static char* crash_db_submit_url = NULL;
85
/* OOPS ID of the last successfully-uploaded crash file */
86
char* last_uploaded_oopsid = NULL;
88
/* The file path and descriptor for our instance lock */
89
static const char* lock_path = "/var/lock/whoopsie/lock";
90
static int lock_fd = 0;
92
/* The report directory */
93
static const char* report_dir = "/var/crash";
99
static int assume_online = 0;
102
* Tells whether whoopsie will exit right after processing existing files.
104
static int use_polling = 1;
106
static GOptionEntry option_entries[] = {
107
{ "foreground", 'f', 0, G_OPTION_ARG_NONE, &foreground, "Run in the foreground", NULL },
108
{ "assume-online", 'a', 0, G_OPTION_ARG_NONE, &assume_online, "Always assume there is a route to $CRASH_DB_URL.", NULL },
109
{ "no-polling", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &use_polling, "Process existing files and exit. Implies --assume-online.", NULL },
114
/* Fields that can be larger than 1KB if present, always send them */
115
static const char* acceptable_fields[] = {
123
"PackageArchitecture",
126
"StacktraceAddressSignature",
128
"DuplicateSignature",
133
"NonfreeKernelModules",
140
"ExecutableTimestamp",
147
"ProcCpuinfoMinimal",
151
/* used to repair the StacktraceAddressSignature if it is corrupt */
156
/* add_kernel_crash_info */
158
/* We use package-from-proposed tag to determine if a problem is occuring
159
* in the release-proposed pocket. */
161
/* We need the OopsText field to be able to generate a crash signature from
162
* KernelOops problems */
164
/* We use the UpgradeStatus field to determine the time between upgrading
165
* and the initial error report */
167
/* We use the InstallationDate and InstallationMedia fields to determine
168
* the time between installing and the initial error report */
171
/* Dumps for debugging iwlwifi firmware crashes */
173
/* System Image information from imaged systems */
175
/* We always want to know why apport thought it shouldn't be reported. */
176
"UnreportableReason",
177
/* Package Install Failure Information */
179
"DpkgHistoryLog.txt",
180
"DpkgTerminalLog.txt",
192
/* desktop app debugging info */
198
/* Fields that we don't ever need, always */
199
static const char* unacceptable_fields[] = {
200
/* We do not need these since we retrace with ddebs on errors */
204
/* We'll have our own count in the database. */
207
/* MarkForUpload is redundant since the crash was uploaded. */
212
/* Redundant with the addition of pa-info LP: #1893899 */
219
is_in_field_list (const char* field, const char * field_list[])
223
g_return_val_if_fail (field, FALSE);
227
if (strcmp (*p, field) == 0)
235
append_key_value (gpointer key, gpointer value, gpointer bson_string)
237
/* Takes a key and its value from a #GHashTable and adds it to a BSON string
238
* as key and its string value. Return %FALSE on error. */
240
bson* str = (bson*) bson_string;
241
char* k = (char*) key;
242
char* v = (char*) value;
244
/* We don't send the core dump in the first upload, as the server might not
246
if (!strcmp ("CoreDump", k))
248
if (!strcmp ("VmCore", k))
251
return bson_append_string (str, k, v) != BSON_ERROR;
255
server_response (char* ptr, size_t size, size_t nmemb, void* s)
257
GString *resp = (GString *) s;
259
g_warning("CURLOPT_WRITEFUNCTION callback received size !=1, against spec!\n");
262
g_string_append_len (resp, ptr, nmemb);
267
split_string (char* head, char** tail)
269
g_return_if_fail (head);
270
g_return_if_fail (tail);
272
*tail = strchr (head, ' ');
281
bsonify (GHashTable* report, bson* b, const char** bson_message,
282
uint32_t* bson_message_len)
284
/* Attempt to convert a #GHashTable of the report into a BSON string.
285
* On error return %FALSE. */
290
*bson_message = NULL;
291
*bson_message_len = 0;
293
g_return_val_if_fail (report, FALSE);
299
g_hash_table_iter_init (&iter, report);
300
while (g_hash_table_iter_next (&iter, &key, &value)) {
301
if (!append_key_value (key, value, b))
304
if (bson_finish (b) == BSON_ERROR)
307
*bson_message = bson_data (b);
308
*bson_message_len = bson_size (b);
309
if (*bson_message_len > 0 && *bson_message)
316
upload_report (const char* message_data, uint32_t message_len, GString *s)
319
CURLcode result_code = 0;
320
long response_code = 0;
321
struct curl_slist* list = NULL;
323
g_return_val_if_fail (message_data, -1);
325
/* TODO use curl_share for DNS caching. */
326
/* Repeated calls to curl_global_init will have no effect. */
327
if (curl_global_init (CURL_GLOBAL_SSL)) {
328
log_msg ("Unable to initialize curl.\n\n");
332
if ((curl = curl_easy_init ()) == NULL) {
333
log_msg ("Couldn't init curl.\n");
336
curl_easy_setopt (curl, CURLOPT_POST, 1);
337
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1);
338
list = curl_slist_append (list, "Content-Type: application/octet-stream");
339
list = curl_slist_append (list, "X-Whoopsie-Version: " VERSION);
340
curl_easy_setopt (curl, CURLOPT_URL, crash_db_submit_url);
341
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, list);
342
curl_easy_setopt (curl, CURLOPT_POSTFIELDSIZE, message_len);
343
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, (void*)message_data);
344
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, server_response);
345
curl_easy_setopt (curl, CURLOPT_WRITEDATA, s);
346
curl_easy_setopt (curl, CURLOPT_VERBOSE, 0L);
347
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, verifypeer);
349
result_code = curl_easy_perform (curl);
350
curl_slist_free_all(list);
351
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &response_code);
353
log_msg ("Sent; server replied with: %s\n",
354
curl_easy_strerror (result_code));
355
log_msg ("Response code: %ld\n", response_code);
356
curl_easy_cleanup (curl);
358
if (result_code != CURLE_OK)
361
return response_code;
365
get_report_max_size (void)
367
const char* value = NULL;
370
value = g_getenv ("REPORT_MAX_SIZE");
372
return MAX_REPORT_FILESIZE;
374
max_size = atol(value);
376
return MAX_REPORT_FILESIZE;
382
parse_report (const char* report_path, gboolean full_report, GError** error)
384
/* We'll eventually modify the contents of the report, rather than sending
385
* it as-is, to make it more amenable to what the server has to stick in
386
* the database, and thus creating less work server-side.
389
GMappedFile* fp = NULL;
390
GHashTable* hash_table = NULL;
391
gchar* contents = NULL;
393
/* Our position in the file. */
395
/* The end or length of the token. */
396
gchar* token_p = NULL;
399
char* old_value = NULL;
400
gchar* value_p = NULL;
410
g_return_val_if_fail (report_path, NULL);
412
max_size = get_report_max_size();
414
fd = open(report_path, O_RDONLY | O_NOFOLLOW);
415
res = fstat(fd, &st);
417
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
418
"%s could not be opened.", report_path);
424
if(!S_ISREG(st.st_mode)) {
425
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
426
"%s is not a regular file.", report_path);
431
/* Limit the size of a report */
432
if(st.st_size > max_size) {
433
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
434
"%s is too big to be processed.", report_path);
439
/* TODO handle the file being modified underneath us. */
440
fp = g_mapped_file_new_from_fd (fd, FALSE, &err);
442
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
443
"Unable to map report: %s", err->message);
448
contents = g_mapped_file_get_contents (fp);
449
file_len = g_mapped_file_get_length (fp);
451
/* Check report size again to make sure */
452
if(file_len > max_size) {
453
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
454
"%s is too big to be processed.", report_path);
458
end = contents + file_len;
459
hash_table = g_hash_table_new_full (g_str_hash, g_str_equal,
464
/* We're either at the beginning of the file or the start of a line,
465
* otherwise this report is corrupted. */
466
if (!(p == contents || *(p-1) == '\n')) {
467
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"),
468
0, "Malformed report.");
474
g_quark_from_static_string ("whoopsie-quark"), 0,
475
"Report may not start with a value.");
478
/* Skip the space. */
481
while (token_p < end && *token_p != '\n')
484
/* The length of this value string */
485
value_length = token_p - p;
487
/* Space for the leading newline too. */
488
value_pos = value_p - value;
489
if (INT_MAX - (1 + value_length + 1) < value_pos) {
491
g_quark_from_static_string ("whoopsie-quark"),
492
0, "Report value too long.");
495
g_hash_table_steal (hash_table, key);
496
value = g_realloc (value, value_pos + 1 + value_length + 1);
497
value_p = value + value_pos;
501
/* Make sure we properly free the old empty string value */
502
old_value = g_hash_table_lookup (hash_table, key);
505
g_hash_table_steal (hash_table, key);
506
value = g_realloc (value, value_length + 1);
509
memcpy (value_p, p, value_length);
510
value_p[value_length] = '\0';
511
for (char *c = value_p; c < value_p + value_length; c++)
512
/* If c is a control character but not TAB. */
513
if (*c != '\t' && *c >= '\0' && *c < ' ')
515
value_p += value_length;
516
if (g_hash_table_contains (hash_table, key) == TRUE) {
518
g_quark_from_static_string ("whoopsie-quark"),
519
0, "Report key must not be a duplicate.");
522
g_hash_table_insert (hash_table, key, value ? value : g_strdup(""));
525
/* Reset the value pointer. */
529
while (token_p < end) {
530
if (*token_p != ':') {
531
if (*token_p == '\n') {
532
/* No colon character found on this line */
534
g_quark_from_static_string ("whoopsie-quark"),
535
0, "Report key must have a value.");
539
} else if ((*(token_p + 1) == '\n' &&
540
*(token_p + 2) != ' ')) {
541
/* The next line doesn't start with a value */
543
g_quark_from_static_string ("whoopsie-quark"),
544
0, "Report key must have a value.");
550
key = g_malloc ((token_p - p) + 1);
551
memcpy (key, p, (token_p - p));
552
key[(token_p - p)] = '\0';
554
/* Replace any embedded NUL bytes. */
555
for (char *c = key; c < key + (token_p - p); c++)
556
if (*c >= '\0' && *c < ' ')
559
/* Eat the semicolon. */
562
/* Skip any leading spaces. */
563
while (token_p < end && *token_p == ' ')
566
/* Start of the value. */
569
while (token_p < end && *token_p != '\n')
571
if ((token_p - p) == 0) {
572
/* Empty value. The key likely has a child. */
575
if (!strncmp ("base64", p, 6)) {
576
/* Just a marker that the following lines are base64
577
* encoded. Don't include it in the value. */
581
value = g_malloc ((token_p - p) + 1);
582
memcpy (value, p, (token_p - p));
583
value[(token_p - p)] = '\0';
584
for (char *c = value; c < value + (token_p - p); c++)
585
if (*c >= '\0' && *c < ' ')
587
value_p = value + (token_p - p);
592
if (g_hash_table_contains (hash_table, key) == TRUE) {
594
g_quark_from_static_string ("whoopsie-quark"),
595
0, "Report key must not be a duplicate.");
598
g_hash_table_insert (hash_table, key, value ? value : g_strdup(""));
601
g_mapped_file_unref (fp);
604
/* Remove entries that we don't want to send */
608
g_hash_table_iter_init(&iter, hash_table);
610
/* We want everything that is in our white list or less than
611
1 KB so that we don't end up DoSing our database. */
612
while (g_hash_table_iter_next(&iter, &key, &value)) {
613
if (!is_in_field_list((const char *)key, unacceptable_fields)) {
614
if (is_in_field_list((const char *)key, acceptable_fields))
616
if (strlen((const char *)value) < 1024)
620
g_hash_table_iter_remove(&iter);
628
g_hash_table_destroy (hash_table);
630
g_mapped_file_unref (fp);
636
upload_core (const char* uuid, const char* arch, const char* core_data) {
639
CURLcode result_code = 0;
640
long response_code = 0;
641
struct curl_slist* list = NULL;
642
char* crash_db_core_url = NULL;
646
g_return_val_if_fail (uuid, FALSE);
647
g_return_val_if_fail (arch, FALSE);
648
g_return_val_if_fail (core_data, FALSE);
650
crash_db_core_url = g_strdup_printf ("%s/%s/submit-core/%s/%s",
651
crash_db_url, uuid, arch,
652
whoopsie_identifier);
654
/* TODO use CURLOPT_READFUNCTION to transparently compress data with
656
if ((curl = curl_easy_init ()) == NULL) {
657
log_msg ("Couldn't init curl.\n");
658
g_free (crash_db_core_url);
661
s = g_string_new (NULL);
662
curl_easy_setopt (curl, CURLOPT_POST, 1);
663
curl_easy_setopt (curl, CURLOPT_NOPROGRESS, 1);
664
list = curl_slist_append (list, "Content-Type: application/octet-stream");
665
list = curl_slist_append (list, "X-Whoopsie-Version: " VERSION);
666
curl_easy_setopt (curl, CURLOPT_URL, crash_db_core_url);
667
curl_easy_setopt (curl, CURLOPT_HTTPHEADER, list);
668
curl_easy_setopt (curl, CURLOPT_POSTFIELDS, (void*)core_data);
669
curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, server_response);
670
curl_easy_setopt (curl, CURLOPT_WRITEDATA, s);
671
curl_easy_setopt (curl, CURLOPT_VERBOSE, 0L);
672
curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, verifypeer);
674
result_code = curl_easy_perform (curl);
675
curl_slist_free_all(list);
677
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &response_code);
678
/* this actually what curl replied with */
679
log_msg ("Sent; server replied with: %s\n",
680
curl_easy_strerror (result_code));
681
log_msg ("Response code: %ld\n", response_code);
682
curl_easy_cleanup (curl);
683
g_free (crash_db_core_url);
684
g_string_free (s, TRUE);
686
return result_code == CURLE_OK && response_code == 200;
689
void unset_last_uploaded_oopsid ()
691
if (last_uploaded_oopsid) {
692
g_free (last_uploaded_oopsid);
693
last_uploaded_oopsid = NULL;
697
void set_last_uploaded_oopsid (char* response)
699
unset_last_uploaded_oopsid();
700
last_uploaded_oopsid = g_strdup_printf ("%.36s", response);
704
handle_response (GHashTable* report, char* response_data)
706
char* command = NULL;
710
g_return_if_fail (report);
712
/* Command could be CORE, which requests the core dump, BUG ######, if in a
713
* development release, which points to the bug report, or UPDATE, if this
714
* is fixed in an update. */
715
split_string (response_data, &command);
717
if (strcmp (command, "CORE") == 0) {
718
log_msg ("Reported OOPS ID %.36s\n", response_data);
719
set_last_uploaded_oopsid(response_data);
720
core = g_hash_table_lookup (report, "CoreDump");
721
arch = g_hash_table_lookup (report, "Architecture");
723
if (!upload_core (response_data, arch, core))
724
/* We do not retry the upload. Once is a big enough hit to
725
* their Internet connection, and we can always count on
726
* the next person in line to send it. */
727
log_msg ("Upload of the core dump failed.\n");
728
} else if (strcmp (command, "OOPSID") == 0) {
729
log_msg ("Reported OOPS ID %.36s\n", response_data);
730
set_last_uploaded_oopsid(response_data);
732
log_msg ("Asked for a core dump that we don't have.\n");
733
} else if (strcmp (command, "OOPSID") == 0) {
734
log_msg ("Reported OOPS ID %.36s\n", response_data);
735
set_last_uploaded_oopsid(response_data);
737
log_msg ("Got command: %s\n", command);
742
parse_and_upload_report (const char* crash_file)
744
GHashTable* report = NULL;
745
gboolean success = FALSE;
746
uint32_t message_len = 0;
747
const char* message_data = NULL;
748
GError* error = NULL;
752
log_msg ("Parsing %s.\n", crash_file);
753
report = parse_report (crash_file, FALSE, &error);
756
log_msg ("Unable to parse report (%s): %s\n", crash_file,
758
g_error_free (error);
760
log_msg ("Unable to parse report (%s)\n", crash_file);
762
/* Do not keep trying to parse and upload this */
766
if (!bsonify (report, b, &message_data, &message_len)) {
767
log_msg ("Unable to bsonify report (%s)\n", crash_file);
770
/* Do not keep trying to parse and upload this */
773
log_msg ("Uploading %s.\n", crash_file);
774
GString* s = g_string_new (NULL);
775
response = upload_report (message_data, message_len, s);
779
/* If the response code is 400, the server did not like what we sent it.
780
* Sending the same thing again is not likely to change that */
781
/* TODO check that there aren't 400 responses that we care about seeing
782
* again, such as a transient database failure. */
783
if (response == 200 || response == 400)
788
if (response > 200) {
789
log_msg ("Server replied with:\n");
790
log_msg ("%s\n", s->str);
793
if (response == 200 && s->len > 0)
794
handle_response (report, s->str);
796
g_string_free (s, TRUE);
799
g_hash_table_destroy (report);
805
process_existing_files_if_online (const char* report_dir)
808
process_existing_files (report_dir);
810
return G_SOURCE_CONTINUE;
814
process_existing_files (const char* report_dir)
817
const gchar* file = NULL;
818
const gchar* ext = NULL;
819
char* upload_file = NULL;
820
char* crash_file = NULL;
822
dir = g_dir_open (report_dir, 0, NULL);
823
while ((file = g_dir_read_name (dir)) != NULL) {
825
upload_file = g_build_filename (report_dir, file, NULL);
829
ext = strrchr (upload_file, '.');
830
if (ext && strcmp(++ext, "upload") != 0) {
831
g_free (upload_file);
835
crash_file = change_file_extension (upload_file, ".crash");
837
g_free (upload_file);
841
if (already_handled_report (crash_file)) {
842
g_free (upload_file);
845
} else if (parse_and_upload_report (crash_file)) {
846
if (!mark_handled (crash_file, last_uploaded_oopsid)) {
847
log_msg ("Unable to mark report as seen (%s) removing it.\n", crash_file);
848
g_unlink (crash_file);
852
g_free (upload_file);
858
void daemonize (void)
862
struct rlimit rl = {0};
864
if (getrlimit (RLIMIT_NOFILE, &rl) < 0) {
865
log_msg ("Could not get resource limits.\n");
879
if ((chdir ("/")) < 0)
882
for (i = 0; i < rl.rlim_max && i < 1024; i++) {
886
if ((open ("/dev/null", O_RDWR) != 0) ||
889
log_msg ("Could not redirect file descriptors to /dev/null.\n");
895
exit_if_already_running (void)
899
if (g_getenv ("APPORT_REPORT_DIR")) {
900
/* keep lock file in custom report directory */
901
lock_path = g_build_filename (report_dir, "whoopsie_lock", NULL);
903
/* use system directory */
904
if (mkdir ("/var/lock/whoopsie", 0755) < 0) {
905
if (errno != EEXIST) {
906
log_msg ("Could not create lock directory.\n");
911
log_msg ("Using lock path: %s\n", lock_path);
913
lock_fd = open (lock_path, O_CREAT | O_RDWR, 0600);
914
rc = flock (lock_fd, LOCK_EX | LOCK_NB);
916
if (EWOULDBLOCK == errno) {
917
log_msg ("Another instance is already running.\n");
920
log_msg ("Could not create lock file: %s\n", strerror (errno));
926
get_crash_db_url (void)
928
const char* url = NULL;
930
url = g_getenv ("CRASH_DB_URL");
934
if ((strncasecmp ("http://", url, 7) || url[7] == '\0') &&
935
(strncasecmp ("https://", url, 8) || url[8] == '\0'))
937
return g_strdup (url);
941
drop_privileges (GError** error)
943
struct passwd *pw = NULL;
945
if (getuid () != 0) {
946
if (g_getenv ("CRASH_DB_IDENTIFIER") == NULL) {
947
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
948
"You must be root to run this program, or set $CRASH_DB_IDENTIFIER.");
952
if (!(pw = getpwnam (username))) {
953
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
954
"Failed to find user: %s", username);
958
/* Drop privileges */
959
if (setgroups (1, &pw->pw_gid) < 0 ||
960
setresgid (pw->pw_gid, pw->pw_gid, pw->pw_gid) < 0 ||
961
setresuid (pw->pw_uid, pw->pw_uid, pw->pw_uid) < 0) {
962
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
963
"Failed to become user: %s", username);
967
if (prctl (PR_SET_DUMPABLE, 1))
968
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
969
"Failed to ensure core dump production.");
971
if ((setenv ("USER", username, 1) < 0) ||
972
(setenv ("USERNAME", username, 1) < 0)) {
973
g_set_error (error, g_quark_from_static_string ("whoopsie-quark"), 0,
974
"Failed to set user environment variables.");
980
network_changed (gboolean available)
982
if (online_state != available)
983
log_msg (available ? "online\n" : "offline\n");
986
online_state = FALSE;
990
if (online_state && available)
993
online_state = available;
996
process_existing_files (report_dir);
1000
check_online_then_upload (const char* crash_file) {
1002
if (!online_state) {
1003
log_msg ("Not online; processing later (%s).\n", crash_file);
1007
if (!parse_and_upload_report (crash_file)) {
1008
log_msg ("Could not upload; processing later (%s).\n", crash_file);
1016
create_crash_directory (void)
1018
struct passwd *pw = NULL;
1020
if (mkdir (report_dir, 0755) < 0) {
1021
if (errno != EEXIST) {
1022
log_msg ("Could not create non-existent report_directory to monitor (%d): %s.\n", errno, report_dir);
1023
exit (EXIT_FAILURE);
1026
/* Only change the permissions if we've just created it */
1027
if (!(pw = getpwnam (username))) {
1028
log_msg ("Could not find user, %s.\n", username);
1029
exit (EXIT_FAILURE);
1031
if (chown (report_dir, -1, pw->pw_gid) < 0) {
1032
log_msg ("Could not change ownership of %s.\n", report_dir);
1033
exit (EXIT_FAILURE);
1035
if (chmod (report_dir, 03777) < 0) {
1036
log_msg ("Could not change permissions on %s.\n", report_dir);
1037
exit (EXIT_FAILURE);
1043
static GMainLoop* loop = NULL;
1046
parse_arguments (int* argc, char** argv[])
1049
GOptionContext* context;
1051
context = g_option_context_new (NULL);
1052
g_option_context_add_main_entries (context, option_entries, NULL);
1053
if (!g_option_context_parse (context, argc, argv, &err)) {
1054
log_msg ("whoopsie: %s\n", err->message);
1056
exit (EXIT_FAILURE);
1058
g_option_context_free (context);
1062
handle_signals (int signo)
1065
g_main_loop_quit (loop);
1071
setup_signals (void)
1073
struct sigaction action;
1076
sigemptyset (&mask);
1077
action.sa_handler = handle_signals;
1078
action.sa_mask = mask;
1079
action.sa_flags = 0;
1080
sigaction (SIGTERM, &action, NULL);
1081
sigaction (SIGINT, &action, NULL);
1085
start_polling (void)
1087
g_timeout_add_seconds (PROCESS_OUTSTANDING_TIMEOUT,
1088
(GSourceFunc) process_existing_files_if_online, (gpointer) report_dir);
1090
loop = g_main_loop_new (NULL, FALSE);
1091
g_main_loop_run (loop);
1095
main (int argc, char** argv)
1099
GFileMonitor* monitor;
1102
parse_arguments (&argc, &argv);
1106
* In non polling mode, we exit after the first invocation of
1107
* process_existing_files.
1108
* Since we start the process with online_status already set to TRUE,
1109
* the first invocation of process_existing_files will always ignore
1110
* the assume_online variable. Therefore, assume_online is irrelevant
1111
* in non polling mode.
1113
assume_online = TRUE;
1118
log_msg ("whoopsie " VERSION " starting up.\n");
1121
if ((crash_db_url = get_crash_db_url ()) == NULL) {
1122
log_msg ("Could not get crash database location.\n");
1123
exit (EXIT_FAILURE);
1126
/* environment might change report directory and identifier */
1127
env = g_getenv ("APPORT_REPORT_DIR");
1128
if (env != NULL && *env != '\0')
1129
report_dir = g_strdup (env);
1131
env = g_getenv ("CRASH_DB_IDENTIFIER");
1133
whoopsie_identifier = g_strdup (env);
1135
whoopsie_identifier_generate (&whoopsie_identifier, &err);
1137
env = g_getenv ("CRASH_DB_NOVERIFYPEER");
1139
log_msg ("CRASH_DB_NOVERIFYPEER is set, not verifying ssl cert.\n");
1144
log_msg ("%s\n", err->message);
1147
crash_db_submit_url = strdup (crash_db_url);
1149
crash_db_submit_url = g_strdup_printf ("%s/%s", crash_db_url,
1150
whoopsie_identifier);
1153
/* Only publish whoopsie-id if it was generated. LP: #1389357 */
1155
g_file_set_contents (WHOOPSIE_ID_PATH, whoopsie_identifier, -1, NULL);
1156
chmod (WHOOPSIE_ID_PATH, 00600);
1159
create_crash_directory ();
1161
drop_privileges (&err);
1163
log_msg ("%s\n", err->message);
1165
exit (EXIT_FAILURE);
1167
exit_if_already_running ();
1175
#if GLIB_MAJOR_VERSION <= 2 && GLIB_MINOR_VERSION < 35
1176
/* Deprecated in glib 2.35/2.36. */
1180
monitor = monitor_directory (report_dir, check_online_then_upload);
1182
exit (EXIT_FAILURE);
1185
monitor_connectivity (crash_db_url, network_changed);
1187
/* As long as we keep online_state to TRUE by default, this initial call
1188
* happens unconditionally. */
1189
process_existing_files_if_online (report_dir);
1194
unmonitor_directory (monitor, check_online_then_upload);
1196
unmonitor_connectivity ();
1200
g_unlink (lock_path);
1202
curl_global_cleanup ();
1204
g_free (crash_db_url);
1205
g_free (crash_db_submit_url);