1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  * vim: set et sw=8 ts=8:
3  *
4  * Copyright (c) 2008, Novell, Inc.
5  * Copyright (C) 2012-2021 MATE Developers
6  *
7  * Authors: Vincent Untz <vuntz@gnome.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
22  *
23  */
24 
25 /* gcc -DHAVE_LIBNOTIFY -DTEST -Wall `pkg-config --cflags --libs gobject-2.0 gio-unix-2.0 glib-2.0 gtk+-2.0 libnotify` -o msd-disk-space-test msd-disk-space.c */
26 
27 #include "config.h"
28 
29 #include <sys/statvfs.h>
30 #include <time.h>
31 #include <unistd.h>
32 
33 #include <glib.h>
34 #include <glib/gi18n.h>
35 #include <glib-object.h>
36 #include <gio/gunixmounts.h>
37 #include <gio/gio.h>
38 #include <gtk/gtk.h>
39 
40 #include "msd-disk-space.h"
41 #include "msd-ldsm-dialog.h"
42 #include "msd-ldsm-trash-empty.h"
43 
44 
45 #define GIGABYTE                   1024 * 1024 * 1024
46 
47 #define CHECK_EVERY_X_SECONDS      60
48 
49 #define DISK_SPACE_ANALYZER        "mate-disk-usage-analyzer"
50 
51 #define SETTINGS_HOUSEKEEPING_SCHEMA      "org.mate.SettingsDaemon.plugins.housekeeping"
52 #define SETTINGS_FREE_PC_NOTIFY_KEY       "free-percent-notify"
53 #define SETTINGS_FREE_PC_NOTIFY_AGAIN_KEY "free-percent-notify-again"
54 #define SETTINGS_FREE_SIZE_NO_NOTIFY      "free-size-gb-no-notify"
55 #define SETTINGS_MIN_NOTIFY_PERIOD        "min-notify-period"
56 #define SETTINGS_IGNORE_PATHS             "ignore-paths"
57 
58 typedef struct
59 {
60         GUnixMountEntry *mount;
61         struct statvfs buf;
62         time_t notify_time;
63 } LdsmMountInfo;
64 
65 static GHashTable        *ldsm_notified_hash = NULL;
66 static unsigned int       ldsm_timeout_id = 0;
67 static GUnixMountMonitor *ldsm_monitor = NULL;
68 static double             free_percent_notify = 0.05;
69 static double             free_percent_notify_again = 0.01;
70 static unsigned int       free_size_gb_no_notify = 2;
71 static unsigned int       min_notify_period = 10;
72 static GSList            *ignore_paths = NULL;
73 static GSettings         *settings = NULL;
74 static MsdLdsmDialog     *dialog = NULL;
75 static guint64           *time_read;
76 
77 static gchar*
ldsm_get_fs_id_for_path(const gchar * path)78 ldsm_get_fs_id_for_path (const gchar *path)
79 {
80         GFile *file;
81         GFileInfo *fileinfo;
82         gchar *attr_id_fs;
83 
84         file = g_file_new_for_path (path);
85         fileinfo = g_file_query_info (file, G_FILE_ATTRIBUTE_ID_FILESYSTEM, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
86         if (fileinfo) {
87                 attr_id_fs = g_strdup (g_file_info_get_attribute_string (fileinfo, G_FILE_ATTRIBUTE_ID_FILESYSTEM));
88                 g_object_unref (fileinfo);
89         } else {
90                 attr_id_fs = NULL;
91         }
92 
93         g_object_unref (file);
94 
95         return attr_id_fs;
96 }
97 
98 static gboolean
ldsm_mount_has_trash(LdsmMountInfo * mount)99 ldsm_mount_has_trash (LdsmMountInfo *mount)
100 {
101         const gchar *user_data_dir;
102         gchar *user_data_attr_id_fs;
103         gchar *path_attr_id_fs;
104         gboolean mount_uses_user_trash = FALSE;
105         gchar *trash_files_dir;
106         gboolean has_trash = FALSE;
107         GDir *dir;
108         const gchar *path;
109 
110         user_data_dir = g_get_user_data_dir ();
111         user_data_attr_id_fs = ldsm_get_fs_id_for_path (user_data_dir);
112 
113         path = g_unix_mount_get_mount_path (mount->mount);
114         path_attr_id_fs = ldsm_get_fs_id_for_path (path);
115 
116         if (g_strcmp0 (user_data_attr_id_fs, path_attr_id_fs) == 0) {
117                 /* The volume that is low on space is on the same volume as our home
118                  * directory. This means the trash is at $XDG_DATA_HOME/Trash,
119                  * not at the root of the volume which is full.
120                  */
121                 mount_uses_user_trash = TRUE;
122         }
123 
124         g_free (user_data_attr_id_fs);
125         g_free (path_attr_id_fs);
126 
127         /* I can't think of a better way to find out if a volume has any trash. Any suggestions? */
128         if (mount_uses_user_trash) {
129                 trash_files_dir = g_build_filename (g_get_user_data_dir (), "Trash", "files", NULL);
130         } else {
131                 gchar *uid;
132 
133                 uid = g_strdup_printf ("%d", getuid ());
134                 trash_files_dir = g_build_filename (path, ".Trash", uid, "files", NULL);
135                 if (!g_file_test (trash_files_dir, G_FILE_TEST_IS_DIR)) {
136                         gchar *trash_dir;
137 
138                         g_free (trash_files_dir);
139                         trash_dir = g_strdup_printf (".Trash-%s", uid);
140                         trash_files_dir = g_build_filename (path, trash_dir, "files", NULL);
141                         g_free (trash_dir);
142                         if (!g_file_test (trash_files_dir, G_FILE_TEST_IS_DIR)) {
143                                 g_free (trash_files_dir);
144                                 g_free (uid);
145                                 return has_trash;
146                         }
147                 }
148                 g_free (uid);
149         }
150 
151         dir = g_dir_open (trash_files_dir, 0, NULL);
152         if (dir) {
153                 if (g_dir_read_name (dir))
154                         has_trash = TRUE;
155                 g_dir_close (dir);
156         }
157 
158         g_free (trash_files_dir);
159 
160         return has_trash;
161 }
162 
163 static void
ldsm_analyze_path(const gchar * path)164 ldsm_analyze_path (const gchar *path)
165 {
166         const gchar *argv[] = { DISK_SPACE_ANALYZER, path, NULL };
167 
168         g_spawn_async (NULL, (gchar **) argv, NULL, G_SPAWN_SEARCH_PATH,
169                         NULL, NULL, NULL, NULL);
170 }
171 
172 static gboolean
ldsm_notify_for_mount(LdsmMountInfo * mount,gboolean multiple_volumes,gboolean other_usable_volumes)173 ldsm_notify_for_mount (LdsmMountInfo *mount,
174                        gboolean       multiple_volumes,
175                        gboolean       other_usable_volumes)
176 {
177         gchar  *name, *program;
178         gint64 free_space;
179         gint response;
180         gboolean has_trash;
181         gboolean has_disk_analyzer;
182         gboolean retval = TRUE;
183         gchar *path;
184 
185         /* Don't show a dialog if one is already displayed */
186         if (dialog)
187                 return retval;
188 
189         name = g_unix_mount_guess_name (mount->mount);
190         free_space = (gint64) mount->buf.f_frsize * (gint64) mount->buf.f_bavail;
191         has_trash = ldsm_mount_has_trash (mount);
192         path = g_strdup (g_unix_mount_get_mount_path (mount->mount));
193 
194         program = g_find_program_in_path (DISK_SPACE_ANALYZER);
195         has_disk_analyzer = (program != NULL);
196         g_free (program);
197 
198         dialog = msd_ldsm_dialog_new (other_usable_volumes,
199                                       multiple_volumes,
200                                       has_disk_analyzer,
201                                       has_trash,
202                                       free_space,
203                                       name,
204                                       path);
205 
206         g_free (name);
207 
208         g_object_ref (G_OBJECT (dialog));
209         response = gtk_dialog_run (GTK_DIALOG (dialog));
210 
211 	gtk_widget_destroy (GTK_WIDGET (dialog));
212         dialog = NULL;
213 
214         switch (response) {
215         case GTK_RESPONSE_CANCEL:
216                 retval = FALSE;
217                 break;
218         case MSD_LDSM_DIALOG_RESPONSE_ANALYZE:
219                 retval = FALSE;
220                 ldsm_analyze_path (path);
221                 break;
222         case MSD_LDSM_DIALOG_RESPONSE_EMPTY_TRASH:
223                 retval = TRUE;
224                 msd_ldsm_trash_empty ();
225                 break;
226         case GTK_RESPONSE_NONE:
227         case GTK_RESPONSE_DELETE_EVENT:
228                 retval = TRUE;
229                 break;
230         default:
231                 g_assert_not_reached ();
232         }
233 
234         g_free (path);
235 
236         return retval;
237 }
238 
239 static gboolean
ldsm_mount_has_space(LdsmMountInfo * mount)240 ldsm_mount_has_space (LdsmMountInfo *mount)
241 {
242         gdouble free_space;
243 
244         free_space = (double) mount->buf.f_bavail / (double) mount->buf.f_blocks;
245         /* enough free space, nothing to do */
246         if (free_space > free_percent_notify)
247                 return TRUE;
248 
249         if (((gint64) mount->buf.f_frsize * (gint64) mount->buf.f_bavail) > ((gint64) free_size_gb_no_notify * GIGABYTE))
250                 return TRUE;
251 
252         /* If we got here, then this volume is low on space */
253         return FALSE;
254 }
255 
256 static gboolean
ldsm_mount_is_virtual(LdsmMountInfo * mount)257 ldsm_mount_is_virtual (LdsmMountInfo *mount)
258 {
259         if (mount->buf.f_blocks == 0) {
260                 /* Filesystems with zero blocks are virtual */
261                 return TRUE;
262         }
263 
264         return FALSE;
265 }
266 
267 static gint
ldsm_ignore_path_compare(gconstpointer a,gconstpointer b)268 ldsm_ignore_path_compare (gconstpointer a,
269                           gconstpointer b)
270 {
271         return g_strcmp0 ((const gchar *)a, (const gchar *)b);
272 }
273 
274 static gboolean
ldsm_mount_is_user_ignore(const gchar * path)275 ldsm_mount_is_user_ignore (const gchar *path)
276 {
277         if (g_slist_find_custom (ignore_paths, path, (GCompareFunc) ldsm_ignore_path_compare) != NULL)
278                 return TRUE;
279         else
280                 return FALSE;
281 }
282 
283 
284 static gboolean
is_in(const gchar * value,const gchar * set[])285 is_in (const gchar *value, const gchar *set[])
286 {
287         int i;
288         for (i = 0; set[i] != NULL; i++)
289         {
290               if (strcmp (set[i], value) == 0)
291                 return TRUE;
292         }
293         return FALSE;
294 }
295 
296 static gboolean
ldsm_mount_should_ignore(GUnixMountEntry * mount)297 ldsm_mount_should_ignore (GUnixMountEntry *mount)
298 {
299         const gchar *fs, *device, *path;
300 
301         path = g_unix_mount_get_mount_path (mount);
302         if (ldsm_mount_is_user_ignore (path))
303                 return TRUE;
304 
305         /* This is borrowed from GLib and used as a way to determine
306          * which mounts we should ignore by default. GLib doesn't
307          * expose this in a way that allows it to be used for this
308          * purpose
309          */
310 
311         /* We also ignore network filesystems */
312 
313         const gchar *ignore_fs[] = {
314                 "adfs",
315                 "afs",
316                 "auto",
317                 "autofs",
318                 "autofs4",
319                 "cifs",
320                 "cxfs",
321                 "devfs",
322                 "devpts",
323                 "ecryptfs",
324                 "fdescfs",
325                 "gfs",
326                 "gfs2",
327                 "kernfs",
328                 "linprocfs",
329                 "linsysfs",
330                 "lustre",
331                 "lustre_lite",
332                 "ncpfs",
333                 "nfs",
334                 "nfs4",
335                 "nfsd",
336                 "ocfs2",
337                 "proc",
338                 "procfs",
339                 "ptyfs",
340                 "rpc_pipefs",
341                 "selinuxfs",
342                 "smbfs",
343                 "sysfs",
344                 "tmpfs",
345                 "usbfs",
346                 "zfs",
347                 NULL
348         };
349         const gchar *ignore_devices[] = {
350                 "none",
351                 "sunrpc",
352                 "devpts",
353                 "nfsd",
354                 "/dev/loop",
355                 "/dev/vn",
356                 NULL
357         };
358 
359         fs = g_unix_mount_get_fs_type (mount);
360         device = g_unix_mount_get_device_path (mount);
361 
362         if (is_in (fs, ignore_fs))
363                 return TRUE;
364 
365         if (is_in (device, ignore_devices))
366                 return TRUE;
367 
368         return FALSE;
369 }
370 
371 static void
ldsm_free_mount_info(gpointer data)372 ldsm_free_mount_info (gpointer data)
373 {
374         LdsmMountInfo *mount = data;
375 
376         g_return_if_fail (mount != NULL);
377 
378         g_unix_mount_free (mount->mount);
379         g_free (mount);
380 }
381 
382 static void
ldsm_maybe_warn_mounts(GList * mounts,gboolean multiple_volumes,gboolean other_usable_volumes)383 ldsm_maybe_warn_mounts (GList *mounts,
384                         gboolean multiple_volumes,
385                         gboolean other_usable_volumes)
386 {
387         GList *l;
388         gboolean done = FALSE;
389 
390         for (l = mounts; l != NULL; l = l->next) {
391                 LdsmMountInfo *mount_info = l->data;
392                 LdsmMountInfo *previous_mount_info;
393                 gdouble free_space;
394                 gdouble previous_free_space;
395                 time_t curr_time;
396                 const gchar *path;
397                 gboolean show_notify;
398 
399                 if (done) {
400                         /* Don't show any more dialogs if the user took action with the last one. The user action
401                          * might free up space on multiple volumes, making the next dialog redundant.
402                          */
403                         ldsm_free_mount_info (mount_info);
404                         continue;
405                 }
406 
407                 path = g_unix_mount_get_mount_path (mount_info->mount);
408 
409                 previous_mount_info = g_hash_table_lookup (ldsm_notified_hash, path);
410                 if (previous_mount_info != NULL)
411                         previous_free_space = (gdouble) previous_mount_info->buf.f_bavail / (gdouble) previous_mount_info->buf.f_blocks;
412 
413                 free_space = (gdouble) mount_info->buf.f_bavail / (gdouble) mount_info->buf.f_blocks;
414 
415                 if (previous_mount_info == NULL) {
416                         /* We haven't notified for this mount yet */
417                         show_notify = TRUE;
418                         mount_info->notify_time = time (NULL);
419                         g_hash_table_replace (ldsm_notified_hash, g_strdup (path), mount_info);
420                 } else if ((previous_free_space - free_space) > free_percent_notify_again) {
421                         /* We've notified for this mount before and free space has decreased sufficiently since last time to notify again */
422                         curr_time = time (NULL);
423                         if (difftime (curr_time, previous_mount_info->notify_time) > (gdouble)(min_notify_period * 60)) {
424                                 show_notify = TRUE;
425                                 mount_info->notify_time = curr_time;
426                         } else {
427                                 /* It's too soon to show the dialog again. However, we still replace the LdsmMountInfo
428                                  * struct in the hash table, but give it the notfiy time from the previous dialog.
429                                  * This will stop the notification from reappearing unnecessarily as soon as the timeout expires.
430                                  */
431                                 show_notify = FALSE;
432                                 mount_info->notify_time = previous_mount_info->notify_time;
433                         }
434                         g_hash_table_replace (ldsm_notified_hash, g_strdup (path), mount_info);
435                 } else {
436                         /* We've notified for this mount before, but the free space hasn't decreased sufficiently to notify again */
437                         ldsm_free_mount_info (mount_info);
438                         show_notify = FALSE;
439                 }
440 
441                 if (show_notify) {
442                         if (ldsm_notify_for_mount (mount_info, multiple_volumes, other_usable_volumes))
443                                 done = TRUE;
444                 }
445         }
446 }
447 
448 static gboolean
ldsm_check_all_mounts(gpointer data)449 ldsm_check_all_mounts (gpointer data)
450 {
451         GList *mounts;
452         GList *l;
453         GList *check_mounts = NULL;
454         GList *full_mounts = NULL;
455         guint number_of_mounts;
456         guint number_of_full_mounts;
457         gboolean multiple_volumes = FALSE;
458         gboolean other_usable_volumes = FALSE;
459 
460         /* We iterate through the static mounts in /etc/fstab first, seeing if
461          * they're mounted by checking if the GUnixMountPoint has a corresponding GUnixMountEntry.
462          * Iterating through the static mounts means we automatically ignore dynamically mounted media.
463          */
464         mounts = g_unix_mount_points_get (time_read);
465 
466         for (l = mounts; l != NULL; l = l->next) {
467                 GUnixMountPoint *mount_point = l->data;
468                 GUnixMountEntry *mount;
469                 LdsmMountInfo *mount_info;
470                 const gchar *path;
471 
472                 path = g_unix_mount_point_get_mount_path (mount_point);
473                 mount = g_unix_mount_at (path, time_read);
474                 g_unix_mount_point_free (mount_point);
475                 if (mount == NULL) {
476                         /* The GUnixMountPoint is not mounted */
477                         continue;
478                 }
479 
480                 mount_info = g_new0 (LdsmMountInfo, 1);
481                 mount_info->mount = mount;
482 
483                 path = g_unix_mount_get_mount_path (mount);
484 
485                 if (g_unix_mount_is_readonly (mount)) {
486                         ldsm_free_mount_info (mount_info);
487                         continue;
488                 }
489 
490                 if (ldsm_mount_should_ignore (mount)) {
491                         ldsm_free_mount_info (mount_info);
492                         continue;
493                 }
494 
495                 if (statvfs (path, &mount_info->buf) != 0) {
496                         ldsm_free_mount_info (mount_info);
497                         continue;
498                 }
499 
500                 if (ldsm_mount_is_virtual (mount_info)) {
501                         ldsm_free_mount_info (mount_info);
502                         continue;
503                 }
504 
505                 check_mounts = g_list_prepend (check_mounts, mount_info);
506         }
507         g_list_free (mounts);
508 
509         number_of_mounts = g_list_length (check_mounts);
510         if (number_of_mounts > 1)
511                 multiple_volumes = TRUE;
512 
513         for (l = check_mounts; l != NULL; l = l->next) {
514                 LdsmMountInfo *mount_info = l->data;
515 
516                 if (!ldsm_mount_has_space (mount_info)) {
517                         full_mounts = g_list_prepend (full_mounts, mount_info);
518                 } else {
519                         g_hash_table_remove (ldsm_notified_hash, g_unix_mount_get_mount_path (mount_info->mount));
520                         ldsm_free_mount_info (mount_info);
521                 }
522         }
523 
524         number_of_full_mounts = g_list_length (full_mounts);
525         if (number_of_mounts > number_of_full_mounts)
526                 other_usable_volumes = TRUE;
527 
528         ldsm_maybe_warn_mounts (full_mounts, multiple_volumes,
529                                 other_usable_volumes);
530 
531         g_list_free (check_mounts);
532         g_list_free (full_mounts);
533 
534         return TRUE;
535 }
536 
537 static gboolean
ldsm_is_hash_item_not_in_mounts(gpointer key,gpointer value,gpointer user_data)538 ldsm_is_hash_item_not_in_mounts (gpointer key,
539                                  gpointer value,
540                                  gpointer user_data)
541 {
542         GList *l;
543 
544         for (l = (GList *) user_data; l != NULL; l = l->next) {
545                 GUnixMountEntry *mount = l->data;
546                 const char *path;
547 
548                 path = g_unix_mount_get_mount_path (mount);
549 
550                 if (strcmp (path, key) == 0)
551                         return FALSE;
552         }
553 
554         return TRUE;
555 }
556 
557 static void
ldsm_mounts_changed(GObject * monitor G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED)558 ldsm_mounts_changed (GObject  *monitor G_GNUC_UNUSED,
559                      gpointer  data G_GNUC_UNUSED)
560 {
561         GList *mounts;
562 
563         /* remove the saved data for mounts that got removed */
564         mounts = g_unix_mounts_get (time_read);
565         g_hash_table_foreach_remove (ldsm_notified_hash,
566                                      ldsm_is_hash_item_not_in_mounts, mounts);
567         g_list_free_full (mounts, (GDestroyNotify) g_unix_mount_free);
568 
569         /* check the status now, for the new mounts */
570         ldsm_check_all_mounts (NULL);
571 
572         /* and reset the timeout */
573         if (ldsm_timeout_id)
574                 g_source_remove (ldsm_timeout_id);
575         ldsm_timeout_id = g_timeout_add_seconds (CHECK_EVERY_X_SECONDS,
576                                                  ldsm_check_all_mounts, NULL);
577 }
578 
579 static gboolean
ldsm_is_hash_item_in_ignore_paths(gpointer key,gpointer value,gpointer user_data)580 ldsm_is_hash_item_in_ignore_paths (gpointer key,
581                                    gpointer value,
582                                    gpointer user_data)
583 {
584         return ldsm_mount_is_user_ignore (key);
585 }
586 
587 static void
msd_ldsm_get_config(void)588 msd_ldsm_get_config (void)
589 {
590         gchar **settings_list;
591 
592         free_percent_notify = g_settings_get_double (settings,
593                                                      SETTINGS_FREE_PC_NOTIFY_KEY);
594         if (free_percent_notify >= 1 || free_percent_notify < 0) {
595                 /* FIXME define min and max in gschema! */
596                 g_warning ("Invalid configuration of free_percent_notify: %f\n" \
597                            "Using sensible default", free_percent_notify);
598                 free_percent_notify = 0.05;
599         }
600 
601         free_percent_notify_again = g_settings_get_double (settings,
602                                                            SETTINGS_FREE_PC_NOTIFY_AGAIN_KEY);
603         if (free_percent_notify_again >= 1 || free_percent_notify_again < 0) {
604                 /* FIXME define min and max in gschema! */
605                 g_warning ("Invalid configuration of free_percent_notify_again: %f\n" \
606                            "Using sensible default\n", free_percent_notify_again);
607                 free_percent_notify_again = 0.01;
608         }
609 
610         free_size_gb_no_notify = g_settings_get_int (settings,
611                                                      SETTINGS_FREE_SIZE_NO_NOTIFY);
612         min_notify_period = g_settings_get_int (settings,
613                                                 SETTINGS_MIN_NOTIFY_PERIOD);
614 
615         if (ignore_paths != NULL) {
616                 g_slist_free_full (ignore_paths, g_free);
617                 ignore_paths = NULL;
618         }
619 
620         settings_list = g_settings_get_strv (settings, SETTINGS_IGNORE_PATHS);
621         if (settings_list != NULL) {
622                 guint i;
623 
624                 for (i = 0; settings_list[i] != NULL; i++) {
625                         if (settings_list[i] != NULL)
626                                 ignore_paths = g_slist_prepend (ignore_paths, g_strdup (settings_list[i]));
627                 }
628 
629                 /* Make sure we dont leave stale entries in ldsm_notified_hash */
630                 g_hash_table_foreach_remove (ldsm_notified_hash,
631                                              ldsm_is_hash_item_in_ignore_paths, NULL);
632 
633                 g_strfreev (settings_list);
634         }
635 
636 }
637 
638 static void
msd_ldsm_update_config(GSettings * gsettings G_GNUC_UNUSED,gchar * key G_GNUC_UNUSED,gpointer user_data G_GNUC_UNUSED)639 msd_ldsm_update_config (GSettings *gsettings G_GNUC_UNUSED,
640                         gchar *key G_GNUC_UNUSED,
641                         gpointer user_data G_GNUC_UNUSED)
642 {
643         msd_ldsm_get_config ();
644 }
645 
646 void
msd_ldsm_setup(gboolean check_now)647 msd_ldsm_setup (gboolean check_now)
648 {
649         if (ldsm_notified_hash || ldsm_timeout_id || ldsm_monitor) {
650                 g_warning ("Low disk space monitor already initialized.");
651                 return;
652         }
653 
654         ldsm_notified_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
655                                                     g_free,
656                                                     ldsm_free_mount_info);
657 
658         settings = g_settings_new (SETTINGS_HOUSEKEEPING_SCHEMA);
659         msd_ldsm_get_config ();
660         g_signal_connect (settings, "changed", G_CALLBACK (msd_ldsm_update_config), NULL);
661 
662         ldsm_monitor = g_unix_mount_monitor_get ();
663         g_signal_connect (ldsm_monitor, "mounts-changed",
664                           G_CALLBACK (ldsm_mounts_changed), NULL);
665 
666         if (check_now)
667                 ldsm_check_all_mounts (NULL);
668 
669         ldsm_timeout_id = g_timeout_add_seconds (CHECK_EVERY_X_SECONDS,
670                                                  ldsm_check_all_mounts, NULL);
671 
672 }
673 
674 void
msd_ldsm_clean(void)675 msd_ldsm_clean (void)
676 {
677         if (ldsm_timeout_id)
678                 g_source_remove (ldsm_timeout_id);
679         ldsm_timeout_id = 0;
680 
681         if (ldsm_notified_hash)
682                 g_hash_table_destroy (ldsm_notified_hash);
683         ldsm_notified_hash = NULL;
684 
685         if (ldsm_monitor)
686                 g_object_unref (ldsm_monitor);
687         ldsm_monitor = NULL;
688 
689         if (settings) {
690                 g_object_unref (settings);
691         }
692 
693         if (dialog) {
694                 gtk_widget_destroy (GTK_WIDGET (dialog));
695                 dialog = NULL;
696         }
697 
698         if (ignore_paths) {
699                 g_slist_free_full (ignore_paths, g_free);
700         }
701 }
702 
703 #ifdef TEST
704 int
main(int argc,char ** argv)705 main (int    argc,
706       char **argv)
707 {
708         GMainLoop *loop;
709 
710         gtk_init (&argc, &argv);
711 
712         loop = g_main_loop_new (NULL, FALSE);
713 
714         msd_ldsm_setup (TRUE);
715 
716         g_main_loop_run (loop);
717 
718         msd_ldsm_clean ();
719         g_main_loop_unref (loop);
720 
721         return 0;
722 }
723 #endif /* TEST */
724