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