int pidof_main(const struct cmd_pidof_info* info) { const char* name = info->name; DIR* procdir = xopendir("/proc"); struct dirent* ent; unsigned long found_pid = 0; bool found = false; while ((ent = readdir(procdir)) != NULL) { SCOPED_RESLIST(rl); char* endptr = NULL; errno = 0; unsigned long pid = strtoul(ent->d_name, &endptr, 10); if (pid == 0 || errno != 0 || *endptr != '\0') continue; int cmdline_fd = try_xopen(xaprintf("/proc/%lu/cmdline", pid), O_RDONLY, 0); if (cmdline_fd == -1) continue; char* name = slurp_fd(cmdline_fd, NULL); if (strcmp(name, info->name) == 0) { if (found) die(EINVAL, "more than one process has name `%s'", name); found = true; found_pid = pid; } } if (!found) die(ENOENT, "no process has name `%s'", name); xprintf(xstdout, "%lu%s", found_pid, info->pidof.zero ? "" : "\n"); xflush(xstdout); return 0; }
static void dbg_internal(const char* fmt, va_list args) { int saved_errno = errno; SCOPED_RESLIST(rl_dbg); dbglock(); fprintf(dbgout, "%s(%4d): ", prgname, getpid()); vfprintf(dbgout, fmt, args); putc('\n', dbgout); fflush(dbgout); errno = saved_errno; }
void hack_reopen_tty(int fd) { // We sometimes need O_NONBLOCK on our input and output streams, // but O_NONBLOCK applies to the entire file object. If the file // object happens to be a tty we've inherited, everything that // uses that tty will start getting EAGAIN and all hell will break // loose. Here, we reopen the tty so we can get a fresh file // object and control the blocking mode separately. SCOPED_RESLIST(rl_hack); xdup3nc(xopen(xttyname(fd), O_RDWR | O_NOCTTY, 0), fd, O_CLOEXEC); }
void adb_send_file(const char* local, const char* remote, const char* const* adb_args) { SCOPED_RESLIST(rl_send_stub); struct child_start_info csi = { .flags = CHILD_MERGE_STDERR, .exename = "adb", .argv = argv_concat((const char*[]){"adb", NULL}, adb_args ?: empty_argv, (const char*[]){"push", local, remote, NULL},
void dbgch(const char* label, struct channel** ch, unsigned nrch) { SCOPED_RESLIST(rl_dbgch); unsigned chno; dbglock(); dbg("DBGCH[%s]", label); for (chno = 0; chno < nrch; ++chno) { struct channel* c = ch[chno]; struct pollfd p = channel_request_poll(ch[chno]); const char* pev; switch (p.events) { case POLLIN | POLLOUT: pev = "POLLIN,POLLOUT"; break; case POLLIN: pev = "POLLIN"; break; case POLLOUT: pev = "POLLOUT"; break; case 0: pev = "NONE"; break; default: pev = xaprintf("%xd", p.events); break; } assert(p.fd == -1 || p.fd == c->fdh->fd); dbg(" %-18s size:%-4zu room:%-4zu window:%-4d %s%-2s %p %s", xaprintf("ch[%d=%s]", chno, chname(chno)), ringbuf_size(c->rb), ringbuf_room(c->rb), c->window, (c->dir == CHANNEL_FROM_FD ? "<" : ">"), ((c->fdh != NULL) ? xaprintf("%d", c->fdh->fd) : (c->sent_eof ? "!!" : "!?")), c, pev); } }
void dbg_1(const char* fmt, ...) { int saved_errno = errno; if (dbg_enabled_p()) { SCOPED_RESLIST(rl_dbg); dbglock(); va_list args; fprintf(dbgout, "%s(%04d): ", prgname, getpid()); va_start(args, fmt); vfprintf(dbgout, fmt, args); va_end(args); fputc('\n', dbgout); fflush(dbgout); } errno = saved_errno; }
static struct property_vector* find_all_properties_raw(void) { for (;;) { SCOPED_RESLIST(rl); struct find_all_properties_context ctx = { .pv = property_vector_new(), .oom = false, }; if (property_foreach(find_all_properties_propfn, &ctx) == 1) continue; if (ctx.oom) die_oom(); reslist_xfer(rl->parent, rl); property_vector_sort(ctx.pv); return ctx.pv; } } static void property_vector_swap(struct property_vector* a, struct property_vector* b) { struct property_vector tmp = *a; *a = *b; *b = tmp; } static bool property_vector_equal(const struct property_vector* a, const struct property_vector* b) { return a->size == b->size && memcmp(a->props, b->props, a->size * sizeof (a->props[0])) == 0; } static struct property_vector* find_all_properties(void) { struct property_vector* pv1 = find_all_properties_raw(); for (;;) { SCOPED_RESLIST(pv); struct property_vector* pv2 = find_all_properties_raw(); if (property_vector_equal(pv1, pv2)) return pv1; property_vector_swap(pv1, pv2); } } static int compat_property_foreach( void (*propfn)(const prop_info *pi, void *cookie), void *cookie) { find_symbol_in_libc("__system_property_find_nth", &property_find_nth); if (property_find_nth == NULL) die(EINVAL, "no property-enumeration functions available"); unsigned propno = 0; for (;;) { const prop_info* pi = property_find_nth(propno++); if (pi == NULL) break; propfn(pi, cookie); } return 0; }
static void compile_dex_with_dexopt(const char* dex_file_name, const char* odex_file_name) { SCOPED_RESLIST(rl); int dex_file = xopen(dex_file_name, O_RDONLY, 0); const char* odex_temp_filename = xaprintf( "%s.tmp.%s", odex_file_name, gen_hex_random(ENOUGH_ENTROPY)); cleanup_commit(cleanup_allocate(), cleanup_tmpfile, odex_temp_filename); int odex_temp_file = xopen(odex_temp_filename, O_RDWR | O_CREAT | O_EXCL, 0644); allow_inherit(dex_file); allow_inherit(odex_temp_file); struct child_start_info csi = { .io[0] = CHILD_IO_DEV_NULL, .io[1] = CHILD_IO_PIPE, .io[2] = CHILD_IO_DUP_TO_STDOUT, .exename = "dexopt", .argv = ARGV( "dexopt", "--zip", xaprintf("%d", dex_file), xaprintf("%d", odex_temp_file), dex_file_name, "v=ao=fm=y"), }; struct child* dexopt = child_start(&csi); struct growable_buffer output = slurp_fd_buf(dexopt->fd[1]->fd); int status = child_status_to_exit_code(child_wait(dexopt)); if (status != 0) die(EINVAL, "dexopt failed: %s", massage_output_buf(output)); xrename(odex_temp_filename, odex_file_name); } static void compile_dex(const char* dex_file_name, const char* odex_file_name) { if (api_level() < 21) compile_dex_with_dexopt(dex_file_name, odex_file_name); } int rdex_main(const struct cmd_rdex_info* info) { const char* dex_file_name = info->dexfile; struct stat dex_stat = xstat(dex_file_name); const char* odex_file_name = make_odex_name(dex_file_name); bool need_recompile = true; struct stat odex_stat; if (stat(odex_file_name, &odex_stat) == 0 && dex_stat.st_mtime <= odex_stat.st_mtime) { need_recompile = false; } (void) need_recompile; (void) odex_file_name; (void) compile_dex; if (need_recompile) compile_dex(dex_file_name, odex_file_name); if (setenv("CLASSPATH", dex_file_name, 1) == -1) die_errno("setenv"); if (info->classname[0] == '-') die(EINVAL, "class name cannot begin with '-'"); execvp("app_process", (char* const*) ARGV_CONCAT( ARGV("app_process", xdirname(dex_file_name), info->classname), info->args ?: empty_argv)); die_errno("execvp(\"app_process\", ..."); }
static void set_window_size(int fd, const struct window_size* ws) { int ret; struct winsize wz = { .ws_row = ws->row, .ws_col = ws->col, .ws_xpixel = ws->xpixel, .ws_ypixel = ws->ypixel, }; do { ret = ioctl(fd, TIOCSWINSZ, &wz); } while (ret == -1 && errno == EINTR); dbg("TIOCSWINSZ(%ux%u): %d", wz.ws_row, wz.ws_col, ret); } struct stub { struct fb_adb_sh sh; struct child* child; }; static void stub_process_msg(struct fb_adb_sh* sh, struct msg mhdr) { if (mhdr.type == MSG_WINDOW_SIZE) { struct msg_window_size m; read_cmdmsg(sh, mhdr, &m, sizeof (m)); dbgmsg(&m.msg, "recv"); struct stub* stub = (struct stub*) sh; if (stub->child->pty_master) set_window_size(stub->child->pty_master->fd, &m.ws); return; } fb_adb_sh_process_msg(sh, mhdr); } static void setup_pty(int master, int slave, void* arg) { struct msg_shex_hello* shex_hello = arg; char* hello_end = (char*) shex_hello + shex_hello->msg.size; struct termios attr = { 0 }; xtcgetattr(slave, &attr); if (shex_hello->ispeed) cfsetispeed(&attr, shex_hello->ispeed); if (shex_hello->ospeed) cfsetospeed(&attr, shex_hello->ospeed); const struct termbit* tb = &termbits[0]; const struct termbit* tb_end = tb + nr_termbits; struct term_control* tc = &shex_hello->tctl[0]; while ((char*)(tc+1) <= hello_end && tb < tb_end) { int cmp = strncmp(tc->name, tb->name, sizeof (tc->name)); if (cmp < 0) { dbg("tc not present: %.*s", (int) sizeof (tc->name), tc->name); tc++; continue; } if (cmp > 0) { dbg("tc not sent: %s", tb->name); tb++; continue; } tcflag_t* flg = NULL; if (tb->thing == TERM_IFLAG) { flg = &attr.c_iflag; } else if (tb->thing == TERM_OFLAG) { flg = &attr.c_oflag; } else if (tb->thing == TERM_LFLAG) { flg = &attr.c_lflag; } else if (tb->thing == TERM_C_CC) { if (tc->value == shex_hello->posix_vdisable_value) attr.c_cc[tb->value] = _POSIX_VDISABLE; else attr.c_cc[tb->value] = tc->value; dbg("c_cc[%s] = %d", tb->name, tc->value); } if (flg) { if (tc->value) { dbg("pty %s: set", tb->name); *flg |= tb->value; } else { dbg("pty %s: reset", tb->name); *flg &= ~tb->value; } } tc++; tb++; } xtcsetattr(slave, &attr); if (shex_hello->have_ws) { dbg("ws %ux%u (%ux%u)", shex_hello->ws.row, shex_hello->ws.col, shex_hello->ws.xpixel, shex_hello->ws.ypixel); set_window_size(master, &shex_hello->ws); } } static char** read_child_arglist(size_t expected) { char** argv; if (expected >= SIZE_MAX / sizeof (*argv)) die(EFBIG, "too many arguments"); argv = xalloc(sizeof (*argv) * (1+expected)); for (size_t argno = 0; argno < expected; ++argno) { SCOPED_RESLIST(rl_read_arg); struct msg_cmdline_argument* m; struct msg* mhdr = read_msg(0, read_all_adb_encoded); reslist_pop_nodestroy(rl_read_arg); const char* argval; size_t arglen; if (mhdr->type == MSG_CMDLINE_ARGUMENT) { m = (struct msg_cmdline_argument*) mhdr; if (mhdr->size < sizeof (*m)) die(ECOMM, "bad handshake: MSG_CMDLINE_ARGUMENT size %u < %u", (unsigned) mhdr->size, (unsigned) sizeof (*m)); argval = m->value; arglen = m->msg.size - sizeof (*m); } else if (mhdr->type == MSG_CMDLINE_DEFAULT_SH || mhdr->type == MSG_CMDLINE_DEFAULT_SH_LOGIN) { argval = getenv("SHELL"); if (argval == NULL) argval = DEFAULT_SHELL; if (mhdr->type == MSG_CMDLINE_DEFAULT_SH_LOGIN) argval = xaprintf("-%s", argval); arglen = strlen(argval); } else { die(ECOMM, "bad handshake: unknown init msg s=%u t=%u", (unsigned) mhdr->size, (unsigned) mhdr->type); } argv[argno] = xalloc(arglen + 1); memcpy(argv[argno], argval, arglen); argv[argno][arglen] = '\0'; } argv[expected] = NULL; return argv; }
static struct child* start_child(struct msg_shex_hello* shex_hello) { if (shex_hello->nr_argv < 2) die(ECOMM, "insufficient arguments given"); SCOPED_RESLIST(rl_args); char** child_args = read_child_arglist(shex_hello->nr_argv); reslist_pop_nodestroy(rl_args); struct child_start_info csi = { .flags = CHILD_SETSID, .exename = child_args[0], .argv = (const char* const *) child_args + 1, .pty_setup = setup_pty, .pty_setup_data = shex_hello, .deathsig = -SIGHUP, }; if (shex_hello->si[0].pty_p) csi.flags |= CHILD_PTY_STDIN; if (shex_hello->si[1].pty_p) csi.flags |= CHILD_PTY_STDOUT; if (shex_hello->si[2].pty_p) csi.flags |= CHILD_PTY_STDERR; return child_start(&csi); } static void __attribute__((noreturn)) re_exec_as_root() { execlp("su", "su", "-c", xaprintf("%s stub", orig_argv0), NULL); die_errno("execlp of su"); } int stub_main(int argc, const char** argv) { if (argc != 1) die(EINVAL, "this command is internal"); /* XMKRAW_SKIP_CLEANUP so we never change from raw back to cooked * mode on exit. The connection dies on exit anyway, and * resetting the pty can send some extra bytes that can confuse * our peer. */ if (isatty(0)) xmkraw(0, XMKRAW_SKIP_CLEANUP); if (isatty(1)) xmkraw(1, XMKRAW_SKIP_CLEANUP); printf(FB_ADB_PROTO_START_LINE "\n", build_time, (int) getuid()); fflush(stdout); struct msg_shex_hello* shex_hello; struct msg* mhdr = read_msg(0, read_all_adb_encoded); if (mhdr->type == MSG_EXEC_AS_ROOT) re_exec_as_root(); // Never returns if (mhdr->type != MSG_SHEX_HELLO || mhdr->size < sizeof (struct msg_shex_hello)) { die(ECOMM, "bad hello"); } shex_hello = (struct msg_shex_hello*) mhdr; struct child* child = start_child(shex_hello); struct stub stub; memset(&stub, 0, sizeof (stub)); stub.child = child; struct fb_adb_sh* sh = &stub.sh; sh->process_msg = stub_process_msg; sh->max_outgoing_msg = shex_hello->maxmsg; sh->nrch = 5; struct channel** ch = xalloc(sh->nrch * sizeof (*ch)); ch[FROM_PEER] = channel_new(fdh_dup(0), shex_hello->stub_recv_bufsz, CHANNEL_FROM_FD); ch[FROM_PEER]->window = UINT32_MAX; ch[FROM_PEER]->adb_encoding_hack = true; replace_with_dev_null(0); ch[TO_PEER] = channel_new(fdh_dup(1), shex_hello->stub_send_bufsz, CHANNEL_TO_FD); replace_with_dev_null(1); ch[CHILD_STDIN] = channel_new(child->fd[0], shex_hello->si[0].bufsz, CHANNEL_TO_FD); ch[CHILD_STDIN]->track_bytes_written = true; ch[CHILD_STDIN]->bytes_written = ringbuf_room(ch[CHILD_STDIN]->rb); ch[CHILD_STDOUT] = channel_new(child->fd[1], shex_hello->si[1].bufsz, CHANNEL_FROM_FD); ch[CHILD_STDOUT]->track_window = true; ch[CHILD_STDERR] = channel_new(child->fd[2], shex_hello->si[2].bufsz, CHANNEL_FROM_FD); ch[CHILD_STDERR]->track_window = true; sh->ch = ch; io_loop_init(sh); PUMP_WHILE(sh, (!channel_dead_p(ch[FROM_PEER]) && !channel_dead_p(ch[TO_PEER]) && (!channel_dead_p(ch[CHILD_STDOUT]) || !channel_dead_p(ch[CHILD_STDERR])))); if (channel_dead_p(ch[FROM_PEER]) || channel_dead_p(ch[TO_PEER])) { // // If we lost our peer connection, make sure the child sees // SIGHUP instead of seeing its stdin close: just drain any // internally-buffered IO and exit. When we lose the pty, the // child gets SIGHUP. // // Make sure we won't be getting any more commands. channel_close(ch[FROM_PEER]); channel_close(ch[TO_PEER]); PUMP_WHILE(sh, (!channel_dead_p(ch[FROM_PEER]) || !channel_dead_p(ch[TO_PEER]))); // Drain output buffers channel_close(ch[CHILD_STDIN]); PUMP_WHILE(sh, !channel_dead_p(ch[CHILD_STDIN])); return 128 + SIGHUP; } // // Clean exit: close standard handles and drain IO. Peer still // has no idea that we're exiting. Get exit status, send that to // peer, then cleanly shut down the peer connection. // dbg("clean exit"); channel_close(ch[CHILD_STDIN]); channel_close(ch[CHILD_STDOUT]); channel_close(ch[CHILD_STDERR]); PUMP_WHILE (sh, (!channel_dead_p(ch[CHILD_STDIN]) || !channel_dead_p(ch[CHILD_STDOUT]) || !channel_dead_p(ch[CHILD_STDERR]))); send_exit_message(child_wait(child), sh); channel_close(ch[TO_PEER]); PUMP_WHILE(sh, !channel_dead_p(ch[TO_PEER])); channel_close(ch[FROM_PEER]); PUMP_WHILE(sh, !channel_dead_p(ch[FROM_PEER])); return 0; }
static void send_stat_packet(int to_peer, int xfer_fd) { struct stat st; if (fstat(xfer_fd, &st) == -1) die_errno("fstat"); struct xfer_msg m = { .type = XFER_MSG_STAT, .u.stat.atime = st.st_atime, .u.stat.mtime = st.st_mtime, #ifdef HAVE_STRUCT_STAT_ST_ATIM .u.stat.atime_ns = st.st_atim.tv_nsec, #endif #ifdef HAVE_STRUCT_STAT_ST_MTIM .u.stat.mtime_ns = st.st_mtim.tv_nsec, #endif .u.stat.size = st.st_size, .u.stat.ugo_bits = st.st_mode & 0777, }; send_xfer_msg(to_peer, &m); } struct xfer_ctx { int from_peer; int to_peer; const struct cmd_xfer_stub_info* info; }; static uint32_t recv_data_header(int from_peer) { struct xfer_msg m = recv_xfer_msg(from_peer); if (m.type != XFER_MSG_DATA) die(ECOMM, "unexpected message type %u", (unsigned) m.type); return m.u.data.payload_size; } static void send_data_header(int to_peer, uint32_t size) { struct xfer_msg m = { .type = XFER_MSG_DATA, .u.data.payload_size = size, }; send_xfer_msg(to_peer, &m); } static uint64_t copy_loop_posix_recv( int from_peer, int dest_fd) { SCOPED_RESLIST(rl); struct growable_buffer buf = { 0 }; uint64_t total_written = 0; size_t chunksz; do { chunksz = recv_data_header(from_peer); dbg("data chunk header chunksz=%u", (unsigned) chunksz); resize_buffer(&buf, chunksz); if (read_all(from_peer, buf.buf, chunksz) != chunksz) die(ECOMM, "unexpected EOF"); write_all(dest_fd, buf.buf, chunksz); if (SATADD(&total_written, total_written, chunksz)) die(ECOMM, "file size too large"); } while (chunksz > 0); return total_written; } static void copy_loop_posix_send( int to_peer, int source_fd) { SCOPED_RESLIST(rl); size_t bufsz = 32 * 1024; uint8_t* buf = xalloc(bufsz); size_t nr_read; assert(bufsz <= UINT32_MAX); do { nr_read = read_all(source_fd, buf, bufsz); send_data_header(to_peer, nr_read); write_all(to_peer, buf, nr_read); } while (nr_read > 0); }