2 * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
3 * Copyright (C) 2015 Etienne Champetier <champetier.etienne@gmail.com>
4 * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License version 2.1
8 * as published by the Free Software Foundation
10 * This program 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. See the
13 * GNU General Public License for more details.
22 #include <linux/limits.h>
30 #include <libubox/avl.h>
31 #include <libubox/avl-cmp.h>
32 #include <libubox/blobmsg.h>
33 #include <libubox/list.h>
44 const char *filesystemtype
;
45 unsigned long mountflags
;
50 struct avl_tree mounts
;
52 int mkdir_p(char *dir
, mode_t mask
)
54 char *l
= strrchr(dir
, '/');
62 if (mkdir_p(dir
, mask
))
67 ret
= mkdir(dir
, mask
);
68 if (ret
&& errno
== EEXIST
)
72 ERROR("mkdir(%s, %d) failed: %m\n", dir
, mask
);
77 static int do_mount(const char *root
, const char *source
, const char *target
, const char *filesystemtype
,
78 unsigned long orig_mountflags
, const char *optstr
, int error
)
83 bool is_bind
= (orig_mountflags
& MS_BIND
);
84 unsigned long mountflags
= orig_mountflags
;
86 if (is_bind
&& stat(source
, &s
)) {
87 ERROR("stat(%s) failed: %m\n", source
);
91 snprintf(new, sizeof(new), "%s%s", root
, target
?target
:source
);
93 if (!is_bind
|| S_ISDIR(s
.st_mode
)) {
96 mkdir_p(dirname(new), 0755);
97 snprintf(new, sizeof(new), "%s%s", root
, target
?target
:source
);
98 fd
= creat(new, 0644);
100 ERROR("creat(%s) failed: %m\n", new);
106 if (mountflags
& MS_BIND
) {
107 if (mount(source
, new, filesystemtype
, MS_BIND
| (mountflags
& MS_REC
), optstr
)) {
108 ERROR("failed to mount -B %s %s: %m\n", source
, new);
111 mountflags
|= MS_REMOUNT
;
114 if (mount(source
, new, filesystemtype
, mountflags
, optstr
)) {
115 ERROR("failed to mount %s %s: %m\n", source
, new);
119 DEBUG("mount %s%s %s (%s)\n", (mountflags
& MS_BIND
)?"-B ":"", source
, new,
120 (mountflags
& MS_RDONLY
)?"ro":"rw");
125 int add_mount(const char *source
, const char *target
, const char *filesystemtype
,
126 unsigned long mountflags
, const char *optstr
, int error
)
128 assert(target
!= NULL
);
130 if (avl_find(&mounts
, target
))
134 m
= calloc(1, sizeof(struct mount
));
136 m
->avl
.key
= m
->target
= strdup(target
);
138 m
->source
= strdup(source
);
140 m
->filesystemtype
= strdup(filesystemtype
);
141 m
->mountflags
= mountflags
;
144 avl_insert(&mounts
, &m
->avl
);
145 DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", m
->source
, m
->target
,
146 !!(m
->mountflags
& MS_BIND
), !!(m
->mountflags
& MS_RDONLY
), m
->error
!= 0);
151 int add_mount_bind(const char *path
, int readonly
, int error
)
153 unsigned long mountflags
= MS_BIND
;
156 mountflags
|= MS_RDONLY
;
158 return add_mount(path
, path
, NULL
, mountflags
, NULL
, error
);
164 OCI_MOUNT_DESTINATION
,
170 static const struct blobmsg_policy oci_mount_policy
[] = {
171 [OCI_MOUNT_SOURCE
] = { "source", BLOBMSG_TYPE_STRING
},
172 [OCI_MOUNT_DESTINATION
] = { "destination", BLOBMSG_TYPE_STRING
},
173 [OCI_MOUNT_TYPE
] = { "type", BLOBMSG_TYPE_STRING
},
174 [OCI_MOUNT_OPTIONS
] = { "options", BLOBMSG_TYPE_ARRAY
},
178 struct list_head list
;
183 #define MS_LAZYTIME (1 << 25)
186 static int parseOCImountopts(struct blob_attr
*msg
, unsigned long *mount_flags
, char **mount_data
, int *error
)
188 struct blob_attr
*cur
;
190 unsigned long mf
= 0;
192 struct list_head fsopts
= LIST_HEAD_INIT(fsopts
);
194 struct mount_opt
*opt
;
196 blobmsg_for_each_attr(cur
, msg
, rem
) {
197 tmp
= blobmsg_get_string(cur
);
198 if (!strcmp("ro", tmp
))
200 else if (!strcmp("rw", tmp
))
202 else if (!strcmp("bind", tmp
))
204 else if (!strcmp("rbind", tmp
))
205 mf
|= MS_BIND
| MS_REC
;
206 else if (!strcmp("sync", tmp
))
207 mf
|= MS_SYNCHRONOUS
;
208 else if (!strcmp("async", tmp
))
209 mf
&= ~MS_SYNCHRONOUS
;
210 else if (!strcmp("atime", tmp
))
212 else if (!strcmp("noatime", tmp
))
214 else if (!strcmp("defaults", tmp
))
215 mf
= 0; /* rw, suid, dev, exec, auto, nouser, and async */
216 else if (!strcmp("dev", tmp
))
218 else if (!strcmp("nodev", tmp
))
220 else if (!strcmp("iversion", tmp
))
222 else if (!strcmp("noiversion", tmp
))
224 else if (!strcmp("diratime", tmp
))
225 mf
&= ~MS_NODIRATIME
;
226 else if (!strcmp("nodiratime", tmp
))
228 else if (!strcmp("dirsync", tmp
))
230 else if (!strcmp("exec", tmp
))
232 else if (!strcmp("noexec", tmp
))
234 else if (!strcmp("mand", tmp
))
236 else if (!strcmp("nomand", tmp
))
238 else if (!strcmp("relatime", tmp
))
240 else if (!strcmp("norelatime", tmp
))
242 else if (!strcmp("strictatime", tmp
))
243 mf
|= MS_STRICTATIME
;
244 else if (!strcmp("nostrictatime", tmp
))
245 mf
&= ~MS_STRICTATIME
;
246 else if (!strcmp("lazytime", tmp
))
248 else if (!strcmp("nolazytime", tmp
))
250 else if (!strcmp("suid", tmp
))
252 else if (!strcmp("nosuid", tmp
))
254 else if (!strcmp("remount", tmp
))
256 else if(!strcmp("nofail", tmp
))
258 else if (!strcmp("auto", tmp
) ||
259 !strcmp("noauto", tmp
) ||
260 !strcmp("user", tmp
) ||
261 !strcmp("group", tmp
) ||
262 !strcmp("_netdev", tmp
))
263 DEBUG("ignoring built-in mount option %s\n", tmp
);
265 /* filesystem-specific free-form option */
266 opt
= calloc(1, sizeof(*opt
));
268 list_add_tail(&opt
->list
, &fsopts
);
274 list_for_each_entry(opt
, &fsopts
, list
) {
278 len
+= strlen(opt
->optstr
);
284 *mount_data
= calloc(len
+ 1, sizeof(char));
289 list_for_each_entry(opt
, &fsopts
, list
) {
291 strcat(*mount_data
, ",");
293 strcat(*mount_data
, opt
->optstr
);
299 DEBUG("mount flags(%08lx) fsopts(\"%s\")\n", mf
, *mount_data
?:"");
304 int parseOCImount(struct blob_attr
*msg
)
306 struct blob_attr
*tb
[__OCI_MOUNT_MAX
];
307 unsigned long mount_flags
= 0;
308 char *mount_data
= NULL
;
311 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
313 if (!tb
[OCI_MOUNT_DESTINATION
])
316 if (tb
[OCI_MOUNT_OPTIONS
]) {
317 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &mount_data
, &err
);
322 add_mount(tb
[OCI_MOUNT_SOURCE
] ? blobmsg_get_string(tb
[OCI_MOUNT_SOURCE
]) : NULL
,
323 blobmsg_get_string(tb
[OCI_MOUNT_DESTINATION
]),
324 tb
[OCI_MOUNT_TYPE
] ? blobmsg_get_string(tb
[OCI_MOUNT_TYPE
]) : NULL
,
325 mount_flags
, mount_data
, err
);
331 int mount_all(const char *jailroot
) {
335 avl_for_each_element(&libraries
, l
, avl
)
336 add_mount_bind(l
->path
, 1, -1);
338 avl_for_each_element(&mounts
, m
, avl
)
339 if (do_mount(jailroot
, m
->source
, m
->target
, m
->filesystemtype
, m
->mountflags
, m
->optstr
, m
->error
))
345 void mount_list_init(void) {
346 avl_init(&mounts
, avl_strcmp
, false, NULL
);
349 static int add_script_interp(const char *path
, const char *map
, int size
)
352 while (start
< size
&& map
[start
] != '/') {
356 ERROR("bad script interp (%s)\n", path
);
359 int stop
= start
+ 1;
360 while (stop
< size
&& map
[stop
] > 0x20 && map
[stop
] <= 0x7e) {
363 if (stop
>= size
|| (stop
-start
) > PATH_MAX
) {
364 ERROR("bad script interp (%s)\n", path
);
368 strncpy(buf
, map
+start
, stop
-start
);
369 return add_path_and_deps(buf
, 1, -1, 0);
372 int add_path_and_deps(const char *path
, int readonly
, int error
, int lib
)
374 assert(path
!= NULL
);
376 if (lib
== 0 && path
[0] != '/') {
377 ERROR("%s is not an absolute path\n", path
);
383 if (path
[0] == '/') {
384 if (avl_find(&mounts
, path
))
386 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
389 add_mount_bind(path
, readonly
, error
);
391 if (avl_find(&libraries
, path
))
394 fd
= lib_open(&fullpath
, path
);
398 alloc_library(fullpath
, path
);
404 if (fstat(fd
, &s
) == -1) {
405 ERROR("fstat(%s) failed: %m\n", path
);
410 if (!S_ISREG(s
.st_mode
)) {
415 /* too small to be an ELF or a script -> "normal" file */
421 map
= mmap(NULL
, s
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
422 if (map
== MAP_FAILED
) {
423 ERROR("failed to mmap %s: %m\n", path
);
428 if (map
[0] == '#' && map
[1] == '!') {
429 ret
= add_script_interp(path
, map
, s
.st_size
);
433 if (map
[0] == ELFMAG0
&& map
[1] == ELFMAG1
&& map
[2] == ELFMAG2
&& map
[3] == ELFMAG3
) {
434 ret
= elf_load_deps(path
, map
);
444 munmap(map
, s
.st_size
);