1 /* Testcase for bug in GIO function g_file_query_filesystem_info()
2 * Author: Nelson Benítez León
3 *
4 * SPDX-License-Identifier: LicenseRef-old-glib-tests
5 *
6 * This work is provided "as is"; redistribution and modification
7 * in whole or in part, in any medium, physical or electronic is
8 * permitted without restriction.
9 *
10 * This work is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 *
14 * In no event shall the authors or contributors be liable for any
15 * direct, indirect, incidental, special, exemplary, or consequential
16 * damages (including, but not limited to, procurement of substitute
17 * goods or services; loss of use, data, or profits; or business
18 * interruption) however caused and on any theory of liability, whether
19 * in contract, strict liability, or tort (including negligence or
20 * otherwise) arising in any way out of the use of this software, even
21 * if advised of the possibility of such damage.
22 */
23
24 #include <errno.h>
25 #include <glib.h>
26 #include <glib/gstdio.h>
27 #include <gio/gio.h>
28 #include <gio/gunixmounts.h>
29
30 static gboolean
31 run (GError **error,
32 const gchar *argv0,
33 ...)
34 {
35 GPtrArray *args;
36 const gchar *arg;
37 va_list ap;
38 GSubprocess *subprocess;
39 gchar *command_line = NULL;
40 gboolean success;
41
42 args = g_ptr_array_new ();
43
44 va_start (ap, argv0);
45 g_ptr_array_add (args, (gchar *) argv0);
46 while ((arg = va_arg (ap, const gchar *)))
47 g_ptr_array_add (args, (gchar *) arg);
48 g_ptr_array_add (args, NULL);
49 va_end (ap);
50
51 command_line = g_strjoinv (" ", (gchar **) args->pdata);
52 g_test_message ("Running command `%s`", command_line);
53 g_free (command_line);
54
55 subprocess = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error);
56 g_ptr_array_free (args, TRUE);
57
58 if (subprocess == NULL)
59 return FALSE;
60
61 success = g_subprocess_wait_check (subprocess, NULL, error);
62 g_object_unref (subprocess);
63
64 return success;
65 }
66
67 static void
68 assert_remove (const gchar *file)
69 {
70 if (g_remove (file) != 0)
71 g_error ("failed to remove %s: %s", file, g_strerror (errno));
72 }
73
74 static gboolean
75 fuse_module_loaded (void)
76 {
77 char *contents = NULL;
78 gboolean ret;
79
80 if (!g_file_get_contents ("/proc/modules", &contents, NULL, NULL) ||
81 contents == NULL)
82 {
83 g_free (contents);
84 return FALSE;
85 }
86
87 ret = (strstr (contents, "\nfuse ") != NULL);
88 g_free (contents);
89 return ret;
90 }
91
92 static void
93 test_filesystem_readonly (gconstpointer with_mount_monitor)
94 {
95 GFileInfo *file_info;
96 GFile *mounted_file;
97 GUnixMountMonitor *mount_monitor = NULL;
98 gchar *bindfs, *fusermount;
99 gchar *curdir, *dir_to_mount, *dir_mountpoint;
100 gchar *file_in_mount, *file_in_mountpoint;
101 GError *error = NULL;
102
103 /* installed by package 'bindfs' in Fedora */
104 bindfs = g_find_program_in_path ("bindfs");
105
106 /* installed by package 'fuse' in Fedora */
107 fusermount = g_find_program_in_path ("fusermount");
108
109 if (bindfs == NULL || fusermount == NULL)
110 {
111 /* We need these because "mount --bind" requires root privileges */
112 g_test_skip ("'bindfs' and 'fusermount' commands are needed to run this test");
113 g_free (fusermount);
114 g_free (bindfs);
115 return;
116 }
117
118 /* If the fuse module is loaded but there's no /dev/fuse, then we're
119 * we're probably in a rootless container and won't be able to
120 * use bindfs to run our tests */
121 if (fuse_module_loaded () &&
122 !g_file_test ("/dev/fuse", G_FILE_TEST_EXISTS))
123 {
124 g_test_skip ("fuse support is needed to run this test (rootless container?)");
125 g_free (fusermount);
126 g_free (bindfs);
127 return;
128 }
129
130 curdir = g_get_current_dir ();
131 dir_to_mount = g_strdup_printf ("%s/dir_bindfs_to_mount", curdir);
132 file_in_mount = g_strdup_printf ("%s/example.txt", dir_to_mount);
133 dir_mountpoint = g_strdup_printf ("%s/dir_bindfs_mountpoint", curdir);
134
135 g_mkdir (dir_to_mount, 0777);
136 g_mkdir (dir_mountpoint, 0777);
137 if (! g_file_set_contents (file_in_mount, "Example", -1, NULL))
138 {
139 g_test_skip ("Failed to create file needed to proceed further with the test");
140
141 g_free (dir_mountpoint);
142 g_free (file_in_mount);
143 g_free (dir_to_mount);
144 g_free (curdir);
145 g_free (fusermount);
146 g_free (bindfs);
147 return;
148 }
149
150 if (with_mount_monitor)
151 mount_monitor = g_unix_mount_monitor_get ();
152
153 /* Use bindfs, which does not need root privileges, to mount the contents of one dir
154 * into another dir (and do the mount as readonly as per passed '-o ro' option) */
155 if (!run (&error, bindfs, "-n", "-o", "ro", dir_to_mount, dir_mountpoint, NULL))
156 {
157 gchar *skip_message = g_strdup_printf ("Failed to run bindfs to set up test: %s", error->message);
158 g_test_skip (skip_message);
159
160 g_free (skip_message);
161 g_clear_error (&error);
162
163 g_clear_object (&mount_monitor);
164 g_free (dir_mountpoint);
165 g_free (file_in_mount);
166 g_free (dir_to_mount);
167 g_free (curdir);
168 g_free (fusermount);
169 g_free (bindfs);
170
171 return;
172 }
173
174 /* Let's check now, that the file is in indeed in a readonly filesystem */
175 file_in_mountpoint = g_strdup_printf ("%s/example.txt", dir_mountpoint);
176 mounted_file = g_file_new_for_path (file_in_mountpoint);
177
178 if (with_mount_monitor)
179 {
180 /* Let UnixMountMonitor process its 'mounts-changed'
181 * signal triggered by mount operation above */
182 while (g_main_context_iteration (NULL, FALSE));
183 }
184
185 file_info = g_file_query_filesystem_info (mounted_file,
186 G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, NULL, &error);
187 g_assert_no_error (error);
188 g_assert_nonnull (file_info);
189 if (! g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
190 {
191 g_test_skip ("Failed to create readonly file needed to proceed further with the test");
192
193 g_clear_object (&file_info);
194 g_clear_object (&mounted_file);
195 g_free (file_in_mountpoint);
196 g_clear_object (&mount_monitor);
197 g_free (dir_mountpoint);
198 g_free (file_in_mount);
199 g_free (dir_to_mount);
200 g_free (curdir);
201 g_free (fusermount);
202 g_free (bindfs);
203
204 return;
205 }
206
207 /* Now we unmount, and mount again but this time rw (not readonly) */
208 run (&error, fusermount, "-z", "-u", dir_mountpoint, NULL);
209 g_assert_no_error (error);
210 run (&error, bindfs, "-n", dir_to_mount, dir_mountpoint, NULL);
211 g_assert_no_error (error);
212
213 if (with_mount_monitor)
214 {
215 /* Let UnixMountMonitor process its 'mounts-changed' signal
216 * triggered by mount/umount operations above */
217 while (g_main_context_iteration (NULL, FALSE));
218 }
219
220 /* Now let's test if GIO will report the new filesystem state */
221 g_clear_object (&file_info);
222 g_clear_object (&mounted_file);
223 mounted_file = g_file_new_for_path (file_in_mountpoint);
224 file_info = g_file_query_filesystem_info (mounted_file,
225 G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, NULL, &error);
226 g_assert_no_error (error);
227 g_assert_nonnull (file_info);
228
229 g_assert_false (g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY));
230
231 /* Clean up */
232 g_clear_object (&mount_monitor);
233 g_clear_object (&file_info);
234 g_clear_object (&mounted_file);
235 run (&error, fusermount, "-z", "-u", dir_mountpoint, NULL);
236 g_assert_no_error (error);
237
238 assert_remove (file_in_mount);
239 assert_remove (dir_to_mount);
240 assert_remove (dir_mountpoint);
241
242 g_free (bindfs);
243 g_free (fusermount);
244 g_free (curdir);
245 g_free (dir_to_mount);
246 g_free (dir_mountpoint);
247 g_free (file_in_mount);
248 g_free (file_in_mountpoint);
249 }
250
251 int
252 main (int argc, char *argv[])
253 {
254 /* To avoid unnecessary D-Bus calls, see http://goo.gl/ir56j2 */
255 g_setenv ("GIO_USE_VFS", "local", FALSE);
256
257 g_test_init (&argc, &argv, NULL);
258
259 g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=787731");
260
261 g_test_add_data_func ("/g-file-info-filesystem-readonly/test-fs-ro",
262 GINT_TO_POINTER (FALSE), test_filesystem_readonly);
263
264 /* This second test is using a running GUnixMountMonitor, so the calls to:
265 * g_unix_mount_get(&time_read) - To fill the time_read parameter
266 * g_unix_mounts_changed_since()
267 *
268 * made from inside g_file_query_filesystem_info() will use the mount_poller_time
269 * from the monitoring of /proc/self/mountinfo , while in the previous test new
270 * created timestamps are returned from those g_unix_mount* functions. */
271 g_test_add_data_func ("/g-file-info-filesystem-readonly/test-fs-ro-with-mount-monitor",
272 GINT_TO_POINTER (TRUE), test_filesystem_readonly);
273
274 return g_test_run ();
275 }