/* * auc - attendedsysUpgrade CLI * Copyright (C) 2017-2021 Daniel Golle * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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/openwrt/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; static bool dont_ask = false; static int avl_verrevcmp(const void *k1, const void *k2, void *ptr); struct branch { struct avl_node avl; char *name; char *git_branch; char *path_packages; char *arch_packages; char **repos; struct avl_tree versions; struct list_head package_changes; bool snapshot; unsigned int branch_off_rev; }; static struct avl_tree branches = AVL_TREE_INIT(branches, avl_verrevcmp, false, NULL); struct branch_version { struct avl_node avl; struct branch *branch; char *path; char *version; char *version_code; char *version_number; bool snapshot; }; struct package_changes { struct list_head list; unsigned int revision; char *source; char *target; bool mandatory; }; static LIST_HEAD(selected_package_changes); 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_BRANCH_OFF_REV, BRANCH_NAME, BRANCH_PATH, BRANCH_PATH_PACKAGES, BRANCH_SNAPSHOT, BRANCH_REPOS, BRANCH_TARGETS, BRANCH_UPDATES, BRANCH_VERSIONS, BRANCH_PACKAGE_CHANGES, __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_BRANCH_OFF_REV] = { .name = "branch_off_rev", .type = BLOBMSG_TYPE_INT32 }, [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_REPOS] = { .name = "repos", .type = BLOBMSG_TYPE_ARRAY }, [BRANCH_TARGETS] = { .name = "targets", .type = BLOBMSG_TYPE_TABLE }, [BRANCH_UPDATES] = { .name = "updates", .type = BLOBMSG_TYPE_STRING }, [BRANCH_VERSIONS] = { .name = "versions", .type = BLOBMSG_TYPE_ARRAY }, [BRANCH_PACKAGE_CHANGES] = { .name = "package_changes", .type = BLOBMSG_TYPE_ARRAY }, }; enum { PACKAGE_CHANGES_SOURCE, PACKAGE_CHANGES_TARGET, PACKAGE_CHANGES_REVISION, PACKAGE_CHANGES_MANDATORY, __PACKAGE_CHANGES_MAX, }; static const struct blobmsg_policy package_changes_policy[__PACKAGE_CHANGES_MAX] = { [PACKAGE_CHANGES_SOURCE] = { .name = "source", .type = BLOBMSG_TYPE_STRING }, [PACKAGE_CHANGES_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, [PACKAGE_CHANGES_REVISION] = { .name = "revision", .type = BLOBMSG_TYPE_INT32 }, [PACKAGE_CHANGES_MANDATORY] = { .name = "mandatory", .type = BLOBMSG_TYPE_BOOL }, }; /* * 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; char *url; 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 config section\n"); return -1; } url = uci_lookup_option_string(uci_ctx, uci_s, "url"); if (!url) { fprintf(stderr, "Failed to read server url from config\n"); return -1; } if (strncmp(url, "https://", strlen("https://")) && strncmp(url, "http://", strlen("http://"))) { fprintf(stderr, "Server url invalid (needs to be http://... or https://...)\n"); return -1; } serverurl = strdup(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 */ /* 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; } static int avl_verrevcmp(const void *k1, const void *k2, void *ptr) { const char *d1 = (const char *)k1, *d2 = (const char*)k2; return verrevcmp(d1, d2); } /* * 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); } /* * Select package_changes from branch to global list */ static void grab_changes(struct branch *br, unsigned int rev) { struct package_changes *c, *n; #ifdef AUC_DEBUG if (debug) fprintf(stderr, "grabbing changes for branch %s from revision %u\n", br->name, rev); #endif list_for_each_entry(c, &br->package_changes, list) { if (c->revision == 0 || c->revision > rev) { n = malloc(sizeof(struct package_changes)); memcpy(n, c, sizeof(struct package_changes)); INIT_LIST_HEAD(&n->list); list_add_tail(&n->list, &selected_package_changes); } } } /** * 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_COLOR_BLUE ANSI_ESC "[1;34m" #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 bool ask_user(const char *message) { char user_input; fflush(stdin); fprintf(stderr, "%s [N/y] ", message); user_input = getchar(); fflush(stdin); if ((user_input == 'y') || (user_input == 'Y')) return true; return false; } static inline bool is_builtin_pkg(const char *pkgname) { return !strcmp(pkgname, "libc") || !strcmp(pkgname, "librt") || !strcmp(pkgname, "libpthread") || !strcmp(pkgname, "kernel"); } static const char *apply_package_changes(const char *pkgname, bool interactive) { struct package_changes *pkc; const char *mpkgname = pkgname; list_for_each_entry(pkc, &selected_package_changes, list) { /* package_change additions are dealt with later */ if (!pkc->source) continue; if (strcmp(pkc->source, mpkgname)) continue; if (!pkc->mandatory && interactive) { if (pkc->target) fprintf(stderr, "Package %s should be replaced by %s.\n", pkc->source, pkc->target); else fprintf(stderr, "Package %s should be removed.\n", pkc->source); if (dont_ask) pkc->mandatory = true; else pkc->mandatory = ask_user("Apply change"); } if (!pkc->mandatory) continue; mpkgname = pkc->target; if (!mpkgname) break; } return mpkgname; } 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; const char *pkgname; struct package_changes *pkc; 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) { pkgname = blobmsg_name(cur); if (is_builtin_pkg(pkgname)) continue; pkgname = apply_package_changes(pkgname, true); if (!pkgname) { fprintf(stderr, " %s: %s%s -> (not installed)%s\n", blobmsg_name(cur), ANSI_COLOR_BLUE, blobmsg_get_string(cur), ANSI_COLOR_RESET); continue; } pkg = avl_find_element(&pkg_tree, pkgname, pkg, avl); if (!pkg) { fprintf(stderr, "installed package %s%s%s cannot be found in remote list!\n", ANSI_COLOR_RED, pkgname, ANSI_COLOR_RESET); *status |= PKG_NOT_FOUND; continue; } if (pkgname != blobmsg_name(cur)) { fprintf(stderr, " %s%s: %s -> %s: %s%s\n", ANSI_COLOR_BLUE, blobmsg_name(cur), blobmsg_get_string(cur), pkgname, pkg->version, ANSI_COLOR_RESET); *status |= PKG_UPGRADE; 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:""); } list_for_each_entry(pkc, &selected_package_changes, list) { /* deal only with package_change additions now */ if (pkc->source) continue; pkg = avl_find_element(&pkg_tree, pkc->target, pkg, avl); if (!pkg) { fprintf(stderr, "new package %s%s%s cannot be found in remote list!\n", ANSI_COLOR_RED, pkc->target, ANSI_COLOR_RESET); *status |= PKG_NOT_FOUND; continue; } fprintf(stderr, " %s: %s(not installed) -> %s%s\n", pkc->target, ANSI_COLOR_BLUE, pkg->version, 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; const char *pkgname; struct package_changes *pkc; 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) { pkgname = blobmsg_name(cur); if (is_builtin_pkg(pkgname)) continue; pkgname = apply_package_changes(pkgname, false); pkg = avl_find_element(&pkg_tree, pkgname, pkg, avl); if (!pkg) continue; blobmsg_add_string(buf, pkgname, pkg->version); } list_for_each_entry(pkc, &selected_package_changes, list) { /* add new packages to request */ if (pkc->source) continue; pkg = avl_find_element(&pkg_tree, pkc->target, pkg, avl); if (!pkg) continue; blobmsg_add_string(buf, pkc->target, 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 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_version *bver, 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]), bver->branch->arch_packages)) return -EINVAL; if (tb[TARGET_VERSION_CODE]) bver->version_code = strdup(blobmsg_get_string(tb[TARGET_VERSION_CODE])); if (tb[TARGET_VERSION_NUMBER]) bver->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], *pkc[__PACKAGE_CHANGES_MAX]; struct blob_attr *curver, *curpkc; int remver, rempkc; struct branch *br; struct package_changes *pkce; char *tmp, *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; br = calloc(1, sizeof(struct branch)); avl_init(&br->versions, avl_verrevcmp, false, NULL); INIT_LIST_HEAD(&br->package_changes); /* check if target is offered in branch and get arch_packages */ br->arch_packages = validate_target(tb[BRANCH_TARGETS]); if (!br->arch_packages) { free(br); return; } if (tb[BRANCH_GIT_BRANCH]) br->git_branch = strdup(blobmsg_get_string(tb[BRANCH_GIT_BRANCH])); if (tb[BRANCH_BRANCH_OFF_REV]) br->branch_off_rev = blobmsg_get_u32(tb[BRANCH_BRANCH_OFF_REV]); else br->branch_off_rev = 0; if (tb[BRANCH_SNAPSHOT]) br->snapshot = blobmsg_get_bool(tb[BRANCH_SNAPSHOT]); else br->snapshot = false; br->name = strdup(blobmsg_get_string(tb[BRANCH_NAME])); br->path_packages = alloc_replace_var(blobmsg_get_string(tb[BRANCH_PATH_PACKAGES]), "branch", br->name); if (!br->path_packages) { free(br->name); free(br->arch_packages); free(br); return; } /* parse package changes */ blobmsg_for_each_attr(curpkc, tb[BRANCH_PACKAGE_CHANGES], rempkc) { if (blobmsg_type(curpkc) != BLOBMSG_TYPE_TABLE) continue; blobmsg_parse(package_changes_policy, __PACKAGE_CHANGES_MAX, pkc, blobmsg_data(curpkc), blobmsg_len(curpkc)); if (!pkc[PACKAGE_CHANGES_REVISION] || (!pkc[PACKAGE_CHANGES_SOURCE] && !pkc[PACKAGE_CHANGES_TARGET])) continue; pkce = calloc(1, sizeof(struct package_changes)); if (!pkce) break; if (pkc[PACKAGE_CHANGES_SOURCE]) pkce->source = strdup(blobmsg_get_string(pkc[PACKAGE_CHANGES_SOURCE])); if (pkc[PACKAGE_CHANGES_TARGET]) pkce->target = strdup(blobmsg_get_string(pkc[PACKAGE_CHANGES_TARGET])); pkce->revision = blobmsg_get_u32(pkc[PACKAGE_CHANGES_REVISION]); if (pkc[PACKAGE_CHANGES_MANDATORY]) pkce->mandatory = blobmsg_get_bool(pkc[PACKAGE_CHANGES_MANDATORY]); list_add_tail(&pkce->list, &br->package_changes); } /* add each version of the branch */ blobmsg_for_each_attr(curver, tb[BRANCH_VERSIONS], remver) { if (blobmsg_type(curver) != BLOBMSG_TYPE_STRING) continue; struct branch_version *bver = calloc(1, sizeof(struct branch_version)); bver->snapshot = !!strcasestr(blobmsg_get_string(curver), "snapshot"); bver->path = alloc_replace_var(blobmsg_get_string(tb[BRANCH_PATH]), "version", blobmsg_get_string(curver)); if (!bver->path) { free(bver); continue; } bver->version = strdup(blobmsg_get_string(curver)); if (!bver->version) { free(bver->path); free(bver); continue; } bver->branch = br; if (asprintf(&board_json_file, "%s/%s/%s/%s/%s/%s/%s%s", serverurl, API_JSON, API_JSON_VERSION, bver->path, API_TARGETS, target, board_name, API_JSON_EXT) < 0) { free(bver->version); free(bver->path); free(bver); continue; } tmp = board_json_file; while ((tmp = strchr(tmp, ','))) *tmp = '_'; if (request_target(bver, board_json_file)) { free(board_json_file); free(bver->version); free(bver->path); free(bver); continue; } free(board_json_file); bver->avl.key = bver->version; avl_insert(&br->versions, &bver->avl); } br->avl.key = br->name; avl_insert(&branches, &br->avl); } 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 void free_branches() { struct branch *br, *tmp; struct branch_version *bver, *tmp2; struct package_changes *pkce, *tmp3; avl_for_each_element_safe(&branches, br, avl, tmp) { free(br->name); free(br->path_packages); free(br->arch_packages); avl_for_each_element_safe(&br->versions, bver, avl, tmp2) { avl_delete(&br->versions, &bver->avl); free(bver->version); free(bver->version_code); free(bver->version_number); free(bver->path); free(bver); } list_for_each_entry_safe(pkce, tmp3, &br->package_changes, list) { list_del(&pkce->list); free(pkce->source); free(pkce->target); free(pkce); } avl_delete(&branches, &br->avl); } } static struct branch *get_current_branch() { struct branch *br, *abr = NULL; avl_for_each_element(&branches, br, avl) { /* if branch name doesn't match version *prefix*, skip */ if (!strncasecmp(br->name, version, strlen(br->name))) { abr = br; break; } } return abr; } static int revision_from_version_code(const char *version_code) { int res; if (sscanf(version_code, "r%d-", &res) == 1) return res; return -1; } static struct branch_version *select_branch(char *name, char *select_version) { struct branch *br; struct branch_version *bver, *abver = NULL; if (!name) name = version; avl_for_each_element(&branches, br, avl) { /* if branch name doesn't match version *prefix*, skip */ if (strncasecmp(br->name, name, strlen(br->name))) continue; avl_for_each_element(&br->versions, bver, avl) { if (select_version) { if (!strcasecmp(bver->version, select_version)) { abver = bver; break; } } else { if (!strcasecmp(name, "snapshot")) { /* we are on the main snapshot branch */ if (br->snapshot && bver->snapshot) { abver = bver; break; } } else { /* skip main snapshot branch */ if (br->snapshot) continue; if (strcasestr(version, "snapshot")) { /* we are on a stable snapshot branch or coming from main snapshot branch */ if (bver->snapshot) { abver = bver; break; } } else { if (bver->snapshot) continue; if (!abver || (openwrt_release_verrevcmp(abver->version, bver->version) < 0)) abver = bver; } } } } if (abver) break; } return abver; } 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 = calloc(1, 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_version *bver) { 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, bver->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, bver->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, bver->path, API_PACKAGES, bver->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(void) { 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; ret = get_image_by_type(images, "sdcard", 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); if (!ret) return 0; ret = get_image_by_type(images, "sdcard", fstype, 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 ] [-B ] [-c] %s[-f] [-h] [-r] [-y]\n", #ifdef AUC_DEBUG "[-d] " #else "" #endif ); fprintf(stdout, " -b \tuse specific release branch\n"); fprintf(stdout, " -B \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 \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 *current_branch, *running_branch; struct branch_version *target_version; int running_revision, covered_revision = 0; uint32_t id; int valid; char url[256]; char *sanetized_board_name, *image_name, *image_sha256, *tmp; char *cmd_target_branch = NULL, *cmd_target_version = NULL, *cmd_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 = 0; int addargs; unsigned char argc = 1; bool force = false, use_get = false, in_queue = false, release_only = false; snprintf(user_agent, sizeof(user_agent), "%s/%s", argv[0], AUC_VERSION); fprintf(stdout, "%s\n", user_agent); while (argcversion_number, target_version->version_code); if (running_branch->snapshot && !target_version->branch->snapshot) revcmp = (running_revision < target_version->branch->branch_off_rev)?-1:1; else if (!running_branch->snapshot && target_version->branch->snapshot) revcmp = -1; else revcmp = verrevcmp(version, target_version->version_number); 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 (target_version->branch == running_branch) grab_changes(running_branch, running_revision); else if (revcmp > 0) fprintf(stderr, "WARNING: Downgrade to older branch may not work as expected!\n"); else avl_for_element_range(running_branch, target_version->branch, current_branch, avl) { if (current_branch == running_branch) grab_changes(running_branch, running_revision); else grab_changes(current_branch, covered_revision); if (current_branch->branch_off_rev > 0) covered_revision = current_branch->branch_off_rev; } if ((rc = request_packages(target_version))) goto freebranches; upg_check |= check_installed_packages(); 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) { if (!ask_user("Are you sure you want to continue the upgrade process?")) { rc = 0; goto freebranches; } } blobmsg_add_string(&reqbuf, "version", target_version->version); blobmsg_add_string(&reqbuf, "version_code", target_version->version_code); blobmsg_add_string(&reqbuf, "target", target); if (cmd_target_fstype || rootfs_type) blobmsg_add_string(&reqbuf, "filesystem", cmd_target_fstype?cmd_target_fstype:rootfs_type); 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], cmd_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: free_branches(); 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; }