--- /dev/null
+/*
+ * auc - attendedsysUpgrade CLI
+ * Copyright (C) 2017 Daniel Golle <daniel@makrotopia.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <glob.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <uci.h>
+#include <uci_blob.h>
+#include <json-c/json.h>
+#include <libubox/ulog.h>
+#include <libubox/list.h>
+#include <libubox/vlist.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/avl-cmp.h>
+#include <libubox/uclient.h>
+#include <libubox/uclient-utils.h>
+#include <libubus.h>
+
+#define REQ_TIMEOUT 15
+#define APIOBJ_CHECK "api/upgrade-check"
+#define APIOBJ_REQUEST "api/upgrade-request"
+
+static const char *user_agent = "auc";
+static char *serverurl;
+static struct ustream_ssl_ctx *ssl_ctx;
+static const struct ustream_ssl_ops *ssl_ops;
+static off_t out_bytes;
+static off_t out_len;
+static off_t out_offset;
+static bool cur_resume;
+static int output_fd = -1;
+static int retry, imagebuilder, building;
+static char *board_name = NULL;
+static char *target = NULL, *subtarget = NULL;
+static char *distribution = NULL, *version = NULL, *revision = NULL;
+static int uptodate;
+static char *filename = NULL;
+
+/*
+ * policy for ubus call system board
+ * see procd/system.c
+ */
+enum {
+ BOARD_KERNEL,
+ BOARD_HOSTNAME,
+ BOARD_SYSTEM,
+ BOARD_MODEL,
+ BOARD_BOARD_NAME,
+ BOARD_RELEASE,
+ __BOARD_MAX,
+};
+
+static const struct blobmsg_policy board_policy[__BOARD_MAX] = {
+ [BOARD_KERNEL] = { .name = "kernel", .type = BLOBMSG_TYPE_STRING },
+ [BOARD_HOSTNAME] = { .name = "hostname", .type = BLOBMSG_TYPE_STRING },
+ [BOARD_SYSTEM] = { .name = "system", .type = BLOBMSG_TYPE_STRING },
+ [BOARD_MODEL] = { .name = "model", .type = BLOBMSG_TYPE_STRING },
+ [BOARD_BOARD_NAME] = { .name = "board_name", .type = BLOBMSG_TYPE_STRING },
+ [BOARD_RELEASE] = { .name = "release", .type = BLOBMSG_TYPE_TABLE },
+};
+
+/*
+ * policy for release information in system board reply
+ * see procd/system.c
+ */
+enum {
+ RELEASE_DISTRIBUTION,
+ RELEASE_VERSION,
+ RELEASE_REVISION,
+ RELEASE_CODENAME,
+ RELEASE_TARGET,
+ RELEASE_DESCRIPTION,
+ __RELEASE_MAX,
+};
+
+static const struct blobmsg_policy release_policy[__RELEASE_MAX] = {
+ [RELEASE_DISTRIBUTION] = { .name = "distribution", .type = BLOBMSG_TYPE_STRING },
+ [RELEASE_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING },
+ [RELEASE_REVISION] = { .name = "revision", .type = BLOBMSG_TYPE_STRING },
+ [RELEASE_CODENAME] = { .name = "codename", .type = BLOBMSG_TYPE_STRING },
+ [RELEASE_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
+ [RELEASE_DESCRIPTION] = { .name = "description", .type = BLOBMSG_TYPE_STRING },
+};
+
+/*
+ * policy for packagelist
+ * see rpcd/sys.c
+ */
+enum {
+ PACKAGELIST_PACKAGES,
+ __PACKAGELIST_MAX,
+};
+
+static const struct blobmsg_policy packagelist_policy[__PACKAGELIST_MAX] = {
+ [PACKAGELIST_PACKAGES] = { .name = "packages", .type = BLOBMSG_TYPE_TABLE },
+};
+
+/*
+ * policy for upgrade_test
+ * see rpcd/sys.c
+ */
+enum {
+ UPGTEST_CODE,
+ UPGTEST_STDOUT,
+ __UPGTEST_MAX,
+};
+
+static const struct blobmsg_policy upgtest_policy[__UPGTEST_MAX] = {
+ [UPGTEST_CODE] = { .name = "code", .type = BLOBMSG_TYPE_INT32 },
+ [UPGTEST_STDOUT] = { .name = "stdout", .type = BLOBMSG_TYPE_STRING },
+};
+
+
+/*
+ * policy to extract version from upgrade-check response
+ */
+enum {
+ CHECK_VERSION,
+ __CHECK_MAX,
+};
+
+static const struct blobmsg_policy check_policy[__CHECK_MAX] = {
+ [CHECK_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING },
+};
+
+/*
+ * policy for upgrade-request response
+ * it can be either only a queue position or the download information
+ * for the ready image.
+ */
+enum {
+ IMAGE_QUEUE,
+ IMAGE_FILESIZE,
+ IMAGE_URL,
+ IMAGE_CHECKSUM,
+ IMAGE_FILES,
+ IMAGE_SYSUPGRADE,
+ __IMAGE_MAX,
+};
+
+static const struct blobmsg_policy image_policy[__IMAGE_MAX] = {
+ [IMAGE_QUEUE] = { .name = "queue", .type = BLOBMSG_TYPE_INT32 },
+ [IMAGE_FILESIZE] = { .name = "filesize", .type = BLOBMSG_TYPE_INT32 },
+ [IMAGE_URL] = { .name = "url", .type = BLOBMSG_TYPE_STRING },
+ [IMAGE_CHECKSUM] = { .name = "checksum", .type = BLOBMSG_TYPE_STRING },
+ [IMAGE_FILES] = { .name = "files", .type = BLOBMSG_TYPE_STRING },
+ [IMAGE_SYSUPGRADE] = { .name = "sysupgrade", .type = BLOBMSG_TYPE_STRING },
+};
+
+
+/*
+ * load serverurl from UCI
+ */
+static int load_config() {
+ static struct uci_context *uci_ctx;
+ static struct uci_package *uci_attendedsysupgrade;
+ struct uci_section *uci_server;
+
+ uci_ctx = uci_alloc_context();
+ if (!uci_ctx)
+ return -1;
+
+ uci_ctx->flags &= ~UCI_FLAG_STRICT;
+
+ if (uci_load(uci_ctx, "attendedsysupgrade", &uci_attendedsysupgrade) ||
+ !uci_attendedsysupgrade) {
+ fprintf(stderr, "Failed to load attendedsysupgrade config\n");
+ return -1;
+ }
+ uci_server = uci_lookup_section(uci_ctx, uci_attendedsysupgrade, "server");
+ if (!uci_server) {
+ fprintf(stderr, "Failed to read server url from config\n");
+ return -1;
+ }
+ serverurl = strdup(uci_lookup_option_string(uci_ctx, uci_server, "url"));
+ uci_free_context(uci_ctx);
+
+ return 0;
+}
+
+
+/**
+ * UBUS response callbacks
+ */
+
+/*
+ * rpc-sys packagelist
+ * append packagelist response to blobbuf given in req->priv
+ */
+static void pkglist_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
+ struct blob_buf *buf = (struct blob_buf *)req->priv;
+ struct blob_attr *tb[__PACKAGELIST_MAX];
+
+ blobmsg_parse(packagelist_policy, __PACKAGELIST_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[PACKAGELIST_PACKAGES]) {
+ fprintf(stderr, "No packagelist received\n");
+ return;
+ }
+
+ blobmsg_add_field(buf, BLOBMSG_TYPE_TABLE, "packages", blobmsg_data(tb[PACKAGELIST_PACKAGES]), blobmsg_data_len(tb[PACKAGELIST_PACKAGES]));
+};
+
+/*
+ * system board
+ * append append board information to blobbuf given in req->priv
+ * populate board and release global strings
+ */
+static void board_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
+ struct blob_buf *buf = (struct blob_buf *)req->priv;
+ struct blob_attr *tb[__BOARD_MAX];
+ struct blob_attr *rel[__RELEASE_MAX];
+
+ blobmsg_parse(board_policy, __BOARD_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[BOARD_BOARD_NAME]) {
+ fprintf(stderr, "No board name received\n");
+ return;
+ }
+ board_name = strdup(blobmsg_get_string(tb[BOARD_BOARD_NAME]));
+
+ if (!tb[BOARD_RELEASE]) {
+ fprintf(stderr, "No release received\n");
+ return;
+ }
+
+ blobmsg_parse(release_policy, __RELEASE_MAX, rel,
+ blobmsg_data(tb[BOARD_RELEASE]), blobmsg_data_len(tb[BOARD_RELEASE]));
+
+ if (!rel[RELEASE_TARGET]) {
+ fprintf(stderr, "No target received\n");
+ return;
+ }
+
+ target = strdup(blobmsg_get_string(rel[RELEASE_TARGET]));
+ subtarget = strchr(target, '/');
+ *subtarget++ = '\0';
+
+ distribution = strdup(blobmsg_get_string(rel[RELEASE_DISTRIBUTION]));
+ version = strdup(blobmsg_get_string(rel[RELEASE_VERSION]));
+ revision = strdup(blobmsg_get_string(rel[RELEASE_REVISION]));
+
+ blobmsg_add_string(buf, "distro", distribution);
+ blobmsg_add_string(buf, "target", target);
+ blobmsg_add_string(buf, "subtarget", subtarget);
+ blobmsg_add_string(buf, "version", version);
+}
+
+/*
+ * rpc-sys upgrade_test
+ * check if downloaded file is accepted by sysupgrade
+ */
+static void upgtest_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
+ int *valid = (int *)req->priv;
+ struct blob_attr *tb[__UPGTEST_MAX];
+
+ blobmsg_parse(upgtest_policy, __UPGTEST_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[UPGTEST_CODE]) {
+ fprintf(stderr, "No sysupgrade test return code received\n");
+ return;
+ }
+
+ *valid = (blobmsg_get_u32(tb[UPGTEST_CODE]) == 0)?1:0;
+ if (*valid == 0)
+ fprintf(stderr, "%s", blobmsg_get_string(tb[UPGTEST_STDOUT]));
+};
+
+/**
+ * uclient stuff
+ */
+static int open_output_file(const char *path, uint64_t resume_offset)
+{
+ char *filename = NULL;
+ int flags;
+ int ret;
+
+ if (cur_resume)
+ flags = O_RDWR;
+ else
+ flags = O_WRONLY | O_EXCL;
+
+ flags |= O_CREAT;
+
+ filename = uclient_get_url_filename(path, "firmware.bin");
+
+ fprintf(stderr, "Writing to '%s'\n", filename);
+ ret = open(filename, flags, 0644);
+ if (ret < 0)
+ goto free;
+
+ if (resume_offset &&
+ lseek(ret, resume_offset, SEEK_SET) < 0) {
+ fprintf(stderr, "Failed to seek %"PRIu64" bytes in output file\n", resume_offset);
+ close(ret);
+ ret = -1;
+ goto free;
+ }
+
+ out_offset = resume_offset;
+ out_bytes += resume_offset;
+
+free:
+ free(filename);
+ return ret;
+}
+
+static void request_done(struct uclient *cl)
+{
+ uclient_disconnect(cl);
+ uloop_end();
+}
+
+static void header_done_cb(struct uclient *cl)
+{
+ enum {
+ H_RANGE,
+ H_LEN,
+ __H_MAX
+ };
+ static const struct blobmsg_policy policy[__H_MAX] = {
+ [H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING },
+ [H_LEN] = { .name = "content-length", .type = BLOBMSG_TYPE_STRING },
+ };
+ struct blob_attr *tb[__H_MAX];
+ uint64_t resume_offset = 0, resume_end, resume_size;
+ static int retries;
+
+ if (retries < 10 && uclient_http_redirect(cl)) {
+ fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
+
+ retries++;
+ return;
+ }
+
+ if (cl->status_code == 204 && cur_resume) {
+ /* Resume attempt failed, try normal download */
+ cur_resume = false;
+ //init_request(cl);
+ return;
+ }
+
+ blobmsg_parse(policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta));
+
+ switch (cl->status_code) {
+ case 400:
+ request_done(cl);
+ break;
+ case 416:
+ fprintf(stderr, "File download already fully retrieved; nothing to do.\n");
+ request_done(cl);
+ break;
+ case 422:
+ fprintf(stderr, "unknown package requested.\n");
+ request_done(cl);
+ break;
+ case 201:
+ if (!imagebuilder) {
+ fprintf(stderr, "server is dispatching build job\n");
+ imagebuilder=1;
+ }
+ retry=1;
+ break;
+ case 204:
+ fprintf(stderr, "system is up to date.\n");
+ uptodate=1;
+ break;
+ case 206:
+ if (!cur_resume) {
+ if (!building) {
+ fprintf(stderr, "server is now building image...\n");
+ building=1;
+ }
+ retry=1;
+ request_done(cl);
+ break;
+ }
+
+ if (!tb[H_RANGE]) {
+ fprintf(stderr, "Content-Range header is missing\n");
+ break;
+ }
+
+ if (sscanf(blobmsg_get_string(tb[H_RANGE]),
+ "bytes %"PRIu64"-%"PRIu64"/%"PRIu64,
+ &resume_offset, &resume_end, &resume_size) != 3) {
+ fprintf(stderr, "Content-Range header is invalid\n");
+ break;
+ }
+ case 200:
+ if (cl->priv)
+ break;
+
+ if (tb[H_LEN])
+ out_len = strtoul(blobmsg_get_string(tb[H_LEN]), NULL, 10);
+
+ output_fd = open_output_file(cl->url->location, resume_offset);
+ if (output_fd < 0) {
+ perror("Cannot open output file");
+ request_done(cl);
+ }
+ break;
+
+ default:
+ fprintf(stderr, "HTTP error %d\n", cl->status_code);
+ request_done(cl);
+ break;
+ }
+}
+
+static void read_data_cb(struct uclient *cl)
+{
+ char buf[256];
+ int len;
+ json_tokener *tok;
+ json_object *jsobj;
+
+ struct blob_buf *outbuf = (struct blob_buf *)cl->priv;
+
+ if (!outbuf) {
+ while (1) {
+ len = uclient_read(cl, buf, sizeof(buf));
+ if (!len)
+ return;
+
+ out_bytes += len;
+ write(output_fd, buf, len);
+ }
+ return;
+ }
+
+ tok = json_tokener_new();
+
+ while (1) {
+ len = uclient_read(cl, buf, sizeof(buf));
+ if (!len)
+ break;
+
+ out_bytes += len;
+
+ jsobj = json_tokener_parse_ex(tok, buf, len);
+
+ if (json_tokener_get_error(tok) == json_tokener_continue)
+ continue;
+
+ if (json_tokener_get_error(tok) != json_tokener_success)
+ break;
+
+ if (jsobj)
+ {
+ if (json_object_get_type(jsobj) == json_type_object)
+ blobmsg_add_object(outbuf, jsobj);
+
+ json_object_put(jsobj);
+ break;
+ }
+ }
+
+ json_tokener_free(tok);
+}
+
+static void eof_cb(struct uclient *cl)
+{
+ if (!cl->data_eof && !uptodate) {
+ fprintf(stderr, "Connection reset prematurely\n");
+ }
+ request_done(cl);
+}
+
+static void handle_uclient_error(struct uclient *cl, int code)
+{
+ const char *type = "Unknown error";
+
+ switch(code) {
+ case UCLIENT_ERROR_CONNECT:
+ type = "Connection failed";
+ break;
+ case UCLIENT_ERROR_TIMEDOUT:
+ type = "Connection timed out";
+ break;
+ case UCLIENT_ERROR_SSL_INVALID_CERT:
+ type = "Invalid SSL certificate";
+ break;
+ case UCLIENT_ERROR_SSL_CN_MISMATCH:
+ type = "Server hostname does not match SSL certificate";
+ break;
+ default:
+ break;
+ }
+
+ fprintf(stderr, "Connection error: %s\n", type);
+
+ request_done(cl);
+}
+
+static const struct uclient_cb check_cb = {
+ .header_done = header_done_cb,
+ .data_read = read_data_cb,
+ .data_eof = eof_cb,
+ .error = handle_uclient_error,
+};
+
+static int server_request(const char *url, struct blob_buf *inbuf, struct blob_buf *outbuf) {
+ struct uclient *ucl;
+ int rc = -1;
+ char *post_data;
+ out_offset = 0;
+ out_bytes = 0;
+ out_len = 0;
+
+ uloop_init();
+
+ ucl = uclient_new(url, NULL, &check_cb);
+ uclient_http_set_ssl_ctx(ucl, ssl_ops, ssl_ctx, 1);
+ ucl->timeout_msecs = REQ_TIMEOUT * 1000;
+ ucl->priv = outbuf;
+ rc = uclient_connect(ucl);
+ if (rc)
+ return rc;
+
+ rc = uclient_http_set_request_type(ucl, inbuf?"POST":"GET");
+ if (rc)
+ return rc;
+
+ uclient_http_reset_headers(ucl);
+ uclient_http_set_header(ucl, "User-Agent", user_agent);
+ if (inbuf) {
+ uclient_http_set_header(ucl, "Content-Type", "text/json");
+ post_data = blobmsg_format_json(inbuf->head, true);
+ uclient_write(ucl, post_data, strlen(post_data));
+ }
+ rc = uclient_request(ucl);
+ if (rc)
+ return rc;
+
+ uloop_run();
+ uloop_done();
+ uclient_free(ucl);
+
+ return 0;
+}
+
+/**
+ * ustream-ssl
+ */
+static int init_ustream_ssl(void) {
+ void *dlh;
+ glob_t gl;
+ int i;
+
+ dlh = dlopen("libustream-ssl.so", RTLD_LAZY | RTLD_LOCAL);
+ if (!dlh)
+ return -1;
+
+ ssl_ops = dlsym(dlh, "ustream_ssl_ops");
+ if (!ssl_ops)
+ return -1;
+
+ ssl_ctx = ssl_ops->context_new(false);
+
+ glob("/etc/ssl/certs/*.crt", 0, NULL, &gl);
+ if (!gl.gl_pathc)
+ return -2;
+
+ for (i = 0; i < gl.gl_pathc; i++)
+ ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]);
+
+ return 0;
+}
+
+/**
+ * use busybox md5sum (from jow's luci-ng)
+ */
+static char *md5sum(const char *file) {
+ pid_t pid;
+ int fds[2];
+ static char md5[33];
+
+ if (pipe(fds))
+ return NULL;
+
+ switch ((pid = fork()))
+ {
+ case -1:
+ return NULL;
+
+ case 0:
+ uloop_done();
+
+ dup2(fds[1], 1);
+
+ close(0);
+ close(2);
+ close(fds[0]);
+ close(fds[1]);
+
+ if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL));
+ return NULL;
+
+ break;
+
+ default:
+ memset(md5, 0, sizeof(md5));
+ read(fds[0], md5, 32);
+ waitpid(pid, NULL, 0);
+ close(fds[0]);
+ close(fds[1]);
+ }
+
+ return md5;
+}
+
+static int ask_user(void)
+{
+ fprintf(stderr, "Are you sure to proceed? [N/y]\n");
+ if (getchar() != 'y')
+ return -1;
+ return 0;
+}
+
+/* this main function is too big... todo: split */
+int main(int args, char *argv[]) {
+ static struct blob_buf checkbuf, reqbuf, imgbuf, upgbuf;
+ struct ubus_context *ctx = ubus_connect(NULL);
+ uint32_t id;
+ int rc;
+ int queuepos, valid;
+ char url[256];
+ char *newversion = NULL;
+ struct blob_attr *tb[__IMAGE_MAX];
+ struct blob_attr *tbc[__CHECK_MAX];
+ unsigned int filesize;
+ char *checksum = NULL;
+ struct stat imgstat;
+
+ if (!ctx) {
+ fprintf(stderr, "failed to connect to ubus.\n");
+ return -1;
+ }
+ if (load_config()) {
+ rc=-1;
+ goto freeubus;
+ }
+
+ if (chdir("/tmp")) {
+ rc=-1;
+ goto freeconfig;
+ }
+
+ rc = init_ustream_ssl();
+ if (rc == -2) {
+ fprintf(stderr, "No CA certificates loaded, please install ca-certificates\n");
+ rc=-1;
+ goto freessl;
+ }
+
+ if (rc || !ssl_ctx) {
+ fprintf(stderr, "SSL support not available, please install ustream-ssl\n");
+ rc=-1;
+ goto freessl;
+ }
+
+ blobmsg_buf_init(&checkbuf);
+ blobmsg_buf_init(&reqbuf);
+ blobmsg_buf_init(&imgbuf);
+ blobmsg_buf_init(&upgbuf);
+
+ if (!ubus_lookup_id(ctx, "system", &id)) {
+ ubus_invoke(ctx, id, "board", NULL, board_cb, &checkbuf, 3000);
+ } else {
+ fprintf(stderr, "cannot request board info from procd\n");
+ rc=-1;
+ goto freebufs;
+ }
+
+ if (!ubus_lookup_id(ctx, "rpc-sys", &id)) {
+ ubus_invoke(ctx, id, "packagelist", NULL, pkglist_cb, &checkbuf, 3000);
+ } else {
+ fprintf(stderr, "cannot request packagelist from rpcd\n");
+ rc=-1;
+ goto freeboard;
+ }
+
+ blobmsg_add_string(&reqbuf, "distro", distribution);
+ blobmsg_add_string(&reqbuf, "target", target);
+ blobmsg_add_string(&reqbuf, "subtarget", subtarget);
+ blobmsg_add_string(&reqbuf, "board", board_name);
+
+ snprintf(url, sizeof(url), "%s/%s", serverurl, APIOBJ_CHECK);
+ uptodate=0;
+ server_request(url, &checkbuf, &reqbuf);
+ blobmsg_parse(check_policy, __CHECK_MAX, tbc, blob_data(reqbuf.head), blob_len(reqbuf.head));
+ if (!tbc[CHECK_VERSION]) {
+ if (!uptodate) {
+ fprintf(stderr, "server reply invalid.\n");
+ rc=-1;
+ goto freeboard;
+ }
+ rc=0;
+ goto freeboard;
+ }
+ newversion = blobmsg_get_string(tbc[CHECK_VERSION]);
+ fprintf(stderr, "new release %s found.\n", newversion);
+
+ rc = ask_user();
+ if (rc)
+ goto freeboard;
+
+ snprintf(url, sizeof(url), "%s/%s", serverurl, APIOBJ_REQUEST);
+
+ imagebuilder = 0;
+ building = 0;
+
+ do {
+ queuepos = 0;
+ retry = 0;
+ server_request(url, &reqbuf, &imgbuf);
+ blobmsg_parse(image_policy, __IMAGE_MAX, tb, blob_data(imgbuf.head), blob_len(imgbuf.head));
+
+ if (tb[IMAGE_QUEUE]) {
+ queuepos = blobmsg_get_u32(tb[IMAGE_QUEUE]);
+ fprintf(stderr, "build is in queue position %u.\n", queuepos);
+ }
+
+ if (retry || queuepos) {
+ if (imgbuf.buf)
+ free(imgbuf.buf);
+
+ memset(&imgbuf, '\0', sizeof(imgbuf));
+ blobmsg_buf_init(&imgbuf);
+ sleep(3);
+ }
+ } while(retry || queuepos);
+
+ if (!tb[IMAGE_SYSUPGRADE]) {
+ fprintf(stderr, "no sysupgrade image returned\n");
+ rc=-1;
+ goto freeboard;
+ }
+ strncpy(url, blobmsg_get_string(tb[IMAGE_SYSUPGRADE]), sizeof(url));
+
+ if (!tb[IMAGE_FILESIZE]) {
+ fprintf(stderr, "no image size returned\n");
+ rc=-1;
+ goto freeboard;
+ }
+ filesize = blobmsg_get_u32(tb[IMAGE_FILESIZE]);
+
+ if (!tb[IMAGE_CHECKSUM]) {
+ fprintf(stderr, "no image checksum returned\n");
+ rc=-1;
+ goto freeboard;
+ }
+ checksum = blobmsg_get_string(tb[IMAGE_CHECKSUM]);
+ server_request(url, NULL, NULL);
+/* usign signature is not yet implemented! */
+// strncat(url, ".sig", sizeof(url));
+// server_request(url, NULL, NULL);
+ filename = uclient_get_url_filename(url, "firmware.bin");
+
+ if (stat(filename, &imgstat)) {
+ fprintf(stderr, "image download failed\n");
+ rc=-1;
+ goto freeboard;
+ }
+
+ if ((intmax_t)imgstat.st_size != filesize) {
+ fprintf(stderr, "file size mismatch\n");
+ unlink(filename);
+ rc=-1;
+ goto freeboard;
+ }
+
+ if (strncmp(checksum, md5sum(filename), 33)) {
+ fprintf(stderr, "image checksum mismatch\n");
+ unlink(filename);
+ rc=-1;
+ goto freeboard;
+ };
+
+ if (strcmp(filename, "firmware.bin")) {
+ if (rename(filename, "firmware.bin")) {
+ fprintf(stderr, "can't rename to firmware.bin\n");
+ unlink(filename);
+ rc=-1;
+ goto freeboard;
+ }
+ }
+
+ if (!ubus_lookup_id(ctx, "rpc-sys", &id)) {
+ valid = 0;
+ ubus_invoke(ctx, id, "upgrade_test", NULL, upgtest_cb, &valid, 3000);
+ if (!valid) {
+ rc=-1;
+ goto freeboard;
+ }
+
+ blobmsg_add_u8(&upgbuf, "keep", 1);
+ fprintf(stderr, "invoking sysupgrade\n");
+ ubus_invoke(ctx, id, "upgrade_start", upgbuf.head, NULL, NULL, 3000);
+ } else {
+ rc=-1;
+ }
+
+freeboard:
+ free(board_name);
+ free(target);
+ /* subtarget is a pointer within target, don't free */
+ free(distribution);
+ free(version);
+ free(revision);
+
+
+freebufs:
+ if (checkbuf.buf)
+ free(checkbuf.buf);
+
+ if (reqbuf.buf)
+ free(reqbuf.buf);
+
+ if (imgbuf.buf)
+ free(imgbuf.buf);
+
+ if (upgbuf.buf)
+ free(upgbuf.buf);
+
+freessl:
+ if (ssl_ctx)
+ ssl_ops->context_free(ssl_ctx);
+
+freeconfig:
+ free(serverurl);
+
+freeubus:
+ ubus_free(ctx);
+
+ return rc;
+}