diff mbox series

[V2,26/36] delta: add process to download chunks

Message ID 20211114172733.71602-27-sbabic@denx.de
State Changes Requested
Headers show
Series DELTA Update | expand

Commit Message

Stefano Babic Nov. 14, 2021, 5:27 p.m. UTC
The delta handler must download the missing parts of an artifacts.
Because it runs with high rights, the download itself should be done by
a separate process to avoid to break privilege separation. The process
gets userid and group id from the configuration file, and as fallback
will run with the same rights as the installer.

The downloader will wait for requests, and it will write the downloaded
data into the IPC pipe. Only one process is allowed to talk to the
downloader at once. If more as one user will exist in the future, the
access to the downloader should be queued (not done at the present).

Signed-off-by: Stefano Babic <sbabic@denx.de>
---
 handlers/delta_downloader.c | 217 ++++++++++++++++++++++++++++++++++++
 handlers/delta_handler.h    |  37 ++++++
 include/delta_process.h     |  10 ++
 include/swupdate_status.h   |   3 +-
 4 files changed, 266 insertions(+), 1 deletion(-)
 create mode 100644 handlers/delta_downloader.c
 create mode 100644 handlers/delta_handler.h
 create mode 100644 include/delta_process.h
diff mbox series

Patch

diff --git a/handlers/delta_downloader.c b/handlers/delta_downloader.c
new file mode 100644
index 0000000..ddb7cdb
--- /dev/null
+++ b/handlers/delta_downloader.c
@@ -0,0 +1,217 @@ 
+/*
+ * (C) Copyright 2021
+ * Stefano Babic, sbabic@denx.de.
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+
+/*
+ * This is part of the delta handler. It is started as separate process
+ * and gets from the main task which chunks should be downloaded.
+ * The main task just sends a RANGE Request, and the downloader start
+ * a curl connection to the server and sends the received data back to the main task.
+ * The IPC is message oriented, and process add small metadata
+ * information to inform if the download reports errors (from libcurl).
+ * This is used explicitely to retrieve ranges : an answer
+ * different as "Partial Content" (206) is rejected. This avoids that the
+ * whole file is downloaded if the server is not able to work with ranges.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <util.h>
+#include <pctl.h>
+#include <zlib.h>
+#include <channel.h>
+#include <channel_curl.h>
+#include "delta_handler.h"
+#include "delta_process.h"
+
+/*
+ * Structure used in curl callbacks
+ */
+typedef struct {
+	unsigned int id;	/* Request id */
+	int writefd;		/* IPC file descriptor */
+	range_answer_t *answer;
+} dwl_data_t;
+
+extern channel_op_res_t channel_curl_init(void);
+
+static channel_data_t channel_data_defaults = {
+					.debug = false,
+					.source=SOURCE_CHUNKS_DOWNLOADER,
+					.retries=CHANNEL_DEFAULT_RESUME_TRIES,
+					.retry_sleep=
+						CHANNEL_DEFAULT_RESUME_DELAY,
+					.nocheckanswer=false,
+					.nofollow=false,
+					.connection_timeout=0,
+					.headers_to_send = NULL,
+					.received_headers = NULL
+					};
+
+/*
+ * Data callback: takes the buffer, surrounded with IPC meta data
+ * and send to the process that reqeusted the download
+ */
+static size_t wrdata_callback(char *buffer, size_t size, size_t nmemb, void *data)
+{
+	channel_data_t *channel_data = (channel_data_t *)data;
+	dwl_data_t *dwl = (dwl_data_t *)channel_data->user;
+	ssize_t nbytes = nmemb * size;
+	int ret;
+	if (!nmemb) {
+		return 0;
+	}
+	if (!data)
+		return 0;
+
+	if (channel_data->http_response_code != 206) {
+		ERROR("Bytes request not supported by server, returning %ld",
+			channel_data->http_response_code);
+		return 0;
+	}
+	while (nbytes > 0) {
+		range_answer_t *answer = dwl->answer;
+		answer->id = dwl->id;
+		answer->type = RANGE_DATA;
+		answer->len = min(nbytes, RANGE_PAYLOAD_SIZE);
+		memcpy(answer->data, buffer, answer->len);
+		answer->crc = crc32(0, (unsigned char *)answer->data, answer->len);
+		ret = copy_write(&dwl->writefd, answer, sizeof(range_answer_t));
+		if (ret < 0) {
+			ERROR("Error sending IPC data !");
+			return 0;
+		}
+		nbytes -= answer->len;
+	}
+
+	return size * nmemb;
+}
+
+/*
+ * This function just extract the header and sends
+ * to the process initiating the transfer.
+ * It envelops the header in the answer struct
+ * The receiver knows from meta data if payload contains headers
+ * or data.
+ * A single header is encapsulated in one IPC message.
+ */
+static size_t delta_callback_headers(char *buffer, size_t size, size_t nitems, void *data)
+{
+	channel_data_t *channel_data = (channel_data_t *)data;
+	dwl_data_t *dwl = (dwl_data_t *)channel_data->user;
+	int ret;
+
+	range_answer_t *answer = dwl->answer;
+	answer->id = dwl->id;
+	answer->type = RANGE_HEADERS;
+	answer->len = min(size * nitems , RANGE_PAYLOAD_SIZE - 2);
+	memcpy(answer->data, buffer, answer->len);
+	answer->len++;
+	answer->data[answer->len] = '\0';
+
+	ret = write(dwl->writefd, answer, sizeof(range_answer_t));
+	if (ret != sizeof(range_answer_t)) {
+		ERROR("Error sending IPC data !");
+		return 0;
+	}
+
+	return nitems * size;
+}
+
+/*
+ * Process that is spawned by the handler to download the missing chunks.
+ * Downloading should be done in a separate process to not break
+ * privilige separation
+ */
+int start_delta_downloader(const char __attribute__ ((__unused__)) *fname,
+				int __attribute__ ((__unused__)) argc,
+				__attribute__ ((__unused__)) char *argv[])
+{
+	ssize_t ret;
+	range_request_t *req = NULL;
+	channel_op_res_t transfer;
+	range_answer_t *answer;
+	struct dict httpheaders;
+	dwl_data_t priv;
+
+	TRACE("Starting Internal process for downloading chunks");
+	if (channel_curl_init() != CHANNEL_OK) {
+		ERROR("Cannot initialize curl");
+		return SERVER_EINIT;
+	}
+	req = (range_request_t *)malloc(sizeof *req);
+	if (!req) {
+		ERROR("OOM requesting request buffers !");
+		exit (EXIT_FAILURE);
+	}
+
+	answer = (range_answer_t *)malloc(sizeof *answer);
+	if (!answer) {
+		ERROR("OOM requesting answer buffers !");
+		exit (EXIT_FAILURE);
+	}
+
+	channel_data_t channel_data = channel_data_defaults;
+	channel_t *channel = channel_new();
+	if (!channel) {
+		ERROR("Cannot get channel for communication");
+		exit (EXIT_FAILURE);
+	}
+	LIST_INIT(&httpheaders);
+	if (dict_insert_value(&httpheaders, "Accept", "*/*")) {
+		ERROR("Database error setting Accept header");
+		exit (EXIT_FAILURE);
+	}
+
+	for (;;) {
+		ret = read(sw_sockfd, req, sizeof(range_request_t));
+		if (ret < 0) {
+			ERROR("reading from sockfd returns error, aborting...");
+			exit (EXIT_FAILURE);
+		}
+
+		if ((req->urllen + req->rangelen) > ret) {
+			ERROR("Malformed data");
+			continue;
+		}
+		priv.writefd = sw_sockfd;
+		priv.id = req->id;
+		priv.answer = answer;
+		channel_data.url = req->data;
+		channel_data.noipc = true;
+		channel_data.method = CHANNEL_GET;
+		channel_data.content_type = "*";
+		channel_data.headers = delta_callback_headers;
+		channel_data.dwlwrdata = wrdata_callback;
+		channel_data.range = &req->data[req->urllen + 1];
+		channel_data.user = &priv;
+
+		if (channel->open(channel, &channel_data) == CHANNEL_OK) {
+			transfer = channel->get_file(channel, (void *)&channel_data);
+		} else {
+			ERROR("Cannot open channel for communication");
+			transfer = CHANNEL_EINIT;
+		}
+
+		answer->id = req->id;
+		answer->type = (transfer == CHANNEL_OK) ? RANGE_COMPLETED : RANGE_ERROR;
+		answer->len = 0;
+		if (write(sw_sockfd, answer, sizeof(*answer)) != sizeof(*answer)) {
+			ERROR("Answer cannot be sent back, maybe deadlock !!");
+		}
+
+		(void)channel->close(channel);
+	}
+
+	exit (EXIT_SUCCESS);
+}
diff --git a/handlers/delta_handler.h b/handlers/delta_handler.h
new file mode 100644
index 0000000..4a9196b
--- /dev/null
+++ b/handlers/delta_handler.h
@@ -0,0 +1,37 @@ 
+/*
+ * (C) Copyright 2021
+ * Stefano Babic, sbabic@denx.de.
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+
+#pragma once
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#define RANGE_PAYLOAD_SIZE (32 * 1024)
+typedef enum {
+	RANGE_GET,
+	RANGE_HEADERS,
+	RANGE_DATA,
+	RANGE_COMPLETED,
+	RANGE_ERROR
+} request_type;
+
+typedef struct {
+	uint32_t id;
+	request_type type;
+	size_t urllen;
+	size_t rangelen;
+	uint32_t crc;
+	char data[RANGE_PAYLOAD_SIZE]; /* URL + RANGE */
+} range_request_t;
+
+typedef struct {
+	uint32_t id;
+	request_type type;
+	size_t len;
+	uint32_t crc;
+	char data[RANGE_PAYLOAD_SIZE]; /* Payload */
+} range_answer_t;
diff --git a/include/delta_process.h b/include/delta_process.h
new file mode 100644
index 0000000..51d1e04
--- /dev/null
+++ b/include/delta_process.h
@@ -0,0 +1,10 @@ 
+/*
+ * (C) Copyright 2021
+ * Stefano Babic, sbabic@denx.de.
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+
+#pragma once
+
+extern int start_delta_downloader(const char *fname, int argc, char *argv[]);
diff --git a/include/swupdate_status.h b/include/swupdate_status.h
index 8ac9af1..29eea0f 100644
--- a/include/swupdate_status.h
+++ b/include/swupdate_status.h
@@ -35,7 +35,8 @@  typedef enum {
 	SOURCE_WEBSERVER,
 	SOURCE_SURICATTA,
 	SOURCE_DOWNLOADER,
-	SOURCE_LOCAL
+	SOURCE_LOCAL,
+	SOURCE_CHUNKS_DOWNLOADER
 } sourcetype;
 
 #ifdef __cplusplus