swuforward requires to start the slaves with the old API, and this
forbids to update directly the slave via Web. This reworks swuforward to
connect to slaves via Websockets, and changes to use the new API. The
old API is then dropped as there are less usages of it.
Kconfig | 8 +
Makefile.deps | 8 +
Makefile.flags | 5 +
handlers/Config.in | 5 +
handlers/Makefile | 2 +-
handlers/swuforward-ws.c | 188 ++++++++++++++
handlers/swuforward_handler.c | 461 ++++++++++++++++++++--------------
handlers/swuforward_handler.h | 77 ++++++
8 files changed, 558 insertions(+), 196 deletions(-)
create mode 100644 handlers/swuforward-ws.c
create mode 100644 handlers/swuforward_handler.h
diff --git a/Kconfig b/Kconfig
index 3f3f672..b652d84 100644
--- a/Kconfig
+++ b/Kconfig
@@ -73,6 +73,14 @@ config HAVE_JSON_C
bool
option env="HAVE_JSON_C"
+config HAVE_LIBWEBSOCKETS
+ bool
+ option env="HAVE_LIBWEBSOCKETS"
+
+config HAVE_URIPARSER
+ bool
+ option env="HAVE_URIPARSER"
+
menu "Swupdate Settings"
menu "General Configuration"
diff --git a/Makefile.deps b/Makefile.deps
index e2bf669..df7379f 100644
--- a/Makefile.deps
+++ b/Makefile.deps
@@ -57,3 +57,11 @@ endif
ifeq ($(HAVE_JSON_C),)
export HAVE_JSON_C = y
endif
+
+ifeq ($(HAVE_LIBWEBSOCKETS),)
+export HAVE_LIBWEBSOCKETS = y
+endif
+
+ifeq ($(HAVE_URIPARSER),)
+export HAVE_URIPARSER = y
+endif
diff --git a/Makefile.flags b/Makefile.flags
index 4ee12c9..663ce57 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -221,6 +221,11 @@ endif
endif
endif
+# SWU forwarder
+ifneq ($(CONFIG_SWUFORWARDER_HANDLER),)
+LDLIBS += websockets uriparser
+endif
+
# If a flat binary should be built, CFLAGS_swupdate="-elf2flt"
# env var should be set for make invocation.
# Here we check whether CFLAGS_swupdate indeed contains that flag.
diff --git a/handlers/Config.in b/handlers/Config.in
index 91d43f3..40d2753 100644
--- a/handlers/Config.in
+++ b/handlers/Config.in
@@ -184,6 +184,8 @@ config SWUFORWARDER_HANDLER
bool "SWU forwarder"
depends on HAVE_LIBCURL
depends on HAVE_JSON_C
+ depends on HAVE_LIBWEBSOCKETS
+ depends on HAVE_URIPARSER
select CHANNEL_CURL
select JSON
default n
@@ -200,6 +202,9 @@ comment "swuforward handler needs json-c and curl"
comment "SWU forwarder requires libcurl"
depends on !HAVE_LIBCURL
+comment "swuforward handler needs json-c and curl"
+ depends on !HAVE_LIBWEBSOCKETS || !HAVE_URIPARSER
+
config BOOTLOADERHANDLER
bool "bootloader"
default n
diff --git a/handlers/Makefile b/handlers/Makefile
index b75c3ba..1de2ca3 100644
--- a/handlers/Makefile
+++ b/handlers/Makefile
@@ -16,7 +16,7 @@ obj-$(CONFIG_LUASCRIPTHANDLER) += lua_scripthandler.o
obj-$(CONFIG_RAW) += raw_handler.o
obj-$(CONFIG_REMOTE_HANDLER) += remote_handler.o
obj-$(CONFIG_SHELLSCRIPTHANDLER) += shell_scripthandler.o
-obj-$(CONFIG_SWUFORWARDER_HANDLER) += swuforward_handler.o
+obj-$(CONFIG_SWUFORWARDER_HANDLER) += swuforward_handler.o swuforward-ws.o
obj-$(CONFIG_UBIVOL) += ubivol_handler.o
obj-$(CONFIG_UCFWHANDLER) += ucfw_handler.o
obj-$(CONFIG_RDIFFHANDLER) += rdiff_handler.o
diff --git a/handlers/swuforward-ws.c b/handlers/swuforward-ws.c
new file mode 100644
index 0000000..c2dbb5e
--- /dev/null
+++ b/handlers/swuforward-ws.c
@@ -0,0 +1,188 @@
+/*
+ * (C) Copyright 2019
+ * Stefano Babic, DENX Software Engineering,
sba...@denx.de.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * This is the websocket connection to an external SWUpdate
+ * Webserver. It is used to check if a remote update was successful
+ * Inspiration for this code comes from libwebsockets
+ * "minimal" examples.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <json-c/json.h>
+#include <util.h>
+#include <parselib.h>
+#include "swuforward_handler.h"
+
+#include <libwebsockets.h>
+#include <uriparser/Uri.h>
+
+struct wsconn {
+ struct lws *client_wsi;
+ struct lws_context *context;
+ struct lws_context_creation_info info;
+ json_object *json_reply;
+ struct curlconn *conn; /* Back pointer to main structure */
+};
+
+#define TEXTRANGE_TO_STR(f) (substring(f.first, 0, f.afterLast - f.first))
+
+static int callback_ws_swupdate(struct lws *wsi, enum lws_callback_reasons reason,
+ void *user, void *in, size_t len)
+{
+ struct wsconn *ws = (struct wsconn *)user;
+ struct json_tokener *json_tokenizer;
+ enum json_tokener_error json_res;
+ struct json_object *json_root;
+
+ switch (reason) {
+
+ /* because we are protocols[0] ... */
+ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+ ERROR("WS Client Connection Error to : %s", ws->conn->url);
+ ws->client_wsi = NULL;
+ ws->conn->connstatus = WS_ERROR;
+ break;
+
+ case LWS_CALLBACK_CLIENT_ESTABLISHED:
+ TRACE("Connection to %s: established", ws->conn->url);
+ ws->conn->connstatus = WS_ESTABLISHED;
+ break;
+
+ case LWS_CALLBACK_CLIENT_RECEIVE:
+ json_tokenizer = json_tokener_new();
+
+ do {
+ json_root = json_tokener_parse_ex(
+ json_tokenizer, in, len);
+ } while ((json_res = json_tokener_get_error(json_tokenizer)) ==
+ json_tokener_continue);
+ if (json_res != json_tokener_success) {
+ ERROR("Error while parsing answer from %s returned JSON data: %s",
+ ws ? ws->conn->url : "", json_tokener_error_desc(json_res));
+ } else {
+ const char *reply_result = json_get_value(json_root, "type");
+ if (reply_result && !strcmp(reply_result, "status")) {
+ const char *status = json_get_value(json_root, "status");
+ if (!strcmp(status, "SUCCESS"))
+ ws->conn->SWUpdateStatus = SUCCESS;
+ if (!strcmp(status, "FAILURE"))
+ ws->conn->SWUpdateStatus = FAILURE;
+ TRACE("Change status on %s : %s", ws->conn->url,
+ status);
+ }
+ if (reply_result && !strcmp(reply_result, "message")) {
+ const char *text = json_get_value(json_root, "text");
+ TRACE("%s : %s", ws->conn->url, text);
+ }
+ }
+ json_tokener_free(json_tokenizer);
+ break;
+
+ case LWS_CALLBACK_CLIENT_CLOSED:
+ ws->client_wsi = NULL;
+ ws->conn->connstatus = WS_CLOSED;
+ break;
+
+ default:
+ break;
+ }
+
+ return lws_callback_http_dummy(wsi, reason, user, in, len);
+}
+
+static const struct lws_protocols protocols[] = {
+ {
+ "swupdate-status-protocol",
+ callback_ws_swupdate,
+ 0,
+ 0,
+ },
+ { NULL, NULL, 0, 0 }
+};
+
+int swuforward_ws_connect(struct curlconn *conn) {
+ UriParserStateA state;
+ UriUriA uri; /* Parsed URL */
+ char *tmp;
+ struct wsconn *ws;
+ struct lws_context_creation_info *info;
+ struct lws_client_connect_info i;
+ const char *posturl = conn->url;
+
+ ws = calloc(1, sizeof(*ws));
+ if (!ws)
+ return -ENOMEM;
+
+ conn->ws = ws;
+ ws->conn = conn;
+
+ state.uri = &uri;
+ if (uriParseUriA(&state, posturl) != URI_SUCCESS) {
+ ERROR("URL seems wrong : %s", posturl);
+ return -EINVAL;
+ }
+
+ /*
+ * Initialization
+ */
+ info = &ws->info;
+ info->options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+ info->port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
+ info->protocols = protocols;
+
+ ws->context = lws_create_context(info);
+ if (!ws->context) {
+ free(ws);
+ ERROR("lws init failed");
+ return EFAULT;
+ }
+
+ memset(&i, 0, sizeof i);
+ i.context = ws->context;
+ i.address = TEXTRANGE_TO_STR(uri.hostText);
+ i.path = "/";
+ i.host = i.address;
+ i.origin = i.address;
+ i.protocol = protocols[0].name;
+ i.pwsi = &ws->client_wsi;
+ i.userdata = ws;
+ tmp = TEXTRANGE_TO_STR(uri.portText);
+ if (tmp) {
+ i.port = strtoul(tmp, NULL, 10);
+ free(tmp);
+ }
+ lws_client_connect_via_info(&i);
+ free((void *)i.address);
+
+ //uriFreeUriMembersA(&uri);
+
+ return 0;
+}
+
+int swuforward_ws_getanswer(struct curlconn *conn, int timeout) {
+ struct wsconn *ws;
+ if (!conn || !conn->ws)
+ return -EFAULT;
+ ws = conn->ws;
+ return lws_service(ws->context, timeout);
+}
+
+void swuforward_ws_free(struct curlconn *conn) {
+ struct wsconn *ws;
+
+ ws = conn->ws;
+
+ if (ws) {
+ lws_context_destroy(ws->context);
+ free(ws);
+ }
+}
diff --git a/handlers/swuforward_handler.c b/handlers/swuforward_handler.c
index 227612a..bd8afc9 100644
--- a/handlers/swuforward_handler.c
+++ b/handlers/swuforward_handler.c
@@ -9,6 +9,15 @@
* This handler allows to create a mesh of devices using SWUpdate
* as agent. The handler is called if an artifact is a SWU image
* and sends it to the devices provided in sw-description.
+ *
+ * To provide zero-copy and support for installing on multiple
+ * remote devices, the multi interface in libcurl is used.
+ *
+ * This handler spawns a task to provide callback with libcurl.
+ * The main task has an own callback for copyimage(), and
+ * writes into cretated FIFOs (one for each remote device)
+ * because the connections to devices is asynchrounous.
+ *
*/
#include <stdbool.h>
@@ -22,60 +31,20 @@
#include <string.h>
#include <swupdate.h>
#include <handler.h>
+#include <pthread.h>
#include <util.h>
-#include <curl/curl.h>
#include <json-c/json.h>
-#include "bsdqueue.h"
-#include "channel_curl.h"
-#include "channel.h"
#include "parselib.h"
-
-/*
- * The Webserver in SWUpdate expets a custom header
- * with the filename
- */
-#define CUSTOM_HEADER "X_FILENAME: "
-#define MAX_WAIT_MS 30000
-#define POST_URL "/handle_post_request"
-#define STATUS_URL "/getstatus.json"
-
-/*
- * The hzandler checks if a remote update was successful
- * asking for the status. It is supposed that the boards go on
- * until they report a success or failure.
- * Following timeout is introduced in case boards answer, but they do not
- * go out for some reasons from the running state.
- */
-#define TIMEOUT_GET_ANSWER_SEC 900 /* 15 minutes */
-#define POLLING_TIME_REQ_STATUS 50 /* in mSec */
+#include "swuforward_handler.h"
void swuforward_handler(void);
-/*
- * Track each connection
- * The handler maintains a list of connections and sends the SWU
- * to all of them at once.
- */
-struct curlconn {
- CURL *curl_handle; /* CURL handle for posting image */
- const void *buffer; /* temporary buffer to transfer image */
- unsigned int nbytes; /* bytes to be transferred per iteration */
- size_t total_bytes; /* size of SWU image */
- char *url; /* URL for forwarding */
- bool gotMsg; /* set if the remote board has sent a new msg */
- RECOVERY_STATUS SWUpdateStatus; /* final status of update */
- LIST_ENTRY(curlconn) next;
-};
-LIST_HEAD(listconns, curlconn);
-
/*
* global handler data
*
*/
struct hnd_priv {
- CURLM *cm; /* libcurl multi handle */
unsigned int maxwaitms; /* maximum time in CURL wait */
- size_t size; /* size of SWU */
struct listconns conns; /* list of connections */
};
@@ -83,7 +52,7 @@ struct hnd_priv {
* CURL callback when posting data
* Read from connection buffer and copy to CURL buffer
*/
-static size_t curl_read_data(void *buffer, size_t size, size_t nmemb, void *userp)
+static size_t curl_read_data(char *buffer, size_t size, size_t nmemb, void *userp)
{
struct curlconn *conn = (struct curlconn *)userp;
size_t nbytes;
@@ -92,17 +61,28 @@ static size_t curl_read_data(void *buffer, size_t size, size_t nmemb, void *user
return 0;
if (!userp) {
ERROR("Failure IPC stream file descriptor ");
- return -EFAULT;
+ return CURL_READFUNC_ABORT;
}
- if (conn->nbytes > (nmemb * size))
- nbytes = nmemb * size;
+ if (nmemb * size > conn->total_bytes)
+ nbytes = conn->total_bytes;
else
- nbytes = conn->nbytes;
+ nbytes = nmemb * size;
- memcpy(buffer, conn->buffer, nbytes);
+ nbytes = read(conn->fifo[0], buffer, nbytes);
+ if (nbytes == -1 && errno == EAGAIN) {
+ TRACE("No data, try again");
+ nbytes = 0;
+ }
- conn->nbytes -= nbytes;
+ if (nbytes < 0) {
+ ERROR("Cannot read from FIFO");
+ return CURL_READFUNC_ABORT;
+ }
+
+ nmemb = nbytes / size;
+
+ conn->total_bytes -= nbytes;
return nmemb;
}
@@ -114,59 +94,130 @@ static size_t curl_read_data(void *buffer, size_t size, size_t nmemb, void *user
static int swu_forward_data(void *data, const void *buf, unsigned int len)
{
struct hnd_priv *priv = (struct hnd_priv *)data;
- int ret, still_running = 0;
-
+ size_t written;
struct curlconn *conn;
+ int index = 0;
+
+ /*
+ * Iterate all connection and copy the incoming buffer
+ * to the corresponding FIFO.
+ * Each connection has own FIFO to transfer the data
+ * to the curl thread
+ */
LIST_FOREACH(conn, &priv->conns, next) {
- conn->nbytes += len;
- conn->buffer = buf;
- }
+ unsigned int nbytes = len;
+ const void *tmp = buf;
- do {
- int ready = 1;
+ while (nbytes) {
+ written = write(conn->fifo[1], buf, len);
- LIST_FOREACH(conn, &priv->conns, next) {
- if (conn->nbytes > 0) {
- ready = 0;
- break;
+ if (written < 0) {
+ ERROR ("Cannot write to fifo %d", index);
+ return -EFAULT;
}
+ nbytes -= written;
+ tmp += written;
}
+ }
- /*
- * Buffer transferred to all connections,
- * just returns and wait for next
- */
- if (ready)
- break;
+ return 0;
+}
- int numfds=0;
- ret = curl_multi_wait(priv->cm, NULL, 0, priv->maxwaitms, &numfds);
- if (ret != CURLM_OK) {
- ERROR("curl_multi_wait() returns %d", ret);
- return FAILURE;
- }
+/*
+ * Internal thread to transfer the SWUs to
+ * the other devices.
+ * The thread reads the per-connection FIFO and handles
+ * the curl multi interface.
+ */
+static void *curl_transfer_thread(void *p)
+{
+ struct curlconn *conn = (struct curlconn *)p;
+ const char no_100_header[] = "Expect:";
+ struct curl_slist *headerlist;
+ curl_mimepart *field = NULL;
+ headerlist = NULL;
- curl_multi_perform(priv->cm, &still_running);
- } while (still_running);
- if (!still_running) {
- LIST_FOREACH(conn, &priv->conns, next) {
- /* check if the buffer was transfered */
- if (conn->nbytes) {
- ERROR("Connection lost, data not transferred");
- }
- conn->total_bytes += len - conn->nbytes;
- if (conn->total_bytes != priv->size) {
- ERROR("Connection lost, SWU not transferred");
- return -EIO;
- }
- }
- ERROR("Connection lost, skipping data");
+ /*
+ * This is just to run the scheduler and let the main
+ * thread to open FIFOs and start writing into it
+ */
+ conn->curl_handle = curl_easy_init();
+ if (!conn->curl_handle) {
+ /* something very bad, it should never happen */
+ ERROR("FAULT: no handle from libcurl");
+ conn->exitval = FAILURE;
+ goto curl_thread_exit;
}
- return 0;
+ /*
+ * The 100-expect header is unwanted
+ * drop it
+ */
+ headerlist = curl_slist_append(headerlist, no_100_header);
+
+ /*
+ * Setting multipart format
+ */
+ conn->form = curl_mime_init(conn->curl_handle);
+
+ /* Fill in the filename field */
+ field = curl_mime_addpart(conn->form);
+ curl_mime_name(field, "swupdate-package");
+ curl_mime_type(field, "application/octet-stream");
+ curl_mime_filename(field, "swupdate.swu");
+
+ if ((curl_easy_setopt(conn->curl_handle, CURLOPT_POST, 1L) != CURLE_OK) ||
+ (curl_mime_data_cb(field, conn->total_bytes, curl_read_data,
+ NULL, NULL, conn) != CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_USERAGENT,
+ "libcurl-agent/1.0") != CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_MIMEPOST,
+ conn->form) != CURLE_OK) ||
+ (curl_easy_setopt(conn->curl_handle, CURLOPT_HTTPHEADER,
+ headerlist) != CURLE_OK)) {
+ ERROR("curl set_option was not successful");
+ conn->exitval = FAILURE;
+ goto curl_thread_exit;
+ }
+
+ /* get verbose debug output please */
+ curl_easy_setopt(conn->curl_handle, CURLOPT_VERBOSE, 1L);
+
+ /*
+ * Set the URL to post the SWU
+ * This corresponds to the URL set in mongoose interface
+ */
+ char *posturl = NULL;
+ posturl = (char *)alloca(strlen(conn->url) + strlen(POST_URL_V2) + 1);
+ sprintf(posturl, "%s%s", conn->url, POST_URL_V2);
+
+ /* Set URL */
+ if (curl_easy_setopt(conn->curl_handle, CURLOPT_URL, posturl) != CURLE_OK) {
+ ERROR("Cannot set URL in libcurl");
+ conn->exitval = FAILURE;
+ goto curl_thread_exit;
+ }
+
+ /*
+ * Now perform the transfer
+ */
+ CURLcode curlrc = curl_easy_perform(conn->curl_handle);
+ if (curlrc != CURLE_OK) {
+ ERROR("SWU transfer to %s failed (%d) : '%s'", conn->url, curlrc,
+ curl_easy_strerror(curlrc));
+ conn->exitval = FAILURE;
+ }
+
+ conn->exitval = SUCCESS;
+
+curl_thread_exit:
+ close(conn->fifo[0]);
+ curl_easy_cleanup(conn->curl_handle);
+ pthread_exit(NULL);
}
+
static json_object *parse_reqstatus(json_object *reply, const char **json_path)
{
json_object *json_data;
@@ -179,21 +230,22 @@ static json_object *parse_reqstatus(json_object *reply, const char **json_path)
return json_data;
}
+
/*
* Send a GET to retrieve all traces from the connected board
*/
-static int get_answer(struct curlconn *conn, RECOVERY_STATUS *result, bool ignore)
+static int get_answer_V1(struct curlconn *conn, RECOVERY_STATUS *result, bool ignore)
{
channel_data_t channel_cfg = {
.debug = false,
.retries = 0,
.retry_sleep = 0,
.usessl = false};
- channel_op_res_t response;
channel_t *channel = channel_new();
json_object *json_data;
int status;
+ conn->response = CHANNEL_EIO;
/*
* Open a curl channel, do not connect yet
*/
@@ -202,19 +254,17 @@ static int get_answer(struct curlconn *conn, RECOVERY_STATUS *result, bool ignor
}
if (asprintf(&channel_cfg.url, "%s%s",
- conn->url, STATUS_URL) < 0) {
+ conn->url, STATUS_URL_V1) < 0) {
ERROR("Out of memory.");
return -ENOMEM;
}
/* Retrieve last message */
- response = channel->get(channel, (void *)&channel_cfg);
+ conn->response = channel->get(channel, (void *)&channel_cfg);
- if (response != CHANNEL_OK) {
- channel->close(channel);
- free(channel);
- free(channel_cfg.url);
- return -1;
+ if (conn->response != CHANNEL_OK) {
+ status = -EIO;
+ goto cleanup;
}
/* Retrieve all fields */
@@ -257,49 +307,82 @@ cleanup:
return status;
}
-static int retrieve_msgs(struct hnd_priv *priv, bool ignore)
-{
+static int retrieve_msgs(struct hnd_priv *priv) {
struct curlconn *conn;
int ret;
int result = 0;
+ bool finished = false;
- LIST_FOREACH(conn, &priv->conns, next) {
- int count = 0;
- do {
- ret = get_answer(conn, &conn->SWUpdateStatus, ignore);
- if (!conn->gotMsg) {
- usleep(POLLING_TIME_REQ_STATUS * 1000);
- count++;
- } else
- count = 0;
- if (count > ((TIMEOUT_GET_ANSWER_SEC * 1000) /
- POLLING_TIME_REQ_STATUS)) {
- ret = -ETIMEDOUT;
+ while (!finished) {
+ finished = true;
+ LIST_FOREACH(conn, &priv->conns, next) {
+ if ((conn->connstatus == WS_ESTABLISHED) &&
+ (conn->SWUpdateStatus != SUCCESS) &&
+ (conn->SWUpdateStatus != FAILURE)) {
+ ret = swuforward_ws_getanswer(conn, POLLING_TIME_REQ_STATUS);
+ if (ret < 0) {
+ conn->SWUpdateStatus = FAILURE;
+ break;
+ }
+ finished = false;
}
- } while (ret > 0);
- if (ret != 0 || (conn->SWUpdateStatus != SUCCESS)) {
- ERROR("Update to %s was NOT successful !", conn->url);
- result = -1;
+ }
+ }
+
+ /*
+ * Now we get results from all connection,
+ * check if all of them were successful
+ */
+ result = 0;
+ LIST_FOREACH(conn, &priv->conns, next) {
+ if (conn->SWUpdateStatus != SUCCESS) {
+ ERROR("Update to %s failed !!", conn->url);
+ return -EFAULT;
}
}
return result;
}
+static int initialize_backchannel(struct hnd_priv *priv)
+{
+ struct curlconn *conn;
+ int ret;
+
+ LIST_FOREACH(conn, &priv->conns, next) {
+
+ ret = swuforward_ws_connect(conn);
+ if (!ret) {
+ do {
+ ret = swuforward_ws_getanswer(conn, POLLING_TIME_REQ_STATUS);
+ } while (ret >= 0 && (conn->connstatus == WS_UNKNOWN));
+ if (conn->connstatus != WS_ESTABLISHED) {
+ ERROR("No connection to %s", conn->url);
+ ret = FAILURE;
+ }
+ }
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
static int install_remote_swu(struct img_type *img,
void __attribute__ ((__unused__)) *data)
{
struct hnd_priv priv;
struct curlconn *conn;
- int ret, still_running = 0;
+ int ret;
struct dict_list_elem *url;
- struct curl_slist *headerlist;
- CURLMsg *msg = NULL;
struct dict_list *urls;
+ int index = 0;
+ pthread_attr_t attr;
+ int thread_ret = -1;
/*
* A single SWU can contains encrypted artifacts,
- * but the SWU itself canot be encrypted.
+ * but the SWU itself cannot be encrypted.
* Raise an error if the encrypted attribute is set
*/
@@ -327,13 +410,17 @@ static int install_remote_swu(struct img_type *img,
ret = FAILURE;
goto handler_exit;
}
-
priv.cm = curl_multi_init();
- priv.maxwaitms = MAX_WAIT_MS;
- priv.size = img->size;
+ /*
+ * Scan all devices in the list and set up
+ * data structure
+ */
LIST_FOREACH(url, urls, next) {
- char curlheader[SWUPDATE_GENERAL_STRING_SIZE + strlen(CUSTOM_HEADER)];
+ /*
+ * Allocates one structure for connection to download
+ * the SWU to all slaves in parallel
+ */
conn = (struct curlconn *)calloc(1, sizeof(struct curlconn));
if (!conn) {
ERROR("FAULT: no memory");
@@ -341,118 +428,102 @@ static int install_remote_swu(struct img_type *img,
goto handler_exit;
}
- headerlist = NULL;
-
- conn->curl_handle = curl_easy_init();
conn->url = url->value;
+ conn->total_bytes = img->size;
+ conn->SWUpdateStatus = IDLE;
- if (!conn->curl_handle) {
- /* something very bad, it should never happen */
- ERROR("FAULT: no handle from libcurl");
- return FAILURE;
+ /*
+ * Try to connect to check Webserver Version
+ * If there is an anwer, but with HTTP=404
+ * it is V2
+ * Just V2 is supported (TODO: support older versions, too ?)
+ */
+
+ ret = get_answer_V1(conn, &conn->SWUpdateStatus, true);
+ if (ret < 0 && (conn->response == CHANNEL_ENOTFOUND)) {
+ conn->ver = SWUPDATE_WWW_V2;
+ } else if (!ret) {
+ conn->ver = SWUPDATE_WWW_V1;
+ } else {
+ ERROR("Remote SWUpdate not answering");
+ ret = FAILURE;
+ goto handler_exit;
}
+ TRACE("Found remote server V%d",
+ conn->ver == SWUPDATE_WWW_V2 ? SWUPDATE_WWW_V2 : SWUPDATE_WWW_V1);
- snprintf(curlheader, sizeof(curlheader), "%s%s", CUSTOM_HEADER, img->fname);
- headerlist = curl_slist_append(headerlist, curlheader);
-
- if ((curl_easy_setopt(conn->curl_handle, CURLOPT_POST, 1L) != CURLE_OK) ||
- (curl_easy_setopt(conn->curl_handle, CURLOPT_READFUNCTION,
- curl_read_data) != CURLE_OK) ||
- (curl_easy_setopt(conn->curl_handle, CURLOPT_READDATA,
- conn) !=CURLE_OK) ||
- (curl_easy_setopt(conn->curl_handle, CURLOPT_USERAGENT,
- "libcurl-agent/1.0") != CURLE_OK) ||
- (curl_easy_setopt(conn->curl_handle, CURLOPT_POSTFIELDSIZE,
- img->size)!=CURLE_OK) ||
- (curl_easy_setopt(conn->curl_handle, CURLOPT_HTTPHEADER,
- headerlist) != CURLE_OK)) {
- ERROR("curl set_option was not successful");
+ if (conn->ver == SWUPDATE_WWW_V1) {
+ ERROR("FAULT: there is no support for older website");
ret = FAILURE;
goto handler_exit;
}
- /* get verbose debug output please */
- curl_easy_setopt(conn->curl_handle, CURLOPT_VERBOSE, 1L);
+ /*
+ * Create one FIFO for each connection to be thread safe
+ */
+ if (pipe(conn->fifo) < 0) {
+ ERROR("Cannot create internal pipes, exit..");
+ ret = FAILURE;
+ goto handler_exit;
+ }
- char *posturl = NULL;
- posturl = (char *)malloc(strlen(conn->url) + strlen(POST_URL) + 1);
- sprintf(posturl, "%s%s", conn->url, POST_URL);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
- /* Set URL */
- if (curl_easy_setopt(conn->curl_handle, CURLOPT_URL, posturl) != CURLE_OK) {
- ERROR("Cannot set URL in libcurl");
- free(posturl);
+ thread_ret = pthread_create(&conn->transfer_thread, &attr, curl_transfer_thread, conn);
+ if (thread_ret) {
+ ERROR("Code from pthread_create() is %d",
+ thread_ret);
+ conn->transfer_thread = 0;
ret = FAILURE;
goto handler_exit;
}
- free(posturl);
- curl_multi_add_handle(
priv.cm, conn->curl_handle);
LIST_INSERT_HEAD(&priv.conns, conn, next);
- }
- retrieve_msgs(&priv, true);
+ index++;
+ }
- curl_multi_perform(
priv.cm, &still_running);
+ if (initialize_backchannel(&priv)) {
+ ERROR("Cannot initialize back connection");
+ goto handler_exit;
+ }
ret = copyimage(&priv, img, swu_forward_data);
-
if (ret) {
ERROR("Transferring SWU image was not successful");
goto handler_exit;
}
- /*
- * Now checks if transfer was successful
- */
- int msgs_left = 0;
- while ((msg = curl_multi_info_read(
priv.cm, &msgs_left))) {
- CURL *eh = NULL;
- int http_status_code=0;
- if (msg->msg != CURLMSG_DONE) {
- ERROR("curl_multi_info_read(), CURLMsg=%d", msg->msg);
- ret = FAILURE;
- break;
- }
- LIST_FOREACH(conn, &priv.conns, next) {
- if (conn->curl_handle == msg->easy_handle) {
- eh = conn->curl_handle;
- break;
- }
- }
-
- if (!eh) {
- ERROR("curl handle not found in connections");
+ ret = 0;
+ LIST_FOREACH(conn, &priv.conns, next) {
+ void *status;
+ ret = pthread_join(conn->transfer_thread, &status);
+ close(conn->fifo[1]);
+ if (ret) {
+ ERROR("return code from pthread_join() is %d", ret);
ret = FAILURE;
- break;
+ goto handler_exit;
}
-
- curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &http_status_code);
-
- if (http_status_code != 200) {
- ERROR("Sending %s to %s failed with %d",
- img->fname, conn->url, http_status_code);
+ if (conn->exitval != SUCCESS)
ret = FAILURE;
- break;
- }
}
/*
* Now check if remote updates were successful
*/
if (!ret) {
- ret = retrieve_msgs(&priv, false);
+ ret = retrieve_msgs(&priv);
}
handler_exit:
LIST_FOREACH(conn, &priv.conns, next) {
+ index = 0;
LIST_REMOVE(conn, next);
- curl_multi_remove_handle(
priv.cm, conn->curl_handle);
- curl_easy_cleanup(conn->curl_handle);
+ swuforward_ws_free(conn);
free(conn);
+ index++;
}
- curl_multi_cleanup(
priv.cm);
-
return ret;
}
diff --git a/handlers/swuforward_handler.h b/handlers/swuforward_handler.h
new file mode 100644
index 0000000..f770ca0
--- /dev/null
+++ b/handlers/swuforward_handler.h
@@ -0,0 +1,77 @@
+/*
+ * (C) Copyright 2017-2019
+ * Stefano Babic, DENX Software Engineering,
sba...@denx.de.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef _SWUFORWARD_HANDLER_H
+#define _SWUFORWARD_HANDLER_H
+
+#include <curl/curl.h>
+#include "bsdqueue.h"
+#include "channel_curl.h"
+#include "channel.h"
+
+/*
+ * The Webserver in SWUpdate expets a custom header
+ * with the filename
+ */
+#define CUSTOM_HEADER "X_FILENAME: "
+#define MAX_WAIT_MS 3000
+#define POST_URL_V1 "/handle_post_request"
+#define POST_URL_V2 "/upload"
+#define STATUS_URL_V1 "/getstatus.json"
+
+/*
+ * The handler checks if a remote update was successful
+ * asking for the status. It is supposed that the boards go on
+ * until they report a success or failure.
+ * Following timeout is introduced in case boards answer, but they do not
+ * go out for some reasons from the running state.
+ */
+#define TIMEOUT_GET_ANSWER_SEC 900 /* 15 minutes */
+#define POLLING_TIME_REQ_STATUS 50 /* in mSec */
+
+typedef enum {
+ SWUPDATE_WWW_V1 = 1,
+ SWUPDATE_WWW_V2 = 2,
+} SWUPDATE_WEB_VER;
+
+typedef enum {
+ WS_UNKNOWN,
+ WS_ESTABLISHED,
+ WS_ERROR,
+ WS_CLOSED
+} SWUPDATE_WS_CONNECTION;
+
+/*
+ * Track each connection
+ * The handler maintains a list of connections and sends the SWU
+ * to all of them at once.
+ */
+struct curlconn {
+ CURL *curl_handle; /* CURL handle for posting image */
+ curl_mime *form; /* Used to set up mulitipart/form-data */
+ struct curl_slist *headerlist; /* List of headers used for each conn */
+ const void *buffer; /* temporary buffer to transfer image */
+ unsigned int nbytes; /* bytes to be transferred */
+ size_t total_bytes; /* size of SWU image */
+ int fifo[2]; /* Pipe for IPC */
+ char *url; /* URL for forwarding */
+ SWUPDATE_WEB_VER ver; /* version of remote Webserver (V1 or V2) */
+ bool gotMsg; /* set if the remote board has sent a new msg */
+ RECOVERY_STATUS SWUpdateStatus; /* final status of update */
+ channel_op_res_t response;
+ void *ws; /* this is used by websockets module */
+ SWUPDATE_WS_CONNECTION connstatus;
+ pthread_t transfer_thread;
+ int exitval;
+ LIST_ENTRY(curlconn) next;
+};
+LIST_HEAD(listconns, curlconn);
+
+int swuforward_ws_connect(struct curlconn *conn);
+int swuforward_ws_getanswer(struct curlconn *conn, int timeout);
+void swuforward_ws_free(struct curlconn *conn);
+#endif
--
2.17.1