1 /*
2  * Copyright (C) 2014, Lanedo <martyn@lanedo.com>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 
22 #include <stdlib.h>
23 #include <stdio.h>
24 
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <glib/gprintf.h>
28 #include <gio/gio.h>
29 
30 #include <libtracker-common/tracker-common.h>
31 #include <libtracker-data/tracker-data.h>
32 #include <libtracker-control/tracker-control.h>
33 
34 #include "tracker-reset.h"
35 #include "tracker-daemon.h"
36 #include "tracker-process.h"
37 #include "tracker-config.h"
38 #include "tracker-color.h"
39 
40 static gboolean hard_reset;
41 static gboolean soft_reset;
42 static gboolean remove_config;
43 static gchar *filename = NULL;
44 
45 #define RESET_OPTIONS_ENABLED() \
46 	(hard_reset || \
47 	 soft_reset || \
48 	 remove_config || \
49 	 filename)
50 
51 static GOptionEntry entries[] = {
52 	{ "hard", 'r', 0, G_OPTION_ARG_NONE, &hard_reset,
53 	  N_("Kill all Tracker processes and remove all databases"),
54 	  NULL },
55 	{ "soft", 'e', 0, G_OPTION_ARG_NONE, &soft_reset,
56 	  N_("Same as --hard but the backup & journal are restored after restart"),
57 	  NULL },
58 	{ "config", 'c', 0, G_OPTION_ARG_NONE, &remove_config,
59 	  N_("Remove all configuration files so they are re-generated on next start"),
60 	  NULL },
61 	{ "file", 'f', 0, G_OPTION_ARG_FILENAME, &filename,
62 	  N_("Erase indexed information about a file, works recursively for directories"),
63 	  N_("FILE") },
64 	{ NULL }
65 };
66 
67 static void
log_handler(const gchar * domain,GLogLevelFlags log_level,const gchar * message,gpointer user_data)68 log_handler (const gchar    *domain,
69              GLogLevelFlags  log_level,
70              const gchar    *message,
71              gpointer        user_data)
72 {
73 	switch (log_level) {
74 	case G_LOG_LEVEL_WARNING:
75 	case G_LOG_LEVEL_CRITICAL:
76 	case G_LOG_LEVEL_ERROR:
77 	case G_LOG_FLAG_RECURSION:
78 	case G_LOG_FLAG_FATAL:
79 		g_fprintf (stderr, "%s\n", message);
80 		fflush (stderr);
81 		break;
82 	case G_LOG_LEVEL_MESSAGE:
83 	case G_LOG_LEVEL_INFO:
84 	case G_LOG_LEVEL_DEBUG:
85 	case G_LOG_LEVEL_MASK:
86 	default:
87 		g_fprintf (stdout, "%s\n", message);
88 		fflush (stdout);
89 		break;
90 	}
91 }
92 
93 static int
delete_info_recursively(GFile * file)94 delete_info_recursively (GFile *file)
95 {
96 	TrackerSparqlConnection *connection;
97 	TrackerMinerManager *miner_manager;
98 	TrackerSparqlCursor *cursor;
99 	gchar *query, *uri;
100 	GError *error = NULL;
101 
102 	connection = tracker_sparql_connection_get (NULL, &error);
103 
104 	if (error)
105 		goto error;
106 
107 	uri = g_file_get_uri (file);
108 
109 	/* First, query whether the item exists */
110 	query = g_strdup_printf ("SELECT ?u { ?u nie:url '%s' }", uri);
111 	cursor = tracker_sparql_connection_query (connection, query,
112 	                                          NULL, &error);
113 
114 	/* If the item doesn't exist, bail out. */
115 	if (!cursor || !tracker_sparql_cursor_next (cursor, NULL, &error)) {
116 		g_clear_object (&cursor);
117 
118 		if (error)
119 			goto error;
120 
121 		return EXIT_SUCCESS;
122 	}
123 
124 	g_object_unref (cursor);
125 
126 	/* Now, delete the element recursively */
127 	g_print ("%s\n", _("Deleting…"));
128 	query = g_strdup_printf ("DELETE { "
129 	                         "  ?f a rdfs:Resource . "
130 	                         "  ?ie a rdfs:Resource "
131 	                         "} WHERE {"
132 	                         "  ?f nie:url ?url . "
133 	                         "  ?ie nie:isStoredAs ?f . "
134 	                         "  FILTER (?url = '%s' ||"
135 	                         "          STRSTARTS (?url, '%s/'))"
136 	                         "}", uri, uri);
137 	g_free (uri);
138 
139 	tracker_sparql_connection_update (connection, query,
140 	                                  G_PRIORITY_DEFAULT, NULL, &error);
141 	g_free (query);
142 
143 	if (error)
144 		goto error;
145 
146 	g_object_unref (connection);
147 
148 	g_print ("%s\n", _("The indexed data for this file has been deleted "
149 	                   "and will be reindexed again."));
150 
151 	/* Request reindexing of this data, it was previously in the store. */
152 	miner_manager = tracker_miner_manager_new_full (FALSE, NULL);
153 	tracker_miner_manager_index_file (miner_manager, file, NULL, &error);
154 	g_object_unref (miner_manager);
155 
156 	if (error)
157 		goto error;
158 
159 	return EXIT_SUCCESS;
160 
161 error:
162 	g_warning ("%s", error->message);
163 	g_error_free (error);
164 	return EXIT_FAILURE;
165 }
166 
167 static gint
reset_run(void)168 reset_run (void)
169 {
170 	GError *error = NULL;
171 
172 	if (hard_reset && soft_reset) {
173 		g_printerr ("%s\n",
174 		            /* TRANSLATORS: --hard and --soft are commandline arguments */
175 		            _("You can not use the --hard and --soft arguments together"));
176 		return EXIT_FAILURE;
177 	}
178 
179 	if (hard_reset || soft_reset) {
180 		gchar response[100] = { 0 };
181 
182 		g_print (CRIT_BEGIN "%s" CRIT_END "\n%s\n\n%s %s: ",
183 		         _("CAUTION: This process may irreversibly delete data."),
184 		         _("Although most content indexed by Tracker can "
185 		           "be safely reindexed, it can’t be assured that "
186 		           "this is the case for all data. Be aware that "
187 		           "you may be incurring in a data loss situation, "
188 		           "proceed at your own risk."),
189 		         _("Are you sure you want to proceed?"),
190 		         /* TRANSLATORS: This is to be displayed on command line output */
191 		         _("[y|N]"));
192 
193 		fgets (response, 100, stdin);
194 		response[strlen (response) - 1] = '\0';
195 
196 		/* TRANSLATORS: this is our test for a [y|N] question in the command line.
197 		 * A partial or full match will be considered an affirmative answer,
198 		 * it is intentionally lowercase, so please keep it like this.
199 		 */
200 		if (!response[0] || !g_str_has_prefix (_("yes"), response)) {
201 			return EXIT_FAILURE;
202 		}
203 	}
204 
205 	if (filename) {
206 		GFile *file;
207 		gint retval;
208 
209 		file = g_file_new_for_commandline_arg (filename);
210 		retval = delete_info_recursively (file);
211 		g_object_unref (file);
212 		return retval;
213 	}
214 
215 	/* KILL processes first... */
216 	if (hard_reset || soft_reset) {
217 		tracker_process_stop (TRACKER_PROCESS_TYPE_NONE, TRACKER_PROCESS_TYPE_ALL);
218 	}
219 
220 	if (hard_reset || soft_reset) {
221 		guint log_handler_id;
222 		GFile *cache_location, *data_location;
223 		gchar *dir;
224 		TrackerDBManager *db_manager;
225 #ifndef DISABLE_JOURNAL
226 		gchar *rotate_to;
227 		TrackerDBConfig *db_config;
228 		gsize chunk_size;
229 		gint chunk_size_mb;
230 		TrackerDBJournal *journal_writer;
231 #endif /* DISABLE_JOURNAL */
232 
233 		dir = g_build_filename (g_get_user_cache_dir (), "tracker", NULL);
234 		cache_location = g_file_new_for_path (dir);
235 		g_free (dir);
236 
237 		dir = g_build_filename (g_get_user_data_dir (), "tracker", "data", NULL);
238 		data_location = g_file_new_for_path (dir);
239 		g_free (dir);
240 
241 		/* Set log handler for library messages */
242 		log_handler_id = g_log_set_handler (NULL,
243 		                                    G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL,
244 		                                    log_handler,
245 		                                    NULL);
246 
247 		g_log_set_default_handler (log_handler, NULL);
248 
249 #ifndef DISABLE_JOURNAL
250 		db_config = tracker_db_config_new ();
251 
252 		chunk_size_mb = tracker_db_config_get_journal_chunk_size (db_config);
253 		chunk_size = (gsize) ((gsize) chunk_size_mb * (gsize) 1024 * (gsize) 1024);
254 		rotate_to = tracker_db_config_get_journal_rotate_destination (db_config);
255 
256 		/* This call is needed to set the journal's filename */
257 		tracker_db_journal_set_rotating ((chunk_size_mb != -1),
258 		                                 chunk_size, rotate_to);
259 
260 		g_free (rotate_to);
261 		g_object_unref (db_config);
262 
263 #endif /* DISABLE_JOURNAL */
264 
265 		/* Clean up (select_cache_size and update_cache_size don't matter here) */
266 		db_manager = tracker_db_manager_new (TRACKER_DB_MANAGER_REMOVE_ALL,
267 		                                     cache_location, data_location,
268 		                                     NULL,
269 		                                     FALSE,
270 		                                     FALSE,
271 		                                     100,
272 		                                     100,
273 		                                     NULL,
274 		                                     NULL,
275 		                                     NULL,
276 		                                     NULL,
277 		                                     NULL,
278 		                                     &error);
279 
280 		if (!db_manager) {
281 			g_message ("Error initializing database: %s", error->message);
282 			g_free (error);
283 
284 			return EXIT_FAILURE;
285 		}
286 
287 		tracker_db_manager_remove_all (db_manager);
288 #ifndef DISABLE_JOURNAL
289 		journal_writer = tracker_db_journal_new (data_location, FALSE, NULL);
290 		tracker_db_journal_remove (journal_writer);
291 #endif /* DISABLE_JOURNAL */
292 
293 		tracker_db_manager_remove_version_file (db_manager);
294 		tracker_db_manager_free (db_manager);
295 
296 		/* Unset log handler */
297 		g_log_remove_handler (NULL, log_handler_id);
298 
299 		if (!remove_config) {
300 			return EXIT_SUCCESS;
301 		}
302 	}
303 
304 	if (remove_config) {
305 		GSList *all, *l;
306 
307 		g_print ("%s\n", _("Resetting existing configuration…"));
308 
309 		all = tracker_gsettings_get_all (NULL);
310 
311 		if (!all) {
312 			return EXIT_FAILURE;
313 		}
314 
315 		for (l = all; l; l = l->next) {
316 			ComponentGSettings *c = l->data;
317 			gchar **keys, **p;
318 
319 			if (!c) {
320 				continue;
321 			}
322 
323 			g_print ("  %s\n", c->name);
324 
325 			keys = g_settings_schema_list_keys (c->schema);
326 			for (p = keys; p && *p; p++) {
327 				g_print ("    %s\n", *p);
328 				g_settings_reset (c->settings, *p);
329 			}
330 
331 			if (keys) {
332 				g_strfreev (keys);
333 			}
334 
335 			g_settings_apply (c->settings);
336 		}
337 
338 		g_settings_sync ();
339 
340 		tracker_gsettings_free (all);
341 
342 		return EXIT_SUCCESS;
343 	}
344 
345 	/* All known options have their own exit points */
346 	g_warn_if_reached ();
347 
348 	return EXIT_FAILURE;
349 }
350 
351 static int
reset_run_default(void)352 reset_run_default (void)
353 {
354 	GOptionContext *context;
355 	gchar *help;
356 
357 	context = g_option_context_new (NULL);
358 	g_option_context_add_main_entries (context, entries, NULL);
359 	help = g_option_context_get_help (context, FALSE, NULL);
360 	g_option_context_free (context);
361 	g_printerr ("%s\n", help);
362 	g_free (help);
363 
364 	return EXIT_FAILURE;
365 }
366 
367 static gboolean
reset_options_enabled(void)368 reset_options_enabled (void)
369 {
370 	return RESET_OPTIONS_ENABLED ();
371 }
372 
373 int
tracker_reset(int argc,const char ** argv)374 tracker_reset (int argc, const char **argv)
375 {
376 	GOptionContext *context;
377 	GError *error = NULL;
378 
379 	context = g_option_context_new (NULL);
380 	g_option_context_add_main_entries (context, entries, NULL);
381 
382 	argv[0] = "tracker reset";
383 
384 	if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
385 		g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
386 		g_error_free (error);
387 		g_option_context_free (context);
388 		return EXIT_FAILURE;
389 	}
390 
391 	g_option_context_free (context);
392 
393 	if (reset_options_enabled ()) {
394 		return reset_run ();
395 	}
396 
397 	return reset_run_default ();
398 }
399