@@ -1278,8 +1278,8 @@ found on the device. It is a partition handler and it runs before any image is i
}
});
-BTRFS Handler
--------------
+BTRFS Partition Handler
+-----------------------
This handler is activated if support for BTRFS is on. It allows to created and delete subvolumes
during an update.
@@ -1299,9 +1299,57 @@ during an update.
})
-If `mount` is set, SWUpdate will mount the device and the path is appenden to the
+If `mount` is set, SWUpdate will mount the device and the path is appended to the
mountpoint used with mount. If device is already mounted, path is the absolute path.
+BTRFS Snapshot Handler
+----------------------
+
+The handler allows to install a BTRFS snapshot created with the "btrfs send" command.
+SWUpdate is using the external "btrfs" utility, that must be installed on the target,
+and "btrfs receive" is executed by sending the stream to the command.
+
+All generic features are avaiulable, that means that an srtifact can be streamed by
+using the "installed-directly" attribute.
+
+
+::
+
+ images: (
+ {
+ filename = "btrfs-snapshot";
+ type = "btrfs-receive";
+ device = <optional, device with BTRFS fs, must be set if tomount is "true">;
+ properties: {
+ path = <mandatory, path where to install subvolume>;
+ btrfs-cmd = <optional, path to btrfs command>;
+ tomount = <boolean, optional, "true" / "false" >;
+ }
+ })
+
+If `tomount` is set, SWUpdate will temporary mount "device" as BTRFS filesystem and will try to install
+the snapshot to `path`. `btrfs-cmd` is optional, fallback is /usr/bin/btrfs.
+
+Generic Executor handler
+------------------------
+
+The BTRFS snapshot handler requires to stream an artifact after normal handling
+(decompression, decryption, etc.) to the external command "btrfs" without any temporary copy.
+The same infrastucture can be used to stream any artifact to any arbitrary external command
+that accepts the stream as stdin. This is done with the "executor" handler.
+
+
+::
+
+ {
+ filename = "test";
+ type = "executor";
+ properties: {
+ cmd = <mandatory, command to be executed in a pipe>;
+ }
+ }
+
+
Delta Update Handler
--------------------
@@ -7,13 +7,16 @@
#include <errno.h>
#include <stdio.h>
-#include <util.h>
#include <unistd.h>
#include <blkid/blkid.h>
+#include <pthread.h>
#include <btrfsutil.h>
#include "progress.h"
#include "swupdate_image.h"
#include "handler.h"
+#include <util.h>
+#include <pctl.h>
+#include "handler_helpers.h"
typedef enum {
BTRFS_UNKNOWN,
@@ -21,7 +24,34 @@ typedef enum {
BTRFS_DELETE_SUBVOLUME
} btrfs_op_t;
-void btrfs_handler(void);
+#define BTRFS_MOUNT(mount, device, mnt, subvol, path) do { \
+ if (mount) { \
+ if (!btrfs_mount(device, &mnt)) { \
+ ERROR("%s cannot be mounted with btrfs", device); \
+ return -1; \
+ } \
+ path = swupdate_strcat(2, mnt, subvol); \
+ } else \
+ path = strdup(subvol); \
+} while (0)
+
+/*
+ * This is just a wrapper as some parms are fixed
+ * (filesystem, etc)
+ */
+static bool btrfs_mount(const char *device, char **mnt)
+{
+ if (!mnt)
+ return false;
+
+ *mnt = swupdate_temporary_mount(MNT_DATA, device, "btrfs");
+ if (!*mnt) {
+ ERROR("%s cannot be mounted with btrfs", device);
+ return false;
+ }
+
+ return true;
+}
static int btrfs(struct img_type *img,
void __attribute__ ((__unused__)) *data)
@@ -32,7 +62,8 @@ static int btrfs(struct img_type *img,
char *subvol_path = dict_get_value(&img->properties, "path");
char *cmd = dict_get_value(&img->properties, "command");
- char *globalpath;
+ bool tomount = strtobool(dict_get_value(&img->properties, "mount"));
+ char *globalpath = NULL;
char *mountpoint = NULL;
op = IS_STR_EQUAL(cmd, "create") ? BTRFS_CREATE_SUBVOLUME :
@@ -42,22 +73,8 @@ static int btrfs(struct img_type *img,
ERROR("Wrong operation of btrfs filesystem: %s", cmd);
return -EINVAL;
}
- bool tomount = strtobool(dict_get_value(&img->properties, "mount"));
- if (tomount) {
- if (!strlen(img->device)) {
- ERROR("btrfs must be mounted, no device set");
- return -EINVAL;
- }
- DEBUG("Try to mount %s as BTRFS", mountpoint);
- mountpoint = swupdate_temporary_mount(MNT_DATA, img->device, "btrfs");
- if (!mountpoint) {
- ERROR("%s cannot be mounted with btrfs", img->device);
- return -1;
- }
- globalpath = alloca(strlen(mountpoint) + strlen(subvol_path) + 2);
- globalpath = strcat(mountpoint, subvol_path);
- } else
- globalpath = subvol_path;
+
+ BTRFS_MOUNT(tomount, img->device, mountpoint, subvol_path, globalpath);
DEBUG("%s subvolume %s...", (op == BTRFS_CREATE_SUBVOLUME) ? "Creating" : "Deleting", subvol_path);
switch (op) {
@@ -78,27 +95,74 @@ static int btrfs(struct img_type *img,
ret = -1;
}
- if (tomount) {
+ if (tomount && mountpoint) {
/*
* btrfs needs some time after creating a subvolume,
* so just delay here
*/
sleep(1);
- if (mountpoint) {
- swupdate_umount(mountpoint);
- }
+ swupdate_umount(mountpoint);
}
/*
* Declare that handler has finished
*/
swupdate_progress_update(100);
+ free(globalpath);
+
+ return ret;
+}
+
+static int install_btrfs_snapshot(struct img_type *img,
+ void __attribute__ ((__unused__)) *data)
+{
+ int ret = 0;
+ struct bgtask_handle btrfs_handle;
+ char *subvol_path = dict_get_value(&img->properties, "path");
+ bool tomount = strtobool(dict_get_value(&img->properties, "mount"));
+ const char *btrfscmd = dict_get_value(&img->properties, "btrfs-cmd");
+ char *globalpath = NULL;
+ char *mountpoint = NULL;
+
+ /*
+ * if no path for the command is found, the handler assumes that
+ * btrfs is in standard path
+ */
+ if (!btrfscmd)
+ btrfscmd = "/usr/bin/btrfs ";
+
+ BTRFS_MOUNT(tomount, img->device, mountpoint, subvol_path, globalpath);
+
+ btrfs_handle.cmd = btrfscmd;
+
+ /*
+ * Note: btrfs tool writes to stderr instead of stdout
+ * and SWUpdate will intercept and show the output as error
+ * even if they are not. Just redirect it to stdout to drop
+ * error messages
+ */
+ btrfs_handle.parms = swupdate_strcat(3, " receive ", globalpath, " 2>&1");
+ free(globalpath);
+ btrfs_handle.img = img;
+
+ ret = bgtask_handler(&btrfs_handle);
+
+ if (tomount && mountpoint)
+ swupdate_umount(mountpoint);
+ free(btrfs_handle.parms);
return ret;
}
__attribute__((constructor))
-void btrfs_handler(void)
+static void btrfs_handler(void)
{
register_handler("btrfs", btrfs,
PARTITION_HANDLER | NO_DATA_HANDLER, NULL);
}
+
+__attribute__((constructor))
+static void btrfs_receive_handler(void)
+{
+ register_handler("btrfs-receive", install_btrfs_snapshot,
+ IMAGE_HANDLER, NULL);
+}
@@ -1,10 +1,11 @@
/*
- * (C) Copyright 2022
+ * Copyright (C) 2024
* Stefano Babic, stefano.babic@swupdate.org.
*
* SPDX-License-Identifier: GPL-2.0-only
*/
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
@@ -13,12 +14,34 @@
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>
+#include <signal.h>
#include <unistd.h>
#include "handler_helpers.h"
#include "installer.h"
+#include "swupdate_image.h"
#include "pctl.h"
#include "util.h"
+struct thread_handle {
+ struct hnd_load_priv *hndtransfer;
+ struct img_type *img;
+};
+
+static void *copyimage_thread(void *p)
+{
+ struct thread_handle *hnd = (struct thread_handle *)p;
+ struct hnd_load_priv *priv = hnd->hndtransfer;
+ int ret = 0;
+
+ ret = copyimage(priv, hnd->img, handler_transfer_data);
+
+ if (ret) {
+ ERROR("Transferring image was not successful");
+ }
+ close(priv->fifo[FIFO_HND_WRITE]);
+ pthread_exit((void*)(intptr_t)ret);
+}
+
/*
* This is the copyimage's callback. When called,
* there is a buffer to be passed to curl connections
@@ -73,4 +96,86 @@ void *chain_handler_thread(void *data)
return (void *)ret;
}
+/*
+ * This receive a command from a handler as external command that must be executed.
+ * It performs the following:
+ * - creates pipes for internal IPC
+ * - creates a thread that run (fork) the command
+ * - execute copyimage with callback that write into the FIFO
+ * - wait until thread exits and returns the result
+ */
+int bgtask_handler(struct bgtask_handle *bg)
+{
+ int ret = 0;
+ struct hnd_load_priv data_handle;
+ struct thread_handle hnd;
+ char *cmd;
+
+ /*
+ * if no path for the command is found, the handler assumes that
+ * btrfs is in standard path
+ */
+ if (!bg->cmd || !bg->img)
+ return -EINVAL;
+
+ if (access(bg->cmd, X_OK)) {
+ ERROR("Handler requires %s, not found.", bg->cmd);
+ return -EINVAL;
+ }
+
+ /*
+ * Create one FIFO for each connection to be thread safe
+ */
+ if (pipe(data_handle.fifo) < 0) {
+ ERROR("Cannot create internal pipes, exit..");
+ return -EFAULT;
+ }
+
+ hnd.hndtransfer = &data_handle;
+ hnd.img = bg->img;
+ cmd = swupdate_strcat(3, bg->cmd, " ", bg->parms);
+
+ /*
+ * Starts a backgroud task to fill in the
+ * FIFO using copyimage
+ */
+ start_thread(copyimage_thread, &hnd);
+
+ /*
+ * Start to write in the FIFO - vene if the bg process is not started.
+ * If the FIFO becomes full, this thread will be paused until the bg
+ * process (and btrfs receive) will start to consume the data
+ */
+
+ ret = run_system_cmd_with_fdin(cmd, data_handle.fifo);
+ free(cmd);
+ return ret;
+}
+
+static int generic_executor(struct img_type *img,
+ void __attribute__ ((__unused__)) *data)
+{
+ int ret = 0;
+ struct bgtask_handle handle;
+ const char *cmd = dict_get_value(&img->properties, "cmd");
+
+ if (!cmd) {
+ ERROR("No cmd set, add cmd property");
+ return -EINVAL;
+ }
+
+ handle.cmd = cmd;
+ handle.parms = dict_get_value(&img->properties, "parms");
+ handle.img = img;
+
+ ret = bgtask_handler(&handle);
+ return ret;
+}
+
+__attribute__((constructor))
+static void executor_handler(void)
+{
+ register_handler("executor", generic_executor,
+ IMAGE_HANDLER, NULL);
+}
@@ -21,5 +21,12 @@ struct hnd_load_priv {
int exit_status;
};
-extern void *chain_handler_thread(void *data);
+struct bgtask_handle {
+ const char *cmd;
+ char *parms;
+ struct img_type *img;
+};
+
+void *chain_handler_thread(void *data);
extern int handler_transfer_data(void *data, const void *buf, size_t len);
+int bgtask_handler(struct bgtask_handle *bg);
Add a handler that supports snapshots created by "btrfs send" as artifact stored into the SWU. The handler needs that btrfs utility is installed on the system, and reports a runtime error if it is not found. It is implemented as image handler, and the image is streamed to a forked process that will run "btrfs receive" in background. To realize this, a generic way to stream an artifact to an external command is implemented. This allows to push the stream from the copyimage() to an external command that accept the data from stdin, like "btrfs receive" is doing. To extend this, a generic "executor" handler is provided, too, that allows to set the external command as one of its property. Signed-off-by: Stefano Babic <stefano.babic@swupdate.org> --- doc/source/handlers.rst | 54 +++++++++++++++++- handlers/btrfs_handler.c | 112 +++++++++++++++++++++++++++++-------- handlers/handler_helpers.c | 107 ++++++++++++++++++++++++++++++++++- include/handler_helpers.h | 9 ++- 4 files changed, 253 insertions(+), 29 deletions(-) -- 2.34.1