1 /*
2 * Copyright 2015 Red Hat, Inc.
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Matthias Clasen <mclasen@redhat.com>
20 */
21
22 #include "config.h"
23
24 #include <gio/gio.h>
25 #include <gi18n.h>
26
27 #include "gio-tool.h"
28
29 static gchar **watch_dirs;
30 static gchar **watch_files;
31 static gchar **watch_direct;
32 static gchar **watch_silent;
33 static gchar **watch_default;
34 static gboolean no_moves;
35 static gboolean mounts;
36
37 static const GOptionEntry entries[] = {
38 { "dir", 'd', 0, G_OPTION_ARG_FILENAME_ARRAY, &watch_dirs,
39 N_("Monitor a directory (default: depends on type)"), N_("LOCATION") },
40 { "file", 'f', 0, G_OPTION_ARG_FILENAME_ARRAY, &watch_files,
41 N_("Monitor a file (default: depends on type)"), N_("LOCATION") },
42 { "direct", 'D', 0, G_OPTION_ARG_FILENAME_ARRAY, &watch_direct,
43 N_("Monitor a file directly (notices changes made via hardlinks)"), N_("LOCATION") },
44 { "silent", 's', 0, G_OPTION_ARG_FILENAME_ARRAY, &watch_silent,
45 N_("Monitors a file directly, but doesn’t report changes"), N_("LOCATION") },
46 { "no-moves", 'n', 0, G_OPTION_ARG_NONE, &no_moves,
47 N_("Report moves and renames as simple deleted/created events"), NULL },
48 { "mounts", 'm', 0, G_OPTION_ARG_NONE, &mounts,
49 N_("Watch for mount events"), NULL },
50 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &watch_default,
51 NULL, NULL },
52 G_OPTION_ENTRY_NULL
53 };
54
55 static void
56 watch_callback (GFileMonitor *monitor,
57 GFile *child,
58 GFile *other,
59 GFileMonitorEvent event_type,
60 gpointer user_data)
61 {
62 gchar *child_str;
63 gchar *other_str;
64
65 g_assert (child);
66
67 if (g_file_is_native (child))
68 child_str = g_file_get_path (child);
69 else
70 child_str = g_file_get_uri (child);
71
72 if (other)
73 {
74 if (g_file_is_native (other))
75 other_str = g_file_get_path (other);
76 else
77 other_str = g_file_get_uri (other);
78 }
79 else
80 other_str = g_strdup ("(none)");
81
82 g_print ("%s: ", (gchar *) user_data);
83 switch (event_type)
84 {
85 case G_FILE_MONITOR_EVENT_CHANGED:
86 g_assert (!other);
87 g_print ("%s: changed", child_str);
88 break;
89 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
90 g_assert (!other);
91 g_print ("%s: changes done", child_str);
92 break;
93 case G_FILE_MONITOR_EVENT_DELETED:
94 g_assert (!other);
95 g_print ("%s: deleted", child_str);
96 break;
97 case G_FILE_MONITOR_EVENT_CREATED:
98 g_assert (!other);
99 g_print ("%s: created", child_str);
100 break;
101 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
102 g_assert (!other);
103 g_print ("%s: attributes changed", child_str);
104 break;
105 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
106 g_assert (!other);
107 g_print ("%s: pre-unmount", child_str);
108 break;
109 case G_FILE_MONITOR_EVENT_UNMOUNTED:
110 g_assert (!other);
111 g_print ("%s: unmounted", child_str);
112 break;
113 case G_FILE_MONITOR_EVENT_MOVED_IN:
114 g_print ("%s: moved in", child_str);
115 if (other)
116 g_print (" (from %s)", other_str);
117 break;
118 case G_FILE_MONITOR_EVENT_MOVED_OUT:
119 g_print ("%s: moved out", child_str);
120 if (other)
121 g_print (" (to %s)", other_str);
122 break;
123 case G_FILE_MONITOR_EVENT_RENAMED:
124 g_assert (other);
125 g_print ("%s: renamed to %s\n", child_str, other_str);
126 break;
127
128 case G_FILE_MONITOR_EVENT_MOVED:
129 default:
130 g_assert_not_reached ();
131 }
132
133 g_free (child_str);
134 g_free (other_str);
135 g_print ("\n");
136 }
137
138 typedef enum
139 {
140 WATCH_DIR,
141 WATCH_FILE,
142 WATCH_AUTO
143 } WatchType;
144
145 static gboolean
146 add_watch (const gchar *cmdline,
147 WatchType watch_type,
148 GFileMonitorFlags flags,
149 gboolean connect_handler)
150 {
151 GFileMonitor *monitor = NULL;
152 GError *error = NULL;
153 GFile *file;
154
155 file = g_file_new_for_commandline_arg (cmdline);
156
157 if (watch_type == WATCH_AUTO)
158 {
159 GFileInfo *info;
160 guint32 type;
161
162 info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, &error);
163 if (!info)
164 goto err;
165
166 type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);
167 watch_type = (type == G_FILE_TYPE_DIRECTORY) ? WATCH_DIR : WATCH_FILE;
168 }
169
170 if (watch_type == WATCH_DIR)
171 monitor = g_file_monitor_directory (file, flags, NULL, &error);
172 else
173 monitor = g_file_monitor (file, flags, NULL, &error);
174
175 if (!monitor)
176 goto err;
177
178 if (connect_handler)
179 g_signal_connect (monitor, "changed", G_CALLBACK (watch_callback), g_strdup (cmdline));
180
181 monitor = NULL; /* leak */
182 g_object_unref (file);
183
184 return TRUE;
185
186 err:
187 print_file_error (file, error->message);
188 g_error_free (error);
189 g_object_unref (file);
190
191 return FALSE;
192 }
193
194 int
195 handle_monitor (int argc, gchar *argv[], gboolean do_help)
196 {
197 GOptionContext *context;
198 gchar *param;
199 GError *error = NULL;
200 GFileMonitorFlags flags;
201 guint i;
202
203 g_set_prgname ("gio monitor");
204
205 /* Translators: commandline placeholder */
206 param = g_strdup_printf ("%s…", _("LOCATION"));
207 context = g_option_context_new (param);
208 g_free (param);
209 g_option_context_set_help_enabled (context, FALSE);
210 g_option_context_set_summary (context,
211 _("Monitor files or directories for changes."));
212 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
213
214 if (do_help)
215 {
216 show_help (context, NULL);
217 g_option_context_free (context);
218 return 0;
219 }
220
221 if (!g_option_context_parse (context, &argc, &argv, &error))
222 {
223 show_help (context, error->message);
224 g_error_free (error);
225 g_option_context_free (context);
226 return 1;
227 }
228
229 if (!watch_dirs && !watch_files && !watch_direct && !watch_silent && !watch_default)
230 {
231 show_help (context, _("No locations given"));
232 g_option_context_free (context);
233 return 1;
234 }
235
236 g_option_context_free (context);
237
238 flags = (no_moves ? 0 : G_FILE_MONITOR_WATCH_MOVES) |
239 (mounts ? G_FILE_MONITOR_WATCH_MOUNTS : 0);
240
241 if (watch_dirs)
242 {
243 for (i = 0; watch_dirs[i]; i++)
244 if (!add_watch (watch_dirs[i], WATCH_DIR, flags, TRUE))
245 return 1;
246 }
247
248 if (watch_files)
249 {
250 for (i = 0; watch_files[i]; i++)
251 if (!add_watch (watch_files[i], WATCH_FILE, flags, TRUE))
252 return 1;
253 }
254
255 if (watch_direct)
256 {
257 for (i = 0; watch_direct[i]; i++)
258 if (!add_watch (watch_direct[i], WATCH_FILE, flags | G_FILE_MONITOR_WATCH_HARD_LINKS, TRUE))
259 return 1;
260 }
261
262 if (watch_silent)
263 {
264 for (i = 0; watch_silent[i]; i++)
265 if (!add_watch (watch_silent[i], WATCH_FILE, flags | G_FILE_MONITOR_WATCH_HARD_LINKS, FALSE))
266 return 1;
267 }
268
269 if (watch_default)
270 {
271 for (i = 0; watch_default[i]; i++)
272 if (!add_watch (watch_default[i], WATCH_AUTO, flags, TRUE))
273 return 1;
274 }
275
276 while (TRUE)
277 g_main_context_iteration (NULL, TRUE);
278
279 return 0;
280 }