From b200e5802061bbbd91b06ea63c14927df553beb3 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 26 Feb 2018 21:09:49 -0800 Subject: [PATCH] client: Add initial cdba client Signed-off-by: Bjorn Andersson --- Makefile | 11 +- bad.h | 27 +--- cdba.c | 465 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ cdba.h | 32 ++++ 4 files changed, 507 insertions(+), 28 deletions(-) create mode 100644 cdba.c create mode 100644 cdba.h diff --git a/Makefile b/Makefile index a0b0bc1..737209d 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,24 @@ +CLIENT := cdba CDBA := bad .PHONY: all -all: $(CDBA) +all: $(CLIENT) $(CDBA) CFLAGS := -Wall -g -O2 LDFLAGS := -ludev +CLIENT_SRCS := cdba.c circ_buf.c +CLIENT_OBJS := $(CLIENT_SRCS:.c=.o) + CDBA_SRCS := bad.c cdb_assist.c circ_buf.c device.c fastboot.c CDBA_OBJS := $(CDBA_SRCS:.c=.o) +$(CLIENT): $(CLIENT_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ + $(CDBA): $(CDBA_OBJS) $(CC) $(LDFLAGS) -o $@ $^ clean: - rm -f $(CDBA_OBJS) + rm -f $(CLIENT) $(CLIENT_OBJS) $(CDBA_OBJS) diff --git a/bad.h b/bad.h index 49b4afe..6ed5aec 100644 --- a/bad.h +++ b/bad.h @@ -2,33 +2,8 @@ #define __BAD_H__ #include -#include -#define __packed __attribute__((packed)) - -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define MAX(x, y) ((x) > (y) ? (x) : (y)) - -struct msg { - uint8_t type; - uint16_t len; - uint8_t data[]; -} __packed; - -enum { - MSG_SELECT_BOARD = 1, - MSG_CONSOLE, - MSG_HARDRESET, - MSG_POWER_ON, - MSG_POWER_OFF, - MSG_FASTBOOT_PRESENT, - MSG_FASTBOOT_DOWNLOAD, - MSG_FASTBOOT_BOOT, - MSG_STATUS_UPDATE, - MSG_VBUS_ON, - MSG_VBUS_OFF, - MSG_FASTBOOT_REBOOT, -}; +#include "cdba.h" void watch_add_readfd(int fd, int (*cb)(int, void*), void *data); int watch_add_quit(int (*cb)(int, void*), void *data); diff --git a/cdba.c b/cdba.c new file mode 100644 index 0000000..eab48d7 --- /dev/null +++ b/cdba.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cdba.h" +#include "circ_buf.h" +#include "list.h" + +static struct termios orig_tios; +static bool quit; + +static int tty_unbuffer(void) +{ + struct termios tios; + int ret; + + ret = tcgetattr(STDIN_FILENO, &orig_tios); + if (ret < 0) + err(1, "unable to retrieve tty tios"); + + memcpy(&tios, &orig_tios, sizeof(struct termios)); + tios.c_lflag &= ~(ICANON | ECHO | ISIG); + tios.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON); + tios.c_cc[VTIME] = 0; + tios.c_cc[VMIN] = 1; + ret = tcsetattr(STDIN_FILENO, TCSANOW, &tios); + if (ret) + err(1, "unable to update tty tios"); + + return 0; +} + +static void tty_reset(void) +{ + int ret; + + tcflush(STDIN_FILENO, TCIFLUSH); + ret = tcsetattr(STDIN_FILENO, TCSANOW, &orig_tios); + if (ret < 0) + err(1, "unable to reset tty tios"); +} + +static int fork_ssh(const char *host, const char *cmd, int *pipes) +{ + int piped_stdin[2]; + int piped_stdout[2]; + int piped_stderr[2]; + pid_t pid; + int flags; + int i; + + pipe(piped_stdin); + pipe(piped_stdout); + pipe(piped_stderr); + + pid = fork(); + switch(pid) { + case -1: + err(1, "failed to fork"); + case 0: + dup2(piped_stdin[0], STDIN_FILENO); + dup2(piped_stdout[1], STDOUT_FILENO); + dup2(piped_stderr[1], STDERR_FILENO); + + close(piped_stdin[0]); + close(piped_stdin[1]); + + close(piped_stdout[0]); + close(piped_stdout[1]); + + close(piped_stderr[0]); + close(piped_stderr[1]); + + execl("/usr/bin/ssh", "ssh", host, cmd, NULL); + err(1, "launching ssh failed"); + default: + close(piped_stdin[0]); + close(piped_stdout[1]); + close(piped_stderr[1]); + } + + pipes[0] = piped_stdin[1]; + pipes[1] = piped_stdout[0]; + pipes[2] = piped_stderr[0]; + + for (i = 0; i < 3; i++) { + flags = fcntl(pipes[i], F_GETFL, 0); + fcntl(pipes[i], F_SETFL, flags | O_NONBLOCK); + } + + return 0; +} + +static int tty_callback(int *ssh_fds) +{ + static bool special; + struct msg hdr; + char buf[32]; + ssize_t k; + ssize_t n; + + n = read(STDIN_FILENO, buf, sizeof(buf)); + if (n < 0) + return n; + + for (k = 0; k < n; k++) { + if (buf[k] == 0x1) { + special = true; + } else if (special) { + switch (buf[k]) { + case 'q': + quit = true; + break; + case 'P': + hdr.type = MSG_POWER_ON; + hdr.len = 0; + write(ssh_fds[0], &hdr, sizeof(hdr)); + break; + case 'p': + hdr.type = MSG_POWER_OFF; + hdr.len = 0; + write(ssh_fds[0], &hdr, sizeof(hdr)); + break; + case 's': + hdr.type = MSG_STATUS_UPDATE; + hdr.len = 0; + write(ssh_fds[0], &hdr, sizeof(hdr)); + break; + case 'V': + hdr.type = MSG_VBUS_ON; + hdr.len = 0; + write(ssh_fds[0], &hdr, sizeof(hdr)); + break; + case 'v': + hdr.type = MSG_VBUS_OFF; + hdr.len = 0; + write(ssh_fds[0], &hdr, sizeof(hdr)); + break; + case 'a': + hdr.type = MSG_CONSOLE; + hdr.len = 1; + + write(ssh_fds[0], &hdr, sizeof(hdr)); + write(ssh_fds[0], "\001", 1); + break; +#if 0 + case 'b': + device_break(device); + break; +#endif + } + + special = false; + } else { + hdr.type = MSG_CONSOLE; + hdr.len = 1; + + write(ssh_fds[0], &hdr, sizeof(hdr)); + write(ssh_fds[0], buf + k, 1); + } + } + + return 0; +} + +struct work { + void (*fn)(struct work *work, int ssh_stdin); + + struct list_head node; +}; + +static struct list_head work_items = LIST_INIT(work_items); + +struct select_board { + struct work work; + + const char *board; +}; + +static void select_board_fn(struct work *work, int ssh_stdin) +{ + struct select_board *board = container_of(work, struct select_board, work); + size_t blen = strlen(board->board) + 1; + struct msg *msg; + + msg = alloca(sizeof(*msg) + blen); + msg->type = MSG_SELECT_BOARD; + msg->len = blen; + memcpy(msg->data, board->board, blen); + + write(ssh_stdin, msg, sizeof(*msg) + blen); + + free(work); +} + +static void request_select_board(const char *board) +{ + struct select_board *work; + + work = malloc(sizeof(*work)); + work->work.fn = select_board_fn; + work->board = board; + + list_add(&work_items, &work->work.node); +} + +static void request_power_on_fn(struct work *work, int ssh_stdin) +{ + struct msg msg = { MSG_POWER_ON, }; + + write(ssh_stdin, &msg, sizeof(msg)); +} + +static void request_power_on(void) +{ + static struct work work = { request_power_on_fn }; + + list_add(&work_items, &work.node); +} + +struct fastboot_download_work { + struct work work; + + void *data; + size_t offset; + size_t size; +}; + +static void fastboot_work_fn(struct work *_work, int ssh_stdin) +{ + struct fastboot_download_work *work = container_of(_work, struct fastboot_download_work, work); + struct msg *msg; + size_t left; + ssize_t n; + + left = MIN(4096, work->size - work->offset); + + msg = alloca(sizeof(*msg) + left); + msg->type = MSG_FASTBOOT_DOWNLOAD; + msg->len = left; + memcpy(msg->data, work->data + work->offset, left); + + n = write(ssh_stdin, msg, sizeof(*msg) + msg->len); + if (n < 0 && errno == EAGAIN) { + list_add(&work_items, &_work->node); + return; + } else if (n < 0) { + err(1, "failed to write fastboot message"); + } + + work->offset += msg->len; + + /* We've sent the entire image, and a zero length packet */ + if (!msg->len) + free(work); + else + list_add(&work_items, &_work->node); +} + +static void request_fastboot_files(void) +{ + struct fastboot_download_work *work; + struct stat sb; + int fd; + + work = calloc(1, sizeof(*work)); + work->work.fn = fastboot_work_fn; + + fd = open("boot.img", O_RDONLY); + if (fd < 0) + err(1, "failed to open \"boot.img\""); + + fstat(fd, &sb); + + work->size = sb.st_size; + work->data = malloc(work->size); + read(fd, work->data, work->size); + close(fd); + + list_add(&work_items, &work->work.node); +} + +static void handle_status_update(const void *data, size_t len) +{ + char *str = alloca(len + 1); + + memcpy(str, data, len); + str[len] = '\0'; + + printf("%s\n", str); +} + +static int handle_message(struct circ_buf *buf) +{ + struct msg *msg; + struct msg hdr; + size_t n; + + for (;;) { + n = circ_peak(buf, &hdr, sizeof(hdr)); + if (n != sizeof(hdr)) + return 0; + + if (CIRC_AVAIL(buf) < sizeof(*msg) + hdr.len) + return 0; + + // fprintf(stderr, "avail: %zd hdr.len: %d\n", CIRC_AVAIL(buf), hdr.len); + + msg = malloc(sizeof(*msg) + hdr.len); + circ_read(buf, msg, sizeof(*msg) + hdr.len); + + switch (msg->type) { + case MSG_SELECT_BOARD: + // printf("======================================== MSG_SELECT_BOARD\n"); + request_power_on(); + break; + case MSG_CONSOLE: + write(STDOUT_FILENO, msg->data, msg->len); + break; + case MSG_HARDRESET: + break; + case MSG_POWER_ON: + // printf("======================================== MSG_POWER_ON\n"); + break; + case MSG_POWER_OFF: + // printf("======================================== MSG_POWER_OFF\n"); + break; + case MSG_FASTBOOT_PRESENT: + if (*(uint8_t*)msg->data) { + // printf("======================================== MSG_FASTBOOT_PRESENT(on)\n"); + request_fastboot_files(); + } else { + // printf("======================================== MSG_FASTBOOT_PRESENT(off)\n"); + } + break; + case MSG_FASTBOOT_DOWNLOAD: + // printf("======================================== MSG_FASTBOOT_DOWNLOAD\n"); + break; + case MSG_FASTBOOT_BOOT: + // printf("======================================== MSG_FASTBOOT_BOOT\n"); + break; + case MSG_STATUS_UPDATE: + handle_status_update(msg->data, msg->len); + break; + default: + fprintf(stderr, "unk %d len %d\n", msg->type, msg->len); + exit(1); + } + + free(msg); + } + + return 0; +} + +int main(int argc, char **argv) +{ + struct timeval *timeout; + struct timeval tv = {0, 0}; + struct work *next; + struct work *work; + struct circ_buf recv_buf = { 0 }; + int ssh_fds[3]; + char buf[128]; + fd_set rfds; + ssize_t n; + int nfds; + int ret; + + request_select_board("db820c"); + + ret = fork_ssh("minitux.lan", "sandbox/cdba/bad", ssh_fds); + if (ret) + err(1, "failed to connect to flasher.lan"); + + //sleep(5); + + tty_unbuffer(); + + while (!quit) { + nfds = MAX(STDIN_FILENO, MAX(ssh_fds[1], ssh_fds[2])); + + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + FD_SET(ssh_fds[1], &rfds); + FD_SET(ssh_fds[2], &rfds); + + tv.tv_sec = 0; + tv.tv_usec = 1; + timeout = list_empty(&work_items) ? NULL : &tv; + + ret = select(nfds + 1, &rfds, NULL, NULL, timeout); +#if 0 + printf("select: %d (%c%c%c)\n", ret, FD_ISSET(STDIN_FILENO, &rfds) ? 'X' : '-', + FD_ISSET(ssh_fds[1], &rfds) ? 'X' : '-', + FD_ISSET(ssh_fds[2], &rfds) ? 'X' : '-'); +#endif + if (ret < 0) + err(1, "select"); + + if (FD_ISSET(STDIN_FILENO, &rfds)) + tty_callback(ssh_fds); + + if (FD_ISSET(ssh_fds[2], &rfds)) { + n = read(ssh_fds[2], buf, sizeof(buf)); + if (!n) { + warnx("EOF on stderr"); + break; + } else if (n < 0 && errno == EAGAIN) { + continue; + } else if (n < 0) { + warn("received %zd on stderr", n); + break; + } + + const char blue[] = "\033[94m"; + const char reset[] = "\033[0m"; + + write(2, blue, sizeof(blue) - 1); + write(2, buf, n); + write(2, reset, sizeof(reset) - 1); + } + + if (FD_ISSET(ssh_fds[1], &rfds)) { + ret = circ_fill(ssh_fds[1], &recv_buf); + if (ret < 0 && errno != EAGAIN) { + warn("received %d on stdout", ret); + break; + } + + n = handle_message(&recv_buf); + if (n < 0) + break; + } + + list_for_each_entry_safe(work, next, &work_items, node) { + list_del(&work->node); + + work->fn(work, ssh_fds[0]); + } + } + + close(ssh_fds[0]); + close(ssh_fds[1]); + close(ssh_fds[2]); + + printf("Waiting for ssh to finish\n"); + + wait(NULL); + + tty_reset(); + + return 0; +} diff --git a/cdba.h b/cdba.h new file mode 100644 index 0000000..adbb44c --- /dev/null +++ b/cdba.h @@ -0,0 +1,32 @@ +#ifndef __CDBA_H__ +#define __CDBA_H__ + +#include + +#define __packed __attribute__((packed)) + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +struct msg { + uint8_t type; + uint16_t len; + uint8_t data[]; +} __packed; + +enum { + MSG_SELECT_BOARD = 1, + MSG_CONSOLE, + MSG_HARDRESET, + MSG_POWER_ON, + MSG_POWER_OFF, + MSG_FASTBOOT_PRESENT, + MSG_FASTBOOT_DOWNLOAD, + MSG_FASTBOOT_BOOT, + MSG_STATUS_UPDATE, + MSG_VBUS_ON, + MSG_VBUS_OFF, + MSG_FASTBOOT_REBOOT, +}; + +#endif