[PATCH 1/5] progress: allow to pass the UDS in the connection

213 views
Skip to first unread message

Stefano Babic

unread,
Jun 24, 2019, 5:27:20 AM6/24/19
to swup...@googlegroups.com, Stefano Babic
Add progress_ipc_connect_with_path() to set which socket must be used to
start a connection. This is useful in case more as one SWUpdate instance
is running on target.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
include/progress_ipc.h | 9 +++++++++
ipc/progress_ipc.c | 17 +++++++++++++----
2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/include/progress_ipc.h b/include/progress_ipc.h
index e427101..e93a5d5 100644
--- a/include/progress_ipc.h
+++ b/include/progress_ipc.h
@@ -31,6 +31,15 @@ struct progress_msg {
char info[2048]; /* additional information about install */
};

+/* Standard function to connect to progress interface */
int progress_ipc_connect(bool reconnect);
+
+/*
+ * In case more as an instance of SWUpdate is running, this allows to select
+ * which should be taken
+ */
+int progress_ipc_connect_with_path(const char *socketpath, bool reconnect);
+
+/* Retrieve messages from progress interface (it blocks) */
int progress_ipc_receive(int *connfd, struct progress_msg *msg);
#endif
diff --git a/ipc/progress_ipc.c b/ipc/progress_ipc.c
index 9ad4e15..7e6054c 100644
--- a/ipc/progress_ipc.c
+++ b/ipc/progress_ipc.c
@@ -21,13 +21,13 @@ char* SOCKET_PROGRESS_PATH = (char*)CONFIG_SOCKET_PROGRESS_PATH;
char* SOCKET_PROGRESS_PATH = (char*)"/tmp/swupdateprog";
#endif

-int progress_ipc_connect(bool reconnect)
+static int _progress_ipc_connect(const char *socketpath, bool reconnect)
{
struct sockaddr_un servaddr;
int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
- strcpy(servaddr.sun_path, SOCKET_PROGRESS_PATH);
+ strncpy(servaddr.sun_path, socketpath, sizeof(servaddr.sun_path));

fprintf(stdout, "Trying to connect to SWUpdate...\n");

@@ -36,17 +36,26 @@ int progress_ipc_connect(bool reconnect)
break;
}
if (!reconnect) {
- fprintf(stderr, "cannot communicate with SWUpdate via %s\n", SOCKET_PROGRESS_PATH);
+ fprintf(stderr, "cannot communicate with SWUpdate via %s\n", socketpath);
exit(1);
}

usleep(10000);
} while (true);

- fprintf(stdout, "Connected to SWUpdate via %s\n", SOCKET_PROGRESS_PATH);
+ fprintf(stdout, "Connected to SWUpdate via %s\n", socketpath);
return fd;
}

+int progress_ipc_connect_with_path(const char *socketpath, bool reconnect) {
+ return _progress_ipc_connect(socketpath, reconnect);
+}
+
+int progress_ipc_connect(bool reconnect)
+{
+ return _progress_ipc_connect(SOCKET_PROGRESS_PATH, reconnect);
+}
+
int progress_ipc_receive(int *connfd, struct progress_msg *msg) {
int ret = read(*connfd, msg, sizeof(*msg));
if (ret != sizeof(*msg)) {
--
2.17.1

Stefano Babic

unread,
Jun 24, 2019, 5:27:21 AM6/24/19
to swup...@googlegroups.com, Stefano Babic
Sockets for IPC can be configured or they are retrieved by systemd, if
active. A compile-time configuration forbids multiple instances of
SWUpdate on the same target because they point to the same sockets.
Change the behavior and creates the sockets in the directory pointed by
the environment variable TMPDIR ("/tmp" is the default). Change is still
compatible with the past, the old names are used if they are set.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
Kconfig | 2 --
corelib/network_thread.c | 6 +++---
corelib/progress_thread.c | 8 ++++----
include/network_ipc.h | 1 +
include/progress_ipc.h | 2 ++
ipc/network_ipc.c | 19 +++++++++++++++++--
ipc/progress_ipc.c | 21 ++++++++++++++++++---
7 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/Kconfig b/Kconfig
index 302166e..3f3f672 100644
--- a/Kconfig
+++ b/Kconfig
@@ -146,13 +146,11 @@ menu "Socket Paths"

config SOCKET_CTRL_PATH
string "SWUpdate control socket path"
- default "/tmp/sockinstctrl"
help
Path to SWUpdate's IPC socket.

config SOCKET_PROGRESS_PATH
string "SWUpdate progress socket path"
- default "/tmp/swupdateprog"
help
Path to the socket progress information is sent to.

diff --git a/corelib/network_thread.c b/corelib/network_thread.c
index 22d0b67..0f4137e 100644
--- a/corelib/network_thread.c
+++ b/corelib/network_thread.c
@@ -194,7 +194,7 @@ static void unlink_socket(void)
return;
}
#endif
- unlink((char*)CONFIG_SOCKET_CTRL_PATH);
+ unlink(get_ctrl_socket());
}

void *network_thread (void *data)
@@ -220,7 +220,7 @@ void *network_thread (void *data)
register_notifier(network_notifier);

/* Initialize and bind to UDS */
- ctrllisten = listener_create((char*)CONFIG_SOCKET_CTRL_PATH, SOCK_STREAM);
+ ctrllisten = listener_create(get_ctrl_socket(), SOCK_STREAM);
if (ctrllisten < 0 ) {
TRACE("Error creating IPC sockets");
exit(2);
@@ -228,7 +228,7 @@ void *network_thread (void *data)

if (atexit(unlink_socket) != 0) {
TRACE("Cannot setup socket cleanup on exit, %s won't be unlinked.",
- (char*)CONFIG_SOCKET_CTRL_PATH);
+ get_ctrl_socket());
}

do {
diff --git a/corelib/progress_thread.c b/corelib/progress_thread.c
index 535ed4c..b3fad3d 100644
--- a/corelib/progress_thread.c
+++ b/corelib/progress_thread.c
@@ -185,7 +185,7 @@ static void unlink_socket(void)
return;
}
#endif
- unlink((char*)CONFIG_SOCKET_PROGRESS_PATH);
+ unlink(get_prog_socket());
}

void *progress_bar_thread (void __attribute__ ((__unused__)) *data)
@@ -200,15 +200,15 @@ void *progress_bar_thread (void __attribute__ ((__unused__)) *data)
SIMPLEQ_INIT(&prbar->conns);

/* Initialize and bind to UDS */
- listen = listener_create((char*)CONFIG_SOCKET_PROGRESS_PATH, SOCK_STREAM);
+ listen = listener_create(get_prog_socket(), SOCK_STREAM);
if (listen < 0 ) {
- ERROR("Error creating IPC socket %s, exiting.", (char*)CONFIG_SOCKET_PROGRESS_PATH);
+ ERROR("Error creating IPC socket %s, exiting.", get_prog_socket());
exit(2);
}

if (atexit(unlink_socket) != 0) {
TRACE("Cannot setup socket cleanup on exit, %s won't be unlinked.",
- (char*)CONFIG_SOCKET_PROGRESS_PATH);
+ get_prog_socket());
}

do {
diff --git a/include/network_ipc.h b/include/network_ipc.h
index ea997b8..e41493c 100644
--- a/include/network_ipc.h
+++ b/include/network_ipc.h
@@ -61,6 +61,7 @@ typedef struct {
msgdata data;
} ipc_message;

+char *get_ctrl_socket(void);
int ipc_inst_start(void);
int ipc_inst_start_ext(sourcetype source, size_t len, const char *info, bool dryrun);
int ipc_send_data(int connfd, char *buf, int size);
diff --git a/include/progress_ipc.h b/include/progress_ipc.h
index e93a5d5..2587cc7 100644
--- a/include/progress_ipc.h
+++ b/include/progress_ipc.h
@@ -31,6 +31,8 @@ struct progress_msg {
char info[2048]; /* additional information about install */
};

+char *get_prog_socket(void);
+
/* Standard function to connect to progress interface */
int progress_ipc_connect(bool reconnect);

diff --git a/ipc/network_ipc.c b/ipc/network_ipc.c
index 4cfe2f3..53e577d 100644
--- a/ipc/network_ipc.c
+++ b/ipc/network_ipc.c
@@ -33,9 +33,11 @@
#ifdef CONFIG_SOCKET_CTRL_PATH
static char* SOCKET_CTRL_PATH = (char*)CONFIG_SOCKET_CTRL_PATH;
#else
-static char* SOCKET_CTRL_PATH = (char*)"/tmp/sockinstctrl";
+static char* SOCKET_CTRL_PATH = NULL;
#endif

+#define SOCKET_CTRL_DEFAULT "sockinstctrl"
+
struct async_lib {
int connfd;
int status;
@@ -50,6 +52,19 @@ static pthread_t async_thread_id;

#define get_request() (&request)

+char *get_ctrl_socket(void) {
+ if (!SOCKET_CTRL_PATH || !strlen(SOCKET_CTRL_PATH)) {
+ const char *tmpdir = getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+ if (asprintf(&SOCKET_CTRL_PATH, "%s/%s", tmpdir, SOCKET_CTRL_DEFAULT) == -1)
+ return (char *)"/tmp/"SOCKET_CTRL_DEFAULT;
+ }
+
+ return SOCKET_CTRL_PATH;
+}
+
static int prepare_ipc(void) {

int connfd;
@@ -61,7 +76,7 @@ static int prepare_ipc(void) {
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;

- strcpy(servaddr.sun_path, SOCKET_CTRL_PATH);
+ strncpy(servaddr.sun_path, get_ctrl_socket(), sizeof(servaddr.sun_path));

ret = connect(connfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if (ret < 0) {
diff --git a/ipc/progress_ipc.c b/ipc/progress_ipc.c
index 7e6054c..b339def 100644
--- a/ipc/progress_ipc.c
+++ b/ipc/progress_ipc.c
@@ -16,11 +16,26 @@
#include <progress_ipc.h>

#ifdef CONFIG_SOCKET_PROGRESS_PATH
-char* SOCKET_PROGRESS_PATH = (char*)CONFIG_SOCKET_PROGRESS_PATH;
+char *SOCKET_PROGRESS_PATH = (char*)CONFIG_SOCKET_PROGRESS_PATH;
#else
-char* SOCKET_PROGRESS_PATH = (char*)"/tmp/swupdateprog";
+char *SOCKET_PROGRESS_PATH = NULL;
#endif

+#define SOCKET_PROGRESS_DEFAULT "swupdateprog"
+
+char *get_prog_socket(void) {
+ if (!SOCKET_PROGRESS_PATH || !strlen(SOCKET_PROGRESS_PATH)) {
+ const char *tmpdir = getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+ if (asprintf(&SOCKET_PROGRESS_PATH, "%s/%s", tmpdir, SOCKET_PROGRESS_DEFAULT) == -1)
+ return (char *)"/tmp/"SOCKET_PROGRESS_DEFAULT;
+ }
+
+ return SOCKET_PROGRESS_PATH;
+}
+
static int _progress_ipc_connect(const char *socketpath, bool reconnect)
{
struct sockaddr_un servaddr;
@@ -53,7 +68,7 @@ int progress_ipc_connect_with_path(const char *socketpath, bool reconnect) {

int progress_ipc_connect(bool reconnect)
{
- return _progress_ipc_connect(SOCKET_PROGRESS_PATH, reconnect);
+ return _progress_ipc_connect(get_prog_socket(), reconnect);
}

int progress_ipc_receive(int *connfd, struct progress_msg *msg) {
--
2.17.1

Stefano Babic

unread,
Jun 24, 2019, 5:27:22 AM6/24/19
to swup...@googlegroups.com, Stefano Babic
substring(string, len)

Signed-off-by: Stefano Babic <sba...@denx.de>
---
core/util.c | 22 ++++++++++++++++++++++
include/util.h | 1 +
2 files changed, 23 insertions(+)

diff --git a/core/util.c b/core/util.c
index ea4ad8a..2855b4f 100644
--- a/core/util.c
+++ b/core/util.c
@@ -171,6 +171,28 @@ char *mstrcat(const char **nodes, const char *delim)
return dest;
}

+/*
+ * Alocate and return a string as part of
+ * another string
+ * s = substring(src, n)
+ * the returned string is allocated on the heap
+ * and must be freed by the caller
+ */
+char *substring(const char *src, int first, int len) {
+ char *s;
+ if (len > strlen(src))
+ len = strlen(src);
+ if (first > len)
+ return NULL;
+ s = malloc(len + 1);
+ if (!s)
+ return NULL;
+ memcpy(s, &src[first], len);
+ s[len] = '\0';
+ return s;
+}
+
+
int openfileoutput(const char *filename)
{
int fdout;
diff --git a/include/util.h b/include/util.h
index 445e9f2..4ec0b8d 100644
--- a/include/util.h
+++ b/include/util.h
@@ -181,6 +181,7 @@ int syslog_init(void);
char **splitargs(char *args, int *argc);
char *mstrcat(const char **nodes, const char *delim);
char** string_split(const char* a_str, const char a_delim);
+char *substring(const char *src, int first, int len);
void freeargs (char **argv);
int get_hw_revision(struct hw_type *hw);
void get_sw_versions(char *cfgfname, struct swupdate_cfg *sw);
--
2.17.1

Stefano Babic

unread,
Jun 24, 2019, 5:27:25 AM6/24/19
to swup...@googlegroups.com, Stefano Babic
This tool receives from the progress interface a list of devices that
are updated at the same time to provide a controlled way to restart them
all at once.

The tool reads INFO messages from progress and search for:

REMOTE:<ip address>

If the pattern is found, the ip address is checked (local addresses are
removed) and added to the list. After a successful update, the tool
initiates a system reboot by connecting to the Webserver of each remote
SWUpdate and sends a reboot command via REST-API. At the end, reboots
itself calling reboot().

Signed-off-by: Stefano Babic <sba...@denx.de>
---
tools/Makefile | 3 +-
tools/swupdate-sysrestart.c | 252 ++++++++++++++++++++++++++++++++++++
2 files changed, 254 insertions(+), 1 deletion(-)
create mode 100644 tools/swupdate-sysrestart.c

diff --git a/tools/Makefile b/tools/Makefile
index a4c4c29..ca0a4a3 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -12,7 +12,8 @@ lib-y += \
client.o \
progress.o \
hawkbitcfg.o \
- sendtohawkbit.o
+ sendtohawkbit.o \
+ swupdate-sysrestart.o

# # Uncomment the next lines to integrate the compiling/linking of
# # any .c files placed alongside the above "official" tools in the
diff --git a/tools/swupdate-sysrestart.c b/tools/swupdate-sysrestart.c
new file mode 100644
index 0000000..ab34914
--- /dev/null
+++ b/tools/swupdate-sysrestart.c
@@ -0,0 +1,252 @@
+/*
+ * (C) Copyright 2016
+ * Stefano Babic, DENX Software Engineering, sba...@denx.de.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/select.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <getopt.h>
+#include <curl/curl.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <linux/if_link.h>
+
+#include <progress_ipc.h>
+
+#define MAX_DEVS 100
+#define PATTERN "REMOTE:"
+
+
+/* Store the ip addresses of device to be rebooted */
+static char ipaddrs[MAX_DEVS][NI_MAXHOST];
+
+static bool is_ipaddress(char *ipaddr)
+{
+ struct sockaddr_in sa;
+ int result = inet_pton(AF_INET, ipaddr, &(sa.sin_addr));
+ return result == 1;
+}
+
+static struct option long_options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"wait", no_argument, NULL, 'w'},
+ {"socket", required_argument, NULL, 's'},
+ {NULL, 0, NULL, 0}
+};
+
+static void usage(char *programname)
+{
+ fprintf(stdout, "%s (compiled %s)\n", programname, __DATE__);
+ fprintf(stdout, "Usage %s [OPTION]\n",
+ programname);
+ fprintf(stdout,
+ " -w, --wait : wait for a connection with SWUpdate\n"
+ " -s, --socket <path> : path to progress IPC socket\n"
+ " -h, --help : print this help and exit\n"
+ );
+}
+
+static void restart_system(unsigned int ndevs)
+{
+ int dev;
+ CURL *curl_handle; /* CURL handle */
+ char url[100];
+ CURLcode curlrc;
+ struct ifaddrs *ifaddr, *ifa;
+ char local[NI_MAXHOST];
+
+ /*
+ * Drop local ip address from the list to avoid that
+ * this board reboots before sending all messages
+ * A local reboot will be done by calling reboot()
+ */
+ if (getifaddrs(&ifaddr) != -1) {
+ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+ if ((ifa->ifa_addr == NULL) || (ifa->ifa_addr->sa_family != AF_INET))
+ continue;
+ if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in),
+ local, NI_MAXHOST, NULL, 0, NI_NUMERICHOST))
+ continue;
+
+ for (dev = 0; dev < ndevs; dev++) {
+ if (!strcmp(local, ipaddrs[dev])) {
+ printf("LOCAL IP : %s %s\n", local, ipaddrs[dev]);
+ ipaddrs[dev][0] = '\0';
+ }
+ }
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ for (dev = 0; dev < ndevs; dev++) {
+ if (!strlen(ipaddrs[dev]))
+ continue;
+
+ curl_handle = curl_easy_init();
+ /* something very bad, it should never happen */
+ if (!curl_handle)
+ exit(2);
+ sprintf(url, "http://%s:8080/restart", ipaddrs[dev]);
+ if ((curl_easy_setopt(curl_handle, CURLOPT_POST, 1L) != CURLE_OK) ||
+ /* get verbose debug output please */
+ (curl_easy_setopt(curl_handle, CURLOPT_VERBOSE,
+ 1L) != CURLE_OK) ||
+ (curl_easy_setopt(curl_handle, CURLOPT_URL,
+ url) != CURLE_OK) ||
+ (curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS,
+ "swupdate=reboot") != CURLE_OK) ||
+ (curl_easy_setopt(curl_handle, CURLOPT_USERAGENT,
+ "libcurl-agent/1.0") != CURLE_OK)) {
+ fprintf(stderr, "Error setting curl options\n");
+ exit(2);
+ }
+
+ fprintf(stdout, "Rebooting %s\n", url);
+
+ curlrc = curl_easy_perform(curl_handle);
+ if (curlrc != CURLE_OK && curlrc != CURLE_GOT_NOTHING) {
+ fprintf(stderr, "Cannot reboot %s, try the next one, error(%d) : %s\n",
+ ipaddrs[dev], curlrc, curl_easy_strerror(curlrc));
+ }
+ curl_easy_cleanup(curl_handle);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int connfd;
+ struct progress_msg msg;
+ const char *tmpdir;
+ int opt_w = 0;
+ int c;
+ int ret;
+ int ndevs = 0;
+
+ RECOVERY_STATUS status = IDLE; /* Update Status (Running, Failure) */
+
+ /* Process options with getopt */
+ while ((c = getopt_long(argc, argv, "whs:",
+ long_options, NULL)) != EOF) {
+ switch (c) {
+ case 'w':
+ opt_w = 1;
+ break;
+ case 's':
+ SOCKET_PROGRESS_PATH = strdup(optarg);
+ break;
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ break;
+ default:
+ usage(argv[0]);
+ exit(1);
+ break;
+ }
+ }
+
+ tmpdir = getenv("TMPDIR");
+ if (!tmpdir)
+ tmpdir = "/tmp";
+
+ /* initialize CURL */
+ ret = curl_global_init(CURL_GLOBAL_DEFAULT);
+ if (ret != CURLE_OK) {
+ fprintf(stderr, "CURL cannot be initialized, exiting..\n");
+ exit(1);
+ }
+
+ connfd = -1;
+ while (1) {
+ if (connfd < 0) {
+ connfd = progress_ipc_connect(opt_w);
+ }
+
+ if (progress_ipc_receive(&connfd, &msg) == -1) {
+ continue;
+ }
+
+ /*
+ * Something happens, show the info
+ */
+ if ((status == IDLE) && (msg.status != IDLE)) {
+ fprintf(stdout, "\nUpdate started !\n");
+ fprintf(stdout, "Interface: ");
+ switch (msg.source) {
+ case SOURCE_UNKNOWN:
+ fprintf(stdout, "UNKNOWN\n\n");
+ break;
+ case SOURCE_WEBSERVER:
+ fprintf(stdout, "WEBSERVER\n\n");
+ break;
+ case SOURCE_SURICATTA:
+ fprintf(stdout, "BACKEND\n\n");
+ break;
+ case SOURCE_DOWNLOADER:
+ fprintf(stdout, "DOWNLOADER\n\n");
+ break;
+ case SOURCE_LOCAL:
+ fprintf(stdout, "LOCAL\n\n");
+ break;
+ }
+
+ }
+
+ if (msg.infolen > 0) {
+ char *ipaddr = strstr(msg.info, PATTERN);
+ char *end;
+ if (ipaddr && (strlen(ipaddr) > strlen(PATTERN))) {
+ ipaddr += strlen(PATTERN);
+ end = strchr(ipaddr, '}');
+ if (end)
+ *end = '\0';
+ if (is_ipaddress(ipaddr)) {
+ memset(ipaddrs[ndevs], '0', NI_MAXHOST);
+ strncpy(ipaddrs[ndevs], ipaddr, sizeof(ipaddrs[ndevs]));
+ fprintf(stdout, "Remote device:%s\n", ipaddr);
+ ndevs++;
+ }
+ } else
+ fprintf(stdout, "INFO : %s\n", msg.info);
+ }
+
+ switch (msg.status) {
+ case SUCCESS:
+ fprintf(stdout, "Ready to reboot !\n");
+ restart_system(ndevs);
+ sleep(5);
+
+ if (system("reboot") < 0) { /* It should never happen */
+ fprintf(stdout, "Please reset the board.\n");
+ }
+ break;
+ case FAILURE:
+ ndevs = 0;
+ break;
+ case DONE:
+ fprintf(stdout, "\nDONE.\n");
+ break;
+ default:
+ break;
+ }
+
+ status = msg.status;
+ }
+}
--
2.17.1

Stefano Babic

unread,
Jun 24, 2019, 5:27:26 AM6/24/19
to swup...@googlegroups.com, Stefano Babic
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.

Signed-off-by: Stefano Babic <sba...@denx.de>
---
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

Pierre-Jean Texier

unread,
Jun 24, 2019, 12:48:11 PM6/24/19
to Stefano Babic, swupdate
Hi Stefano,
Thanks a lot for this update !

Works fine on my side, setup:
- Master: Microchip sama5d27-som1-ek1
- Slave: i.MX8
- Build System: Yocto/OE with the commit in [1].

Logs on Master:
--------------

<SNIP>
Ok, swupdate.swu - 408526336 bytes.
* Closing connection 0
[TRACE] : SWUPDATE running : [callback_ws_swupdate] : Change status on
http://192.168.20.1:8080 : START
[TRACE] : SWUPDATE running : [callback_ws_swupdate] : Change status on
http://192.168.20.1:8080 : RUN
[TRACE] : SWUPDATE running : [callback_ws_swupdate] :
http://192.168.20.1:8080 : Software Update started !
[TRACE] : SWUPDATE running : [callback_ws_swupdate] :
http://192.168.20.1:8080 : [extract_file_to_tmp] : Found file: filename
sw-description size 1056
[TRACE] : SWUPDATE running : [callback_ws_swupdate] :
http://192.168.20.1:8080 : [extract_file_to_tmp] : Found file: filename
sw-description.sig size 256
[TRACE] : SWUPDATE running : [callback_ws_swupdate] :
http://192.168.20.1:8080 : [swupdate_verify_file] : Verify signed image:
Read 1056 bytes
[TRACE] : SWUPDATE running : [callback_ws_swupdate] :
http://192.168.20.1:8080 : [swupdate_verify_file] : Verified OK
<SNIP>
[TRACE] : SWUPDATE running : [callback_ws_swupdate] : Change status on
http://192.168.20.1:8080 : SUCCESS
[TRACE] : SWUPDATE running : [extract_files] : END INSTALLING STREAMING
[TRACE] : SWUPDATE running : [network_initializer] : Valid image found:
copying to FLASH

So,

Tested-by: Pierre-Jean Texier <pjte...@koncepto.io>

Thanks !

Pierre-Jean

[1] -
https://github.com/bdx-iot/meta-swupdate/commit/c3bbb572c72982fc261a9c16f355a0d018f7069e
--
Pierre-Jean Texier
Embedded Linux Engineer
https://koncepto.io

Joris Offouga

unread,
Jun 24, 2019, 4:23:13 PM6/24/19
to Stefano Babic, swup...@googlegroups.com

Hi Stefano

Le 24/06/2019 à 11:27, Stefano Babic a écrit :

Works fine on my side, setup:

- Master: Technexion Pico-Pi i.MX7D

- Slave: NXP WaRP7 - Build System: Yocto/OE with the commit in https://github.com/bdx-iot/meta-swupdate/commit/2c030bcaf3a4be990907147135f63b75777db3cb and https://github.com/bdx-iot/meta-swupdate-dev.

Tested-by: Joris Offouga <offoug...@gmail.com>

Best regards,

Joris Offouga

Sam Casacio

unread,
Sep 26, 2019, 5:23:27 PM9/26/19
to swupdate
Hi Stefano,

I was able to confirm these changes fix the behavior Alison observed on our system.  Thank you!

Regards,

-Sam Casacio

On Monday, June 24, 2019 at 1:23:13 PM UTC-7, Joris Offouga wrote:

Hi Stefano

Le 24/06/2019 à 11:27, Stefano Babic a écrit :
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.

Signed-off-by: Stefano Babic <sb...@denx.de>

Tested-by: Joris Offouga <offou...@gmail.com>

Best regards,

Joris Offouga

Stefano Babic

unread,
Sep 27, 2019, 2:32:04 PM9/27/19
to Sam Casacio, swupdate
Hi Sam,

On 26/09/19 23:23, Sam Casacio wrote:
> Hi Stefano,
>
> I was able to confirm these changes fix the behavior Alison observed on
> our system.  Thank you!
>

Thanks for reporting this.

Best regards,
Stefano Babic

> Regards,
>
> -Sam Casacio
>
> On Monday, June 24, 2019 at 1:23:13 PM UTC-7, Joris Offouga wrote:
>
> Hi Stefano
>
> Le 24/06/2019 à 11:27, Stefano Babic a écrit :
>> 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.
>>
>> Signed-off-by: Stefano Babic <sb...@denx.de> <javascript:>
>> + * Stefano Babic, DENX Software Engineering, sba...@denx.de <javascript:>.
>> - priv.cm <http://priv.cm> = curl_multi_init();
>> - curl_multi_add_handle(priv.cm <http://priv.cm>, conn->curl_handle);
>> LIST_INSERT_HEAD(&priv.conns, conn, next);
>> - }
>>
>> - retrieve_msgs(&priv, true);
>> + index++;
>> + }
>>
>> - curl_multi_perform(priv.cm <http://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 <http://priv.cm>, &msgs_left))) {
>> - curl_multi_remove_handle(priv.cm <http://priv.cm>, conn->curl_handle);
>> - curl_easy_cleanup(conn->curl_handle);
>> + swuforward_ws_free(conn);
>> free(conn);
>> + index++;
>> }
>>
>> - curl_multi_cleanup(priv.cm <http://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 <javascript:>.
> <https://github.com/bdx-iot/meta-swupdate/commit/2c030bcaf3a4be990907147135f63b75777db3cb>
> and https://github.com/bdx-iot/meta-swupdate-dev
> <https://github.com/bdx-iot/meta-swupdate-dev>.
>
> Tested-by: Joris Offouga <offou...@gmail.com> <javascript:>
>
> Best regards,
>
> Joris Offouga
>
> --
> You received this message because you are subscribed to the Google
> Groups "swupdate" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to swupdate+u...@googlegroups.com
> <mailto:swupdate+u...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/swupdate/9c199170-2686-4b74-b118-7b3a5a0430b8%40googlegroups.com
> <https://groups.google.com/d/msgid/swupdate/9c199170-2686-4b74-b118-7b3a5a0430b8%40googlegroups.com?utm_medium=email&utm_source=footer>.

Reply all
Reply to author
Forward
0 new messages