void dbglock_init(void) { if (dbglock_fd != -1) return; if (!dbg_enabled_p()) return; const char envvar[] = "FB_ADB_DBGLOCK_NAME"; /* No, we can't just inherit the file descriptor. Without a * separate file open, taking the lock won't block. */ const char* fn = getenv(envvar); if (fn == NULL) { const char* pfx = DEFAULT_TEMP_DIR; char* tmpfname = xaprintf("%s/fb-adb-dbg-XXXXXX", pfx); struct cleanup* cl = cleanup_allocate(); int tmpfd = mkostemp(tmpfname, O_CLOEXEC); if (tmpfd != -1) { setenv(envvar, tmpfname, 1); cleanup_commit(cl, cleanup_dbginit, tmpfname); dbglock_fd = tmpfd; } return; } dbglock_fd = open(fn, O_CLOEXEC | O_RDWR); }
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; }
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); } }
static const char* xttyname(int fd) { #ifndef __ANDROID__ return ttyname(fd); #else return xreadlink(xaprintf("/proc/self/fd/%d", fd)); #endif }
static char* make_odex_name(const char* dex_file_name) { char* dex_file_name_copy = xstrdup(dex_file_name); char* suffix = strrchr(dex_file_name_copy, '.'); if (suffix == NULL || strcmp(suffix, ".jar") != 0) die(EINVAL, "invalid .jar file name %s", dex_file_name); *suffix = '\0'; return xaprintf("%s.odex", dex_file_name_copy); }
int bash_completion_main(const struct cmd_bash_completion_info* info) { const char* fb_adb_binary = orig_argv0; #ifdef HAVE_REALPATH // If argv0 isn't a relative or absolute path, assume we got it // from PATH and don't touch it. if (strchr(fb_adb_binary, '/')) fb_adb_binary = xrealpath(fb_adb_binary); #endif char* argv0_line = xaprintf( ": ${_fb_adb:=%s}\n", xshellquote(fb_adb_binary)); write_all(STDOUT_FILENO, argv0_line, strlen(argv0_line)); write_all(STDOUT_FILENO, bash_completion, sizeof (bash_completion)); 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 do_xfer_recv(const struct xfer_opts xfer_opts, const char* filename, const char* desired_basename, int from_peer) { struct xfer_msg statm = recv_xfer_msg(from_peer); if (statm.type != XFER_MSG_STAT) die(ECOMM, "expected stat msg"); struct cleanup* error_cl = cleanup_allocate(); struct stat st; const char* parent_directory = NULL; const char* rename_to = NULL; const char* write_mode = NULL; int dest_fd; if (stat(filename, &st) == 0) { if (S_ISDIR(st.st_mode)) { if (desired_basename == NULL) die(EISDIR, "\"%s\" is a directory", filename); parent_directory = filename; filename = xaprintf("%s/%s", parent_directory, desired_basename); } else if (S_ISREG(st.st_mode)) { if (st.st_nlink > 1) write_mode = "inplace"; } else { write_mode = "inplace"; } } if (parent_directory == NULL) parent_directory = xdirname(filename); if (write_mode == NULL) write_mode = xfer_opts.write_mode; bool atomic; bool automatic_mode = false; if (write_mode == NULL) { automatic_mode = true; atomic = true; } else if (strcmp(write_mode, "atomic") == 0) { atomic = true; } else if (strcmp(write_mode, "inplace") == 0) { atomic = false; } else { die(EINVAL, "unknown write mode \"%s\"", write_mode); } bool regular_file = true; bool preallocated = false; bool chmod_explicit = false; mode_t chmod_explicit_modes = 0; if (xfer_opts.preserve) { chmod_explicit = true; chmod_explicit_modes = statm.u.stat.ugo_bits; } if (xfer_opts.mode) { char* endptr = NULL; errno = 0; unsigned long omode = strtoul(xfer_opts.mode, &endptr, 8); if (errno != 0 || *endptr != '\0' || (omode &~ 0777) != 0) die(EINVAL, "invalid mode bits: %s", xfer_opts.mode); chmod_explicit = true; chmod_explicit_modes = (mode_t) omode; } mode_t creat_mode = (chmod_explicit_modes ? 0200 : 0666); if (atomic) { rename_to = filename; filename = xaprintf("%s.fb-adb-%s", filename, gen_hex_random(ENOUGH_ENTROPY)); dest_fd = try_xopen( filename, O_CREAT | O_WRONLY | O_EXCL, creat_mode); if (dest_fd == -1) { if (errno == EACCES && automatic_mode) { atomic = false; filename = rename_to; rename_to = NULL; } else { die_errno("open(\"%s\")", filename); } } } if (!atomic) { dest_fd = xopen(filename, O_WRONLY | O_CREAT | O_TRUNC, creat_mode); if (!S_ISREG(xfstat(dest_fd).st_mode)) regular_file = false; } if (regular_file) cleanup_commit(error_cl, unlink_cleanup, filename); if (regular_file && statm.u.stat.size > 0) preallocated = fallocate_if_supported( dest_fd, statm.u.stat.size); uint64_t total_written = copy_loop_posix_recv(from_peer, dest_fd); if (preallocated && total_written < statm.u.stat.size) xftruncate(dest_fd, total_written); if (xfer_opts.preserve) { struct timeval times[2] = { { statm.u.stat.atime, statm.u.stat.atime_ns / 1000 }, { statm.u.stat.mtime, statm.u.stat.mtime_ns / 1000 }, }; #ifdef HAVE_FUTIMES if (futimes(dest_fd, times) == -1) die_errno("futimes"); #else if (utimes(filename, times) == -1) die_errno("times"); #endif } if (chmod_explicit) if (fchmod(dest_fd, chmod_explicit_modes) == -1) die_errno("fchmod"); if (xfer_opts.sync) xfsync(dest_fd); if (rename_to) xrename(filename, rename_to); if (xfer_opts.sync) xfsync(xopen(parent_directory, O_DIRECTORY|O_RDONLY, 0)); cleanup_forget(error_cl); }