1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2022 Karel Zak <kzak@redhat.com>
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 *
12 *
13 * This is classic mount(2) based mount.
14 *
15 * Please, see the comment in libmount/src/hooks.c to understand how hooks work.
16 */
17
18 #include "mountP.h"
19
20 /* mount(2) flags for additional propagation changes etc. */
21 struct hook_data {
22 unsigned long flags;
23 };
24
25 static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
26 {
27 void *data = NULL;
28
29 DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name));
30
31 /* remove all our hooks and free hook data */
32 while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) {
33 if (data)
34 free(data);
35 data = NULL;
36 }
37
38 return 0;
39 }
40
41 static struct hook_data *new_hook_data(void)
42 {
43 return calloc(1, sizeof(struct hook_data));
44 }
45
46 /* call mount(2) for propagation flags */
47 static int hook_propagation(struct libmnt_context *cxt,
48 const struct libmnt_hookset *hs,
49 void *data)
50 {
51 int rc;
52 struct hook_data *hd = (struct hook_data *) data;
53 unsigned long extra = 0;
54
55 assert(hd);
56 assert(cxt);
57 assert(cxt->fs);
58 assert(cxt->optlist);
59
60 DBG(HOOK, ul_debugobj(hs, " calling mount(2) for propagation: 0x%08lx %s",
61 hd->flags,
62 hd->flags & MS_REC ? " (recursive)" : ""));
63
64 if (mnt_context_is_fake(cxt)) {
65 DBG(CXT, ul_debugobj(cxt, " FAKE (-f)"));
66 cxt->syscall_status = 0;
67 return 0;
68 }
69
70 /*
71 * hd->flags are propagation flags as set in prepare_propagation()
72 *
73 * @cxt contains global mount flags, may be modified after preparation
74 * stage (for example when libmount blindly tries FS type then it uses
75 * MS_SILENT)
76 */
77 if (mnt_optlist_is_silent(cxt->optlist))
78 extra |= MS_SILENT;
79
80 rc = mount("none", mnt_fs_get_target(cxt->fs), NULL,
81 hd->flags | extra, NULL);
82
83 if (rc) {
84 /* Update global syscall status if only this function called */
85 if (mnt_context_propagation_only(cxt)) {
86 cxt->syscall_status = -errno;
87 cxt->syscall_name = "mount";
88 }
89
90 DBG(HOOK, ul_debugobj(hs, " mount(2) failed [errno=%d %m]", errno));
91 rc = -MNT_ERR_APPLYFLAGS;
92 }
93 return rc;
94 }
95
96 /*
97 * add additional mount(2) syscalls to set propagation flags after regular mount(2).
98 */
99 static int prepare_propagation(struct libmnt_context *cxt,
100 const struct libmnt_hookset *hs)
101 {
102 struct libmnt_optlist *ol;
103 struct libmnt_iter itr;
104 struct libmnt_opt *opt;
105
106 assert(cxt);
107 assert(cxt->fs);
108
109 ol = mnt_context_get_optlist(cxt);
110 if (!ol)
111 return -ENOMEM;
112
113 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
114
115 while (mnt_optlist_next_opt(ol, &itr, &opt) == 0) {
116 int rc;
117 struct hook_data *data;
118 const struct libmnt_optmap *map = mnt_opt_get_map(opt);
119 const struct libmnt_optmap *ent = mnt_opt_get_mapent(opt);
120
121 if (!map || map != cxt->map_linux)
122 continue;
123 if (!(ent->id & MS_PROPAGATION))
124 continue;
125
126 data = new_hook_data();
127 if (!data)
128 return -ENOMEM;
129
130 data->flags = ent->id;
131
132 DBG(HOOK, ul_debugobj(hs, " adding mount(2) call for %s", ent->name));
133 rc = mnt_context_append_hook(cxt, hs,
134 MNT_STAGE_MOUNT_POST,
135 data,
136 hook_propagation);
137 if (rc)
138 return rc;
139
140 DBG(HOOK, ul_debugobj(hs, " removing '%s' flag from primary mount(2)", ent->name));
141 mnt_optlist_remove_opt(ol, opt);
142 }
143
144 return 0;
145 }
146
147 /* call mount(2) for bind,remount */
148 static int hook_bindremount(struct libmnt_context *cxt,
149 const struct libmnt_hookset *hs, void *data)
150 {
151 int rc;
152 struct hook_data *hd = (struct hook_data *) data;
153 unsigned long extra = 0;
154
155 DBG(HOOK, ul_debugobj(hs, " mount(2) for bind-remount: 0x%08lx %s",
156 hd->flags,
157 hd->flags & MS_REC ? " (recursive)" : ""));
158
159 if (mnt_context_is_fake(cxt)) {
160 DBG(CXT, ul_debugobj(cxt, " FAKE (-f)"));
161 cxt->syscall_status = 0;
162 return 0;
163 }
164
165 if (mnt_optlist_is_silent(cxt->optlist))
166 extra |= MS_SILENT;
167
168 /* for the flags see comment in hook_propagation() */
169 rc = mount("none", mnt_fs_get_target(cxt->fs), NULL,
170 hd->flags | extra, NULL);
171
172 if (rc)
173 DBG(HOOK, ul_debugobj(hs, " mount(2) failed"
174 " [rc=%d errno=%d %m]", rc, errno));
175 return rc;
176 }
177
178 /*
179 * add additional mount(2) syscall request to implement "bind,<flags>", the first regular
180 * mount(2) is the "bind" operation, the second is "remount,bind,<flags>" call.
181 */
182 static int prepare_bindremount(struct libmnt_context *cxt,
183 const struct libmnt_hookset *hs)
184 {
185 struct hook_data *data;
186 int rc;
187
188 assert(cxt);
189
190 DBG(HOOK, ul_debugobj(hs, " adding mount(2) call for bint-remount"));
191
192 data = new_hook_data();
193 if (!data)
194 return -ENOMEM;
195
196 mnt_context_get_mflags(cxt, &data->flags);
197
198 assert(data->flags & MS_BIND);
199 assert(!(data->flags & MS_REMOUNT));
200
201 data->flags |= (MS_REMOUNT | MS_BIND);
202
203 rc = mnt_context_append_hook(cxt, hs,
204 MNT_STAGE_MOUNT_POST, data, hook_bindremount);
205 return rc;
206 }
207
208
209
210
211 /* call mount(2) for regular FS mount, mount flags and options are read from
212 * library context struct. There are no private hook data.
213 */
214 static int hook_mount(struct libmnt_context *cxt,
215 const struct libmnt_hookset *hs,
216 void *data __attribute__((__unused__)))
217 {
218 int rc = 0;
219 unsigned long flags = 0;
220 struct libmnt_optlist *ol = NULL;
221 const char *src, *target, *type, *options = NULL;
222
223 src = mnt_fs_get_srcpath(cxt->fs);
224 target = mnt_fs_get_target(cxt->fs);
225 type = mnt_fs_get_fstype(cxt->fs);
226
227 ol = mnt_context_get_optlist(cxt);
228 if (!ol)
229 return -ENOMEM;
230 if (!target)
231 return -EINVAL;
232 if (!src)
233 src = "none";
234
235 /* FS specific mount options/data */
236 if (cxt->flags & MNT_FL_MOUNTDATA)
237 options = cxt->mountdata;
238 else
239 rc = mnt_optlist_get_optstr(ol, &options,
240 NULL, MNT_OL_FLTR_UNKNOWN);
241 /* mount flags */
242 if (!rc)
243 rc = mnt_optlist_get_flags(ol, &flags,
244 mnt_get_builtin_optmap(MNT_LINUX_MAP), 0);
245 if (rc)
246 return rc;
247
248 DBG(HOOK, ul_debugobj(hs, " mount(2) "
249 "[source=%s, target=%s, type=%s, flags=0x%08lx, options=%s]",
250 src, target, type, flags,
251 options ? (cxt->flags & MNT_FL_MOUNTDATA) ? "binary" :
252 options : "<none>"));
253
254 if (mnt_context_is_fake(cxt)) {
255 DBG(HOOK, ul_debugobj(hs, " FAKE (-f)"));
256 cxt->syscall_status = 0;
257 return 0;
258 }
259
260 if (mount(src, target, type, flags, options)) {
261 cxt->syscall_status = -errno;
262 cxt->syscall_name = "mount";
263 DBG(HOOK, ul_debugobj(hs, " mount(2) failed [errno=%d %m]",
264 -cxt->syscall_status));
265 rc = -cxt->syscall_status;
266 return rc;
267 }
268
269 cxt->syscall_status = 0;
270 return rc;
271 }
272
273 /*
274 * analyze library context and register hooks to call one or more mount(2) syscalls
275 */
276 static int hook_prepare(struct libmnt_context *cxt,
277 const struct libmnt_hookset *hs,
278 void *data __attribute__((__unused__)))
279 {
280 int rc = 0;
281 unsigned long flags = 0;
282
283 assert(cxt);
284 assert(hs == &hookset_mount_legacy);
285
286 #ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
287 /* do nothing when a new __mount succesfully registred */
288 if (mnt_context_has_hook(cxt, &hookset_mount, 0, NULL))
289 return 0;
290 #endif
291 /* append regular FS mount(2) */
292 if (!mnt_context_propagation_only(cxt) && !cxt->helper)
293 rc = mnt_context_append_hook(cxt, hs,
294 MNT_STAGE_MOUNT, NULL, hook_mount);
295
296 if (!rc)
297 rc = mnt_context_get_mflags(cxt, &flags);
298 if (rc)
299 return rc;
300
301 /* add extra mount(2) calls for each propagation flag */
302 if (flags & MS_PROPAGATION) {
303 rc = prepare_propagation(cxt, hs);
304 if (rc)
305 return rc;
306 }
307
308 /* add extra mount(2) call to implement "bind,remount,<flags>" */
309 if ((flags & MS_BIND)
310 && (flags & MNT_BIND_SETTABLE)
311 && !(flags & MS_REMOUNT)) {
312 rc = prepare_bindremount(cxt, hs);
313 if (rc)
314 return rc;
315 }
316
317 return rc;
318 }
319
320 const struct libmnt_hookset hookset_mount_legacy =
321 {
322 .name = "__legacy-mount",
323
324 .firststage = MNT_STAGE_PREP,
325 .firstcall = hook_prepare,
326
327 .deinit = hookset_deinit
328 };