]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl_jail.c
Fix up concurrent test zpool setup and teardown
[FreeBSD/FreeBSD.git] / sbin / bectl / bectl_jail.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
22  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30
31 #include <sys/param.h>
32 #include <sys/jail.h>
33 #include <sys/mount.h>
34 #include <sys/wait.h>
35 #include <err.h>
36 #include <jail.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <unistd.h>
41
42 #include <be.h>
43
44 #include "bectl.h"
45
46 static void jailparam_grow(void);
47 static void jailparam_add(const char *name, const char *val);
48 static int jailparam_del(const char *name);
49 static bool jailparam_addarg(char *arg);
50 static int jailparam_delarg(char *arg);
51
52 static int bectl_search_jail_paths(const char *mnt);
53 static int bectl_locate_jail(const char *ident);
54
55 /* We'll start with 8 parameters initially and grow as needed. */
56 #define INIT_PARAMCOUNT 8
57
58 static struct jailparam *jp;
59 static int jpcnt;
60 static int jpused;
61 static char mnt_loc[BE_MAXPATHLEN];
62
63 static void
64 jailparam_grow(void)
65 {
66
67         jpcnt *= 2;
68         jp = realloc(jp, jpcnt * sizeof(*jp));
69         if (jp == NULL)
70                 err(2, "realloc");
71 }
72
73 static void
74 jailparam_add(const char *name, const char *val)
75 {
76         int i;
77
78         for (i = 0; i < jpused; ++i) {
79                 if (strcmp(name, jp[i].jp_name) == 0)
80                         break;
81         }
82
83         if (i < jpused)
84                 jailparam_free(&jp[i], 1);
85         else if (jpused == jpcnt)
86                 /* The next slot isn't allocated yet */
87                 jailparam_grow();
88
89         if (jailparam_init(&jp[i], name) != 0)
90                 return;
91         if (jailparam_import(&jp[i], val) != 0)
92                 return;
93         ++jpused;
94 }
95
96 static int
97 jailparam_del(const char *name)
98 {
99         int i;
100         char *val;
101
102         for (i = 0; i < jpused; ++i) {
103                 if (strcmp(name, jp[i].jp_name) == 0)
104                         break;
105         }
106
107         if (i == jpused)
108                 return (ENOENT);
109
110         for (; i < jpused - 1; ++i) {
111                 val = jailparam_export(&jp[i + 1]);
112
113                 jailparam_free(&jp[i], 1);
114                 /*
115                  * Given the context, the following will really only fail if
116                  * they can't allocate the copy of the name or value.
117                  */
118                 if (jailparam_init(&jp[i], jp[i + 1].jp_name) != 0) {
119                         free(val);
120                         return (ENOMEM);
121                 }
122                 if (jailparam_import(&jp[i], val) != 0) {
123                         jailparam_free(&jp[i], 1);
124                         free(val);
125                         return (ENOMEM);
126                 }
127                 free(val);
128         }
129
130         jailparam_free(&jp[i], 1);
131         --jpused;
132         return (0);
133 }
134
135 static bool
136 jailparam_addarg(char *arg)
137 {
138         char *name, *val;
139
140         if (arg == NULL)
141                 return (false);
142         name = arg;
143         if ((val = strchr(arg, '=')) == NULL) {
144                 fprintf(stderr, "bectl jail: malformed jail option '%s'\n",
145                     arg);
146                 return (false);
147         }
148
149         *val++ = '\0';
150         if (strcmp(name, "path") == 0) {
151                 if (strlen(val) >= BE_MAXPATHLEN) {
152                         fprintf(stderr,
153                             "bectl jail: skipping too long path assignment '%s' (max length = %d)\n",
154                             val, BE_MAXPATHLEN);
155                         return (false);
156                 }
157                 strlcpy(mnt_loc, val, sizeof(mnt_loc));
158         }
159         jailparam_add(name, val);
160         return (true);
161 }
162
163 static int
164 jailparam_delarg(char *arg)
165 {
166         char *name, *val;
167
168         if (arg == NULL)
169                 return (EINVAL);
170         name = arg;
171         if ((val = strchr(name, '=')) != NULL)
172                 *val++ = '\0';
173
174         if (strcmp(name, "path") == 0)
175                 *mnt_loc = '\0';
176         return (jailparam_del(name));
177 }
178
179 int
180 bectl_cmd_jail(int argc, char *argv[])
181 {
182         char *bootenv, *mountpoint;
183         int jid, mntflags, opt, ret;
184         bool default_hostname, interactive, unjail;
185         pid_t pid;
186
187         /* XXX TODO: Allow shallow */
188         mntflags = BE_MNT_DEEP;
189         default_hostname = interactive = unjail = true;
190         jpcnt = INIT_PARAMCOUNT;
191         jp = malloc(jpcnt * sizeof(*jp));
192         if (jp == NULL)
193                 err(2, "malloc");
194
195         jailparam_add("persist", "true");
196         jailparam_add("allow.mount", "true");
197         jailparam_add("allow.mount.devfs", "true");
198         jailparam_add("enforce_statfs", "1");
199
200         while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) {
201                 switch (opt) {
202                 case 'b':
203                         interactive = false;
204                         break;
205                 case 'o':
206                         if (jailparam_addarg(optarg)) {
207                                 /*
208                                  * optarg has been modified to null terminate
209                                  * at the assignment operator.
210                                  */
211                                 if (strcmp(optarg, "host.hostname") == 0)
212                                         default_hostname = false;
213                         }
214                         break;
215                 case 'U':
216                         unjail = false;
217                         break;
218                 case 'u':
219                         if ((ret = jailparam_delarg(optarg)) == 0) {
220                                 if (strcmp(optarg, "host.hostname") == 0)
221                                         default_hostname = true;
222                         } else if (ret != ENOENT) {
223                                 fprintf(stderr,
224                                     "bectl jail: error unsetting \"%s\"\n",
225                                     optarg);
226                                 return (ret);
227                         }
228                         break;
229                 default:
230                         fprintf(stderr, "bectl jail: unknown option '-%c'\n",
231                             optopt);
232                         return (usage(false));
233                 }
234         }
235
236         argc -= optind;
237         argv += optind;
238
239         /* struct jail be_jail = { 0 }; */
240         if (argc < 1) {
241                 fprintf(stderr, "bectl jail: missing boot environment name\n");
242                 return (usage(false));
243         }
244
245         bootenv = argv[0];
246
247         /*
248          * XXX TODO: if its already mounted, perhaps there should be a flag to
249          * indicate its okay to proceed??
250          */
251         if (*mnt_loc == '\0')
252                 mountpoint = NULL;
253         else
254                 mountpoint = mnt_loc;
255         if (be_mount(be, bootenv, mountpoint, mntflags, mnt_loc) != BE_ERR_SUCCESS) {
256                 fprintf(stderr, "could not mount bootenv\n");
257                 return (1);
258         }
259
260         if (default_hostname)
261                 jailparam_add("host.hostname", bootenv);
262
263         /*
264          * This is our indicator that path was not set by the user, so we'll use
265          * the path that libbe generated for us.
266          */
267         if (mountpoint == NULL)
268                 jailparam_add("path", mnt_loc);
269         /* Create the jail for now, attach later as-needed */
270         jid = jailparam_set(jp, jpused, JAIL_CREATE);
271         if (jid == -1) {
272                 fprintf(stderr, "unable to create jail.  error: %d\n", errno);
273                 return (1);
274         }
275
276         jailparam_free(jp, jpused);
277         free(jp);
278
279         /* We're not interactive, nothing more to do here. */
280         if (!interactive)
281                 return (0);
282
283         pid = fork();
284         switch(pid) {
285         case -1:
286                 perror("fork");
287                 return (1);
288         case 0:
289                 jail_attach(jid);
290                 /* We're attached within the jail... good bye! */
291                 chdir("/");
292                 if (argc > 1)
293                         execve(argv[1], &argv[1], NULL);
294                 else
295                         execl("/bin/sh", "/bin/sh", NULL);
296                 fprintf(stderr, "bectl jail: failed to execute %s\n",
297                     (argc > 1 ? argv[1] : "/bin/sh"));
298                 _exit(1);
299         default:
300                 /* Wait for the child to get back, see if we need to unjail */
301                 waitpid(pid, NULL, 0);
302         }
303
304         if (unjail) {
305                 jail_remove(jid);
306                 be_unmount(be, bootenv, 0);
307         }
308
309         return (0);
310 }
311
312 static int
313 bectl_search_jail_paths(const char *mnt)
314 {
315         int jid;
316         char lastjid[16];
317         char jailpath[MAXPATHLEN];
318
319         /* jail_getv expects name/value strings */
320         snprintf(lastjid, sizeof(lastjid), "%d", 0);
321
322         jid = 0;
323         while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath,
324             NULL)) != -1) {
325
326                 /* the jail we've been looking for */
327                 if (strcmp(jailpath, mnt) == 0)
328                         return (jid);
329
330                 /* update lastjid and keep on looking */
331                 snprintf(lastjid, sizeof(lastjid), "%d", jid);
332         }
333
334         return (-1);
335 }
336
337 /*
338  * Locate a jail based on an arbitrary identifier.  This may be either a name,
339  * a jid, or a BE name.  Returns the jid or -1 on failure.
340  */
341 static int
342 bectl_locate_jail(const char *ident)
343 {
344         nvlist_t *belist, *props;
345         char *mnt;
346         int jid;
347
348         /* Try the easy-match first */
349         jid = jail_getid(ident);
350         if (jid != -1)
351                 return (jid);
352
353         /* Attempt to try it as a BE name, first */
354         if (be_prop_list_alloc(&belist) != 0)
355                 return (-1);
356
357         if (be_get_bootenv_props(be, belist) != 0)
358                 return (-1);
359
360         if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
361
362                 /* path where a boot environment is mounted */
363                 if (nvlist_lookup_string(props, "mounted", &mnt) == 0) {
364
365                         /* looking for a jail that matches our bootenv path */
366                         jid = bectl_search_jail_paths(mnt);
367                         be_prop_list_free(belist);
368                         return (jid);
369                 }
370
371                 be_prop_list_free(belist);
372         }
373
374         return (-1);
375 }
376
377 int
378 bectl_cmd_unjail(int argc, char *argv[])
379 {
380         char path[MAXPATHLEN];
381         char *cmd, *name, *target;
382         int jid;
383
384         /* Store alias used */
385         cmd = argv[0];
386
387         if (argc != 2) {
388                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
389                 return (usage(false));
390         }
391
392         target = argv[1];
393
394         /* Locate the jail */
395         if ((jid = bectl_locate_jail(target)) == -1) {
396                 fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd,
397                     target);
398                 return (1);
399         }
400
401         bzero(&path, MAXPATHLEN);
402         name = jail_getname(jid);
403         if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
404                 free(name);
405                 fprintf(stderr,
406                     "bectl %s: failed to get path for jail requested by '%s'\n",
407                     cmd, target);
408                 return (1);
409         }
410
411         free(name);
412
413         if (be_mounted_at(be, path, NULL) != 0) {
414                 fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n",
415                     cmd, target);
416                 return (1);
417         }
418
419         jail_remove(jid);
420         be_unmount(be, target, 0);
421
422         return (0);
423 }