├── Makefile ├── README ├── generate-je.sh ├── jectl-howto.md ├── jectl.c ├── jectl.h ├── jectl_activate.c ├── jectl_dump.c ├── jectl_import.c ├── jectl_mount.c ├── jectl_unmount.c ├── jectl_update.c ├── jectl_util.c ├── overview-generate-je.txt ├── overview-jectl.txt └── overview-zfs-layout.txt /Makefile: -------------------------------------------------------------------------------- 1 | # $FreeBSD$ 2 | 3 | .include 4 | 5 | PROG= jectl 6 | MAN= 7 | 8 | SRCS= jectl.c \ 9 | jectl_activate.c \ 10 | jectl_util.c \ 11 | jectl_dump.c \ 12 | jectl_import.c \ 13 | jectl_mount.c \ 14 | jectl_unmount.c \ 15 | jectl_update.c 16 | 17 | LIBADD+=nvpair \ 18 | zfs 19 | 20 | CFLAGS+= -DIN_BASE 21 | CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/include 22 | CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/ 23 | CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/lib/libspl/include/os/freebsd 24 | CFLAGS+= -I${SRCTOP}/sys/contrib/openzfs/lib/libzfs 25 | CFLAGS+= -include ${SRCTOP}/sys/contrib/openzfs/include/os/freebsd/spl/sys/ccompile.h 26 | CFLAGS.jectl.c= -Wno-cast-qual 27 | CFLAGS.jectl_activate.c= -Wno-cast-qual 28 | CFLAGS.jectl_util.c= -Wno-cast-qual 29 | CFLAGS.jectl_dump.c= -Wno-cast-qual 30 | CFLAGS.jectl_import.c= -Wno-cast-qual 31 | CFLAGS.jectl_mount.c= -Wno-cast-qual 32 | CFLAGS.jectl_unmount.c= -Wno-cast-qual 33 | CFLAGS.jectl_update.c= -Wno-cast-qual 34 | 35 | .include 36 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | To build and install: 2 | 3 | % cd /usr/src/sbin 4 | % git clone git@github.com:KlaraSystems/jectl.git jectl 5 | % cd jectl 6 | % make && make install 7 | 8 | To see usage: 9 | % jectl 10 | 11 | See overview-jectl.txt for tool usage. This needs to be converted 12 | into a manual page. 13 | 14 | overview-zfs-layout.txt gives a brief explanation of how jectl 15 | manipulates zfs datasets. 16 | 17 | overview-generate-je.txt explains how to create jail environments 18 | using poudriere-image(8) and generate-je.sh. The created jail 19 | environments are meant to be consumed by jectl. 20 | -------------------------------------------------------------------------------- /generate-je.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # poudriere chokes on a non-empty value if etc/rc.conf 4 | # doesn't exist - jail.conf will set hostname anyway. 5 | HOSTNAME="" 6 | 7 | # temporary variable to delay copying the overlay directory 8 | _OVERLAYDIR= 9 | 10 | # type of stream to generate 11 | # 0 = create new jail 12 | # 1 = update existing jail 13 | je_update=0 14 | 15 | # set defaults 16 | ZFS_JEROOT= 17 | ZFS_JAIL_NAME="main" 18 | ZFS_BOOTFS_NAME="default" 19 | ZFS_BEROOT_NAME="JAIL" 20 | 21 | # create temporary pool 22 | _zfs_create_zpool() { 23 | truncate -s ${IMAGESIZE} ${WRKDIR}/raw.img; 24 | md=$(/sbin/mdconfig ${WRKDIR}/raw.img); 25 | 26 | zpool create \ 27 | -O mountpoint=/${ZFS_POOL_NAME} \ 28 | -O canmount=noauto \ 29 | -O checksum=sha512 \ 30 | -O compression=on \ 31 | -O atime=off \ 32 | -R ${WRKDIR}/world ${ZFS_POOL_NAME} /dev/${md} || exit; 33 | } 34 | 35 | zfs_prepare() { 36 | _zfs_create_zpool 37 | 38 | # create new jail 39 | case "${MEDIAREMAINDER}" in 40 | *full*|send|zfs) 41 | je_update=0 42 | ZFS_JEROOT="${ZFS_POOL_NAME}/${ZFS_JAIL_NAME}" 43 | 44 | msg "[jail environment] generate stream to create new jail" 45 | 46 | zfs create -o canmount=off -o mountpoint=none ${ZFS_JEROOT} 47 | zfs create -o mountpoint=/ ${ZFS_JEROOT}/${ZFS_BOOTFS_NAME} 48 | zfs create ${ZFS_JEROOT}/${ZFS_BOOTFS_NAME}/config; 49 | ;; 50 | esac 51 | 52 | # update existing jail 53 | case "${MEDIAREMAINDER}" in 54 | *be*) 55 | je_update=1 56 | ZFS_JEROOT="${ZFS_POOL_NAME}" 57 | 58 | msg "[jail environment] generate stream to update existing jail" 59 | 60 | zfs create -o mountpoint=/ ${ZFS_JEROOT}/${ZFS_BOOTFS_NAME} 61 | ;; 62 | esac 63 | 64 | # delay copying the overlay directory until files are installed 65 | # to ${ZFS_POOL_NAME}/${ZFS_BOOTFS_NAME}. 66 | # 67 | # depending on the contents of the overlay directory, the config 68 | # dataset will be bootstrapped with files of interest. 69 | if [ -d "${EXTRADIR}" ]; then 70 | _OVERLAYDIR=${EXTRADIR}; 71 | EXTRADIR=; 72 | fi 73 | 74 | } 75 | 76 | bootstrap_links() 77 | { 78 | cd ${_OVERLAYDIR} 79 | find * -type l | \ 80 | while read link; do 81 | src=$(readlink $link); 82 | 83 | # nothing to shuffle around, check next link 84 | if [ ! -e "${WRKDIR}/world/$link" ]; then 85 | continue; 86 | fi 87 | 88 | # does this link link into the config dataset? 89 | # if not, skip it 90 | if ! dirname $src | grep -Eq "^/config$|^/config/"; then 91 | continue; 92 | fi 93 | 94 | # remove ${WRKDIR}/world/$link to avoid clashing when the 95 | # overlay directory is copied in - for example, cp will 96 | # complain when copying a symbolic link over an existing 97 | # directory. 98 | if [ ${je_update} -eq 0 ]; then 99 | msg "[jail environment] moving $link to $src" 100 | # bootstrap config dataset with defaults 101 | mkdir -p $(dirname ${WRKDIR}/world/$src) 102 | cp -fRPp ${WRKDIR}/world/$link ${WRKDIR}/world/$src 103 | rm -rf ${WRKDIR}/world/$link 104 | else 105 | msg "[jail environment] removing $link from jail environment" 106 | rm -rf ${WRKDIR}/world/$link 107 | fi 108 | done 109 | } 110 | 111 | zfs_build() { 112 | zroot=${ZFS_JEROOT} 113 | jail_version=$(poudriere jail -i -j $JAILNAME | awk '$2 == "version:" { print $3 }') 114 | freebsd_version="" 115 | 116 | if [ $je_update -eq 0 ]; then 117 | zroot=${ZFS_POOL_NAME}/${ZFS_JAIL_NAME} 118 | fi 119 | 120 | if [ -f "${WRKDIR}/world/usr/include/sys/param.h" ]; then 121 | freebsd_version=$(awk '/^\#define[[:space:]]*__FreeBSD_version/ {print $3}' \ 122 | ${WRKDIR}/world/usr/include/sys/param.h) 123 | fi 124 | 125 | if [ -d "${_OVERLAYDIR}" ]; then 126 | # not sure if bootstrap is necessary 127 | (bootstrap_links) 128 | 129 | msg "[jail environment] copying in overlay directory from ${_OVERLAYDIR}" 130 | cp -fRPp "${_OVERLAYDIR}/" ${WRKDIR}/world/ 131 | 132 | EXTRADIR=${_OVERLAYDIR} 133 | _OVERLAYDIR= 134 | fi 135 | 136 | msg "[jail environment] setting zfs user properties" 137 | 138 | # not quite a fingerprint 139 | zfs set je:version="${jail_version}" ${zroot}/${ZFS_BOOTFS_NAME} 140 | zfs set je:poudriere:jailname="${JAILNAME}" ${zroot}/${ZFS_BOOTFS_NAME} 141 | zfs set je:poudriere:overlaydir="${EXTRADIR}" ${zroot}/${ZFS_BOOTFS_NAME} 142 | zfs set je:poudriere:packagelist="${PACKAGELIST}" ${zroot}/${ZFS_BOOTFS_NAME} 143 | zfs set je:poudriere:freebsd_version="${freebsd_version}" ${zroot}/${ZFS_BOOTFS_NAME} 144 | 145 | # dont know the final mountpoint, so none. 146 | zfs set mountpoint=none canmount=off ${zroot} 147 | zfs set mountpoint=none canmount=noauto ${zroot}/${ZFS_BOOTFS_NAME} 148 | 149 | if [ $je_update -eq 0 ]; then 150 | zfs set canmount=noauto ${zroot}/${ZFS_BOOTFS_NAME}/config 151 | fi 152 | } 153 | 154 | zfs_generate() 155 | { 156 | : ${SNAPSHOT_NAME:=$IMAGENAME} 157 | zroot=${ZFS_JEROOT} 158 | 159 | msg "[jail environment] creating snapshot for replication stream" 160 | 161 | if [ ${je_update} -eq 0 ]; then 162 | # jectl checks this property to determine if the generated 163 | # stream will be used to create a new jail 164 | zfs set je:poudriere:create="${ZFS_BOOTFS_NAME}" ${ZFS_JEROOT} 165 | 166 | SNAPSPEC="${ZFS_JEROOT}@${SNAPSHOT_NAME}" 167 | zfs snapshot -r "$SNAPSPEC" 168 | 169 | FINALIMAGE=${IMAGENAME}.full.zfs 170 | _zfs_writereplicationstream "${SNAPSPEC}" "${FINALIMAGE}" 171 | 172 | else 173 | BESNAPSPEC="${ZFS_JEROOT}/${ZFS_BOOTFS_NAME}@${SNAPSHOT_NAME}" 174 | zfs snapshot "$BESNAPSPEC" 175 | 176 | FINALIMAGE=${IMAGENAME}.je.zfs 177 | _zfs_writereplicationstream "${BESNAPSPEC}" "${FINALIMAGE}" 178 | fi 179 | 180 | zpool export ${ZFS_POOL_NAME} 181 | zroot= 182 | /sbin/mdconfig -d -u ${md#md} 183 | md= 184 | } 185 | -------------------------------------------------------------------------------- /jectl-howto.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This will allow to generate jail with ZFS to permit fast updating. 4 | 5 | ## What you need 6 | 7 | You need : 8 | - A freebsd server with poudriere-devel and a zpool NOT named `zroot` 9 | - A poudriere setup 10 | - A jail poudiere created. 11 | - Jail server that will run jails. It needs a `zroot` pool TODO: avoid hardcoded things 12 | 13 | ## Poudriere environement 14 | 15 | Notice : *DON'T* have zroot pool otherwize it will not be possible to generate 16 | zfs streams. 17 | 18 | ### Create poudriere jail environement 19 | 20 | We will create 2 jail env to permit upgrade from 12.4 to 13.2 21 | 22 | ``` 23 | # poudriere jail -c -j 120x64 -v 12.4-RELEASE 24 | # poudriere jail -c -j 130x64 -v 13.2-RELEASE 25 | ``` 26 | 27 | ### Create images. 28 | 29 | There is 2 kind of images : 30 | - the INITIAL image, which name is "full" 31 | - the UPDATE image, which name is "je" 32 | 33 | #### Initial image : 34 | 35 | You will need the 'generate-je.sh' from jectl github. 36 | 37 | For example to generate a initial image of 12.4-RELEASE : 38 | ``` 39 | # poudriere image -t zfs+send -B /home/generate-je.sh -j 120x64 -n 120stream -s 1G 40 | [00:00:00] Preparing the image '120stream' 41 | [00:00:00] Calculated image size 976m 42 | [00:00:00] [jail environment] generate stream to create new jail 43 | [00:00:01] Installing world with tar 44 | >>> Removing old files (only deletes safe to delete libs) 45 | >>> Old files removed 46 | >>> Removing old directories 47 | >>> Old directories removed 48 | To remove old libraries run 'make delete-old-libs'. 49 | >>> Removing old libraries 50 | Please be sure no application still uses those libraries, else you 51 | can not start such an application. Consult UPDATING for more 52 | information regarding how to cope with the removal/revision bump 53 | of a specific library. 54 | >>> Old libraries removed 55 | [00:02:06] Installing world done 56 | [00:02:06] Installing packages 57 | [00:02:07] [jail environment] setting zfs user properties 58 | [00:02:07] [jail environment] creating snapshot for replication stream 59 | [00:02:07] Creating replication stream 60 | [00:02:13] Image available at: /usr/local/poudriere/data/images/120stream.full.zfs 61 | dd 62 | ``` 63 | 64 | #### Update Image 65 | 66 | As initial image you will need the generate-je + poudriere. Notice the `+be` 67 | used to generate update image : 68 | 69 | ``` 70 | poudriere image -t zfs+send+be -B /home/generate-je.sh -j 130x64 -n 130stream -s 1G 71 | [00:00:00] Preparing the image '130stream' 72 | [00:00:00] Calculated image size 976m 73 | [00:00:01] [jail environment] generate stream to update existing jail 74 | [00:00:01] Installing world with tar 75 | >>> Removing old files (only deletes safe to delete libs) 76 | >>> Old files removed 77 | >>> Removing old directories 78 | >>> Old directories removed 79 | To remove old libraries run 'make delete-old-libs'. 80 | >>> Removing old libraries 81 | Please be sure no application still uses those libraries, else you 82 | can not start such an application. Consult UPDATING for more 83 | information regarding how to cope with the removal/revision bump 84 | of a specific library. 85 | >>> Old libraries removed 86 | [00:01:55] Installing world done 87 | [00:01:55] Installing packages 88 | [00:01:55] [jail environment] setting zfs user properties 89 | [00:01:56] [jail environment] creating snapshot for replication stream 90 | [00:01:56] Creating replication stream 91 | [00:01:24] Image available at: /usr/local/poudriere/data/images/130stream.je.zfs 92 | ``` 93 | 94 | Then we have 2 stream that can be used for jails. 95 | 96 | ## Usage in production 97 | 98 | ### Create jail.conf 99 | 100 | ``` 101 | path = /$name; 102 | exec.prepare = "/sbin/jectl mount $name $path"; 103 | exec.start = "/bin/sh /etc/rc"; 104 | exec.stop = "/bin/sh /etc/rc.shutdown"; 105 | 106 | klara { 107 | 108 | } 109 | ``` 110 | 111 | The `klara` is the name of the jail. 112 | Notice you need to have a `zroot` zpool to use `jectl`. 113 | 114 | ### Create the initial jail 115 | 116 | Simply : 117 | 118 | ``` 119 | # cat 120stream.full.zfs | jectl import klara 120 | create zroot/JAIL 121 | create zroot/JE 122 | ``` 123 | 124 | The following dataset will be created : 125 | 126 | ``` 127 | zroot/JAIL 711M 7.87G 192K none 128 | zroot/JAIL/klara 711M 7.87G 192K none 129 | zroot/JAIL/klara/default 711M 7.87G 710M none 130 | zroot/JAIL/klara/default/config 192K 7.87G 192K none 131 | zroot/JE 192K 7.87G 192K none 132 | ``` 133 | 134 | Starting the jail : 135 | ``` 136 | # service jail onestart 137 | Starting jails: klara. 138 | # jls 139 | JID IP Address Hostname Path 140 | 1 /klara 141 | ``` 142 | 143 | Connecting into jail : 144 | 145 | ``` 146 | # jexec 1 /bin/sh 147 | sh: can't access tty; job control turned off 148 | # uname -a 149 | FreeBSD 13.2-RELEASE-p4 FreeBSD 13.2-RELEASE-p4 GENERIC amd64 150 | # freebsd-version -ru 151 | 13.2-RELEASE-p4 152 | 12.4-RELEASE-p9 153 | ``` 154 | 155 | 156 | ### Upgrading a jail 157 | 158 | In this case we will upgrade a jail from 12.4 to 13.2 159 | 160 | ### First import the ZFS stream 161 | 162 | ``` 163 | # cat 130stream.je.zfs | jectl import 13.2-RELEASE 164 | ``` 165 | *NOTICE*: this should be a "je" zfs stream made by poudriere `-t zfs+send+be` 166 | otherwise it will *not* being imported (and fails without any warning). 167 | 168 | A new JE is there: 169 | ``` 170 | zroot/JE 733M 7.15G 192K none 171 | zroot/JE/13.2-RELEASE 733M 7.15G 733M none 172 | ``` 173 | 174 | Then activate the JE to `klara` jail : 175 | 176 | ``` 177 | # jectl dump klara 178 | Jail name: klara 179 | Environments: 180 | 1. Name: default (ACTIVE) 181 | branch: 12.4-RELEASE-p9 182 | version: 1204000 183 | poudriere-jail: 120x64 184 | # jectl activate klara 13.2-RELEASE 185 | # jectl dump klara 186 | Jail name: klara 187 | Environments: 188 | 1. Name: default 189 | branch: 12.4-RELEASE-p9 190 | version: 1204000 191 | poudriere-jail: 120x64 192 | 2. Name: 13.2-RELEASE (ACTIVE) 193 | branch: 13.2-RELEASE-p7 194 | version: 1302001 195 | poudriere-jail: 130x64 196 | ``` 197 | 198 | Next starting of jail will take 13.2 BE 199 | 200 | ``` 201 | # service jail onestart 202 | Starting jails: klara. 203 | root@klara-jectl:/home/kiwi # jls 204 | JID IP Address Hostname Path 205 | 2 /klara 206 | # jexec 2 /bin/sh 207 | sh: can't access tty; job control turned off 208 | # freebsd-version -ru 209 | 13.2-RELEASE-p4 210 | 13.2-RELEASE-p7 211 | ``` 212 | 213 | ## Saving configuration 214 | 215 | The system will lost everything when upgrading jail environment. 216 | Package, configuration, users ... 217 | 218 | To avoid that, like a mfsboot you will have to prepare the stream to 219 | look such data somewhere else that IS not destroy while changing root environment 220 | 221 | For that you'll have to cread an overlay directory : 222 | 223 | ``` 224 | mkdir /home/ovr 225 | cd /home/ovr 226 | mkdir etc 227 | cd etc 228 | ln -s /config/rc.conf . 229 | ln -s /config/master.passwd . 230 | ``` 231 | 232 | And run again : 233 | ``` 234 | # poudriere image -t zfs+send -B /home/generate-je.sh -j 120x64 -n 120stream -s 1G -c /home/ovr 235 | [00:00:00] Preparing the image '120stream' 236 | [00:00:00] Calculated image size 976m 237 | [00:00:00] [jail environment] generate stream to create new jail 238 | [00:00:01] Installing world with tar 239 | >>> Removing old files (only deletes safe to delete libs) 240 | >>> Old files removed 241 | >>> Removing old directories 242 | >>> Old directories removed 243 | To remove old libraries run 'make delete-old-libs'. 244 | >>> Removing old libraries 245 | Please be sure no application still uses those libraries, else you 246 | can not start such an application. Consult UPDATING for more 247 | information regarding how to cope with the removal/revision bump 248 | of a specific library. 249 | >>> Old libraries removed 250 | [00:01:13] Installing world done 251 | [00:01:14] Installing packages 252 | [00:01:14] [jail environment] moving etc/master.passwd to /config/master.passwd 253 | [00:01:14] [jail environment] copying in overlay directory from /usr/home/ovr 254 | [00:01:14] [jail environment] setting zfs user properties 255 | [00:01:15] [jail environment] creating snapshot for replication stream 256 | [00:01:15] Creating replication stream 257 | [00:01:18] Image available at: /usr/local/poudriere/data/images/120stream.full.zfs 258 | ``` 259 | 260 | Same again for the JE : 261 | 262 | ``` 263 | # poudriere image -t zfs+send+be -B /home/generate-je.sh -j 130x64 -n 130stream -s 1G -c /home/ovr 264 | [00:00:00] Preparing the image '130stream' 265 | [00:00:00] Calculated image size 976m 266 | [00:00:01] [jail environment] generate stream to update existing jail 267 | [00:00:01] Installing world with tar 268 | >>> Removing old files (only deletes safe to delete libs) 269 | >>> Old files removed 270 | >>> Removing old directories 271 | >>> Old directories removed 272 | To remove old libraries run 'make delete-old-libs'. 273 | >>> Removing old libraries 274 | Please be sure no application still uses those libraries, else you 275 | can not start such an application. Consult UPDATING for more 276 | information regarding how to cope with the removal/revision bump 277 | of a specific library. 278 | >>> Old libraries removed 279 | [00:01:08] Installing world done 280 | [00:01:08] Installing packages 281 | [00:01:08] [jail environment] removing etc/master.passwd from jail environment 282 | [00:01:08] [jail environment] copying in overlay directory from /usr/home/ovr 283 | [00:01:08] [jail environment] setting zfs user properties 284 | [00:01:08] [jail environment] creating snapshot for replication stream 285 | [00:01:09] Creating replication stream 286 | [00:01:11] Image available at: /usr/local/poudriere/data/images/130stream.je.zfs 287 | ``` 288 | 289 | Then you can use the previous way to handle initial jail setup and jail upgrade 290 | 291 | -------------------------------------------------------------------------------- /jectl.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | #include 30 | 31 | #include "jectl.h" 32 | 33 | libzfs_handle_t *lzh; 34 | 35 | const char *jepool = "zroot/JE"; 36 | const char *jeroot = "zroot/JAIL"; 37 | 38 | static void 39 | usage(void) 40 | { 41 | fprintf(stderr, "usage: jectl ...\n\n"); 42 | fprintf(stderr, "Commands:\n"); 43 | fprintf(stderr, " activate - activate jail environment\n"); 44 | fprintf(stderr, " dump [jailname] - print detailed information\n"); 45 | fprintf(stderr, " import - receive ZFS replication stream\n"); 46 | fprintf(stderr, " list [jailname] - proxy to zfs list, no options accepted\n"); 47 | fprintf(stderr, " mount - mount jail at given path\n"); 48 | fprintf(stderr, " umount - unmount jail\n"); 49 | fprintf(stderr, " update [mountpoint] - update jail and optionally mount\n"); 50 | exit(1); 51 | } 52 | 53 | static int 54 | init_root(void) 55 | { 56 | nvlist_t *nvl; 57 | 58 | nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP); 59 | nvlist_add_string(nvl, "canmount", "off"); 60 | nvlist_add_string(nvl, "mountpoint", "none"); 61 | 62 | if (!zfs_dataset_exists(lzh, jeroot, ZFS_TYPE_FILESYSTEM)) { 63 | if (zfs_create(lzh, jeroot, ZFS_TYPE_FILESYSTEM, nvl) != 0) { 64 | fprintf(stderr, "jectl: cannot create %s\n", jeroot); 65 | return (1); 66 | } 67 | printf("create %s\n", jeroot); 68 | } 69 | 70 | if (!zfs_dataset_exists(lzh, jepool, ZFS_TYPE_FILESYSTEM)) { 71 | if (zfs_create(lzh, jepool, ZFS_TYPE_FILESYSTEM, nvl) != 0) { 72 | fprintf(stderr, "jectl: cannot create %s\n", jepool); 73 | return (1); 74 | } 75 | printf("create %s\n", jepool); 76 | } 77 | 78 | nvlist_free(nvl); 79 | return (0); 80 | } 81 | 82 | static int 83 | jectl_list(int argc __unused, char **argv __unused) 84 | { 85 | zfs_handle_t *jds; 86 | 87 | switch (argc) { 88 | case 1: 89 | execl("/sbin/zfs", "zfs", "list", "-r", jepool, jeroot, NULL); 90 | break; 91 | case 2: 92 | if ((jds = get_jail_dataset(argv[1])) == NULL) 93 | return (1); 94 | execl("/sbin/zfs", "zfs", "list", "-r", zfs_get_name(jds), NULL); 95 | zfs_close(jds); 96 | break; 97 | default: 98 | fprintf(stderr, "usage: jectl list [jailname]\n"); 99 | exit(1); 100 | } 101 | 102 | return (1); 103 | } 104 | JE_COMMAND(jectl, list, jectl_list); 105 | 106 | int 107 | main(int argc, char *argv[]) 108 | { 109 | struct jectl_command **jc; 110 | 111 | if (argc < 2) 112 | usage(); 113 | argv++; 114 | argc--; 115 | 116 | if ((lzh = libzfs_init()) == NULL) 117 | return (1); 118 | 119 | libzfs_print_on_error(lzh, B_TRUE); 120 | 121 | if (init_root() != 0) 122 | return (1); 123 | 124 | SET_FOREACH(jc, jectl) { 125 | if (strcmp((*jc)->name, argv[0]) == 0) 126 | return ((*jc)->handler(argc, argv)); 127 | } 128 | 129 | fprintf(stderr, "jectl: sub-command not found: %s\n", argv[0]); 130 | return (1); 131 | } 132 | -------------------------------------------------------------------------------- /jectl.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | 32 | struct jectl_command { 33 | const char *name; 34 | int (*handler)(int argc, char **argv); 35 | }; 36 | 37 | SET_DECLARE(jectl, struct jectl_command); 38 | 39 | #define JE_COMMAND(set, name, function) \ 40 | static struct jectl_command name ## _jectl_command = \ 41 | { #name, function }; \ 42 | DATA_SET(set, name ## _jectl_command); 43 | 44 | extern libzfs_handle_t *lzh; 45 | extern const char *jepool; 46 | extern const char *jeroot; 47 | 48 | int get_property(zfs_handle_t *, const char *, char **); 49 | 50 | zfs_handle_t * get_jail_dataset(const char *); 51 | zfs_handle_t * get_active_je(zfs_handle_t *); 52 | 53 | zfs_handle_t * je_copy(zfs_handle_t *, zfs_handle_t *); 54 | 55 | int je_activate(zfs_handle_t *, const char *); 56 | int je_destroy(zfs_handle_t *); 57 | int je_mount(zfs_handle_t *, const char *); 58 | int je_swapin(zfs_handle_t *, zfs_handle_t *); 59 | int je_unmount(zfs_handle_t *, int); 60 | 61 | -------------------------------------------------------------------------------- /jectl_activate.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | 30 | #include "jectl.h" 31 | 32 | static zfs_handle_t * 33 | search_jepool(const char *target) 34 | { 35 | char name[ZFS_MAXPROPLEN]; 36 | 37 | snprintf(name, sizeof(name), "%s/%s", jepool, target); 38 | 39 | if (!zfs_dataset_exists(lzh, name, ZFS_TYPE_FILESYSTEM)) 40 | return (NULL); 41 | 42 | return (zfs_open(lzh, name, ZFS_TYPE_FILESYSTEM)); 43 | } 44 | 45 | int 46 | je_activate(zfs_handle_t *jds, const char *target) 47 | { 48 | int error; 49 | char name[ZFS_MAXPROPLEN]; 50 | zfs_handle_t *next, *zhp; 51 | 52 | snprintf(name, sizeof(name), "%s/%s", zfs_get_name(jds), target); 53 | 54 | if (zfs_dataset_exists(lzh, name, ZFS_TYPE_FILESYSTEM)) { 55 | next = zfs_open(lzh, name, ZFS_TYPE_FILESYSTEM); 56 | } else { 57 | if ((zhp = search_jepool(target)) == NULL) 58 | return (1); 59 | next = je_copy(zhp, jds); 60 | } 61 | 62 | if (next == NULL) 63 | return (1); 64 | 65 | error = je_swapin(jds, next); 66 | 67 | zfs_close(next); 68 | 69 | return (error); 70 | } 71 | 72 | 73 | static int 74 | jectl_activate(int argc, char **argv) 75 | { 76 | int error; 77 | zfs_handle_t *jds; 78 | 79 | if (argc != 3) { 80 | fprintf(stderr, "usage: jectl activate \n"); 81 | exit(1); 82 | } 83 | 84 | if ((jds = get_jail_dataset(argv[1])) == NULL) 85 | return (1); 86 | 87 | error = je_activate(jds, argv[2]); 88 | 89 | zfs_close(jds); 90 | 91 | return (error); 92 | } 93 | JE_COMMAND(jectl, activate, jectl_activate); 94 | 95 | -------------------------------------------------------------------------------- /jectl_dump.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | #include 30 | 31 | #include "jectl.h" 32 | 33 | 34 | static void 35 | print_je(zfs_handle_t *je) 36 | { 37 | char *name; 38 | char *value; 39 | char buffer[ZFS_MAXPROPLEN]; 40 | 41 | name = strdup(zfs_get_name(je)); 42 | 43 | if (get_property(je, "je:active", &value) == 0 && 44 | strcmp(value, zfs_get_name(je)) == 0) { 45 | snprintf(buffer, sizeof(buffer), "%s (ACTIVE)", basename(name)); 46 | } else 47 | snprintf(buffer, sizeof(buffer), "%s", basename(name)); 48 | 49 | printf(" Name: %s\n", buffer); 50 | 51 | if (get_property(je, "je:version", &value) == 0) 52 | printf(" branch: %s\n", value); 53 | if (get_property(je, "je:poudriere:freebsd_version", &value) == 0) 54 | printf(" version: %s\n", value); 55 | if (get_property(je, "je:poudriere:jailname", &value) == 0) 56 | printf(" poudriere-jail: %s\n", value); 57 | if (get_property(je, "je:poudriere:overlaydir", &value) == 0) 58 | printf(" overlay: %s\n", value); 59 | if (get_property(je, "je:poudriere:packagelist", &value) == 0) 60 | printf(" packagelist: %s\n", value); 61 | 62 | free(name); 63 | return; 64 | } 65 | 66 | static int 67 | print_jail_cb(zfs_handle_t *zhp, void *arg __unused) 68 | { 69 | int *count = arg; 70 | 71 | printf("%d.", (*count)++); 72 | print_je(zhp); 73 | zfs_close(zhp); 74 | return (0); 75 | } 76 | 77 | 78 | static int 79 | print_jail(zfs_handle_t *jds, void *arg __unused) 80 | { 81 | int count; 82 | zfs_handle_t *je; 83 | 84 | if ((je = get_active_je(jds)) == NULL) 85 | return (0); 86 | 87 | char *name; 88 | name = strdup(zfs_get_name(jds)); 89 | printf("Jail name: %s\n", basename(name)); 90 | printf("Environments:\n"); 91 | 92 | count = 1; 93 | zfs_iter_filesystems(jds, print_jail_cb, &count); 94 | printf("\n"); 95 | 96 | return (0); 97 | } 98 | 99 | static int 100 | print_all(void) 101 | { 102 | int count; 103 | zfs_handle_t *zhp; 104 | 105 | count = 1; 106 | 107 | /* print jails */ 108 | zhp = zfs_open(lzh, jeroot, ZFS_TYPE_FILESYSTEM); 109 | zfs_iter_filesystems(zhp, print_jail, NULL); 110 | zfs_close(zhp); 111 | 112 | printf("Available jail environments:\n"); 113 | zhp = zfs_open(lzh, jepool, ZFS_TYPE_FILESYSTEM); 114 | zfs_iter_filesystems(zhp, print_jail_cb, &count); 115 | zfs_close(zhp); 116 | 117 | return (0); 118 | } 119 | 120 | static int 121 | jectl_dump(int argc, char **argv) 122 | { 123 | zfs_handle_t *jds; 124 | 125 | if (argc < 1 || argc > 2) { 126 | fprintf(stderr, "usage: jectl dump [jailname]\n"); 127 | exit(1); 128 | } 129 | 130 | if (argc == 1) { 131 | print_all(); 132 | return (0); 133 | } 134 | 135 | if ((jds = get_jail_dataset(argv[1])) == NULL) 136 | return (1); 137 | 138 | print_jail(jds, NULL); 139 | 140 | zfs_close(jds); 141 | 142 | return (0); 143 | } 144 | JE_COMMAND(jectl, dump, jectl_dump); 145 | 146 | -------------------------------------------------------------------------------- /jectl_import.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | 30 | #include "jectl.h" 31 | 32 | /* 33 | * zfs recv into a temporary dataset to peek at the user properties. 34 | * If je:poudriere:create is set, the temporary dataset will be renamed 35 | * to $jeroot/$import_name; this is how a jail is created. 36 | * Otherwise, the temporary dataset is renamed to $jepool/$import_name 37 | * so that it can be consumed as a jail environment. 38 | */ 39 | static int 40 | je_import(const char *import_name) 41 | { 42 | nvlist_t *props; 43 | zfs_handle_t *zhp; 44 | recvflags_t flags = { .nomount = 1 }; 45 | char name[ZFS_MAXPROPLEN]; 46 | char *default_je; 47 | struct renameflags rflags = { 0 }; 48 | 49 | snprintf(name, sizeof(name), "%s/jectl.XXXXXX", jeroot); 50 | if (mktemp(name) == NULL) 51 | return (1); 52 | 53 | if (zfs_receive(lzh, name, NULL, &flags, STDIN_FILENO, NULL) != 0) 54 | return (1); 55 | 56 | if ((zhp = zfs_open(lzh, name, ZFS_TYPE_FILESYSTEM)) == NULL) 57 | return (1); 58 | 59 | if (get_property(zhp, "je:poudriere:create", &default_je) == 0) 60 | snprintf(name, sizeof(name), "%s/%s", jeroot, import_name); 61 | else 62 | snprintf(name, sizeof(name), "%s/%s", jepool, import_name); 63 | 64 | if (zfs_dataset_exists(lzh, name, ZFS_TYPE_FILESYSTEM)) { 65 | fprintf(stderr, "jectl: cannot import '%s': jail dataset already exists\n", import_name); 66 | je_destroy(zhp); 67 | return (1); 68 | } 69 | 70 | if (zfs_rename(zhp, name, rflags) != 0) { 71 | je_destroy(zhp); 72 | return (1); 73 | } 74 | 75 | /* 76 | * zhp goes stale after the rename, refresh it 77 | */ 78 | zfs_close(zhp); 79 | if ((zhp = zfs_open(lzh, name, ZFS_TYPE_FILESYSTEM)) == NULL) { 80 | fprintf(stderr, "cannot open imported dataset '%s'\n", name); 81 | return (1); 82 | } 83 | 84 | nvlist_alloc(&props, NV_UNIQUE_NAME, KM_SLEEP); 85 | 86 | if (get_property(zhp, "je:poudriere:create", &default_je) == 0) { 87 | /* XXX: should be set by nvlist_add_string */ 88 | je_activate(zhp, default_je); 89 | 90 | nvlist_add_string(props, "canmount", "off"); 91 | nvlist_add_string(props, "mountpoint", "none"); 92 | nvlist_add_string(props, "je:poudriere:create", ""); 93 | } else { 94 | nvlist_add_string(props, "canmount", "noauto"); 95 | nvlist_add_string(props, "mountpoint", "none"); 96 | } 97 | 98 | zfs_prop_set_list(zhp, props); 99 | nvlist_free(props); 100 | 101 | zfs_close(zhp); 102 | 103 | return (0); 104 | } 105 | 106 | static int 107 | jectl_import(int argc, char **argv) 108 | { 109 | if (argc != 2) { 110 | fprintf(stderr, "usage: jectl import \n"); 111 | exit(1); 112 | } 113 | 114 | return (je_import(argv[1])); 115 | } 116 | JE_COMMAND(jectl, import, jectl_import); 117 | 118 | -------------------------------------------------------------------------------- /jectl_mount.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | 30 | #include "jectl.h" 31 | 32 | static int 33 | gather_cb(zfs_handle_t *zhp, void *arg __unused) 34 | { 35 | get_all_cb_t *cb = arg; 36 | 37 | if (zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM) { 38 | libzfs_add_handle(cb, zhp); 39 | zfs_iter_filesystems(zhp, gather_cb, cb); 40 | } else 41 | zfs_close(zhp); 42 | 43 | return (0); 44 | } 45 | 46 | static int 47 | mount_one(zfs_handle_t *zhp, void *arg __unused) 48 | { 49 | int error; 50 | 51 | error = zfs_mount(zhp, NULL, 0); 52 | 53 | zfs_close(zhp); 54 | return (error); 55 | } 56 | 57 | /* mount jail at given mountpoint */ 58 | int 59 | je_mount(zfs_handle_t *jds, const char *mountpoint) 60 | { 61 | zfs_handle_t *je; 62 | get_all_cb_t cb = { 0 }; 63 | 64 | if ((je = get_active_je(jds)) == NULL) { 65 | fprintf(stderr, 66 | "je_mount: cannot find active jail environment for '%s'\n", 67 | zfs_get_name(jds)); 68 | return (1); 69 | } 70 | 71 | /* 72 | * XXX: work-around dying jails 73 | * 74 | * A dying jail can prevent the backing dataset from being unmounted. 75 | * Do a forced unmount until dying jails can be cleaned properly. 76 | */ 77 | if (je_unmount(je, MNT_FORCE) != 0) { 78 | char mp[ZFS_MAXPROPLEN]; 79 | 80 | zfs_prop_get(je, ZFS_PROP_MOUNTPOINT, mp, sizeof(mp), 81 | NULL, NULL, 0, B_FALSE); 82 | fprintf(stderr, 83 | "je_mount: cannot unmount '%s' from '%s'\n", 84 | zfs_get_name(je), mp); 85 | return (1); 86 | } 87 | 88 | if (zfs_prop_set(je, "mountpoint", mountpoint) != 0) { 89 | fprintf(stderr, 90 | "je_mount: cannot set mountpoint for '%s' at '%s'\n", 91 | zfs_get_name(je), mountpoint); 92 | return (1); 93 | } 94 | 95 | libzfs_add_handle(&cb, je); 96 | zfs_iter_filesystems(je, gather_cb, &cb); 97 | zfs_foreach_mountpoint(lzh, cb.cb_handles, cb.cb_used, 98 | mount_one, NULL, B_FALSE); 99 | 100 | return (0); 101 | } 102 | 103 | static int 104 | jectl_mount(int argc, char **argv) 105 | { 106 | int error; 107 | zfs_handle_t *jds; 108 | 109 | if (argc != 3) { 110 | fprintf(stderr, "usage: jectl mount \n"); 111 | exit(1); 112 | } 113 | 114 | if ((jds = get_jail_dataset(argv[1])) == NULL) 115 | return (1); 116 | 117 | error = je_mount(jds, argv[2]); 118 | 119 | zfs_close(jds); 120 | 121 | return (error); 122 | } 123 | JE_COMMAND(jectl, mount, jectl_mount); 124 | -------------------------------------------------------------------------------- /jectl_unmount.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | 30 | #include "jectl.h" 31 | 32 | static void 33 | usage(void) 34 | { 35 | fprintf(stderr, "usage: jectl umount [-f] \n"); 36 | exit(1); 37 | } 38 | 39 | /* 40 | * unmount zhp and child datasets inheriting zhp's mountpoint 41 | */ 42 | int 43 | je_unmount(zfs_handle_t *zhp, int flags) 44 | { 45 | if (!zfs_is_mounted(zhp, NULL)) 46 | return (0); 47 | 48 | return (zfs_unmountall(zhp, flags)); 49 | } 50 | 51 | static int 52 | jectl_unmount(int argc, char **argv) 53 | { 54 | int c; 55 | int error; 56 | int flags = 0; 57 | zfs_handle_t *je, *jds; 58 | 59 | while ((c = getopt(argc, argv, "f")) != -1) { 60 | switch (c) { 61 | case 'f': 62 | flags |= MNT_FORCE; 63 | break; 64 | case '?': 65 | usage(); 66 | } 67 | } 68 | 69 | argc -= optind; 70 | argv += optind; 71 | 72 | if (argc != 1) { 73 | fprintf(stderr, "must provide jail name\n"); 74 | usage(); 75 | } 76 | 77 | if ((jds = get_jail_dataset(argv[0])) == NULL) 78 | return (1); 79 | 80 | if ((je = get_active_je(jds)) == NULL) { 81 | zfs_close(jds); 82 | return (1); 83 | } 84 | 85 | error = je_unmount(je, flags); 86 | 87 | zfs_close(je); 88 | zfs_close(jds); 89 | 90 | return (error); 91 | } 92 | JE_COMMAND(jectl, umount, jectl_unmount); 93 | -------------------------------------------------------------------------------- /jectl_update.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | #include 30 | 31 | #include "jectl.h" 32 | 33 | struct compare_info { 34 | zfs_handle_t *je; 35 | zfs_handle_t *result; 36 | }; 37 | 38 | /* compare a property between two datasets */ 39 | static bool 40 | is_equal(zfs_handle_t *a, zfs_handle_t *b, const char *property) 41 | { 42 | char *astr, *bstr; 43 | int error1, error2; 44 | 45 | error1 = get_property(a, property, &astr); 46 | error2 = get_property(b, property, &bstr); 47 | 48 | /* neither dataset has this property */ 49 | if (error1 && error2) 50 | return (true); 51 | /* one of the datasets has this property */ 52 | else if (error1 || error2) 53 | return (false); 54 | 55 | return (strcmp(astr, bstr) == 0); 56 | } 57 | 58 | static int 59 | compare_je(zfs_handle_t *zhp, void *arg) 60 | { 61 | struct compare_info *ci __unused; 62 | zfs_handle_t *je; 63 | char *v1, *v2; 64 | 65 | ci = arg; 66 | 67 | if (ci->result != NULL) 68 | je = ci->result; 69 | else 70 | je = ci->je; 71 | 72 | /* dont close our handle */ 73 | if (je == zhp) 74 | return (0); 75 | 76 | if (!is_equal(je, zhp, "je:poudriere:jailname")) 77 | goto done; 78 | if (!is_equal(je, zhp, "je:poudriere:overlaydir")) 79 | goto done; 80 | if (!is_equal(je, zhp, "je:poudriere:packagelist")) 81 | goto done; 82 | 83 | if (get_property(je, "je:poudriere:freebsd_version", &v1) != 0) 84 | goto done; 85 | if (get_property(zhp, "je:poudriere:freebsd_version", &v2) != 0) 86 | goto done; 87 | 88 | if (strtoul(v1, NULL, 10) < strtoul(v2, NULL, 10)) { 89 | if (ci->result != NULL) 90 | zfs_close(ci->result); 91 | ci->result = zhp; 92 | return (0); 93 | } 94 | 95 | done: 96 | zfs_close(zhp); 97 | return (0); 98 | } 99 | 100 | /* 101 | * only handles when FreeBSD_version is bumped 102 | * needs a more sophisticated update mechanism. 103 | */ 104 | static zfs_handle_t * 105 | je_next(zfs_handle_t *jds) 106 | { 107 | struct compare_info ci; 108 | zfs_handle_t *root, *je; 109 | 110 | if ((je = get_active_je(jds)) == NULL) { 111 | fprintf(stderr, "cannot find active jail environment: %s\n", zfs_get_name(jds)); 112 | return (NULL); 113 | } 114 | 115 | root = zfs_open(lzh, jepool, ZFS_TYPE_FILESYSTEM); 116 | 117 | ci.je = je; 118 | ci.result = NULL; 119 | zfs_iter_filesystems(root, compare_je, &ci); 120 | 121 | zfs_close(je); 122 | zfs_close(root); 123 | 124 | if (ci.result == NULL) 125 | return (NULL); 126 | 127 | return (je_copy(ci.result, jds)); 128 | } 129 | 130 | static int 131 | je_update(zfs_handle_t *jds) 132 | { 133 | int error; 134 | zfs_handle_t *next; 135 | 136 | /* no update found, return with no error */ 137 | if ((next = je_next(jds)) == NULL) 138 | return (0); 139 | 140 | error = je_swapin(jds, next); 141 | 142 | zfs_close(next); 143 | 144 | return (error); 145 | } 146 | 147 | static int 148 | jectl_update(int argc, char **argv) 149 | { 150 | int error; 151 | zfs_handle_t *jds; 152 | 153 | if (argc < 2 || argc > 3) { 154 | fprintf(stderr, "usage: jectl update [mountpoint]\n"); 155 | exit(1); 156 | } 157 | 158 | if ((jds = get_jail_dataset(argv[1])) == NULL) 159 | return (1); 160 | 161 | if (je_update(jds) != 0) 162 | fprintf(stderr, "cannot update '%s'\n", zfs_get_name(jds)); 163 | 164 | if (argc == 3) { 165 | error = je_mount(jds, argv[2]); 166 | } else 167 | error = 0; 168 | 169 | zfs_close(jds); 170 | 171 | return (error); 172 | } 173 | JE_COMMAND(jectl, update, jectl_update); 174 | -------------------------------------------------------------------------------- /jectl_util.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 | * 4 | * Copyright (c) 2022 Klara Inc. 5 | * Copyright (c) 2022 Rob Wing 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 1. Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | #include 29 | #include 30 | #include 31 | 32 | #include "jectl.h" 33 | 34 | /* get dataset for the given jail */ 35 | zfs_handle_t * 36 | get_jail_dataset(const char *jailname) 37 | { 38 | char jds_name[ZFS_MAXPROPLEN]; 39 | 40 | /* path to jail dataset, zroot/JAIL/$jailname */ 41 | snprintf(jds_name, sizeof(jds_name), "%s/%s", jeroot, jailname); 42 | 43 | return (zfs_open(lzh, jds_name, ZFS_TYPE_FILESYSTEM)); 44 | } 45 | 46 | /* get active jail environment */ 47 | zfs_handle_t * 48 | get_active_je(zfs_handle_t *jds) 49 | { 50 | char *je_name; 51 | 52 | if (get_property(jds, "je:active", &je_name) != 0) 53 | return (NULL); 54 | 55 | return (zfs_open(lzh, je_name, ZFS_TYPE_FILESYSTEM)); 56 | } 57 | 58 | int 59 | get_property(zfs_handle_t *zhp, const char *property, char **val) 60 | { 61 | int error; 62 | nvlist_t *nvl, *propval; 63 | 64 | if ((nvl = zfs_get_user_props(zhp)) == NULL) 65 | return (1); 66 | 67 | if ((error = nvlist_lookup_nvlist(nvl, property, &propval)) != 0) 68 | return (error); 69 | 70 | if ((error = nvlist_lookup_string(propval, ZPROP_VALUE, val)) != 0) 71 | return (error); 72 | 73 | /* user property has been "unset" */ 74 | if (strcmp(*val, "") == 0) 75 | return (1); 76 | 77 | return (0); 78 | } 79 | 80 | /* 81 | * copy user properties from src to target 82 | */ 83 | static int 84 | je_copy_user_props(zfs_handle_t *src, zfs_handle_t *target) 85 | { 86 | struct nvpair *nvp; 87 | nvlist_t *nvl, *propval, *nnvl; 88 | char *value; 89 | 90 | if (nvlist_alloc(&nnvl, NV_UNIQUE_NAME, 0) != 0) 91 | return (ENOMEM); 92 | 93 | nvl = zfs_get_user_props(src); 94 | 95 | /* 96 | * This seems like a hack. 97 | * Trying to set the nvlist of src on target 98 | * without going through a temporary nvlist 99 | * doesn't work. I'm curious why that is. 100 | */ 101 | nvp = NULL; 102 | while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { 103 | nvpair_value_nvlist(nvp, &propval); 104 | nvlist_lookup_string(propval, ZPROP_VALUE, &value); 105 | nvlist_add_string(nnvl, nvpair_name(nvp), value); 106 | } 107 | 108 | /* XXX: hmm..not sure if this should be set for every copy */ 109 | nvlist_add_string(nnvl, "canmount", "noauto"); 110 | 111 | zfs_prop_set_list(target, nnvl); 112 | 113 | nvlist_free(nnvl); 114 | return (0); 115 | } 116 | 117 | 118 | /* 119 | * Do the dirty work of copying a dataset: 120 | * - take a snapshot of src 121 | * - clone that snapshot to dest 122 | * - return zfs handle to the clone (i.e., a new dataset) 123 | */ 124 | static zfs_handle_t * 125 | je_copy_impl(zfs_handle_t *src, const char *dest) 126 | { 127 | int error; 128 | zfs_handle_t *snapshot, *target; 129 | char snapshot_name[ZFS_MAX_DATASET_NAME_LEN]; 130 | 131 | /* take a snapshot of target dataset */ 132 | snprintf(snapshot_name, sizeof(snapshot_name), "%s@%s", zfs_get_name(src), "jectl"); 133 | if (!zfs_dataset_exists(lzh, snapshot_name, ZFS_TYPE_SNAPSHOT) && 134 | zfs_snapshot(lzh, snapshot_name, B_FALSE, NULL) != 0) 135 | return (NULL); 136 | 137 | if ((snapshot = zfs_open(lzh, snapshot_name, ZFS_TYPE_SNAPSHOT)) == NULL) 138 | return (NULL); 139 | 140 | error = zfs_clone(snapshot, dest, NULL); 141 | 142 | zfs_close(snapshot); 143 | 144 | if (error != 0) 145 | return (NULL); 146 | 147 | if ((target = zfs_open(lzh, dest, ZFS_TYPE_FILESYSTEM)) != NULL) 148 | je_copy_user_props(src, target); 149 | 150 | return (target); 151 | } 152 | 153 | /* 154 | * copy src to target, the copied dataset becomes a child of target 155 | */ 156 | zfs_handle_t * 157 | je_copy(zfs_handle_t *src, zfs_handle_t *target) 158 | { 159 | char dest[ZFS_MAX_DATASET_NAME_LEN]; 160 | zfs_handle_t *je; 161 | char *name; 162 | 163 | name = strdup(zfs_get_name(src)); 164 | 165 | snprintf(dest, sizeof(dest), "%s/%s", zfs_get_name(target), basename(name)); 166 | 167 | if (zfs_dataset_exists(lzh, dest, ZFS_TYPE_FILESYSTEM)) { 168 | je = zfs_open(lzh, dest, ZFS_TYPE_FILESYSTEM); 169 | } else { 170 | je = je_copy_impl(src, dest); 171 | } 172 | 173 | zfs_close(src); 174 | free(name); 175 | return (je); 176 | } 177 | 178 | static int 179 | destroy_cb(zfs_handle_t *zhp, void *arg __unused) 180 | { 181 | zfs_destroy(zhp, false); 182 | return (0); 183 | } 184 | 185 | int 186 | je_destroy(zfs_handle_t *zhp) 187 | { 188 | zfs_iter_dependents(zhp, B_TRUE, destroy_cb, NULL); 189 | return (zfs_destroy(zhp, false)); 190 | } 191 | 192 | static int 193 | rename_cb(zfs_handle_t *src, void *arg) 194 | { 195 | zfs_handle_t *target; 196 | struct renameflags flags = { 0 }; 197 | char *name; 198 | char dest[ZFS_MAX_DATASET_NAME_LEN]; 199 | 200 | target = arg; 201 | 202 | name = strdup(zfs_get_name(src)); 203 | 204 | snprintf(dest, sizeof(dest), "%s/%s", zfs_get_name(target), basename(name)); 205 | 206 | zfs_rename(src, dest, flags); 207 | 208 | zfs_close(src); 209 | free(name); 210 | return (0); 211 | } 212 | 213 | /* 214 | * move (rename) all child datasets from src to target 215 | */ 216 | static int 217 | je_rename(zfs_handle_t *src, zfs_handle_t *target) 218 | { 219 | zfs_iter_filesystems(src, rename_cb, target); 220 | 221 | return (0); 222 | } 223 | 224 | /* 225 | * set target to be the active jail environment 226 | */ 227 | int 228 | je_swapin(zfs_handle_t *jds, zfs_handle_t *target) 229 | { 230 | zfs_handle_t *src; 231 | 232 | if ((src = get_active_je(jds)) == NULL) { 233 | zfs_prop_set(jds, "je:active", zfs_get_name(target)); 234 | return (0); 235 | } 236 | 237 | /* already the active dataset */ 238 | if (strcmp(zfs_get_name(src), zfs_get_name(target)) == 0) { 239 | zfs_close(src); 240 | return (0); 241 | } 242 | 243 | if (je_unmount(src, 0) != 0 || je_unmount(target, 0) != 0) { 244 | zfs_close(src); 245 | return (1); 246 | } 247 | 248 | /* move child datasets from src to target */ 249 | je_rename(src, target); 250 | 251 | /* set new jail environment */ 252 | zfs_prop_set(jds, "je:active", zfs_get_name(target)); 253 | 254 | zfs_close(src); 255 | return (0); 256 | } 257 | -------------------------------------------------------------------------------- /overview-generate-je.txt: -------------------------------------------------------------------------------- 1 | Brief explanation of "generate-je.sh", a script used with poudriere-image(8) 2 | to generate a ZFS send stream. The generated stream is used to create a 3 | new jail or to update an existing jail (that was previously created by generate-je). 4 | 5 | When using this script, the results of poudriere-image are contolled by 6 | the -t flag. The two available options are "-t zfs+send" or "-t zfs+send+be". 7 | 8 | Before showing examples, this is the zfs context being operated in: 9 | % zfs list -r zroot/JAIL 10 | NAME USED AVAIL REFER MOUNTPOINT 11 | zroot/JAIL 24K 82.2G 24K none 12 | 13 | The first option, "-t zfs+send", generates a send stream used to create 14 | a new jail (required poudriere-image arguments omitted for brevity): 15 | % poudriere image -t zfs+send -B ~/generate-je.sh -n stream 16 | 17 | The above command generates a send stream named "stream.full.zfs": 18 | % cat stream.full.zfs | zfs recv -u zroot/JAIL/main 19 | 20 | After receiving the stream, the layout looks like: 21 | % zfs list -r zroot/JAIL 22 | NAME USED AVAIL REFER MOUNTPOINT 23 | zroot/JAIL 609M 81.6G 24K none 24 | zroot/JAIL/main 609M 81.6G 24K none 25 | zroot/JAIL/main/default 609M 81.6G 609M none 26 | zroot/JAIL/main/default/config 24K 81.6G 24K none 27 | 28 | The second option, "-t zfs+send+be", generates a send stream to update 29 | an existing jail: 30 | % poudriere image -t zfs+send+be -B ~/generate-je.sh -n stream 31 | 32 | The above command generates a send stream named "stream.je.zfs": 33 | % cat stream.je.zfs | zfs recv -u zroot/JAIL/main/je 34 | 35 | After receiving the stream, the layout looks like: 36 | % zfs list -r zroot/JAIL 37 | NAME USED AVAIL REFER MOUNTPOINT 38 | zroot/JAIL 1.19G 81.0G 24K none 39 | zroot/JAIL/main 1.19G 81.0G 24K none 40 | zroot/JAIL/main/default 609M 81.0G 609M none 41 | zroot/JAIL/main/default/config 24K 81.0G 24K none 42 | zroot/JAIL/main/je 609M 81.0G 609M none 43 | 44 | The config dataset contains data that is to persist between jail upgrades. 45 | For example, if the desire is to have /etc/rc.conf and /etc/master.passwd 46 | remain unchanged between upgrades, one can use poudriere-image's overlay 47 | directory feature accomplish this. 48 | 49 | First, an overlay directory needs to be created: 50 | % mkdir /home/rew/overlay 51 | % cd /home/rew/overlay 52 | 53 | Then, create symbolic links in the overlay directory for the files 54 | and/or directories that are to remain unchanged between upgrades: 55 | % mkdir etc 56 | % ln -s /config/rc.conf etc 57 | % ln -s /config/master.passwd etc 58 | 59 | Now that the overlay directory is configured, run poudriere-image: 60 | % poudriere image -t zfs+send -B ~/generate-je.sh -n stream -c /home/rew/overlay 61 | 62 | When creating a jail with an overlay directory, the config dataset will 63 | be bootstrapped with the files of interest, if the file/directory exists. 64 | Given the overlay example above, the config dataset will contain the default 65 | etc/master.passwd as it would be from a standard base installation of FreeBSD. 66 | 67 | It is important (i.e., necessary) to use the same overlay directory when 68 | generating a stream to upgrade an existing jail: 69 | % poudriere image -t zfs+send+be -B ~/generate-je.sh -n stream -c /home/rew/overlay 70 | 71 | The reason for this is so the symbolic link gets hooked up to etc/master.passwd 72 | correctly. Otherwise, the symbolic link will not exist and the contents of 73 | etc/master.passwd will be that from a standard base install. 74 | 75 | ZFS user properties set by generate-je.sh: 76 | je:version (e.g., 12.2-RELEASE-p8) 77 | je:poudriere:create (create jail stream was generated) 78 | je:poudriere:jailname (name of poudriere jail used) 79 | je:poudriere:overlaydir (name of overlay directory used) 80 | je:poudriere:packagelist (name of package list used) 81 | je:poudriere:freebsd_version (output of uname -U => 1301000) 82 | 83 | These properties are used by jectl. 84 | -------------------------------------------------------------------------------- /overview-jectl.txt: -------------------------------------------------------------------------------- 1 | Overview of jectl: 2 | 3 | What is it? 4 | 5 | jectl is the nexus between poudriere and jail.conf 6 | 7 | What is its purpose? 8 | 9 | To ease the process of updating jails. 10 | 11 | How does it do that? 12 | 13 | By using ZFS to manage the underlying jail filesystem, called jail 14 | environments. jectl is to jail environments as bectl is to boot 15 | environments. The concept is the same, but the behavior of jectl is 16 | slightly different. 17 | 18 | Prior reading? 19 | It assumed the reader has a prior understanding of poudriere. 20 | See overview-generate-je.txt and overview-zfs-layout.txt as well. 21 | 22 | A walkthrough of usage: 23 | 24 | See or search MANPAGE in this document for explanation of flags: 25 | % jectl 26 | 27 | Run the following before/after jectl commands to see what's happening: 28 | % zfs list -r zroot/JE zroot/JAIL 29 | 30 | Generate a ZFS send stream using poudriere-image(8) and generate-je.sh: 31 | 32 | % poudriere jail -c -j rew -v 12.0-RELEASE 33 | % poudriere image -t zfs+send -B generate-je.sh -s 1G -j rew -o /home/rew -n stream 34 | [ ... snipped ... ] 35 | [00:00:23] Image available at: /home/rew/stream.full.zfs 36 | 37 | Create a jail dataset named klara from the generated ZFS stream: 38 | % cat stream.full.zfs | jectl import klara 39 | 40 | Where 'klara' corresponds to a jail defined in jail.conf(5): 41 | 42 | path = /$name; 43 | exec.prepare = "jectl mount $name $path"; 44 | exec.start = "/bin/sh /etc/rc"; 45 | exec.stop = "/bin/sh /etc/rc.shutdown"; 46 | 47 | klara { } 48 | 49 | Check info for klara jail: 50 | % jectl dump klara 51 | Jail name: klara 52 | Environments: 53 | 1. Name: default (ACTIVE) 54 | branch: 12.0-RELEASE-p13 55 | version: 1200086 56 | poudriere-jail: rew 57 | 58 | Starting and stopping the jail behaves as normal: 59 | % service jail start 60 | Starting jails: klara. 61 | 62 | % service jail stop 63 | Stopping jails: klara. 64 | 65 | After some amount of time passes..it is time to update. 66 | 67 | The dirty work of updating is handed off to poudriere, there are two ways 68 | to do this. One way is to create a new poudriere jail with the desired version: 69 | % poudriere jail -c -j next -v 12.1-RELEASE 70 | 71 | And then generate a jail environment using poudriere + generate-je.sh. 72 | Note that passing -t zfs+send+be creates a jail environment only: 73 | % poudriere image -t zfs+send+be -B generate-je.sh -s 1G -j next -o /home/rew -n stream 74 | [ ... snipped ... ] 75 | [00:00:37] Image available at: /home/rew/stream.je.zfs 76 | 77 | Import the generated ZFS stream: 78 | % cat stream.je.zfs | jectl import 12.1-RELEASE 79 | 80 | Set the active jail environment for the klara jail: 81 | % jectl activate klara 12.1-RELEASE 82 | 83 | Check the updated info for klara jail: 84 | % jectl dump klara 85 | Jail name: klara 86 | Environments: 87 | 1. Name: default (ACTIVE) 88 | branch: 12.0-RELEASE-p13 89 | version: 1200086 90 | poudriere-jail: rew 91 | 2. Name: 12.1-RELEASE 92 | branch: 12.1-RELEASE-p13 93 | version: 1201000 94 | poudriere-jail: next 95 | 96 | The next time the klara jail is started, it will be running the newer version of FreeBSD. 97 | 98 | Continuing with the example above, here is the second way to update a jail. 99 | Again, the dirty work of updating is deferred to poudriere: 100 | % poudriere jail -u -j next -t 12.2-RELEASE 101 | 102 | Once the poudriere jail is updated, generate a jail environment from it: 103 | % poudriere image -t zfs+send+be -B generate-je.sh -s 1G -j next -o /home/rew -n stream 104 | [ ... snipped ... ] 105 | [00:00:21] Image available at: /home/rew/stream.je.zfs 106 | 107 | And then import it: 108 | % cat stream.je.zfs | jectl import 12.2-RELEASE 109 | 110 | Inspect the imported jail environment: 111 | % jectl dump 112 | [ ... snippped .. ] 113 | Available jail environments: 114 | [ ... snippped .. ] 115 | 3. Name: 12.2-RELEASE 116 | branch: 12.2-RELEASE-p15 117 | version: 1202000 118 | poudriere-jail: next 119 | 120 | Before updating, check active jail environment of klara: 121 | % jectl dump klara 122 | Jail name: klara 123 | Environments: 124 | [ ... snippped .. ] 125 | 2. Name: 12.1-RELEASE (ACTIVE) 126 | branch: 12.1-RELEASE-p13 127 | version: 1201000 128 | poudriere-jail: next 129 | 130 | Then update klara jail: 131 | % jectl update klara 132 | 133 | Verify that it updated: 134 | % jectl dump klara 135 | Jail name: klara 136 | Environments: 137 | [ ... snippped .. ] 138 | 3. Name: 12.2-RELEASE (ACTIVE) 139 | branch: 12.2-RELEASE-p15 140 | version: 1202000 141 | poudriere-jail: next 142 | 143 | When jectl does an update, it compares the following properties between 144 | the active jail environment dataset and imported jail environment datasets. 145 | If the jailname, overlaydir, and packagelist are the same; and if the 146 | freebsd_version for the active environment is less than the imported jail 147 | environment, an update is performed. Therefore, if freebsd_version is 148 | not bumped, an update is not recognized. List of properties: 149 | 150 | je:version (unused, mentioned for patch updates) 151 | je:poudriere:jailname (name of poudriere jail) 152 | je:poudriere:overlaydir (name of overlay directory used) 153 | je:poudriere:packagelist (name of package list used) 154 | je:poudriere:freebsd_version (output of uname -U => 1301000) 155 | 156 | With all that explained, consider the following workflow. 157 | 158 | Use the jectl's update subcommand in jail.conf(5): 159 | path = /$name; 160 | exec.prepare = "jectl update $name $path"; 161 | exec.start = "/bin/sh /etc/rc"; 162 | exec.stop = "/bin/sh /etc/rc.shutdown"; 163 | 164 | klara { } 165 | 166 | Update the poudriere jail, generate a stream, and import it. 167 | % poudriere jail -u -j next -t 13.1-BETA2 168 | % poudriere image -t zfs+send+be -B generate-je.sh -s 1G -j next -o /home/rew -n stream 169 | % cat stream.je.zfs | jectl import 13.1-BETA2 170 | 171 | Restart the klara jail: 172 | % service jail restart 173 | 174 | Assuming no errors, the klara jail will now be running 13.1-BETA2 175 | -------------------------------------------------------------------------------- /overview-zfs-layout.txt: -------------------------------------------------------------------------------- 1 | The following is a proposed overview of the zfs layout for jail environments. 2 | 3 | First off, there is a root dataset to contain the jail datasets and jail environment datasets: 4 | zroot/JAIL 5 | 6 | A jail dataset is the top-level dataset for a given jail. The name of 7 | a jail dataset should correspond to a jail name in jail.conf: 8 | zroot/JAIL/$jailname 9 | 10 | A jail dataset can have one or more jail environments associated with it, 11 | example of a jail with one jail environment: 12 | zroot/JAIL/$jailname 13 | zroot/JAIL/$jailname/$jail_environment 14 | 15 | An example of a jail with three different jail environments: 16 | zroot/JAIL/$jailname 17 | zroot/JAIL/$jailname/12.1-RELEASE-p0 18 | zroot/JAIL/$jailname/12.1-RELEASE-p4 19 | zroot/JAIL/$jailname/12.1-RELEASE-p7 20 | 21 | The jail dataset (i.e., zroot/JAIL/$jailname) uses a zfs user property, 22 | `je:active`, to determine which jail environment to use. The 23 | `je:active` property is managed by the jectl(?) command line utility, 24 | and should not need to be set explicitly. 25 | 26 | Datasets that are a child of or added to the currently active jail 27 | environment will persist after the jail environment has been updated. 28 | 29 | Here's an example, let's say we have a jail named 'www': 30 | zroot/JAIL/www 31 | 32 | And there are two jail environments available to `www`: 33 | zroot/JAIL/www 34 | zroot/JAIL/www/12.1-RELEASE-p0 35 | zroot/JAIL/www/12.1-RELEASE-p4 36 | 37 | Let's assume `www` jail also has two persistent datasets and that the 38 | active jail environment is 12.1-RELEASE-p0: 39 | zroot/JAIL/www 40 | zroot/JAIL/www/12.1-RELEASE-p0 41 | zroot/JAIL/www/12.1-RELEASE-p0/persistent0 42 | zroot/JAIL/www/12.1-RELEASE-p0/persistent1 43 | zroot/JAIL/www/12.1-RELEASE-p4 44 | 45 | To upgrade `www` from 12.1-RELEASE-p0 to 12.1-RELEASE-p4: 46 | jectl --jail www --activate 12.1-RELEASE-p4 47 | 48 | The above command will now have the zfs layout looking like: 49 | zroot/JAIL/www 50 | zroot/JAIL/www/12.1-RELEASE-p0 51 | zroot/JAIL/www/12.1-RELEASE-p4 52 | zroot/JAIL/www/12.1-RELEASE-p4/persistent0 53 | zroot/JAIL/www/12.1-RELEASE-p4/persistent1 54 | 55 | 56 | The persistent datasets are kept under the active jail environment, to 57 | take advantage of mountpoint inheritance. When a jail environment is 58 | swapped out, a few things occur: 59 | 1. The active (soon to be old), jail environment is unmounted. 60 | 2. The persistent datasets are moved over to the new jail environment 61 | 3. The mountpoint is set on the new jail environment 62 | 4. The jail dataset sets 'je:active' to reflect the new jail environment. 63 | 64 | --------------------------------------------------------------------------------