Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,16 @@ forge latest 10.17M 2021-05-27 00:32:08
%
```

To remove an image, give its name and tag (the tag defaults to `latest`):
```
% sudo cblock images --remove freebsd-13_4:latest
Removed freebsd-13_4:latest
%
```
If the image has other tags, only this tag is removed. The image itself is
removed with its last tag, and cblockd refuses to remove an image that a
running cellblock or build is using.

### Launching your Cellblock

Now we are ready to launch the container. Note with `--host-networking` the cblock daemon
Expand Down
50 changes: 48 additions & 2 deletions src/cblock/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,28 @@
struct image_config {
int i_quiet;
int i_do_prune;
int i_do_remove;
};

static struct option image_options[] = {
{ "help", no_argument, 0, 'h' },
{ "quiet", no_argument, 0, 'q' },
{ "prune", no_argument, 0, 'p' },
{ "remove", no_argument, 0, 'r' },
{ 0, 0, 0, 0 }
};

static void
image_usage(void)
{
(void) fprintf(stderr,
"Usage: cblock images [OPTIONS]\n\n"
"Usage: cblock images [OPTIONS]\n"
" cblock images --remove NAME[:TAG] ...\n\n"
"Options\n"
" -h, --help Print help\n"
" -p, --prune Remove stopped/dead images\n"
" -r, --remove Remove image tags, and each image\n"
" with its last tag unless it is in use\n"
" -q, --quiet Do not print column headers\n");
exit(1);
}
Expand All @@ -78,6 +83,37 @@ image_prune(struct image_config *icp __attribute__((unused)), int ctlsock)
sock_ipc_from_sock_to_tty(ctlsock);
}

static void
image_remove(int argc, char *argv[], int ctlsock)
{
struct cblock_generic_command arg;
char *marshalled;
vec_t *vec;
int k;

vec = vec_init(argc + 1);
if (vec == NULL) {
err(1, "vec_init failed");
}
for (k = 0; k < argc; k++) {
vec_append(vec, argv[k]);
}
vec_finalize(vec);
marshalled = vec_marshal(vec);
if (marshalled == NULL) {
err(1, "vec_marshal failed");
}
bzero(&arg, sizeof(arg));
snprintf(arg.p_cmdname, sizeof(arg.p_cmdname), "image_remove");
arg.p_mlen = vec->vec_marshalled_len;
if (sock_ipc_write_u32(ctlsock, PRISON_IPC_GENERIC_COMMAND) == -1 ||
proto_send_generic_command(ctlsock, &arg, marshalled) == -1) {
errx(1, "failed to send image request");
}
vec_free(vec);
sock_ipc_from_sock_to_tty(ctlsock);
}

static void
image_get(struct image_config *icp __attribute__((unused)), int ctlsock)
{
Expand All @@ -102,7 +138,7 @@ image_main(int argc, char *argv [], int ctlsock)
reset_getopt_state();
while (1) {
option_index = 0;
c = getopt_long(argc, argv, "qhp", image_options,
c = getopt_long(argc, argv, "qhpr", image_options,
&option_index);
if (c == -1) {
break;
Expand All @@ -111,6 +147,9 @@ image_main(int argc, char *argv [], int ctlsock)
case 'p':
ic.i_do_prune = 1;
break;
case 'r':
ic.i_do_remove = 1;
break;
case 'q':
ic.i_quiet = 1;
break;
Expand All @@ -124,6 +163,13 @@ image_main(int argc, char *argv [], int ctlsock)
}
argc -= optind;
argv += optind;
if (ic.i_do_remove) {
if (argc == 0) {
image_usage();
}
image_remove(argc, argv, ctlsock);
return (0);
}
if (ic.i_do_prune) {
image_prune(&ic, ctlsock);
return (0);
Expand Down
6 changes: 6 additions & 0 deletions src/cblockd/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ static struct command_ent command_list[] = {
{ "instance_prune", "cmd_instance_prune.sh" },
{ "image_prune", "cmd_image_prune.sh" },
{ "image_list", "cmd_image.sh" },
{ "image_remove", "cmd_image_remove.sh" },
{ NULL, NULL }
};

Expand Down Expand Up @@ -128,6 +129,11 @@ dispatch_generic_command(int sock)
vec_append(cmd_vec, script_path);
vec_append(cmd_vec, "-R");
vec_append(cmd_vec, gcfg.c_data_dir);
/*
* The client's arguments follow "--" so the script can't
* take any of them as an option, such as another -R.
*/
vec_append(cmd_vec, "--");
if (vec_merge(vec, cmd_vec) != 0) {
err(1, "error argv vectors: HINT: increase cmd_vec");
}
Expand Down
3 changes: 2 additions & 1 deletion src/libcblock/vec.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ vec_merge(vec_t *from, vec_t *to)
{
int k;

if ((from->vec_used + to->vec_used) > to->vec_alloc) {
/* Leave room for the NULL that vec_finalize() adds. */
if ((from->vec_used + to->vec_used) >= to->vec_alloc) {
fprintf(stderr, "vec_append: overflow\n");
return (VEC_OVERFLOW);
}
Expand Down
1 change: 1 addition & 0 deletions src/shell/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
TARGETS=cmd_image.sh \
cmd_image_remove.sh \
cmd_image_prune.sh \
cmd_instance_prune.sh \
common.sh \
Expand Down
14 changes: 1 addition & 13 deletions src/shell/cmd_image_prune.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,7 @@ do_image_purge()
continue
fi
printf "Removing un-referenced image: %s\n" $(basename $image)
case $CBLOCK_FS in
zfs)
vol=$(path_to_vol $image)
zfs destroy -r "$vol"
rm -fr "$vol"
;;
ufs)
chflags -R noschg "$image"
rm -Wfr "$image"
;;
*)
echo "No match on CBLOCK_FS"
esac
remove_image "$image"
done
}

Expand Down
94 changes: 94 additions & 0 deletions src/shell/cmd_image_remove.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/bin/sh
#
# Copyright (c) 2026 Christian S.J. Peron
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# Remove image tags: cmd_image_remove.sh -R DATA_DIR -- NAME[:TAG] ...
# A missing tag means "latest". The image itself is deleted with its
# last tag, unless a running instance or build is using it.
#
. "$(dirname "$0")/common.sh"
data_dir=""

remove_tag()
{
name="${1%%:*}"
tag="latest"
case "$1" in
*:*)
tag="${1#*:}"
;;
esac
# The names come from the client. Keep them inside the images
# directory.
case "$name:$tag" in
-*|*/*|:*|*:|*:*:*)
echo "${1}: invalid image name"
return 1
;;
esac
link="${data_dir}/images/${name}:${tag}"
if [ ! -h "$link" ]; then
echo "${name}:${tag}: no such image"
return 1
fi
image=$(readlink "$link")
for other in $(symlinks); do
if [ "$other" != "$link" ] && [ "$(readlink "$other")" = "$image" ]; then
rm "$link"
echo "Untagged ${name}:${tag}"
return 0
fi
done
if image_in_use "$image"; then
echo "${name}:${tag}: image is in use, not removed"
return 1
fi
rm "$link"
remove_image "$image" || return 1
echo "Removed ${name}:${tag}"
}

while getopts "R:" opt; do
case $opt in
R)
data_dir="$OPTARG"
;;
*)
exit 1
;;
esac
done
shift $((OPTIND - 1))

if [ ! "$data_dir" ]; then
echo "Must specify cblock data directory -R"
exit 1
fi

status=0
for arg in "$@"; do
remove_tag "$arg" || status=1
done
exit $status
40 changes: 40 additions & 0 deletions src/shell/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ symlinks()
-mindepth 1 -maxdepth 1 -type l
}

# Delete an image directory (or dataset) under ${data_dir}/images. The
# caller makes sure no tag points at it and nothing is using it.
remove_image()
{
case $CBLOCK_FS in
zfs)
zfs destroy -r "$(path_to_vol "$1")"
rm -fr "$1"
;;
ufs)
chflags -R noschg "$1"
rm -fr "$1"
;;
*)
echo "No match on CBLOCK_FS"
return 1
;;
esac
}

# Succeeds if a running instance or build is using the image in $1.
# Launches and builds mount the image's root as the lower layer of a
# unionfs (ufs), or clone a snapshot of its dataset (zfs).
image_in_use()
{
case $CBLOCK_FS in
zfs)
zfs list -H -o clones -t snapshot -d 1 "$(path_to_vol "$1")" | \
grep -qv '^-$'
;;
ufs)
mount -p | awk -v dir="$1/root" '
{ n = split($1, layer, ":")
for (i = 1; i <= n; i++)
if (layer[i] == dir) found = 1 }
END { exit !found }'
;;
esac
}

ip_to_int()
{
echo "$1" | awk -F. '{ print (($1 * 256 + $2) * 256 + $3) * 256 + $4 }'
Expand Down
Loading