auc: sync with server API
[feed/packages.git] / utils / auc / src / auc.c
1 /*
2 * auc - attendedsysUpgrade CLI
3 * Copyright (C) 2017 Daniel Golle <daniel@makrotopia.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3
7 * as published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15 #define _GNU_SOURCE
16 #define AUC_VERSION "0.0.9"
17
18 #include <fcntl.h>
19 #include <dlfcn.h>
20 #include <glob.h>
21 #include <stdio.h>
22 #include <sys/stat.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25
26 #include <uci.h>
27 #include <uci_blob.h>
28 #include <json-c/json.h>
29 #include <libubox/ulog.h>
30 #include <libubox/list.h>
31 #include <libubox/vlist.h>
32 #include <libubox/blobmsg_json.h>
33 #include <libubox/avl-cmp.h>
34 #include <libubox/uclient.h>
35 #include <libubox/uclient-utils.h>
36 #include <libubus.h>
37
38 #define REQ_TIMEOUT 15
39 #define APIOBJ_CHECK "api/upgrade-check"
40 #define APIOBJ_REQUEST "api/upgrade-request"
41
42 #define PUBKEY_PATH "/etc/opkg/keys"
43
44 #ifdef AUC_DEBUG
45 #define DPRINTF(...) if (debug) fprintf(stderr, __VA_ARGS__)
46 #else
47 #define DPRINTF(...)
48 #endif
49
50 static const char server_issues[]="https://github.com/aparcar/attendedsysupgrade-server/issues";
51
52 static char user_agent[80];
53 static char *serverurl;
54 static int upgrade_packages;
55 static struct ustream_ssl_ctx *ssl_ctx;
56 static const struct ustream_ssl_ops *ssl_ops;
57 static off_t out_bytes;
58 static off_t out_len;
59 static off_t out_offset;
60 static bool cur_resume;
61 static int output_fd = -1;
62 static int retry, imagebuilder, building, ibready;
63 static char *board_name = NULL;
64 static char *target = NULL, *subtarget = NULL;
65 static char *distribution = NULL, *version = NULL;
66 static int uptodate;
67 static char *filename = NULL;
68 static int rc;
69
70 #ifdef AUC_DEBUG
71 static int debug = 0;
72 #endif
73
74 /*
75 * policy for ubus call system board
76 * see procd/system.c
77 */
78 enum {
79 BOARD_BOARD_NAME,
80 BOARD_RELEASE,
81 __BOARD_MAX,
82 };
83
84 static const struct blobmsg_policy board_policy[__BOARD_MAX] = {
85 [BOARD_BOARD_NAME] = { .name = "board_name", .type = BLOBMSG_TYPE_STRING },
86 [BOARD_RELEASE] = { .name = "release", .type = BLOBMSG_TYPE_TABLE },
87 };
88
89 /*
90 * policy for release information in system board reply
91 * see procd/system.c
92 */
93 enum {
94 RELEASE_DISTRIBUTION,
95 RELEASE_VERSION,
96 RELEASE_TARGET,
97 __RELEASE_MAX,
98 };
99
100 static const struct blobmsg_policy release_policy[__RELEASE_MAX] = {
101 [RELEASE_DISTRIBUTION] = { .name = "distribution", .type = BLOBMSG_TYPE_STRING },
102 [RELEASE_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING },
103 [RELEASE_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING },
104 };
105
106 /*
107 * policy for packagelist
108 * see rpcd/sys.c
109 */
110 enum {
111 PACKAGELIST_PACKAGES,
112 __PACKAGELIST_MAX,
113 };
114
115 static const struct blobmsg_policy packagelist_policy[__PACKAGELIST_MAX] = {
116 [PACKAGELIST_PACKAGES] = { .name = "packages", .type = BLOBMSG_TYPE_TABLE },
117 };
118
119 /*
120 * policy for upgrade_test
121 * see rpcd/sys.c
122 */
123 enum {
124 UPGTEST_CODE,
125 UPGTEST_STDOUT,
126 __UPGTEST_MAX,
127 };
128
129 static const struct blobmsg_policy upgtest_policy[__UPGTEST_MAX] = {
130 [UPGTEST_CODE] = { .name = "code", .type = BLOBMSG_TYPE_INT32 },
131 [UPGTEST_STDOUT] = { .name = "stdout", .type = BLOBMSG_TYPE_STRING },
132 };
133
134
135 /*
136 * policy to extract version from upgrade-check response
137 */
138 enum {
139 CHECK_VERSION,
140 CHECK_UPGRADES,
141 __CHECK_MAX,
142 };
143
144 static const struct blobmsg_policy check_policy[__CHECK_MAX] = {
145 [CHECK_VERSION] = { .name = "version", .type = BLOBMSG_TYPE_STRING },
146 [CHECK_UPGRADES] = { .name = "upgrades", .type = BLOBMSG_TYPE_TABLE },
147 };
148
149 static const struct blobmsg_policy pkg_upgrades_policy[2] = {
150 { .type = BLOBMSG_TYPE_STRING },
151 { .type = BLOBMSG_TYPE_STRING },
152 };
153
154 /*
155 * policy for upgrade-request response
156 * parse download information for the ready image.
157 */
158 enum {
159 IMAGE_REQHASH,
160 IMAGE_FILESIZE,
161 IMAGE_URL,
162 IMAGE_CHECKSUM,
163 IMAGE_FILES,
164 IMAGE_SYSUPGRADE,
165 __IMAGE_MAX,
166 };
167
168 static const struct blobmsg_policy image_policy[__IMAGE_MAX] = {
169 [IMAGE_REQHASH] = { .name = "request_hash", .type = BLOBMSG_TYPE_STRING },
170 [IMAGE_URL] = { .name = "url", .type = BLOBMSG_TYPE_STRING },
171 [IMAGE_FILES] = { .name = "files", .type = BLOBMSG_TYPE_STRING },
172 [IMAGE_SYSUPGRADE] = { .name = "sysupgrade", .type = BLOBMSG_TYPE_STRING },
173 };
174
175 /*
176 * policy for HTTP headers received from server
177 */
178 enum {
179 H_RANGE,
180 H_LEN,
181 H_IBSTATUS,
182 H_IBQUEUEPOS,
183 H_UNKNOWN_PACKAGE,
184 __H_MAX
185 };
186
187 static const struct blobmsg_policy policy[__H_MAX] = {
188 [H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING },
189 [H_LEN] = { .name = "content-length", .type = BLOBMSG_TYPE_STRING },
190 [H_IBSTATUS] = { .name = "x-imagebuilder-status", .type = BLOBMSG_TYPE_STRING },
191 [H_IBQUEUEPOS] = { .name = "x-build-queue-position", .type = BLOBMSG_TYPE_STRING },
192 [H_UNKNOWN_PACKAGE] = { .name = "x-unknown-package", .type = BLOBMSG_TYPE_STRING },
193 };
194
195 /*
196 * load serverurl from UCI
197 */
198 static int load_config() {
199 struct uci_context *uci_ctx;
200 struct uci_package *uci_attendedsysupgrade;
201 struct uci_section *uci_s;
202
203 uci_ctx = uci_alloc_context();
204 if (!uci_ctx)
205 return -1;
206
207 uci_ctx->flags &= ~UCI_FLAG_STRICT;
208
209 if (uci_load(uci_ctx, "attendedsysupgrade", &uci_attendedsysupgrade) ||
210 !uci_attendedsysupgrade) {
211 fprintf(stderr, "Failed to load attendedsysupgrade config\n");
212 return -1;
213 }
214
215 uci_s = uci_lookup_section(uci_ctx, uci_attendedsysupgrade, "server");
216 if (!uci_s) {
217 fprintf(stderr, "Failed to read server url from config\n");
218 return -1;
219 }
220 serverurl = strdup(uci_lookup_option_string(uci_ctx, uci_s, "url"));
221
222 uci_s = uci_lookup_section(uci_ctx, uci_attendedsysupgrade, "client");
223 if (!uci_s) {
224 fprintf(stderr, "Failed to read client config\n");
225 return -1;
226 }
227 upgrade_packages = atoi(uci_lookup_option_string(uci_ctx, uci_s, "upgrade_packages"));
228
229 uci_free_context(uci_ctx);
230
231 return 0;
232 }
233
234
235 /**
236 * UBUS response callbacks
237 */
238
239 /*
240 * rpc-sys packagelist
241 * append packagelist response to blobbuf given in req->priv
242 */
243 static void pkglist_check_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
244 struct blob_buf *buf = (struct blob_buf *)req->priv;
245 struct blob_attr *tb[__PACKAGELIST_MAX];
246
247 blobmsg_parse(packagelist_policy, __PACKAGELIST_MAX, tb, blob_data(msg), blob_len(msg));
248
249 if (!tb[PACKAGELIST_PACKAGES]) {
250 fprintf(stderr, "No packagelist received\n");
251 rc=-1;
252 return;
253 }
254
255 blobmsg_add_field(buf, BLOBMSG_TYPE_TABLE, "installed", blobmsg_data(tb[PACKAGELIST_PACKAGES]), blobmsg_data_len(tb[PACKAGELIST_PACKAGES]));
256 };
257
258 /*
259 * rpc-sys packagelist
260 * append array of package names to blobbuf given in req->priv
261 */
262 static void pkglist_req_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
263 struct blob_buf *buf = (struct blob_buf *)req->priv;
264 struct blob_attr *tb[__PACKAGELIST_MAX];
265 struct blob_attr *cur;
266 int rem;
267 void *array;
268
269 blobmsg_parse(packagelist_policy, __PACKAGELIST_MAX, tb, blob_data(msg), blob_len(msg));
270
271 if (!tb[PACKAGELIST_PACKAGES]) {
272 fprintf(stderr, "No packagelist received\n");
273 return;
274 }
275
276 array = blobmsg_open_array(buf, "packages");
277 blobmsg_for_each_attr(cur, tb[PACKAGELIST_PACKAGES], rem)
278 blobmsg_add_string(buf, NULL, blobmsg_name(cur));
279
280 blobmsg_close_array(buf, array);
281 };
282
283
284 /*
285 * system board
286 * append append board information to blobbuf given in req->priv
287 * populate board and release global strings
288 */
289 static void board_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
290 struct blob_buf *buf = (struct blob_buf *)req->priv;
291 struct blob_attr *tb[__BOARD_MAX];
292 struct blob_attr *rel[__RELEASE_MAX];
293
294 blobmsg_parse(board_policy, __BOARD_MAX, tb, blob_data(msg), blob_len(msg));
295
296 if (!tb[BOARD_BOARD_NAME]) {
297 fprintf(stderr, "No board name received\n");
298 rc=-1;
299 return;
300 }
301 board_name = strdup(blobmsg_get_string(tb[BOARD_BOARD_NAME]));
302
303 if (!tb[BOARD_RELEASE]) {
304 fprintf(stderr, "No release received\n");
305 rc=-1;
306 return;
307 }
308
309 blobmsg_parse(release_policy, __RELEASE_MAX, rel,
310 blobmsg_data(tb[BOARD_RELEASE]), blobmsg_data_len(tb[BOARD_RELEASE]));
311
312 if (!rel[RELEASE_TARGET]) {
313 fprintf(stderr, "No target received\n");
314 rc=-1;
315 return;
316 }
317
318 target = strdup(blobmsg_get_string(rel[RELEASE_TARGET]));
319 subtarget = strchr(target, '/');
320 *subtarget++ = '\0';
321
322 distribution = strdup(blobmsg_get_string(rel[RELEASE_DISTRIBUTION]));
323 version = strdup(blobmsg_get_string(rel[RELEASE_VERSION]));
324
325 blobmsg_add_string(buf, "distro", distribution);
326 blobmsg_add_string(buf, "target", target);
327 blobmsg_add_string(buf, "subtarget", subtarget);
328 blobmsg_add_string(buf, "version", version);
329 }
330
331 /*
332 * rpc-sys upgrade_test
333 * check if downloaded file is accepted by sysupgrade
334 */
335 static void upgtest_cb(struct ubus_request *req, int type, struct blob_attr *msg) {
336 int *valid = (int *)req->priv;
337 struct blob_attr *tb[__UPGTEST_MAX];
338
339 blobmsg_parse(upgtest_policy, __UPGTEST_MAX, tb, blob_data(msg), blob_len(msg));
340
341 if (!tb[UPGTEST_CODE]) {
342 fprintf(stderr, "No sysupgrade test return code received\n");
343 return;
344 }
345
346 *valid = (blobmsg_get_u32(tb[UPGTEST_CODE]) == 0)?1:0;
347 if (*valid == 0)
348 fprintf(stderr, "%s", blobmsg_get_string(tb[UPGTEST_STDOUT]));
349 };
350
351 /**
352 * uclient stuff
353 */
354 static int open_output_file(const char *path, uint64_t resume_offset)
355 {
356 char *filename = NULL;
357 int flags;
358 int ret;
359
360 if (cur_resume)
361 flags = O_RDWR;
362 else
363 flags = O_WRONLY | O_EXCL;
364
365 flags |= O_CREAT;
366
367 filename = uclient_get_url_filename(path, "firmware.bin");
368
369 fprintf(stderr, "Writing to '%s'\n", filename);
370 ret = open(filename, flags, 0644);
371 if (ret < 0)
372 goto free;
373
374 if (resume_offset &&
375 lseek(ret, resume_offset, SEEK_SET) < 0) {
376 fprintf(stderr, "Failed to seek %"PRIu64" bytes in output file\n", resume_offset);
377 close(ret);
378 ret = -1;
379 goto free;
380 }
381
382 out_offset = resume_offset;
383 out_bytes += resume_offset;
384
385 free:
386 free(filename);
387 return ret;
388 }
389
390 struct jsonblobber {
391 json_tokener *tok;
392 struct blob_buf *outbuf;
393 };
394
395 static void request_done(struct uclient *cl)
396 {
397 struct jsonblobber *jsb = (struct jsonblobber *)cl->priv;
398 if (jsb) {
399 json_tokener_free(jsb->tok);
400 free(jsb);
401 };
402
403 uclient_disconnect(cl);
404 uloop_end();
405 }
406
407 static void header_done_cb(struct uclient *cl)
408 {
409 struct blob_attr *tb[__H_MAX];
410 uint64_t resume_offset = 0, resume_end, resume_size;
411 char *ibstatus;
412 unsigned int queuepos = 0;
413
414 if (uclient_http_redirect(cl)) {
415 fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host);
416
417 return;
418 }
419
420 if (cl->status_code == 204 && cur_resume) {
421 /* Resume attempt failed, try normal download */
422 cur_resume = false;
423 //init_request(cl);
424 return;
425 }
426
427 DPRINTF("headers:\n%s\n", blobmsg_format_json_indent(cl->meta, true, 0));
428
429 blobmsg_parse(policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta));
430
431 switch (cl->status_code) {
432 case 400:
433 request_done(cl);
434 rc=-1;
435 break;
436 case 412:
437 fprintf(stderr, "%s target %s/%s (%s) not found. Please report this at %s\n",
438 distribution, target, subtarget, board_name, server_issues);
439 request_done(cl);
440 rc=-2;
441 break;
442 case 413:
443 fprintf(stderr, "image too big.\n");
444 rc=-1;
445 request_done(cl);
446 break;
447 case 416:
448 fprintf(stderr, "File download already fully retrieved; nothing to do.\n");
449 request_done(cl);
450 break;
451 case 422:
452 fprintf(stderr, "unknown package '%s' requested.\n",
453 blobmsg_get_string(tb[H_UNKNOWN_PACKAGE]));
454 rc=-1;
455 request_done(cl);
456 break;
457 case 501:
458 fprintf(stderr, "ImageBuilder didn't produce sysupgrade file.\n");
459 rc=-2;
460 request_done(cl);
461 break;
462 case 204:
463 fprintf(stdout, "system is up to date.\n");
464 uptodate=1;
465 request_done(cl);
466 break;
467 case 206:
468 if (!cur_resume) {
469 fprintf(stderr, "Error: Partial content received, full content requested\n");
470 request_done(cl);
471 break;
472 }
473
474 if (!tb[H_RANGE]) {
475 fprintf(stderr, "Content-Range header is missing\n");
476 break;
477 }
478
479 if (sscanf(blobmsg_get_string(tb[H_RANGE]),
480 "bytes %"PRIu64"-%"PRIu64"/%"PRIu64,
481 &resume_offset, &resume_end, &resume_size) != 3) {
482 fprintf(stderr, "Content-Range header is invalid\n");
483 break;
484 }
485 case 202:
486 if (!tb[H_IBSTATUS])
487 break;
488
489 ibstatus = blobmsg_get_string(tb[H_IBSTATUS]);
490
491 if (!strncmp(ibstatus, "queue", 6)) {
492 if (!imagebuilder) {
493 fprintf(stderr, "server is dispatching build job\n");
494 imagebuilder=1;
495 } else {
496 if (tb[H_IBQUEUEPOS]) {
497 queuepos = atoi(blobmsg_get_string(tb[H_IBQUEUEPOS]));
498 fprintf(stderr, "build is in queue position %u.\n", queuepos);
499 }
500 }
501 retry=1;
502 } else if (!strncmp(ibstatus, "building", 9)) {
503 if (!building) {
504 fprintf(stderr, "server is now building image...\n");
505 building=1;
506 }
507 retry=1;
508 } else if (!strncmp(ibstatus, "initialize", 11)) {
509 if (!ibready) {
510 fprintf(stderr, "server is setting up ImageBuilder...\n");
511 ibready=1;
512 }
513 retry=1;
514 } else {
515 fprintf(stderr, "unrecognized remote imagebuilder status '%s'\n", ibstatus);
516 rc=-2;
517 }
518 // fall through
519 case 200:
520 if (cl->priv)
521 break;
522
523 if (tb[H_LEN])
524 out_len = strtoul(blobmsg_get_string(tb[H_LEN]), NULL, 10);
525
526 output_fd = open_output_file(cl->url->location, resume_offset);
527 if (output_fd < 0) {
528 perror("Cannot open output file");
529 request_done(cl);
530 }
531 break;
532
533 default:
534 fprintf(stderr, "HTTP error %d\n", cl->status_code);
535 request_done(cl);
536 break;
537 }
538 }
539
540 static void read_data_cb(struct uclient *cl)
541 {
542 char buf[256];
543 int len;
544 json_object *jsobj;
545 struct blob_buf *outbuf = NULL;
546 json_tokener *tok = NULL;
547 struct jsonblobber *jsb = (struct jsonblobber *)cl->priv;
548
549 if (!jsb) {
550 while (1) {
551 len = uclient_read(cl, buf, sizeof(buf));
552 if (!len)
553 return;
554
555 out_bytes += len;
556 write(output_fd, buf, len);
557 }
558 return;
559 }
560
561 outbuf = jsb->outbuf;
562 tok = jsb->tok;
563
564 while (1) {
565 len = uclient_read(cl, buf, sizeof(buf));
566 if (!len)
567 break;
568
569 out_bytes += len;
570
571 jsobj = json_tokener_parse_ex(tok, buf, len);
572
573 if (json_tokener_get_error(tok) == json_tokener_continue)
574 continue;
575
576 if (json_tokener_get_error(tok) != json_tokener_success)
577 break;
578
579 if (jsobj)
580 {
581 if (json_object_get_type(jsobj) == json_type_object)
582 blobmsg_add_object(outbuf, jsobj);
583
584 json_object_put(jsobj);
585 break;
586 }
587 }
588 }
589
590 static void eof_cb(struct uclient *cl)
591 {
592 if (!cl->data_eof && !uptodate) {
593 fprintf(stderr, "Connection reset prematurely\n");
594 }
595 request_done(cl);
596 }
597
598 static void handle_uclient_error(struct uclient *cl, int code)
599 {
600 const char *type = "Unknown error";
601
602 switch(code) {
603 case UCLIENT_ERROR_CONNECT:
604 type = "Connection failed";
605 break;
606 case UCLIENT_ERROR_TIMEDOUT:
607 type = "Connection timed out";
608 break;
609 case UCLIENT_ERROR_SSL_INVALID_CERT:
610 type = "Invalid SSL certificate";
611 break;
612 case UCLIENT_ERROR_SSL_CN_MISMATCH:
613 type = "Server hostname does not match SSL certificate";
614 break;
615 default:
616 break;
617 }
618
619 fprintf(stderr, "Connection error: %s\n", type);
620
621 request_done(cl);
622 }
623
624 static const struct uclient_cb check_cb = {
625 .header_done = header_done_cb,
626 .data_read = read_data_cb,
627 .data_eof = eof_cb,
628 .error = handle_uclient_error,
629 };
630
631 static int server_request(const char *url, struct blob_buf *inbuf, struct blob_buf *outbuf) {
632 struct uclient *ucl;
633 struct jsonblobber *jsb = NULL;
634 int rc = -1;
635 char *post_data;
636 out_offset = 0;
637 out_bytes = 0;
638 out_len = 0;
639
640 uloop_init();
641
642 ucl = uclient_new(url, NULL, &check_cb);
643 if (outbuf) {
644 jsb = malloc(sizeof(struct jsonblobber));
645 jsb->outbuf = outbuf;
646 jsb->tok = json_tokener_new();
647 };
648
649 uclient_http_set_ssl_ctx(ucl, ssl_ops, ssl_ctx, 1);
650 ucl->timeout_msecs = REQ_TIMEOUT * 1000;
651 ucl->priv = jsb;
652 rc = uclient_connect(ucl);
653 if (rc)
654 return rc;
655
656 rc = uclient_http_set_request_type(ucl, inbuf?"POST":"GET");
657 if (rc)
658 return rc;
659
660 uclient_http_reset_headers(ucl);
661 uclient_http_set_header(ucl, "User-Agent", user_agent);
662 if (inbuf) {
663 uclient_http_set_header(ucl, "Content-Type", "text/json");
664 post_data = blobmsg_format_json(inbuf->head, true);
665 uclient_write(ucl, post_data, strlen(post_data));
666 }
667 rc = uclient_request(ucl);
668 if (rc)
669 return rc;
670
671 uloop_run();
672 uloop_done();
673 uclient_free(ucl);
674
675 return 0;
676 }
677
678 /**
679 * ustream-ssl
680 */
681 static int init_ustream_ssl(void) {
682 void *dlh;
683 glob_t gl;
684 int i;
685
686 dlh = dlopen("libustream-ssl.so", RTLD_LAZY | RTLD_LOCAL);
687 if (!dlh)
688 return -1;
689
690 ssl_ops = dlsym(dlh, "ustream_ssl_ops");
691 if (!ssl_ops)
692 return -1;
693
694 ssl_ctx = ssl_ops->context_new(false);
695
696 glob("/etc/ssl/certs/*.crt", 0, NULL, &gl);
697 if (!gl.gl_pathc)
698 return -2;
699
700 for (i = 0; i < gl.gl_pathc; i++)
701 ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]);
702
703 return 0;
704 }
705
706 static int ask_user(void)
707 {
708 fprintf(stderr, "Are you sure you want to continue the upgrade process? [N/y] ");
709 if (getchar() != 'y')
710 return -1;
711 return 0;
712 }
713
714 static void print_package_updates(struct blob_attr *upgrades) {
715 struct blob_attr *cur;
716 struct blob_attr *tb[2];
717 int rem;
718
719 blobmsg_for_each_attr(cur, upgrades, rem) {
720 blobmsg_parse_array(pkg_upgrades_policy, ARRAY_SIZE(policy), tb, blobmsg_data(cur), blobmsg_data_len(cur));
721 if (!tb[0] || !tb[1])
722 continue;
723
724 fprintf(stdout, "\t%s (%s -> %s)\n", blobmsg_name(cur),
725 blobmsg_get_string(tb[1]), blobmsg_get_string(tb[0]));
726 };
727 }
728
729 /* this main function is too big... todo: split */
730 int main(int args, char *argv[]) {
731 static struct blob_buf allpkg, checkbuf, infobuf, reqbuf, imgbuf, upgbuf;
732 struct ubus_context *ctx = ubus_connect(NULL);
733 uint32_t id;
734 int valid, use_get;
735 char url[256];
736 char *newversion = NULL;
737 struct blob_attr *tb[__IMAGE_MAX];
738 struct blob_attr *tbc[__CHECK_MAX];
739 char *tmp;
740 struct stat imgstat;
741 int check_only = 0;
742 int ignore_sig = 0;
743 unsigned char argc = 1;
744
745 snprintf(user_agent, sizeof(user_agent), "%s (%s)", argv[0], AUC_VERSION);
746 fprintf(stdout, "%s\n", user_agent);
747
748 while (argc<args) {
749 if (!strncmp(argv[argc], "-h", 3) ||
750 !strncmp(argv[argc], "--help", 7)) {
751 fprintf(stdout, "%s: Attended sysUpgrade CLI client\n", argv[0]);
752 fprintf(stdout, "Usage: auc [-d] [-h]\n");
753 fprintf(stdout, " -c\tonly check if system is up-to-date\n");
754 fprintf(stdout, " -F\tignore result of signature verification\n");
755 #ifdef AUC_DEBUG
756 fprintf(stdout, " -d\tenable debugging output\n");
757 #endif
758 fprintf(stdout, " -h\toutput help\n");
759 return 0;
760 }
761
762 #ifdef AUC_DEBUG
763 if (!strncmp(argv[argc], "-d", 3))
764 debug = 1;
765 #endif
766 if (!strncmp(argv[argc], "-c", 3))
767 check_only = 1;
768
769 if (!strncmp(argv[argc], "-F", 3))
770 ignore_sig = 1;
771
772 argc++;
773 };
774
775 if (!ctx) {
776 fprintf(stderr, "failed to connect to ubus.\n");
777 return -1;
778 }
779 if (load_config()) {
780 rc=-1;
781 goto freeubus;
782 }
783
784 if (chdir("/tmp")) {
785 rc=-1;
786 goto freeconfig;
787 }
788
789 if (!strncmp(serverurl, "https", 5)) {
790 rc = init_ustream_ssl();
791 if (rc == -2) {
792 fprintf(stderr, "No CA certificates loaded, please install ca-certificates\n");
793 rc=-1;
794 goto freessl;
795 }
796
797 if (rc || !ssl_ctx) {
798 fprintf(stderr, "SSL support not available, please install ustream-ssl\n");
799 rc=-1;
800 goto freessl;
801 }
802 }
803
804 blobmsg_buf_init(&checkbuf);
805 blobmsg_buf_init(&infobuf);
806 blobmsg_buf_init(&reqbuf);
807 blobmsg_buf_init(&imgbuf);
808 /* ubus requires BLOBMSG_TYPE_UNSPEC */
809 blob_buf_init(&allpkg, 0);
810 blob_buf_init(&upgbuf, 0);
811
812 if (ubus_lookup_id(ctx, "system", &id) ||
813 ubus_invoke(ctx, id, "board", NULL, board_cb, &checkbuf, 3000)) {
814 fprintf(stderr, "cannot request board info from procd\n");
815 rc=-1;
816 goto freebufs;
817 }
818
819 if (rc)
820 goto freebufs;
821
822 blobmsg_add_u8(&allpkg, "all", 1);
823 blobmsg_add_string(&allpkg, "dummy", "foo");
824 if (ubus_lookup_id(ctx, "rpc-sys", &id) ||
825 ubus_invoke(ctx, id, "packagelist", allpkg.head, pkglist_check_cb, &checkbuf, 3000)) {
826 fprintf(stderr, "cannot request packagelist from rpcd\n");
827 rc=-1;
828 goto freeboard;
829 }
830
831 if (rc)
832 goto freeboard;
833
834 blobmsg_add_u32(&checkbuf, "upgrade_packages", upgrade_packages);
835
836 fprintf(stdout, "running %s %s on %s/%s (%s)\n", distribution,
837 version, target, subtarget, board_name);
838
839 fprintf(stdout, "checking %s for release upgrade%s\n", serverurl,
840 upgrade_packages?" or updated packages":"");
841
842
843 snprintf(url, sizeof(url), "%s/%s", serverurl, APIOBJ_CHECK);
844 uptodate=0;
845
846 do {
847 retry=0;
848 DPRINTF("requesting:\n%s\n", blobmsg_format_json_indent(checkbuf.head, true, 0));
849 if (server_request(url, &checkbuf, &infobuf)) {
850 fprintf(stderr, "failed to connect to server\n");
851 rc=-1;
852 goto freeboard;
853 };
854
855 if (retry)
856 sleep(3);
857 } while(retry);
858
859 DPRINTF("reply:\n%s\n", blobmsg_format_json_indent(infobuf.head, true, 0));
860
861 blobmsg_parse(check_policy, __CHECK_MAX, tbc, blob_data(infobuf.head), blob_len(infobuf.head));
862
863 if (!tbc[CHECK_VERSION] && !tbc[CHECK_UPGRADES]) {
864 if (uptodate) {
865 rc=0;
866 } else if (!rc) {
867 fprintf(stderr, "server reply invalid.\n");
868 rc=-2;
869 }
870 goto freeboard;
871 }
872
873 if (tbc[CHECK_VERSION]) {
874 newversion = blobmsg_get_string(tbc[CHECK_VERSION]);
875 fprintf(stdout, "new %s release %s found.\n", distribution, newversion);
876 } else {
877 newversion = version;
878 fprintf(stdout, "staying on %s release version %s\n", distribution, version);
879 };
880
881 blobmsg_add_string(&reqbuf, "version", newversion);
882
883 if (tbc[CHECK_UPGRADES]) {
884 fprintf(stdout, "package updates:\n");
885 print_package_updates(tbc[CHECK_UPGRADES]);
886 }
887
888 if (check_only) {
889 rc=1;
890 goto freeboard;
891 };
892
893 rc = ask_user();
894 if (rc)
895 goto freeboard;
896
897 blobmsg_add_string(&reqbuf, "distro", distribution);
898 blobmsg_add_string(&reqbuf, "target", target);
899 blobmsg_add_string(&reqbuf, "subtarget", subtarget);
900 blobmsg_add_string(&reqbuf, "board", board_name);
901
902 blob_buf_init(&allpkg, 0);
903 blobmsg_add_u8(&allpkg, "all", 0);
904 blobmsg_add_string(&allpkg, "dummy", "foo");
905 if (ubus_invoke(ctx, id, "packagelist", allpkg.head, pkglist_req_cb, &reqbuf, 3000)) {
906 fprintf(stderr, "cannot request packagelist from rpcd\n");
907 rc=-1;
908 goto freeboard;
909 }
910
911 snprintf(url, sizeof(url), "%s/%s", serverurl, APIOBJ_REQUEST);
912
913 imagebuilder = 0;
914 building = 0;
915 use_get = 0;
916
917 do {
918 retry = 0;
919
920 DPRINTF("requesting:\n%s\n", use_get?"":blobmsg_format_json_indent(reqbuf.head, true, 0));
921
922 server_request(url, use_get?NULL:&reqbuf, &imgbuf);
923 blobmsg_parse(image_policy, __IMAGE_MAX, tb, blob_data(imgbuf.head), blob_len(imgbuf.head));
924
925 if (!use_get && tb[IMAGE_REQHASH]) {
926 snprintf(url, sizeof(url), "%s/%s/%s", serverurl,
927 APIOBJ_REQUEST,
928 blobmsg_get_string(tb[IMAGE_REQHASH]));
929 DPRINTF("polling via GET %s\n", url);
930 retry=1;
931 use_get=1;
932 }
933
934 if (retry) {
935 blob_buf_free(&imgbuf);
936 blobmsg_buf_init(&imgbuf);
937 sleep(3);
938 }
939 } while(retry);
940
941 DPRINTF("reply:\n%s\n", blobmsg_format_json_indent(imgbuf.head, true, 0));
942
943 if (!tb[IMAGE_SYSUPGRADE]) {
944 if (!rc) {
945 fprintf(stderr, "no sysupgrade image returned\n");
946 rc=-1;
947 }
948 goto freeboard;
949 }
950
951 strncpy(url, blobmsg_get_string(tb[IMAGE_SYSUPGRADE]), sizeof(url));
952
953 server_request(url, NULL, NULL);
954
955 filename = uclient_get_url_filename(url, "firmware.bin");
956
957 if (stat(filename, &imgstat)) {
958 fprintf(stderr, "image download failed\n");
959 rc=-1;
960 goto freeboard;
961 }
962
963 if ((intmax_t)imgstat.st_size != out_len) {
964 fprintf(stderr, "file size mismatch\n");
965 unlink(filename);
966 rc=-1;
967 goto freeboard;
968 }
969
970 if (strcmp(filename, "firmware.bin")) {
971 if (rename(filename, "firmware.bin")) {
972 fprintf(stderr, "can't rename to firmware.bin\n");
973 unlink(filename);
974 rc=-1;
975 goto freeboard;
976 }
977 }
978
979 valid = 0;
980 ubus_invoke(ctx, id, "upgrade_test", NULL, upgtest_cb, &valid, 3000);
981 if (!valid) {
982 fprintf(stdout, "image verification failed\n");
983 rc=-1;
984 goto freeboard;
985 }
986
987 blobmsg_add_u8(&upgbuf, "keep", 1);
988 fprintf(stdout, "invoking sysupgrade\n");
989 ubus_invoke(ctx, id, "upgrade_start", upgbuf.head, NULL, NULL, 3000);
990
991 freeboard:
992 free(board_name);
993 free(target);
994 /* subtarget is a pointer within target, don't free */
995 free(distribution);
996 free(version);
997
998 freebufs:
999 blob_buf_free(&checkbuf);
1000 blob_buf_free(&infobuf);
1001 blob_buf_free(&reqbuf);
1002 blob_buf_free(&imgbuf);
1003 blob_buf_free(&upgbuf);
1004
1005 freessl:
1006 if (ssl_ctx)
1007 ssl_ops->context_free(ssl_ctx);
1008
1009 freeconfig:
1010 free(serverurl);
1011
1012 freeubus:
1013 ubus_free(ctx);
1014
1015 return rc;
1016 }