/* A simple shell on top of qemu-ga. */ #include #include #include #include #include #include #include #include #include #include static struct { const char *host_ga_socket; const char *host_io_socket; const char *guest_io_socket; } conf; static struct { int gafd; int iofd; int guest_io_handle; int guest_sh_pid; } ctx; static void ctx_cleanup(void); static void usage(void) { printf("Usage: qemu-sh HOST_GA_SOCKET HOST_IO_SOCKET GUEST_IO_SOCKET\n"); } static void dbg(const char *msg) { printf("qemu-sh: debug: %s\n", msg); } static void warn(const char *msg) { fprintf(stderr, "qemu-sh: warning: %s\n", msg); } static void error(const char *msg) { fprintf(stderr, "qemu-sh: error: %s\n", msg); ctx_cleanup(); exit(1); } static void prepare_ga(const char *host_ga_socket) { struct sockaddr_un adr; adr.sun_family = AF_UNIX; if (strlen(host_ga_socket) >= sizeof(adr.sun_path)) { error("too large path to unix socket"); } strcpy(adr.sun_path, host_ga_socket); ctx.gafd = socket(AF_UNIX, SOCK_STREAM, 0); if (ctx.gafd == -1) { error("socket() GA socket on host"); } dbg("connecting to GA..."); if (connect(ctx.gafd, (struct sockaddr *)&adr, sizeof(struct sockaddr_un)) != 0) { error("connect() to GA"); } dbg("connected to GA"); } static void prepare_io(const char *host_io_socket) { struct sockaddr_un adr; adr.sun_family = AF_UNIX; if (strlen(host_io_socket) >= sizeof(adr.sun_path)) { error("too large path to unix socket"); } strcpy(adr.sun_path, host_io_socket); ctx.iofd = socket(AF_UNIX, SOCK_STREAM, 0); if (ctx.iofd == -1) { error("socket() IO socket on host"); } dbg("connecting to IO socket..."); if (connect(ctx.iofd, (struct sockaddr *)&adr, sizeof(struct sockaddr_un)) != 0) { error("connect() to GA"); } dbg("connected to IO socket"); { int n = 1; ioctl(ctx.iofd, FIONBIO, &n); } } static void prepare_guest_io(const char *guest_io_socket) { char *cmd; int cmdlen, handle; char buf[4096]; size_t nbuf = 0, r; cmdlen = asprintf(&cmd, "{\"execute\": \"guest-file-open\", \"arguments\":{\"path\":\"%s\", \"mode\":\"r+b\"}}", guest_io_socket); if (cmdlen == -1) { error("asprintf()"); } dbg("writing to GA..."); if (write(ctx.gafd, cmd, cmdlen) != cmdlen) { error("write() to GA"); } free(cmd); /*{"return": 1000}*/ for (;;) { dbg("reading from GA..."); r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1); if (r == -1 || r == 0) { error("read() from GA"); } nbuf += r; buf[nbuf] = '\0'; if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) { error(buf); } if (sscanf(buf, "{\"return\": %u}", &handle) != EOF) { break; } if (nbuf == sizeof(buf) - 1) { error("too large response from GA"); } } ctx.guest_io_handle = handle; dbg("got guest_io_handle"); } static void close_guest_io(void) { char *cmd; int cmdlen, handle; char buf[4096]; size_t nbuf = 0, r; cmdlen = asprintf(&cmd, "{\"execute\": \"guest-file-close\", \"arguments\":{\"handle\":%u}}", ctx.guest_io_handle); if (cmdlen == -1) { warn("asprintf()"); return; } dbg("writing to GA..."); if (write(ctx.gafd, cmd, cmdlen) != cmdlen) { warn("write() to GA"); return; } free(cmd); for (;;) { dbg("reading from GA..."); r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1); if (r == -1 || r == 0) { warn("read() from GA"); return; } nbuf += r; buf[nbuf] = '\0'; if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) { warn(buf); return; } break; } ctx.guest_io_handle = -1; dbg("closed guest_io_handle"); } static void close_guest_sh(void) { char *cmd; int cmdlen, handle; char buf[4096]; size_t nbuf = 0, r; cmdlen = asprintf(&cmd, "{\"execute\": \"guest-exec-status\", \"arguments\":{\"pid\":%u}}", ctx.guest_sh_pid); if (cmdlen == -1) { warn("asprintf()"); return; } dbg("writing to GA..."); if (write(ctx.gafd, cmd, cmdlen) != cmdlen) { warn("write() to GA"); return; } free(cmd); for (;;) { dbg("reading from GA..."); r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1); if (r == -1 || r == 0) { warn("read() from GA"); return; } nbuf += r; buf[nbuf] = '\0'; if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) { warn(buf); return; } break; } ctx.guest_sh_pid = 0; dbg("closed guest_sh_pid"); } static void exec_shell() { char *cmd; int cmdlen, pid; char buf[4096]; size_t nbuf = 0, r; cmdlen = asprintf(&cmd, "{\"execute\":\"guest-exec\", \"arguments\":{\"path\":\"/bin/sh\"," "\"handle_stdin\":%u,\"handle_stdout\":%u,\"handle_stderr\":%u }}", ctx.guest_io_handle, ctx.guest_io_handle, ctx.guest_io_handle); if (cmdlen == -1) { error("asprintf()"); } dbg("writing to GA..."); if (write(ctx.gafd, cmd, cmdlen) != cmdlen) { error("write() to GA"); } free(cmd); /*{"return": 2395}*/ for (;;) { dbg("reading from GA..."); r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1); if (r == -1 || r == 0) { error("read() from GA"); } nbuf += r; buf[nbuf] = '\0'; if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) { error(buf); } if (sscanf(buf, "{\"return\": %u}", &pid) != EOF) { break; } if (nbuf == sizeof(buf) - 1) { error("too large response from GA"); } } ctx.guest_sh_pid = pid; dbg("got guest_sh_pid"); } static void intr_handler(int signo) { dbg("signal caught"); } static void ctx_cleanup(void) { if (ctx.guest_io_handle != 0) { close_guest_io(); } if (ctx.guest_sh_pid != 0) { close_guest_sh(); } if (ctx.iofd != -1) { close(ctx.iofd); ctx.iofd = -1; } if (ctx.gafd != -1) { close(ctx.gafd); ctx.gafd = -1; } } static void ev_loop(void) { char buf[4096]; size_t r; fd_set rset; for (;;) { FD_ZERO(&rset); FD_SET(STDIN_FILENO, &rset); FD_SET(ctx.iofd, &rset); r = select(ctx.iofd + 1, &rset, NULL, NULL, NULL); if (r == -1) { if (errno == EINTR) break; error("select()"); } if (FD_ISSET(ctx.iofd, &rset)) { r = read(ctx.iofd, buf, sizeof(buf)); if (r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) continue; if (r == -1) error("read() from socket"); write(STDOUT_FILENO, buf, r); } if (FD_ISSET(STDIN_FILENO, &rset)) { r = read(STDIN_FILENO, buf, sizeof(buf)); if (r == -1) error("read() from stdin"); write(ctx.iofd, buf, r); } } } int main(int argc, char **argv, char **envp) { if (argc != 4) { usage(); return 0; } ctx.gafd = -1; ctx.iofd = -1; ctx.guest_io_handle = -1; conf.host_ga_socket = argv[1]; conf.host_io_socket = argv[2]; conf.guest_io_socket = argv[3]; { struct sigaction sa = {0}; sa.sa_handler = &intr_handler; if (sigaction(SIGINT, &sa, NULL) != 0) { error("sigaction()"); } } prepare_ga(conf.host_ga_socket); prepare_io(conf.host_io_socket); prepare_guest_io(conf.guest_io_socket); exec_shell(); dbg("Shell's started. Press Ctrl+C to exit."); ev_loop(); ctx_cleanup(); return 0; }