openwrt-packages/utils/auc/src/auc.c

1834 lines
44 KiB
C

/*
* auc - attendedsysUpgrade CLI
* Copyright (C) 2017-2021 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
#ifndef AUC_VERSION
#define AUC_VERSION "unknown"
#endif
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <glob.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdbool.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 API_BRANCHES "branches"
#define API_INDEX "index"
#define API_JSON "json"
#define API_JSON_VERSION "v1"
#define API_JSON_EXT "." API_JSON
#define API_PACKAGES "packages"
#define API_REQUEST "api/v1/build"
#define API_STATUS_QUEUED "queued"
#define API_STATUS_STARTED "started"
#define API_STORE "store"
#define API_TARGETS "targets"
#define PUBKEY_PATH "/etc/opkg/keys"
#define SHA256SUM "/bin/busybox sha256sum"
#ifdef AUC_DEBUG
#define DPRINTF(...) if (debug) fprintf(stderr, __VA_ARGS__)
#else
#define DPRINTF(...)
#endif
static const char server_issues[]="https://github.com/aparcar/asu/issues";
static struct ubus_context *ctx;
static struct uclient *ucl = NULL;
static char user_agent[80];
static char *serverurl;
static int upgrade_packages;
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 bool retry = false;
static char *board_name = NULL;
static char *target = NULL;
static char *distribution = NULL, *version = NULL, *revision = NULL;
static char *rootfs_type = NULL;
static int uptodate;
static char *filename = NULL;
static void *dlh = NULL;
static int rc;
struct branch {
struct list_head list;
char *name;
char *git_branch;
char *version;
char *version_code;
char *version_number;
bool snapshot;
char *path;
char *path_packages;
char *arch_packages;
char **repos;
};
static LIST_HEAD(branches);
struct avl_pkg {
struct avl_node avl;
char *name;
char *version;
};
static struct avl_tree pkg_tree = AVL_TREE_INIT(pkg_tree, avl_strcmp, false, NULL);
#ifdef AUC_DEBUG
static int debug = 0;
#endif
/*
* policy for ubus call system board
* see procd/system.c
*/
enum {
BOARD_BOARD_NAME,
BOARD_RELEASE,
BOARD_ROOTFS_TYPE,
__BOARD_MAX,
};
static const struct blobmsg_policy board_policy[__BOARD_MAX] = {
[BOARD_BOARD_NAME] = { .name = "board_name", .type = BLOBMSG_TYPE_STRING },
[BOARD_RELEASE] = { .name = "release", .type = BLOBMSG_TYPE_TABLE },
[BOARD_ROOTFS_TYPE] = { .name = "rootfs_type", .type = BLOBMSG_TYPE_STRING },
};
/*
* policy for release information in system board reply
* see procd/system.c
*/
enum {
RELEASE_DISTRIBUTION,
RELEASE_REVISION,
RELEASE_TARGET,
RELEASE_VERSION,
__RELEASE_MAX,
};
static const struct blobmsg_policy release_policy[__RELEASE_MAX] = {
[RELEASE_DISTRIBUTION] = { .name = "distribution", .type = BLOBMSG_TYPE_STRING },
[RELEASE_REVISION] = { .name = "revision", .type = BLOBMSG_TYPE_STRING },
[RELEASE_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
[RELEASE_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING },
};
/*
* policy for package list returned from rpc-sys or from server
* see rpcd/sys.c and ASU sources
*/
enum {
PACKAGES_ARCHITECTURE,
PACKAGES_PACKAGES,
__PACKAGES_MAX,
};
static const struct blobmsg_policy packages_policy[__PACKAGES_MAX] = {
[PACKAGES_ARCHITECTURE] = { .name = "architecture", .type = BLOBMSG_TYPE_STRING },
[PACKAGES_PACKAGES] = { .name = "packages", .type = BLOBMSG_TYPE_TABLE },
};
/*
* policy for upgrade_test
* see rpcd/sys.c
*/
enum {
UPGTEST_CODE,
UPGTEST_STDERR,
__UPGTEST_MAX,
};
static const struct blobmsg_policy upgtest_policy[__UPGTEST_MAX] = {
[UPGTEST_CODE] = { .name = "code", .type = BLOBMSG_TYPE_INT32 },
[UPGTEST_STDERR] = { .name = "stderr", .type = BLOBMSG_TYPE_STRING },
};
/*
* policy for branches.json
*/
enum {
BRANCH_ENABLED,
BRANCH_GIT_BRANCH,
BRANCH_NAME,
BRANCH_PATH,
BRANCH_PATH_PACKAGES,
BRANCH_SNAPSHOT,
BRANCH_TARGETS,
BRANCH_VERSIONS,
__BRANCH_MAX,
};
static const struct blobmsg_policy branches_policy[__BRANCH_MAX] = {
[BRANCH_ENABLED] = { .name = "enabled", .type = BLOBMSG_TYPE_BOOL },
[BRANCH_GIT_BRANCH] = { .name = "git_branch", .type = BLOBMSG_TYPE_STRING },
[BRANCH_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
[BRANCH_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
[BRANCH_PATH_PACKAGES] = { .name = "path_packages", .type = BLOBMSG_TYPE_STRING },
[BRANCH_SNAPSHOT] = { .name = "snapshot", .type = BLOBMSG_TYPE_BOOL },
[BRANCH_TARGETS] = { .name = "targets", .type = BLOBMSG_TYPE_TABLE },
[BRANCH_VERSIONS] = { .name = "versions", .type = BLOBMSG_TYPE_ARRAY },
};
/*
* shared policy for target.json and server image request reply
*/
enum {
TARGET_ARCH_PACKAGES,
TARGET_BINDIR,
TARGET_DEVICE_PACKAGES,
TARGET_ENQUEUED_AT,
TARGET_IMAGES,
TARGET_DETAIL,
TARGET_MANIFEST,
TARGET_METADATA_VERSION,
TARGET_REQUEST_HASH,
TARGET_QUEUE_POSITION,
TARGET_STATUS,
TARGET_STDERR,
TARGET_STDOUT,
TARGET_TARGET,
TARGET_TITLES,
TARGET_VERSION_CODE,
TARGET_VERSION_NUMBER,
__TARGET_MAX,
};
static const struct blobmsg_policy target_policy[__TARGET_MAX] = {
[TARGET_ARCH_PACKAGES] = { .name = "arch_packages", .type = BLOBMSG_TYPE_STRING },
[TARGET_BINDIR] = { .name = "bin_dir", .type = BLOBMSG_TYPE_STRING },
[TARGET_DEVICE_PACKAGES] = { .name = "device_packages", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_ENQUEUED_AT] = { .name = "enqueued_at", .type = BLOBMSG_TYPE_STRING },
[TARGET_IMAGES] = { .name = "images", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_MANIFEST] = { .name = "manifest", .type = BLOBMSG_TYPE_TABLE },
[TARGET_DETAIL] = { .name = "detail", .type = BLOBMSG_TYPE_STRING },
[TARGET_METADATA_VERSION] = { .name = "metadata_version", .type = BLOBMSG_TYPE_INT32 },
[TARGET_REQUEST_HASH] = { .name = "request_hash", .type = BLOBMSG_TYPE_STRING },
[TARGET_QUEUE_POSITION] = { .name = "queue_position", .type = BLOBMSG_TYPE_INT32 },
[TARGET_STATUS] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
[TARGET_STDERR] = { .name = "stderr", .type = BLOBMSG_TYPE_STRING },
[TARGET_STDOUT] = { .name = "stdout", .type = BLOBMSG_TYPE_STRING },
[TARGET_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
[TARGET_TITLES] = { .name = "titles", .type = BLOBMSG_TYPE_ARRAY },
[TARGET_VERSION_CODE] = { .name = "version_code", .type = BLOBMSG_TYPE_STRING },
[TARGET_VERSION_NUMBER] = { .name = "version_number", .type = BLOBMSG_TYPE_STRING },
};
/*
* policy for images object in target
*/
enum {
IMAGES_FILESYSTEM,
IMAGES_NAME,
IMAGES_SHA256,
IMAGES_TYPE,
__IMAGES_MAX,
};
static const struct blobmsg_policy images_policy[__IMAGES_MAX] = {
[IMAGES_FILESYSTEM] = { .name = "filesystem", .type = BLOBMSG_TYPE_STRING },
[IMAGES_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
[IMAGES_SHA256] = { .name = "sha256", .type = BLOBMSG_TYPE_STRING },
[IMAGES_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
};
/*
* generic policy for HTTP JSON reply
*/
enum {
REPLY_ARRAY,
REPLY_OBJECT,
__REPLY_MAX,
};
static const struct blobmsg_policy reply_policy[__REPLY_MAX] = {
[REPLY_ARRAY] = { .name = "reply", .type = BLOBMSG_TYPE_ARRAY },
[REPLY_OBJECT] = { .name = "reply", .type = BLOBMSG_TYPE_TABLE },
};
/*
* policy for HTTP headers received from server
*/
enum {
H_LEN,
H_RANGE,
H_UNKNOWN_PACKAGE,
H_QUEUE_POSITION,
__H_MAX
};
static const struct blobmsg_policy header_policy[__H_MAX] = {
[H_LEN] = { .name = "content-length", .type = BLOBMSG_TYPE_STRING },
[H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING },
[H_UNKNOWN_PACKAGE] = { .name = "x-unknown-package", .type = BLOBMSG_TYPE_STRING },
[H_QUEUE_POSITION] = { .name = "x-queue-position", .type = BLOBMSG_TYPE_INT32 },
};
/*
* load serverurl from UCI
*/
static int load_config() {
struct uci_context *uci_ctx;
struct uci_package *uci_attendedsysupgrade;
struct uci_section *uci_s;
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_s = uci_lookup_section(uci_ctx, uci_attendedsysupgrade, "server");
if (!uci_s) {
fprintf(stderr, "Failed to read server url from config\n");
return -1;
}
serverurl = strdup(uci_lookup_option_string(uci_ctx, uci_s, "url"));
uci_s = uci_lookup_section(uci_ctx, uci_attendedsysupgrade, "client");
if (!uci_s) {
fprintf(stderr, "Failed to read client config\n");
return -1;
}
upgrade_packages = atoi(uci_lookup_option_string(uci_ctx, uci_s, "upgrade_packages"));
uci_free_context(uci_ctx);
return 0;
}
/*
* libdpkg - Debian packaging suite library routines
* vercmp.c - comparison of version numbers
*
* Copyright (C) 1995 Ian Jackson <iwj10@cus.cam.ac.uk>
*/
/* assume ascii; warning: evaluates x multiple times! */
#define order(x) ((x) == '~' ? -1 \
: isdigit((x)) ? 0 \
: !(x) ? 0 \
: isalpha((x)) ? (x) \
: (x) + 256)
static int verrevcmp(const char *val, const char *ref)
{
if (!val)
val = "";
if (!ref)
ref = "";
while (*val || *ref) {
int first_diff = 0;
while ((*val && !isdigit(*val)) || (*ref && !isdigit(*ref))) {
int vc = order(*val), rc = order(*ref);
if (vc != rc)
return vc - rc;
val++;
ref++;
}
while (*val == '0')
val++;
while (*ref == '0')
ref++;
while (isdigit(*val) && isdigit(*ref)) {
if (!first_diff)
first_diff = *val - *ref;
val++;
ref++;
}
if (isdigit(*val))
return 1;
if (isdigit(*ref))
return -1;
if (first_diff)
return first_diff;
}
return 0;
}
/*
* replace '-rc' by '~' in string
*/
static inline void release_replace_rc(char *ver)
{
char *tmp;
tmp = strstr(ver, "-rc");
if (tmp && strlen(tmp) > 3) {
*tmp = '~';
memmove(tmp + 1, tmp + 3, strlen(tmp + 3) + 1);
}
}
/*
* OpenWrt release version string comperator
* replaces '-rc' by '~' to fix ordering of release(s) (candidates)
* using the void release_replace_rc(char *ver) function above.
*/
static int openwrt_release_verrevcmp(const char *ver1, const char *ver2)
{
char mver1[16], mver2[16];
strncpy(mver1, ver1, sizeof(mver1) - 1);
mver1[sizeof(mver1) - 1] = '\0';
strncpy(mver2, ver2, sizeof(mver2) - 1);
mver2[sizeof(mver2) - 1] = '\0';
release_replace_rc(mver1);
release_replace_rc(mver2);
return verrevcmp(mver1, mver2);
}
/**
* UBUS response callbacks
*/
/*
* rpc-sys packagelist
* append array of package names to blobbuf given in req->priv
*/
#define ANSI_ESC "\x1b"
#define ANSI_COLOR_RESET ANSI_ESC "[0m"
#define ANSI_COLOR_RED ANSI_ESC "[1;31m"
#define ANSI_COLOR_GREEN ANSI_ESC "[1;32m"
#define ANSI_CURSOR_SAFE "[s"
#define ANSI_CURSOR_RESTORE "[u"
#define ANSI_ERASE_LINE "[K"
#define PKG_UPGRADE 0x1
#define PKG_DOWNGRADE 0x2
#define PKG_NOT_FOUND 0x4
#define PKG_ERROR 0x8
static inline bool is_builtin_pkg(const char *pkgname)
{
return !strcmp(pkgname, "libc") ||
!strcmp(pkgname, "librt") ||
!strcmp(pkgname, "libpthread") ||
!strcmp(pkgname, "kernel");
}
static void pkglist_check_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
int *status = (int *)req->priv;
struct blob_attr *tb[__PACKAGES_MAX], *cur;
struct avl_pkg *pkg;
int rem;
int cmpres;
blobmsg_parse(packages_policy, __PACKAGES_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
if (!tb[PACKAGES_PACKAGES])
return;
blobmsg_for_each_attr(cur, tb[PACKAGES_PACKAGES], rem) {
if (is_builtin_pkg(blobmsg_name(cur)))
continue;
pkg = avl_find_element(&pkg_tree, blobmsg_name(cur), pkg, avl);
if (!pkg) {
fprintf(stderr, "installed package %s%s%s cannot be found in remote list!\n",
ANSI_COLOR_RED, blobmsg_name(cur), ANSI_COLOR_RESET);
*status |= PKG_NOT_FOUND;
continue;
}
cmpres = verrevcmp(blobmsg_get_string(cur), pkg->version);
if (cmpres < 0)
*status |= PKG_UPGRADE;
if (cmpres > 0)
*status |= PKG_DOWNGRADE;
if (cmpres
#ifdef AUC_DEBUG
|| debug
#endif
)
fprintf(stderr, " %s: %s%s -> %s%s\n", blobmsg_name(cur),
(!cmpres)?"":(cmpres > 0)?ANSI_COLOR_RED:ANSI_COLOR_GREEN,
blobmsg_get_string(cur), pkg->version,
(cmpres)?ANSI_COLOR_RESET:"");
}
}
/*
* rpc-sys packagelist
* append array of package names to blobbuf given in req->priv
*/
static void pkglist_req_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
struct blob_buf *buf = (struct blob_buf *)req->priv;
struct blob_attr *tb[__PACKAGES_MAX];
struct blob_attr *cur;
int rem;
struct avl_pkg *pkg;
void *table;
blobmsg_parse(packages_policy, __PACKAGES_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[PACKAGES_PACKAGES]) {
fprintf(stderr, "No packagelist received\n");
return;
}
table = blobmsg_open_table(buf, "packages_versions");
blobmsg_for_each_attr(cur, tb[PACKAGES_PACKAGES], rem) {
if (is_builtin_pkg(blobmsg_name(cur)))
continue;
pkg = avl_find_element(&pkg_tree, blobmsg_name(cur), pkg, avl);
if (!pkg)
continue;
blobmsg_add_string(buf, blobmsg_name(cur), pkg->version);
}
blobmsg_close_table(buf, table);
};
/*
* 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_RELEASE]) {
fprintf(stderr, "No release received\n");
rc=-ENODATA;
return;
}
blobmsg_parse(release_policy, __RELEASE_MAX, rel,
blobmsg_data(tb[BOARD_RELEASE]), blobmsg_data_len(tb[BOARD_RELEASE]));
if (!rel[RELEASE_TARGET] ||
!rel[RELEASE_DISTRIBUTION] ||
!rel[RELEASE_VERSION] ||
!rel[RELEASE_REVISION]) {
fprintf(stderr, "No release information received\n");
rc=-ENODATA;
return;
}
target = strdup(blobmsg_get_string(rel[RELEASE_TARGET]));
distribution = strdup(blobmsg_get_string(rel[RELEASE_DISTRIBUTION]));
version = strdup(blobmsg_get_string(rel[RELEASE_VERSION]));
revision = strdup(blobmsg_get_string(rel[RELEASE_REVISION]));
if (!strcmp(target, "x86/64") || !strcmp(target, "x86/generic")) {
/*
* ugly work-around ahead:
* ignore board name on generic x86 targets, as image name is always 'generic'
*/
board_name = strdup("generic");
} else {
if (!tb[BOARD_BOARD_NAME]) {
fprintf(stderr, "No board name received\n");
rc=-ENODATA;
return;
}
board_name = strdup(blobmsg_get_string(tb[BOARD_BOARD_NAME]));
}
if (tb[BOARD_ROOTFS_TYPE])
rootfs_type = strdup(blobmsg_get_string(tb[BOARD_ROOTFS_TYPE]));
blobmsg_add_string(buf, "target", target);
blobmsg_add_string(buf, "version", version);
blobmsg_add_string(buf, "revision", revision);
}
/*
* 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 (tb[UPGTEST_STDERR])
fprintf(stderr, "%s", blobmsg_get_string(tb[UPGTEST_STDERR]));
else if (*valid == 0)
fprintf(stderr, "image verification failed\n");
else
fprintf(stderr, "image verification succeeded\n");
};
/**
* 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;
}
struct jsonblobber {
json_tokener *tok;
struct blob_buf *outbuf;
};
static void request_done(struct uclient *cl)
{
struct jsonblobber *jsb = (struct jsonblobber *)cl->priv;
if (jsb) {
json_tokener_free(jsb->tok);
free(jsb);
};
uclient_disconnect(cl);
uloop_end();
}
static void header_done_cb(struct uclient *cl)
{
struct blob_attr *tb[__H_MAX];
struct jsonblobber *jsb = (struct jsonblobber *)cl->priv;
struct blob_buf *outbuf = NULL;
if (jsb)
outbuf = jsb->outbuf;
uint64_t resume_offset = 0, resume_end, resume_size;
if (uclient_http_redirect(cl)) {
fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
return;
}
if (cl->status_code == 204 && cur_resume) {
/* Resume attempt failed, try normal download */
cur_resume = false;
//init_request(cl);
return;
}
DPRINTF("status code: %d\n", cl->status_code);
DPRINTF("headers:\n%s\n", blobmsg_format_json_indent(cl->meta, true, 0));
blobmsg_parse(header_policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta));
switch (cl->status_code) {
case 400:
request_done(cl);
rc=-ESRCH;
break;
case 422:
fprintf(stderr, "unknown package '%s' requested.\n",
blobmsg_get_string(tb[H_UNKNOWN_PACKAGE]));
rc=-ENOPKG;
request_done(cl);
break;
case 201:
case 202:
retry = true;
if (!outbuf)
break;
blobmsg_add_u32(outbuf, "status", cl->status_code);
if (tb[H_QUEUE_POSITION])
blobmsg_add_u32(outbuf, "queue_position", blobmsg_get_u32(tb[H_QUEUE_POSITION]));
break;
case 200:
retry = false;
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;
case 500:
/* server may reply JSON object */
break;
default:
DPRINTF("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_object *jsobj;
struct blob_buf *outbuf = NULL;
json_tokener *tok = NULL;
struct jsonblobber *jsb = (struct jsonblobber *)cl->priv;
if (!jsb) {
while (1) {
len = uclient_read(cl, buf, sizeof(buf));
if (!len)
return;
out_bytes += len;
write(output_fd, buf, len);
}
return;
}
outbuf = jsb->outbuf;
tok = jsb->tok;
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)
{
blobmsg_add_json_element(outbuf, "reply", jsobj);
json_object_put(jsobj);
break;
}
}
}
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 jsonblobber *jsb = NULL;
int rc = -ENOENT;
char *post_data;
out_offset = 0;
out_bytes = 0;
out_len = 0;
#ifdef AUC_DEBUG
if (debug)
fprintf(stderr, "Requesting URL: %s\n", url);
#endif
if (outbuf) {
jsb = malloc(sizeof(struct jsonblobber));
jsb->outbuf = outbuf;
jsb->tok = json_tokener_new();
};
if (!ucl) {
ucl = uclient_new(url, NULL, &check_cb);
uclient_http_set_ssl_ctx(ucl, ssl_ops, ssl_ctx, 1);
ucl->timeout_msecs = REQ_TIMEOUT * 1000;
} else {
uclient_set_url(ucl, url, NULL);
}
ucl->priv = jsb;
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", "application/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();
return 0;
}
/**
* ustream-ssl
*/
static int init_ustream_ssl(void) {
glob_t gl;
int i;
dlh = dlopen("libustream-ssl.so", RTLD_LAZY | RTLD_LOCAL);
if (!dlh)
return -ENOENT;
ssl_ops = dlsym(dlh, "ustream_ssl_ops");
if (!ssl_ops)
return -ENOENT;
ssl_ctx = ssl_ops->context_new(false);
glob("/etc/ssl/certs/*.crt", 0, NULL, &gl);
if (!gl.gl_pathc)
return -ENOKEY;
for (i = 0; i < gl.gl_pathc; i++)
ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]);
return 0;
}
static int ask_user(void)
{
fprintf(stderr, "Are you sure you want to continue the upgrade process? [N/y] ");
if (getchar() != 'y')
return -EINTR;
return 0;
}
static char* alloc_replace_var(char *in, const char *var, const char *replace)
{
char *tmp = in;
char *res = NULL;
char *eptr;
while ((tmp = strchr(tmp, '{'))) {
++tmp;
eptr = strchr(tmp, '}');
if (!eptr)
return NULL;
if (!strncmp(tmp, var, (unsigned int)(eptr - tmp))) {
asprintf(&res, "%.*s%s%s",
(unsigned int)(tmp - in) - 1, in, replace, eptr + 1);
break;
}
}
if (!res)
res = strdup(in);
return res;
}
static int request_target(struct branch *br, char *url)
{
static struct blob_buf boardbuf;
struct blob_attr *tbr[__REPLY_MAX], *tb[__TARGET_MAX];
blobmsg_buf_init(&boardbuf);
if ((rc = server_request(url, NULL, &boardbuf))) {
blob_buf_free(&boardbuf);
return rc;
}
blobmsg_parse(reply_policy, __REPLY_MAX, tbr, blob_data(boardbuf.head), blob_len(boardbuf.head));
if (!tbr[REPLY_OBJECT])
return -ENODATA;
blobmsg_parse(target_policy, __TARGET_MAX, tb, blobmsg_data(tbr[REPLY_OBJECT]), blobmsg_len(tbr[REPLY_OBJECT]));
if (!tb[TARGET_METADATA_VERSION] ||
!tb[TARGET_ARCH_PACKAGES] ||
!tb[TARGET_IMAGES] ||
!tb[TARGET_TARGET]) {
blob_buf_free(&boardbuf);
return -ENODATA;
}
if (blobmsg_get_u32(tb[TARGET_METADATA_VERSION]) != 1) {
blob_buf_free(&boardbuf);
return -EPFNOSUPPORT;
}
if (strcmp(blobmsg_get_string(tb[TARGET_TARGET]), target))
return -EINVAL;
if (strcmp(blobmsg_get_string(tb[TARGET_ARCH_PACKAGES]), br->arch_packages))
return -EINVAL;
if (tb[TARGET_VERSION_CODE])
br->version_code = strdup(blobmsg_get_string(tb[TARGET_VERSION_CODE]));
if (tb[TARGET_VERSION_NUMBER])
br->version_number = strdup(blobmsg_get_string(tb[TARGET_VERSION_NUMBER]));
blob_buf_free(&boardbuf);
return 0;
};
static char* validate_target(struct blob_attr *branch)
{
struct blob_attr *cur;
int rem;
blobmsg_for_each_attr(cur, branch, rem)
if (!strcmp(blobmsg_name(cur), target))
return strdup(blobmsg_get_string(cur));
return NULL;
}
static void process_branch(struct blob_attr *branch, bool only_active)
{
struct blob_attr *tb[__BRANCH_MAX];
struct blob_attr *curver;
int remver;
struct branch *br;
char *tmp, *arch_packages, *board_json_file;
const char *brname;
blobmsg_parse(branches_policy, __BRANCH_MAX, tb, blobmsg_data(branch), blobmsg_len(branch));
/* mandatory fields */
if (!(tb[BRANCH_ENABLED] && blobmsg_get_bool(tb[BRANCH_ENABLED]) &&
tb[BRANCH_NAME] && tb[BRANCH_PATH] && tb[BRANCH_PATH_PACKAGES] &&
tb[BRANCH_VERSIONS] && tb[BRANCH_TARGETS]))
return;
brname = blobmsg_get_string(tb[BRANCH_NAME]);
if (only_active && strncmp(brname, version, strlen(brname)))
return;
/* check if target is offered in branch and get arch_packages */
arch_packages = validate_target(tb[BRANCH_TARGETS]);
if (!arch_packages)
return;
/* add each version of the branch */
blobmsg_for_each_attr(curver, tb[BRANCH_VERSIONS], remver) {
br = malloc(sizeof(struct branch));
if (tb[BRANCH_GIT_BRANCH])
br->git_branch = strdup(blobmsg_get_string(tb[BRANCH_GIT_BRANCH]));
br->name = strdup(blobmsg_get_string(tb[BRANCH_NAME]));
br->path = strdup(blobmsg_get_string(tb[BRANCH_PATH]));
br->path_packages = strdup(blobmsg_get_string(tb[BRANCH_PATH_PACKAGES]));
br->version = strdup(blobmsg_get_string(curver));
br->snapshot = !!strcasestr(blobmsg_get_string(curver), "snapshot");
br->path = alloc_replace_var(blobmsg_get_string(tb[BRANCH_PATH]), "version", br->version);
br->path_packages = alloc_replace_var(blobmsg_get_string(tb[BRANCH_PATH_PACKAGES]), "branch", br->name);
br->arch_packages = arch_packages;
if (!br->path || !br->path_packages) {
free(br);
continue;
}
asprintf(&board_json_file, "%s/%s/%s/%s/%s/%s/%s%s", serverurl, API_JSON,
API_JSON_VERSION, br->path, API_TARGETS, target, board_name, API_JSON_EXT);
tmp = board_json_file;
while ((tmp = strchr(tmp, ',')))
*tmp = '_';
if (request_target(br, board_json_file)) {
free(board_json_file);
free(br);
continue;
}
free(board_json_file);
list_add_tail(&br->list, &branches);
}
}
static int request_branches(bool only_active)
{
static struct blob_buf brbuf;
struct blob_attr *cur;
struct blob_attr *tb[__REPLY_MAX];
int rem;
char url[256];
struct blob_attr *data;
blobmsg_buf_init(&brbuf);
snprintf(url, sizeof(url), "%s/%s/%s/%s%s", serverurl, API_JSON,
API_JSON_VERSION, API_BRANCHES, API_JSON_EXT);
if ((rc = server_request(url, NULL, &brbuf))) {
blob_buf_free(&brbuf);
return rc;
};
blobmsg_parse(reply_policy, __REPLY_MAX, tb, blob_data(brbuf.head), blob_len(brbuf.head));
/* newer server API replies OBJECT, older API replies ARRAY... */
if ((!tb[REPLY_ARRAY] && !tb[REPLY_OBJECT]))
return -ENODATA;
if (tb[REPLY_OBJECT])
data = tb[REPLY_OBJECT];
else
data = tb[REPLY_ARRAY];
blobmsg_for_each_attr(cur, data, rem)
process_branch(cur, only_active);
blob_buf_free(&brbuf);
return 0;
}
static struct branch *select_branch(char *name, char *select_version)
{
struct branch *br, *abr = NULL;
if (!name)
name = version;
list_for_each_entry(br, &branches, list) {
/* if branch name doesn't match version *prefix*, skip */
if (strncasecmp(br->name, name, strlen(br->name)))
continue;
if (select_version) {
if (!strcasecmp(br->version, select_version)) {
abr = br;
break;
}
} else {
if (strcasestr(name, "snapshot")) {
/* if we are on the snapshot branch, stay there */
if (br->snapshot) {
abr = br;
break;
}
} else {
/* on release branch, skip snapshots and pick latest release */
if (br->snapshot)
continue;
if (!abr || (openwrt_release_verrevcmp(abr->version, br->version) < 0))
abr = br;
}
}
}
return abr;
}
static int add_upg_packages(struct blob_attr *reply, char *arch)
{
struct blob_attr *tbr[__REPLY_MAX];
struct blob_attr *tba[__PACKAGES_MAX];
struct blob_attr *packages;
struct blob_attr *cur;
int rem;
struct avl_pkg *avpk;
blobmsg_parse(reply_policy, __REPLY_MAX, tbr, blob_data(reply), blob_len(reply));
if (!tbr[REPLY_OBJECT])
return -ENODATA;
if (arch) {
blobmsg_parse(packages_policy, __PACKAGES_MAX, tba, blobmsg_data(tbr[REPLY_OBJECT]), blobmsg_len(tbr[REPLY_OBJECT]));
if (!tba[PACKAGES_ARCHITECTURE] ||
!tba[PACKAGES_PACKAGES])
return -ENODATA;
if (strcmp(blobmsg_get_string(tba[PACKAGES_ARCHITECTURE]), arch))
return -EBADMSG;
packages = tba[PACKAGES_PACKAGES];
} else {
packages = tbr[REPLY_OBJECT];
}
blobmsg_for_each_attr(cur, packages, rem) {
avpk = malloc(sizeof(struct avl_pkg));
if (!avpk)
return -ENOMEM;
avpk->name = strdup(blobmsg_name(cur));
if (!avpk->name) {
free(avpk);
return -ENOMEM;
}
avpk->version = strdup(blobmsg_get_string(cur));
if (!avpk->version) {
free(avpk->name);
free(avpk);
return -ENOMEM;
}
avpk->avl.key = avpk->name;
if (avl_insert(&pkg_tree, &avpk->avl)) {
#ifdef AUC_DEBUG
if (debug)
fprintf(stderr, "failed to insert package %s (%s)!\n", blobmsg_name(cur), blobmsg_get_string(cur));
#endif
if (avpk->name)
free(avpk->name);
if (avpk->version)
free(avpk->version);
free(avpk);
}
}
return 0;
}
static int request_packages(struct branch *branch)
{
static struct blob_buf pkgbuf, archpkgbuf;
char url[256];
int ret;
fprintf(stderr, "Requesting package lists...\n");
blobmsg_buf_init(&archpkgbuf);
snprintf(url, sizeof(url), "%s/%s/%s/%s/%s/%s/%s%s", serverurl, API_JSON,
API_JSON_VERSION, branch->path, API_TARGETS, target, API_INDEX, API_JSON_EXT);
if ((rc = server_request(url, NULL, &archpkgbuf))) {
blob_buf_free(&archpkgbuf);
return rc;
};
ret = add_upg_packages(archpkgbuf.head, branch->arch_packages);
blob_buf_free(&archpkgbuf);
if (ret)
return ret;
blobmsg_buf_init(&pkgbuf);
snprintf(url, sizeof(url), "%s/%s/%s/%s/%s/%s-%s%s", serverurl, API_JSON,
API_JSON_VERSION, branch->path, API_PACKAGES, branch->arch_packages,
API_INDEX, API_JSON_EXT);
if ((rc = server_request(url, NULL, &pkgbuf))) {
blob_buf_free(&archpkgbuf);
blob_buf_free(&pkgbuf);
return rc;
};
ret = add_upg_packages(pkgbuf.head, NULL);
blob_buf_free(&pkgbuf);
return ret;
}
static int check_installed_packages(struct blob_attr *pkgs)
{
static struct blob_buf allpkg;
uint32_t id;
int status = 0;
blob_buf_init(&allpkg, 0);
blobmsg_add_u8(&allpkg, "all", 1);
blobmsg_add_string(&allpkg, "dummy", "foo");
if (ubus_lookup_id(ctx, "rpc-sys", &id) ||
ubus_invoke(ctx, id, "packagelist", allpkg.head, pkglist_check_cb, &status, 3000)) {
fprintf(stderr, "cannot request packagelist from rpcd\n");
status |= PKG_ERROR;
}
return status;
}
static int req_add_selected_packages(struct blob_buf *req)
{
static struct blob_buf allpkg;
uint32_t id;
blob_buf_init(&allpkg, 0);
blobmsg_add_u8(&allpkg, "all", 0);
blobmsg_add_string(&allpkg, "dummy", "foo");
if (ubus_lookup_id(ctx, "rpc-sys", &id) ||
ubus_invoke(ctx, id, "packagelist", allpkg.head, pkglist_req_cb, req, 3000)) {
fprintf(stderr, "cannot request packagelist from rpcd\n");
return -EFAULT;
}
return 0;
}
#if defined(__amd64__) || defined(__i386__)
static int system_is_efi(void)
{
const char efidname[] = "/sys/firmware/efi/efivars";
int fd = open(efidname, O_DIRECTORY | O_PATH);
if (fd != -1) {
close(fd);
return 1;
} else {
return 0;
}
}
#else
static inline int system_is_efi(void) { return 0; }
#endif
static int get_image_by_type(struct blob_attr *images, const char *typestr, const char *fstype, char **image_name, char **image_sha256)
{
struct blob_attr *tb[__IMAGES_MAX];
struct blob_attr *cur;
int rem, ret = -ENOENT;
blobmsg_for_each_attr(cur, images, rem) {
blobmsg_parse(images_policy, __IMAGES_MAX, tb, blobmsg_data(cur), blobmsg_len(cur));
if (!tb[IMAGES_FILESYSTEM] ||
!tb[IMAGES_NAME] ||
!tb[IMAGES_TYPE] ||
!tb[IMAGES_SHA256])
continue;
if (fstype && strcmp(blobmsg_get_string(tb[IMAGES_FILESYSTEM]), fstype))
continue;
if (!strcmp(blobmsg_get_string(tb[IMAGES_TYPE]), typestr)) {
*image_name = strdup(blobmsg_get_string(tb[IMAGES_NAME]));
*image_sha256 = strdup(blobmsg_get_string(tb[IMAGES_SHA256]));
ret = 0;
break;
}
}
return ret;
}
static int select_image(struct blob_attr *images, const char *target_fstype, char **image_name, char **image_sha256)
{
const char *combined_type;
const char *fstype = rootfs_type;
int ret = -ENOENT;
if (target_fstype)
fstype = target_fstype;
if (system_is_efi())
combined_type = "combined-efi";
else
combined_type = "combined";
DPRINTF("images: %s\n", blobmsg_format_json_indent(images, true, 0));
if (fstype) {
ret = get_image_by_type(images, "sysupgrade", fstype, image_name, image_sha256);
if (!ret)
return 0;
ret = get_image_by_type(images, combined_type, fstype, image_name, image_sha256);
if (!ret)
return 0;
}
/* fallback to squashfs unless fstype requested explicitly */
if (!target_fstype) {
ret = get_image_by_type(images, "sysupgrade", "squashfs", image_name, image_sha256);
if (!ret)
return 0;
ret = get_image_by_type(images, combined_type, "squashfs", image_name, image_sha256);
}
return ret;
}
static bool validate_sha256(char *filename, char *sha256str)
{
char *cmd = calloc(strlen(SHA256SUM) + 1 + strlen(filename) + 1, sizeof(char));
size_t reslen = (64 + 2 + strlen(filename) + 1) * sizeof(char);
char *resstr = malloc(reslen);
FILE *f;
bool ret = false;
strcpy(cmd, SHA256SUM);
strcat(cmd, " ");
strcat(cmd, filename);
f = popen(cmd, "r");
if (!f)
goto sha256free;
if (fread(resstr, reslen, 1, f) < 1)
goto sha256close;
if (!strncmp(sha256str, resstr, 64))
ret = true;
sha256close:
fflush(f);
pclose(f);
sha256free:
free(cmd);
free(resstr);
return ret;
}
static inline bool status_delay(const char *status)
{
return !strcmp(API_STATUS_QUEUED, status) ||
!strcmp(API_STATUS_STARTED, status);
}
static void usage(const char *arg0)
{
fprintf(stdout, "%s: Attended sysUpgrade CLI client\n", arg0);
fprintf(stdout, "Usage: auc [-b <branch>] [-B <ver>] [-c] %s[-f] [-h] [-r] [-y]\n",
#ifdef AUC_DEBUG
"[-d] "
#else
""
#endif
);
fprintf(stdout, " -b <branch>\tuse specific release branch\n");
fprintf(stdout, " -B <ver>\tuse specific release version\n");
fprintf(stdout, " -c\t\tonly check if system is up-to-date\n");
#ifdef AUC_DEBUG
fprintf(stdout, " -d\t\tenable debugging output\n");
#endif
fprintf(stdout, " -f\t\tuse force\n");
fprintf(stdout, " -h\t\toutput help\n");
fprintf(stdout, " -r\t\tcheck only for release upgrades\n");
fprintf(stdout, " -F <fstype>\toverride filesystem type\n");
fprintf(stdout, " -y\t\tdon't wait for user confirmation\n");
fprintf(stdout, "\n");
fprintf(stdout, "Please report issues to improve the server:\n");
fprintf(stdout, "%s\n", server_issues);
}
/* this main function is too big... todo: split */
int main(int args, char *argv[]) {
static struct blob_buf checkbuf, infobuf, reqbuf, imgbuf, upgbuf;
struct branch *branch;
uint32_t id;
int valid;
char url[256];
char *sanetized_board_name, *image_name, *image_sha256, *tmp;
char *target_branch = NULL, *target_version = NULL, *target_fstype = NULL;
struct blob_attr *tbr[__REPLY_MAX];
struct blob_attr *tb[__TARGET_MAX] = {}; /* make sure tb is NULL initialized even if blobmsg_parse isn't called */
struct stat imgstat;
int check_only = 0;
int retry_delay = 0;
int upg_check = 0;
int revcmp;
int addargs;
unsigned char argc = 1;
bool force = false, use_get = false, in_queue = false, dont_ask = false, release_only = false;
snprintf(user_agent, sizeof(user_agent), "%s (%s)", argv[0], AUC_VERSION);
fprintf(stdout, "%s\n", user_agent);
while (argc<args) {
if (!strncmp(argv[argc], "-h", 3) ||
!strncmp(argv[argc], "--help", 7)) {
usage(argv[0]);
return 0;
}
addargs = 0;
#ifdef AUC_DEBUG
if (!strncmp(argv[argc], "-d", 3))
debug = 1;
#endif
if (!strncmp(argv[argc], "-b", 3)) {
target_branch = argv[argc + 1];
addargs = 1;
}
if (!strncmp(argv[argc], "-B", 3)) {
target_version = argv[argc + 1];
addargs = 1;
}
if (!strncmp(argv[argc], "-c", 3))
check_only = 1;
if (!strncmp(argv[argc], "-f", 3))
force = true;
if (!strncmp(argv[argc], "-F", 3)) {
target_fstype = argv[argc + 1];
addargs = 1;
}
if (!strncmp(argv[argc], "-r", 3))
release_only = true;
if (!strncmp(argv[argc], "-y", 3))
dont_ask = true;
argc += 1 + addargs;
};
if (load_config()) {
rc=-EFAULT;
goto freeubus;
}
if (chdir("/tmp")) {
rc=-EFAULT;
goto freeconfig;
}
if (!strncmp(serverurl, "https", 5)) {
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=-EPROTONOSUPPORT;
goto freessl;
}
}
uloop_init();
ctx = ubus_connect(NULL);
if (!ctx) {
fprintf(stderr, "failed to connect to ubus.\n");
return -1;
}
blobmsg_buf_init(&checkbuf);
blobmsg_buf_init(&infobuf);
blobmsg_buf_init(&reqbuf);
blobmsg_buf_init(&imgbuf);
/* ubus requires BLOBMSG_TYPE_UNSPEC */
blob_buf_init(&upgbuf, 0);
if (ubus_lookup_id(ctx, "system", &id) ||
ubus_invoke(ctx, id, "board", NULL, board_cb, &checkbuf, 3000)) {
fprintf(stderr, "cannot request board info from procd\n");
rc=-EFAULT;
goto freebufs;
}
fprintf(stdout, "Server: %s\n", serverurl);
fprintf(stdout, "Running: %s %s on %s (%s)\n", version, revision, target, board_name);
if (target_fstype && rootfs_type && strcmp(rootfs_type, target_fstype))
fprintf(stderr, "WARNING: will change rootfs type from '%s' to '%s'\n",
rootfs_type, target_fstype);
if (request_branches(!(target_branch || target_version))) {
rc=-ENODATA;
goto freeboard;
}
branch = select_branch(target_branch, target_version);
if (!branch) {
rc=-EINVAL;
goto freebranches;
}
fprintf(stdout, "Available: %s %s\n", branch->version_number, branch->version_code);
revcmp = verrevcmp(revision, branch->version_code);
if (revcmp < 0)
upg_check |= PKG_UPGRADE;
else if (revcmp > 0)
upg_check |= PKG_DOWNGRADE;
if (release_only && !(upg_check & PKG_UPGRADE)) {
fprintf(stderr, "Nothing to be updated. Use '-f' to force.\n");
rc=0;
goto freebranches;
}
if ((rc = request_packages(branch)))
goto freebranches;
upg_check |= check_installed_packages(reqbuf.head);
if (upg_check & PKG_ERROR) {
rc=-ENOPKG;
goto freebranches;
}
if (!upg_check && !force) {
fprintf(stderr, "Nothing to be updated. Use '-f' to force.\n");
rc=0;
goto freebranches;
};
if (!force && (upg_check & PKG_DOWNGRADE)) {
fprintf(stderr, "Refusing to downgrade. Use '-f' to force.\n");
rc=-ENOTRECOVERABLE;
goto freebranches;
};
if (!force && (upg_check & PKG_NOT_FOUND)) {
fprintf(stderr, "Not all installed packages found in remote lists. Use '-f' to force.\n");
rc=-ENOTRECOVERABLE;
goto freebranches;
};
if (check_only)
goto freebranches;
if (!dont_ask) {
rc = ask_user();
if (rc)
goto freebranches;
}
blobmsg_add_string(&reqbuf, "version", branch->version);
blobmsg_add_string(&reqbuf, "version_code", branch->version_code);
blobmsg_add_string(&reqbuf, "target", target);
sanetized_board_name = strdup(board_name);
tmp = sanetized_board_name;
while ((tmp = strchr(tmp, ',')))
*tmp = '_';
blobmsg_add_string(&reqbuf, "profile", sanetized_board_name);
blobmsg_add_u8(&reqbuf, "diff_packages", 1);
req_add_selected_packages(&reqbuf);
snprintf(url, sizeof(url), "%s/%s", serverurl, API_REQUEST);
use_get = false;
do {
retry = false;
DPRINTF("requesting from %s\n%s%s", url, use_get?"":blobmsg_format_json_indent(reqbuf.head, true, 0), use_get?"":"\n");
rc = server_request(url, use_get?NULL:&reqbuf, &imgbuf);
if (rc)
break;
blobmsg_parse(reply_policy, __REPLY_MAX, tbr, blob_data(imgbuf.head), blob_len(imgbuf.head));
if (!tbr[REPLY_OBJECT])
break;
blobmsg_parse(target_policy, __TARGET_MAX, tb, blobmsg_data(tbr[REPLY_OBJECT]), blobmsg_len(tbr[REPLY_OBJECT]));
/* for compatibility with old server version, also support status in 200 reply */
if (tb[TARGET_STATUS]) {
tmp = blobmsg_get_string(tb[TARGET_STATUS]);
if (status_delay(tmp))
retry = 1;
}
if (tb[TARGET_REQUEST_HASH]) {
if (retry) {
if (!retry_delay)
fputs("Requesting build", stderr);
retry_delay = 2;
if (tb[TARGET_QUEUE_POSITION]) {
fprintf(stderr, "%s%s (position in queue: %d)",
ANSI_ESC, in_queue?ANSI_CURSOR_RESTORE:ANSI_CURSOR_SAFE,
blobmsg_get_u32(tb[TARGET_QUEUE_POSITION]));
in_queue = true;
} else {
if (in_queue)
fprintf(stderr, "%s%s%s%s",
ANSI_ESC, ANSI_CURSOR_RESTORE,
ANSI_ESC, ANSI_ERASE_LINE);
fputc('.', stderr);
in_queue = false;
}
} else {
retry_delay = 0;
}
if (!use_get) {
snprintf(url, sizeof(url), "%s/%s/%s", serverurl,
API_REQUEST,
blobmsg_get_string(tb[TARGET_REQUEST_HASH]));
DPRINTF("polling via GET %s\n", url);
}
use_get = true;
} else if (retry_delay) {
fputc('\n', stderr);
retry_delay = 0;
}
#ifdef AUC_DEBUG
if (debug && tb[TARGET_STDOUT])
fputs(blobmsg_get_string(tb[TARGET_STDOUT]), stdout);
if (debug && tb[TARGET_STDERR])
fputs(blobmsg_get_string(tb[TARGET_STDERR]), stderr);
#endif
if (retry) {
blob_buf_free(&imgbuf);
blobmsg_buf_init(&imgbuf);
sleep(retry_delay);
}
} while(retry);
free(sanetized_board_name);
if (!tb[TARGET_IMAGES] || !tb[TARGET_BINDIR]) {
if (!rc)
rc=-EBADMSG;
goto freebranches;
}
if ((rc = select_image(tb[TARGET_IMAGES], target_fstype, &image_name, &image_sha256)))
goto freebranches;
snprintf(url, sizeof(url), "%s/%s/%s/%s", serverurl, API_STORE,
blobmsg_get_string(tb[TARGET_BINDIR]),
image_name);
fprintf(stderr, "Downloading image from %s\n", url);
rc = server_request(url, NULL, NULL);
if (rc)
goto freebranches;
filename = uclient_get_url_filename(url, "firmware.bin");
if (stat(filename, &imgstat)) {
fprintf(stderr, "image download failed\n");
rc=-EPIPE;
goto freebranches;
}
if ((intmax_t)imgstat.st_size != out_len) {
fprintf(stderr, "file size mismatch\n");
unlink(filename);
rc=-EMSGSIZE;
goto freebranches;
}
if (!validate_sha256(filename, image_sha256)) {
fprintf(stderr, "sha256 mismatch\n");
unlink(filename);
rc=-EBADMSG;
goto freebranches;
}
if (strcmp(filename, "firmware.bin")) {
if (rename(filename, "firmware.bin")) {
fprintf(stderr, "can't rename to firmware.bin\n");
unlink(filename);
rc=-errno;
goto freebranches;
}
}
valid = 0;
if (ubus_lookup_id(ctx, "rpc-sys", &id) ||
ubus_invoke(ctx, id, "upgrade_test", NULL, upgtest_cb, &valid, 15000)) {
rc=-EFAULT;
goto freebranches;
}
if (!valid) {
rc=-EINVAL;
goto freebranches;
}
fprintf(stdout, "invoking sysupgrade\n");
blobmsg_add_u8(&upgbuf, "keep", 1);
ubus_invoke(ctx, id, "upgrade_start", upgbuf.head, NULL, NULL, 120000);
sleep(10);
freebranches:
if (rc && tb[TARGET_STDOUT]
#ifdef AUC_DEBUG
&& !debug
#endif
)
fputs(blobmsg_get_string(tb[TARGET_STDOUT]), stdout);
if (rc && tb[TARGET_STDERR]
#ifdef AUC_DEBUG
&& !debug
#endif
)
fputs(blobmsg_get_string(tb[TARGET_STDERR]), stderr);
if (tb[TARGET_DETAIL]) {
fputs(blobmsg_get_string(tb[TARGET_DETAIL]), stderr);
fputc('\n', stderr);
}
/* ToDo */
freeboard:
if (rootfs_type)
free(rootfs_type);
free(board_name);
free(target);
free(distribution);
free(version);
freebufs:
blob_buf_free(&checkbuf);
blob_buf_free(&infobuf);
blob_buf_free(&reqbuf);
blob_buf_free(&imgbuf);
blob_buf_free(&upgbuf);
freessl:
if (ssl_ctx)
ssl_ops->context_free(ssl_ctx);
freeconfig:
free(serverurl);
freeubus:
uloop_done();
ubus_free(ctx);
if (ucl)
uclient_free(ucl);
if (dlh)
dlclose(dlh);
if (rc)
fprintf(stderr, "%s (%d)\n", strerror(-1 * rc), -1 * rc);
return rc;
}