1 /*
2  * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA  02110-1301, USA.
18  */
19 
20 #include "config-miners.h"
21 
22 #include <string.h>
23 
24 #include <gio/gio.h>
25 #include <gio/gunixmounts.h>
26 
27 #include <libtracker-miners-common/tracker-log.h>
28 
29 #include "tracker-storage.h"
30 
31 /**
32  * SECTION:tracker-storage
33  * @short_description: Removable storage and mount point convenience API
34  * @include: libtracker-miner/tracker-miner.h
35  *
36  * This API is a convenience to to be able to keep track of volumes
37  * which are mounted and also the type of removable media available.
38  * The API is built upon the top of GIO's #GMount, #GDrive and #GVolume API.
39  **/
40 
41 #define TRACKER_STORAGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_STORAGE, TrackerStoragePrivate))
42 
43 typedef struct {
44 	GVolumeMonitor *volume_monitor;
45 
46 	GNode *mounts;
47 	GHashTable *mounts_by_uuid;
48 	GHashTable *unmount_watchdogs;
49 } TrackerStoragePrivate;
50 
51 typedef struct {
52 	gchar *mount_point;
53 	gchar *uuid;
54 	guint unmount_timer_id;
55 	guint removable : 1;
56 	guint optical : 1;
57 } MountInfo;
58 
59 typedef struct {
60 	const gchar *path;
61 	GNode *node;
62 } TraverseData;
63 
64 typedef struct {
65 	GSList *roots;
66 	TrackerStorageType type;
67 	gboolean exact_match;
68 } GetRoots;
69 
70 typedef struct {
71 	TrackerStorage *storage;
72 	GMount *mount;
73 } UnmountCheckData;
74 
75 static void     tracker_storage_finalize (GObject        *object);
76 static gboolean mount_info_free          (GNode          *node,
77                                           gpointer        user_data);
78 static void     mount_node_free          (GNode          *node);
79 static gboolean mounts_setup             (TrackerStorage *storage);
80 static void     mount_added_cb           (GVolumeMonitor *monitor,
81                                           GMount         *mount,
82                                           gpointer        user_data);
83 static void     mount_removed_cb         (GVolumeMonitor *monitor,
84                                           GMount         *mount,
85                                           gpointer        user_data);
86 static void     mount_pre_removed_cb     (GVolumeMonitor *monitor,
87                                           GMount         *mount,
88                                           gpointer        user_data);
89 
90 enum {
91 	MOUNT_POINT_ADDED,
92 	MOUNT_POINT_REMOVED,
93 	LAST_SIGNAL
94 };
95 
96 static guint signals[LAST_SIGNAL] = {0};
97 
98 G_DEFINE_TYPE_WITH_PRIVATE (TrackerStorage, tracker_storage, G_TYPE_OBJECT);
99 
100 static void
tracker_storage_class_init(TrackerStorageClass * klass)101 tracker_storage_class_init (TrackerStorageClass *klass)
102 {
103 	GObjectClass *object_class;
104 
105 	object_class = G_OBJECT_CLASS (klass);
106 
107 	object_class->finalize     = tracker_storage_finalize;
108 
109 	signals[MOUNT_POINT_ADDED] =
110 		g_signal_new ("mount-point-added",
111 		              G_TYPE_FROM_CLASS (klass),
112 		              G_SIGNAL_RUN_LAST,
113 		              0,
114 		              NULL, NULL,
115 		              NULL,
116 		              G_TYPE_NONE,
117 		              5,
118 		              G_TYPE_STRING,
119 		              G_TYPE_STRING,
120                               G_TYPE_STRING,
121 		              G_TYPE_BOOLEAN,
122 		              G_TYPE_BOOLEAN);
123 
124 	signals[MOUNT_POINT_REMOVED] =
125 		g_signal_new ("mount-point-removed",
126 		              G_TYPE_FROM_CLASS (klass),
127 		              G_SIGNAL_RUN_LAST,
128 		              0,
129 		              NULL, NULL,
130 		              NULL,
131 		              G_TYPE_NONE,
132 		              2,
133 		              G_TYPE_STRING,
134 		              G_TYPE_STRING);
135 }
136 
137 static void
tracker_storage_init(TrackerStorage * storage)138 tracker_storage_init (TrackerStorage *storage)
139 {
140 	TrackerStoragePrivate *priv;
141 
142 	g_message ("Initializing Storage...");
143 
144 	priv = tracker_storage_get_instance_private (storage);
145 
146 	priv->mounts = g_node_new (NULL);
147 
148 	priv->mounts_by_uuid = g_hash_table_new_full (g_str_hash,
149 	                                              g_str_equal,
150 	                                              (GDestroyNotify) g_free,
151 	                                              NULL);
152 	priv->unmount_watchdogs = g_hash_table_new_full (NULL, NULL, NULL,
153 							 (GDestroyNotify) g_source_remove);
154 
155 	priv->volume_monitor = g_volume_monitor_get ();
156 
157 	/* Volume and property notification callbacks */
158 	g_signal_connect_object (priv->volume_monitor, "mount-removed",
159 	                         G_CALLBACK (mount_removed_cb), storage, 0);
160 	g_signal_connect_object (priv->volume_monitor, "mount-pre-unmount",
161 	                         G_CALLBACK (mount_pre_removed_cb), storage, 0);
162 	g_signal_connect_object (priv->volume_monitor, "mount-added",
163 	                         G_CALLBACK (mount_added_cb), storage, 0);
164 
165 	g_message ("Mount monitors set up for to watch for added, removed and pre-unmounts...");
166 
167 	/* Get all mounts and set them up */
168 	if (!mounts_setup (storage)) {
169 		return;
170 	}
171 }
172 
173 static void
tracker_storage_finalize(GObject * object)174 tracker_storage_finalize (GObject *object)
175 {
176 	TrackerStoragePrivate *priv;
177 
178 	priv = tracker_storage_get_instance_private (TRACKER_STORAGE (object));
179 
180 	g_hash_table_destroy (priv->unmount_watchdogs);
181 
182 	if (priv->mounts_by_uuid) {
183 		g_hash_table_unref (priv->mounts_by_uuid);
184 	}
185 
186 	if (priv->mounts) {
187 		mount_node_free (priv->mounts);
188 	}
189 
190 	if (priv->volume_monitor) {
191 		g_object_unref (priv->volume_monitor);
192 	}
193 
194 	(G_OBJECT_CLASS (tracker_storage_parent_class)->finalize) (object);
195 }
196 
197 static void
mount_node_free(GNode * node)198 mount_node_free (GNode *node)
199 {
200 	g_node_traverse (node,
201 	                 G_POST_ORDER,
202 	                 G_TRAVERSE_ALL,
203 	                 -1,
204 	                 mount_info_free,
205 	                 NULL);
206 
207 	g_node_destroy (node);
208 }
209 
210 static gboolean
mount_node_traverse_func(GNode * node,gpointer user_data)211 mount_node_traverse_func (GNode    *node,
212                           gpointer  user_data)
213 {
214 	TraverseData *data;
215 	MountInfo *info;
216 
217 	if (!node->data) {
218 		/* Root node */
219 		return FALSE;
220 	}
221 
222 	data = user_data;
223 	info = node->data;
224 
225 	if (g_str_has_prefix (data->path, info->mount_point)) {
226 		data->node = node;
227 		return TRUE;
228 	}
229 
230 	return FALSE;
231 }
232 
233 static GNode *
mount_node_find(GNode * root,const gchar * path)234 mount_node_find (GNode       *root,
235                  const gchar *path)
236 {
237 	TraverseData data = { path, NULL };
238 
239 	g_node_traverse (root,
240 	                 G_POST_ORDER,
241 	                 G_TRAVERSE_ALL,
242 	                 -1,
243 	                 mount_node_traverse_func,
244 	                 &data);
245 
246 	return data.node;
247 }
248 
249 static gboolean
mount_info_free(GNode * node,gpointer user_data)250 mount_info_free (GNode    *node,
251                  gpointer  user_data)
252 {
253 	MountInfo *info;
254 
255 	info = node->data;
256 
257 	if (info) {
258 		g_free (info->mount_point);
259 		g_free (info->uuid);
260 
261 		g_slice_free (MountInfo, info);
262 	}
263 
264 	return FALSE;
265 }
266 
267 static MountInfo *
mount_info_find(GNode * root,const gchar * path)268 mount_info_find (GNode       *root,
269                  const gchar *path)
270 {
271 	GNode *node;
272 
273 	node = mount_node_find (root, path);
274 	return (node) ? node->data : NULL;
275 }
276 
277 static TrackerStorageType
mount_info_get_type(MountInfo * info)278 mount_info_get_type (MountInfo *info)
279 {
280 	TrackerStorageType mount_type = 0;
281 
282 	if (info->removable) {
283 		mount_type |= TRACKER_STORAGE_REMOVABLE;
284 	}
285 
286 	if (info->optical) {
287 		mount_type |= TRACKER_STORAGE_OPTICAL;
288 	}
289 
290 	return mount_type;
291 }
292 
293 static gchar *
mount_point_normalize(const gchar * mount_point)294 mount_point_normalize (const gchar *mount_point)
295 {
296 	gchar *mp;
297 
298 	/* Normalize all mount points to have a / at the end */
299 	if (g_str_has_suffix (mount_point, G_DIR_SEPARATOR_S)) {
300 		mp = g_strdup (mount_point);
301 	} else {
302 		mp = g_strconcat (mount_point, G_DIR_SEPARATOR_S, NULL);
303 	}
304 
305 	return mp;
306 }
307 
308 static GNode *
mount_add_hierarchy(GNode * root,const gchar * uuid,const gchar * mount_point,gboolean removable,gboolean optical)309 mount_add_hierarchy (GNode       *root,
310                      const gchar *uuid,
311                      const gchar *mount_point,
312                      gboolean     removable,
313                      gboolean     optical)
314 {
315 	MountInfo *info;
316 	GNode *node;
317 	gchar *mp;
318 
319 	mp = mount_point_normalize (mount_point);
320 	node = mount_node_find (root, mp);
321 
322 	if (!node) {
323 		node = root;
324 	}
325 
326 	info = g_slice_new (MountInfo);
327 	info->mount_point = mp;
328 	info->uuid = g_strdup (uuid);
329 	info->removable = removable;
330 	info->optical = optical;
331 
332 	return g_node_append_data (node, info);
333 }
334 
335 static void
mount_add_new(TrackerStorage * storage,const gchar * uuid,const gchar * mount_point,const gchar * mount_name,gboolean removable_device,gboolean optical_disc)336 mount_add_new (TrackerStorage *storage,
337                const gchar    *uuid,
338                const gchar    *mount_point,
339                const gchar    *mount_name,
340                gboolean        removable_device,
341                gboolean        optical_disc)
342 {
343 	TrackerStoragePrivate *priv;
344 	GNode *node;
345 
346 	priv = tracker_storage_get_instance_private (storage);
347 
348 	node = mount_add_hierarchy (priv->mounts, uuid, mount_point, removable_device, optical_disc);
349 	g_hash_table_insert (priv->mounts_by_uuid, g_strdup (uuid), node);
350 
351 	g_signal_emit (storage,
352 	               signals[MOUNT_POINT_ADDED],
353 	               0,
354 	               uuid,
355 	               mount_point,
356                        mount_name,
357 	               removable_device,
358 	               optical_disc,
359 	               NULL);
360 }
361 
362 static gchar *
mount_guess_content_type(GMount * mount,gboolean * is_optical,gboolean * is_multimedia,gboolean * is_blank)363 mount_guess_content_type (GMount   *mount,
364                           gboolean *is_optical,
365                           gboolean *is_multimedia,
366                           gboolean *is_blank)
367 {
368 	gchar *content_type = NULL;
369 	gchar **guess_type;
370 
371 	*is_optical = FALSE;
372 	*is_multimedia = FALSE;
373 	*is_blank = FALSE;
374 
375 	/* This function has 2 purposes:
376 	 *
377 	 * 1. Detect if we are using optical media
378 	 * 2. Detect if we are video or music, we can't index those types
379 	 *
380 	 * We try to determine the content type because we don't want
381 	 * to store Volume information in Tracker about DVDs and media
382 	 * which has no real data for us to mine.
383 	 *
384 	 * Generally, if is_multimedia is TRUE then we end up ignoring
385 	 * the media.
386 	 */
387 	guess_type = g_mount_guess_content_type_sync (mount, TRUE, NULL, NULL);
388 
389 	if (guess_type) {
390 		gint i = 0;
391 
392 		while (!content_type && guess_type[i]) {
393 			if (!g_strcmp0 (guess_type[i], "x-content/image-picturecd")) {
394 				/* Images */
395 				content_type = g_strdup (guess_type[i]);
396 			} else if (!g_strcmp0 (guess_type[i], "x-content/video-bluray") ||
397 			           !g_strcmp0 (guess_type[i], "x-content/video-dvd") ||
398 			           !g_strcmp0 (guess_type[i], "x-content/video-hddvd") ||
399 			           !g_strcmp0 (guess_type[i], "x-content/video-svcd") ||
400 			           !g_strcmp0 (guess_type[i], "x-content/video-vcd")) {
401 				/* Videos */
402 				*is_multimedia = TRUE;
403 				content_type = g_strdup (guess_type[i]);
404 			} else if (!g_strcmp0 (guess_type[i], "x-content/audio-cdda") ||
405 			           !g_strcmp0 (guess_type[i], "x-content/audio-dvd") ||
406 			           !g_strcmp0 (guess_type[i], "x-content/audio-player")) {
407 				/* Audios */
408 				*is_multimedia = TRUE;
409 				content_type = g_strdup (guess_type[i]);
410 			} else if (!g_strcmp0 (guess_type[i], "x-content/blank-bd") ||
411 			           !g_strcmp0 (guess_type[i], "x-content/blank-cd") ||
412 			           !g_strcmp0 (guess_type[i], "x-content/blank-dvd") ||
413 			           !g_strcmp0 (guess_type[i], "x-content/blank-hddvd")) {
414 				/* Blank */
415 				*is_blank = TRUE;
416 				content_type = g_strdup (guess_type[i]);
417 			} else if (!g_strcmp0 (guess_type[i], "x-content/software") ||
418 			           !g_strcmp0 (guess_type[i], "x-content/unix-software") ||
419 			           !g_strcmp0 (guess_type[i], "x-content/win32-software")) {
420 				/* NOTE: This one is a guess, can we
421 				 * have this content type on
422 				 * none-optical mount points?
423 				 */
424 				content_type = g_strdup (guess_type[i]);
425 			} else {
426 				/* else, keep on with the next guess, if any */
427 				i++;
428 			}
429 		}
430 
431 		/* If we didn't have an exact match on possible guessed content types,
432 		 *  then use the first one returned (best guess always first) if any */
433 		if (!content_type && guess_type[0]) {
434 			content_type = g_strdup (guess_type[0]);
435 		}
436 
437 		g_strfreev (guess_type);
438 	}
439 
440 	if (content_type) {
441 		if (strstr (content_type, "vcd") ||
442 		    strstr (content_type, "cdda") ||
443 		    strstr (content_type, "dvd") ||
444 		    strstr (content_type, "bluray")) {
445 			*is_optical = TRUE;
446 		}
447 	} else {
448 		GUnixMountEntry *entry;
449 		gchar *mount_path;
450 		GFile *mount_root;
451 
452 		/* No content type was guessed, try to find out
453 		 * at least whether it's an optical media or not
454 		 */
455 		mount_root = g_mount_get_root (mount);
456 		mount_path = g_file_get_path (mount_root);
457 
458 		/* FIXME: Try to assume we have a unix mount :(
459 		 * EEK, once in a while, I have to write crack, oh well
460 		 */
461 		if (mount_path &&
462 		    (entry = g_unix_mount_at (mount_path, NULL)) != NULL) {
463 			const gchar *filesystem_type;
464 			gchar *device_path = NULL;
465 			GVolume *volume;
466 
467 			volume = g_mount_get_volume (mount);
468 			filesystem_type = g_unix_mount_get_fs_type (entry);
469 			g_debug ("  Using filesystem type:'%s'",
470 			         filesystem_type);
471 
472 			/* Volume may be NULL */
473 			if (volume) {
474 				device_path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
475 				g_debug ("  Using device path:'%s'",
476 				         device_path);
477 				g_object_unref (volume);
478 			}
479 
480 			/* NOTE: This code was taken from guess_mount_type()
481 			 * in GIO's gunixmounts.c and adapted purely for
482 			 * guessing optical media. We don't use the guessing
483 			 * code for other types such as MEMSTICKS, ZIPs,
484 			 * IPODs, etc.
485 			 *
486 			 * This code may need updating over time since it is
487 			 * very situational depending on how distributions
488 			 * mount their devices and how devices are named in
489 			 * /dev.
490 			 */
491 			if (strcmp (filesystem_type, "udf") == 0 ||
492 			    strcmp (filesystem_type, "iso9660") == 0 ||
493 			    strcmp (filesystem_type, "cd9660") == 0 ||
494 			    (device_path &&
495 			     (g_str_has_prefix (device_path, "/dev/cdrom") ||
496 			      g_str_has_prefix (device_path, "/dev/acd") ||
497 			      g_str_has_prefix (device_path, "/dev/cd")))) {
498 				*is_optical = TRUE;
499 			} else if (device_path &&
500 			           g_str_has_prefix (device_path, "/vol/")) {
501 				const gchar *name;
502 
503 				name = mount_path + strlen ("/");
504 
505 				if (g_str_has_prefix (name, "cdrom")) {
506 					*is_optical = TRUE;
507 				}
508 			} else {
509 				gchar *basename = g_path_get_basename (mount_path);
510 
511 				if (g_str_has_prefix (basename, "cdr") ||
512 				    g_str_has_prefix (basename, "cdwriter") ||
513 				    g_str_has_prefix (basename, "burn") ||
514 				    g_str_has_prefix (basename, "dvdr")) {
515 					*is_optical = TRUE;
516 				}
517 
518 				g_free (basename);
519 			}
520 
521 			g_free (device_path);
522 			g_free (mount_path);
523 			g_unix_mount_free (entry);
524 		} else {
525 			g_debug ("  No GUnixMountEntry found, needed for detecting if optical media... :(");
526 			g_free (mount_path);
527 		}
528 
529 		g_object_unref (mount_root);
530 	}
531 
532 	return content_type;
533 }
534 
535 static void
mount_add(TrackerStorage * storage,GMount * mount)536 mount_add (TrackerStorage *storage,
537            GMount         *mount)
538 {
539 	TrackerStoragePrivate *priv;
540 	GFile *root;
541 	GVolume *volume;
542 	gchar *mount_name, *mount_path, *uuid;
543 	gboolean is_optical = FALSE;
544 	gboolean is_removable = FALSE;
545 
546 	/* Get mount name */
547 	mount_name = g_mount_get_name (mount);
548 
549 	/* Get root path of the mount */
550 	root = g_mount_get_root (mount);
551 	if (!g_file_is_native (root)) {
552 		gchar *uri = g_file_get_uri (root);
553 
554 		g_debug ("Ignoring mount '%s', URI '%s' is not native",
555 		         mount_name, uri);
556 		g_free (uri);
557 		return;
558 	}
559 
560 	mount_path = g_file_get_path (root);
561 	g_debug ("Found '%s' mounted on path '%s'",
562 	         mount_name,
563 	         mount_path);
564 
565 	/* Do not process shadowed mounts! */
566 	if (g_mount_is_shadowed (mount)) {
567 		g_debug ("  Skipping shadowed mount '%s'", mount_name);
568 		g_object_unref (root);
569 		g_free (mount_path);
570 		g_free (mount_name);
571 		return;
572 	}
573 
574 	priv = tracker_storage_get_instance_private (storage);
575 
576 	/* fstab partitions may not have corresponding
577 	 * GVolumes, so volume may be NULL */
578 	volume = g_mount_get_volume (mount);
579 	if (volume) {
580 		/* GMount with GVolume */
581 
582 		/* Try to get UUID from the Volume.
583 		 * Note that g_volume_get_uuid() is NOT equivalent */
584 		uuid = g_volume_get_identifier (volume,
585 		                                G_VOLUME_IDENTIFIER_KIND_UUID);
586 		if (!uuid) {
587 			gchar *content_type;
588 			gboolean is_multimedia;
589 			gboolean is_blank;
590 
591 			/* Optical discs usually won't have UUID in the GVolume */
592 			content_type = mount_guess_content_type (mount, &is_optical, &is_multimedia, &is_blank);
593 			is_removable = TRUE;
594 
595 			/* We don't index content which is video, music or blank */
596 			if (!is_multimedia && !is_blank) {
597 				uuid = g_compute_checksum_for_string (G_CHECKSUM_MD5,
598 				                                      mount_name,
599 				                                      -1);
600 				g_debug ("  No UUID, generated:'%s' (based on mount name)", uuid);
601 				g_debug ("  Assuming GVolume has removable media, if wrong report a bug! "
602 				         "content type is '%s'",
603 				         content_type);
604 			} else {
605 				g_debug ("  Being ignored because mount with volume is music/video/blank "
606 				         "(content type:%s, optical:%s, multimedia:%s, blank:%s)",
607 				         content_type,
608 				         is_optical ? "yes" : "no",
609 				         is_multimedia ? "yes" : "no",
610 				         is_blank ? "yes" : "no");
611 			}
612 
613 			g_free (content_type);
614 		} else {
615 			/* Any other removable media will have UUID in the
616 			 * GVolume. Note that this also may include some
617 			 * partitions in the machine which have GVolumes
618 			 * associated to the GMounts. We also check a drive
619 			 * exists to be sure the device is local. */
620 			GDrive *drive;
621 
622 			drive = g_volume_get_drive (volume);
623 
624 			if (drive) {
625 				/* We can't mount/unmount system volumes, so tag
626 				 * them as non removable. */
627 				is_removable = g_volume_can_mount (volume);
628 				g_debug ("  Found mount with volume and drive which %s be mounted: "
629 				         "Assuming it's %s removable, if wrong report a bug!",
630 				         is_removable ? "can" : "cannot",
631 				         is_removable ? "" : "not");
632 				g_object_unref (drive);
633 			} else {
634 				/* Note: not sure when this can happen... */
635 				g_debug ("  Mount with volume but no drive, "
636 				         "assuming not a removable device, "
637 				         "if wrong report a bug!");
638 				is_removable = FALSE;
639 			}
640 		}
641 
642 		g_object_unref (volume);
643 	} else {
644 		/* GMount without GVolume.
645 		 * Note: Never found a case where this g_mount_get_uuid() returns
646 		 * non-NULL... :-) */
647 		uuid = g_mount_get_uuid (mount);
648 		if (!uuid) {
649 			if (mount_path) {
650 				gchar *content_type;
651 				gboolean is_multimedia;
652 				gboolean is_blank;
653 
654 				content_type = mount_guess_content_type (mount, &is_optical, &is_multimedia, &is_blank);
655 
656 				/* Note: for GMounts without GVolume, is_blank should NOT be considered,
657 				 * as it may give unwanted results... */
658 				if (!is_multimedia) {
659 					uuid = g_compute_checksum_for_string (G_CHECKSUM_MD5,
660 					                                      mount_path,
661 					                                      -1);
662 					g_debug ("  No UUID, generated:'%s' (based on mount path)", uuid);
663 				} else {
664 					g_debug ("  Being ignored because mount is music/video "
665 					         "(content type:%s, optical:%s, multimedia:%s)",
666 					         content_type,
667 					         is_optical ? "yes" : "no",
668 					         is_multimedia ? "yes" : "no");
669 				}
670 
671 				g_free (content_type);
672 			} else {
673 				g_debug ("  Being ignored because mount has no GVolume (i.e. not user mountable) "
674 				         "and has no mount root path available");
675 			}
676 		}
677 	}
678 
679 	/* If we got something to be used as UUID, then add the mount
680 	 * to the TrackerStorage */
681 	if (uuid && mount_path && !g_hash_table_lookup (priv->mounts_by_uuid, uuid)) {
682 		g_debug ("  Adding mount point with UUID: '%s', removable: %s, optical: %s, path: '%s'",
683 		         uuid,
684 		         is_removable ? "yes" : "no",
685 		         is_optical ? "yes" : "no",
686 		         mount_path);
687 		mount_add_new (storage, uuid, mount_path, mount_name, is_removable, is_optical);
688 	} else {
689 		g_debug ("  Skipping mount point with UUID: '%s', path: '%s', already managed: '%s'",
690 		         uuid ? uuid : "none",
691 		         mount_path ? mount_path : "none",
692 		         (uuid && g_hash_table_lookup (priv->mounts_by_uuid, uuid)) ? "yes" : "no");
693 	}
694 
695 	g_free (mount_name);
696 	g_free (mount_path);
697 	g_free (uuid);
698 	g_object_unref (root);
699 }
700 
701 static gboolean
mounts_setup(TrackerStorage * storage)702 mounts_setup (TrackerStorage *storage)
703 {
704 	TrackerStoragePrivate *priv;
705 	GList *mounts, *lm;
706 
707 	priv = tracker_storage_get_instance_private (storage);
708 
709 	mounts = g_volume_monitor_get_mounts (priv->volume_monitor);
710 
711 	if (!mounts) {
712 		g_message ("No mounts found to iterate");
713 		return TRUE;
714 	}
715 
716 	/* Iterate over all available mounts and add them.
717 	 * Note that GVolumeMonitor shows only those mounts which are
718 	 * actually mounted. */
719 	for (lm = mounts; lm; lm = g_list_next (lm)) {
720 		mount_add (storage, lm->data);
721 		g_object_unref (lm->data);
722 	}
723 
724 	g_list_free (mounts);
725 
726 	return TRUE;
727 }
728 
729 static void
mount_added_cb(GVolumeMonitor * monitor,GMount * mount,gpointer user_data)730 mount_added_cb (GVolumeMonitor *monitor,
731                 GMount         *mount,
732                 gpointer        user_data)
733 {
734 	mount_add (user_data, mount);
735 }
736 
737 static void
mount_remove(TrackerStorage * storage,GMount * mount)738 mount_remove (TrackerStorage *storage,
739               GMount         *mount)
740 {
741 	TrackerStoragePrivate *priv;
742 	MountInfo *info;
743 	GNode *node;
744 	GFile *file;
745 	gchar *name;
746 	gchar *mount_point;
747 	gchar *mp;
748 
749 	priv = tracker_storage_get_instance_private (storage);
750 
751 	file = g_mount_get_root (mount);
752 	mount_point = g_file_get_path (file);
753 	name = g_mount_get_name (mount);
754 
755 	mp = mount_point_normalize (mount_point);
756 	node = mount_node_find (priv->mounts, mp);
757 	g_free (mp);
758 
759 	if (node) {
760 		info = node->data;
761 
762 		g_message ("Mount:'%s' with UUID:'%s' now unmounted from:'%s'",
763 		           name,
764 		           info->uuid,
765 		           mount_point);
766 
767 		g_signal_emit (storage, signals[MOUNT_POINT_REMOVED], 0, info->uuid, mount_point, NULL);
768 
769 		g_hash_table_remove (priv->mounts_by_uuid, info->uuid);
770 		mount_node_free (node);
771 	} else {
772 		g_message ("Mount:'%s' now unmounted from:'%s' (was not tracked)",
773 		           name,
774 		           mount_point);
775 	}
776 
777 	g_free (name);
778 	g_free (mount_point);
779 	g_object_unref (file);
780 }
781 
782 static void
mount_removed_cb(GVolumeMonitor * monitor,GMount * mount,gpointer user_data)783 mount_removed_cb (GVolumeMonitor *monitor,
784                   GMount         *mount,
785                   gpointer        user_data)
786 {
787 	TrackerStorage *storage;
788 	TrackerStoragePrivate *priv;
789 
790 	storage = user_data;
791 	priv = tracker_storage_get_instance_private (storage);
792 
793 	mount_remove (storage, mount);
794 
795 	/* Unmount suceeded, remove the pending check */
796 	g_hash_table_remove (priv->unmount_watchdogs, mount);
797 }
798 
799 static gboolean
unmount_failed_cb(gpointer user_data)800 unmount_failed_cb (gpointer user_data)
801 {
802 	UnmountCheckData *data = user_data;
803 	TrackerStoragePrivate *priv;
804 
805 	/* If this timeout gets to be executed, this is due
806 	 * to a pre-unmount signal with no corresponding
807 	 * unmount in a timely fashion, we assume this is
808 	 * due to an error, and add back the still mounted
809 	 * path.
810 	 */
811 	priv = tracker_storage_get_instance_private (data->storage);
812 
813 	g_warning ("Unmount operation failed, adding back mount point...");
814 
815 	mount_add (data->storage, data->mount);
816 
817 	g_hash_table_remove (priv->unmount_watchdogs, data->mount);
818 	return FALSE;
819 }
820 
821 static void
mount_pre_removed_cb(GVolumeMonitor * monitor,GMount * mount,gpointer user_data)822 mount_pre_removed_cb (GVolumeMonitor *monitor,
823                       GMount         *mount,
824                       gpointer        user_data)
825 {
826 	TrackerStorage *storage;
827 	TrackerStoragePrivate *priv;
828 	UnmountCheckData *data;
829 	guint id;
830 
831 	storage = user_data;
832 	priv = tracker_storage_get_instance_private (storage);
833 
834 	mount_remove (storage, mount);
835 
836 	/* Add check for failed unmounts */
837 	data = g_new (UnmountCheckData, 1);
838 	data->storage = storage;
839 	data->mount = mount;
840 
841 	id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT_IDLE + 10, 3,
842 	                                 unmount_failed_cb,
843 	                                 data, (GDestroyNotify) g_free);
844 	g_hash_table_insert (priv->unmount_watchdogs, data->mount,
845 	                     GUINT_TO_POINTER (id));
846 }
847 
848 /**
849  * tracker_storage_new:
850  *
851  * Creates a new instance of #TrackerStorage.
852  *
853  * Returns: The newly created #TrackerStorage.
854  *
855  * Since: 0.8
856  **/
857 TrackerStorage *
tracker_storage_new(void)858 tracker_storage_new (void)
859 {
860 	return g_object_new (TRACKER_TYPE_STORAGE, NULL);
861 }
862 
863 static void
get_mount_point_by_uuid_foreach(gpointer key,gpointer value,gpointer user_data)864 get_mount_point_by_uuid_foreach (gpointer key,
865                                  gpointer value,
866                                  gpointer user_data)
867 {
868 	GetRoots *gr;
869 	GNode *node;
870 	MountInfo *info;
871 	TrackerStorageType mount_type;
872 
873 	gr = user_data;
874 	node = value;
875 	info = node->data;
876 	mount_type = mount_info_get_type (info);
877 
878 	/* is mount of the type we're looking for? */
879 	if ((gr->exact_match && mount_type == gr->type) ||
880 	    (!gr->exact_match && (mount_type & gr->type))) {
881 		gchar *normalized_mount_point;
882 		gint len;
883 
884 		normalized_mount_point = g_strdup (info->mount_point);
885 		len = strlen (normalized_mount_point);
886 
887 		/* Don't include trailing slashes */
888 		if (len > 2 && normalized_mount_point[len - 1] == G_DIR_SEPARATOR) {
889 			normalized_mount_point[len - 1] = '\0';
890 		}
891 
892 		gr->roots = g_slist_prepend (gr->roots, normalized_mount_point);
893 	}
894 }
895 
896 /**
897  * tracker_storage_get_device_roots:
898  * @storage: A #TrackerStorage
899  * @type: A #TrackerStorageType
900  * @exact_match: if all devices should exactly match the types
901  *
902  * Returns: (transfer full) (element-type utf8): a #GSList of strings
903  * containing the root directories for devices with @type based on
904  * @exact_match. Each element must be freed using g_free() and the
905  * list itself through g_slist_free().
906  *
907  * Since: 0.8
908  **/
909 GSList *
tracker_storage_get_device_roots(TrackerStorage * storage,TrackerStorageType type,gboolean exact_match)910 tracker_storage_get_device_roots (TrackerStorage     *storage,
911                                   TrackerStorageType  type,
912                                   gboolean            exact_match)
913 {
914 	TrackerStoragePrivate *priv;
915 	GetRoots gr;
916 
917 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), NULL);
918 
919 	priv = tracker_storage_get_instance_private (storage);
920 
921 	gr.roots = NULL;
922 	gr.type = type;
923 	gr.exact_match = exact_match;
924 
925 	g_hash_table_foreach (priv->mounts_by_uuid,
926 	                      get_mount_point_by_uuid_foreach,
927 	                      &gr);
928 
929 	return gr.roots;
930 }
931 
932 /**
933  * tracker_storage_get_device_uuids:
934  * @storage: A #TrackerStorage
935  * @type: A #TrackerStorageType
936  * @exact_match: if all devices should exactly match the types
937  *
938  * Returns: (transfer full) (element-type utf8): a #GSList of
939  * strings containing the UUID for devices with @type based
940  * on @exact_match. Each element must be freed using g_free()
941  * and the list itself through g_slist_free().
942  *
943  * Since: 0.8
944  **/
945 GSList *
tracker_storage_get_device_uuids(TrackerStorage * storage,TrackerStorageType type,gboolean exact_match)946 tracker_storage_get_device_uuids (TrackerStorage     *storage,
947                                   TrackerStorageType  type,
948                                   gboolean            exact_match)
949 {
950 	TrackerStoragePrivate *priv;
951 	GHashTableIter iter;
952 	gpointer key, value;
953 	GSList *uuids;
954 
955 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), NULL);
956 
957 	priv = tracker_storage_get_instance_private (storage);
958 
959 	uuids = NULL;
960 
961 	g_hash_table_iter_init (&iter, priv->mounts_by_uuid);
962 
963 	while (g_hash_table_iter_next (&iter, &key, &value)) {
964 		const gchar *uuid;
965 		GNode *node;
966 		MountInfo *info;
967 		TrackerStorageType mount_type;
968 
969 		uuid = key;
970 		node = value;
971 		info = node->data;
972 
973 		mount_type = mount_info_get_type (info);
974 
975 		/* is mount of the type we're looking for? */
976 		if ((exact_match && mount_type == type) ||
977 		    (!exact_match && (mount_type & type))) {
978 			uuids = g_slist_prepend (uuids, g_strdup (uuid));
979 		}
980 	}
981 
982 	return uuids;
983 }
984 
985 /**
986  * tracker_storage_get_mount_point_for_uuid:
987  * @storage: A #TrackerStorage
988  * @uuid: A string pointer to the UUID for the %GVolume.
989  *
990  * Returns: The mount point for @uuid, this should not be freed.
991  *
992  * Since: 0.8
993  **/
994 const gchar *
tracker_storage_get_mount_point_for_uuid(TrackerStorage * storage,const gchar * uuid)995 tracker_storage_get_mount_point_for_uuid (TrackerStorage *storage,
996                                           const gchar    *uuid)
997 {
998 	TrackerStoragePrivate *priv;
999 	GNode *node;
1000 	MountInfo *info;
1001 
1002 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), NULL);
1003 	g_return_val_if_fail (uuid != NULL, NULL);
1004 
1005 	priv = tracker_storage_get_instance_private (storage);
1006 
1007 	node = g_hash_table_lookup (priv->mounts_by_uuid, uuid);
1008 
1009 	if (!node) {
1010 		return NULL;
1011 	}
1012 
1013 	info = node->data;
1014 
1015 	return info->mount_point;
1016 }
1017 
1018 /**
1019  * tracker_storage_get_type_for_uuid:
1020  * @storage: A #TrackerStorage
1021  * @uuid: A string pointer to the UUID for the %GVolume.
1022  *
1023  * Returns: The type flags for @uuid.
1024  *
1025  * Since: 0.10
1026  **/
1027 TrackerStorageType
tracker_storage_get_type_for_uuid(TrackerStorage * storage,const gchar * uuid)1028 tracker_storage_get_type_for_uuid (TrackerStorage     *storage,
1029                                    const gchar        *uuid)
1030 {
1031 	TrackerStoragePrivate *priv;
1032 	GNode *node;
1033 	TrackerStorageType type = 0;
1034 
1035 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), 0);
1036 	g_return_val_if_fail (uuid != NULL, 0);
1037 
1038 	priv = tracker_storage_get_instance_private (storage);
1039 
1040 	node = g_hash_table_lookup (priv->mounts_by_uuid, uuid);
1041 
1042 	if (node) {
1043 		MountInfo *info;
1044 
1045 		info = node->data;
1046 
1047 		if (info->removable) {
1048 			type |= TRACKER_STORAGE_REMOVABLE;
1049 		}
1050 		if (info->optical) {
1051 			type |= TRACKER_STORAGE_OPTICAL;
1052 		}
1053 	}
1054 
1055 	return type;
1056 }
1057 
1058 /**
1059  * tracker_storage_get_uuid_for_file:
1060  * @storage: A #TrackerStorage
1061  * @file: a file
1062  *
1063  * Returns the UUID of the removable device for @file
1064  *
1065  * Returns: Returns the UUID of the removable device for @file, this
1066  * should not be freed.
1067  *
1068  * Since: 0.8
1069  **/
1070 const gchar *
tracker_storage_get_uuid_for_file(TrackerStorage * storage,GFile * file)1071 tracker_storage_get_uuid_for_file (TrackerStorage *storage,
1072                                    GFile          *file)
1073 {
1074 	TrackerStoragePrivate *priv;
1075 	gchar *path;
1076 	MountInfo *info;
1077 
1078 	g_return_val_if_fail (TRACKER_IS_STORAGE (storage), FALSE);
1079 
1080 	path = g_file_get_path (file);
1081 
1082 	if (!path) {
1083 		return NULL;
1084 	}
1085 
1086 	/* Normalize all paths to have a / at the end */
1087 	if (!g_str_has_suffix (path, G_DIR_SEPARATOR_S)) {
1088 		gchar *norm_path;
1089 
1090 		norm_path = g_strconcat (path, G_DIR_SEPARATOR_S, NULL);
1091 		g_free (path);
1092 		path = norm_path;
1093 	}
1094 
1095 	priv = tracker_storage_get_instance_private (storage);
1096 
1097 	info = mount_info_find (priv->mounts, path);
1098 
1099 	if (!info) {
1100 		g_free (path);
1101 		return NULL;
1102 	}
1103 
1104 	/* g_debug ("Mount for path '%s' is '%s' (UUID:'%s')", */
1105 	/*          path, info->mount_point, info->uuid); */
1106 
1107 	g_free (path);
1108 
1109 	return info->uuid;
1110 }
1111 
1112