diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2015-07-13 21:41:23 +0200 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2015-07-14 12:43:46 +0200 |
commit | 964a0f9c89be0b84f13d4d0997e353643712dddc (patch) | |
tree | e51869a32bd81f400ced27ef1409a82b88402e25 | |
parent | aa6bc969c4413a5e2954d68b543ae8825763ced9 (diff) | |
download | tracker-wip/collect-bug-info.tar.gz |
cli: Add collect-bug-info subcommand to "tracker" CLI toolwip/collect-bug-info
This command basically does everything we usually ask to the users, it:
- queries all info about a given resource or file
- Gets seemingly related journald logs
- Optionally, copy the file or
- Gets gvfs-info and file(1) output
- Gets the lowlevel database schema
- Packs everything on a tar.zx, ready to be attached to bz, shared via
email...
The --inspect-logs/-l switch can be used to check the logs from the last
30 days on the look for tracker errors with an uri/urn we can pull info
from.
-rw-r--r-- | docs/manpages/Makefile.am | 1 | ||||
-rw-r--r-- | docs/manpages/tracker-collect-bug-info.1 | 32 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | src/tracker/Makefile.am | 2 | ||||
-rw-r--r-- | src/tracker/bash-completion/tracker | 7 | ||||
-rw-r--r-- | src/tracker/tracker-collect-bug-info.c | 895 | ||||
-rw-r--r-- | src/tracker/tracker-collect-bug-info.h | 28 | ||||
-rw-r--r-- | src/tracker/tracker-main.c | 2 |
8 files changed, 967 insertions, 1 deletions
diff --git a/docs/manpages/Makefile.am b/docs/manpages/Makefile.am index 1651d7727..e01e6a5cf 100644 --- a/docs/manpages/Makefile.am +++ b/docs/manpages/Makefile.am @@ -3,6 +3,7 @@ tn = tracker-needle.1 tmrss = tracker-miner-rss.1 common = \ + tracker-collect-bug-info.1 \ tracker-extract.1 \ tracker-info.1 \ tracker-miner-fs.1 \ diff --git a/docs/manpages/tracker-collect-bug-info.1 b/docs/manpages/tracker-collect-bug-info.1 new file mode 100644 index 000000000..8f3008bb5 --- /dev/null +++ b/docs/manpages/tracker-collect-bug-info.1 @@ -0,0 +1,32 @@ +.TH tracker-info 1 "Oct 2008" GNU "User Commands" + +.SH NAME +tracker-collect-bug-info \- Collect information about Tracker failures + +.SH SYNOPSIS +\fBtracker collect-bug-info\fR [\fIoptions\fR...] <\fIfile1\fR> [[\fIfile2\fR] ...] + +.SH DESCRIPTION +.B tracker collect-bug-info + +collects and packs relevant information that might be useful to Tracker +developers at the time of reporting bugs. + +The \fIfile\fR argument can be either a local path or a URI. It also +does not have to be an absolute path. + +.SH OPTIONS +.TP +.B \-l, \-\-inspect\-logs +Inspects journald logs, and looks for URIs/URNs in tracker failures. +An interactive menu will be offered so multiple report files can be +created at once. +.TP +.B \-b, \-\-bypass\-limits +Bypass the file limits imposed on the file. By default, only files +smaller than 10MB may get attached in the collected bundle. + +.SH SEE ALSO +.BR tracker-extract (1), +.BR tracker-info (1), +.BR journalctl (1). diff --git a/po/POTFILES.in b/po/POTFILES.in index 5e26a431b..e5dc9ac32 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -52,6 +52,7 @@ src/tracker-preferences/tracker-preferences.vala src/tracker-store/tracker-main.vala src/tracker-store/tracker-store.desktop.in.in src/tracker-store/org.freedesktop.Tracker.Store.gschema.xml.in +src/tracker/tracker-collect-bug-info.c src/tracker/tracker-config.c src/tracker/tracker-compatible.c src/tracker/tracker-daemon.c diff --git a/src/tracker/Makefile.am b/src/tracker/Makefile.am index ed4a31320..d30ae08e2 100644 --- a/src/tracker/Makefile.am +++ b/src/tracker/Makefile.am @@ -20,6 +20,8 @@ tracker_SOURCES = \ tracker-main.c \ tracker-compatible.c \ tracker-compatible.h \ + tracker-collect-bug-info.c \ + tracker-collect-bug-info.h \ tracker-config.c \ tracker-config.h \ tracker-color.h \ diff --git a/src/tracker/bash-completion/tracker b/src/tracker/bash-completion/tracker index 12749cdc7..80151aa7f 100644 --- a/src/tracker/bash-completion/tracker +++ b/src/tracker/bash-completion/tracker @@ -17,12 +17,17 @@ tracker_cmds() -h|--help) return 0; ;; - info|-f|--file) + collect-bug-info|info|-f|--file) if [[ $cur != -* ]]; then _filedir return 0; fi ;; + -l|--inspect-logs) + if [[ ${words[1]} -eq "collect-bug-info" ]]; then + return 0; + fi + ;; *) ;; esac diff --git a/src/tracker/tracker-collect-bug-info.c b/src/tracker/tracker-collect-bug-info.c new file mode 100644 index 000000000..47c36933e --- /dev/null +++ b/src/tracker/tracker-collect-bug-info.c @@ -0,0 +1,895 @@ +/* + * Copyright (C) 2015, Carlos Garnacho <carlosg@gnome.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Author: Carlos garnacho <carlosg@gnome.org> + */ + +#include "config.h" + +#include "tracker-collect-bug-info.h" +#include "tracker-color.h" + +#include <sys/stat.h> +#include <stdio.h> +#include <glib.h> +#include <glib/gprintf.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gio/gunixinputstream.h> +#include <libtracker-data/tracker-data.h> +#include <libtracker-sparql/tracker-sparql.h> + +/* We most usually appear on logs due to gnome-session */ +#define LOG_IDENTIFIER_PATTERN "gnome-session" + +#define OPTIONS_ENABLED (inspect_logs || (urns && g_strv_length (urns) > 0)) + +#define MAX_MEGS 10 +#define MAX_SIZE (MAX_MEGS * 1024 * 1024) + +typedef struct { + gchar *uri; + gchar *urn; +} FailureInfo; + +static gchar **urns; +gboolean inspect_logs; +gboolean bypass_limits; + +static GOptionEntry entries[] = { + { "bypass-limits", 'b', 0, + G_OPTION_ARG_NONE, &bypass_limits, + N_("Bypass file size restrictions"), + NULL, + }, + { "inspect-logs", 'l', 0, + G_OPTION_ARG_NONE, &inspect_logs, + N_("Inspect logs for Tracker failures"), + NULL, + }, + { G_OPTION_REMAINING, 0, 0, + G_OPTION_ARG_FILENAME_ARRAY, &urns, NULL, + /* Translators: URN stands for "Uniform resource name", this is a + * Tracker term, and should be probably better left untranslated. + */ + N_("[FILE OR URN...]") + }, + { NULL } +}; + +static gchar * +simple_query (TrackerSparqlConnection *connection, + const gchar *query) +{ + TrackerSparqlCursor *cursor = NULL; + GError *error = NULL; + + cursor = tracker_sparql_connection_query (connection, query, NULL, &error); + + if (!cursor || error) { + if (error) { + g_warning ("%s", error->message); + g_error_free (error); + } + return NULL; + } + + if (!tracker_sparql_cursor_next (cursor, NULL, &error)) { + if (error) { + g_warning ("%s", error->message); + g_error_free (error); + } + return NULL; + } + + return g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL)); +} + +static gchar * +query_urn_uri (TrackerSparqlConnection *connection, + const gchar *urn) +{ + gchar *query, *str; + + query = g_strdup_printf ("SELECT nie:url(<%s>) {}", urn); + str = simple_query (connection, query); + g_free (query); + + return str; +} + +static gchar * +query_uri_urn (TrackerSparqlConnection *connection, + const gchar *uri) +{ + gchar *query, *str; + + query = g_strdup_printf ("SELECT ?u { ?u nie:url \"%s\" }", uri); + str = simple_query (connection, query); + g_free (query); + + return str; +} + +static gboolean +extract_one_subject (TrackerSparqlConnection *connection, + const gchar *subject, + FILE *f, + GList **urns_found) +{ + TrackerSparqlCursor *cursor; + GError *error = NULL; + gboolean first = TRUE; + gchar *query; + + query = g_strdup_printf ("SELECT ?p ?o { <%s> ?p ?o }", subject); + cursor = tracker_sparql_connection_query (connection, query, NULL, &error); + + if (!cursor || error) { + if (error) { + g_warning ("%s", error->message); + g_error_free (error); + } + return FALSE; + } + + g_fprintf (f, "<%s>", subject); + + while (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + const gchar *pred, *object; + + g_fprintf (f, "%s\n", first ? "" : ";"); + first = FALSE; + + pred = tracker_sparql_cursor_get_string (cursor, 0, NULL); + object = tracker_sparql_cursor_get_string (cursor, 1, NULL); + g_fprintf (f, "\t%s ", pred); + + if (g_str_has_suffix (pred, "plainTextContent")) + object = "[EDITED]"; + + if (g_str_has_prefix (object, "urn:")) { + if (urns_found && strcmp (object, subject) != 0) { + *urns_found = g_list_prepend (*urns_found, + g_strdup (object)); + } + g_fprintf (f, "<%s>", object); + } else { + g_fprintf (f, "'%s'", object); + } + } + + g_fprintf (f, ".\n\n"); + + return TRUE; +} + +static gboolean +extract_urn_info (TrackerSparqlConnection *connection, + const gchar *urn, + const gchar *destdir) +{ + GList *found = NULL; + gchar *filename; + FILE *f; + + filename = g_build_filename (destdir, "tracker-database-info.txt", NULL); + f = fopen (filename, "w"); + g_free (filename); + + if (!extract_one_subject (connection, urn, f, &found)) { + fclose (f); + return FALSE; + } + + while (found) { + extract_one_subject (connection, found->data, f, NULL); + g_free (found->data); + found = g_list_delete_link (found, found); + } + + fclose (f); + return TRUE; +} + +static GArray * +filter_duplicates (TrackerSparqlConnection *connection, + GList *file_list, + GList *urn_list) +{ + GHashTable *urns, *files; + GHashTableIter iter; + gchar *uri, *urn; + GArray *result; + GList *l; + + urns = g_hash_table_new (g_str_hash, g_str_equal); + files = g_hash_table_new (g_str_hash, g_str_equal); + result = g_array_new (FALSE, FALSE, sizeof (FailureInfo)); + + /* Start with urns */ + for (l = urn_list; l; l = l->next) { + gchar *uri; + + if (g_hash_table_lookup (urns, l->data)) + continue; + + uri = query_urn_uri (connection, l->data); + + if (uri) { + g_hash_table_insert (urns, g_strdup (l->data), uri); + g_hash_table_insert (files, uri, l->data); + } + } + + /* Continue with files */ + for (l = file_list; l; l = l->next) { + gchar *urn; + + if (g_hash_table_lookup (files, l->data)) + continue; + + urn = query_uri_urn (connection, l->data); + + if (urn) { + g_hash_table_insert (urns, urn, g_strdup (l->data)); + g_hash_table_insert (files, l->data, urn); + } + } + + g_hash_table_iter_init (&iter, urns); + + while (g_hash_table_iter_next (&iter, (gpointer *) &urn, (gpointer *) &uri)) { + FailureInfo info; + + info.urn = g_strdup (urn); + info.uri = g_strdup (uri); + g_array_append_val (result, info); + } + + g_hash_table_destroy (files); + g_hash_table_destroy (urns); + + return result; +} + +static GArray * +fetch_log_failures (TrackerSparqlConnection *connection) +{ + gchar *argv[] = { "journalctl", "--no-pager", "-t", LOG_IDENTIFIER_PATTERN, + "--since=-30d", "--output=short-iso", NULL }; + GError *error = NULL; + GDataInputStream *dstream; + GInputStream *stream; + gint output; + gchar *line; + gssize size; + GPid pid; + GRegex *file_regex, *urn_regex; + GList *files = NULL, *urns = NULL; + GArray *result; + + if (!g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_SEARCH_PATH | + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &pid, + NULL, &output, NULL, &error)) { + if (error) { + g_warning ("%s", error->message); + g_error_free (error); + } + return NULL; + } + + file_regex = g_regex_new ("file:/[^()'\",]+", G_REGEX_OPTIMIZE, 0, NULL); + urn_regex = g_regex_new ("urn:uuid:[a-zA-Z0-9\\-]+", G_REGEX_OPTIMIZE, 0, NULL); + + stream = g_unix_input_stream_new (output, FALSE); + dstream = g_data_input_stream_new (stream); + + do { + line = g_data_input_stream_read_line (dstream, &size, NULL, &error); + + if (!line) { + if (error) { + g_warning ("%s", error->message); + g_error_free (error); + } + break; + } + + if (strstr (line, "Tracker")) { + GMatchInfo *info; + + if (g_regex_match (file_regex, line, 0, &info)) { + files = g_list_prepend (files, g_match_info_fetch (info, 0)); + g_match_info_free (info); + } + + if (g_regex_match (urn_regex, line, 0, &info)) { + urns = g_list_prepend (urns, g_match_info_fetch (info, 0)); + g_match_info_free (info); + } + } + + g_free (line); + } while (TRUE); + + g_spawn_close_pid (pid); + g_object_unref (dstream); + g_object_unref (stream); + + g_regex_unref (file_regex); + g_regex_unref (urn_regex); + + result = filter_duplicates (connection, files, urns); + + g_list_foreach (files, (GFunc) g_free, NULL); + g_list_free (files); + g_list_foreach (urns, (GFunc) g_free, NULL); + g_list_free (urns); + + return result; +} + +static void +extract_related_logs (const gchar *urn, + const gchar *uri, + const gchar *destdir, + const gchar *filename) +{ + gchar *argv[] = { "journalctl", "--no-pager", "-t", LOG_IDENTIFIER_PATTERN, + "--since=-30d", "--output=short-iso", NULL }; + GError *error = NULL; + GDataInputStream *dstream; + GInputStream *stream; + gchar *line, *path; + gint output; + gssize size; + GPid pid; + FILE *f; + + g_print (_("Extracting related logs... ")); + + if (!g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_SEARCH_PATH | + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &pid, + NULL, &output, NULL, &error)) { + if (error) { + g_print (WARN_BEGIN "%s" WARN_END "\n", error->message); + g_error_free (error); + } + return; + } + + path = g_build_filename (destdir, filename, NULL); + f = fopen (path, "w"); + g_free (path); + + stream = g_unix_input_stream_new (output, FALSE); + dstream = g_data_input_stream_new (stream); + + do { + line = g_data_input_stream_read_line (dstream, &size, NULL, &error); + + if (!line) { + if (error) { + g_print (WARN_BEGIN "%s" WARN_END "\n", error->message); + g_error_free (error); + } + break; + } + + if (strstr (line, "Tracker") && + ((urn && strstr (line, urn)) || + (uri && strstr (line, uri)))) { + g_fprintf (f, line); + } + + g_free (line); + } while (TRUE); + + g_spawn_close_pid (pid); + g_object_unref (dstream); + g_object_unref (stream); + fclose (f); + + g_print (TITLE_BEGIN "%s" TITLE_END "\n", _("Done")); +} + +static gboolean +answer_yes_no_question (const gchar *str) +{ + /* Translators: these are the replies to yes/no questions, the first + * char is used for "[Y/N]" hints on a command line tool, and is also + * used to match the response on user input. + */ + gchar *yes = N_("Yes"), *no = N_("No"); + gunichar yes_lower, yes_upper, no_lower, no_upper, c; + gchar y[10], n[10], option[10]; + gssize len; + + c = g_utf8_get_char (_(yes)); + yes_upper = g_unichar_toupper (c); + yes_lower = g_unichar_tolower (c); + len = g_unichar_to_utf8 (yes_upper, y); + y[len] = '\0'; + + c = g_utf8_get_char (_(no)); + no_upper = g_unichar_toupper (c); + no_lower = g_unichar_tolower (c); + len = g_unichar_to_utf8 (no_upper, n); + n[len] = '\0'; + + while (TRUE) { + g_print ("%s [%s/%s] ", str, y, n); + scanf ("%10s", (gchar *) &option); + c = g_utf8_get_char (option); + + if (c == yes_upper || c == yes_lower) + return TRUE; + else if (c == no_upper || c == no_lower) + return FALSE; + } + + return FALSE; +} + +static TrackerSparqlConnection * +get_connection (void) +{ + TrackerSparqlConnection *connection; + GError *error = NULL; + + connection = tracker_sparql_connection_get (NULL, &error); + + if (!connection) { + g_printerr ("%s: %s\n", + _("Could not establish a connection to Tracker"), + error ? error->message : _("No error given")); + g_clear_error (&error); + } + + return connection; +} + +static void +save_sqlite_structure (const gchar *destdir, + const gchar *filename) +{ + TrackerDBInterface *iface; + TrackerDBStatement *stmt; + TrackerDBCursor *cursor = NULL; + GError *error = NULL; + gboolean first_time = FALSE; + gchar *path; + FILE *f; + + g_print (_("Saving SQLITE database schema... ")); + + if (!tracker_data_manager_init (0, NULL, &first_time, FALSE, FALSE, + 100, 100, NULL, NULL, NULL, &error)) { + g_print (WARN_BEGIN "%s" WARN_END "\n", error->message); + g_error_free (error); + return; + } + + iface = tracker_db_manager_get_db_interface (); + + stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_NONE, &error, + "select sql from sqlite_master"); + + if (stmt) { + cursor = tracker_db_statement_start_cursor (stmt, &error); + } + + if (error) { + g_print (WARN_BEGIN "%s" WARN_END "\n", error->message); + g_error_free (error); + return; + } + + path = g_build_filename (destdir, filename, NULL); + f = fopen (path, "w"); + g_free (path); + + while (tracker_db_cursor_iter_next (cursor, NULL, &error)) { + const gchar *str; + + str = tracker_db_cursor_get_string (cursor, 0, NULL); + g_fprintf (f, "%s\n", str); + } + + fclose (f); + + if (error) { + g_print (WARN_BEGIN "%s" WARN_END "\n", error->message); + g_error_free (error); + return; + } + + g_print (TITLE_BEGIN "%s" TITLE_END "\n", _("Done")); +} + +static void +copy_file (const gchar *uri, + const gchar *destdir, + const gchar *filename) +{ + GFileInputStream *istream; + GFileOutputStream *ostream; + GFile *ifile, *ofile; + gchar *dest, buf[1024]; + GError *error = NULL; + gssize len; + + g_print (_("Copying file... ")); + + dest = g_build_filename (destdir, filename, NULL); + ifile = g_file_new_for_uri (uri); + ofile = g_file_new_for_path (dest); + g_free (dest); + + istream = g_file_read (ifile, NULL, &error); + ostream = g_file_create (ofile, G_FILE_CREATE_REPLACE_DESTINATION, NULL, &error); + g_object_unref (ifile); + g_object_unref (ofile); + + if (!istream || !ostream) { + g_print (WARN_BEGIN "%s" WARN_END "\n", error->message); + g_clear_object (&istream); + g_clear_object (&ostream); + g_error_free (error); + return; + } + + while ((len = g_input_stream_read (G_INPUT_STREAM (istream), buf, sizeof (buf), NULL, NULL))) { + buf[len] = '\0'; + g_output_stream_write (G_OUTPUT_STREAM (ostream), buf, len, NULL, NULL); + } + + g_input_stream_close (G_INPUT_STREAM (istream), NULL, NULL); + g_output_stream_close (G_OUTPUT_STREAM (ostream), NULL, NULL); + + g_print (TITLE_BEGIN "%s" TITLE_END "\n", _("Done")); + g_object_unref (istream); + g_object_unref (ostream); +} + +static void +save_command_output (const gchar *visible_name, + gchar **argv, + const gchar *destdir, + const gchar *filename) +{ + gchar *dest, str[1024]; + GError *error = NULL; + FILE *o, *d; + gint output; + GPid pid; + + /* Translators: This refers to command line tools we gather info from, + * meant to be the executable name only (eg. gvfs-info, file...). + */ + g_print (_("Saving %s output... "), visible_name); + + if (!g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_SEARCH_PATH | + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &pid, + NULL, &output, NULL, &error)) { + if (error) { + g_print (WARN_BEGIN "%s" WARN_END "\n", error->message); + g_error_free (error); + } + return; + } + + dest = g_build_filename (destdir, filename, NULL); + d = fopen (dest, "w"); + o = fdopen (output, "r"); + + while (fgets (str, sizeof (str), o)) + fputs (str, d); + + g_print (TITLE_BEGIN "%s" TITLE_END "\n", _("Done")); + + fclose (d); + fclose (o); + g_free (dest); +} + +static gboolean +compress_dir (const gchar *dir, + gchar **dest_file) +{ + gchar *dest = g_strdup_printf ("%s.tar.xz", dir); + gchar *str = g_strdup_printf ("tar -Jcf %s -C %s .", dest, dir); + gchar *out, *err; + gboolean success; + gint status; + + success = g_spawn_command_line_sync (str, &out, &err, &status, NULL); + g_free (out); + g_free (err); + g_free (str); + + if (!success || status != 0) { + g_free (dest); + return FALSE; + } + + chmod (dest, 0700); + + *dest_file = dest; + return TRUE; +} + +static gboolean +check_file (GFile *file) +{ + GFileInfo *info; + gssize fsize; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR) { + g_print (WARN_BEGIN "%s" WARN_END "\n", _("Not a regular file")); + g_object_unref (info); + return FALSE; + } + + fsize = g_file_info_get_size (info); + + if (!bypass_limits && fsize > MAX_SIZE) { + g_print (WARN_BEGIN); + g_print (_("File size exceeds limit (%d MB)"), MAX_MEGS); + g_print (WARN_END "\n"); + g_object_unref (info); + return FALSE; + } + + g_object_unref (info); + return TRUE; +} + +static void +extract_resource_info (const gchar *resource) +{ + TrackerSparqlConnection *connection; + gchar *urn = NULL, *uri = NULL; + gboolean check_uri_info = TRUE; + gchar *dir, *compressed_file; + GFile *file = NULL; + + connection = get_connection (); + dir = g_strdup ("/tmp/tracker-collect-info-XXXXXX"); + g_mkdtemp_full (dir, 0700); + + if (!connection) + return EXIT_FAILURE; + + if (strstr (resource, ":/")) { + /* Looks like an uri */ + uri = g_strdup (resource); + } else if (resource[0] == '/') { + /* Looks like a path */ + file = g_file_new_for_path (resource); + uri = g_file_get_uri (file); + } else { + /* Fallback to treating these as URNs */ + urn = g_strdup (resource); + } + + if (!uri && urn) + uri = query_urn_uri (connection, urn); + else if (!urn && uri) + urn = query_uri_urn (connection, uri); + + if (!file) + file = g_file_new_for_uri (uri); + + g_print ("\n"); + g_print (_("Collecting info for '%s':\n"), uri ? uri : urn); + g_print (_("Retrieving Tracker database information... ")); + + if (!extract_urn_info (connection, urn, dir)) { + g_print (WARN_BEGIN "%s" WARN_END "\n", _("Element not found")); + g_object_unref (file); + g_free (dir); + return; + } else { + g_print (TITLE_BEGIN "%s" TITLE_END "\n", _("Done")); + } + + save_sqlite_structure (dir, "sqlite-db-schemas.txt"); + extract_related_logs (urn, uri, dir, "journald-logs.txt"); + + if (uri) { + gchar *tracker_extract_argv[] = { "/usr/libexec/tracker-extract", "-v", "3", "-f", uri, NULL }; + gchar *q; + + save_command_output ("tracker-extract", tracker_extract_argv, + dir, "tracker-extract-output.txt"); + + if (!check_file (file)) { + check_uri_info = TRUE; + } else { + /* Translators: This refers to a file/uri */ + q = g_strdup_printf (_("Save copy of %s?"), uri); + + if (answer_yes_no_question (q)) { + copy_file (uri, dir, "sample"); + check_uri_info = FALSE; + } + } + } else { + check_uri_info = FALSE; + } + + if (check_uri_info) { + gchar *path = g_file_get_path (file); + gchar *gvfs_info_argv[] = { "gvfs-info", uri, NULL }; + gchar *file_argv[] = { "file", path, NULL }; + + save_command_output ("gvfs-info", gvfs_info_argv, + dir, "gvfs-info-output.txt"); + save_command_output ("file(1)", file_argv, + dir, "file-output.txt"); + g_free (path); + } + + g_print (_("Compressing collected information... ")); + + if (!compress_dir (dir, &compressed_file)) { + g_print (WARN_BEGIN "%s" WARN_END "\n", _("Error")); + g_print ("\n"); + /* Translators: this refers to a directory path */ + g_print (_("Collected information left on %s"), dir); + g_free (dir); + return; + } else { + g_print (TITLE_BEGIN "%s" TITLE_END "\n", _("Done")); + } + + g_print ("\n"); + /* Translators: placeholders respectively refer to + * a file path, and a bugzilla URL. + */ + g_print (_("Report file created at: %s\n" + "Please consider filing a bug at %s with this file attached."), + compressed_file, PACKAGE_BUGREPORT); + g_print ("\n"); + + g_object_unref (file); + g_free (compressed_file); + g_free (dir); +} + +static void +process_log_failures (GArray *failures) +{ + FailureInfo *info; + gint i; + + while (failures->len > 0) { + g_print (ngettext ("Found %d possible failure:", + "Found %d possible failures:", + failures->len), failures->len); + g_print ("\n"); + + for (i = 0; i < failures->len; i++) { + info = &g_array_index (failures, FailureInfo, i); + g_print ("[%d]\t%s\n\t(%s)\n", i + 1, info->uri, info->urn); + } + + g_print ("\n"); + g_print (_("Select number: ")); + scanf ("%d", &i); + i--; + + if (i >= 0 && i < failures->len) { + info = &g_array_index (failures, FailureInfo, i); + extract_resource_info (info->urn); + g_array_remove_index (failures, i); + + if (failures->len > 0) { + g_print ("\n"); + if (!answer_yes_no_question (_("Continue with other failures?"))) + break; + } + } + } + + if (failures->len == 0) { + g_print (_("No Tracker failures found in logs")); + g_print ("\n"); + } +} + +static int +info_run (void) +{ + if (inspect_logs) { + TrackerSparqlConnection *connection; + GArray *failures; + + connection = get_connection (); + failures = fetch_log_failures (connection); + process_log_failures (failures); + + g_array_unref (failures); + } else if (urns && *urns) { + guint i; + + for (i = 0; urns[i]; i++) + extract_resource_info (urns[i]); + } + + g_print (_("Thanks for helping improve Tracker.")); + g_print ("\n\n"); + + return EXIT_SUCCESS; +} + +static int +info_run_default (void) +{ + GOptionContext *context; + gchar *help; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + help = g_option_context_get_help (context, TRUE, NULL); + g_option_context_free (context); + g_printerr ("%s\n", help); + g_free (help); + + return EXIT_FAILURE; +} + +int +tracker_collect_bug_info (int argc, const char **argv) +{ + GOptionContext *context; + GError *error = NULL; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + argv[0] = "tracker collect-info"; + + if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) { + g_printerr ("%s, %s\n", _("Unrecognized options"), error->message); + g_error_free (error); + g_option_context_free (context); + return EXIT_FAILURE; + } + + g_option_context_free (context); + + if (OPTIONS_ENABLED) + return info_run (); + else + return info_run_default (); +} diff --git a/src/tracker/tracker-collect-bug-info.h b/src/tracker/tracker-collect-bug-info.h new file mode 100644 index 000000000..430b98a7d --- /dev/null +++ b/src/tracker/tracker-collect-bug-info.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015, Carlos Garnacho <carlosg@gnome.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * Author: Carlos garnacho <carlosg@gnome.org> + */ + +#ifndef __TRACKER_COLLECT_BUG_INFO_H__ +#define __TRACKER_COLLECT_BUG_INFO_H__ + +int tracker_collect_bug_info (int argc, + const char **argv); + +#endif /* __TRACKER_COLLECT_BUG_INFO_H__ */ diff --git a/src/tracker/tracker-main.c b/src/tracker/tracker-main.c index 4633f4240..7ae141b69 100644 --- a/src/tracker/tracker-main.c +++ b/src/tracker/tracker-main.c @@ -28,6 +28,7 @@ #include <libtracker-common/tracker-common.h> +#include "tracker-collect-bug-info.h" #include "tracker-daemon.h" #include "tracker-compatible.h" #include "tracker-help.h" @@ -94,6 +95,7 @@ struct cmd_struct { }; static struct cmd_struct commands[] = { + { "collect-bug-info", tracker_collect_bug_info, NEED_NOTHING, N_("Collect information about Tracker failures") }, { "daemon", tracker_daemon, NEED_WORK_TREE, N_("Start, stop, pause and list processes responsible for indexing content") }, { "help", tracker_help, NEED_NOTHING, N_("Get help on how to use Tracker and any of these commands") }, { "info", tracker_info, NEED_WORK_TREE, N_("Show information known about local files or items indexed") }, |