Add option to allow only requesting an image but not actually download or sysupgrade anything. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
1842 lines
44 KiB
C
1842 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, " -n\t\tdry-run (don't download or upgrade)\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;
|
|
bool check_only = false;
|
|
bool retry_delay = false;
|
|
bool upg_check = false;
|
|
bool dry_run = false;
|
|
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 = true;
|
|
|
|
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], "-n", 3))
|
|
dry_run = true;
|
|
|
|
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) {
|
|
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);
|
|
|
|
if (dry_run) {
|
|
fprintf(stderr, "\nImage available at %s\n", url);
|
|
rc = 0;
|
|
goto freebranches;
|
|
}
|
|
|
|
fprintf(stderr, "\nDownloading 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);
|
|
}
|
|
|
|
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;
|
|
}
|