diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:55 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:32:55 -0800 |
commit | dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0 (patch) | |
tree | 2ba8d1a0846d69b18f623515e8d9b5d9fe38b590 /adb | |
parent | e54eebbf1a908d65ee8cf80bab62821c05666d70 (diff) | |
download | system_core-dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0.zip system_core-dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0.tar.gz system_core-dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'adb')
40 files changed, 13929 insertions, 0 deletions
diff --git a/adb/Android.mk b/adb/Android.mk new file mode 100644 index 0000000..2296610 --- /dev/null +++ b/adb/Android.mk @@ -0,0 +1,140 @@ +# Copyright 2005 The Android Open Source Project +# +# Android.mk for adb +# + +LOCAL_PATH:= $(call my-dir) + +# adb host tool +# ========================================================= +ifneq ($(TARGET_SIMULATOR),true) # not 64 bit clean (also unused with the sim) +include $(CLEAR_VARS) + +# Default to a virtual (sockets) usb interface +USB_SRCS := +EXTRA_SRCS := + +ifeq ($(HOST_OS),linux) + USB_SRCS := usb_linux.c + EXTRA_SRCS := get_my_path_linux.c + LOCAL_LDLIBS += -lrt -lncurses -lpthread +endif + +ifeq ($(HOST_OS),darwin) + USB_SRCS := usb_osx.c + EXTRA_SRCS := get_my_path_darwin.c + LOCAL_LDLIBS += -lpthread -framework CoreFoundation -framework IOKit -framework Carbon +endif + +ifeq ($(HOST_OS),windows) + USB_SRCS := usb_windows.c + EXTRA_SRCS := get_my_path_windows.c + EXTRA_STATIC_LIBS := AdbWinApi + LOCAL_C_INCLUDES += /usr/include/w32api/ddk development/host/windows/usb/api/ + ifneq ($(strip $(USE_CYGWIN)),) + LOCAL_LDLIBS += -lpthread + else + LOCAL_LDLIBS += -lws2_32 + USE_SYSDEPS_WIN32 := 1 + endif +endif + +LOCAL_SRC_FILES := \ + adb.c \ + console.c \ + transport.c \ + transport_local.c \ + transport_usb.c \ + commandline.c \ + adb_client.c \ + sockets.c \ + services.c \ + file_sync_client.c \ + $(EXTRA_SRCS) \ + $(USB_SRCS) \ + shlist.c \ + utils.c \ + + +ifneq ($(USE_SYSDEPS_WIN32),) + LOCAL_SRC_FILES += sysdeps_win32.c +endif + +LOCAL_CFLAGS += -O2 -g -DADB_HOST=1 -Wall -Wno-unused-parameter +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE -DSH_HISTORY +LOCAL_MODULE := adb + +LOCAL_STATIC_LIBRARIES := libzipfile libunz $(EXTRA_STATIC_LIBS) +ifeq ($(USE_SYSDEPS_WIN32),) + LOCAL_STATIC_LIBRARIES += libcutils +endif + +include $(BUILD_HOST_EXECUTABLE) + +$(call dist-for-goals,droid,$(LOCAL_BUILT_MODULE)) + +ifeq ($(HOST_OS),windows) +$(LOCAL_INSTALLED_MODULE): $(HOST_OUT_EXECUTABLES)/AdbWinApi.dll +endif + +endif + +# adbd device daemon +# ========================================================= + +# build adbd in all non-simulator builds +BUILD_ADBD := false +ifneq ($(TARGET_SIMULATOR),true) + BUILD_ADBD := true +endif + +# build adbd for the Linux simulator build +# so we can use it to test the adb USB gadget driver on x86 +ifeq ($(HOST_OS),linux) + BUILD_ADBD := true +endif + + +ifeq ($(BUILD_ADBD),true) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + adb.c \ + transport.c \ + transport_local.c \ + transport_usb.c \ + sockets.c \ + services.c \ + file_sync_service.c \ + jdwp_service.c \ + framebuffer_service.c \ + remount_service.c \ + usb_linux_client.c \ + log_service.c \ + utils.c \ + +LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE + +# TODO: This should probably be board specific, whether or not the kernel has +# the gadget driver; rather than relying on the architecture type. +ifeq ($(TARGET_ARCH),arm) +LOCAL_CFLAGS += -DANDROID_GADGET=1 +endif + +LOCAL_MODULE := adbd + +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT_SBIN) +LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_SBIN_UNSTRIPPED) + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_STATIC_LIBRARIES := libcutils + LOCAL_LDLIBS += -lpthread + include $(BUILD_HOST_EXECUTABLE) +else + LOCAL_STATIC_LIBRARIES := libcutils libc + include $(BUILD_EXECUTABLE) +endif + +endif diff --git a/adb/OVERVIEW.TXT b/adb/OVERVIEW.TXT new file mode 100644 index 0000000..6a5191a --- /dev/null +++ b/adb/OVERVIEW.TXT @@ -0,0 +1,139 @@ +Implementation notes regarding ADB. + +I. General Overview: + +The Android Debug Bridge (ADB) is used to: + +- keep track of all Android devices and emulators instances + connected to or running on a given host developer machine + +- implement various control commands (e.g. "adb shell", "adb pull", etc..) + for the benefit of clients (command-line users, or helper programs like + DDMS). These commands are what is called a 'service' in ADB. + +As a whole, everything works through the following components: + + 1. The ADB server + + This is a background process that runs on the host machine. Its purpose + if to sense the USB ports to know when devices are attached/removed, + as well as when emulator instances start/stop. + + It thus maintains a list of "connected devices" and assigns a 'state' + to each one of them: OFFLINE, BOOTLOADER, RECOVERY or ONLINE (more on + this below). + + The ADB server is really one giant multiplexing loop whose purpose is + to orchestrate the exchange of data (packets, really) between clients, + services and devices. + + + 2. The ADB daemon (adbd) + + The 'adbd' program runs as a background process within an Android device + or emulated system. Its purpose is to connect to the ADB server + (through USB for devices, through TCP for emulators) and provide a + few services for clients that run on the host. + + The ADB server considers that a device is ONLINE when it has succesfully + connected to the adbd program within it. Otherwise, the device is OFFLINE, + meaning that the ADB server detected a new device/emulator, but could not + connect to the adbd daemon. + + the BOOTLOADER and RECOVERY states correspond to alternate states of + devices when they are in the bootloader or recovery mode. + + 3. The ADB command-line client + + The 'adb' command-line program is used to run adb commands from a shell + or a script. It first tries to locate the ADB server on the host machine, + and will start one automatically if none is found. + + then, the client sends its service requests to the ADB server. It doesn't + need to know. + + Currently, a single 'adb' binary is used for both the server and client. + this makes distribution and starting the server easier. + + + 4. Services + + There are essentially two kinds of services that a client can talk to. + + Host Services: + these services run within the ADB Server and thus do not need to + communicate with a device at all. A typical example is "adb devices" + which is used to return the list of currently known devices and their + state. They are a few couple other services though. + + Local Services: + these services either run within the adbd daemon, or are started by + it on the device. The ADB server is used to multiplex streams + between the client and the service running in adbd. In this case + its role is to initiate the connection, then of being a pass-through + for the data. + + +II. Protocol details: + + 1. Client <-> Server protocol: + + This details the protocol used between ADB clients and the ADB + server itself. The ADB server listens on TCP:localhost:5037. + + A client sends a request using the following format: + + 1. A 4-byte hexadecimal string giving the length of the payload + 2. Followed by the payload itself. + + For example, to query the ADB server for its internal version number, + the client will do the following: + + 1. Connect to tcp:localhost:5037 + 2. Send the string "000Chost:version" to the corresponding socket + + The 'host:' prefix is used to indicate that the request is addressed + to the server itself (we will talk about other kinds of requests later). + The content length is encoded in ASCII for easier debugging. + + The server should answer a request with one of the following: + + 1. For success, the 4-byte "OKAY" string + + 2. For failure, the 4-byte "FAIL" string, followed by a + 4-byte hex length, followed by a string giving the reason + for failure. + + 3. As a special exception, for 'host:version', a 4-byte + hex string corresponding to the server's internal version number + + Note that the connection is still alive after an OKAY, which allows the + client to make other requests. But in certain cases, an OKAY will even + change the state of the connection. + + For example, the case of the 'host:transport:<serialnumber>' request, + where '<serialnumber>' is used to identify a given device/emulator; after + the "OKAY" answer, all further requests made by the client will go + directly to the corresponding adbd daemon. + + The file SERVICES.TXT lists all services currently implemented by ADB. + + + 2. Transports: + + An ADB transport models a connection between the ADB server and one device + or emulator. There are currently two kinds of transports: + + - USB transports, for physical devices through USB + + - Local transports, for emulators running on the host, connected to + the server through TCP + + In theory, it should be possible to write a local transport that proxies + a connection between an ADB server and a device/emulator connected to/ + running on another machine. This hasn't been done yet though. + + Each transport can carry one or more multiplexed streams between clients + and the device/emulator they point to. The ADB server must handle + unexpected transport disconnections (e.g. when a device is physically + unplugged) properly. diff --git a/adb/SERVICES.TXT b/adb/SERVICES.TXT new file mode 100644 index 0000000..b0124a4 --- /dev/null +++ b/adb/SERVICES.TXT @@ -0,0 +1,236 @@ +This file tries to document all requests a client can make +to the ADB server of an adbd daemon. See the OVERVIEW.TXT document +to understand what's going on here. + +HOST SERVICES: + +host:version + Ask the ADB server for its internal version number. + + As a special exception, the server will respond with a 4-byte + hex string corresponding to its internal version number, without + any OKAY or FAIL. + +host:kill + Ask the ADB server to quit immediately. This is used when the + ADB client detects that an obsolete server is running after an + upgrade. + +host:devices + Ask to return the list of available Android devices and their + state. After the OKAY, this is followed by a 4-byte hex len, + and a string that will be dumped as-is by the client, then + the connection is closed + +host:track-devices + This is a variant of host:devices which doesn't close the + connection. Instead, a new device list description is sent + each time a device is added/removed or the state of a given + device changes (hex4 + content). This allows tools like DDMS + to track the state of connected devices in real-time without + polling the server repeatedly. + +host:emulator:<port> + This is a special query that is sent to the ADB server when a + new emulator starts up. <port> is a decimal number corresponding + to the emulator's ADB control port, i.e. the TCP port that the + emulator will forward automatically to the adbd daemon running + in the emulator system. + + This mechanism allows the ADB server to know when new emulator + instances start. + +host:transport:<serial-number> + Ask to switch the connection to the device/emulator identified by + <serial-number>. After the OKAY response, every client request will + be sent directly to the adbd daemon running on the device. + (Used to implement the -s option) + +host:transport-usb + Ask to switch the connection to one device connected through USB + to the host machine. This will fail if there are more than one such + devices. (Used to implement the -d convenience option) + +host:transport-local + Ask to switch the connection to one emulator connected through TCP. + This will fail if there is more than one such emulator instance + running. (Used to implement the -e convenience option) + +host:transport-any + Another host:transport variant. Ask to switch the connection to + either the device or emulator connect to/running on the host. + Will fail if there is more than one such device/emulator available. + (Used when neither -s, -d or -e are provided) + +host-serial:<serial-number>:<request> + This is a special form of query, where the 'host-serial:<serial-number>:' + prefix can be used to indicate that the client is asking the ADB server + for information related to a specific device. <request> can be in one + of the format described below. + +host-usb:<request> + A variant of host-serial used to target the single USB device connected + to the host. This will fail if there is none or more than one. + +host-local:<request> + A variant of host-serial used to target the single emulator instance + running on the host. This will fail if therre is none or more than one. + +host:<request> + When asking for information related to a device, 'host:' can also be + interpreted as 'any single device or emulator connected to/running on + the host'. + +<host-prefix>:get-product + XXX + +<host-prefix>:get-serialno + Returns the serial number of the corresponding device/emulator. + Note that emulator serial numbers are of the form "emulator-5554" + +<host-prefix>:get-state + Returns the state of a given device as a string. + +<host-prefix>:forward:<local>;<remote> + Asks the ADB server to forward local connections from <local> + to the <remote> address on a given device. + + There, <host-prefix> can be one of the + host-serial/host-usb/host-local/host prefixes as described previously + and indicates which device/emulator to target. + + the format of <local> is one of: + + tcp:<port> -> TCP connection on localhost:<port> + local:<path> -> Unix local domain socket on <path> + + the format of <remote> is one of: + + tcp:<port> -> TCP localhost:<port> on device + local:<path> -> Unix local domain socket on device + jdwp:<pid> -> JDWP thread on VM process <pid> + + or even any one of the local services described below. + + + +LOCAL SERVICES: + +All the queries below assumed that you already switched the transport +to a real device, or that you have used a query prefix as described +above. + +shell:command arg1 arg2 ... + Run 'command arg1 arg2 ...' in a shell on the device, and return + its output and error streams. Note that arguments must be separated + by spaces. If an argument contains a space, it must be quoted with + double-quotes. Arguments cannot contain double quotes or things + will go very wrong. + + Note that this is the non-interactive version of "adb shell" + +shell: + Start an interactive shell session on the device. Redirect + stdin/stdout/stderr as appropriate. Note that the ADB server uses + this to implement "adb shell", but will also cook the input before + sending it to the device (see interactive_shell() in commandline.c) + +remount: + Ask adbd to remount the device's filesystem in read-write mode, + instead of read-only. This is usually necessary before performing + an "adb sync" or "adb push" request. + + This request may not succeed on certain builds which do not allow + that. + +dev:<path> + Opens a device file and connects the client directly to it for + read/write purposes. Useful for debugging, but may require special + priviledges and thus may not run on all devices. <path> is a full + path from the root of the filesystem. + +tcp:<port> + Tries to connect to tcp port <port> on localhost. + +tcp:<port>:<server-name> + Tries to connect to tcp port <port> on machine <server-name> from + the device. This can be useful to debug some networking/proxy + issues that can only be revealed on the device itself. + +local:<path> + Tries to connect to a Unix domain socket <path> on the device + +localreserved:<path> +localabstract:<path> +localfilesystem:<path> + Variants of local:<path> that are used to access other Android + socket namespaces. + +log:<name> + Opens one of the system logs (/dev/log/<name>) and allows the client + to read them directly. Used to implement 'adb logcat'. The stream + will be read-only for the client. + +framebuffer: + This service is used to send snapshots of the framebuffer to a client. + It requires sufficient priviledges but works as follow: + + After the OKAY, the service sends 16-byte binary structure + containing the following fields (little-endian format): + + depth: uint32_t: framebuffer depth + size: uint32_t: framebuffer size in bytes + width: uint32_t: framebuffer width in pixels + height: uint32_t: framebuffer height in pixels + + With the current implementation, depth is always 16, and + size is always width*height*2 + + Then, each time the client wants a snapshot, it should send + one byte through the channel, which will trigger the service + to send it 'size' bytes of framebuffer data. + + If the adbd daemon doesn't have sufficient priviledges to open + the framebuffer device, the connection is simply closed immediately. + +dns:<server-name> + This service is an exception because it only runs within the ADB server. + It is used to implement USB networking, i.e. to provide a network connection + to the device through the host machine (note: this is the exact opposite of + network thetering). + + It is used to perform a gethostbyname(<address>) on the host and return + the corresponding IP address as a 4-byte string. + +recover:<size> + This service is used to upload a recovery image to the device. <size> + must be a number corresponding to the size of the file. The service works + by: + + - creating a file named /tmp/update + - reading 'size' bytes from the client and writing them to /tmp/update + - when everything is read succesfully, create a file named /tmp/update.start + + This service can only work when the device is in recovery mode. Otherwise, + the /tmp directory doesn't exist and the connection will be closed immediately. + +jdwp:<pid> + Connects to the JDWP thread running in the VM of process <pid>. + +track-jdwp + This is used to send the list of JDWP pids periodically to the client. + The format of the returned data is the following: + + <hex4>: the length of all content as a 4-char hexadecimal string + <content>: a series of ASCII lines of the following format: + <pid> "\n" + + This service is used by DDMS to know which debuggable processes are running + on the device/emulator. + + Note that there is no single-shot service to retrieve the list only once. + +sync: + This starts the file synchronisation service, used to implement "adb push" + and "adb pull". Since this service is pretty complex, it will be detailed + in a companion document named SYNC.TXT diff --git a/adb/adb.c b/adb/adb.c new file mode 100644 index 0000000..fa5269f --- /dev/null +++ b/adb/adb.c @@ -0,0 +1,1083 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define TRACE_TAG TRACE_ADB + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> +#include <errno.h> +#include <string.h> +#include <time.h> + +#include "sysdeps.h" +#include "adb.h" + +#if !ADB_HOST +#include <private/android_filesystem_config.h> +#endif + + +int HOST = 0; + +static const char *adb_device_banner = "device"; + +void fatal(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(-1); +} + +void fatal_errno(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "error: %s: ", strerror(errno)); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(-1); +} + +int adb_trace_mask; + +/* read a comma/space/colum/semi-column separated list of tags + * from the ADB_TRACE environment variable and build the trace + * mask from it. note that '1' and 'all' are special cases to + * enable all tracing + */ +void adb_trace_init(void) +{ + const char* p = getenv("ADB_TRACE"); + const char* q; + + static const struct { + const char* tag; + int flag; + } tags[] = { + { "1", 0 }, + { "all", 0 }, + { "adb", TRACE_ADB }, + { "sockets", TRACE_SOCKETS }, + { "packets", TRACE_PACKETS }, + { "rwx", TRACE_RWX }, + { "usb", TRACE_USB }, + { "sync", TRACE_SYNC }, + { "sysdeps", TRACE_SYSDEPS }, + { "transport", TRACE_TRANSPORT }, + { "jdwp", TRACE_JDWP }, + { NULL, 0 } + }; + + if (p == NULL) + return; + + /* use a comma/column/semi-colum/space separated list */ + while (*p) { + int len, tagn; + + q = strpbrk(p, " ,:;"); + if (q == NULL) { + q = p + strlen(p); + } + len = q - p; + + for (tagn = 0; tags[tagn].tag != NULL; tagn++) + { + int taglen = strlen(tags[tagn].tag); + + if (len == taglen && !memcmp(tags[tagn].tag, p, len) ) + { + int flag = tags[tagn].flag; + if (flag == 0) { + adb_trace_mask = ~0; + return; + } + adb_trace_mask |= (1 << flag); + break; + } + } + p = q; + if (*p) + p++; + } +} + + +apacket *get_apacket(void) +{ + apacket *p = malloc(sizeof(apacket)); + if(p == 0) fatal("failed to allocate an apacket"); + memset(p, 0, sizeof(apacket) - MAX_PAYLOAD); + return p; +} + +void put_apacket(apacket *p) +{ + free(p); +} + +void handle_online(void) +{ + D("adb: online\n"); +#if !ADB_HOST + property_set("adb.connected","1"); +#endif +} + +void handle_offline(atransport *t) +{ + D("adb: offline\n"); + //Close the associated usb + run_transport_disconnects(t); +#if !ADB_HOST + property_set("adb.connected",""); +#endif +} + +#if TRACE_PACKETS +#define DUMPMAX 32 +void print_packet(const char *label, apacket *p) +{ + char *tag; + char *x; + unsigned count; + + switch(p->msg.command){ + case A_SYNC: tag = "SYNC"; break; + case A_CNXN: tag = "CNXN" ; break; + case A_OPEN: tag = "OPEN"; break; + case A_OKAY: tag = "OKAY"; break; + case A_CLSE: tag = "CLSE"; break; + case A_WRTE: tag = "WRTE"; break; + default: tag = "????"; break; + } + + fprintf(stderr, "%s: %s %08x %08x %04x \"", + label, tag, p->msg.arg0, p->msg.arg1, p->msg.data_length); + count = p->msg.data_length; + x = (char*) p->data; + if(count > DUMPMAX) { + count = DUMPMAX; + tag = "\n"; + } else { + tag = "\"\n"; + } + while(count-- > 0){ + if((*x >= ' ') && (*x < 127)) { + fputc(*x, stderr); + } else { + fputc('.', stderr); + } + x++; + } + fprintf(stderr, tag); +} +#endif + +static void send_ready(unsigned local, unsigned remote, atransport *t) +{ + D("Calling send_ready \n"); + apacket *p = get_apacket(); + p->msg.command = A_OKAY; + p->msg.arg0 = local; + p->msg.arg1 = remote; + send_packet(p, t); +} + +static void send_close(unsigned local, unsigned remote, atransport *t) +{ + D("Calling send_close \n"); + apacket *p = get_apacket(); + p->msg.command = A_CLSE; + p->msg.arg0 = local; + p->msg.arg1 = remote; + send_packet(p, t); +} + +static void send_connect(atransport *t) +{ + D("Calling send_connect \n"); + apacket *cp = get_apacket(); + cp->msg.command = A_CNXN; + cp->msg.arg0 = A_VERSION; + cp->msg.arg1 = MAX_PAYLOAD; + snprintf((char*) cp->data, sizeof cp->data, "%s::", + HOST ? "host" : adb_device_banner); + cp->msg.data_length = strlen((char*) cp->data) + 1; + send_packet(cp, t); +#if ADB_HOST + /* XXX why sleep here? */ + // allow the device some time to respond to the connect message + adb_sleep_ms(1000); +#endif +} + +static char *connection_state_name(atransport *t) +{ + if (t == NULL) { + return "unknown"; + } + + switch(t->connection_state) { + case CS_BOOTLOADER: + return "bootloader"; + case CS_DEVICE: + return "device"; + case CS_OFFLINE: + return "offline"; + default: + return "unknown"; + } +} + +void parse_banner(char *banner, atransport *t) +{ + char *type, *product, *end; + + D("parse_banner: %s\n", banner); + type = banner; + product = strchr(type, ':'); + if(product) { + *product++ = 0; + } else { + product = ""; + } + + /* remove trailing ':' */ + end = strchr(product, ':'); + if(end) *end = 0; + + /* save product name in device structure */ + if (t->product == NULL) { + t->product = strdup(product); + } else if (strcmp(product, t->product) != 0) { + free(t->product); + t->product = strdup(product); + } + + if(!strcmp(type, "bootloader")){ + D("setting connection_state to CS_BOOTLOADER\n"); + t->connection_state = CS_BOOTLOADER; + update_transports(); + return; + } + + if(!strcmp(type, "device")) { + D("setting connection_state to CS_DEVICE\n"); + t->connection_state = CS_DEVICE; + update_transports(); + return; + } + + if(!strcmp(type, "recovery")) { + D("setting connection_state to CS_RECOVERY\n"); + t->connection_state = CS_RECOVERY; + update_transports(); + return; + } + + t->connection_state = CS_HOST; +} + +void handle_packet(apacket *p, atransport *t) +{ + asocket *s; + + D("handle_packet() %d\n", p->msg.command); + + print_packet("recv", p); + + switch(p->msg.command){ + case A_SYNC: + if(p->msg.arg0){ + send_packet(p, t); + if(HOST) send_connect(t); + } else { + t->connection_state = CS_OFFLINE; + handle_offline(t); + send_packet(p, t); + } + return; + + case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */ + /* XXX verify version, etc */ + if(t->connection_state != CS_OFFLINE) { + t->connection_state = CS_OFFLINE; + handle_offline(t); + } + parse_banner((char*) p->data, t); + handle_online(); + if(!HOST) send_connect(t); + break; + + case A_OPEN: /* OPEN(local-id, 0, "destination") */ + if(t->connection_state != CS_OFFLINE) { + char *name = (char*) p->data; + name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0; + s = create_local_service_socket(name); + if(s == 0) { + send_close(0, p->msg.arg0, t); + } else { + s->peer = create_remote_socket(p->msg.arg0, t); + s->peer->peer = s; + send_ready(s->id, s->peer->id, t); + s->ready(s); + } + } + break; + + case A_OKAY: /* READY(local-id, remote-id, "") */ + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + if(s->peer == 0) { + s->peer = create_remote_socket(p->msg.arg0, t); + s->peer->peer = s; + } + s->ready(s); + } + } + break; + + case A_CLSE: /* CLOSE(local-id, remote-id, "") */ + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + s->close(s); + } + } + break; + + case A_WRTE: + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + unsigned rid = p->msg.arg0; + p->len = p->msg.data_length; + + if(s->enqueue(s, p) == 0) { + D("Enqueue the socket\n"); + send_ready(s->id, rid, t); + } + return; + } + } + break; + + default: + printf("handle_packet: what is %08x?!\n", p->msg.command); + } + + put_apacket(p); +} + +alistener listener_list = { + .next = &listener_list, + .prev = &listener_list, +}; + +static void ss_listener_event_func(int _fd, unsigned ev, void *_l) +{ + asocket *s; + + if(ev & FDE_READ) { + struct sockaddr addr; + socklen_t alen; + int fd; + + alen = sizeof(addr); + fd = adb_socket_accept(_fd, &addr, &alen); + if(fd < 0) return; + + adb_socket_setbufsize(fd, CHUNK_SIZE); + + s = create_local_socket(fd); + if(s) { + connect_to_smartsocket(s); + return; + } + + adb_close(fd); + } +} + +static void listener_event_func(int _fd, unsigned ev, void *_l) +{ + alistener *l = _l; + asocket *s; + + if(ev & FDE_READ) { + struct sockaddr addr; + socklen_t alen; + int fd; + + alen = sizeof(addr); + fd = adb_socket_accept(_fd, &addr, &alen); + if(fd < 0) return; + + s = create_local_socket(fd); + if(s) { + s->transport = l->transport; + connect_to_remote(s, l->connect_to); + return; + } + + adb_close(fd); + } +} + +static void free_listener(alistener* l) +{ + if (l->next) { + l->next->prev = l->prev; + l->prev->next = l->next; + l->next = l->prev = l; + } + + // closes the corresponding fd + fdevent_remove(&l->fde); + + if (l->local_name) + free((char*)l->local_name); + + if (l->connect_to) + free((char*)l->connect_to); + + if (l->transport) { + remove_transport_disconnect(l->transport, &l->disconnect); + } + free(l); +} + +static void listener_disconnect(void* _l, atransport* t) +{ + alistener* l = _l; + + free_listener(l); +} + +int local_name_to_fd(const char *name) +{ + int port; + + if(!strncmp("tcp:", name, 4)){ + int ret; + port = atoi(name + 4); + ret = socket_loopback_server(port, SOCK_STREAM); + return ret; + } +#ifndef HAVE_WIN32_IPC /* no Unix-domain sockets on Win32 */ + // It's non-sensical to support the "reserved" space on the adb host side + if(!strncmp(name, "local:", 6)) { + return socket_local_server(name + 6, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + } else if(!strncmp(name, "localabstract:", 14)) { + return socket_local_server(name + 14, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + } else if(!strncmp(name, "localfilesystem:", 16)) { + return socket_local_server(name + 16, + ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); + } + +#endif + printf("unknown local portname '%s'\n", name); + return -1; +} + +static int remove_listener(const char *local_name, const char *connect_to, atransport* transport) +{ + alistener *l; + + for (l = listener_list.next; l != &listener_list; l = l->next) { + if (!strcmp(local_name, l->local_name) && + !strcmp(connect_to, l->connect_to) && + l->transport && l->transport == transport) { + + listener_disconnect(l, transport); + return 0; + } + } + + return -1; +} + +static int install_listener(const char *local_name, const char *connect_to, atransport* transport) +{ + alistener *l; + + //printf("install_listener('%s','%s')\n", local_name, connect_to); + + for(l = listener_list.next; l != &listener_list; l = l->next){ + if(strcmp(local_name, l->local_name) == 0) { + char *cto; + + /* can't repurpose a smartsocket */ + if(l->connect_to[0] == '*') { + return -1; + } + + cto = strdup(connect_to); + if(cto == 0) { + return -1; + } + + //printf("rebinding '%s' to '%s'\n", local_name, connect_to); + free((void*) l->connect_to); + l->connect_to = cto; + if (l->transport != transport) { + remove_transport_disconnect(l->transport, &l->disconnect); + l->transport = transport; + add_transport_disconnect(l->transport, &l->disconnect); + } + return 0; + } + } + + if((l = calloc(1, sizeof(alistener))) == 0) goto nomem; + if((l->local_name = strdup(local_name)) == 0) goto nomem; + if((l->connect_to = strdup(connect_to)) == 0) goto nomem; + + + l->fd = local_name_to_fd(local_name); + if(l->fd < 0) { + free((void*) l->local_name); + free((void*) l->connect_to); + free(l); + printf("cannot bind '%s'\n", local_name); + return -2; + } + + close_on_exec(l->fd); + if(!strcmp(l->connect_to, "*smartsocket*")) { + fdevent_install(&l->fde, l->fd, ss_listener_event_func, l); + } else { + fdevent_install(&l->fde, l->fd, listener_event_func, l); + } + fdevent_set(&l->fde, FDE_READ); + + l->next = &listener_list; + l->prev = listener_list.prev; + l->next->prev = l; + l->prev->next = l; + l->transport = transport; + + if (transport) { + l->disconnect.opaque = l; + l->disconnect.func = listener_disconnect; + add_transport_disconnect(transport, &l->disconnect); + } + return 0; + +nomem: + fatal("cannot allocate listener"); + return 0; +} + +#ifdef HAVE_FORKEXEC +static void sigchld_handler(int n) +{ + int status; + while(waitpid(-1, &status, WNOHANG) > 0) ; +} +#endif + +#ifdef HAVE_WIN32_PROC +static BOOL WINAPI ctrlc_handler(DWORD type) +{ + exit(STATUS_CONTROL_C_EXIT); + return TRUE; +} +#endif + +static void adb_cleanup(void) +{ + usb_cleanup(); +} + +void start_logging(void) +{ +#ifdef HAVE_WIN32_PROC + char temp[ MAX_PATH ]; + FILE* fnul; + FILE* flog; + + GetTempPath( sizeof(temp) - 8, temp ); + strcat( temp, "adb.log" ); + + /* Win32 specific redirections */ + fnul = fopen( "NUL", "rt" ); + if (fnul != NULL) + stdin[0] = fnul[0]; + + flog = fopen( temp, "at" ); + if (flog == NULL) + flog = fnul; + + setvbuf( flog, NULL, _IONBF, 0 ); + + stdout[0] = flog[0]; + stderr[0] = flog[0]; + fprintf(stderr,"--- adb starting (pid %d) ---\n", getpid()); +#else + int fd; + + fd = unix_open("/dev/null", O_RDONLY); + dup2(fd, 0); + + fd = unix_open("/tmp/adb.log", O_WRONLY | O_CREAT | O_APPEND, 0640); + if(fd < 0) { + fd = unix_open("/dev/null", O_WRONLY); + } + dup2(fd, 1); + dup2(fd, 2); + fprintf(stderr,"--- adb starting (pid %d) ---\n", getpid()); +#endif +} + +#if !ADB_HOST +void start_device_log(void) +{ + int fd; + char path[100]; + + snprintf(path, sizeof path, "/data/adb_%ld.txt", (long)time(NULL)); + fd = unix_open(path, O_WRONLY | O_CREAT | O_APPEND, 0640); + if (fd < 0) + return; + + // redirect stdout and stderr to the log file + dup2(fd, 1); + dup2(fd, 2); + fprintf(stderr,"--- adb starting (pid %d) ---\n", getpid()); + + fd = unix_open("/dev/null", O_RDONLY); + dup2(fd, 0); + + // log everything + adb_trace_mask = ~0; + // except TRACE_RWX is a bit too verbose + adb_trace_mask &= ~TRACE_RWX; +} +#endif + +#if ADB_HOST +int launch_server() +{ +#ifdef HAVE_WIN32_PROC + /* we need to start the server in the background */ + /* we create a PIPE that will be used to wait for the server's "OK" */ + /* message since the pipe handles must be inheritable, we use a */ + /* security attribute */ + HANDLE pipe_read, pipe_write; + SECURITY_ATTRIBUTES sa; + STARTUPINFO startup; + PROCESS_INFORMATION pinfo; + char program_path[ MAX_PATH ]; + int ret; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + /* create pipe, and ensure its read handle isn't inheritable */ + ret = CreatePipe( &pipe_read, &pipe_write, &sa, 0 ); + if (!ret) { + fprintf(stderr, "CreatePipe() failure, error %ld\n", GetLastError() ); + return -1; + } + + SetHandleInformation( pipe_read, HANDLE_FLAG_INHERIT, 0 ); + + ZeroMemory( &startup, sizeof(startup) ); + startup.cb = sizeof(startup); + startup.hStdInput = GetStdHandle( STD_INPUT_HANDLE ); + startup.hStdOutput = pipe_write; + startup.hStdError = GetStdHandle( STD_ERROR_HANDLE ); + startup.dwFlags = STARTF_USESTDHANDLES; + + ZeroMemory( &pinfo, sizeof(pinfo) ); + + /* get path of current program */ + GetModuleFileName( NULL, program_path, sizeof(program_path) ); + + ret = CreateProcess( + program_path, /* program path */ + "adb fork-server server", + /* the fork-server argument will set the + debug = 2 in the child */ + NULL, /* process handle is not inheritable */ + NULL, /* thread handle is not inheritable */ + TRUE, /* yes, inherit some handles */ + DETACHED_PROCESS, /* the new process doesn't have a console */ + NULL, /* use parent's environment block */ + NULL, /* use parent's starting directory */ + &startup, /* startup info, i.e. std handles */ + &pinfo ); + + CloseHandle( pipe_write ); + + if (!ret) { + fprintf(stderr, "CreateProcess failure, error %ld\n", GetLastError() ); + CloseHandle( pipe_read ); + return -1; + } + + CloseHandle( pinfo.hProcess ); + CloseHandle( pinfo.hThread ); + + /* wait for the "OK\n" message */ + { + char temp[3]; + DWORD count; + + ret = ReadFile( pipe_read, temp, 3, &count, NULL ); + CloseHandle( pipe_read ); + if ( !ret ) { + fprintf(stderr, "could not read ok from ADB Server, error = %ld\n", GetLastError() ); + return -1; + } + if (count != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') { + fprintf(stderr, "ADB server didn't ACK\n" ); + return -1; + } + } +#elif defined(HAVE_FORKEXEC) + char path[PATH_MAX]; + int fd[2]; + + // set up a pipe so the child can tell us when it is ready. + // fd[0] will be parent's end, and fd[1] will get mapped to stderr in the child. + if (pipe(fd)) { + fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno); + return -1; + } + get_my_path(path); + pid_t pid = fork(); + if(pid < 0) return -1; + + if (pid == 0) { + // child side of the fork + + // redirect stderr to the pipe + // we use stderr instead of stdout due to stdout's buffering behavior. + adb_close(fd[0]); + dup2(fd[1], STDERR_FILENO); + adb_close(fd[1]); + + // child process + int result = execl(path, "adb", "fork-server", "server", NULL); + // this should not return + fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno); + } else { + // parent side of the fork + + char temp[3]; + + temp[0] = 'A'; temp[1] = 'B'; temp[2] = 'C'; + // wait for the "OK\n" message + adb_close(fd[1]); + int ret = adb_read(fd[0], temp, 3); + adb_close(fd[0]); + if (ret < 0) { + fprintf(stderr, "could not read ok from ADB Server, errno = %d\n", errno); + return -1; + } + if (ret != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') { + fprintf(stderr, "ADB server didn't ACK\n" ); + return -1; + } + + setsid(); + } +#else +#error "cannot implement background server start on this platform" +#endif + return 0; +} +#endif + +int adb_main(int is_daemon) +{ +#if !ADB_HOST + int secure = 0; + char value[PROPERTY_VALUE_MAX]; + + // prevent the OOM killer from killing us + char text[64]; + snprintf(text, sizeof text, "/proc/%d/oom_adj", (int)getpid()); + int fd = adb_open(text, O_WRONLY); + if (fd >= 0) { + // -17 should make us immune to OOM + snprintf(text, sizeof text, "%d", -17); + adb_write(fd, text, strlen(text)); + adb_close(fd); + } else { + D("adb: unable to open %s\n", text); + } +#endif + + atexit(adb_cleanup); +#ifdef HAVE_WIN32_PROC + SetConsoleCtrlHandler( ctrlc_handler, TRUE ); +#elif defined(HAVE_FORKEXEC) + signal(SIGCHLD, sigchld_handler); + signal(SIGPIPE, SIG_IGN); +#endif + + init_transport_registration(); + + +#if ADB_HOST + HOST = 1; + usb_init(); + local_init(); + + if(install_listener("tcp:5037", "*smartsocket*", NULL)) { + exit(1); + } +#else + /* run adbd in secure mode if ro.secure is set and + ** we are not in the emulator + */ + property_get("ro.kernel.qemu", value, ""); + if (strcmp(value, "1") != 0) { + property_get("ro.secure", value, ""); + if (strcmp(value, "1") == 0) + secure = 1; + } + + /* don't listen on port 5037 if we are running in secure mode */ + /* don't run as root if we are running in secure mode */ + if (secure) { + /* add extra groups: + ** AID_ADB to access the USB driver + ** AID_LOG to read system logs (adb logcat) + ** AID_INPUT to diagnose input issues (getevent) + ** AID_INET to diagnose network issues (netcfg, ping) + ** AID_GRAPHICS to access the frame buffer + */ + gid_t groups[] = { AID_ADB, AID_LOG, AID_INPUT, AID_INET, AID_GRAPHICS }; + setgroups(sizeof(groups)/sizeof(groups[0]), groups); + + /* then switch user and group to "shell" */ + setgid(AID_SHELL); + setuid(AID_SHELL); + + D("Local port 5037 disabled\n"); + } else { + if(install_listener("tcp:5037", "*smartsocket*", NULL)) { + exit(1); + } + } + + /* for the device, start the usb transport if the + ** android usb device exists, otherwise start the + ** network transport. + */ + if(access("/dev/android_adb", F_OK) == 0 || + access("/dev/android", F_OK) == 0) { + usb_init(); + } else { + local_init(); + } + init_jdwp(); +#endif + + if (is_daemon) + { + // inform our parent that we are up and running. +#ifdef HAVE_WIN32_PROC + DWORD count; + WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), "OK\n", 3, &count, NULL ); +#elif defined(HAVE_FORKEXEC) + fprintf(stderr, "OK\n"); +#endif + start_logging(); + } + + fdevent_loop(); + + usb_cleanup(); + + return 0; +} + +int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s) +{ + atransport *transport = NULL; + char buf[4096]; + + if(!strcmp(service, "kill")) { + fprintf(stderr,"adb server killed by remote request\n"); + fflush(stdout); + adb_write(reply_fd, "OKAY", 4); + usb_cleanup(); + exit(0); + } + +#if ADB_HOST + // "transport:" is used for switching transport with a specified serial number + // "transport-usb:" is used for switching transport to the only USB transport + // "transport-local:" is used for switching transport to the only local transport + // "transport-any:" is used for switching transport to the only transport + if (!strncmp(service, "transport", strlen("transport"))) { + char* error_string = "unknown failure"; + transport_type type = kTransportAny; + + if (!strncmp(service, "transport-usb", strlen("transport-usb"))) { + type = kTransportUsb; + } else if (!strncmp(service, "transport-local", strlen("transport-local"))) { + type = kTransportLocal; + } else if (!strncmp(service, "transport-any", strlen("transport-any"))) { + type = kTransportAny; + } else if (!strncmp(service, "transport:", strlen("transport:"))) { + service += strlen("transport:"); + serial = strdup(service); + } + + transport = acquire_one_transport(CS_ANY, type, serial, &error_string); + + if (transport) { + s->transport = transport; + adb_write(reply_fd, "OKAY", 4); + } else { + sendfailmsg(reply_fd, error_string); + } + return 1; + } + + // return a list of all connected devices + if (!strcmp(service, "devices")) { + char buffer[4096]; + memset(buf, 0, sizeof(buf)); + memset(buffer, 0, sizeof(buffer)); + D("Getting device list \n"); + list_transports(buffer, sizeof(buffer)); + snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer),buffer); + D("Wrote device list \n"); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + + // returns our value for ADB_SERVER_VERSION + if (!strcmp(service, "version")) { + char version[12]; + snprintf(version, sizeof version, "%04x", ADB_SERVER_VERSION); + snprintf(buf, sizeof buf, "OKAY%04x%s", (unsigned)strlen(version), version); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + + if(!strncmp(service,"get-serialno",strlen("get-serialno"))) { + char *out = "unknown"; + transport = acquire_one_transport(CS_ANY, ttype, serial, NULL); + if (transport && transport->serial) { + out = transport->serial; + } + snprintf(buf, sizeof buf, "OKAY%04x%s",(unsigned)strlen(out),out); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + // indicates a new emulator instance has started + if (!strncmp(service,"emulator:",9)) { + int port = atoi(service+9); + local_connect(port); + /* we don't even need to send a reply */ + return 0; + } +#endif // ADB_HOST + + if(!strncmp(service,"forward:",8) || !strncmp(service,"killforward:",12)) { + char *local, *remote, *err; + int r; + atransport *transport; + + int createForward = strncmp(service,"kill",4); + + local = service + (createForward ? 8 : 12); + remote = strchr(local,';'); + if(remote == 0) { + sendfailmsg(reply_fd, "malformed forward spec"); + return 0; + } + + *remote++ = 0; + if((local[0] == 0) || (remote[0] == 0) || (remote[0] == '*')){ + sendfailmsg(reply_fd, "malformed forward spec"); + return 0; + } + + transport = acquire_one_transport(CS_ANY, ttype, serial, &err); + if (!transport) { + sendfailmsg(reply_fd, err); + return 0; + } + + if (createForward) { + r = install_listener(local, remote, transport); + } else { + r = remove_listener(local, remote, transport); + } + if(r == 0) { + /* 1st OKAY is connect, 2nd OKAY is status */ + writex(reply_fd, "OKAYOKAY", 8); + return 0; + } + + if (createForward) { + sendfailmsg(reply_fd, (r == -1) ? "cannot rebind smartsocket" : "cannot bind socket"); + } else { + sendfailmsg(reply_fd, "cannot remove listener"); + } + return 0; + } + + if(!strncmp(service,"get-state",strlen("get-state"))) { + transport = acquire_one_transport(CS_ANY, ttype, serial, NULL); + char *state = connection_state_name(transport); + snprintf(buf, sizeof buf, "OKAY%04x%s",(unsigned)strlen(state),state); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + return -1; +} + +#if !ADB_HOST +int recovery_mode = 0; +#endif + +int main(int argc, char **argv) +{ + adb_trace_init(); +#if ADB_HOST + adb_sysdeps_init(); + return adb_commandline(argc - 1, argv + 1); +#else + if((argc > 1) && (!strcmp(argv[1],"recovery"))) { + adb_device_banner = "recovery"; + recovery_mode = 1; + } +#if ADB_DEVICE_LOG + start_device_log(); +#endif + return adb_main(0); +#endif +} + diff --git a/adb/adb.h b/adb/adb.h new file mode 100644 index 0000000..a17c8dd --- /dev/null +++ b/adb/adb.h @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __ADB_H +#define __ADB_H + +#include <limits.h> + +#define MAX_PAYLOAD 4096 + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 + +#define A_VERSION 0x01000000 // ADB protocol version + +#define ADB_VERSION_MAJOR 1 // Used for help/version information +#define ADB_VERSION_MINOR 0 // Used for help/version information + +#define ADB_SERVER_VERSION 20 // Increment this when we want to force users to start a new adb server + +typedef struct amessage amessage; +typedef struct apacket apacket; +typedef struct asocket asocket; +typedef struct alistener alistener; +typedef struct aservice aservice; +typedef struct atransport atransport; +typedef struct adisconnect adisconnect; +typedef struct usb_handle usb_handle; + +struct amessage { + unsigned command; /* command identifier constant */ + unsigned arg0; /* first argument */ + unsigned arg1; /* second argument */ + unsigned data_length; /* length of payload (0 is allowed) */ + unsigned data_check; /* checksum of data payload */ + unsigned magic; /* command ^ 0xffffffff */ +}; + +struct apacket +{ + apacket *next; + + unsigned len; + unsigned char *ptr; + + amessage msg; + unsigned char data[MAX_PAYLOAD]; +}; + +/* An asocket represents one half of a connection between a local and +** remote entity. A local asocket is bound to a file descriptor. A +** remote asocket is bound to the protocol engine. +*/ +struct asocket { + /* chain pointers for the local/remote list of + ** asockets that this asocket lives in + */ + asocket *next; + asocket *prev; + + /* the unique identifier for this asocket + */ + unsigned id; + + /* flag: set when the socket's peer has closed + ** but packets are still queued for delivery + */ + int closing; + + /* the asocket we are connected to + */ + + asocket *peer; + + /* For local asockets, the fde is used to bind + ** us to our fd event system. For remote asockets + ** these fields are not used. + */ + fdevent fde; + int fd; + + /* queue of apackets waiting to be written + */ + apacket *pkt_first; + apacket *pkt_last; + + /* enqueue is called by our peer when it has data + ** for us. It should return 0 if we can accept more + ** data or 1 if not. If we return 1, we must call + ** peer->ready() when we once again are ready to + ** receive data. + */ + int (*enqueue)(asocket *s, apacket *pkt); + + /* ready is called by the peer when it is ready for + ** us to send data via enqueue again + */ + void (*ready)(asocket *s); + + /* close is called by the peer when it has gone away. + ** we are not allowed to make any further calls on the + ** peer once our close method is called. + */ + void (*close)(asocket *s); + + /* socket-type-specific extradata */ + void *extra; + + /* A socket is bound to atransport */ + atransport *transport; +}; + + +/* the adisconnect structure is used to record a callback that +** will be called whenever a transport is disconnected (e.g. by the user) +** this should be used to cleanup objects that depend on the +** transport (e.g. remote sockets, listeners, etc...) +*/ +struct adisconnect +{ + void (*func)(void* opaque, atransport* t); + void* opaque; + adisconnect* next; + adisconnect* prev; +}; + + +/* a transport object models the connection to a remote device or emulator +** there is one transport per connected device/emulator. a "local transport" +** connects through TCP (for the emulator), while a "usb transport" through +** USB (for real devices) +** +** note that kTransportHost doesn't really correspond to a real transport +** object, it's a special value used to indicate that a client wants to +** connect to a service implemented within the ADB server itself. +*/ +typedef enum transport_type { + kTransportUsb, + kTransportLocal, + kTransportAny, + kTransportHost, +} transport_type; + +struct atransport +{ + atransport *next; + atransport *prev; + + int (*read_from_remote)(apacket *p, atransport *t); + int (*write_to_remote)(apacket *p, atransport *t); + void (*close)(atransport *t); + void (*kick)(atransport *t); + + int fd; + int transport_socket; + fdevent transport_fde; + int ref_count; + unsigned sync_token; + int connection_state; + transport_type type; + + /* usb handle or socket fd as needed */ + usb_handle *usb; + int sfd; + + /* used to identify transports for clients */ + char *serial; + char *product; + + /* a list of adisconnect callbacks called when the transport is kicked */ + int kicked; + adisconnect disconnects; +}; + + +/* A listener is an entity which binds to a local port +** and, upon receiving a connection on that port, creates +** an asocket to connect the new local connection to a +** specific remote service. +** +** TODO: some listeners read from the new connection to +** determine what exact service to connect to on the far +** side. +*/ +struct alistener +{ + alistener *next; + alistener *prev; + + fdevent fde; + int fd; + + const char *local_name; + const char *connect_to; + atransport *transport; + adisconnect disconnect; +}; + + +void print_packet(const char *label, apacket *p); + +asocket *find_local_socket(unsigned id); +void install_local_socket(asocket *s); +void remove_socket(asocket *s); +void close_all_sockets(atransport *t); + +#define LOCAL_CLIENT_PREFIX "emulator-" + +asocket *create_local_socket(int fd); +asocket *create_local_service_socket(const char *destination); + +asocket *create_remote_socket(unsigned id, atransport *t); +void connect_to_remote(asocket *s, const char *destination); +void connect_to_smartsocket(asocket *s); + +void fatal(const char *fmt, ...); +void fatal_errno(const char *fmt, ...); + +void handle_packet(apacket *p, atransport *t); +void send_packet(apacket *p, atransport *t); + +void get_my_path(char s[PATH_MAX]); +int launch_server(); +int adb_main(int is_daemon); + + +/* transports are ref-counted +** get_device_transport does an acquire on your behalf before returning +*/ +void init_transport_registration(void); +int list_transports(char *buf, size_t bufsize); +void update_transports(void); + +asocket* create_device_tracker(void); + +/* Obtain a transport from the available transports. +** If state is != CS_ANY, only transports in that state are considered. +** If serial is non-NULL then only the device with that serial will be chosen. +** If no suitable transport is found, error is set. +*/ +atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char **error_out); +void add_transport_disconnect( atransport* t, adisconnect* dis ); +void remove_transport_disconnect( atransport* t, adisconnect* dis ); +void run_transport_disconnects( atransport* t ); +void kick_transport( atransport* t ); + +/* initialize a transport object's func pointers and state */ +int init_socket_transport(atransport *t, int s, int port); +void init_usb_transport(atransport *t, usb_handle *usb); + +/* for MacOS X cleanup */ +void close_usb_devices(); + +/* cause new transports to be init'd and added to the list */ +void register_socket_transport(int s, const char *serial, int port); +void register_usb_transport(usb_handle *h, const char *serial); + +int service_to_fd(const char *name); +#if ADB_HOST +asocket *host_service_to_socket(const char* name, const char *serial); +#endif + +#if !ADB_HOST +int init_jdwp(void); +asocket* create_jdwp_service_socket(); +asocket* create_jdwp_tracker_service_socket(); +int create_jdwp_connection_fd(int jdwp_pid); +#endif + +#if !ADB_HOST +void framebuffer_service(int fd, void *cookie); +void log_service(int fd, void *cookie); +void remount_service(int fd, void *cookie); +char * get_log_file_path(const char * log_name); +#endif + +/* packet allocator */ +apacket *get_apacket(void); +void put_apacket(apacket *p); + +int check_header(apacket *p); +int check_data(apacket *p); + +/* convenience wrappers around read/write that will retry on +** EINTR and/or short read/write. Returns 0 on success, -1 +** on error or EOF. +*/ +int readx(int fd, void *ptr, size_t len); +int writex(int fd, const void *ptr, size_t len); + +/* define ADB_TRACE to 1 to enable tracing support, or 0 to disable it */ + +#define ADB_TRACE 1 + +/* IMPORTANT: if you change the following list, don't + * forget to update the corresponding 'tags' table in + * the adb_trace_init() function implemented in adb.c + */ +typedef enum { + TRACE_ADB = 0, + TRACE_SOCKETS, + TRACE_PACKETS, + TRACE_TRANSPORT, + TRACE_RWX, + TRACE_USB, + TRACE_SYNC, + TRACE_SYSDEPS, + TRACE_JDWP, +} AdbTrace; + +#if ADB_TRACE + + int adb_trace_mask; + + void adb_trace_init(void); + +# define ADB_TRACING ((adb_trace_mask & (1 << TRACE_TAG)) != 0) + + /* you must define TRACE_TAG before using this macro */ + #define D(...) \ + do { \ + if (ADB_TRACING) \ + fprintf(stderr, __VA_ARGS__ ); \ + } while (0) +#else +# define D(...) ((void)0) +# define ADB_TRACING 0 +#endif + + +/* set this to log to /data/adb/adb_<time>.txt on the device. + * has no effect if the /data/adb/ directory does not exist. + */ +#define ADB_DEVICE_LOG 0 + +#if !TRACE_PACKETS +#define print_packet(tag,p) do {} while (0) +#endif + +#define ADB_PORT 5037 +#define ADB_LOCAL_TRANSPORT_PORT 5555 + +// Google's USB Vendor ID +#define VENDOR_ID_GOOGLE 0x18d1 +// HTC's USB Vendor ID +#define VENDOR_ID_HTC 0x0bb4 + +// products for VENDOR_ID_GOOGLE +#define PRODUCT_ID_SOONER 0xd00d // Sooner bootloader +#define PRODUCT_ID_SOONER_COMP 0xdeed // Sooner composite device + +// products for VENDOR_ID_HTC +#define PRODUCT_ID_DREAM 0x0c01 // Dream bootloader +#define PRODUCT_ID_DREAM_COMP 0x0c02 // Dream composite device + +void local_init(); +int local_connect(int port); + +/* usb host/client interface */ +void usb_init(); +void usb_cleanup(); +int usb_write(usb_handle *h, const void *data, int len); +int usb_read(usb_handle *h, void *data, int len); +int usb_close(usb_handle *h); +void usb_kick(usb_handle *h); + +/* used for USB device detection */ +int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol); + +unsigned host_to_le32(unsigned n); +int adb_commandline(int argc, char **argv); + +int connection_state(atransport *t); + +#define CS_ANY -1 +#define CS_OFFLINE 0 +#define CS_BOOTLOADER 1 +#define CS_DEVICE 2 +#define CS_HOST 3 +#define CS_RECOVERY 4 +#define CS_ERROR 5 + +extern int HOST; + +#define CHUNK_SIZE (64*1024) + +int sendfailmsg(int fd, const char *reason); +int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s); + +#endif diff --git a/adb/adb_client.c b/adb/adb_client.c new file mode 100644 index 0000000..5868744 --- /dev/null +++ b/adb/adb_client.c @@ -0,0 +1,318 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <zipfile/zipfile.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_ADB +#include "adb_client.h" + +static transport_type __adb_transport = kTransportAny; +static const char* __adb_serial = NULL; + +void adb_set_transport(transport_type type, const char* serial) +{ + __adb_transport = type; + __adb_serial = serial; +} + +int adb_get_emulator_console_port(void) +{ + const char* serial = __adb_serial; + int port; + + if (serial == NULL) { + /* if no specific device was specified, we need to look at */ + /* the list of connected devices, and extract an emulator */ + /* name from it. two emulators is an error */ + char* tmp = adb_query("host:devices"); + char* p = tmp; + if(!tmp) { + printf("no emulator connected\n"); + return -1; + } + while (*p) { + char* q = strchr(p, '\n'); + if (q != NULL) + *q++ = 0; + else + q = p + strlen(p); + + if (!memcmp(p, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1)) { + if (serial != NULL) { /* more than one emulator listed */ + free(tmp); + return -2; + } + serial = p; + } + + p = q; + } + free(tmp); + + if (serial == NULL) + return -1; /* no emulator found */ + } + else { + if (memcmp(serial, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1) != 0) + return -1; /* not an emulator */ + } + + serial += sizeof(LOCAL_CLIENT_PREFIX)-1; + port = strtol(serial, NULL, 10); + return port; +} + +static char __adb_error[256] = { 0 }; + +const char *adb_error(void) +{ + return __adb_error; +} + +static int switch_socket_transport(int fd) +{ + char service[64]; + char tmp[5]; + int len; + + if (__adb_serial) + snprintf(service, sizeof service, "host:transport:%s", __adb_serial); + else { + char* transport_type = "???"; + + switch (__adb_transport) { + case kTransportUsb: + transport_type = "transport-usb"; + break; + case kTransportLocal: + transport_type = "transport-local"; + break; + case kTransportAny: + transport_type = "transport-any"; + break; + case kTransportHost: + // no switch necessary + return 0; + break; + } + + snprintf(service, sizeof service, "host:%s", transport_type); + } + len = strlen(service); + snprintf(tmp, sizeof tmp, "%04x", len); + + if(writex(fd, tmp, 4) || writex(fd, service, len)) { + strcpy(__adb_error, "write failure during connection"); + adb_close(fd); + return -1; + } + D("Switch transport in progress\n"); + + if(adb_status(fd)) { + adb_close(fd); + D("Switch transport failed\n"); + return -1; + } + D("Switch transport success\n"); + return 0; +} + +int adb_status(int fd) +{ + unsigned char buf[5]; + unsigned len; + + if(readx(fd, buf, 4)) { + strcpy(__adb_error, "protocol fault (no status)"); + return -1; + } + + if(!memcmp(buf, "OKAY", 4)) { + return 0; + } + + if(memcmp(buf, "FAIL", 4)) { + sprintf(__adb_error, + "protocol fault (status %02x %02x %02x %02x?!)", + buf[0], buf[1], buf[2], buf[3]); + return -1; + } + + if(readx(fd, buf, 4)) { + strcpy(__adb_error, "protocol fault (status len)"); + return -1; + } + buf[4] = 0; + len = strtoul((char*)buf, 0, 16); + if(len > 255) len = 255; + if(readx(fd, __adb_error, len)) { + strcpy(__adb_error, "protocol fault (status read)"); + return -1; + } + __adb_error[len] = 0; + return -1; +} + +int _adb_connect(const char *service) +{ + char tmp[5]; + int len; + int fd; + + D("_adb_connect: %s\n", service); + len = strlen(service); + if((len < 1) || (len > 1024)) { + strcpy(__adb_error, "service name too long"); + return -1; + } + snprintf(tmp, sizeof tmp, "%04x", len); + + fd = socket_loopback_client(ADB_PORT, SOCK_STREAM); + if(fd < 0) { + strcpy(__adb_error, "cannot connect to daemon"); + return -2; + } + + if (memcmp(service,"host",4) != 0 && switch_socket_transport(fd)) { + return -1; + } + + if(writex(fd, tmp, 4) || writex(fd, service, len)) { + strcpy(__adb_error, "write failure during connection"); + adb_close(fd); + return -1; + } + + if(adb_status(fd)) { + adb_close(fd); + return -1; + } + + return fd; +} + +int adb_connect(const char *service) +{ + // first query the adb server's version + int fd = _adb_connect("host:version"); + + if(fd == -2) { + fprintf(stdout,"* daemon not running. starting it now *\n"); + start_server: + if(launch_server(0)) { + fprintf(stderr,"* failed to start daemon *\n"); + return -1; + } else { + fprintf(stdout,"* daemon started successfully *\n"); + } + /* give the server some time to start properly and detect devices */ + adb_sleep_ms(2000); + // fall through to _adb_connect + } else { + // if server was running, check its version to make sure it is not out of date + char buf[100]; + int n; + int version = ADB_SERVER_VERSION - 1; + + // if we have a file descriptor, then parse version result + if(fd >= 0) { + if(readx(fd, buf, 4)) goto error; + + buf[4] = 0; + n = strtoul(buf, 0, 16); + if(n > (int)sizeof(buf)) goto error; + if(readx(fd, buf, n)) goto error; + adb_close(fd); + + if (sscanf(buf, "%04x", &version) != 1) goto error; + } else { + // if fd is -1, then check for "unknown host service", + // which would indicate a version of adb that does not support the version command + if (strcmp(__adb_error, "unknown host service") != 0) + return fd; + } + + if(version != ADB_SERVER_VERSION) { + printf("adb server is out of date. killing...\n"); + fd = _adb_connect("host:kill"); + adb_close(fd); + + /* XXX can we better detect its death? */ + adb_sleep_ms(2000); + goto start_server; + } + } + + // if the command is start-server, we are done. + if (!strcmp(service, "host:start-server")) + return 0; + + fd = _adb_connect(service); + if(fd == -2) { + fprintf(stderr,"** daemon still not running"); + } + + return fd; +error: + adb_close(fd); + return -1; +} + + +int adb_command(const char *service) +{ + int fd = adb_connect(service); + if(fd < 0) { + return -1; + } + + if(adb_status(fd)) { + adb_close(fd); + return -1; + } + + return 0; +} + +char *adb_query(const char *service) +{ + char buf[5]; + unsigned n; + char *tmp; + + D("adb_query: %s\n", service); + int fd = adb_connect(service); + if(fd < 0) { + fprintf(stderr,"error: %s\n", __adb_error); + return 0; + } + + if(readx(fd, buf, 4)) goto oops; + + buf[4] = 0; + n = strtoul(buf, 0, 16); + if(n > 1024) goto oops; + + tmp = malloc(n + 1); + if(tmp == 0) goto oops; + + if(readx(fd, tmp, n) == 0) { + tmp[n] = 0; + adb_close(fd); + return tmp; + } + free(tmp); + +oops: + adb_close(fd); + return 0; +} + + diff --git a/adb/adb_client.h b/adb/adb_client.h new file mode 100644 index 0000000..8061579 --- /dev/null +++ b/adb/adb_client.h @@ -0,0 +1,49 @@ +#ifndef _ADB_CLIENT_H_ +#define _ADB_CLIENT_H_ + +#include "adb.h" + +/* connect to adb, connect to the named service, and return +** a valid fd for interacting with that service upon success +** or a negative number on failure +*/ +int adb_connect(const char *service); +int _adb_connect(const char *service); + +/* connect to adb, connect to the named service, return 0 if +** the connection succeeded AND the service returned OKAY +*/ +int adb_command(const char *service); + +/* connect to adb, connect to the named service, return +** a malloc'd string of its response upon success or NULL +** on failure. +*/ +char *adb_query(const char *service); + +/* Set the preferred transport to connect to. +*/ +void adb_set_transport(transport_type type, const char* serial); + +/* Return the console port of the currently connected emulator (if any) + * of -1 if there is no emulator, and -2 if there is more than one. + * assumes adb_set_transport() was alled previously... + */ +int adb_get_emulator_console_port(void); + +/* send commands to the current emulator instance. will fail if there + * is zero, or more than one emulator connected (or if you use -s <serial> + * with a <serial> that does not designate an emulator) + */ +int adb_send_emulator_command(int argc, char** argv); + +/* return verbose error string from last operation */ +const char *adb_error(void); + +/* read a standard adb status response (OKAY|FAIL) and +** return 0 in the event of OKAY, -1 in the event of FAIL +** or protocol error +*/ +int adb_status(int fd); + +#endif diff --git a/adb/commandline.c b/adb/commandline.c new file mode 100644 index 0000000..be596ce --- /dev/null +++ b/adb/commandline.c @@ -0,0 +1,1249 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <limits.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <assert.h> + +#include "sysdeps.h" + +#ifdef HAVE_TERMIO_H +#include <termios.h> +#endif + +#define TRACE_TAG TRACE_ADB +#include "adb.h" +#include "adb_client.h" +#include "file_sync_service.h" + +#ifdef SH_HISTORY +#include "shlist.h" +#include "history.h" +#endif + +enum { + IGNORE_DATA, + WIPE_DATA, + FLASH_DATA +}; + +static int do_cmd(transport_type ttype, char* serial, char *cmd, ...); + +void get_my_path(char s[PATH_MAX]); +int find_sync_dirs(const char *srcarg, + char **android_srcdir_out, char **data_srcdir_out); +int install_app(transport_type transport, char* serial, int argc, char** argv); +int uninstall_app(transport_type transport, char* serial, int argc, char** argv); + +static const char *gProductOutPath = NULL; + +static char *product_file(const char *extra) +{ + int n; + char *x; + + if (gProductOutPath == NULL) { + fprintf(stderr, "adb: Product directory not specified; " + "use -p or define ANDROID_PRODUCT_OUT\n"); + exit(1); + } + + n = strlen(gProductOutPath) + strlen(extra) + 2; + x = malloc(n); + if (x == 0) { + fprintf(stderr, "adb: Out of memory (product_file())\n"); + exit(1); + } + + snprintf(x, (size_t)n, "%s" OS_PATH_SEPARATOR_STR "%s", gProductOutPath, extra); + return x; +} + +void version(FILE * out) { + fprintf(out, "Android Debug Bridge version %d.%d.%d\n", + ADB_VERSION_MAJOR, ADB_VERSION_MINOR, ADB_SERVER_VERSION); +} + +void help() +{ + version(stderr); + + fprintf(stderr, + "\n" + " -d - directs command to the only connected USB device\n" + " returns an error if more than one USB device is present.\n" + " -e - directs command to the only running emulator.\n" + " returns an error if more than one emulator is running.\n" + " -s <serial number> - directs command to the USB device or emulator with\n" + " the given serial number\n" + " -p <product name or path> - simple product name like 'sooner', or\n" + " a relative/absolute path to a product\n" + " out directory like 'out/target/product/sooner'.\n" + " If -p is not specified, the ANDROID_PRODUCT_OUT\n" + " environment variable is used, which must\n" + " be an absolute path.\n" + " devices - list all connected devices\n" + "\n" + "device commands:\n" + " adb push <local> <remote> - copy file/dir to device\n" + " adb pull <remote> <local> - copy file/dir from device\n" + " adb sync [ <directory> ] - copy host->device only if changed\n" + " (see 'adb help all')\n" + " adb shell - run remote shell interactively\n" + " adb shell <command> - run remote shell command\n" + " adb emu <command> - run emulator console command\n" + " adb logcat [ <filter-spec> ] - View device log\n" + " adb forward <local> <remote> - forward socket connections\n" + " forward specs are one of: \n" + " tcp:<port>\n" + " localabstract:<unix domain socket name>\n" + " localreserved:<unix domain socket name>\n" + " localfilesystem:<unix domain socket name>\n" + " dev:<character device name>\n" + " jdwp:<process pid> (remote only)\n" + " adb jdwp - list PIDs of processes hosting a JDWP transport\n" + " adb install [-l] [-r] <file> - push this package file to the device and install it\n" + " ('-l' means forward-lock the app)\n" + " ('-r' means reinstall the app, keeping its data)\n" + " adb uninstall [-k] <package> - remove this app package from the device\n" + " ('-k' means keep the data and cache directories)\n" + " adb bugreport - return all information from the device\n" + " that should be included in a bug report.\n" + "\n" + " adb help - show this help message\n" + " adb version - show version num\n" + "\n" + "DATAOPTS:\n" + " (no option) - don't touch the data partition\n" + " -w - wipe the data partition\n" + " -d - flash the data partition\n" + "\n" + "scripting:\n" + " adb wait-for-device - block until device is online\n" + " adb start-server - ensure that there is a server running\n" + " adb kill-server - kill the server if it is running\n" + " adb get-state - prints: offline | bootloader | device\n" + " adb get-serialno - prints: <serial-number>\n" + " adb status-window - continuously print device status for a specified device\n" + " adb remount - remounts the /system partition on the device read-write\n" + "\n" + "networking:\n" + " adb ppp <tty> [parameters] - Run PPP over USB.\n" + " Note: you should not automatically start a PDP connection.\n" + " <tty> refers to the tty for PPP stream. Eg. dev:/dev/omap_csmi_tty1\n" + " [parameters] - Eg. defaultroute debug dump local notty usepeerdns\n" + "\n" + "adb sync notes: adb sync [ <directory> ]\n" + " <localdir> can be interpreted in several ways:\n" + "\n" + " - If <directory> is not specified, both /system and /data partitions will be updated.\n" + "\n" + " - If it is \"system\" or \"data\", only the corresponding partition\n" + " is updated.\n" + ); +} + +int usage() +{ + help(); + return 1; +} + +#ifdef HAVE_TERMIO_H +static struct termios tio_save; + +static void stdin_raw_init(int fd) +{ + struct termios tio; + + if(tcgetattr(fd, &tio)) return; + if(tcgetattr(fd, &tio_save)) return; + + tio.c_lflag = 0; /* disable CANON, ECHO*, etc */ + + /* no timeout but request at least one character per read */ + tio.c_cc[VTIME] = 0; + tio.c_cc[VMIN] = 1; + + tcsetattr(fd, TCSANOW, &tio); + tcflush(fd, TCIFLUSH); +} + +static void stdin_raw_restore(int fd) +{ + tcsetattr(fd, TCSANOW, &tio_save); + tcflush(fd, TCIFLUSH); +} +#endif + +static void read_and_dump(int fd) +{ + char buf[4096]; + int len; + + while(fd >= 0) { + len = adb_read(fd, buf, 4096); + if(len == 0) { + break; + } + + if(len < 0) { + if(errno == EINTR) continue; + break; + } + /* we want to output to stdout, so no adb_write here !! */ + unix_write(1, buf, len); + } +} + +#ifdef SH_HISTORY +int shItemCmp( void *val, void *idata ) +{ + return( (strcmp( val, idata ) == 0) ); +} +#endif + +static void *stdin_read_thread(void *x) +{ + int fd, fdi; + unsigned char buf[1024]; +#ifdef SH_HISTORY + unsigned char realbuf[1024], *buf_ptr; + SHLIST history; + SHLIST *item = &history; + int cmdlen = 0, ins_flag = 0; +#endif + int r, n; + int state = 0; + + int *fds = (int*) x; + fd = fds[0]; + fdi = fds[1]; + free(fds); + +#ifdef SH_HISTORY + shListInitList( &history ); +#endif + for(;;) { + /* fdi is really the client's stdin, so use read, not adb_read here */ + r = unix_read(fdi, buf, 1024); + if(r == 0) break; + if(r < 0) { + if(errno == EINTR) continue; + break; + } +#ifdef SH_HISTORY + if( (r == 3) && /* Arrow processing */ + (memcmp( (void *)buf, SH_ARROW_ANY, 2 ) == 0) ) { + switch( buf[2] ) { + case SH_ARROW_UP: + item = shListGetNextItem( &history, item ); + break; + case SH_ARROW_DOWN: + item = shListGetPrevItem( &history, item ); + break; + default: + item = NULL; + break; + } + memset( buf, SH_DEL_CHAR, cmdlen ); + if( item != NULL ) { + n = snprintf( (char *)(&buf[cmdlen]), sizeof buf - cmdlen, "%s", (char *)(item->data) ); + memcpy( realbuf, item->data, n ); + } + else { /* Clean buffer */ + item = &history; + n = 0; + } + r = n + cmdlen; + cmdlen = n; + ins_flag = 0; + if( r == 0 ) + continue; + } + else { +#endif + for(n = 0; n < r; n++){ + switch(buf[n]) { + case '\n': +#ifdef SH_HISTORY + if( ins_flag && (SH_BLANK_CHAR <= realbuf[0]) ) { + buf_ptr = malloc(cmdlen + 1); + if( buf_ptr != NULL ) { + memcpy( buf_ptr, realbuf, cmdlen ); + buf_ptr[cmdlen] = '\0'; + if( (item = shListFindItem( &history, (void *)buf_ptr, shItemCmp )) == NULL ) { + shListInsFirstItem( &history, (void *)buf_ptr ); + item = &history; + } + } + } + cmdlen = 0; + ins_flag = 0; +#endif + state = 1; + break; + case '\r': + state = 1; + break; + case '~': + if(state == 1) state++; + break; + case '.': + if(state == 2) { + fprintf(stderr,"\n* disconnect *\n"); + #ifdef HAVE_TERMIO_H + stdin_raw_restore(fdi); + #endif + exit(0); + } + default: +#ifdef SH_HISTORY + if( buf[n] == SH_DEL_CHAR ) { + if( cmdlen > 0 ) + cmdlen--; + } + else { + realbuf[cmdlen] = buf[n]; + cmdlen++; + } + ins_flag = 1; +#endif + state = 0; + } + } +#ifdef SH_HISTORY + } +#endif + r = adb_write(fd, buf, r); + if(r <= 0) { + break; + } + } +#ifdef SH_HISTORY + shListDelAllItems( &history, (shListFree)free ); +#endif + return 0; +} + +int interactive_shell(void) +{ + adb_thread_t thr; + int fdi, fd; + int *fds; + + fd = adb_connect("shell:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + fdi = 0; //dup(0); + + fds = malloc(sizeof(int) * 2); + fds[0] = fd; + fds[1] = fdi; + +#ifdef HAVE_TERMIO_H + stdin_raw_init(fdi); +#endif + adb_thread_create(&thr, stdin_read_thread, fds); + read_and_dump(fd); +#ifdef HAVE_TERMIO_H + stdin_raw_restore(fdi); +#endif + return 0; +} + + +static void format_host_command(char* buffer, size_t buflen, const char* command, transport_type ttype, const char* serial) +{ + if (serial) { + snprintf(buffer, buflen, "host-serial:%s:%s", serial, command); + } else { + const char* prefix = "host"; + if (ttype == kTransportUsb) + prefix = "host-usb"; + else if (ttype == kTransportLocal) + prefix = "host-local"; + + snprintf(buffer, buflen, "%s:%s", prefix, command); + } +} + +static void status_window(transport_type ttype, const char* serial) +{ + char command[4096]; + char *state = 0; + char *laststate = 0; + + /* silence stderr */ +#ifdef _WIN32 + /* XXX: TODO */ +#else + int fd; + fd = unix_open("/dev/null", O_WRONLY); + dup2(fd, 2); + adb_close(fd); +#endif + + format_host_command(command, sizeof command, "get-state", ttype, serial); + + for(;;) { + adb_sleep_ms(250); + + if(state) { + free(state); + state = 0; + } + + state = adb_query(command); + + if(state) { + if(laststate && !strcmp(state,laststate)){ + continue; + } else { + if(laststate) free(laststate); + laststate = strdup(state); + } + } + + printf("%c[2J%c[2H", 27, 27); + printf("Android Debug Bridge\n"); + printf("State: %s\n", state ? state : "offline"); + fflush(stdout); + } +} + +/** duplicate string and quote all \ " ( ) chars + space character. */ +static char * +dupAndQuote(const char *s) +{ + const char *ts; + size_t alloc_len; + char *ret; + char *dest; + + ts = s; + + alloc_len = 0; + + for( ;*ts != '\0'; ts++) { + alloc_len++; + if (*ts == ' ' || *ts == '"' || *ts == '\\' || *ts == '(' || *ts == ')') { + alloc_len++; + } + } + + ret = (char *)malloc(alloc_len + 1); + + ts = s; + dest = ret; + + for ( ;*ts != '\0'; ts++) { + if (*ts == ' ' || *ts == '"' || *ts == '\\' || *ts == '(' || *ts == ')') { + *dest++ = '\\'; + } + + *dest++ = *ts; + } + + *dest++ = '\0'; + + return ret; +} + +/** + * Run ppp in "notty" mode against a resource listed as the first parameter + * eg: + * + * ppp dev:/dev/omap_csmi_tty0 <ppp options> + * + */ +int ppp(int argc, char **argv) +{ +#ifdef HAVE_WIN32_PROC + fprintf(stderr, "error: adb %s not implemented on Win32\n", argv[0]); + return -1; +#else + char *adb_service_name; + pid_t pid; + int fd; + + if (argc < 2) { + fprintf(stderr, "usage: adb %s <adb service name> [ppp opts]\n", + argv[0]); + + return 1; + } + + adb_service_name = argv[1]; + + fd = adb_connect(adb_service_name); + + if(fd < 0) { + fprintf(stderr,"Error: Could not open adb service: %s. Error: %s\n", + adb_service_name, adb_error()); + return 1; + } + + pid = fork(); + + if (pid < 0) { + perror("from fork()"); + return 1; + } else if (pid == 0) { + int err; + int i; + const char **ppp_args; + + // copy args + ppp_args = (const char **) alloca(sizeof(char *) * argc + 1); + ppp_args[0] = "pppd"; + for (i = 2 ; i < argc ; i++) { + //argv[2] and beyond become ppp_args[1] and beyond + ppp_args[i - 1] = argv[i]; + } + ppp_args[i-1] = NULL; + + // child side + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + adb_close(STDERR_FILENO); + adb_close(fd); + + err = execvp("pppd", (char * const *)ppp_args); + + if (err < 0) { + perror("execing pppd"); + } + exit(-1); + } else { + // parent side + + adb_close(fd); + return 0; + } +#endif /* !HAVE_WIN32_PROC */ +} + +static int send_shellcommand(transport_type transport, char* serial, char* buf) +{ + int fd, ret; + + for(;;) { + fd = adb_connect(buf); + if(fd >= 0) + break; + fprintf(stderr,"- waiting for device -\n"); + adb_sleep_ms(1000); + do_cmd(transport, serial, "wait-for-device", 0); + } + + read_and_dump(fd); + ret = adb_close(fd); + if (ret) + perror("close"); + + return ret; +} + +static int logcat(transport_type transport, char* serial, int argc, char **argv) +{ + char buf[4096]; + + char *log_tags; + char *quoted_log_tags; + + log_tags = getenv("ANDROID_LOG_TAGS"); + quoted_log_tags = dupAndQuote(log_tags == NULL ? "" : log_tags); + + snprintf(buf, sizeof(buf), + "shell:export ANDROID_LOG_TAGS=\"\%s\" ; exec logcat", + quoted_log_tags); + + free(quoted_log_tags); + + argc -= 1; + argv += 1; + while(argc-- > 0) { + char *quoted; + + quoted = dupAndQuote (*argv++); + + strncat(buf, " ", sizeof(buf)-1); + strncat(buf, quoted, sizeof(buf)-1); + free(quoted); + } + + send_shellcommand(transport, serial, buf); + return 0; +} + +#define SENTINEL_FILE "config" OS_PATH_SEPARATOR_STR "envsetup.make" +static int top_works(const char *top) +{ + if (top != NULL && adb_is_absolute_host_path(top)) { + char path_buf[PATH_MAX]; + snprintf(path_buf, sizeof(path_buf), + "%s" OS_PATH_SEPARATOR_STR SENTINEL_FILE, top); + return access(path_buf, F_OK) == 0; + } + return 0; +} + +static char *find_top_from(const char *indir, char path_buf[PATH_MAX]) +{ + strcpy(path_buf, indir); + while (1) { + if (top_works(path_buf)) { + return path_buf; + } + char *s = adb_dirstop(path_buf); + if (s != NULL) { + *s = '\0'; + } else { + path_buf[0] = '\0'; + return NULL; + } + } +} + +static char *find_top(char path_buf[PATH_MAX]) +{ + char *top = getenv("ANDROID_BUILD_TOP"); + if (top != NULL && top[0] != '\0') { + if (!top_works(top)) { + fprintf(stderr, "adb: bad ANDROID_BUILD_TOP value \"%s\"\n", top); + return NULL; + } + } else { + top = getenv("TOP"); + if (top != NULL && top[0] != '\0') { + if (!top_works(top)) { + fprintf(stderr, "adb: bad TOP value \"%s\"\n", top); + return NULL; + } + } else { + top = NULL; + } + } + + if (top != NULL) { + /* The environment pointed to a top directory that works. + */ + strcpy(path_buf, top); + return path_buf; + } + + /* The environment didn't help. Walk up the tree from the CWD + * to see if we can find the top. + */ + char dir[PATH_MAX]; + top = find_top_from(getcwd(dir, sizeof(dir)), path_buf); + if (top == NULL) { + /* If the CWD isn't under a good-looking top, see if the + * executable is. + */ + get_my_path(dir); + top = find_top_from(dir, path_buf); + } + return top; +} + +/* <hint> may be: + * - A simple product name + * e.g., "sooner" +TODO: debug? sooner-debug, sooner:debug? + * - A relative path from the CWD to the ANDROID_PRODUCT_OUT dir + * e.g., "out/target/product/sooner" + * - An absolute path to the PRODUCT_OUT dir + * e.g., "/src/device/out/target/product/sooner" + * + * Given <hint>, try to construct an absolute path to the + * ANDROID_PRODUCT_OUT dir. + */ +static const char *find_product_out_path(const char *hint) +{ + static char path_buf[PATH_MAX]; + + if (hint == NULL || hint[0] == '\0') { + return NULL; + } + + /* If it's already absolute, don't bother doing any work. + */ + if (adb_is_absolute_host_path(hint)) { + strcpy(path_buf, hint); + return path_buf; + } + + /* If there are any slashes in it, assume it's a relative path; + * make it absolute. + */ + if (adb_dirstart(hint) != NULL) { + if (getcwd(path_buf, sizeof(path_buf)) == NULL) { + fprintf(stderr, "adb: Couldn't get CWD: %s\n", strerror(errno)); + return NULL; + } + if (strlen(path_buf) + 1 + strlen(hint) >= sizeof(path_buf)) { + fprintf(stderr, "adb: Couldn't assemble path\n"); + return NULL; + } + strcat(path_buf, OS_PATH_SEPARATOR_STR); + strcat(path_buf, hint); + return path_buf; + } + + /* It's a string without any slashes. Try to do something with it. + * + * Try to find the root of the build tree, and build a PRODUCT_OUT + * path from there. + */ + char top_buf[PATH_MAX]; + const char *top = find_top(top_buf); + if (top == NULL) { + fprintf(stderr, "adb: Couldn't find top of build tree\n"); + return NULL; + } +//TODO: if we have a way to indicate debug, look in out/debug/target/... + snprintf(path_buf, sizeof(path_buf), + "%s" OS_PATH_SEPARATOR_STR + "out" OS_PATH_SEPARATOR_STR + "target" OS_PATH_SEPARATOR_STR + "product" OS_PATH_SEPARATOR_STR + "%s", top_buf, hint); + if (access(path_buf, F_OK) < 0) { + fprintf(stderr, "adb: Couldn't find a product dir " + "based on \"-p %s\"; \"%s\" doesn't exist\n", hint, path_buf); + return NULL; + } + return path_buf; +} + +int adb_commandline(int argc, char **argv) +{ + char buf[4096]; + int no_daemon = 0; + int is_daemon = 0; + int persist = 0; + int r; + int quote; + transport_type ttype = kTransportAny; + char* serial = NULL; + + /* If defined, this should be an absolute path to + * the directory containing all of the various system images + * for a particular product. If not defined, and the adb + * command requires this information, then the user must + * specify the path using "-p". + */ + gProductOutPath = getenv("ANDROID_PRODUCT_OUT"); + if (gProductOutPath == NULL || gProductOutPath[0] == '\0') { + gProductOutPath = NULL; + } + // TODO: also try TARGET_PRODUCT/TARGET_DEVICE as a hint + + /* modifiers and flags */ + while(argc > 0) { + if(!strcmp(argv[0],"nodaemon")) { + no_daemon = 1; + } else if (!strcmp(argv[0], "fork-server")) { + /* this is a special flag used only when the ADB client launches the ADB Server */ + is_daemon = 1; + } else if(!strcmp(argv[0],"persist")) { + persist = 1; + } else if(!strncmp(argv[0], "-p", 2)) { + const char *product = NULL; + if (argv[0][2] == '\0') { + if (argc < 2) return usage(); + product = argv[1]; + argc--; + argv++; + } else { + product = argv[1] + 2; + } + gProductOutPath = find_product_out_path(product); + if (gProductOutPath == NULL) { + fprintf(stderr, "adb: could not resolve \"-p %s\"\n", + product); + return usage(); + } + } else if (argv[0][0]=='-' && argv[0][1]=='s') { + if (isdigit(argv[0][2])) { + serial = argv[0] + 2; + } else { + if(argc < 2) return usage(); + serial = argv[1]; + argc--; + argv++; + } + } else if (!strcmp(argv[0],"-d")) { + ttype = kTransportUsb; + } else if (!strcmp(argv[0],"-e")) { + ttype = kTransportLocal; + } else { + /* out of recognized modifiers and flags */ + break; + } + argc--; + argv++; + } + + adb_set_transport(ttype, serial); + + if ((argc > 0) && (!strcmp(argv[0],"server"))) { + if (no_daemon || is_daemon) { + r = adb_main(is_daemon); + } else { + r = launch_server(); + } + if(r) { + fprintf(stderr,"* could not start server *\n"); + } + return r; + } + +top: + if(argc == 0) { + return usage(); + } + + /* adb_connect() commands */ + + if(!strcmp(argv[0], "devices")) { + char *tmp; + snprintf(buf, sizeof buf, "host:%s", argv[0]); + tmp = adb_query(buf); + if(tmp) { + printf("List of devices attached \n"); + printf("%s\n", tmp); + return 0; + } else { + return 1; + } + } + + if (!strcmp(argv[0], "emu")) { + return adb_send_emulator_command(argc, argv); + } + + if(!strcmp(argv[0], "shell")) { + int r; + int fd; + + if(argc < 2) { + return interactive_shell(); + } + + snprintf(buf, sizeof buf, "shell:%s", argv[1]); + argc -= 2; + argv += 2; + while(argc-- > 0) { + strcat(buf, " "); + + /* quote empty strings and strings with spaces */ + quote = (**argv == 0 || strchr(*argv, ' ')); + if (quote) + strcat(buf, "\""); + strcat(buf, *argv++); + if (quote) + strcat(buf, "\""); + } + + for(;;) { + fd = adb_connect(buf); + if(fd >= 0) { + read_and_dump(fd); + adb_close(fd); + r = 0; + } else { + fprintf(stderr,"error: %s\n", adb_error()); + r = -1; + } + + if(persist) { + fprintf(stderr,"\n- waiting for device -\n"); + adb_sleep_ms(1000); + do_cmd(ttype, serial, "wait-for-device", 0); + } else { + return r; + } + } + } + + if(!strcmp(argv[0], "kill-server")) { + int fd; + fd = _adb_connect("host:kill"); + if(fd == -1) { + fprintf(stderr,"* server not running *\n"); + return 1; + } + return 0; + } + + if(!strcmp(argv[0], "remount")) { + int fd = adb_connect("remount:"); + if(fd >= 0) { + read_and_dump(fd); + adb_close(fd); + return 0; + } + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(!strcmp(argv[0], "bugreport")) { + if (argc != 1) { + return 1; + } + do_cmd(ttype, serial, "shell", "dumpstate", "-", 0); + return 0; + } + + /* adb_command() wrapper commands */ + + if(!strncmp(argv[0], "wait-for-", strlen("wait-for-"))) { + char* service = argv[0]; + if (!strncmp(service, "wait-for-device", strlen("wait-for-device"))) { + if (ttype == kTransportUsb) { + service = "wait-for-usb"; + } else if (ttype == kTransportLocal) { + service = "wait-for-local"; + } else { + service = "wait-for-any"; + } + } + + format_host_command(buf, sizeof buf, service, ttype, serial); + + if (adb_command(buf)) { + D("failure: %s *\n",adb_error()); + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + /* Allow a command to be run after wait-for-device, + * e.g. 'adb wait-for-device shell'. + */ + if(argc > 1) { + argc--; + argv++; + goto top; + } + return 0; + } + + if(!strcmp(argv[0], "forward")) { + if(argc != 3) return usage(); + if (serial) { + snprintf(buf, sizeof buf, "host-serial:%s:forward:%s;%s",serial,argv[1],argv[2]); + } else { + snprintf(buf, sizeof buf, "host:forward:%s;%s",argv[1],argv[2]); + } + if(adb_command(buf)) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + return 0; + } + + /* do_sync_*() commands */ + + if(!strcmp(argv[0], "ls")) { + if(argc != 2) return usage(); + return do_sync_ls(argv[1]); + } + + if(!strcmp(argv[0], "push")) { + if(argc != 3) return usage(); + return do_sync_push(argv[1], argv[2], 0 /* no verify APK */); + } + + if(!strcmp(argv[0], "pull")) { + if(argc != 3) return usage(); + return do_sync_pull(argv[1], argv[2]); + } + + if(!strcmp(argv[0], "install")) { + if (argc < 2) return usage(); + return install_app(ttype, serial, argc, argv); + } + + if(!strcmp(argv[0], "uninstall")) { + if (argc < 2) return usage(); + return uninstall_app(ttype, serial, argc, argv); + } + + if(!strcmp(argv[0], "sync")) { + char *srcarg, *android_srcpath, *data_srcpath; + int ret; + if(argc < 2) { + /* No local path was specified. */ + srcarg = NULL; + } else if(argc == 2) { + /* A local path or "android"/"data" arg was specified. */ + srcarg = argv[1]; + } else { + return usage(); + } + ret = find_sync_dirs(srcarg, &android_srcpath, &data_srcpath); + if(ret != 0) return usage(); + + if(android_srcpath != NULL) + ret = do_sync_sync(android_srcpath, "/system"); + if(ret == 0 && data_srcpath != NULL) + ret = do_sync_sync(data_srcpath, "/data"); + + free(android_srcpath); + free(data_srcpath); + return ret; + } + + /* passthrough commands */ + + if(!strcmp(argv[0],"get-state") || + !strcmp(argv[0],"get-serialno")) + { + char *tmp; + + format_host_command(buf, sizeof buf, argv[0], ttype, serial); + tmp = adb_query(buf); + if(tmp) { + printf("%s\n", tmp); + return 0; + } else { + return 1; + } + } + + /* other commands */ + + if(!strcmp(argv[0],"status-window")) { + status_window(ttype, serial); + return 0; + } + + if(!strcmp(argv[0],"logcat") || !strcmp(argv[0],"lolcat")) { + return logcat(ttype, serial, argc, argv); + } + + if(!strcmp(argv[0],"ppp")) { + return ppp(argc, argv); + } + + if (!strcmp(argv[0], "start-server")) { + return adb_connect("host:start-server"); + } + + if (!strcmp(argv[0], "jdwp")) { + int fd = adb_connect("jdwp"); + if (fd >= 0) { + read_and_dump(fd); + adb_close(fd); + return 0; + } else { + fprintf(stderr, "error: %s\n", adb_error()); + return -1; + } + } + + /* "adb /?" is a common idiom under Windows */ + if(!strcmp(argv[0], "help") || !strcmp(argv[0], "/?")) { + help(); + return 0; + } + + if(!strcmp(argv[0], "version")) { + version(stdout); + return 0; + } + + usage(); + return 1; +} + +static int do_cmd(transport_type ttype, char* serial, char *cmd, ...) +{ + char *argv[16]; + int argc; + va_list ap; + + va_start(ap, cmd); + argc = 0; + + if (serial) { + argv[argc++] = "-s"; + argv[argc++] = serial; + } else if (ttype == kTransportUsb) { + argv[argc++] = "-d"; + } else if (ttype == kTransportLocal) { + argv[argc++] = "-e"; + } + + argv[argc++] = cmd; + while((argv[argc] = va_arg(ap, char*)) != 0) argc++; + va_end(ap); + +#if 0 + int n; + fprintf(stderr,"argc = %d\n",argc); + for(n = 0; n < argc; n++) { + fprintf(stderr,"argv[%d] = \"%s\"\n", n, argv[n]); + } +#endif + + return adb_commandline(argc, argv); +} + +int find_sync_dirs(const char *srcarg, + char **android_srcdir_out, char **data_srcdir_out) +{ + char *android_srcdir, *data_srcdir; + + if(srcarg == NULL) { + android_srcdir = product_file("system"); + data_srcdir = product_file("data"); + } else { + /* srcarg may be "data", "system" or NULL. + * if srcarg is NULL, then both data and system are synced + */ + if(strcmp(srcarg, "system") == 0) { + android_srcdir = product_file("system"); + data_srcdir = NULL; + } else if(strcmp(srcarg, "data") == 0) { + android_srcdir = NULL; + data_srcdir = product_file("data"); + } else { + /* It's not "system" or "data". + */ + return 1; + } + } + + if(android_srcdir_out != NULL) + *android_srcdir_out = android_srcdir; + else + free(android_srcdir); + + if(data_srcdir_out != NULL) + *data_srcdir_out = data_srcdir; + else + free(data_srcdir); + + return 0; +} + +static int pm_command(transport_type transport, char* serial, + int argc, char** argv) +{ + char buf[4096]; + + snprintf(buf, sizeof(buf), "shell:pm"); + + while(argc-- > 0) { + char *quoted; + + quoted = dupAndQuote(*argv++); + + strncat(buf, " ", sizeof(buf)-1); + strncat(buf, quoted, sizeof(buf)-1); + free(quoted); + } + + send_shellcommand(transport, serial, buf); + return 0; +} + +int uninstall_app(transport_type transport, char* serial, int argc, char** argv) +{ + /* if the user choose the -k option, we refuse to do it until devices are + out with the option to uninstall the remaining data somehow (adb/ui) */ + if (argc == 3 && strcmp(argv[1], "-k") == 0) + { + printf( + "The -k option uninstalls the application while retaining the data/cache.\n" + "At the moment, there is no way to remove the remaining data.\n" + "You will have to reinstall the application with the same signature, and fully uninstall it.\n" + "If you truly wish to continue, execute 'adb shell pm uninstall -k %s'\n", argv[2]); + return -1; + } + + /* 'adb uninstall' takes the same arguments as 'pm uninstall' on device */ + return pm_command(transport, serial, argc, argv); +} + +static int delete_file(transport_type transport, char* serial, char* filename) +{ + char buf[4096]; + char* quoted; + + snprintf(buf, sizeof(buf), "shell:rm "); + quoted = dupAndQuote(filename); + strncat(buf, quoted, sizeof(buf)-1); + free(quoted); + + send_shellcommand(transport, serial, buf); + return 0; +} + +int install_app(transport_type transport, char* serial, int argc, char** argv) +{ + struct stat st; + int err; + const char *const WHERE = "/data/local/tmp/%s"; + char to[PATH_MAX]; + char* filename = argv[argc - 1]; + const char* p; + + p = adb_dirstop(filename); + if (p) { + p++; + snprintf(to, sizeof to, WHERE, p); + } else { + snprintf(to, sizeof to, WHERE, filename); + } + if (p[0] == '\0') { + } + + err = stat(filename, &st); + if (err != 0) { + fprintf(stderr, "can't find '%s' to install\n", filename); + return 1; + } + if (!S_ISREG(st.st_mode)) { + fprintf(stderr, "can't install '%s' because it's not a file\n", + filename); + return 1; + } + + if (!(err = do_sync_push(filename, to, 1 /* verify APK */))) { + /* file in place; tell the Package Manager to install it */ + argv[argc - 1] = to; /* destination name, not source location */ + pm_command(transport, serial, argc, argv); + delete_file(transport, serial, to); + } + + return err; +} diff --git a/adb/console.c b/adb/console.c new file mode 100644 index 0000000..b813d33 --- /dev/null +++ b/adb/console.c @@ -0,0 +1,45 @@ +#include "sysdeps.h" +#include "adb.h" +#include "adb_client.h" +#include <stdio.h> + +static int connect_to_console(void) +{ + int fd, port; + + port = adb_get_emulator_console_port(); + if (port < 0) { + if (port == -2) + fprintf(stderr, "error: more than one emulator detected. use -s option\n"); + else + fprintf(stderr, "error: no emulator detected\n"); + return -1; + } + fd = socket_loopback_client( port, SOCK_STREAM ); + if (fd < 0) { + fprintf(stderr, "error: could not connect to TCP port %d\n", port); + return -1; + } + return fd; +} + + +int adb_send_emulator_command(int argc, char** argv) +{ + int fd, nn; + + fd = connect_to_console(); + if (fd < 0) + return 1; + +#define QUIT "quit\n" + + for (nn = 1; nn < argc; nn++) { + adb_write( fd, argv[nn], strlen(argv[nn]) ); + adb_write( fd, (nn == argc-1) ? "\n" : " ", 1 ); + } + adb_write( fd, QUIT, sizeof(QUIT)-1 ); + adb_close(fd); + + return 0; +} diff --git a/adb/file_sync_client.c b/adb/file_sync_client.c new file mode 100644 index 0000000..4e6d385 --- /dev/null +++ b/adb/file_sync_client.c @@ -0,0 +1,1022 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> +#include <dirent.h> +#include <limits.h> +#include <sys/types.h> +#include <zipfile/zipfile.h> + +#include "sysdeps.h" +#include "adb.h" +#include "adb_client.h" +#include "file_sync_service.h" + + +static unsigned total_bytes; +static long long start_time; + +static long long NOW() +{ + struct timeval tv; + gettimeofday(&tv, 0); + return ((long long) tv.tv_usec) + + 1000000LL * ((long long) tv.tv_sec); +} + +static void BEGIN() +{ + total_bytes = 0; + start_time = NOW(); +} + +static void END() +{ + long long t = NOW() - start_time; + if(total_bytes == 0) return; + + if (t == 0) /* prevent division by 0 :-) */ + t = 1000000; + + fprintf(stderr,"%lld KB/s (%d bytes in %lld.%03llds)\n", + ((((long long) total_bytes) * 1000000LL) / t) / 1024LL, + total_bytes, (t / 1000000LL), (t % 1000000LL) / 1000LL); +} + +void sync_quit(int fd) +{ + syncmsg msg; + + msg.req.id = ID_QUIT; + msg.req.namelen = 0; + + writex(fd, &msg.req, sizeof(msg.req)); +} + +typedef void (*sync_ls_cb)(unsigned mode, unsigned size, unsigned time, const char *name, void *cookie); + +int sync_ls(int fd, const char *path, sync_ls_cb func, void *cookie) +{ + syncmsg msg; + char buf[257]; + int len; + + len = strlen(path); + if(len > 1024) goto fail; + + msg.req.id = ID_LIST; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + goto fail; + } + + for(;;) { + if(readx(fd, &msg.dent, sizeof(msg.dent))) break; + if(msg.dent.id == ID_DONE) return 0; + if(msg.dent.id != ID_DENT) break; + + len = ltohl(msg.dent.namelen); + if(len > 256) break; + + if(readx(fd, buf, len)) break; + buf[len] = 0; + + func(ltohl(msg.dent.mode), + ltohl(msg.dent.size), + ltohl(msg.dent.time), + buf, cookie); + } + +fail: + adb_close(fd); + return -1; +} + +typedef struct syncsendbuf syncsendbuf; + +struct syncsendbuf { + unsigned id; + unsigned size; + char data[SYNC_DATA_MAX]; +}; + +static syncsendbuf send_buffer; + +int sync_readtime(int fd, const char *path, unsigned *timestamp) +{ + syncmsg msg; + int len = strlen(path); + + msg.req.id = ID_STAT; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + return -1; + } + + if(readx(fd, &msg.stat, sizeof(msg.stat))) { + return -1; + } + + if(msg.stat.id != ID_STAT) { + return -1; + } + + *timestamp = ltohl(msg.stat.time); + return 0; +} + +static int sync_start_readtime(int fd, const char *path) +{ + syncmsg msg; + int len = strlen(path); + + msg.req.id = ID_STAT; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + return -1; + } + + return 0; +} + +static int sync_finish_readtime(int fd, unsigned int *timestamp, + unsigned int *mode, unsigned int *size) +{ + syncmsg msg; + + if(readx(fd, &msg.stat, sizeof(msg.stat))) + return -1; + + if(msg.stat.id != ID_STAT) + return -1; + + *timestamp = ltohl(msg.stat.time); + *mode = ltohl(msg.stat.mode); + *size = ltohl(msg.stat.size); + + return 0; +} + +int sync_readmode(int fd, const char *path, unsigned *mode) +{ + syncmsg msg; + int len = strlen(path); + + msg.req.id = ID_STAT; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + return -1; + } + + if(readx(fd, &msg.stat, sizeof(msg.stat))) { + return -1; + } + + if(msg.stat.id != ID_STAT) { + return -1; + } + + *mode = ltohl(msg.stat.mode); + return 0; +} + +static int write_data_file(int fd, const char *path, syncsendbuf *sbuf) +{ + int lfd, err = 0; + + lfd = adb_open(path, O_RDONLY); + if(lfd < 0) { + fprintf(stderr,"cannot open '%s': %s\n", path, strerror(errno)); + return -1; + } + + sbuf->id = ID_DATA; + for(;;) { + int ret; + + ret = adb_read(lfd, sbuf->data, SYNC_DATA_MAX); + if(!ret) + break; + + if(ret < 0) { + if(errno == EINTR) + continue; + fprintf(stderr,"cannot read '%s': %s\n", path, strerror(errno)); + break; + } + + sbuf->size = htoll(ret); + if(writex(fd, sbuf, sizeof(unsigned) * 2 + ret)){ + err = -1; + break; + } + total_bytes += ret; + } + + adb_close(lfd); + return err; +} + +static int write_data_buffer(int fd, char* file_buffer, int size, syncsendbuf *sbuf) +{ + int err = 0; + int total = 0; + + sbuf->id = ID_DATA; + while (total < size) { + int count = size - total; + if (count > SYNC_DATA_MAX) { + count = SYNC_DATA_MAX; + } + + memcpy(sbuf->data, &file_buffer[total], count); + sbuf->size = htoll(count); + if(writex(fd, sbuf, sizeof(unsigned) * 2 + count)){ + err = -1; + break; + } + total += count; + total_bytes += count; + } + + return err; +} + +#ifdef HAVE_SYMLINKS +static int write_data_link(int fd, const char *path, syncsendbuf *sbuf) +{ + int len, ret; + + len = readlink(path, sbuf->data, SYNC_DATA_MAX-1); + if(len < 0) { + fprintf(stderr, "error reading link '%s': %s\n", path, strerror(errno)); + return -1; + } + sbuf->data[len] = '\0'; + + sbuf->size = htoll(len + 1); + sbuf->id = ID_DATA; + + ret = writex(fd, sbuf, sizeof(unsigned) * 2 + len + 1); + if(ret) + return -1; + + total_bytes += len + 1; + + return 0; +} +#endif + +static int sync_send(int fd, const char *lpath, const char *rpath, + unsigned mtime, mode_t mode, int verifyApk) +{ + syncmsg msg; + int len, r; + syncsendbuf *sbuf = &send_buffer; + char* file_buffer = NULL; + int size = 0; + char tmp[64]; + + len = strlen(rpath); + if(len > 1024) goto fail; + + snprintf(tmp, sizeof(tmp), ",%d", mode); + r = strlen(tmp); + + if (verifyApk) { + int lfd; + zipfile_t zip; + zipentry_t entry; + int amt; + + // if we are transferring an APK file, then sanity check to make sure + // we have a real zip file that contains an AndroidManifest.xml + // this requires that we read the entire file into memory. + lfd = adb_open(lpath, O_RDONLY); + if(lfd < 0) { + fprintf(stderr,"cannot open '%s': %s\n", lpath, strerror(errno)); + return -1; + } + + size = adb_lseek(lfd, 0, SEEK_END); + if (size == -1 || -1 == adb_lseek(lfd, 0, SEEK_SET)) { + fprintf(stderr, "error seeking in file '%s'\n", lpath); + adb_close(lfd); + return 1; + } + + file_buffer = (char *)malloc(size); + if (file_buffer == NULL) { + fprintf(stderr, "could not allocate buffer for '%s'\n", + lpath); + adb_close(lfd); + return 1; + } + amt = adb_read(lfd, file_buffer, size); + if (amt != size) { + fprintf(stderr, "error reading from file: '%s'\n", lpath); + adb_close(lfd); + free(file_buffer); + return 1; + } + + adb_close(lfd); + + zip = init_zipfile(file_buffer, size); + if (zip == NULL) { + fprintf(stderr, "file '%s' is not a valid zip file\n", + lpath); + free(file_buffer); + return 1; + } + + entry = lookup_zipentry(zip, "AndroidManifest.xml"); + release_zipfile(zip); + if (entry == NULL) { + fprintf(stderr, "file '%s' does not contain AndroidManifest.xml\n", + lpath); + free(file_buffer); + return 1; + } + } + + msg.req.id = ID_SEND; + msg.req.namelen = htoll(len + r); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, rpath, len) || writex(fd, tmp, r)) { + free(file_buffer); + goto fail; + } + + if (file_buffer) { + write_data_buffer(fd, file_buffer, size, sbuf); + free(file_buffer); + } else if (S_ISREG(mode)) + write_data_file(fd, lpath, sbuf); +#ifdef HAVE_SYMLINKS + else if (S_ISLNK(mode)) + write_data_link(fd, lpath, sbuf); +#endif + else + goto fail; + + msg.data.id = ID_DONE; + msg.data.size = htoll(mtime); + if(writex(fd, &msg.data, sizeof(msg.data))) + goto fail; + + if(readx(fd, &msg.status, sizeof(msg.status))) + return -1; + + if(msg.status.id != ID_OKAY) { + if(msg.status.id == ID_FAIL) { + len = ltohl(msg.status.msglen); + if(len > 256) len = 256; + if(readx(fd, sbuf->data, len)) { + return -1; + } + sbuf->data[len] = 0; + } else + strcpy(sbuf->data, "unknown reason"); + + fprintf(stderr,"failed to copy '%s' to '%s': %s\n", lpath, rpath, sbuf->data); + return -1; + } + + return 0; + +fail: + fprintf(stderr,"protocol failure\n"); + adb_close(fd); + return -1; +} + +static int mkdirs(char *name) +{ + int ret; + char *x = name + 1; + + for(;;) { + x = adb_dirstart(x); + if(x == 0) return 0; + *x = 0; + ret = adb_mkdir(name, 0775); + *x = OS_PATH_SEPARATOR; + if((ret < 0) && (errno != EEXIST)) { + return ret; + } + x++; + } + return 0; +} + +int sync_recv(int fd, const char *rpath, const char *lpath) +{ + syncmsg msg; + int len; + int lfd = -1; + char *buffer = send_buffer.data; + unsigned id; + + len = strlen(rpath); + if(len > 1024) return -1; + + msg.req.id = ID_RECV; + msg.req.namelen = htoll(len); + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, rpath, len)) { + return -1; + } + + if(readx(fd, &msg.data, sizeof(msg.data))) { + return -1; + } + id = msg.data.id; + + if((id == ID_DATA) || (id == ID_DONE)) { + adb_unlink(lpath); + mkdirs((char *)lpath); + lfd = adb_creat(lpath, 0644); + if(lfd < 0) { + fprintf(stderr,"cannot create '%s': %s\n", lpath, strerror(errno)); + return -1; + } + goto handle_data; + } else { + goto remote_error; + } + + for(;;) { + if(readx(fd, &msg.data, sizeof(msg.data))) { + return -1; + } + id = msg.data.id; + + handle_data: + len = ltohl(msg.data.size); + if(id == ID_DONE) break; + if(id != ID_DATA) goto remote_error; + if(len > SYNC_DATA_MAX) { + fprintf(stderr,"data overrun\n"); + adb_close(lfd); + return -1; + } + + if(readx(fd, buffer, len)) { + adb_close(lfd); + return -1; + } + + if(writex(lfd, buffer, len)) { + fprintf(stderr,"cannot write '%s': %s\n", rpath, strerror(errno)); + adb_close(lfd); + return -1; + } + + total_bytes += len; + } + + adb_close(lfd); + return 0; + +remote_error: + adb_close(lfd); + adb_unlink(lpath); + + if(id == ID_FAIL) { + len = ltohl(msg.data.size); + if(len > 256) len = 256; + if(readx(fd, buffer, len)) { + return -1; + } + buffer[len] = 0; + } else { + memcpy(buffer, &id, 4); + buffer[4] = 0; +// strcpy(buffer,"unknown reason"); + } + fprintf(stderr,"failed to copy '%s' to '%s': %s\n", rpath, lpath, buffer); + return 0; +} + + + +/* --- */ + + +static void do_sync_ls_cb(unsigned mode, unsigned size, unsigned time, + const char *name, void *cookie) +{ + printf("%08x %08x %08x %s\n", mode, size, time, name); +} + +int do_sync_ls(const char *path) +{ + int fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(sync_ls(fd, path, do_sync_ls_cb, 0)) { + return 1; + } else { + sync_quit(fd); + return 0; + } +} + +typedef struct copyinfo copyinfo; + +struct copyinfo +{ + copyinfo *next; + const char *src; + const char *dst; + unsigned int time; + unsigned int mode; + unsigned int size; + int flag; + //char data[0]; +}; + +copyinfo *mkcopyinfo(const char *spath, const char *dpath, + const char *name, int isdir) +{ + int slen = strlen(spath); + int dlen = strlen(dpath); + int nlen = strlen(name); + int ssize = slen + nlen + 2; + int dsize = dlen + nlen + 2; + + copyinfo *ci = malloc(sizeof(copyinfo) + ssize + dsize); + if(ci == 0) { + fprintf(stderr,"out of memory\n"); + abort(); + } + + ci->next = 0; + ci->time = 0; + ci->mode = 0; + ci->size = 0; + ci->flag = 0; + ci->src = (const char*)(ci + 1); + ci->dst = ci->src + ssize; + snprintf((char*) ci->src, ssize, isdir ? "%s%s/" : "%s%s", spath, name); + snprintf((char*) ci->dst, dsize, isdir ? "%s%s/" : "%s%s", dpath, name); + +// fprintf(stderr,"mkcopyinfo('%s','%s')\n", ci->src, ci->dst); + return ci; +} + + +static int local_build_list(copyinfo **filelist, + const char *lpath, const char *rpath) +{ + DIR *d; + struct dirent *de; + struct stat st; + copyinfo *dirlist = 0; + copyinfo *ci, *next; + +// fprintf(stderr,"local_build_list('%s','%s')\n", lpath, rpath); + + d = opendir(lpath); + if(d == 0) { + fprintf(stderr,"cannot open '%s': %s\n", lpath, strerror(errno)); + return -1; + } + + while((de = readdir(d))) { + char stat_path[PATH_MAX]; + char *name = de->d_name; + + if(name[0] == '.') { + if(name[1] == 0) continue; + if((name[1] == '.') && (name[2] == 0)) continue; + } + + /* + * We could use d_type if HAVE_DIRENT_D_TYPE is defined, but reiserfs + * always returns DT_UNKNOWN, so we just use stat() for all cases. + */ + if (strlen(lpath) + strlen(de->d_name) + 1 > sizeof(stat_path)) + continue; + strcpy(stat_path, lpath); + strcat(stat_path, de->d_name); + stat(stat_path, &st); + + if (S_ISDIR(st.st_mode)) { + ci = mkcopyinfo(lpath, rpath, name, 1); + ci->next = dirlist; + dirlist = ci; + } else { + ci = mkcopyinfo(lpath, rpath, name, 0); + if(lstat(ci->src, &st)) { + closedir(d); + fprintf(stderr,"cannot stat '%s': %s\n", ci->src, strerror(errno)); + return -1; + } + if(!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { + fprintf(stderr, "skipping special file '%s'\n", ci->src); + free(ci); + } else { + ci->time = st.st_mtime; + ci->mode = st.st_mode; + ci->size = st.st_size; + ci->next = *filelist; + *filelist = ci; + } + } + } + + closedir(d); + + for(ci = dirlist; ci != 0; ci = next) { + next = ci->next; + local_build_list(filelist, ci->src, ci->dst); + free(ci); + } + + return 0; +} + + +static int copy_local_dir_remote(int fd, const char *lpath, const char *rpath, int checktimestamps) +{ + copyinfo *filelist = 0; + copyinfo *ci, *next; + int pushed = 0; + int skipped = 0; + + if((lpath[0] == 0) || (rpath[0] == 0)) return -1; + if(lpath[strlen(lpath) - 1] != '/') { + int tmplen = strlen(lpath)+2; + char *tmp = malloc(tmplen); + if(tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/",lpath); + lpath = tmp; + } + if(rpath[strlen(rpath) - 1] != '/') { + int tmplen = strlen(rpath)+2; + char *tmp = malloc(tmplen); + if(tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/",rpath); + rpath = tmp; + } + + if(local_build_list(&filelist, lpath, rpath)) { + return -1; + } + + if(checktimestamps){ + for(ci = filelist; ci != 0; ci = ci->next) { + if(sync_start_readtime(fd, ci->dst)) { + return 1; + } + } + for(ci = filelist; ci != 0; ci = ci->next) { + unsigned int timestamp, mode, size; + if(sync_finish_readtime(fd, ×tamp, &mode, &size)) + return 1; + if(size == ci->size) { + /* for links, we cannot update the atime/mtime */ + if((S_ISREG(ci->mode & mode) && timestamp == ci->time) || + (S_ISLNK(ci->mode & mode) && timestamp >= ci->time)) + ci->flag = 1; + } + } + } + for(ci = filelist; ci != 0; ci = next) { + next = ci->next; + if(ci->flag == 0) { + fprintf(stderr,"push: %s -> %s\n", ci->src, ci->dst); + if(sync_send(fd, ci->src, ci->dst, ci->time, ci->mode, 0 /* no verify APK */)){ + return 1; + } + pushed++; + } else { + skipped++; + } + free(ci); + } + + fprintf(stderr,"%d file%s pushed. %d file%s skipped.\n", + pushed, (pushed == 1) ? "" : "s", + skipped, (skipped == 1) ? "" : "s"); + + return 0; +} + + +int do_sync_push(const char *lpath, const char *rpath, int verifyApk) +{ + struct stat st; + unsigned mode; + int fd; + + fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(stat(lpath, &st)) { + fprintf(stderr,"cannot stat '%s': %s\n", lpath, strerror(errno)); + sync_quit(fd); + return 1; + } + + if(S_ISDIR(st.st_mode)) { + BEGIN(); + if(copy_local_dir_remote(fd, lpath, rpath, 0)) { + return 1; + } else { + END(); + sync_quit(fd); + } + } else { + if(sync_readmode(fd, rpath, &mode)) { + return 1; + } + if((mode != 0) && S_ISDIR(mode)) { + /* if we're copying a local file to a remote directory, + ** we *really* want to copy to remotedir + "/" + localfilename + */ + const char *name = adb_dirstop(lpath); + if(name == 0) { + name = lpath; + } else { + name++; + } + int tmplen = strlen(name) + strlen(rpath) + 2; + char *tmp = malloc(strlen(name) + strlen(rpath) + 2); + if(tmp == 0) return 1; + snprintf(tmp, tmplen, "%s/%s", rpath, name); + rpath = tmp; + } + BEGIN(); + if(sync_send(fd, lpath, rpath, st.st_mtime, st.st_mode, verifyApk)) { + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } + } + + return 0; +} + + +typedef struct { + copyinfo **filelist; + copyinfo **dirlist; + const char *rpath; + const char *lpath; +} sync_ls_build_list_cb_args; + +void +sync_ls_build_list_cb(unsigned mode, unsigned size, unsigned time, + const char *name, void *cookie) +{ + sync_ls_build_list_cb_args *args = (sync_ls_build_list_cb_args *)cookie; + copyinfo *ci; + + if (S_ISDIR(mode)) { + copyinfo **dirlist = args->dirlist; + + /* Don't try recursing down "." or ".." */ + if (name[0] == '.') { + if (name[1] == '\0') return; + if ((name[1] == '.') && (name[2] == '\0')) return; + } + + ci = mkcopyinfo(args->rpath, args->lpath, name, 1); + ci->next = *dirlist; + *dirlist = ci; + } else if (S_ISREG(mode) || S_ISLNK(mode)) { + copyinfo **filelist = args->filelist; + + ci = mkcopyinfo(args->rpath, args->lpath, name, 0); + ci->time = time; + ci->mode = mode; + ci->size = size; + ci->next = *filelist; + *filelist = ci; + } else { + fprintf(stderr, "skipping special file '%s'\n", name); + } +} + +static int remote_build_list(int syncfd, copyinfo **filelist, + const char *rpath, const char *lpath) +{ + copyinfo *dirlist = NULL; + sync_ls_build_list_cb_args args; + + args.filelist = filelist; + args.dirlist = &dirlist; + args.rpath = rpath; + args.lpath = lpath; + + /* Put the files/dirs in rpath on the lists. */ + if (sync_ls(syncfd, rpath, sync_ls_build_list_cb, (void *)&args)) { + return 1; + } + + /* Recurse into each directory we found. */ + while (dirlist != NULL) { + copyinfo *next = dirlist->next; + if (remote_build_list(syncfd, filelist, dirlist->src, dirlist->dst)) { + return 1; + } + free(dirlist); + dirlist = next; + } + + return 0; +} + +static int copy_remote_dir_local(int fd, const char *rpath, const char *lpath, + int checktimestamps) +{ + copyinfo *filelist = 0; + copyinfo *ci, *next; + int pulled = 0; + int skipped = 0; + + /* Make sure that both directory paths end in a slash. */ + if (rpath[0] == 0 || lpath[0] == 0) return -1; + if (rpath[strlen(rpath) - 1] != '/') { + int tmplen = strlen(rpath) + 2; + char *tmp = malloc(tmplen); + if (tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/", rpath); + rpath = tmp; + } + if (lpath[strlen(lpath) - 1] != '/') { + int tmplen = strlen(lpath) + 2; + char *tmp = malloc(tmplen); + if (tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/", lpath); + lpath = tmp; + } + + fprintf(stderr, "pull: building file list...\n"); + /* Recursively build the list of files to copy. */ + if (remote_build_list(fd, &filelist, rpath, lpath)) { + return -1; + } + +#if 0 + if (checktimestamps) { + for (ci = filelist; ci != 0; ci = ci->next) { + if (sync_start_readtime(fd, ci->dst)) { + return 1; + } + } + for (ci = filelist; ci != 0; ci = ci->next) { + unsigned int timestamp, mode, size; + if (sync_finish_readtime(fd, ×tamp, &mode, &size)) + return 1; + if (size == ci->size) { + /* for links, we cannot update the atime/mtime */ + if ((S_ISREG(ci->mode & mode) && timestamp == ci->time) || + (S_ISLNK(ci->mode & mode) && timestamp >= ci->time)) + ci->flag = 1; + } + } + } +#endif + for (ci = filelist; ci != 0; ci = next) { + next = ci->next; + if (ci->flag == 0) { + fprintf(stderr, "pull: %s -> %s\n", ci->src, ci->dst); + if (sync_recv(fd, ci->src, ci->dst)) { + return 1; + } + pulled++; + } else { + skipped++; + } + free(ci); + } + + fprintf(stderr, "%d file%s pulled. %d file%s skipped.\n", + pulled, (pulled == 1) ? "" : "s", + skipped, (skipped == 1) ? "" : "s"); + + return 0; +} + +int do_sync_pull(const char *rpath, const char *lpath) +{ + unsigned mode; + struct stat st; + + int fd; + + fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(sync_readmode(fd, rpath, &mode)) { + return 1; + } + if(mode == 0) { + fprintf(stderr,"remote object '%s' does not exist\n", rpath); + return 1; + } + + if(S_ISREG(mode) || S_ISCHR(mode) || S_ISBLK(mode)) { + if(stat(lpath, &st) == 0) { + if(S_ISDIR(st.st_mode)) { + /* if we're copying a remote file to a local directory, + ** we *really* want to copy to localdir + "/" + remotefilename + */ + const char *name = adb_dirstop(rpath); + if(name == 0) { + name = rpath; + } else { + name++; + } + int tmplen = strlen(name) + strlen(lpath) + 2; + char *tmp = malloc(tmplen); + if(tmp == 0) return 1; + snprintf(tmp, tmplen, "%s/%s", lpath, name); + lpath = tmp; + } + } + BEGIN(); + if(sync_recv(fd, rpath, lpath)) { + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } + } else if(S_ISDIR(mode)) { + BEGIN(); + if (copy_remote_dir_local(fd, rpath, lpath, 0)) { + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } + } else { + fprintf(stderr,"remote object '%s' not a file or directory\n", rpath); + return 1; + } +} + +int do_sync_sync(const char *lpath, const char *rpath) +{ + fprintf(stderr,"syncing %s...\n",rpath); + + int fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + BEGIN(); + if(copy_local_dir_remote(fd, lpath, rpath, 1)){ + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } +} diff --git a/adb/file_sync_service.c b/adb/file_sync_service.c new file mode 100644 index 0000000..a231e93 --- /dev/null +++ b/adb/file_sync_service.c @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> +#include <utime.h> + +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_SYNC +#include "adb.h" +#include "file_sync_service.h" + +static int mkdirs(char *name) +{ + int ret; + char *x = name + 1; + + if(name[0] != '/') return -1; + + for(;;) { + x = adb_dirstart(x); + if(x == 0) return 0; + *x = 0; + ret = adb_mkdir(name, 0775); + if((ret < 0) && (errno != EEXIST)) { + D("mkdir(\"%s\") -> %s\n", name, strerror(errno)); + *x = '/'; + return ret; + } + *x++ = '/'; + } + return 0; +} + +static int do_stat(int s, const char *path) +{ + syncmsg msg; + struct stat st; + + msg.stat.id = ID_STAT; + + if(lstat(path, &st)) { + msg.stat.mode = 0; + msg.stat.size = 0; + msg.stat.time = 0; + } else { + msg.stat.mode = htoll(st.st_mode); + msg.stat.size = htoll(st.st_size); + msg.stat.time = htoll(st.st_mtime); + } + + return writex(s, &msg.stat, sizeof(msg.stat)); +} + +static int do_list(int s, const char *path) +{ + DIR *d; + struct dirent *de; + struct stat st; + syncmsg msg; + int len; + + char tmp[1024 + 256 + 1]; + char *fname; + + len = strlen(path); + memcpy(tmp, path, len); + tmp[len] = '/'; + fname = tmp + len + 1; + + msg.dent.id = ID_DENT; + + d = opendir(path); + if(d == 0) goto done; + + while((de = readdir(d))) { + int len = strlen(de->d_name); + + /* not supposed to be possible, but + if it does happen, let's not buffer overrun */ + if(len > 256) continue; + + strcpy(fname, de->d_name); + if(lstat(tmp, &st) == 0) { + msg.dent.mode = htoll(st.st_mode); + msg.dent.size = htoll(st.st_size); + msg.dent.time = htoll(st.st_mtime); + msg.dent.namelen = htoll(len); + + if(writex(s, &msg.dent, sizeof(msg.dent)) || + writex(s, de->d_name, len)) { + return -1; + } + } + } + + closedir(d); + +done: + msg.dent.id = ID_DONE; + msg.dent.mode = 0; + msg.dent.size = 0; + msg.dent.time = 0; + msg.dent.namelen = 0; + return writex(s, &msg.dent, sizeof(msg.dent)); +} + +static int fail_message(int s, const char *reason) +{ + syncmsg msg; + int len = strlen(reason); + + D("sync: failure: %s\n", reason); + + msg.data.id = ID_FAIL; + msg.data.size = htoll(len); + if(writex(s, &msg.data, sizeof(msg.data)) || + writex(s, reason, len)) { + return -1; + } else { + return 0; + } +} + +static int fail_errno(int s) +{ + return fail_message(s, strerror(errno)); +} + +static int handle_send_file(int s, char *path, mode_t mode, char *buffer) +{ + syncmsg msg; + unsigned int timestamp = 0; + int fd; + + fd = adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL, mode); + if(fd < 0 && errno == ENOENT) { + mkdirs(path); + fd = adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL, mode); + } + if(fd < 0 && errno == EEXIST) { + fd = adb_open_mode(path, O_WRONLY, mode); + } + if(fd < 0) { + if(fail_errno(s)) + return -1; + fd = -1; + } + + for(;;) { + unsigned int len; + + if(readx(s, &msg.data, sizeof(msg.data))) + goto fail; + + if(msg.data.id != ID_DATA) { + if(msg.data.id == ID_DONE) { + timestamp = ltohl(msg.data.size); + break; + } + fail_message(s, "invalid data message"); + goto fail; + } + len = ltohl(msg.data.size); + if(len > SYNC_DATA_MAX) { + fail_message(s, "oversize data message"); + goto fail; + } + if(readx(s, buffer, len)) + goto fail; + + if(fd < 0) + continue; + if(writex(fd, buffer, len)) { + adb_close(fd); + adb_unlink(path); + fd = -1; + if(fail_errno(s)) return -1; + } + } + + if(fd >= 0) { + struct utimbuf u; + adb_close(fd); + u.actime = timestamp; + u.modtime = timestamp; + utime(path, &u); + + msg.status.id = ID_OKAY; + msg.status.msglen = 0; + if(writex(s, &msg.status, sizeof(msg.status))) + return -1; + } + return 0; + +fail: + if(fd >= 0) + adb_close(fd); + adb_unlink(path); + return -1; +} + +#ifdef HAVE_SYMLINKS +static int handle_send_link(int s, char *path, char *buffer) +{ + syncmsg msg; + unsigned int len; + int ret; + + if(readx(s, &msg.data, sizeof(msg.data))) + return -1; + + if(msg.data.id != ID_DATA) { + fail_message(s, "invalid data message: expected ID_DATA"); + return -1; + } + + len = ltohl(msg.data.size); + if(len > SYNC_DATA_MAX) { + fail_message(s, "oversize data message"); + return -1; + } + if(readx(s, buffer, len)) + return -1; + + ret = symlink(buffer, path); + if(ret && errno == ENOENT) { + mkdirs(path); + ret = symlink(buffer, path); + } + if(ret) { + fail_errno(s); + return -1; + } + + if(readx(s, &msg.data, sizeof(msg.data))) + return -1; + + if(msg.data.id == ID_DONE) { + msg.status.id = ID_OKAY; + msg.status.msglen = 0; + if(writex(s, &msg.status, sizeof(msg.status))) + return -1; + } else { + fail_message(s, "invalid data message: expected ID_DONE"); + return -1; + } + + return 0; +} +#endif /* HAVE_SYMLINKS */ + +static int do_send(int s, char *path, char *buffer) +{ + char *tmp; + mode_t mode; + int is_link, ret; + + tmp = strrchr(path,','); + if(tmp) { + *tmp = 0; + errno = 0; + mode = strtoul(tmp + 1, NULL, 0); +#ifndef HAVE_SYMLINKS + is_link = 0; +#else + is_link = S_ISLNK(mode); +#endif + mode &= 0777; + } + if(!tmp || errno) { + mode = 0644; + is_link = 0; + } + + adb_unlink(path); + + +#ifdef HAVE_SYMLINKS + if(is_link) + ret = handle_send_link(s, path, buffer); + else { +#else + { +#endif + /* copy user permission bits to "group" and "other" permissions */ + mode |= ((mode >> 3) & 0070); + mode |= ((mode >> 3) & 0007); + + ret = handle_send_file(s, path, mode, buffer); + } + + return ret; +} + +static int do_recv(int s, const char *path, char *buffer) +{ + syncmsg msg; + int fd, r; + + fd = adb_open(path, O_RDONLY); + if(fd < 0) { + if(fail_errno(s)) return -1; + return 0; + } + + msg.data.id = ID_DATA; + for(;;) { + r = adb_read(fd, buffer, SYNC_DATA_MAX); + if(r <= 0) { + if(r == 0) break; + if(errno == EINTR) continue; + r = fail_errno(s); + adb_close(fd); + return r; + } + msg.data.size = htoll(r); + if(writex(s, &msg.data, sizeof(msg.data)) || + writex(s, buffer, r)) { + adb_close(fd); + return -1; + } + } + + adb_close(fd); + + msg.data.id = ID_DONE; + msg.data.size = 0; + if(writex(s, &msg.data, sizeof(msg.data))) { + return -1; + } + + return 0; +} + +void file_sync_service(int fd, void *cookie) +{ + syncmsg msg; + char name[1025]; + unsigned namelen; + + char *buffer = malloc(SYNC_DATA_MAX); + if(buffer == 0) goto fail; + + for(;;) { + D("sync: waiting for command\n"); + + if(readx(fd, &msg.req, sizeof(msg.req))) { + fail_message(fd, "command read failure"); + break; + } + namelen = ltohl(msg.req.namelen); + if(namelen > 1024) { + fail_message(fd, "invalid namelen"); + break; + } + if(readx(fd, name, namelen)) { + fail_message(fd, "filename read failure"); + break; + } + name[namelen] = 0; + + msg.req.namelen = 0; + D("sync: '%s' '%s'\n", (char*) &msg.req, name); + + switch(msg.req.id) { + case ID_STAT: + if(do_stat(fd, name)) goto fail; + break; + case ID_LIST: + if(do_list(fd, name)) goto fail; + break; + case ID_SEND: + if(do_send(fd, name, buffer)) goto fail; + break; + case ID_RECV: + if(do_recv(fd, name, buffer)) goto fail; + break; + case ID_QUIT: + goto fail; + default: + fail_message(fd, "unknown command"); + goto fail; + } + } + +fail: + if(buffer != 0) free(buffer); + D("sync: done\n"); + adb_close(fd); +} diff --git a/adb/file_sync_service.h b/adb/file_sync_service.h new file mode 100644 index 0000000..4ee40ba --- /dev/null +++ b/adb/file_sync_service.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _FILE_SYNC_SERVICE_H_ +#define _FILE_SYNC_SERVICE_H_ + +#ifdef __ppc__ +static inline unsigned __swap_uint32(unsigned x) +{ + return (((x) & 0xFF000000) >> 24) + | (((x) & 0x00FF0000) >> 8) + | (((x) & 0x0000FF00) << 8) + | (((x) & 0x000000FF) << 24); +} +#define htoll(x) __swap_uint32(x) +#define ltohl(x) __swap_uint32(x) +#define MKID(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((a) << 24)) +#else +#define htoll(x) (x) +#define ltohl(x) (x) +#define MKID(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)) +#endif + +#define ID_STAT MKID('S','T','A','T') +#define ID_LIST MKID('L','I','S','T') +#define ID_ULNK MKID('U','L','N','K') +#define ID_SEND MKID('S','E','N','D') +#define ID_RECV MKID('R','E','C','V') +#define ID_DENT MKID('D','E','N','T') +#define ID_DONE MKID('D','O','N','E') +#define ID_DATA MKID('D','A','T','A') +#define ID_OKAY MKID('O','K','A','Y') +#define ID_FAIL MKID('F','A','I','L') +#define ID_QUIT MKID('Q','U','I','T') + +typedef union { + unsigned id; + struct { + unsigned id; + unsigned namelen; + } req; + struct { + unsigned id; + unsigned mode; + unsigned size; + unsigned time; + } stat; + struct { + unsigned id; + unsigned mode; + unsigned size; + unsigned time; + unsigned namelen; + } dent; + struct { + unsigned id; + unsigned size; + } data; + struct { + unsigned id; + unsigned msglen; + } status; +} syncmsg; + + +void file_sync_service(int fd, void *cookie); +int do_sync_ls(const char *path); +int do_sync_push(const char *lpath, const char *rpath, int verifyApk); +int do_sync_sync(const char *lpath, const char *rpath); +int do_sync_pull(const char *rpath, const char *lpath); + +#define SYNC_DATA_MAX (64*1024) + +#endif diff --git a/adb/framebuffer_service.c b/adb/framebuffer_service.c new file mode 100644 index 0000000..0de0dd5 --- /dev/null +++ b/adb/framebuffer_service.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> + +#include <cutils/fdevent.h> +#include "adb.h" + +#include <linux/fb.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +/* TODO: +** - grab the current buffer, not the first buffer +** - sync with vsync to avoid tearing +*/ + +void framebuffer_service(int fd, void *cookie) +{ + struct fb_var_screeninfo vinfo; + int fb; + void *ptr = MAP_FAILED; + char x; + + unsigned fbinfo[4]; + + fb = open("/dev/graphics/fb0", O_RDONLY); + if(fb < 0) goto done; + + if(ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0) goto done; + fcntl(fb, F_SETFD, FD_CLOEXEC); + + fbinfo[0] = 16; + fbinfo[1] = vinfo.xres * vinfo.yres * 2; + fbinfo[2] = vinfo.xres; + fbinfo[3] = vinfo.yres; + + ptr = mmap(0, fbinfo[1], PROT_READ, MAP_SHARED, fb, 0); + if(ptr == MAP_FAILED) goto done; + + if(writex(fd, fbinfo, sizeof(unsigned) * 4)) goto done; + + for(;;) { + if(readx(fd, &x, 1)) goto done; + if(writex(fd, ptr, fbinfo[1])) goto done; + } + +done: + if(ptr != MAP_FAILED) munmap(ptr, fbinfo[1]); + if(fb >= 0) close(fb); + close(fd); +} + diff --git a/adb/get_my_path_darwin.c b/adb/get_my_path_darwin.c new file mode 100644 index 0000000..00dfee4 --- /dev/null +++ b/adb/get_my_path_darwin.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/executablepath.h> +#import <Carbon/Carbon.h> +#include <unistd.h> + +void get_my_path(char s[PATH_MAX]) +{ + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + CFDictionaryRef dict; + dict = ProcessInformationCopyDictionary(&psn, 0xffffffff); + CFStringRef value = (CFStringRef)CFDictionaryGetValue(dict, + CFSTR("CFBundleExecutable")); + CFStringGetCString(value, s, PATH_MAX - 1, kCFStringEncodingUTF8); +} + diff --git a/adb/get_my_path_linux.c b/adb/get_my_path_linux.c new file mode 100644 index 0000000..f516e59 --- /dev/null +++ b/adb/get_my_path_linux.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <limits.h> +#include <stdio.h> + +void get_my_path(char exe[PATH_MAX]) +{ + char proc[64]; + snprintf(proc, sizeof proc, "/proc/%d/exe", getpid()); + int err = readlink(proc, exe, PATH_MAX - 1); + if(err > 0) { + exe[err] = 0; + } else { + exe[0] = 0; + } +} + diff --git a/adb/get_my_path_windows.c b/adb/get_my_path_windows.c new file mode 100644 index 0000000..fc7143c --- /dev/null +++ b/adb/get_my_path_windows.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <limits.h> +#include <assert.h> +#include <windows.h> + +void get_my_path(char exe[PATH_MAX]) +{ + char* r; + + GetModuleFileName( NULL, exe, PATH_MAX-1 ); + exe[PATH_MAX-1] = 0; + r = strrchr( exe, '\\' ); + if (r) + *r = 0; +} + diff --git a/adb/history.h b/adb/history.h new file mode 100755 index 0000000..ef86ad9 --- /dev/null +++ b/adb/history.h @@ -0,0 +1,13 @@ +#ifndef _HISTORY_H_
+#define _HISTORY_H_
+
+#define SH_ARROW_ANY "\x1b\x5b"
+#define SH_ARROW_UP '\x41'
+#define SH_ARROW_DOWN '\x42'
+#define SH_ARROW_RIGHT '\x43'
+#define SH_ARROW_LEFT '\x44'
+#define SH_DEL_CHAR '\x7F'
+#define SH_BLANK_CHAR '\x20'
+
+#endif
+
diff --git a/adb/jdwp_service.c b/adb/jdwp_service.c new file mode 100644 index 0000000..ae7f12d --- /dev/null +++ b/adb/jdwp_service.c @@ -0,0 +1,709 @@ +/* implement the "debug-ports" and "track-debug-ports" device services */ +#include "sysdeps.h" +#define TRACE_TAG TRACE_JDWP +#include "adb.h" +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/* here's how these things work. + + when adbd starts, it creates a unix server socket + named @vm-debug-control (@ is a shortcut for "first byte is zero" + to use the private namespace instead of the file system) + + when a new JDWP daemon thread starts in a new VM process, it creates + a connection to @vm-debug-control to announce its availability. + + + JDWP thread @vm-debug-control + | | + |-------------------------------> | + | hello I'm in process <pid> | + | | + | | + + the connection is kept alive. it will be closed automatically if + the JDWP process terminates (this allows adbd to detect dead + processes). + + adbd thus maintains a list of "active" JDWP processes. it can send + its content to clients through the "device:debug-ports" service, + or even updates through the "device:track-debug-ports" service. + + when a debugger wants to connect, it simply runs the command + equivalent to "adb forward tcp:<hostport> jdwp:<pid>" + + "jdwp:<pid>" is a new forward destination format used to target + a given JDWP process on the device. when sutch a request arrives, + adbd does the following: + + - first, it calls socketpair() to create a pair of equivalent + sockets. + + - it attaches the first socket in the pair to a local socket + which is itself attached to the transport's remote socket: + + + - it sends the file descriptor of the second socket directly + to the JDWP process with the help of sendmsg() + + + JDWP thread @vm-debug-control + | | + | <----------------------| + | OK, try this file descriptor | + | | + | | + + then, the JDWP thread uses this new socket descriptor as its + pass-through connection to the debugger (and receives the + JDWP-Handshake message, answers to it, etc...) + + this gives the following graphics: + ____________________________________ + | | + | ADB Server (host) | + | | + Debugger <---> LocalSocket <----> RemoteSocket | + | ^^ | + |___________________________||_______| + || + Transport || + (TCP for emulator - USB for device) || + || + ___________________________||_______ + | || | + | ADBD (device) || | + | VV | + JDWP <======> LocalSocket <----> RemoteSocket | + | | + |____________________________________| + + due to the way adb works, this doesn't need a special socket + type or fancy handling of socket termination if either the debugger + or the JDWP process closes the connection. + + THIS IS THE SIMPLEST IMPLEMENTATION I COULD FIND, IF YOU HAPPEN + TO HAVE A BETTER IDEA, LET ME KNOW - Digit + +**********************************************************************/ + +/** JDWP PID List Support Code + ** for each JDWP process, we record its pid and its connected socket + **/ + +#define MAX_OUT_FDS 4 + +#if !ADB_HOST + +#include <sys/socket.h> +#include <sys/un.h> + +typedef struct JdwpProcess JdwpProcess; +struct JdwpProcess { + JdwpProcess* next; + JdwpProcess* prev; + int pid; + int socket; + fdevent* fde; + + char in_buff[4]; /* input character to read PID */ + int in_len; /* number from JDWP process */ + + int out_fds[MAX_OUT_FDS]; /* output array of file descriptors */ + int out_count; /* to send to the JDWP process */ +}; + +static JdwpProcess _jdwp_list; + +static int +jdwp_process_list( char* buffer, int bufferlen ) +{ + char* end = buffer + bufferlen; + char* p = buffer; + JdwpProcess* proc = _jdwp_list.next; + + for ( ; proc != &_jdwp_list; proc = proc->next ) { + int len; + + /* skip transient connections */ + if (proc->pid < 0) + continue; + + len = snprintf(p, end-p, "%d\n", proc->pid); + if (p + len >= end) + break; + p += len; + } + p[0] = 0; + return (p - buffer); +} + + +static int +jdwp_process_list_msg( char* buffer, int bufferlen ) +{ + char head[5]; + int len = jdwp_process_list( buffer+4, bufferlen-4 ); + snprintf(head, sizeof head, "%04x", len); + memcpy(buffer, head, 4); + return len + 4; +} + + +static void jdwp_process_list_updated(void); + +static void +jdwp_process_free( JdwpProcess* proc ) +{ + if (proc) { + int n; + + proc->prev->next = proc->next; + proc->next->prev = proc->prev; + + if (proc->socket >= 0) { + shutdown(proc->socket, SHUT_RDWR); + adb_close(proc->socket); + proc->socket = -1; + } + + if (proc->fde != NULL) { + fdevent_destroy(proc->fde); + proc->fde = NULL; + } + proc->pid = -1; + + for (n = 0; n < proc->out_count; n++) { + adb_close(proc->out_fds[n]); + } + proc->out_count = 0; + + free(proc); + + jdwp_process_list_updated(); + } +} + + +static void jdwp_process_event(int, unsigned, void*); /* forward */ + + +static JdwpProcess* +jdwp_process_alloc( int socket ) +{ + JdwpProcess* proc = calloc(1,sizeof(*proc)); + + if (proc == NULL) { + D("not enough memory to create new JDWP process\n"); + return NULL; + } + + proc->socket = socket; + proc->pid = -1; + proc->next = proc; + proc->prev = proc; + + proc->fde = fdevent_create( socket, jdwp_process_event, proc ); + if (proc->fde == NULL) { + D("could not create fdevent for new JDWP process\n" ); + free(proc); + return NULL; + } + + proc->fde->state |= FDE_DONT_CLOSE; + proc->in_len = 0; + proc->out_count = 0; + + /* append to list */ + proc->next = &_jdwp_list; + proc->prev = proc->next->prev; + + proc->prev->next = proc; + proc->next->prev = proc; + + /* start by waiting for the PID */ + fdevent_add(proc->fde, FDE_READ); + + return proc; +} + + +static void +jdwp_process_event( int socket, unsigned events, void* _proc ) +{ + JdwpProcess* proc = _proc; + + if (events & FDE_READ) { + if (proc->pid < 0) { + /* read the PID as a 4-hexchar string */ + char* p = proc->in_buff + proc->in_len; + int size = 4 - proc->in_len; + char temp[5]; + while (size > 0) { + int len = recv( socket, p, size, 0 ); + if (len < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + return; + /* this can fail here if the JDWP process crashes very fast */ + D("weird unknown JDWP process failure: %s\n", + strerror(errno)); + + goto CloseProcess; + } + if (len == 0) { /* end of stream ? */ + D("weird end-of-stream from unknown JDWP process\n"); + goto CloseProcess; + } + p += len; + proc->in_len += len; + size -= len; + } + /* we have read 4 characters, now decode the pid */ + memcpy(temp, proc->in_buff, 4); + temp[4] = 0; + + if (sscanf( temp, "%04x", &proc->pid ) != 1) { + D("could not decode JDWP %p PID number: '%s'\n", proc, temp); + goto CloseProcess; + } + + /* all is well, keep reading to detect connection closure */ + D("Adding pid %d to jdwp process list\n", proc->pid); + jdwp_process_list_updated(); + } + else + { + /* the pid was read, if we get there it's probably because the connection + * was closed (e.g. the JDWP process exited or crashed) */ + char buf[32]; + + for (;;) { + int len = recv(socket, buf, sizeof(buf), 0); + + if (len <= 0) { + if (len < 0 && errno == EINTR) + continue; + if (len < 0 && errno == EAGAIN) + return; + else { + D("terminating JDWP %d connection: %s\n", proc->pid, + strerror(errno)); + break; + } + } + else { + D( "ignoring unexpected JDWP %d control socket activity (%d bytes)\n", + proc->pid, len ); + } + } + + CloseProcess: + if (proc->pid >= 0) + D( "remove pid %d to jdwp process list\n", proc->pid ); + jdwp_process_free(proc); + return; + } + } + + if (events & FDE_WRITE) { + D("trying to write to JDWP pid controli (count=%d first=%d) %d\n", + proc->pid, proc->out_count, proc->out_fds[0]); + if (proc->out_count > 0) { + int fd = proc->out_fds[0]; + int n, ret; + struct cmsghdr* cmsg; + struct msghdr msg; + struct iovec iov; + char dummy = '!'; + char buffer[sizeof(struct cmsghdr) + sizeof(int)]; + + iov.iov_base = &dummy; + iov.iov_len = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = buffer; + msg.msg_controllen = sizeof(buffer); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = msg.msg_controllen; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + ((int*)CMSG_DATA(cmsg))[0] = fd; + + for (;;) { + ret = sendmsg(proc->socket, &msg, 0); + if (ret >= 0) + break; + if (errno == EINTR) + continue; + D("sending new file descriptor to JDWP %d failed: %s\n", + proc->pid, strerror(errno)); + goto CloseProcess; + } + + D("sent file descriptor %d to JDWP process %d\n", + fd, proc->pid); + + for (n = 1; n < proc->out_count; n++) + proc->out_fds[n-1] = proc->out_fds[n]; + + if (--proc->out_count == 0) + fdevent_del( proc->fde, FDE_WRITE ); + } + } +} + + +int +create_jdwp_connection_fd(int pid) +{ + JdwpProcess* proc = _jdwp_list.next; + + D("looking for pid %d in JDWP process list\n", pid); + for ( ; proc != &_jdwp_list; proc = proc->next ) { + if (proc->pid == pid) { + goto FoundIt; + } + } + D("search failed !!\n"); + return -1; + +FoundIt: + { + int fds[2]; + + if (proc->out_count >= MAX_OUT_FDS) { + D("%s: too many pending JDWP connection for pid %d\n", + __FUNCTION__, pid); + return -1; + } + + if (adb_socketpair(fds) < 0) { + D("%s: socket pair creation failed: %s\n", + __FUNCTION__, strerror(errno)); + return -1; + } + + proc->out_fds[ proc->out_count ] = fds[1]; + if (++proc->out_count == 1) + fdevent_add( proc->fde, FDE_WRITE ); + + return fds[0]; + } +} + +/** VM DEBUG CONTROL SOCKET + ** + ** we do implement a custom asocket to receive the data + **/ + +/* name of the debug control Unix socket */ +#define JDWP_CONTROL_NAME "\0jdwp-control" +#define JDWP_CONTROL_NAME_LEN (sizeof(JDWP_CONTROL_NAME)-1) + +typedef struct { + int listen_socket; + fdevent* fde; + +} JdwpControl; + + +static void +jdwp_control_event(int s, unsigned events, void* user); + + +static int +jdwp_control_init( JdwpControl* control, + const char* sockname, + int socknamelen ) +{ + struct sockaddr_un addr; + socklen_t addrlen; + int s; + int maxpath = sizeof(addr.sun_path); + int pathlen = socknamelen; + + if (pathlen >= maxpath) { + D( "vm debug control socket name too long (%d extra chars)\n", + pathlen+1-maxpath ); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, sockname, socknamelen); + + s = socket( AF_UNIX, SOCK_STREAM, 0 ); + if (s < 0) { + D( "could not create vm debug control socket. %d: %s\n", + errno, strerror(errno)); + return -1; + } + + addrlen = (pathlen + sizeof(addr.sun_family)); + + if (bind(s, (struct sockaddr*)&addr, addrlen) < 0) { + D( "could not bind vm debug control socket: %d: %s\n", + errno, strerror(errno) ); + adb_close(s); + return -1; + } + + if ( listen(s, 4) < 0 ) { + D("listen failed in jdwp control socket: %d: %s\n", + errno, strerror(errno)); + adb_close(s); + return -1; + } + + control->listen_socket = s; + + control->fde = fdevent_create(s, jdwp_control_event, control); + if (control->fde == NULL) { + D( "could not create fdevent for jdwp control socket\n" ); + adb_close(s); + return -1; + } + + /* only wait for incoming connections */ + fdevent_add(control->fde, FDE_READ); + + D("jdwp control socket started (%d)\n", control->listen_socket); + return 0; +} + + +static void +jdwp_control_event( int s, unsigned events, void* _control ) +{ + JdwpControl* control = (JdwpControl*) _control; + + if (events & FDE_READ) { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + int s = -1; + JdwpProcess* proc; + + do { + s = adb_socket_accept( control->listen_socket, &addr, &addrlen ); + if (s < 0) { + if (errno == EINTR) + continue; + if (errno == ECONNABORTED) { + /* oops, the JDWP process died really quick */ + D("oops, the JDWP process died really quick\n"); + return; + } + /* the socket is probably closed ? */ + D( "weird accept() failed on jdwp control socket: %s\n", + strerror(errno) ); + return; + } + } + while (s < 0); + + proc = jdwp_process_alloc( s ); + if (proc == NULL) + return; + } +} + + +static JdwpControl _jdwp_control; + +/** "jdwp" local service implementation + ** this simply returns the list of known JDWP process pids + **/ + +typedef struct { + asocket socket; + int pass; +} JdwpSocket; + +static void +jdwp_socket_close( asocket* s ) +{ + asocket* peer = s->peer; + + remove_socket(s); + + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + free(s); +} + +static int +jdwp_socket_enqueue( asocket* s, apacket* p ) +{ + /* you can't write to this asocket */ + put_apacket(p); + s->peer->close(s->peer); + return -1; +} + + +static void +jdwp_socket_ready( asocket* s ) +{ + JdwpSocket* jdwp = (JdwpSocket*)s; + asocket* peer = jdwp->socket.peer; + + /* on the first call, send the list of pids, + * on the second one, close the connection + */ + if (jdwp->pass == 0) { + apacket* p = get_apacket(); + p->len = jdwp_process_list((char*)p->data, MAX_PAYLOAD); + peer->enqueue(peer, p); + jdwp->pass = 1; + } + else { + peer->close(peer); + } +} + +asocket* +create_jdwp_service_socket( void ) +{ + JdwpSocket* s = calloc(sizeof(*s),1); + + if (s == NULL) + return NULL; + + install_local_socket(&s->socket); + + s->socket.ready = jdwp_socket_ready; + s->socket.enqueue = jdwp_socket_enqueue; + s->socket.close = jdwp_socket_close; + s->pass = 0; + + return &s->socket; +} + +/** "track-jdwp" local service implementation + ** this periodically sends the list of known JDWP process pids + ** to the client... + **/ + +typedef struct JdwpTracker JdwpTracker; + +struct JdwpTracker { + asocket socket; + JdwpTracker* next; + JdwpTracker* prev; + int need_update; +}; + +static JdwpTracker _jdwp_trackers_list; + + +static void +jdwp_process_list_updated(void) +{ + char buffer[1024]; + int len; + JdwpTracker* t = _jdwp_trackers_list.next; + + len = jdwp_process_list_msg(buffer, sizeof(buffer)); + + for ( ; t != &_jdwp_trackers_list; t = t->next ) { + apacket* p = get_apacket(); + asocket* peer = t->socket.peer; + memcpy(p->data, buffer, len); + p->len = len; + peer->enqueue( peer, p ); + } +} + +static void +jdwp_tracker_close( asocket* s ) +{ + JdwpTracker* tracker = (JdwpTracker*) s; + asocket* peer = s->peer; + + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + + remove_socket(s); + + tracker->prev->next = tracker->next; + tracker->next->prev = tracker->prev; + + free(s); +} + +static void +jdwp_tracker_ready( asocket* s ) +{ + JdwpTracker* t = (JdwpTracker*) s; + + if (t->need_update) { + apacket* p = get_apacket(); + t->need_update = 0; + p->len = jdwp_process_list_msg((char*)p->data, sizeof(p->data)); + s->peer->enqueue(s->peer, p); + } +} + +static int +jdwp_tracker_enqueue( asocket* s, apacket* p ) +{ + /* you can't write to this socket */ + put_apacket(p); + s->peer->close(s->peer); + return -1; +} + + +asocket* +create_jdwp_tracker_service_socket( void ) +{ + JdwpTracker* t = calloc(sizeof(*t),1); + + if (t == NULL) + return NULL; + + t->next = &_jdwp_trackers_list; + t->prev = t->next->prev; + + t->next->prev = t; + t->prev->next = t; + + install_local_socket(&t->socket); + + t->socket.ready = jdwp_tracker_ready; + t->socket.enqueue = jdwp_tracker_enqueue; + t->socket.close = jdwp_tracker_close; + t->need_update = 1; + + return &t->socket; +} + + +int +init_jdwp(void) +{ + _jdwp_list.next = &_jdwp_list; + _jdwp_list.prev = &_jdwp_list; + + _jdwp_trackers_list.next = &_jdwp_trackers_list; + _jdwp_trackers_list.prev = &_jdwp_trackers_list; + + return jdwp_control_init( &_jdwp_control, + JDWP_CONTROL_NAME, + JDWP_CONTROL_NAME_LEN ); +} + +#endif /* !ADB_HOST */ + diff --git a/adb/log_service.c b/adb/log_service.c new file mode 100644 index 0000000..6e9bdee --- /dev/null +++ b/adb/log_service.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/socket.h> +#include <cutils/logger.h> +#include "sysdeps.h" +#include "adb.h" + +#define LOG_FILE_DIR "/dev/log/" + +void write_log_entry(int fd, struct logger_entry *buf); + +void log_service(int fd, void *cookie) +{ + /* get the name of the log filepath to read */ + char * log_filepath = cookie; + + /* open the log file. */ + int logfd = unix_open(log_filepath, O_RDONLY); + if (logfd < 0) { + goto done; + } + + // temp buffer to read the entries + unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4))); + struct logger_entry *entry = (struct logger_entry *) buf; + + while (1) { + int ret; + + ret = unix_read(logfd, entry, LOGGER_ENTRY_MAX_LEN); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + // perror("logcat read"); + goto done; + } + else if (!ret) { + // fprintf(stderr, "read: Unexpected EOF!\n"); + goto done; + } + + /* NOTE: driver guarantees we read exactly one full entry */ + + entry->msg[entry->len] = '\0'; + + write_log_entry(fd, entry); + } + +done: + unix_close(fd); + free(log_filepath); +} + +/* returns the full path to the log file in a newly allocated string */ +char * get_log_file_path(const char * log_name) { + char *log_device = malloc(strlen(LOG_FILE_DIR) + strlen(log_name) + 1); + + strcpy(log_device, LOG_FILE_DIR); + strcat(log_device, log_name); + + return log_device; +} + + +/* prints one log entry into the file descriptor fd */ +void write_log_entry(int fd, struct logger_entry *buf) +{ + size_t size = sizeof(struct logger_entry) + buf->len; + + writex(fd, buf, size); +} diff --git a/adb/mutex_list.h b/adb/mutex_list.h new file mode 100644 index 0000000..eebe0df --- /dev/null +++ b/adb/mutex_list.h @@ -0,0 +1,14 @@ +/* the list of mutexes used by addb */ +#ifndef ADB_MUTEX +#error ADB_MUTEX not defined when including this file +#endif + +ADB_MUTEX(dns_lock) +ADB_MUTEX(socket_list_lock) +ADB_MUTEX(transport_lock) +#if ADB_HOST +ADB_MUTEX(local_transports_lock) +#endif +ADB_MUTEX(usb_lock) + +#undef ADB_MUTEX diff --git a/adb/protocol.txt b/adb/protocol.txt new file mode 100644 index 0000000..d0f307c --- /dev/null +++ b/adb/protocol.txt @@ -0,0 +1,252 @@ + +--- a replacement for aproto ------------------------------------------- + +When it comes down to it, aproto's primary purpose is to forward +various streams between the host computer and client device (in either +direction). + +This replacement further simplifies the concept, reducing the protocol +to an extremely straightforward model optimized to accomplish the +forwarding of these streams and removing additional state or +complexity. + +The host side becomes a simple comms bridge with no "UI", which will +be used by either commandline or interactive tools to communicate with +a device or emulator that is connected to the bridge. + +The protocol is designed to be straightforward and well-defined enough +that if it needs to be reimplemented in another environment (Java +perhaps), there should not problems ensuring perfect interoperability. + +The protocol discards the layering aproto has and should allow the +implementation to be much more robust. + + +--- protocol overview and basics --------------------------------------- + +The transport layer deals in "messages", which consist of a 24 byte +header followed (optionally) by a payload. The header consists of 6 +32 bit words which are sent across the wire in little endian format. + +struct message { + unsigned command; /* command identifier constant */ + unsigned arg0; /* first argument */ + unsigned arg1; /* second argument */ + unsigned data_length; /* length of payload (0 is allowed) */ + unsigned data_crc32; /* crc32 of data payload */ + unsigned magic; /* command ^ 0xffffffff */ +}; + +Receipt of an invalid message header, corrupt message payload, or an +unrecognized command MUST result in the closing of the remote +connection. The protocol depends on shared state and any break in the +message stream will result in state getting out of sync. + +The following sections describe the six defined message types in +detail. Their format is COMMAND(arg0, arg1, payload) where the payload +is represented by a quoted string or an empty string if none should be +sent. + +The identifiers "local-id" and "remote-id" are always relative to the +*sender* of the message, so for a receiver, the meanings are effectively +reversed. + + + +--- CONNECT(version, maxdata, "system-identity-string") ---------------- + +The CONNECT message establishes the presence of a remote system. +The version is used to ensure protocol compatibility and maxdata +declares the maximum message body size that the remote system +is willing to accept. + +Currently, version=0x01000000 and maxdata=4096 + +Both sides send a CONNECT message when the connection between them is +established. Until a CONNECT message is received no other messages may +be sent. Any messages received before a CONNECT message MUST be ignored. + +If a CONNECT message is received with an unknown version or insufficiently +large maxdata value, the connection with the other side must be closed. + +The system identity string should be "<systemtype>:<serialno>:<banner>" +where systemtype is "bootloader", "device", or "host", serialno is some +kind of unique ID (or empty), and banner is a human-readable version +or identifier string (informational only). + + +--- OPEN(local-id, 0, "destination") ----------------------------------- + +The OPEN message informs the recipient that the sender has a stream +identified by local-id that it wishes to connect to the named +destination in the message payload. The local-id may not be zero. + +The OPEN message MUST result in either a READY message indicating that +the connection has been established (and identifying the other end) or +a CLOSE message, indicating failure. An OPEN message also implies +a READY message sent at the same time. + +Common destination naming conventions include: + +* "tcp:<host>:<port>" - host may be omitted to indicate localhost +* "udp:<host>:<port>" - host may be omitted to indicate localhost +* "local-dgram:<identifier>" +* "local-stream:<identifier>" +* "shell" - local shell service +* "upload" - service for pushing files across (like aproto's /sync) +* "fs-bridge" - FUSE protocol filesystem bridge + + +--- READY(local-id, remote-id, "") ------------------------------------- + +The READY message informs the recipient that the sender's stream +identified by local-id is ready for write messages and that it is +connected to the recipient's stream identified by remote-id. + +Neither the local-id nor the remote-id may be zero. + +A READY message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have been +closed while this message was in-flight. + +The local-id is ignored on all but the first READY message (where it +is used to establish the connection). Nonetheless, the local-id MUST +not change on later READY messages sent to the same stream. + + + +--- WRITE(0, remote-id, "data") ---------------------------------------- + +The WRITE message sends data to the recipient's stream identified by +remote-id. The payload MUST be <= maxdata in length. + +A WRITE message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have been +closed while this message was in-flight. + +A WRITE message may not be sent until a READY message is received. +Once a WRITE message is sent, an additional WRITE message may not be +sent until another READY message has been received. Recipients of +a WRITE message that is in violation of this requirement will CLOSE +the connection. + + +--- CLOSE(local-id, remote-id, "") ------------------------------------- + +The CLOSE message informs recipient that the connection between the +sender's stream (local-id) and the recipient's stream (remote-id) is +broken. The remote-id MUST not be zero, but the local-id MAY be zero +if this CLOSE indicates a failed OPEN. + +A CLOSE message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have +already been closed by the recipient while this message was in-flight. + +The recipient should not respond to a CLOSE message in any way. The +recipient should cancel pending WRITEs or CLOSEs, but this is not a +requirement, since they will be ignored. + + +--- SYNC(online, sequence, "") ----------------------------------------- + +The SYNC message is used by the io pump to make sure that stale +outbound messages are discarded when the connection to the remote side +is broken. It is only used internally to the bridge and never valid +to send across the wire. + +* when the connection to the remote side goes offline, the io pump + sends a SYNC(0, 0) and starts discarding all messages +* when the connection to the remote side is established, the io pump + sends a SYNC(1, token) and continues to discard messages +* when the io pump receives a matching SYNC(1, token), it once again + starts accepting messages to forward to the remote side + + +--- message command constants ------------------------------------------ + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 + + + +--- implementation details --------------------------------------------- + +The core of the bridge program will use three threads. One thread +will be a select/epoll loop to handle io between various inbound and +outbound connections and the connection to the remote side. + +The remote side connection will be implemented as two threads (one for +reading, one for writing) and a datagram socketpair to provide the +channel between the main select/epoll thread and the remote connection +threadpair. The reason for this is that for usb connections, the +kernel interface on linux and osx does not allow you to do meaningful +nonblocking IO. + +The endian swapping for the message headers will happen (as needed) in +the remote connection threadpair and that the rest of the program will +always treat message header values as native-endian. + +The bridge program will be able to have a number of mini-servers +compiled in. They will be published under known names (examples +"shell", "fs-bridge", etc) and upon receiving an OPEN() to such a +service, the bridge program will create a stream socketpair and spawn +a thread or subprocess to handle the io. + + +--- simplified / embedded implementation ------------------------------- + +For limited environments, like the bootloader, it is allowable to +support a smaller, fixed number of channels using pre-assigned channel +ID numbers such that only one stream may be connected to a bootloader +endpoint at any given time. The protocol remains unchanged, but the +"embedded" version of it is less dynamic. + +The bootloader will support two streams. A "bootloader:debug" stream, +which may be opened to get debug messages from the bootloader and a +"bootloader:control", stream which will support the set of basic +bootloader commands. + +Example command stream dialogues: + "flash_kernel,2515049,........\n" "okay\n" + "flash_ramdisk,5038,........\n" "fail,flash write error\n" + "bogus_command......" <CLOSE> + + +--- future expansion --------------------------------------------------- + +I plan on providing either a message or a special control stream so that +the client device could ask the host computer to setup inbound socket +translations on the fly on behalf of the client device. + + +The initial design does handshaking to provide flow control, with a +message flow that looks like: + + >OPEN <READY >WRITE <READY >WRITE <READY >WRITE <CLOSE + +The far side may choose to issue the READY message as soon as it receives +a WRITE or it may defer the READY until the write to the local stream +succeeds. A future version may want to do some level of windowing where +multiple WRITEs may be sent without requiring individual READY acks. + +------------------------------------------------------------------------ + +--- smartsockets ------------------------------------------------------- + +Port 5037 is used for smart sockets which allow a client on the host +side to request access to a service in the host adb daemon or in the +remote (device) daemon. The service is requested by ascii name, +preceeded by a 4 digit hex length. Upon successful connection an +"OKAY" response is sent, otherwise a "FAIL" message is returned. Once +connected the client is talking to that (remote or local) service. + +client: <hex4> <service-name> +server: "OKAY" + +client: <hex4> <service-name> +server: "FAIL" <hex4> <reason> + diff --git a/adb/remount_service.c b/adb/remount_service.c new file mode 100644 index 0000000..26bc841 --- /dev/null +++ b/adb/remount_service.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/mount.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_ADB +#include "adb.h" + + +static int system_ro = 1; + +/* Returns the mount number of the requested partition from /proc/mtd */ +static int find_mount(const char *findme) +{ + int fd; + int res; + int size; + char *token = NULL; + const char delims[] = "\n"; + char buf[1024]; + + fd = unix_open("/proc/mtd", O_RDONLY); + if (fd < 0) + return -errno; + + buf[sizeof(buf) - 1] = '\0'; + size = adb_read(fd, buf, sizeof(buf) - 1); + adb_close(fd); + + token = strtok(buf, delims); + + while (token) { + char mtdname[16]; + int mtdnum, mtdsize, mtderasesize; + + res = sscanf(token, "mtd%d: %x %x %15s", + &mtdnum, &mtdsize, &mtderasesize, mtdname); + + if (res == 4 && !strcmp(mtdname, findme)) + return mtdnum; + + token = strtok(NULL, delims); + } + return -1; +} + +/* Init mounts /system as read only, remount to enable writes. */ +static int remount_system() +{ + int num; + char source[64]; + if (system_ro == 0) { + return 0; + } + if ((num = find_mount("\"system\"")) < 0) + return -1; + + snprintf(source, sizeof source, "/dev/block/mtdblock%d", num); + system_ro = mount(source, "/system", "yaffs2", MS_REMOUNT, NULL); + return system_ro; +} + +static void write_string(int fd, const char* str) +{ + writex(fd, str, strlen(str)); +} + +void remount_service(int fd, void *cookie) +{ + int ret = remount_system(); + + if (!ret) + write_string(fd, "remount succeeded\n"); + else { + char buffer[200]; + snprintf(buffer, sizeof(buffer), "remount failed: %s\n", strerror(errno)); + write_string(fd, buffer); + } + + adb_close(fd); +} + diff --git a/adb/services.c b/adb/services.c new file mode 100644 index 0000000..e686949 --- /dev/null +++ b/adb/services.c @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_ADB +#include "adb.h" +#include "file_sync_service.h" + +#if ADB_HOST +# ifndef HAVE_WINSOCK +# include <netinet/in.h> +# include <netdb.h> +# endif +#endif + +typedef struct stinfo stinfo; + +struct stinfo { + void (*func)(int fd, void *cookie); + int fd; + void *cookie; +}; + + +void *service_bootstrap_func(void *x) +{ + stinfo *sti = x; + sti->func(sti->fd, sti->cookie); + free(sti); + return 0; +} + +#if ADB_HOST +ADB_MUTEX_DEFINE( dns_lock ); + +static void dns_service(int fd, void *cookie) +{ + char *hostname = cookie; + struct hostent *hp; + unsigned zero = 0; + + adb_mutex_lock(&dns_lock); + hp = gethostbyname(hostname); + if(hp == 0) { + writex(fd, &zero, 4); + } else { + writex(fd, hp->h_addr, 4); + } + adb_mutex_unlock(&dns_lock); + adb_close(fd); +} +#else +extern int recovery_mode; + +static void recover_service(int s, void *cookie) +{ + unsigned char buf[4096]; + unsigned count = (unsigned) cookie; + int fd; + + fd = adb_creat("/tmp/update", 0644); + if(fd < 0) { + adb_close(s); + return; + } + + while(count > 0) { + unsigned xfer = (count > 4096) ? 4096 : count; + if(readx(s, buf, xfer)) break; + if(writex(fd, buf, xfer)) break; + count -= xfer; + } + + if(count == 0) { + writex(s, "OKAY", 4); + } else { + writex(s, "FAIL", 4); + } + adb_close(fd); + adb_close(s); + + fd = adb_creat("/tmp/update.begin", 0644); + adb_close(fd); +} + +#endif + +#if 0 +static void echo_service(int fd, void *cookie) +{ + char buf[4096]; + int r; + char *p; + int c; + + for(;;) { + r = read(fd, buf, 4096); + if(r == 0) goto done; + if(r < 0) { + if(errno == EINTR) continue; + else goto done; + } + + c = r; + p = buf; + while(c > 0) { + r = write(fd, p, c); + if(r > 0) { + c -= r; + p += r; + continue; + } + if((r < 0) && (errno == EINTR)) continue; + goto done; + } + } +done: + close(fd); +} +#endif + +static int create_service_thread(void (*func)(int, void *), void *cookie) +{ + stinfo *sti; + adb_thread_t t; + int s[2]; + + if(adb_socketpair(s)) { + printf("cannot create service socket pair\n"); + return -1; + } + + sti = malloc(sizeof(stinfo)); + if(sti == 0) fatal("cannot allocate stinfo"); + sti->func = func; + sti->cookie = cookie; + sti->fd = s[1]; + + if(adb_thread_create( &t, service_bootstrap_func, sti)){ + free(sti); + adb_close(s[0]); + adb_close(s[1]); + printf("cannot create service thread\n"); + return -1; + } + + D("service thread started, %d:%d\n",s[0], s[1]); + return s[0]; +} + +static int create_subprocess(const char *cmd, const char *arg0, const char *arg1) +{ +#ifdef HAVE_WIN32_PROC + fprintf(stderr, "error: create_subprocess not implemented on Win32 (%s %s %s)\n", cmd, arg0, arg1); + return -1; +#else /* !HAVE_WIN32_PROC */ + char *devname; + int ptm; + pid_t pid; + + ptm = unix_open("/dev/ptmx", O_RDWR); // | O_NOCTTY); + if(ptm < 0){ + printf("[ cannot open /dev/ptmx - %s ]\n",strerror(errno)); + return -1; + } + fcntl(ptm, F_SETFD, FD_CLOEXEC); + + if(grantpt(ptm) || unlockpt(ptm) || + ((devname = (char*) ptsname(ptm)) == 0)){ + printf("[ trouble with /dev/ptmx - %s ]\n", strerror(errno)); + return -1; + } + + pid = fork(); + if(pid < 0) { + printf("- fork failed: %s -\n", strerror(errno)); + return -1; + } + + if(pid == 0){ + int pts; + + setsid(); + + pts = unix_open(devname, O_RDWR); + if(pts < 0) exit(-1); + + dup2(pts, 0); + dup2(pts, 1); + dup2(pts, 2); + + adb_close(ptm); + + execl(cmd, cmd, arg0, arg1, NULL); + fprintf(stderr, "- exec '%s' failed: %s (%d) -\n", + cmd, strerror(errno), errno); + exit(-1); + } else { + return ptm; + } +#endif /* !HAVE_WIN32_PROC */ +} + +#if ADB_HOST +#define SHELL_COMMAND "/bin/sh" +#else +#define SHELL_COMMAND "/system/bin/sh" +#endif + +int service_to_fd(const char *name) +{ + int ret = -1; + + if(!strncmp(name, "tcp:", 4)) { + int port = atoi(name + 4); + name = strchr(name + 4, ':'); + if(name == 0) { + ret = socket_loopback_client(port, SOCK_STREAM); + if (ret >= 0) + disable_tcp_nagle(ret); + } else { +#if ADB_HOST + adb_mutex_lock(&dns_lock); + ret = socket_network_client(name + 1, port, SOCK_STREAM); + adb_mutex_unlock(&dns_lock); +#else + return -1; +#endif + } +#ifndef HAVE_WINSOCK /* winsock doesn't implement unix domain sockets */ + } else if(!strncmp(name, "local:", 6)) { + ret = socket_local_client(name + 6, + ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + } else if(!strncmp(name, "localreserved:", 14)) { + ret = socket_local_client(name + 14, + ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + } else if(!strncmp(name, "localabstract:", 14)) { + ret = socket_local_client(name + 14, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + } else if(!strncmp(name, "localfilesystem:", 16)) { + ret = socket_local_client(name + 16, + ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); +#endif +#if ADB_HOST + } else if(!strncmp("dns:", name, 4)){ + char *n = strdup(name + 4); + if(n == 0) return -1; + ret = create_service_thread(dns_service, n); +#else /* !ADB_HOST */ + } else if(!strncmp("dev:", name, 4)) { + ret = unix_open(name + 4, O_RDWR); + } else if(!strncmp(name, "framebuffer:", 12)) { + ret = create_service_thread(framebuffer_service, 0); + } else if(recovery_mode && !strncmp(name, "recover:", 8)) { + ret = create_service_thread(recover_service, (void*) atoi(name + 8)); + } else if (!strncmp(name, "jdwp:", 5)) { + ret = create_jdwp_connection_fd(atoi(name+5)); + } else if (!strncmp(name, "log:", 4)) { + ret = create_service_thread(log_service, get_log_file_path(name + 4)); +#endif + } else if(!HOST && !strncmp(name, "shell:", 6)) { + if(name[6]) { + ret = create_subprocess(SHELL_COMMAND, "-c", name + 6); + } else { + ret = create_subprocess(SHELL_COMMAND, "-", 0); + } +#if !ADB_HOST + } else if(!strncmp(name, "sync:", 5)) { + ret = create_service_thread(file_sync_service, NULL); + } else if(!strncmp(name, "remount:", 8)) { + ret = create_service_thread(remount_service, NULL); +#endif +#if 0 + } else if(!strncmp(name, "echo:", 5)){ + ret = create_service_thread(echo_service, 0); +#endif + } + if (ret >= 0) { + close_on_exec(ret); + } + return ret; +} + +#if ADB_HOST +struct state_info { + transport_type transport; + char* serial; + int state; +}; + +static void wait_for_state(int fd, void* cookie) +{ + struct state_info* sinfo = cookie; + char* err = "unknown error"; + + D("wait_for_state %d\n", sinfo->state); + + atransport *t = acquire_one_transport(sinfo->state, sinfo->transport, sinfo->serial, &err); + if(t != 0) { + writex(fd, "OKAY", 4); + } else { + sendfailmsg(fd, err); + } + + if (sinfo->serial) + free(sinfo->serial); + free(sinfo); + adb_close(fd); + D("wait_for_state is done\n"); +} +#endif + +#if ADB_HOST +asocket* host_service_to_socket(const char* name, const char *serial) +{ + if (!strcmp(name,"track-devices")) { + return create_device_tracker(); + } else if (!strncmp(name, "wait-for-", strlen("wait-for-"))) { + struct state_info* sinfo = malloc(sizeof(struct state_info)); + + if (serial) + sinfo->serial = strdup(serial); + else + sinfo->serial = NULL; + + name += strlen("wait-for-"); + + if (!strncmp(name, "local", strlen("local"))) { + sinfo->transport = kTransportLocal; + sinfo->state = CS_DEVICE; + } else if (!strncmp(name, "usb", strlen("usb"))) { + sinfo->transport = kTransportUsb; + sinfo->state = CS_DEVICE; + } else if (!strncmp(name, "any", strlen("any"))) { + sinfo->transport = kTransportAny; + sinfo->state = CS_DEVICE; + } else { + free(sinfo); + return NULL; + } + + int fd = create_service_thread(wait_for_state, sinfo); + return create_local_socket(fd); + } + return NULL; +} +#endif /* ADB_HOST */ diff --git a/adb/shlist.c b/adb/shlist.c new file mode 100755 index 0000000..44919ef --- /dev/null +++ b/adb/shlist.c @@ -0,0 +1,185 @@ +/*-------------------------------------------------------------------*/ +/* List Functionality */ +/*-------------------------------------------------------------------*/ +/* #define SH_LIST_DEBUG */ +/*-------------------------------------------------------------------*/ +#include <stdio.h> +#include <stdlib.h> +#include "shlist.h" +/*-------------------------------------------------------------------*/ +void shListInitList( SHLIST *listPtr ) +{ + listPtr->data = (void *)0L; + listPtr->next = listPtr; + listPtr->prev = listPtr; +} + +SHLIST *shListFindItem( SHLIST *head, void *val, shListEqual func ) +{ + SHLIST *item; + + for(item=head->next;( item != head );item=item->next) + if( func ) { + if( func( val, item->data ) ) { + return( item ); + } + } + else { + if( item->data == val ) { + return( item ); + } + } + return( NULL ); +} + +SHLIST *shListGetLastItem( SHLIST *head ) +{ + if( head->prev != head ) + return( head->prev ); + return( NULL ); +} + +SHLIST *shListGetFirstItem( SHLIST *head ) +{ + if( head->next != head ) + return( head->next ); + return( NULL ); +} + +SHLIST *shListGetNItem( SHLIST *head, unsigned long num ) +{ + SHLIST *item; + unsigned long i; + + for(i=0,item=head->next;( (i < num) && (item != head) );i++,item=item->next); + if( item != head ) + return( item ); + return( NULL ); +} + +SHLIST *shListGetNextItem( SHLIST *head, SHLIST *item ) +{ + if( item == NULL ) + return( NULL ); + if( item->next != head ) + return( item->next ); + return( NULL ); +} + +SHLIST *shListGetPrevItem( SHLIST *head, SHLIST *item ) +{ + if( item == NULL ) + return( NULL ); + if( item->prev != head ) + return( item->prev ); + return( NULL ); +} + +void shListDelItem( SHLIST *head, SHLIST *item, shListFree func ) +{ + if( item == NULL ) + return; +#ifdef SH_LIST_DEBUG + fprintf(stderr, "Del %lx\n", (unsigned long)(item->data)); +#endif + (item->prev)->next = item->next; + (item->next)->prev = item->prev; + if( func && item->data ) { + func( (void *)(item->data) ); + } + free( item ); + head->data = (void *)((unsigned long)(head->data) - 1); +} + +void shListInsFirstItem( SHLIST *head, void *val ) +{ /* Insert to the beginning of the list */ + SHLIST *item; + + item = (SHLIST *)malloc( sizeof(SHLIST) ); + if( item == NULL ) + return; + item->data = val; + item->next = head->next; + item->prev = head; + (head->next)->prev = item; + head->next = item; +#ifdef SH_LIST_DEBUG + fprintf(stderr, "Ins First %lx\n", (unsigned long)(item->data)); +#endif + head->data = (void *)((unsigned long)(head->data) + 1); +} + +void shListInsLastItem( SHLIST *head, void *val ) +{ /* Insert to the end of the list */ + SHLIST *item; + + item = (SHLIST *)malloc( sizeof(SHLIST) ); + if( item == NULL ) + return; + item->data = val; + item->next = head; + item->prev = head->prev; + (head->prev)->next = item; + head->prev = item; +#ifdef SH_LIST_DEBUG + fprintf(stderr, "Ins Last %lx\n", (unsigned long)(item->data)); +#endif + head->data = (void *)((unsigned long)(head->data) + 1); +} + +void shListInsBeforeItem( SHLIST *head, void *val, void *etal, + shListCmp func ) +{ + SHLIST *item, *iptr; + + if( func == NULL ) + shListInsFirstItem( head, val ); + else { + item = (SHLIST *)malloc( sizeof(SHLIST) ); + if( item == NULL ) + return; + item->data = val; + for(iptr=head->next;( iptr != head );iptr=iptr->next) + if( func( val, iptr->data, etal ) ) + break; + item->next = iptr; + item->prev = iptr->prev; + (iptr->prev)->next = item; + iptr->prev = item; +#ifdef SH_LIST_DEBUG + fprintf(stderr, "Ins Before %lx\n", (unsigned long)(item->data)); +#endif + head->data = (void *)((unsigned long)(head->data) + 1); + } +} + +void shListDelAllItems( SHLIST *head, shListFree func ) +{ + SHLIST *item; + + for(item=head->next;( item != head );) { + shListDelItem( head, item, func ); + item = head->next; + } + head->data = (void *)0L; +} + +void shListPrintAllItems( SHLIST *head, shListPrint func ) +{ +#ifdef SH_LIST_DEBUG + SHLIST *item; + + for(item=head->next;( item != head );item=item->next) + if( func ) { + func(item->data); + } + else { + fprintf(stderr, "Item: %lx\n",(unsigned long)(item->data)); + } +#endif +} + +unsigned long shListGetCount( SHLIST *head ) +{ + return( (unsigned long)(head->data) ); +} diff --git a/adb/shlist.h b/adb/shlist.h new file mode 100755 index 0000000..0a9b07b --- /dev/null +++ b/adb/shlist.h @@ -0,0 +1,34 @@ +/*-------------------------------------------------------------------*/ +/* List Functionality */ +/*-------------------------------------------------------------------*/ +#ifndef _SHLIST_H_ +#define _SHLIST_H_ + +typedef struct SHLIST_STRUC { + void *data; + struct SHLIST_STRUC *next; + struct SHLIST_STRUC *prev; +} SHLIST; + +typedef int (*shListCmp)( void *valo, void *valn, void *etalon ); +typedef int (*shListPrint)( void *val ); +typedef void (*shListFree)( void *val ); +typedef int (*shListEqual)( void *val, void *idata ); + +void shListInitList( SHLIST *listPtr ); +SHLIST *shListFindItem( SHLIST *head, void *val, shListEqual func ); +SHLIST *shListGetFirstItem( SHLIST *head ); +SHLIST *shListGetNItem( SHLIST *head, unsigned long num ); +SHLIST *shListGetLastItem( SHLIST *head ); +SHLIST *shListGetNextItem( SHLIST *head, SHLIST *item ); +SHLIST *shListGetPrevItem( SHLIST *head, SHLIST *item ); +void shListDelItem( SHLIST *head, SHLIST *item, shListFree func ); +void shListInsFirstItem( SHLIST *head, void *val ); +void shListInsBeforeItem( SHLIST *head, void *val, void *etalon, + shListCmp func ); +void shListInsLastItem( SHLIST *head, void *val ); +void shListDelAllItems( SHLIST *head, shListFree func ); +void shListPrintAllItems( SHLIST *head, shListPrint func ); +unsigned long shListGetCount( SHLIST *head ); + +#endif diff --git a/adb/sockets.c b/adb/sockets.c new file mode 100644 index 0000000..9f1b598 --- /dev/null +++ b/adb/sockets.c @@ -0,0 +1,787 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_SOCKETS +#include "adb.h" + +ADB_MUTEX_DEFINE( socket_list_lock ); + +static void local_socket_close_locked(asocket *s); + +int sendfailmsg(int fd, const char *reason) +{ + char buf[9]; + int len; + len = strlen(reason); + if(len > 0xffff) len = 0xffff; + snprintf(buf, sizeof buf, "FAIL%04x", len); + if(writex(fd, buf, 8)) return -1; + return writex(fd, reason, len); +} + +//extern int online; + +static unsigned local_socket_next_id = 1; + +static asocket local_socket_list = { + .next = &local_socket_list, + .prev = &local_socket_list, +}; + +/* the the list of currently closing local sockets. +** these have no peer anymore, but still packets to +** write to their fd. +*/ +static asocket local_socket_closing_list = { + .next = &local_socket_closing_list, + .prev = &local_socket_closing_list, +}; + +asocket *find_local_socket(unsigned id) +{ + asocket *s; + asocket *result = NULL; + + adb_mutex_lock(&socket_list_lock); + for(s = local_socket_list.next; s != &local_socket_list && !result; s = s->next) { + if(s->id == id) result = s; + } + adb_mutex_unlock(&socket_list_lock); + + return result; +} + +static void +insert_local_socket(asocket* s, asocket* list) +{ + s->next = list; + s->prev = s->next->prev; + s->prev->next = s; + s->next->prev = s; +} + + +void install_local_socket(asocket *s) +{ + adb_mutex_lock(&socket_list_lock); + + s->id = local_socket_next_id++; + insert_local_socket(s, &local_socket_list); + + adb_mutex_unlock(&socket_list_lock); +} + +void remove_socket(asocket *s) +{ + // socket_list_lock should already be held + if (s->prev && s->next) + { + s->prev->next = s->next; + s->next->prev = s->prev; + s->next = 0; + s->prev = 0; + s->id = 0; + } +} + +void close_all_sockets(atransport *t) +{ + asocket *s; + + /* this is a little gross, but since s->close() *will* modify + ** the list out from under you, your options are limited. + */ + adb_mutex_lock(&socket_list_lock); +restart: + for(s = local_socket_list.next; s != &local_socket_list; s = s->next){ + if(s->transport == t || (s->peer && s->peer->transport == t)) { + local_socket_close_locked(s); + goto restart; + } + } + adb_mutex_unlock(&socket_list_lock); +} + +static int local_socket_enqueue(asocket *s, apacket *p) +{ + D("LS(%d): enqueue %d\n", s->id, p->len); + + p->ptr = p->data; + + /* if there is already data queue'd, we will receive + ** events when it's time to write. just add this to + ** the tail + */ + if(s->pkt_first) { + goto enqueue; + } + + /* write as much as we can, until we + ** would block or there is an error/eof + */ + while(p->len > 0) { + int r = adb_write(s->fd, p->ptr, p->len); + if(r > 0) { + p->len -= r; + p->ptr += r; + continue; + } + if((r == 0) || (errno != EAGAIN)) { + D( "LS(%d): not ready, errno=%d: %s\n", s->id, errno, strerror(errno) ); + s->close(s); + return 1; /* not ready (error) */ + } else { + break; + } + } + + if(p->len == 0) { + put_apacket(p); + return 0; /* ready for more data */ + } + +enqueue: + p->next = 0; + if(s->pkt_first) { + s->pkt_last->next = p; + } else { + s->pkt_first = p; + } + s->pkt_last = p; + + /* make sure we are notified when we can drain the queue */ + fdevent_add(&s->fde, FDE_WRITE); + + return 1; /* not ready (backlog) */ +} + +static void local_socket_ready(asocket *s) +{ + /* far side is ready for data, pay attention to + readable events */ + fdevent_add(&s->fde, FDE_READ); +// D("LS(%d): ready()\n", s->id); +} + +static void local_socket_close(asocket *s) +{ + adb_mutex_lock(&socket_list_lock); + local_socket_close_locked(s); + adb_mutex_unlock(&socket_list_lock); +} + +// be sure to hold the socket list lock when calling this +static void local_socket_destroy(asocket *s) +{ + apacket *p, *n; + + /* IMPORTANT: the remove closes the fd + ** that belongs to this socket + */ + fdevent_remove(&s->fde); + + /* dispose of any unwritten data */ + for(p = s->pkt_first; p; p = n) { + D("LS(%d): discarding %d bytes\n", s->id, p->len); + n = p->next; + put_apacket(p); + } + remove_socket(s); + free(s); +} + + +static void local_socket_close_locked(asocket *s) +{ + if(s->peer) { + s->peer->peer = 0; + // tweak to avoid deadlock + if (s->peer->close == local_socket_close) + local_socket_close_locked(s->peer); + else + s->peer->close(s->peer); + } + + /* If we are already closing, or if there are no + ** pending packets, destroy immediately + */ + if (s->closing || s->pkt_first == NULL) { + int id = s->id; + local_socket_destroy(s); + D("LS(%d): closed\n", id); + return; + } + + /* otherwise, put on the closing list + */ + D("LS(%d): closing\n", s->id); + s->closing = 1; + fdevent_del(&s->fde, FDE_READ); + remove_socket(s); + insert_local_socket(s, &local_socket_closing_list); +} + +static void local_socket_event_func(int fd, unsigned ev, void *_s) +{ + asocket *s = _s; + + /* put the FDE_WRITE processing before the FDE_READ + ** in order to simplify the code. + */ + if(ev & FDE_WRITE){ + apacket *p; + + while((p = s->pkt_first) != 0) { + while(p->len > 0) { + int r = adb_write(fd, p->ptr, p->len); + if(r > 0) { + p->ptr += r; + p->len -= r; + continue; + } + if(r < 0) { + /* returning here is ok because FDE_READ will + ** be processed in the next iteration loop + */ + if(errno == EAGAIN) return; + if(errno == EINTR) continue; + } + s->close(s); + return; + } + + if(p->len == 0) { + s->pkt_first = p->next; + if(s->pkt_first == 0) s->pkt_last = 0; + put_apacket(p); + } + } + + /* if we sent the last packet of a closing socket, + ** we can now destroy it. + */ + if (s->closing) { + s->close(s); + return; + } + + /* no more packets queued, so we can ignore + ** writable events again and tell our peer + ** to resume writing + */ + fdevent_del(&s->fde, FDE_WRITE); + s->peer->ready(s->peer); + } + + + if(ev & FDE_READ){ + apacket *p = get_apacket(); + unsigned char *x = p->data; + size_t avail = MAX_PAYLOAD; + int r; + int is_eof = 0; + + while(avail > 0) { + r = adb_read(fd, x, avail); + if(r > 0) { + avail -= r; + x += r; + continue; + } + if(r < 0) { + if(errno == EAGAIN) break; + if(errno == EINTR) continue; + } + + /* r = 0 or unhandled error */ + is_eof = 1; + break; + } + + if((avail == MAX_PAYLOAD) || (s->peer == 0)) { + put_apacket(p); + } else { + p->len = MAX_PAYLOAD - avail; + + r = s->peer->enqueue(s->peer, p); + + if(r < 0) { + /* error return means they closed us as a side-effect + ** and we must return immediately. + ** + ** note that if we still have buffered packets, the + ** socket will be placed on the closing socket list. + ** this handler function will be called again + ** to process FDE_WRITE events. + */ + return; + } + + if(r > 0) { + /* if the remote cannot accept further events, + ** we disable notification of READs. They'll + ** be enabled again when we get a call to ready() + */ + fdevent_del(&s->fde, FDE_READ); + } + } + + if(is_eof) { + s->close(s); + } + } + + if(ev & FDE_ERROR){ + /* this should be caught be the next read or write + ** catching it here means we may skip the last few + ** bytes of readable data. + */ +// s->close(s); + return; + } +} + +asocket *create_local_socket(int fd) +{ + asocket *s = calloc(1, sizeof(asocket)); + if(s == 0) fatal("cannot allocate socket"); + install_local_socket(s); + s->fd = fd; + s->enqueue = local_socket_enqueue; + s->ready = local_socket_ready; + s->close = local_socket_close; + + fdevent_install(&s->fde, fd, local_socket_event_func, s); +/* fdevent_add(&s->fde, FDE_ERROR); */ + //fprintf(stderr, "Created local socket in create_local_socket \n"); + D("LS(%d): created (fd=%d)\n", s->id, s->fd); + return s; +} + +asocket *create_local_service_socket(const char *name) +{ + asocket *s; + int fd; + +#if !ADB_HOST + if (!strcmp(name,"jdwp")) { + return create_jdwp_service_socket(); + } + if (!strcmp(name,"track-jdwp")) { + return create_jdwp_tracker_service_socket(); + } +#endif + fd = service_to_fd(name); + if(fd < 0) return 0; + + s = create_local_socket(fd); + D("LS(%d): bound to '%s'\n", s->id, name); + return s; +} + +#if ADB_HOST +static asocket *create_host_service_socket(const char *name, const char* serial) +{ + asocket *s; + + s = host_service_to_socket(name, serial); + + if (s != NULL) { + D("LS(%d) bound to '%s'\n", s->id, name); + return s; + } + + return s; +} +#endif /* ADB_HOST */ + +/* a Remote socket is used to send/receive data to/from a given transport object +** it needs to be closed when the transport is forcibly destroyed by the user +*/ +typedef struct aremotesocket { + asocket socket; + adisconnect disconnect; +} aremotesocket; + +static int remote_socket_enqueue(asocket *s, apacket *p) +{ + D("Calling remote_socket_enqueue\n"); + p->msg.command = A_WRTE; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + p->msg.data_length = p->len; + send_packet(p, s->transport); + return 1; +} + +static void remote_socket_ready(asocket *s) +{ + D("Calling remote_socket_ready\n"); + apacket *p = get_apacket(); + p->msg.command = A_OKAY; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + send_packet(p, s->transport); +} + +static void remote_socket_close(asocket *s) +{ + D("Calling remote_socket_close\n"); + apacket *p = get_apacket(); + p->msg.command = A_CLSE; + if(s->peer) { + p->msg.arg0 = s->peer->id; + s->peer->peer = 0; + s->peer->close(s->peer); + } + p->msg.arg1 = s->id; + send_packet(p, s->transport); + D("RS(%d): closed\n", s->id); + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +static void remote_socket_disconnect(void* _s, atransport* t) +{ + asocket* s = _s; + asocket* peer = s->peer; + + D("remote_socket_disconnect RS(%d)\n", s->id); + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +asocket *create_remote_socket(unsigned id, atransport *t) +{ + asocket *s = calloc(1, sizeof(aremotesocket)); + adisconnect* dis = &((aremotesocket*)s)->disconnect; + + if(s == 0) fatal("cannot allocate socket"); + s->id = id; + s->enqueue = remote_socket_enqueue; + s->ready = remote_socket_ready; + s->close = remote_socket_close; + s->transport = t; + + dis->func = remote_socket_disconnect; + dis->opaque = s; + add_transport_disconnect( t, dis ); + D("RS(%d): created\n", s->id); + return s; +} + +void connect_to_remote(asocket *s, const char *destination) +{ + D("Connect_to_remote call \n"); + apacket *p = get_apacket(); + int len = strlen(destination) + 1; + + if(len > (MAX_PAYLOAD-1)) { + fatal("destination oversized"); + } + + D("LS(%d): connect('%s')\n", s->id, destination); + p->msg.command = A_OPEN; + p->msg.arg0 = s->id; + p->msg.data_length = len; + strcpy((char*) p->data, destination); + send_packet(p, s->transport); +} + + +/* this is used by magic sockets to rig local sockets to + send the go-ahead message when they connect */ +static void local_socket_ready_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + adb_write(s->fd, "OKAY", 4); + s->ready(s); +} + +/* this is used by magic sockets to rig local sockets to + send the failure message if they are closed before + connected (to avoid closing them without a status message) */ +static void local_socket_close_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + sendfailmsg(s->fd, "closed"); + s->close(s); +} + +unsigned unhex(unsigned char *s, int len) +{ + unsigned n = 0, c; + + while(len-- > 0) { + switch((c = *s++)) { + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': + case '9': + c -= '0'; + break; + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + c = c - 'a' + 10; + break; + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + c = c - 'A' + 10; + break; + default: + return 0xffffffff; + } + + n = (n << 4) | c; + } + + return n; +} + +static int smart_socket_enqueue(asocket *s, apacket *p) +{ + unsigned len; +#if ADB_HOST + char *service = NULL; + char* serial = NULL; + transport_type ttype = kTransportAny; +#endif + + D("SS(%d): enqueue %d\n", s->id, p->len); + + if(s->pkt_first == 0) { + s->pkt_first = p; + s->pkt_last = p; + } else { + if((s->pkt_first->len + p->len) > MAX_PAYLOAD) { + D("SS(%d): overflow\n", s->id); + put_apacket(p); + goto fail; + } + + memcpy(s->pkt_first->data + s->pkt_first->len, + p->data, p->len); + s->pkt_first->len += p->len; + put_apacket(p); + + p = s->pkt_first; + } + + /* don't bother if we can't decode the length */ + if(p->len < 4) return 0; + + len = unhex(p->data, 4); + if((len < 1) || (len > 1024)) { + D("SS(%d): bad size (%d)\n", s->id, len); + goto fail; + } + + D("SS(%d): len is %d\n", s->id, len ); + /* can't do anything until we have the full header */ + if((len + 4) > p->len) { + D("SS(%d): waiting for %d more bytes\n", s->id, len+4 - p->len); + return 0; + } + + p->data[len + 4] = 0; + + D("SS(%d): '%s'\n", s->id, (char*) (p->data + 4)); + +#if ADB_HOST + service = (char *)p->data + 4; + if(!strncmp(service, "host-serial:", strlen("host-serial:"))) { + char* serial_end; + service += strlen("host-serial:"); + + // serial number should follow "host:" + serial_end = strchr(service, ':'); + if (serial_end) { + *serial_end = 0; // terminate string + serial = service; + service = serial_end + 1; + } + } else if (!strncmp(service, "host-usb:", strlen("host-usb:"))) { + ttype = kTransportUsb; + service += strlen("host-usb:"); + } else if (!strncmp(service, "host-local:", strlen("host-local:"))) { + ttype = kTransportLocal; + service += strlen("host-local:"); + } else if (!strncmp(service, "host:", strlen("host:"))) { + ttype = kTransportAny; + service += strlen("host:"); + } else { + service = NULL; + } + + if (service) { + asocket *s2; + + /* some requests are handled immediately -- in that + ** case the handle_host_request() routine has sent + ** the OKAY or FAIL message and all we have to do + ** is clean up. + */ + if(handle_host_request(service, ttype, serial, s->peer->fd, s) == 0) { + /* XXX fail message? */ + D( "SS(%d): handled host service '%s'\n", s->id, service ); + goto fail; + } + if (!strncmp(service, "transport", strlen("transport"))) { + D( "SS(%d): okay transport\n", s->id ); + p->len = 0; + return 0; + } + + /* try to find a local service with this name. + ** if no such service exists, we'll fail out + ** and tear down here. + */ + s2 = create_host_service_socket(service, serial); + if(s2 == 0) { + D( "SS(%d): couldn't create host service '%s'\n", s->id, service ); + sendfailmsg(s->peer->fd, "unknown host service"); + goto fail; + } + + /* we've connected to a local host service, + ** so we make our peer back into a regular + ** local socket and bind it to the new local + ** service socket, acknowledge the successful + ** connection, and close this smart socket now + ** that its work is done. + */ + adb_write(s->peer->fd, "OKAY", 4); + + s->peer->ready = local_socket_ready; + s->peer->close = local_socket_close; + s->peer->peer = s2; + s2->peer = s->peer; + s->peer = 0; + D( "SS(%d): okay\n", s->id ); + s->close(s); + + /* initial state is "ready" */ + s2->ready(s2); + return 0; + } +#else /* !ADB_HOST */ + if (s->transport == NULL) { + char* error_string = "unknown failure"; + s->transport = acquire_one_transport (CS_ANY, + kTransportAny, NULL, &error_string); + + if (s->transport == NULL) { + sendfailmsg(s->peer->fd, error_string); + goto fail; + } + } +#endif + + if(!(s->transport) || (s->transport->connection_state == CS_OFFLINE)) { + /* if there's no remote we fail the connection + ** right here and terminate it + */ + sendfailmsg(s->peer->fd, "device offline (x)"); + goto fail; + } + + + /* instrument our peer to pass the success or fail + ** message back once it connects or closes, then + ** detach from it, request the connection, and + ** tear down + */ + s->peer->ready = local_socket_ready_notify; + s->peer->close = local_socket_close_notify; + s->peer->peer = 0; + /* give him our transport and upref it */ + s->peer->transport = s->transport; + + connect_to_remote(s->peer, (char*) (p->data + 4)); + s->peer = 0; + s->close(s); + return 1; + +fail: + /* we're going to close our peer as a side-effect, so + ** return -1 to signal that state to the local socket + ** who is enqueueing against us + */ + s->close(s); + return -1; +} + +static void smart_socket_ready(asocket *s) +{ + D("SS(%d): ready\n", s->id); +} + +static void smart_socket_close(asocket *s) +{ + D("SS(%d): closed\n", s->id); + if(s->pkt_first){ + put_apacket(s->pkt_first); + } + if(s->peer) { + s->peer->peer = 0; + s->peer->close(s->peer); + } + free(s); +} + +asocket *create_smart_socket(void (*action_cb)(asocket *s, const char *act)) +{ + D("Creating smart socket \n"); + asocket *s = calloc(1, sizeof(asocket)); + if(s == 0) fatal("cannot allocate socket"); + s->id = 0; + s->enqueue = smart_socket_enqueue; + s->ready = smart_socket_ready; + s->close = smart_socket_close; + s->extra = action_cb; + + D("SS(%d): created %p\n", s->id, action_cb); + return s; +} + +void smart_socket_action(asocket *s, const char *act) +{ + +} + +void connect_to_smartsocket(asocket *s) +{ + D("Connecting to smart socket \n"); + asocket *ss = create_smart_socket(smart_socket_action); + s->peer = ss; + ss->peer = s; + s->ready(s); +} diff --git a/adb/sockets.dia b/adb/sockets.dia Binary files differnew file mode 100644 index 0000000..c626f20 --- /dev/null +++ b/adb/sockets.dia diff --git a/adb/sysdeps.h b/adb/sysdeps.h new file mode 100644 index 0000000..e5d17a8 --- /dev/null +++ b/adb/sysdeps.h @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* this file contains system-dependent definitions used by ADB + * they're related to threads, sockets and file descriptors + */ +#ifndef _ADB_SYSDEPS_H +#define _ADB_SYSDEPS_H + +#ifdef __CYGWIN__ +# undef _WIN32 +#endif + +#ifdef _WIN32 + +#include <windows.h> +#include <winsock2.h> +#include <ws2tcpip.h> +#include <process.h> +#include <fcntl.h> +#include <io.h> +#include <sys/stat.h> +#include <errno.h> +#include <ctype.h> + +#define OS_PATH_SEPARATOR '\\' +#define OS_PATH_SEPARATOR_STR "\\" + +typedef CRITICAL_SECTION adb_mutex_t; + +#define ADB_MUTEX_DEFINE(x) adb_mutex_t x + +/* declare all mutexes */ +#define ADB_MUTEX(x) extern adb_mutex_t x; +#include "mutex_list.h" + +extern void adb_sysdeps_init(void); + +static __inline__ void adb_mutex_lock( adb_mutex_t* lock ) +{ + EnterCriticalSection( lock ); +} + +static __inline__ void adb_mutex_unlock( adb_mutex_t* lock ) +{ + LeaveCriticalSection( lock ); +} + +typedef struct { unsigned tid; } adb_thread_t; + +typedef void* (*adb_thread_func_t)(void* arg); + +typedef void (*win_thread_func_t)(void* arg); + +static __inline__ int adb_thread_create( adb_thread_t *thread, adb_thread_func_t func, void* arg) +{ + thread->tid = _beginthread( (win_thread_func_t)func, 0, arg ); + if (thread->tid == (unsigned)-1L) { + return -1; + } + return 0; +} + +static __inline__ void close_on_exec(int fd) +{ + /* nothing really */ +} + +extern void disable_tcp_nagle(int fd); + +#define lstat stat /* no symlinks on Win32 */ + +#define S_ISLNK(m) 0 /* no symlinks on Win32 */ + +static __inline__ int adb_unlink(const char* path) +{ + int rc = unlink(path); + + if (rc == -1 && errno == EACCES) { + /* unlink returns EACCES when the file is read-only, so we first */ + /* try to make it writable, then unlink again... */ + rc = chmod(path, _S_IREAD|_S_IWRITE ); + if (rc == 0) + rc = unlink(path); + } + return rc; +} +#undef unlink +#define unlink ___xxx_unlink + +static __inline__ int adb_mkdir(const char* path, int mode) +{ + return _mkdir(path); +} +#undef mkdir +#define mkdir ___xxx_mkdir + +extern int adb_open(const char* path, int options); +extern int adb_creat(const char* path, int mode); +extern int adb_read(int fd, void* buf, int len); +extern int adb_write(int fd, const void* buf, int len); +extern int adb_lseek(int fd, int pos, int where); +extern int adb_close(int fd); + +static __inline__ int unix_close(int fd) +{ + return close(fd); +} +#undef close +#define close ____xxx_close + +static __inline__ int unix_read(int fd, void* buf, size_t len) +{ + return read(fd, buf, len); +} +#undef read +#define read ___xxx_read + +static __inline__ int unix_write(int fd, const void* buf, size_t len) +{ + return write(fd, buf, len); +} +#undef write +#define write ___xxx_write + +static __inline__ int adb_open_mode(const char* path, int options, int mode) +{ + return adb_open(path, options); +} + +static __inline__ int unix_open(const char* path, int options,...) +{ + if ((options & O_CREAT) == 0) + { + return open(path, options); + } + else + { + int mode; + va_list args; + va_start( args, options ); + mode = va_arg( args, int ); + va_end( args ); + return open(path, options, mode); + } +} +#define open ___xxx_unix_open + + +/* normally provided by <cutils/misc.h> */ +extern void* load_file(const char* pathname, unsigned* psize); + +/* normally provided by <cutils/sockets.h> */ +extern int socket_loopback_client(int port, int type); +extern int socket_network_client(const char *host, int port, int type); +extern int socket_loopback_server(int port, int type); +extern int socket_inaddr_any_server(int port, int type); + +/* normally provided by <cutils/fdevent.h> */ + +#define FDE_READ 0x0001 +#define FDE_WRITE 0x0002 +#define FDE_ERROR 0x0004 +#define FDE_DONT_CLOSE 0x0080 + +typedef struct fdevent fdevent; + +typedef void (*fd_func)(int fd, unsigned events, void *userdata); + +fdevent *fdevent_create(int fd, fd_func func, void *arg); +void fdevent_destroy(fdevent *fde); +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg); +void fdevent_remove(fdevent *item); +void fdevent_set(fdevent *fde, unsigned events); +void fdevent_add(fdevent *fde, unsigned events); +void fdevent_del(fdevent *fde, unsigned events); +void fdevent_loop(); + +struct fdevent { + fdevent *next; + fdevent *prev; + + int fd; + unsigned short state; + unsigned short events; + + fd_func func; + void *arg; +}; + +static __inline__ void adb_sleep_ms( int mseconds ) +{ + Sleep( mseconds ); +} + +extern int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen); + +#undef accept +#define accept ___xxx_accept + +static __inline__ int adb_socket_setbufsize( int fd, int bufsize ) +{ + int opt = bufsize; + return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char*)&opt, sizeof(opt)); +} + +extern int adb_socketpair( int sv[2] ); + +static __inline__ char* adb_dirstart( const char* path ) +{ + char* p = strchr(path, '/'); + char* p2 = strchr(path, '\\'); + + if ( !p ) + p = p2; + else if ( p2 && p2 > p ) + p = p2; + + return p; +} + +static __inline__ char* adb_dirstop( const char* path ) +{ + char* p = strrchr(path, '/'); + char* p2 = strrchr(path, '\\'); + + if ( !p ) + p = p2; + else if ( p2 && p2 > p ) + p = p2; + + return p; +} + +static __inline__ int adb_is_absolute_host_path( const char* path ) +{ + return isalpha(path[0]) && path[1] == ':' && path[2] == '\\'; +} + +#else /* !_WIN32 a.k.a. Unix */ + +#include <cutils/fdevent.h> +#include <cutils/sockets.h> +#include <cutils/properties.h> +#include <cutils/misc.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdarg.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <string.h> + +#define OS_PATH_SEPARATOR '/' +#define OS_PATH_SEPARATOR_STR "/" + +typedef pthread_mutex_t adb_mutex_t; +#define ADB_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#define adb_mutex_init pthread_mutex_init +#define adb_mutex_lock pthread_mutex_lock +#define adb_mutex_unlock pthread_mutex_unlock +#define adb_mutex_destroy pthread_mutex_destroy + +#define ADB_MUTEX_DEFINE(m) static adb_mutex_t m = PTHREAD_MUTEX_INITIALIZER + +#define adb_cond_t pthread_cond_t +#define adb_cond_init pthread_cond_init +#define adb_cond_wait pthread_cond_wait +#define adb_cond_broadcast pthread_cond_broadcast +#define adb_cond_signal pthread_cond_signal +#define adb_cond_destroy pthread_cond_destroy + +static __inline__ void close_on_exec(int fd) +{ + fcntl( fd, F_SETFD, FD_CLOEXEC ); +} + +static __inline__ int unix_open(const char* path, int options,...) +{ + if ((options & O_CREAT) == 0) + { + return open(path, options); + } + else + { + int mode; + va_list args; + va_start( args, options ); + mode = va_arg( args, int ); + va_end( args ); + return open(path, options, mode); + } +} + +static __inline__ int adb_open_mode( const char* pathname, int options, int mode ) +{ + return open( pathname, options, mode ); +} + + +static __inline__ int adb_open( const char* pathname, int options ) +{ + int fd = open( pathname, options ); + if (fd < 0) + return -1; + close_on_exec( fd ); + return fd; +} +#undef open +#define open ___xxx_open + +static __inline__ int adb_close(int fd) +{ + return close(fd); +} +#undef close +#define close ____xxx_close + + +static __inline__ int adb_read(int fd, void* buf, size_t len) +{ + return read(fd, buf, len); +} + +#undef read +#define read ___xxx_read + +static __inline__ int adb_write(int fd, const void* buf, size_t len) +{ + return write(fd, buf, len); +} +#undef write +#define write ___xxx_write + +static __inline__ int adb_lseek(int fd, int pos, int where) +{ + return lseek(fd, pos, where); +} +#undef lseek +#define lseek ___xxx_lseek + +static __inline__ int adb_unlink(const char* path) +{ + return unlink(path); +} +#undef unlink +#define unlink ___xxx_unlink + +static __inline__ int adb_creat(const char* path, int mode) +{ + int fd = creat(path, mode); + + if ( fd < 0 ) + return -1; + + close_on_exec(fd); + return fd; +} +#undef creat +#define creat ___xxx_creat + +static __inline__ int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen) +{ + return accept( serverfd, addr, addrlen ); +} + +#undef accept +#define accept ___xxx_accept + +#define unix_read adb_read +#define unix_write adb_write +#define unix_close adb_close + +typedef pthread_t adb_thread_t; + +typedef void* (*adb_thread_func_t)( void* arg ); + +static __inline__ int adb_thread_create( adb_thread_t *pthread, adb_thread_func_t start, void* arg ) +{ + pthread_attr_t attr; + + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + + return pthread_create( pthread, &attr, start, arg ); +} + +static __inline__ int adb_socket_setbufsize( int fd, int bufsize ) +{ + int opt = bufsize; + return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); +} + +static __inline__ void disable_tcp_nagle(int fd) +{ + int on = 1; + setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on) ); +} + + +static __inline__ int unix_socketpair( int d, int type, int protocol, int sv[2] ) +{ + return socketpair( d, type, protocol, sv ); +} + +static __inline__ int adb_socketpair( int sv[2] ) +{ + int rc; + + rc = unix_socketpair( AF_UNIX, SOCK_STREAM, 0, sv ); + if (rc < 0) + return -1; + + close_on_exec( sv[0] ); + close_on_exec( sv[1] ); + return 0; +} + +#undef socketpair +#define socketpair ___xxx_socketpair + +static __inline__ void adb_sleep_ms( int mseconds ) +{ + usleep( mseconds*1000 ); +} + +static __inline__ int adb_mkdir(const char* path, int mode) +{ + return mkdir(path, mode); +} +#undef mkdir +#define mkdir ___xxx_mkdir + +static __inline__ void adb_sysdeps_init(void) +{ +} + +static __inline__ char* adb_dirstart(const char* path) +{ + return strchr(path, '/'); +} + +static __inline__ char* adb_dirstop(const char* path) +{ + return strrchr(path, '/'); +} + +static __inline__ int adb_is_absolute_host_path( const char* path ) +{ + return path[0] == '/'; +} + +#endif /* !_WIN32 */ + +#endif /* _ADB_SYSDEPS_H */ diff --git a/adb/sysdeps_win32.c b/adb/sysdeps_win32.c new file mode 100644 index 0000000..c2a9a98 --- /dev/null +++ b/adb/sysdeps_win32.c @@ -0,0 +1,1953 @@ +#include "sysdeps.h" +#include <windows.h> +#include <winsock2.h> +#include <stdio.h> +#include <errno.h> +#define TRACE_TAG TRACE_SYSDEPS +#include "adb.h" + +extern void fatal(const char *fmt, ...); + +#define assert(cond) do { if (!(cond)) fatal( "assertion failed '%s' on %s:%ld\n", #cond, __FILE__, __LINE__ ); } while (0) + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** replaces libs/cutils/load_file.c *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +void *load_file(const char *fn, unsigned *_sz) +{ + HANDLE file; + char *data; + DWORD file_size; + + file = CreateFile( fn, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + 0, + NULL ); + + if (file == INVALID_HANDLE_VALUE) + return NULL; + + file_size = GetFileSize( file, NULL ); + data = NULL; + + if (file_size > 0) { + data = (char*) malloc( file_size + 1 ); + if (data == NULL) { + D("load_file: could not allocate %ld bytes\n", file_size ); + file_size = 0; + } else { + DWORD out_bytes; + + if ( !ReadFile( file, data, file_size, &out_bytes, NULL ) || + out_bytes != file_size ) + { + D("load_file: could not read %ld bytes from '%s'\n", file_size, fn); + free(data); + data = NULL; + file_size = 0; + } + } + } + CloseHandle( file ); + + *_sz = (unsigned) file_size; + return data; +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** common file descriptor handling *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +typedef const struct FHClassRec_* FHClass; + +typedef struct FHRec_* FH; + +typedef struct EventHookRec_* EventHook; + +typedef struct FHClassRec_ +{ + void (*_fh_init) ( FH f ); + int (*_fh_close)( FH f ); + int (*_fh_lseek)( FH f, int pos, int origin ); + int (*_fh_read) ( FH f, void* buf, int len ); + int (*_fh_write)( FH f, const void* buf, int len ); + void (*_fh_hook) ( FH f, int events, EventHook hook ); + +} FHClassRec; + +/* used to emulate unix-domain socket pairs */ +typedef struct SocketPairRec_* SocketPair; + +typedef struct FHRec_ +{ + FHClass clazz; + int used; + int eof; + union { + HANDLE handle; + SOCKET socket; + SocketPair pair; + } u; + + HANDLE event; + int mask; + + char name[32]; + +} FHRec; + +#define fh_handle u.handle +#define fh_socket u.socket +#define fh_pair u.pair + +#define WIN32_FH_BASE 100 + +#define WIN32_MAX_FHS 128 + +static adb_mutex_t _win32_lock; +static FHRec _win32_fhs[ WIN32_MAX_FHS ]; +static int _win32_fh_count; + +static FH +_fh_from_int( int fd ) +{ + FH f; + + fd -= WIN32_FH_BASE; + + if (fd < 0 || fd >= _win32_fh_count) { + D( "_fh_from_int: invalid fd %d\n", fd + WIN32_FH_BASE ); + errno = EBADF; + return NULL; + } + + f = &_win32_fhs[fd]; + + if (f->used == 0) { + D( "_fh_from_int: invalid fd %d\n", fd + WIN32_FH_BASE ); + errno = EBADF; + return NULL; + } + + return f; +} + + +static int +_fh_to_int( FH f ) +{ + if (f && f->used && f >= _win32_fhs && f < _win32_fhs + WIN32_MAX_FHS) + return (int)(f - _win32_fhs) + WIN32_FH_BASE; + + return -1; +} + +static FH +_fh_alloc( FHClass clazz ) +{ + int nn; + FH f = NULL; + + adb_mutex_lock( &_win32_lock ); + + if (_win32_fh_count < WIN32_MAX_FHS) { + f = &_win32_fhs[ _win32_fh_count++ ]; + goto Exit; + } + + for (nn = 0; nn < WIN32_MAX_FHS; nn++) { + if ( _win32_fhs[nn].clazz == NULL) { + f = &_win32_fhs[nn]; + goto Exit; + } + } + D( "_fh_alloc: no more free file descriptors\n" ); +Exit: + if (f) { + f->clazz = clazz; + f->used = 1; + f->eof = 0; + clazz->_fh_init(f); + } + adb_mutex_unlock( &_win32_lock ); + return f; +} + + +static int +_fh_close( FH f ) +{ + if ( f->used ) { + f->clazz->_fh_close( f ); + f->used = 0; + f->eof = 0; + f->clazz = NULL; + } + return 0; +} + +/* forward definitions */ +static const FHClassRec _fh_file_class; +static const FHClassRec _fh_socket_class; + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** file-based descriptor handling *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +static void +_fh_file_init( FH f ) +{ + f->fh_handle = INVALID_HANDLE_VALUE; +} + +static int +_fh_file_close( FH f ) +{ + CloseHandle( f->fh_handle ); + f->fh_handle = INVALID_HANDLE_VALUE; + return 0; +} + +static int +_fh_file_read( FH f, void* buf, int len ) +{ + DWORD read_bytes; + + if ( !ReadFile( f->fh_handle, buf, (DWORD)len, &read_bytes, NULL ) ) { + D( "adb_read: could not read %d bytes from %s\n", len, f->name ); + errno = EIO; + return -1; + } else if (read_bytes < (DWORD)len) { + f->eof = 1; + } + return (int)read_bytes; +} + +static int +_fh_file_write( FH f, const void* buf, int len ) +{ + DWORD wrote_bytes; + + if ( !WriteFile( f->fh_handle, buf, (DWORD)len, &wrote_bytes, NULL ) ) { + D( "adb_file_write: could not write %d bytes from %s\n", len, f->name ); + errno = EIO; + return -1; + } else if (wrote_bytes < (DWORD)len) { + f->eof = 1; + } + return (int)wrote_bytes; +} + +static int +_fh_file_lseek( FH f, int pos, int origin ) +{ + DWORD method; + DWORD result; + + switch (origin) + { + case SEEK_SET: method = FILE_BEGIN; break; + case SEEK_CUR: method = FILE_CURRENT; break; + case SEEK_END: method = FILE_END; break; + default: + errno = EINVAL; + return -1; + } + + result = SetFilePointer( f->fh_handle, pos, NULL, method ); + if (result == INVALID_SET_FILE_POINTER) { + errno = EIO; + return -1; + } else { + f->eof = 0; + } + return (int)result; +} + +static void _fh_file_hook( FH f, int event, EventHook eventhook ); /* forward */ + +static const FHClassRec _fh_file_class = +{ + _fh_file_init, + _fh_file_close, + _fh_file_lseek, + _fh_file_read, + _fh_file_write, + _fh_file_hook +}; + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** file-based descriptor handling *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +int adb_open(const char* path, int options) +{ + FH f; + + DWORD desiredAccess = 0; + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + switch (options) { + case O_RDONLY: + desiredAccess = GENERIC_READ; + break; + case O_WRONLY: + desiredAccess = GENERIC_WRITE; + break; + case O_RDWR: + desiredAccess = GENERIC_READ | GENERIC_WRITE; + break; + default: + D("adb_open: invalid options (0x%0x)\n", options); + errno = EINVAL; + return -1; + } + + f = _fh_alloc( &_fh_file_class ); + if ( !f ) { + errno = ENOMEM; + return -1; + } + + f->fh_handle = CreateFile( path, desiredAccess, shareMode, NULL, OPEN_EXISTING, + 0, NULL ); + + if ( f->fh_handle == INVALID_HANDLE_VALUE ) { + _fh_close(f); + D( "adb_open: could not open '%s':", path ); + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + D( "file not found\n" ); + errno = ENOENT; + return -1; + + case ERROR_PATH_NOT_FOUND: + D( "path not found\n" ); + errno = ENOTDIR; + return -1; + + default: + D( "unknown error\n" ); + errno = ENOENT; + return -1; + } + } + + snprintf( f->name, sizeof(f->name), "%d(%s)", _fh_to_int(f), path ); + D( "adb_open: '%s' => fd %d\n", path, _fh_to_int(f) ); + return _fh_to_int(f); +} + +/* ignore mode on Win32 */ +int adb_creat(const char* path, int mode) +{ + FH f; + + f = _fh_alloc( &_fh_file_class ); + if ( !f ) { + errno = ENOMEM; + return -1; + } + + f->fh_handle = CreateFile( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + NULL ); + + if ( f->fh_handle == INVALID_HANDLE_VALUE ) { + _fh_close(f); + D( "adb_creat: could not open '%s':", path ); + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + D( "file not found\n" ); + errno = ENOENT; + return -1; + + case ERROR_PATH_NOT_FOUND: + D( "path not found\n" ); + errno = ENOTDIR; + return -1; + + default: + D( "unknown error\n" ); + errno = ENOENT; + return -1; + } + } + snprintf( f->name, sizeof(f->name), "%d(%s)", _fh_to_int(f), path ); + D( "adb_creat: '%s' => fd %d\n", path, _fh_to_int(f) ); + return _fh_to_int(f); +} + + +int adb_read(int fd, void* buf, int len) +{ + FH f = _fh_from_int(fd); + + if (f == NULL) { + return -1; + } + + return f->clazz->_fh_read( f, buf, len ); +} + + +int adb_write(int fd, const void* buf, int len) +{ + FH f = _fh_from_int(fd); + + if (f == NULL) { + return -1; + } + + return f->clazz->_fh_write(f, buf, len); +} + + +int adb_lseek(int fd, int pos, int where) +{ + FH f = _fh_from_int(fd); + + if (!f) { + return -1; + } + + return f->clazz->_fh_lseek(f, pos, where); +} + + +int adb_close(int fd) +{ + FH f = _fh_from_int(fd); + + if (!f) { + return -1; + } + + D( "adb_close: %s\n", f->name); + _fh_close(f); + return 0; +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** socket-based file descriptors *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +static void +_socket_set_errno( void ) +{ + switch (WSAGetLastError()) { + case 0: errno = 0; break; + case WSAEWOULDBLOCK: errno = EAGAIN; break; + case WSAEINTR: errno = EINTR; break; + default: + D( "_socket_set_errno: unhandled value %d\n", WSAGetLastError() ); + errno = EINVAL; + } +} + +static void +_fh_socket_init( FH f ) +{ + f->fh_socket = INVALID_SOCKET; + f->event = WSACreateEvent(); + f->mask = 0; +} + +static int +_fh_socket_close( FH f ) +{ + /* gently tell any peer that we're closing the socket */ + shutdown( f->fh_socket, SD_BOTH ); + closesocket( f->fh_socket ); + f->fh_socket = INVALID_SOCKET; + CloseHandle( f->event ); + f->mask = 0; + return 0; +} + +static int +_fh_socket_lseek( FH f, int pos, int origin ) +{ + errno = EPIPE; + return -1; +} + +static int +_fh_socket_read( FH f, void* buf, int len ) +{ + int result = recv( f->fh_socket, buf, len, 0 ); + if (result == SOCKET_ERROR) { + _socket_set_errno(); + result = -1; + } + return result; +} + +static int +_fh_socket_write( FH f, const void* buf, int len ) +{ + int result = send( f->fh_socket, buf, len, 0 ); + if (result == SOCKET_ERROR) { + _socket_set_errno(); + result = -1; + } + return result; +} + +static void _fh_socket_hook( FH f, int event, EventHook hook ); /* forward */ + +static const FHClassRec _fh_socket_class = +{ + _fh_socket_init, + _fh_socket_close, + _fh_socket_lseek, + _fh_socket_read, + _fh_socket_write, + _fh_socket_hook +}; + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** replacement for libs/cutils/socket_xxxx.c *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +#include <winsock2.h> + +static int _winsock_init; + +static void +_cleanup_winsock( void ) +{ + WSACleanup(); +} + +static void +_init_winsock( void ) +{ + if (!_winsock_init) { + WSADATA wsaData; + int rc = WSAStartup( MAKEWORD(2,2), &wsaData); + if (rc != 0) { + fatal( "adb: could not initialize Winsock\n" ); + } + atexit( _cleanup_winsock ); + _winsock_init = 1; + } +} + +int socket_loopback_client(int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct sockaddr_in addr; + SOCKET s; + + if (!f) + return -1; + + if (!_winsock_init) + _init_winsock(); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket(AF_INET, type, 0); + if(s == INVALID_SOCKET) { + D("socket_loopback_client: could not create socket\n" ); + _fh_close(f); + return -1; + } + + f->fh_socket = s; + if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + D("socket_loopback_client: could not connect to %s:%d\n", type != SOCK_STREAM ? "udp" : "tcp", port ); + _fh_close(f); + return -1; + } + snprintf( f->name, sizeof(f->name), "%d(lo-client:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_loopback_client: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + +#define LISTEN_BACKLOG 4 + +int socket_loopback_server(int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct sockaddr_in addr; + SOCKET s; + int n; + + if (!f) { + return -1; + } + + if (!_winsock_init) + _init_winsock(); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket(AF_INET, type, 0); + if(s == INVALID_SOCKET) return -1; + + f->fh_socket = s; + + n = 1; + setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&n, sizeof(n)); + + if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + _fh_close(f); + return -1; + } + if (type == SOCK_STREAM) { + int ret; + + ret = listen(s, LISTEN_BACKLOG); + if (ret < 0) { + _fh_close(f); + return -1; + } + } + snprintf( f->name, sizeof(f->name), "%d(lo-server:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_loopback_server: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + + +int socket_network_client(const char *host, int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct hostent *hp; + struct sockaddr_in addr; + SOCKET s; + + if (!f) + return -1; + + if (!_winsock_init) + _init_winsock(); + + hp = gethostbyname(host); + if(hp == 0) { + _fh_close(f); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = hp->h_addrtype; + addr.sin_port = htons(port); + memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); + + s = socket(hp->h_addrtype, type, 0); + if(s == INVALID_SOCKET) { + _fh_close(f); + return -1; + } + f->fh_socket = s; + + if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + _fh_close(f); + return -1; + } + + snprintf( f->name, sizeof(f->name), "%d(net-client:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_network_client: host '%s' port %d type %s => fd %d\n", host, port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + + +int socket_inaddr_any_server(int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct sockaddr_in addr; + SOCKET s; + int n; + + if (!f) + return -1; + + if (!_winsock_init) + _init_winsock(); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + s = socket(AF_INET, type, 0); + if(s == INVALID_SOCKET) { + _fh_close(f); + return -1; + } + + f->fh_socket = s; + n = 1; + setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&n, sizeof(n)); + + if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + _fh_close(f); + return -1; + } + + if (type == SOCK_STREAM) { + int ret; + + ret = listen(s, LISTEN_BACKLOG); + if (ret < 0) { + _fh_close(f); + return -1; + } + } + snprintf( f->name, sizeof(f->name), "%d(any-server:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_inaddr_server: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + +#undef accept +int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen) +{ + FH serverfh = _fh_from_int(serverfd); + FH fh; + + if ( !serverfh || serverfh->clazz != &_fh_socket_class ) { + D( "adb_socket_accept: invalid fd %d\n", serverfd ); + return -1; + } + + fh = _fh_alloc( &_fh_socket_class ); + if (!fh) { + D( "adb_socket_accept: not enough memory to allocate accepted socket descriptor\n" ); + return -1; + } + + fh->fh_socket = accept( serverfh->fh_socket, addr, addrlen ); + if (fh->fh_socket == INVALID_SOCKET) { + _fh_close( fh ); + D( "adb_socket_accept: accept on fd %d return error %ld\n", serverfd, GetLastError() ); + return -1; + } + + snprintf( fh->name, sizeof(fh->name), "%d(accept:%s)", _fh_to_int(fh), serverfh->name ); + D( "adb_socket_accept on fd %d returns fd %d\n", serverfd, _fh_to_int(fh) ); + return _fh_to_int(fh); +} + + +void disable_tcp_nagle(int fd) +{ + FH fh = _fh_from_int(fd); + int on; + + if ( !fh || fh->clazz != &_fh_socket_class ) + return; + + setsockopt( fh->fh_socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(on) ); +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** emulated socketpairs *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +/* we implement socketpairs directly in use space for the following reasons: + * - it avoids copying data from/to the Nt kernel + * - it allows us to implement fdevent hooks easily and cheaply, something + * that is not possible with standard Win32 pipes !! + * + * basically, we use two circular buffers, each one corresponding to a given + * direction. + * + * each buffer is implemented as two regions: + * + * region A which is (a_start,a_end) + * region B which is (0, b_end) with b_end <= a_start + * + * an empty buffer has: a_start = a_end = b_end = 0 + * + * a_start is the pointer where we start reading data + * a_end is the pointer where we start writing data, unless it is BUFFER_SIZE, + * then you start writing at b_end + * + * the buffer is full when b_end == a_start && a_end == BUFFER_SIZE + * + * there is room when b_end < a_start || a_end < BUFER_SIZE + * + * when reading, a_start is incremented, it a_start meets a_end, then + * we do: a_start = 0, a_end = b_end, b_end = 0, and keep going on.. + */ + +#define BIP_BUFFER_SIZE 4096 + +#if 0 +#include <stdio.h> +# define BIPD(x) D x +# define BIPDUMP bip_dump_hex + +static void bip_dump_hex( const unsigned char* ptr, size_t len ) +{ + int nn, len2 = len; + + if (len2 > 8) len2 = 8; + + for (nn = 0; nn < len2; nn++) + printf("%02x", ptr[nn]); + printf(" "); + + for (nn = 0; nn < len2; nn++) { + int c = ptr[nn]; + if (c < 32 || c > 127) + c = '.'; + printf("%c", c); + } + printf("\n"); + fflush(stdout); +} + +#else +# define BIPD(x) do {} while (0) +# define BIPDUMP(p,l) BIPD(p) +#endif + +typedef struct BipBufferRec_ +{ + int a_start; + int a_end; + int b_end; + int fdin; + int fdout; + int closed; + int can_write; /* boolean */ + HANDLE evt_write; /* event signaled when one can write to a buffer */ + int can_read; /* boolean */ + HANDLE evt_read; /* event signaled when one can read from a buffer */ + CRITICAL_SECTION lock; + unsigned char buff[ BIP_BUFFER_SIZE ]; + +} BipBufferRec, *BipBuffer; + +static void +bip_buffer_init( BipBuffer buffer ) +{ + D( "bit_buffer_init %p\n", buffer ); + buffer->a_start = 0; + buffer->a_end = 0; + buffer->b_end = 0; + buffer->can_write = 1; + buffer->can_read = 0; + buffer->fdin = 0; + buffer->fdout = 0; + buffer->closed = 0; + buffer->evt_write = CreateEvent( NULL, TRUE, TRUE, NULL ); + buffer->evt_read = CreateEvent( NULL, TRUE, FALSE, NULL ); + InitializeCriticalSection( &buffer->lock ); +} + +static void +bip_buffer_close( BipBuffer bip ) +{ + bip->closed = 1; + + if (!bip->can_read) { + SetEvent( bip->evt_read ); + } + if (!bip->can_write) { + SetEvent( bip->evt_write ); + } +} + +static void +bip_buffer_done( BipBuffer bip ) +{ + BIPD(( "bip_buffer_done: %d->%d\n", bip->fdin, bip->fdout )); + CloseHandle( bip->evt_read ); + CloseHandle( bip->evt_write ); + DeleteCriticalSection( &bip->lock ); +} + +static int +bip_buffer_write( BipBuffer bip, const void* src, int len ) +{ + int avail, count = 0; + + if (len <= 0) + return 0; + + BIPD(( "bip_buffer_write: enter %d->%d len %d\n", bip->fdin, bip->fdout, len )); + BIPDUMP( src, len ); + + EnterCriticalSection( &bip->lock ); + + while (!bip->can_write) { + int ret; + LeaveCriticalSection( &bip->lock ); + + if (bip->closed) { + errno = EPIPE; + return -1; + } + /* spinlocking here is probably unfair, but let's live with it */ + ret = WaitForSingleObject( bip->evt_write, INFINITE ); + if (ret != WAIT_OBJECT_0) { /* buffer probably closed */ + D( "bip_buffer_write: error %d->%d WaitForSingleObject returned %d, error %ld\n", bip->fdin, bip->fdout, ret, GetLastError() ); + return 0; + } + if (bip->closed) { + errno = EPIPE; + return -1; + } + EnterCriticalSection( &bip->lock ); + } + + BIPD(( "bip_buffer_write: exec %d->%d len %d\n", bip->fdin, bip->fdout, len )); + + avail = BIP_BUFFER_SIZE - bip->a_end; + if (avail > 0) + { + /* we can append to region A */ + if (avail > len) + avail = len; + + memcpy( bip->buff + bip->a_end, src, avail ); + src += avail; + count += avail; + len -= avail; + + bip->a_end += avail; + if (bip->a_end == BIP_BUFFER_SIZE && bip->a_start == 0) { + bip->can_write = 0; + ResetEvent( bip->evt_write ); + goto Exit; + } + } + + if (len == 0) + goto Exit; + + avail = bip->a_start - bip->b_end; + assert( avail > 0 ); /* since can_write is TRUE */ + + if (avail > len) + avail = len; + + memcpy( bip->buff + bip->b_end, src, avail ); + count += avail; + bip->b_end += avail; + + if (bip->b_end == bip->a_start) { + bip->can_write = 0; + ResetEvent( bip->evt_write ); + } + +Exit: + assert( count > 0 ); + + if ( !bip->can_read ) { + bip->can_read = 1; + SetEvent( bip->evt_read ); + } + + BIPD(( "bip_buffer_write: exit %d->%d count %d (as=%d ae=%d be=%d cw=%d cr=%d\n", + bip->fdin, bip->fdout, count, bip->a_start, bip->a_end, bip->b_end, bip->can_write, bip->can_read )); + LeaveCriticalSection( &bip->lock ); + + return count; + } + +static int +bip_buffer_read( BipBuffer bip, void* dst, int len ) +{ + int avail, count = 0; + + if (len <= 0) + return 0; + + BIPD(( "bip_buffer_read: enter %d->%d len %d\n", bip->fdin, bip->fdout, len )); + + EnterCriticalSection( &bip->lock ); + while ( !bip->can_read ) + { +#if 0 + LeaveCriticalSection( &bip->lock ); + errno = EAGAIN; + return -1; +#else + int ret; + LeaveCriticalSection( &bip->lock ); + + if (bip->closed) { + errno = EPIPE; + return -1; + } + + ret = WaitForSingleObject( bip->evt_read, INFINITE ); + if (ret != WAIT_OBJECT_0) { /* probably closed buffer */ + D( "bip_buffer_read: error %d->%d WaitForSingleObject returned %d, error %ld\n", bip->fdin, bip->fdout, ret, GetLastError()); + return 0; + } + if (bip->closed) { + errno = EPIPE; + return -1; + } + EnterCriticalSection( &bip->lock ); +#endif + } + + BIPD(( "bip_buffer_read: exec %d->%d len %d\n", bip->fdin, bip->fdout, len )); + + avail = bip->a_end - bip->a_start; + assert( avail > 0 ); /* since can_read is TRUE */ + + if (avail > len) + avail = len; + + memcpy( dst, bip->buff + bip->a_start, avail ); + dst += avail; + count += avail; + len -= avail; + + bip->a_start += avail; + if (bip->a_start < bip->a_end) + goto Exit; + + bip->a_start = 0; + bip->a_end = bip->b_end; + bip->b_end = 0; + + avail = bip->a_end; + if (avail > 0) { + if (avail > len) + avail = len; + memcpy( dst, bip->buff, avail ); + count += avail; + bip->a_start += avail; + + if ( bip->a_start < bip->a_end ) + goto Exit; + + bip->a_start = bip->a_end = 0; + } + + bip->can_read = 0; + ResetEvent( bip->evt_read ); + +Exit: + assert( count > 0 ); + + if (!bip->can_write ) { + bip->can_write = 1; + SetEvent( bip->evt_write ); + } + + BIPDUMP( (const unsigned char*)dst - count, count ); + BIPD(( "bip_buffer_read: exit %d->%d count %d (as=%d ae=%d be=%d cw=%d cr=%d\n", + bip->fdin, bip->fdout, count, bip->a_start, bip->a_end, bip->b_end, bip->can_write, bip->can_read )); + LeaveCriticalSection( &bip->lock ); + + return count; +} + +typedef struct SocketPairRec_ +{ + BipBufferRec a2b_bip; + BipBufferRec b2a_bip; + FH a_fd; + int used; + +} SocketPairRec; + +void _fh_socketpair_init( FH f ) +{ + f->fh_pair = NULL; +} + +static int +_fh_socketpair_close( FH f ) +{ + if ( f->fh_pair ) { + SocketPair pair = f->fh_pair; + + if ( f == pair->a_fd ) { + pair->a_fd = NULL; + } + + bip_buffer_close( &pair->b2a_bip ); + bip_buffer_close( &pair->a2b_bip ); + + if ( --pair->used == 0 ) { + bip_buffer_done( &pair->b2a_bip ); + bip_buffer_done( &pair->a2b_bip ); + free( pair ); + } + f->fh_pair = NULL; + } + return 0; +} + +static int +_fh_socketpair_lseek( FH f, int pos, int origin ) +{ + errno = ESPIPE; + return -1; +} + +static int +_fh_socketpair_read( FH f, void* buf, int len ) +{ + SocketPair pair = f->fh_pair; + BipBuffer bip; + + if (!pair) + return -1; + + if ( f == pair->a_fd ) + bip = &pair->b2a_bip; + else + bip = &pair->a2b_bip; + + return bip_buffer_read( bip, buf, len ); +} + +static int +_fh_socketpair_write( FH f, const void* buf, int len ) +{ + SocketPair pair = f->fh_pair; + BipBuffer bip; + + if (!pair) + return -1; + + if ( f == pair->a_fd ) + bip = &pair->a2b_bip; + else + bip = &pair->b2a_bip; + + return bip_buffer_write( bip, buf, len ); +} + + +static void _fh_socketpair_hook( FH f, int event, EventHook hook ); /* forward */ + +static const FHClassRec _fh_socketpair_class = +{ + _fh_socketpair_init, + _fh_socketpair_close, + _fh_socketpair_lseek, + _fh_socketpair_read, + _fh_socketpair_write, + _fh_socketpair_hook +}; + + +int adb_socketpair( int sv[2] ) +{ + FH fa, fb; + SocketPair pair; + + fa = _fh_alloc( &_fh_socketpair_class ); + fb = _fh_alloc( &_fh_socketpair_class ); + + if (!fa || !fb) + goto Fail; + + pair = malloc( sizeof(*pair) ); + if (pair == NULL) { + D("adb_socketpair: not enough memory to allocate pipes\n" ); + goto Fail; + } + + bip_buffer_init( &pair->a2b_bip ); + bip_buffer_init( &pair->b2a_bip ); + + fa->fh_pair = pair; + fb->fh_pair = pair; + pair->used = 2; + pair->a_fd = fa; + + sv[0] = _fh_to_int(fa); + sv[1] = _fh_to_int(fb); + + pair->a2b_bip.fdin = sv[0]; + pair->a2b_bip.fdout = sv[1]; + pair->b2a_bip.fdin = sv[1]; + pair->b2a_bip.fdout = sv[0]; + + snprintf( fa->name, sizeof(fa->name), "%d(pair:%d)", sv[0], sv[1] ); + snprintf( fb->name, sizeof(fb->name), "%d(pair:%d)", sv[1], sv[0] ); + D( "adb_socketpair: returns (%d, %d)\n", sv[0], sv[1] ); + return 0; + +Fail: + _fh_close(fb); + _fh_close(fa); + return -1; +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** fdevents emulation *****/ +/***** *****/ +/***** this is a very simple implementation, we rely on the fact *****/ +/***** that ADB doesn't use FDE_ERROR. *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +#define FATAL(x...) fatal(__FUNCTION__, x) + +#if DEBUG +static void dump_fde(fdevent *fde, const char *info) +{ + fprintf(stderr,"FDE #%03d %c%c%c %s\n", fde->fd, + fde->state & FDE_READ ? 'R' : ' ', + fde->state & FDE_WRITE ? 'W' : ' ', + fde->state & FDE_ERROR ? 'E' : ' ', + info); +} +#else +#define dump_fde(fde, info) do { } while(0) +#endif + +#define FDE_EVENTMASK 0x00ff +#define FDE_STATEMASK 0xff00 + +#define FDE_ACTIVE 0x0100 +#define FDE_PENDING 0x0200 +#define FDE_CREATED 0x0400 + +static void fdevent_plist_enqueue(fdevent *node); +static void fdevent_plist_remove(fdevent *node); +static fdevent *fdevent_plist_dequeue(void); + +static fdevent list_pending = { + .next = &list_pending, + .prev = &list_pending, +}; + +static fdevent **fd_table = 0; +static int fd_table_max = 0; + +typedef struct EventLooperRec_* EventLooper; + +typedef struct EventHookRec_ +{ + EventHook next; + FH fh; + HANDLE h; + int wanted; /* wanted event flags */ + int ready; /* ready event flags */ + void* aux; + void (*prepare)( EventHook hook ); + int (*start) ( EventHook hook ); + void (*stop) ( EventHook hook ); + int (*check) ( EventHook hook ); + int (*peek) ( EventHook hook ); +} EventHookRec; + +static EventHook _free_hooks; + +static EventHook +event_hook_alloc( FH fh ) +{ + EventHook hook = _free_hooks; + if (hook != NULL) + _free_hooks = hook->next; + else { + hook = malloc( sizeof(*hook) ); + if (hook == NULL) + fatal( "could not allocate event hook\n" ); + } + hook->next = NULL; + hook->fh = fh; + hook->wanted = 0; + hook->ready = 0; + hook->h = INVALID_HANDLE_VALUE; + hook->aux = NULL; + + hook->prepare = NULL; + hook->start = NULL; + hook->stop = NULL; + hook->check = NULL; + hook->peek = NULL; + + return hook; +} + +static void +event_hook_free( EventHook hook ) +{ + hook->fh = NULL; + hook->wanted = 0; + hook->ready = 0; + hook->next = _free_hooks; + _free_hooks = hook; +} + + +static void +event_hook_signal( EventHook hook ) +{ + FH f = hook->fh; + int fd = _fh_to_int(f); + fdevent* fde = fd_table[ fd - WIN32_FH_BASE ]; + + if (fde != NULL && fde->fd == fd) { + if ((fde->state & FDE_PENDING) == 0) { + fde->state |= FDE_PENDING; + fdevent_plist_enqueue( fde ); + } + fde->events |= hook->wanted; + } +} + + +#define MAX_LOOPER_HANDLES WIN32_MAX_FHS + +typedef struct EventLooperRec_ +{ + EventHook hooks; + HANDLE htab[ MAX_LOOPER_HANDLES ]; + int htab_count; + +} EventLooperRec; + +static EventHook* +event_looper_find_p( EventLooper looper, FH fh ) +{ + EventHook *pnode = &looper->hooks; + EventHook node = *pnode; + for (;;) { + if ( node == NULL || node->fh == fh ) + break; + pnode = &node->next; + node = *pnode; + } + return pnode; +} + +static void +event_looper_hook( EventLooper looper, int fd, int events ) +{ + FH f = _fh_from_int(fd); + EventHook *pnode; + EventHook node; + + if (f == NULL) /* invalid arg */ { + D("event_looper_hook: invalid fd=%d\n", fd); + return; + } + + pnode = event_looper_find_p( looper, f ); + node = *pnode; + if ( node == NULL ) { + node = event_hook_alloc( f ); + node->next = *pnode; + *pnode = node; + } + + if ( (node->wanted & events) != events ) { + /* this should update start/stop/check/peek */ + D("event_looper_hook: call hook for %d (new=%x, old=%x)\n", + fd, node->wanted, events); + f->clazz->_fh_hook( f, events & ~node->wanted, node ); + node->wanted |= events; + } else { + D("event_looper_hook: ignoring events %x for %d wanted=%x)\n", + events, fd, node->wanted); + } +} + +static void +event_looper_unhook( EventLooper looper, int fd, int events ) +{ + FH fh = _fh_from_int(fd); + EventHook *pnode = event_looper_find_p( looper, fh ); + EventHook node = *pnode; + + if (node != NULL) { + int events2 = events & node->wanted; + if ( events2 == 0 ) { + D( "event_looper_unhook: events %x not registered for fd %d\n", events, fd ); + return; + } + node->wanted &= ~events2; + if (!node->wanted) { + *pnode = node->next; + event_hook_free( node ); + } + } +} + +static EventLooperRec win32_looper; + +static void fdevent_init(void) +{ + win32_looper.htab_count = 0; + win32_looper.hooks = NULL; +} + +static void fdevent_connect(fdevent *fde) +{ + EventLooper looper = &win32_looper; + int events = fde->state & FDE_EVENTMASK; + + if (events != 0) + event_looper_hook( looper, fde->fd, events ); +} + +static void fdevent_disconnect(fdevent *fde) +{ + EventLooper looper = &win32_looper; + int events = fde->state & FDE_EVENTMASK; + + if (events != 0) + event_looper_unhook( looper, fde->fd, events ); +} + +static void fdevent_update(fdevent *fde, unsigned events) +{ + EventLooper looper = &win32_looper; + unsigned events0 = fde->state & FDE_EVENTMASK; + + if (events != events0) { + int removes = events0 & ~events; + int adds = events & ~events0; + if (removes) { + D("fdevent_update: remove %x from %d\n", removes, fde->fd); + event_looper_unhook( looper, fde->fd, removes ); + } + if (adds) { + D("fdevent_update: add %x to %d\n", adds, fde->fd); + event_looper_hook ( looper, fde->fd, adds ); + } + } +} + +static void fdevent_process() +{ + EventLooper looper = &win32_looper; + EventHook hook; + int gotone = 0; + + /* if we have at least one ready hook, execute it/them */ + for (hook = looper->hooks; hook; hook = hook->next) { + hook->ready = 0; + if (hook->prepare) { + hook->prepare(hook); + if (hook->ready != 0) { + event_hook_signal( hook ); + gotone = 1; + } + } + } + + /* nothing's ready yet, so wait for something to happen */ + if (!gotone) + { + looper->htab_count = 0; + + for (hook = looper->hooks; hook; hook = hook->next) + { + if (hook->start && !hook->start(hook)) { + D( "fdevent_process: error when starting a hook\n" ); + return; + } + if (hook->h != INVALID_HANDLE_VALUE) { + int nn; + + for (nn = 0; nn < looper->htab_count; nn++) + { + if ( looper->htab[nn] == hook->h ) + goto DontAdd; + } + looper->htab[ looper->htab_count++ ] = hook->h; + DontAdd: + ; + } + } + + if (looper->htab_count == 0) { + D( "fdevent_process: nothing to wait for !!\n" ); + return; + } + + do + { + int wait_ret; + + D( "adb_win32: waiting for %d events\n", looper->htab_count ); + if (looper->htab_count > MAXIMUM_WAIT_OBJECTS) { + D("handle count %d exceeds MAXIMUM_WAIT_OBJECTS, aborting!\n", looper->htab_count); + abort(); + } + wait_ret = WaitForMultipleObjects( looper->htab_count, looper->htab, FALSE, INFINITE ); + if (wait_ret == (int)WAIT_FAILED) { + D( "adb_win32: wait failed, error %ld\n", GetLastError() ); + } else { + D( "adb_win32: got one (index %d)\n", wait_ret ); + + /* according to Cygwin, some objects like consoles wake up on "inappropriate" events + * like mouse movements. we need to filter these with the "check" function + */ + if ((unsigned)wait_ret < (unsigned)looper->htab_count) + { + for (hook = looper->hooks; hook; hook = hook->next) + { + if ( looper->htab[wait_ret] == hook->h && + (!hook->check || hook->check(hook)) ) + { + D( "adb_win32: signaling %s for %x\n", hook->fh->name, hook->ready ); + event_hook_signal( hook ); + gotone = 1; + break; + } + } + } + } + } + while (!gotone); + + for (hook = looper->hooks; hook; hook = hook->next) { + if (hook->stop) + hook->stop( hook ); + } + } + + for (hook = looper->hooks; hook; hook = hook->next) { + if (hook->peek && hook->peek(hook)) + event_hook_signal( hook ); + } +} + + +static void fdevent_register(fdevent *fde) +{ + int fd = fde->fd - WIN32_FH_BASE; + + if(fd < 0) { + FATAL("bogus negative fd (%d)\n", fde->fd); + } + + if(fd >= fd_table_max) { + int oldmax = fd_table_max; + if(fde->fd > 32000) { + FATAL("bogus huuuuge fd (%d)\n", fde->fd); + } + if(fd_table_max == 0) { + fdevent_init(); + fd_table_max = 256; + } + while(fd_table_max <= fd) { + fd_table_max *= 2; + } + fd_table = realloc(fd_table, sizeof(fdevent*) * fd_table_max); + if(fd_table == 0) { + FATAL("could not expand fd_table to %d entries\n", fd_table_max); + } + memset(fd_table + oldmax, 0, sizeof(int) * (fd_table_max - oldmax)); + } + + fd_table[fd] = fde; +} + +static void fdevent_unregister(fdevent *fde) +{ + int fd = fde->fd - WIN32_FH_BASE; + + if((fd < 0) || (fd >= fd_table_max)) { + FATAL("fd out of range (%d)\n", fde->fd); + } + + if(fd_table[fd] != fde) { + FATAL("fd_table out of sync"); + } + + fd_table[fd] = 0; + + if(!(fde->state & FDE_DONT_CLOSE)) { + dump_fde(fde, "close"); + adb_close(fde->fd); + } +} + +static void fdevent_plist_enqueue(fdevent *node) +{ + fdevent *list = &list_pending; + + node->next = list; + node->prev = list->prev; + node->prev->next = node; + list->prev = node; +} + +static void fdevent_plist_remove(fdevent *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; +} + +static fdevent *fdevent_plist_dequeue(void) +{ + fdevent *list = &list_pending; + fdevent *node = list->next; + + if(node == list) return 0; + + list->next = node->next; + list->next->prev = list; + node->next = 0; + node->prev = 0; + + return node; +} + +fdevent *fdevent_create(int fd, fd_func func, void *arg) +{ + fdevent *fde = (fdevent*) malloc(sizeof(fdevent)); + if(fde == 0) return 0; + fdevent_install(fde, fd, func, arg); + fde->state |= FDE_CREATED; + return fde; +} + +void fdevent_destroy(fdevent *fde) +{ + if(fde == 0) return; + if(!(fde->state & FDE_CREATED)) { + FATAL("fde %p not created by fdevent_create()\n", fde); + } + fdevent_remove(fde); +} + +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg) +{ + memset(fde, 0, sizeof(fdevent)); + fde->state = FDE_ACTIVE; + fde->fd = fd; + fde->func = func; + fde->arg = arg; + + fdevent_register(fde); + dump_fde(fde, "connect"); + fdevent_connect(fde); + fde->state |= FDE_ACTIVE; +} + +void fdevent_remove(fdevent *fde) +{ + if(fde->state & FDE_PENDING) { + fdevent_plist_remove(fde); + } + + if(fde->state & FDE_ACTIVE) { + fdevent_disconnect(fde); + dump_fde(fde, "disconnect"); + fdevent_unregister(fde); + } + + fde->state = 0; + fde->events = 0; +} + + +void fdevent_set(fdevent *fde, unsigned events) +{ + events &= FDE_EVENTMASK; + + if((fde->state & FDE_EVENTMASK) == (int)events) return; + + if(fde->state & FDE_ACTIVE) { + fdevent_update(fde, events); + dump_fde(fde, "update"); + } + + fde->state = (fde->state & FDE_STATEMASK) | events; + + if(fde->state & FDE_PENDING) { + /* if we're pending, make sure + ** we don't signal an event that + ** is no longer wanted. + */ + fde->events &= (~events); + if(fde->events == 0) { + fdevent_plist_remove(fde); + fde->state &= (~FDE_PENDING); + } + } +} + +void fdevent_add(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) | (events & FDE_EVENTMASK)); +} + +void fdevent_del(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) & (~(events & FDE_EVENTMASK))); +} + +void fdevent_loop() +{ + fdevent *fde; + + for(;;) { +#if DEBUG + fprintf(stderr,"--- ---- waiting for events\n"); +#endif + fdevent_process(); + + while((fde = fdevent_plist_dequeue())) { + unsigned events = fde->events; + fde->events = 0; + fde->state &= (~FDE_PENDING); + dump_fde(fde, "callback"); + fde->func(fde->fd, events, fde->arg); + } + } +} + +/** FILE EVENT HOOKS + **/ + +static void _event_file_prepare( EventHook hook ) +{ + if (hook->wanted & (FDE_READ|FDE_WRITE)) { + /* we can always read/write */ + hook->ready |= hook->wanted & (FDE_READ|FDE_WRITE); + } +} + +static int _event_file_peek( EventHook hook ) +{ + return (hook->wanted & (FDE_READ|FDE_WRITE)); +} + +static void _fh_file_hook( FH f, int events, EventHook hook ) +{ + hook->h = f->fh_handle; + hook->prepare = _event_file_prepare; + hook->peek = _event_file_peek; +} + +/** SOCKET EVENT HOOKS + **/ + +static void _event_socket_verify( EventHook hook, WSANETWORKEVENTS* evts ) +{ + if ( evts->lNetworkEvents & (FD_READ|FD_ACCEPT|FD_CLOSE) ) { + if (hook->wanted & FDE_READ) + hook->ready |= FDE_READ; + if ((evts->iErrorCode[FD_READ] != 0) && hook->wanted & FDE_ERROR) + hook->ready |= FDE_ERROR; + } + if ( evts->lNetworkEvents & (FD_WRITE|FD_CONNECT|FD_CLOSE) ) { + if (hook->wanted & FDE_WRITE) + hook->ready |= FDE_WRITE; + if ((evts->iErrorCode[FD_WRITE] != 0) && hook->wanted & FDE_ERROR) + hook->ready |= FDE_ERROR; + } + if ( evts->lNetworkEvents & FD_OOB ) { + if (hook->wanted & FDE_ERROR) + hook->ready |= FDE_ERROR; + } +} + +static void _event_socket_prepare( EventHook hook ) +{ + WSANETWORKEVENTS evts; + + /* look if some of the events we want already happened ? */ + if (!WSAEnumNetworkEvents( hook->fh->fh_socket, NULL, &evts )) + _event_socket_verify( hook, &evts ); +} + +static int _socket_wanted_to_flags( int wanted ) +{ + int flags = 0; + if (wanted & FDE_READ) + flags |= FD_READ | FD_ACCEPT | FD_CLOSE; + + if (wanted & FDE_WRITE) + flags |= FD_WRITE | FD_CONNECT | FD_CLOSE; + + if (wanted & FDE_ERROR) + flags |= FD_OOB; + + return flags; +} + +static int _event_socket_start( EventHook hook ) +{ + /* create an event which we're going to wait for */ + FH fh = hook->fh; + long flags = _socket_wanted_to_flags( hook->wanted ); + + hook->h = fh->event; + if (hook->h == INVALID_HANDLE_VALUE) { + D( "_event_socket_start: no event for %s\n", fh->name ); + return 0; + } + + if ( flags != fh->mask ) { + D( "_event_socket_start: hooking %s for %x (flags %ld)\n", hook->fh->name, hook->wanted, flags ); + if ( WSAEventSelect( fh->fh_socket, hook->h, flags ) ) { + D( "_event_socket_start: WSAEventSelect() for %s failed, error %d\n", hook->fh->name, WSAGetLastError() ); + CloseHandle( hook->h ); + hook->h = INVALID_HANDLE_VALUE; + exit(1); + return 0; + } + fh->mask = flags; + } + return 1; +} + +static void _event_socket_stop( EventHook hook ) +{ + hook->h = INVALID_HANDLE_VALUE; +} + +static int _event_socket_check( EventHook hook ) +{ + int result = 0; + FH fh = hook->fh; + WSANETWORKEVENTS evts; + + if (!WSAEnumNetworkEvents( fh->fh_socket, hook->h, &evts ) ) { + _event_socket_verify( hook, &evts ); + result = (hook->ready != 0); + if (result) { + ResetEvent( hook->h ); + } + } + D( "_event_socket_check %s returns %d\n", fh->name, result ); + return result; +} + +static int _event_socket_peek( EventHook hook ) +{ + WSANETWORKEVENTS evts; + FH fh = hook->fh; + + /* look if some of the events we want already happened ? */ + if (!WSAEnumNetworkEvents( fh->fh_socket, NULL, &evts )) { + _event_socket_verify( hook, &evts ); + if (hook->ready) + ResetEvent( hook->h ); + } + + return hook->ready != 0; +} + + + +static void _fh_socket_hook( FH f, int events, EventHook hook ) +{ + hook->prepare = _event_socket_prepare; + hook->start = _event_socket_start; + hook->stop = _event_socket_stop; + hook->check = _event_socket_check; + hook->peek = _event_socket_peek; + + _event_socket_start( hook ); +} + +/** SOCKETPAIR EVENT HOOKS + **/ + +static void _event_socketpair_prepare( EventHook hook ) +{ + FH fh = hook->fh; + SocketPair pair = fh->fh_pair; + BipBuffer rbip = (pair->a_fd == fh) ? &pair->b2a_bip : &pair->a2b_bip; + BipBuffer wbip = (pair->a_fd == fh) ? &pair->a2b_bip : &pair->b2a_bip; + + if (hook->wanted & FDE_READ && rbip->can_read) + hook->ready |= FDE_READ; + + if (hook->wanted & FDE_WRITE && wbip->can_write) + hook->ready |= FDE_WRITE; + } + + static int _event_socketpair_start( EventHook hook ) + { + FH fh = hook->fh; + SocketPair pair = fh->fh_pair; + BipBuffer rbip = (pair->a_fd == fh) ? &pair->b2a_bip : &pair->a2b_bip; + BipBuffer wbip = (pair->a_fd == fh) ? &pair->a2b_bip : &pair->b2a_bip; + + if (hook->wanted == FDE_READ) + hook->h = rbip->evt_read; + + else if (hook->wanted == FDE_WRITE) + hook->h = wbip->evt_write; + + else { + D("_event_socketpair_start: can't handle FDE_READ+FDE_WRITE\n" ); + return 0; + } + D( "_event_socketpair_start: hook %s for %x wanted=%x\n", + hook->fh->name, _fh_to_int(fh), hook->wanted); + return 1; +} + +static int _event_socketpair_peek( EventHook hook ) +{ + _event_socketpair_prepare( hook ); + return hook->ready != 0; +} + +static void _fh_socketpair_hook( FH fh, int events, EventHook hook ) +{ + hook->prepare = _event_socketpair_prepare; + hook->start = _event_socketpair_start; + hook->peek = _event_socketpair_peek; +} + + +void +adb_sysdeps_init( void ) +{ +#define ADB_MUTEX(x) InitializeCriticalSection( & x ); +#include "mutex_list.h" + InitializeCriticalSection( &_win32_lock ); +} + diff --git a/adb/test_track_devices.c b/adb/test_track_devices.c new file mode 100644 index 0000000..77b3ad9 --- /dev/null +++ b/adb/test_track_devices.c @@ -0,0 +1,97 @@ +/* a simple test program, connects to ADB server, and opens a track-devices session */ +#include <netdb.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <memory.h> + +static void +panic( const char* msg ) +{ + fprintf(stderr, "PANIC: %s: %s\n", msg, strerror(errno)); + exit(1); +} + +static int +unix_write( int fd, const char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = write(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + +static int +unix_read( int fd, char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = read(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + + +int main( void ) +{ + int ret, s; + struct sockaddr_in server; + char buffer[1024]; + const char* request = "host:track-devices"; + int len; + + memset( &server, 0, sizeof(server) ); + server.sin_family = AF_INET; + server.sin_port = htons(5037); + server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket( PF_INET, SOCK_STREAM, 0 ); + ret = connect( s, (struct sockaddr*) &server, sizeof(server) ); + if (ret < 0) panic( "could not connect to server" ); + + /* send the request */ + len = snprintf( buffer, sizeof buffer, "%04x%s", strlen(request), request ); + if (unix_write(s, buffer, len) < 0) + panic( "could not send request" ); + + /* read the OKAY answer */ + if (unix_read(s, buffer, 4) != 4) + panic( "could not read request" ); + + printf( "server answer: %.*s\n", 4, buffer ); + + /* now loop */ + for (;;) { + char head[5] = "0000"; + + if (unix_read(s, head, 4) < 0) + panic("could not read length"); + + if ( sscanf( head, "%04x", &len ) != 1 ) + panic("could not decode length"); + + if (unix_read(s, buffer, len) != len) + panic("could not read data"); + + printf( "received header %.*s (%d bytes):\n%.*s", 4, head, len, len, buffer ); + } + close(s); +} diff --git a/adb/test_track_jdwp.c b/adb/test_track_jdwp.c new file mode 100644 index 0000000..8ecc6b8 --- /dev/null +++ b/adb/test_track_jdwp.c @@ -0,0 +1,97 @@ +/* a simple test program, connects to ADB server, and opens a track-devices session */ +#include <netdb.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <memory.h> + +static void +panic( const char* msg ) +{ + fprintf(stderr, "PANIC: %s: %s\n", msg, strerror(errno)); + exit(1); +} + +static int +unix_write( int fd, const char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = write(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + +static int +unix_read( int fd, char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = read(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + + +int main( void ) +{ + int ret, s; + struct sockaddr_in server; + char buffer[1024]; + const char* request = "track-jdwp"; + int len; + + memset( &server, 0, sizeof(server) ); + server.sin_family = AF_INET; + server.sin_port = htons(5037); + server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket( PF_INET, SOCK_STREAM, 0 ); + ret = connect( s, (struct sockaddr*) &server, sizeof(server) ); + if (ret < 0) panic( "could not connect to server" ); + + /* send the request */ + len = snprintf( buffer, sizeof buffer, "%04x%s", strlen(request), request ); + if (unix_write(s, buffer, len) < 0) + panic( "could not send request" ); + + /* read the OKAY answer */ + if (unix_read(s, buffer, 4) != 4) + panic( "could not read request" ); + + printf( "server answer: %.*s\n", 4, buffer ); + + /* now loop */ + for (;;) { + char head[5] = "0000"; + + if (unix_read(s, head, 4) < 0) + panic("could not read length"); + + if ( sscanf( head, "%04x", &len ) != 1 ) + panic("could not decode length"); + + if (unix_read(s, buffer, len) != len) + panic("could not read data"); + + printf( "received header %.*s (%d bytes):\n%.*s", 4, head, len, len, buffer ); + } + close(s); +} diff --git a/adb/transport.c b/adb/transport.c new file mode 100644 index 0000000..c76f1a5 --- /dev/null +++ b/adb/transport.c @@ -0,0 +1,958 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +static void transport_unref(atransport *t); + +static atransport transport_list = { + .next = &transport_list, + .prev = &transport_list, +}; + +ADB_MUTEX_DEFINE( transport_lock ); + +#if ADB_TRACE +static void dump_hex( const unsigned char* ptr, size_t len ) +{ + int nn, len2 = len; + + if (len2 > 16) len2 = 16; + + for (nn = 0; nn < len2; nn++) + D("%02x", ptr[nn]); + D(" "); + + for (nn = 0; nn < len2; nn++) { + int c = ptr[nn]; + if (c < 32 || c > 127) + c = '.'; + D("%c", c); + } + D("\n"); + fflush(stdout); +} +#endif + +void +kick_transport(atransport* t) +{ + if (t && !t->kicked) + { + int kicked; + + adb_mutex_lock(&transport_lock); + kicked = t->kicked; + if (!kicked) + t->kicked = 1; + adb_mutex_unlock(&transport_lock); + + if (!kicked) + t->kick(t); + } +} + +void +run_transport_disconnects(atransport* t) +{ + adisconnect* dis = t->disconnects.next; + + D("run_transport_disconnects: %p (%s)\n", t, t->serial ? t->serial : "unknown" ); + while (dis != &t->disconnects) { + adisconnect* next = dis->next; + dis->func( dis->opaque, t ); + dis = next; + } +} + +static int +read_packet(int fd, apacket** ppacket) +{ + char *p = (char*)ppacket; /* really read a packet address */ + int r; + int len = sizeof(*ppacket); + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("read_packet: %d error %d %d\n", fd, r, errno); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + +#if ADB_TRACE + if (ADB_TRACING) + { + unsigned command = (*ppacket)->msg.command; + int len = (*ppacket)->msg.data_length; + char cmd[5]; + int n; + + for (n = 0; n < 4; n++) { + int b = (command >> (n*8)) & 255; + if (b >= 32 && b < 127) + cmd[n] = (char)b; + else + cmd[n] = '.'; + } + cmd[4] = 0; + + D("read_packet: %d ok: [%08x %s] %08x %08x (%d) ", + fd, command, cmd, (*ppacket)->msg.arg0, (*ppacket)->msg.arg1, len); + dump_hex((*ppacket)->data, len); + } +#endif + return 0; +} + +static int +write_packet(int fd, apacket** ppacket) +{ + char *p = (char*) ppacket; /* we really write the packet address */ + int r, len = sizeof(ppacket); + +#if ADB_TRACE + if (ADB_TRACING) + { + unsigned command = (*ppacket)->msg.command; + int len = (*ppacket)->msg.data_length; + char cmd[5]; + int n; + + for (n = 0; n < 4; n++) { + int b = (command >> (n*8)) & 255; + if (b >= 32 && b < 127) + cmd[n] = (char)b; + else + cmd[n] = '.'; + } + cmd[4] = 0; + + D("write_packet: %d [%08x %s] %08x %08x (%d) ", + fd, command, cmd, (*ppacket)->msg.arg0, (*ppacket)->msg.arg1, len); + dump_hex((*ppacket)->data, len); + } +#endif + len = sizeof(ppacket); + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("write_packet: %d error %d %d\n", fd, r, errno); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + return 0; +} + +static void transport_socket_events(int fd, unsigned events, void *_t) +{ + if(events & FDE_READ){ + apacket *p = 0; + if(read_packet(fd, &p)){ + D("failed to read packet from transport socket on fd %d\n", fd); + } else { + handle_packet(p, (atransport *) _t); + } + } +} + +void send_packet(apacket *p, atransport *t) +{ + unsigned char *x; + unsigned sum; + unsigned count; + + p->msg.magic = p->msg.command ^ 0xffffffff; + + count = p->msg.data_length; + x = (unsigned char *) p->data; + sum = 0; + while(count-- > 0){ + sum += *x++; + } + p->msg.data_check = sum; + + print_packet("send", p); + + if (t == NULL) { + fatal_errno("Transport is null"); + D("Transport is null \n"); + } + + if(write_packet(t->transport_socket, &p)){ + fatal_errno("cannot enqueue packet on transport socket"); + } +} + +/* The transport is opened by transport_register_func before +** the input and output threads are started. +** +** The output thread issues a SYNC(1, token) message to let +** the input thread know to start things up. In the event +** of transport IO failure, the output thread will post a +** SYNC(0,0) message to ensure shutdown. +** +** The transport will not actually be closed until both +** threads exit, but the input thread will kick the transport +** on its way out to disconnect the underlying device. +*/ + +static void *output_thread(void *_t) +{ + atransport *t = _t; + apacket *p; + + D("from_remote: starting thread for transport %p, on fd %d\n", t, t->fd ); + + D("from_remote: transport %p SYNC online (%d)\n", t, t->sync_token + 1); + p = get_apacket(); + p->msg.command = A_SYNC; + p->msg.arg0 = 1; + p->msg.arg1 = ++(t->sync_token); + p->msg.magic = A_SYNC ^ 0xffffffff; + if(write_packet(t->fd, &p)) { + put_apacket(p); + D("from_remote: failed to write SYNC apacket to transport %p", t); + goto oops; + } + + D("from_remote: data pump for transport %p\n", t); + for(;;) { + p = get_apacket(); + + if(t->read_from_remote(p, t) == 0){ + D("from_remote: received remote packet, sending to transport %p\n", + t); + if(write_packet(t->fd, &p)){ + put_apacket(p); + D("from_remote: failed to write apacket to transport %p", t); + goto oops; + } + } else { + D("from_remote: remote read failed for transport %p\n", p); + put_apacket(p); + break; + } + } + + D("from_remote: SYNC offline for transport %p\n", t); + p = get_apacket(); + p->msg.command = A_SYNC; + p->msg.arg0 = 0; + p->msg.arg1 = 0; + p->msg.magic = A_SYNC ^ 0xffffffff; + if(write_packet(t->fd, &p)) { + put_apacket(p); + D("from_remote: failed to write SYNC apacket to transport %p", t); + } + +oops: + D("from_remote: thread is exiting for transport %p\n", t); + kick_transport(t); + transport_unref(t); + return 0; +} + +static void *input_thread(void *_t) +{ + atransport *t = _t; + apacket *p; + int active = 0; + + D("to_remote: starting input_thread for %p, reading from fd %d\n", + t, t->fd); + + for(;;){ + if(read_packet(t->fd, &p)) { + D("to_remote: failed to read apacket from transport %p on fd %d\n", + t, t->fd ); + break; + } + if(p->msg.command == A_SYNC){ + if(p->msg.arg0 == 0) { + D("to_remote: transport %p SYNC offline\n", t); + put_apacket(p); + break; + } else { + if(p->msg.arg1 == t->sync_token) { + D("to_remote: transport %p SYNC online\n", t); + active = 1; + } else { + D("to_remote: trandport %p ignoring SYNC %d != %d\n", + t, p->msg.arg1, t->sync_token); + } + } + } else { + if(active) { + D("to_remote: transport %p got packet, sending to remote\n", t); + t->write_to_remote(p, t); + } else { + D("to_remote: transport %p ignoring packet while offline\n", t); + } + } + + put_apacket(p); + } + + // this is necessary to avoid a race condition that occured when a transport closes + // while a client socket is still active. + close_all_sockets(t); + + D("to_remote: thread is exiting for transport %p, fd %d\n", t, t->fd); + kick_transport(t); + transport_unref(t); + return 0; +} + + +static int transport_registration_send = -1; +static int transport_registration_recv = -1; +static fdevent transport_registration_fde; + + +#if ADB_HOST +static int list_transports_msg(char* buffer, size_t bufferlen) +{ + char head[5]; + int len; + + len = list_transports(buffer+4, bufferlen-4); + snprintf(head, sizeof(head), "%04x", len); + memcpy(buffer, head, 4); + len += 4; + return len; +} + +/* this adds support required by the 'track-devices' service. + * this is used to send the content of "list_transport" to any + * number of client connections that want it through a single + * live TCP connection + */ +typedef struct device_tracker device_tracker; +struct device_tracker { + asocket socket; + int update_needed; + device_tracker* next; +}; + +/* linked list of all device trackers */ +static device_tracker* device_tracker_list; + +static void +device_tracker_remove( device_tracker* tracker ) +{ + device_tracker** pnode = &device_tracker_list; + device_tracker* node = *pnode; + + adb_mutex_lock( &transport_lock ); + while (node) { + if (node == tracker) { + *pnode = node->next; + break; + } + pnode = &node->next; + node = *pnode; + } + adb_mutex_unlock( &transport_lock ); +} + +static void +device_tracker_close( asocket* socket ) +{ + device_tracker* tracker = (device_tracker*) socket; + asocket* peer = socket->peer; + + D( "device tracker %p removed\n", tracker); + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + device_tracker_remove(tracker); + free(tracker); +} + +static int +device_tracker_enqueue( asocket* socket, apacket* p ) +{ + /* you can't read from a device tracker, close immediately */ + put_apacket(p); + device_tracker_close(socket); + return -1; +} + +static int +device_tracker_send( device_tracker* tracker, + const char* buffer, + int len ) +{ + apacket* p = get_apacket(); + asocket* peer = tracker->socket.peer; + + memcpy(p->data, buffer, len); + p->len = len; + return peer->enqueue( peer, p ); +} + + +static void +device_tracker_ready( asocket* socket ) +{ + device_tracker* tracker = (device_tracker*) socket; + + /* we want to send the device list when the tracker connects + * for the first time, even if no update occured */ + if (tracker->update_needed > 0) { + char buffer[1024]; + int len; + + tracker->update_needed = 0; + + len = list_transports_msg(buffer, sizeof(buffer)); + device_tracker_send(tracker, buffer, len); + } +} + + +asocket* +create_device_tracker(void) +{ + device_tracker* tracker = calloc(1,sizeof(*tracker)); + + if(tracker == 0) fatal("cannot allocate device tracker"); + + D( "device tracker %p created\n", tracker); + + tracker->socket.enqueue = device_tracker_enqueue; + tracker->socket.ready = device_tracker_ready; + tracker->socket.close = device_tracker_close; + tracker->update_needed = 1; + + tracker->next = device_tracker_list; + device_tracker_list = tracker; + + return &tracker->socket; +} + + +/* call this function each time the transport list has changed */ +void update_transports(void) +{ + char buffer[1024]; + int len; + device_tracker* tracker; + + len = list_transports_msg(buffer, sizeof(buffer)); + + tracker = device_tracker_list; + while (tracker != NULL) { + device_tracker* next = tracker->next; + /* note: this may destroy the tracker if the connection is closed */ + device_tracker_send(tracker, buffer, len); + tracker = next; + } +} +#else +void update_transports(void) +{ + // nothing to do on the device side +} +#endif // ADB_HOST + +typedef struct tmsg tmsg; +struct tmsg +{ + atransport *transport; + int action; +}; + +static int +transport_read_action(int fd, struct tmsg* m) +{ + char *p = (char*)m; + int len = sizeof(*m); + int r; + + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if((r < 0) && (errno == EINTR)) continue; + D("transport_read_action: on fd %d, error %d: %s\n", + fd, errno, strerror(errno)); + return -1; + } + } + return 0; +} + +static int +transport_write_action(int fd, struct tmsg* m) +{ + char *p = (char*)m; + int len = sizeof(*m); + int r; + + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if((r < 0) && (errno == EINTR)) continue; + D("transport_write_action: on fd %d, error %d: %s\n", + fd, errno, strerror(errno)); + return -1; + } + } + return 0; +} + +static void transport_registration_func(int _fd, unsigned ev, void *data) +{ + tmsg m; + adb_thread_t output_thread_ptr; + adb_thread_t input_thread_ptr; + int s[2]; + atransport *t; + + if(!(ev & FDE_READ)) { + return; + } + + if(transport_read_action(_fd, &m)) { + fatal_errno("cannot read transport registration socket"); + } + + t = m.transport; + + if(m.action == 0){ + D("transport: %p removing and free'ing %d\n", t, t->transport_socket); + + /* IMPORTANT: the remove closes one half of the + ** socket pair. The close closes the other half. + */ + fdevent_remove(&(t->transport_fde)); + adb_close(t->fd); + + adb_mutex_lock(&transport_lock); + t->next->prev = t->prev; + t->prev->next = t->next; + adb_mutex_unlock(&transport_lock); + + run_transport_disconnects(t); + + if (t->product) + free(t->product); + if (t->serial) + free(t->serial); + + memset(t,0xee,sizeof(atransport)); + free(t); + + update_transports(); + return; + } + + /* initial references are the two threads */ + t->ref_count = 2; + + if(adb_socketpair(s)) { + fatal_errno("cannot open transport socketpair"); + } + + D("transport: %p (%d,%d) starting\n", t, s[0], s[1]); + + t->transport_socket = s[0]; + t->fd = s[1]; + + /* put us on the master device list */ + adb_mutex_lock(&transport_lock); + t->next = &transport_list; + t->prev = transport_list.prev; + t->next->prev = t; + t->prev->next = t; + adb_mutex_unlock(&transport_lock); + + D("transport: %p install %d\n", t, t->transport_socket ); + fdevent_install(&(t->transport_fde), + t->transport_socket, + transport_socket_events, + t); + + fdevent_set(&(t->transport_fde), FDE_READ); + + if(adb_thread_create(&input_thread_ptr, input_thread, t)){ + fatal_errno("cannot create input thread"); + } + + if(adb_thread_create(&output_thread_ptr, output_thread, t)){ + fatal_errno("cannot create output thread"); + } + + t->disconnects.next = t->disconnects.prev = &t->disconnects; + + update_transports(); +} + +void init_transport_registration(void) +{ + int s[2]; + + if(adb_socketpair(s)){ + fatal_errno("cannot open transport registration socketpair"); + } + + transport_registration_send = s[0]; + transport_registration_recv = s[1]; + + fdevent_install(&transport_registration_fde, + transport_registration_recv, + transport_registration_func, + 0); + + fdevent_set(&transport_registration_fde, FDE_READ); +} + +/* the fdevent select pump is single threaded */ +static void register_transport(atransport *transport) +{ + tmsg m; + m.transport = transport; + m.action = 1; + D("transport: %p registered\n", transport); + if(transport_write_action(transport_registration_send, &m)) { + fatal_errno("cannot write transport registration socket\n"); + } +} + +static void remove_transport(atransport *transport) +{ + tmsg m; + m.transport = transport; + m.action = 0; + D("transport: %p removed\n", transport); + if(transport_write_action(transport_registration_send, &m)) { + fatal_errno("cannot write transport registration socket\n"); + } +} + + +static void transport_unref(atransport *t) +{ + if (t) { + adb_mutex_lock(&transport_lock); + t->ref_count--; + D("transport: %p R- (ref=%d)\n", t, t->ref_count); + if (t->ref_count == 0) { + D("transport: %p kicking and closing\n", t); + if (!t->kicked) { + t->kicked = 1; + t->kick(t); + } + t->close(t); + remove_transport(t); + } + adb_mutex_unlock(&transport_lock); + } +} + +void add_transport_disconnect(atransport* t, adisconnect* dis) +{ + adb_mutex_lock(&transport_lock); + dis->next = &t->disconnects; + dis->prev = dis->next->prev; + dis->prev->next = dis; + dis->next->prev = dis; + adb_mutex_unlock(&transport_lock); +} + +void remove_transport_disconnect(atransport* t, adisconnect* dis) +{ + dis->prev->next = dis->next; + dis->next->prev = dis->prev; + dis->next = dis->prev = dis; +} + + +atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out) +{ + atransport *t; + atransport *result = NULL; + int ambiguous = 0; + +retry: + if (error_out) + *error_out = "device not found"; + + adb_mutex_lock(&transport_lock); + for (t = transport_list.next; t != &transport_list; t = t->next) { + /* check for matching serial number */ + if (serial) { + if (t->serial && !strcmp(serial, t->serial)) { + result = t; + break; + } + } else { + if (ttype == kTransportUsb && t->type == kTransportUsb) { + if (result) { + if (error_out) + *error_out = "more than one device"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } else if (ttype == kTransportLocal && t->type == kTransportLocal) { + if (result) { + if (error_out) + *error_out = "more than one emulator"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } else if (ttype == kTransportAny) { + if (result) { + if (error_out) + *error_out = "more than one device and emulator"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } + } + } + adb_mutex_unlock(&transport_lock); + + if (result) { + /* offline devices are ignored -- they are either being born or dying */ + if (result && result->connection_state == CS_OFFLINE) { + if (error_out) + *error_out = "device offline"; + result = NULL; + } + + /* check for required connection state */ + if (result && state != CS_ANY && result->connection_state != state) { + if (error_out) + *error_out = "invalid device state"; + result = NULL; + } + } + + if (result) { + /* found one that we can take */ + if (error_out) + *error_out = NULL; + } else if (state != CS_ANY && (serial || !ambiguous)) { + adb_sleep_ms(1000); + goto retry; + } + + return result; +} + +#if ADB_HOST +static const char *statename(atransport *t) +{ + switch(t->connection_state){ + case CS_OFFLINE: return "offline"; + case CS_BOOTLOADER: return "bootloader"; + case CS_DEVICE: return "device"; + case CS_HOST: return "host"; + case CS_RECOVERY: return "recovery"; + default: return "unknown"; + } +} + +int list_transports(char *buf, size_t bufsize) +{ + char* p = buf; + char* end = buf + bufsize; + int len; + atransport *t; + + /* XXX OVERRUN PROBLEMS XXX */ + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + len = snprintf(p, end - p, "%s\t%s\n", + t->serial ? t->serial : "", + statename(t)); + + if (p + len >= end) { + /* discard last line if buffer is too short */ + break; + } + p += len; + } + p[0] = 0; + adb_mutex_unlock(&transport_lock); + return p - buf; +} + + +/* hack for osx */ +void close_usb_devices() +{ + atransport *t; + + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + if ( !t->kicked ) { + t->kicked = 1; + t->kick(t); + } + } + adb_mutex_unlock(&transport_lock); +} +#endif // ADB_HOST + +void register_socket_transport(int s, const char *serial, int port) +{ + atransport *t = calloc(1, sizeof(atransport)); + D("transport: %p init'ing for socket %d, on port %d\n", t, s, port); + if ( init_socket_transport(t, s, port) < 0 ) { + adb_close(s); + free(t); + return; + } + if(serial) { + t->serial = strdup(serial); + } + register_transport(t); +} + +void register_usb_transport(usb_handle *usb, const char *serial) +{ + atransport *t = calloc(1, sizeof(atransport)); + D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb, + serial ? serial : ""); + init_usb_transport(t, usb); + if(serial) { + t->serial = strdup(serial); + } + register_transport(t); +} + + +#undef TRACE_TAG +#define TRACE_TAG TRACE_RWX + +int readx(int fd, void *ptr, size_t len) +{ + char *p = ptr; + int r; +#if ADB_TRACE + int len0 = len; +#endif + D("readx: %d %p %d\n", fd, ptr, (int)len); + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("readx: %d %d %s\n", fd, r, strerror(errno)); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + +#if ADB_TRACE + D("readx: %d ok: ", fd); + dump_hex( ptr, len0 ); +#endif + return 0; +} + +int writex(int fd, const void *ptr, size_t len) +{ + char *p = (char*) ptr; + int r; + +#if ADB_TRACE + D("writex: %d %p %d: ", fd, ptr, (int)len); + dump_hex( ptr, len ); +#endif + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("writex: %d %d %s\n", fd, r, strerror(errno)); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + + D("writex: %d ok\n", fd); + return 0; +} + +int check_header(apacket *p) +{ + if(p->msg.magic != (p->msg.command ^ 0xffffffff)) { + D("check_header(): invalid magic\n"); + return -1; + } + + if(p->msg.data_length > MAX_PAYLOAD) { + D("check_header(): %d > MAX_PAYLOAD\n", p->msg.data_length); + return -1; + } + + return 0; +} + +int check_data(apacket *p) +{ + unsigned count, sum; + unsigned char *x; + + count = p->msg.data_length; + x = p->data; + sum = 0; + while(count-- > 0) { + sum += *x++; + } + + if(sum != p->msg.data_check) { + return -1; + } else { + return 0; + } +} + diff --git a/adb/transport_local.c b/adb/transport_local.c new file mode 100644 index 0000000..be01f29 --- /dev/null +++ b/adb/transport_local.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "sysdeps.h" +#include <sys/types.h> + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +#ifdef __ppc__ +#define H4(x) (((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24) +static inline void fix_endians(apacket *p) +{ + p->msg.command = H4(p->msg.command); + p->msg.arg0 = H4(p->msg.arg0); + p->msg.arg1 = H4(p->msg.arg1); + p->msg.data_length = H4(p->msg.data_length); + p->msg.data_check = H4(p->msg.data_check); + p->msg.magic = H4(p->msg.magic); +} +#else +#define fix_endians(p) do {} while (0) +#endif + +#if ADB_HOST +/* we keep a list of opened transports, transport 0 is bound to 5555, + * transport 1 to 5557, .. transport n to 5555 + n*2. the list is used + * to detect when we're trying to connect twice to a given local transport + */ +#define ADB_LOCAL_TRANSPORT_MAX 16 + +ADB_MUTEX_DEFINE( local_transports_lock ); + +static atransport* local_transports[ ADB_LOCAL_TRANSPORT_MAX ]; +#endif /* ADB_HOST */ + +static int remote_read(apacket *p, atransport *t) +{ + if(readx(t->sfd, &p->msg, sizeof(amessage))){ + D("remote local: read terminated (message)\n"); + return -1; + } + + fix_endians(p); + +#if 0 && defined __ppc__ + D("read remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n", + p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic); +#endif + if(check_header(p)) { + D("bad header: terminated (data)\n"); + return -1; + } + + if(readx(t->sfd, p->data, p->msg.data_length)){ + D("remote local: terminated (data)\n"); + return -1; + } + + if(check_data(p)) { + D("bad data: terminated (data)\n"); + return -1; + } + + return 0; +} + +static int remote_write(apacket *p, atransport *t) +{ + int length = p->msg.data_length; + + fix_endians(p); + +#if 0 && defined __ppc__ + D("write remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n", + p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic); +#endif + if(writex(t->sfd, &p->msg, sizeof(amessage) + length)) { + D("remote local: write terminated\n"); + return -1; + } + + return 0; +} + + +int local_connect(int port) +{ + char buf[64]; + int fd = -1; + +#if ADB_HOST + const char *host = getenv("ADBHOST"); + if (host) { + fd = socket_network_client(host, port, SOCK_STREAM); + } +#endif + if (fd < 0) { + fd = socket_loopback_client(port, SOCK_STREAM); + } + + if (fd >= 0) { + D("client: connected on remote on fd %d\n", fd); + close_on_exec(fd); + disable_tcp_nagle(fd); + snprintf(buf, sizeof buf, "%s%d", LOCAL_CLIENT_PREFIX, port - 1); + register_socket_transport(fd, buf, port); + return 0; + } + return -1; +} + + +static void *client_socket_thread(void *x) +{ +#if ADB_HOST + int port = ADB_LOCAL_TRANSPORT_PORT; + int count = ADB_LOCAL_TRANSPORT_MAX; + + D("transport: client_socket_thread() starting\n"); + + /* try to connect to any number of running emulator instances */ + /* this is only done when ADB starts up. later, each new emulator */ + /* will send a message to ADB to indicate that is is starting up */ + for ( ; count > 0; count--, port += 2 ) { + (void) local_connect(port); + } +#endif + return 0; +} + +static void *server_socket_thread(void *x) +{ + int serverfd, fd; + struct sockaddr addr; + socklen_t alen; + + D("transport: server_socket_thread() starting\n"); + serverfd = -1; + for(;;) { + if(serverfd == -1) { + serverfd = socket_inaddr_any_server(ADB_LOCAL_TRANSPORT_PORT, SOCK_STREAM); + if(serverfd < 0) { + D("server: cannot bind socket yet\n"); + adb_sleep_ms(1000); + continue; + } + close_on_exec(serverfd); + } + + alen = sizeof(addr); + D("server: trying to get new connection from %d\n", ADB_LOCAL_TRANSPORT_PORT); + fd = adb_socket_accept(serverfd, &addr, &alen); + if(fd >= 0) { + D("server: new connection on fd %d\n", fd); + close_on_exec(fd); + disable_tcp_nagle(fd); + register_socket_transport(fd,"host",ADB_LOCAL_TRANSPORT_PORT); + } + } + D("transport: server_socket_thread() exiting\n"); + return 0; +} + +void local_init(void) +{ + adb_thread_t thr; + void* (*func)(void *); + + if(HOST) { + func = client_socket_thread; + } else { + func = server_socket_thread; + } + + D("transport: local %s init\n", HOST ? "client" : "server"); + + if(adb_thread_create(&thr, func, 0)) { + fatal_errno("cannot create local socket %s thread", + HOST ? "client" : "server"); + } +} + +static void remote_kick(atransport *t) +{ + int fd = t->sfd; + t->sfd = -1; + adb_close(fd); + +#if ADB_HOST + if(HOST) { + int nn; + adb_mutex_lock( &local_transports_lock ); + for (nn = 0; nn < ADB_LOCAL_TRANSPORT_MAX; nn++) { + if (local_transports[nn] == t) { + local_transports[nn] = NULL; + break; + } + } + adb_mutex_unlock( &local_transports_lock ); + } +#endif +} + +static void remote_close(atransport *t) +{ + adb_close(t->fd); +} + +int init_socket_transport(atransport *t, int s, int port) +{ + int fail = 0; + + t->kick = remote_kick; + t->close = remote_close; + t->read_from_remote = remote_read; + t->write_to_remote = remote_write; + t->sfd = s; + t->sync_token = 1; + t->connection_state = CS_OFFLINE; + t->type = kTransportLocal; + +#if ADB_HOST + if (HOST) { + adb_mutex_lock( &local_transports_lock ); + { + int index = (port - ADB_LOCAL_TRANSPORT_PORT)/2; + + if (!(port & 1) || index < 0 || index >= ADB_LOCAL_TRANSPORT_MAX) { + D("bad local transport port number: %d\n", port); + fail = -1; + } + else if (local_transports[index] != NULL) { + D("local transport for port %d already registered (%p)?\n", + port, local_transports[index]); + fail = -1; + } + else + local_transports[index] = t; + } + adb_mutex_unlock( &local_transports_lock ); + } +#endif + return fail; +} diff --git a/adb/transport_usb.c b/adb/transport_usb.c new file mode 100644 index 0000000..01c4a7e --- /dev/null +++ b/adb/transport_usb.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sysdeps.h> + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +/* XXX better define? */ +#ifdef __ppc__ +#define H4(x) (((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24) +static inline void fix_endians(apacket *p) +{ + p->msg.command = H4(p->msg.command); + p->msg.arg0 = H4(p->msg.arg0); + p->msg.arg1 = H4(p->msg.arg1); + p->msg.data_length = H4(p->msg.data_length); + p->msg.data_check = H4(p->msg.data_check); + p->msg.magic = H4(p->msg.magic); +} +unsigned host_to_le32(unsigned n) +{ + return H4(n); +} +#else +#define fix_endians(p) do {} while (0) +unsigned host_to_le32(unsigned n) +{ + return n; +} +#endif + +static int remote_read(apacket *p, atransport *t) +{ + if(usb_read(t->usb, &p->msg, sizeof(amessage))){ + D("remote usb: read terminated (message)\n"); + return -1; + } + + fix_endians(p); + + if(check_header(p)) { + D("remote usb: check_header failed\n"); + return -1; + } + + if(p->msg.data_length) { + if(usb_read(t->usb, p->data, p->msg.data_length)){ + D("remote usb: terminated (data)\n"); + return -1; + } + } + + if(check_data(p)) { + D("remote usb: check_data failed\n"); + return -1; + } + + return 0; +} + +static int remote_write(apacket *p, atransport *t) +{ + unsigned size = p->msg.data_length; + + fix_endians(p); + + if(usb_write(t->usb, &p->msg, sizeof(amessage))) { + D("remote usb: 1 - write terminated\n"); + return -1; + } + if(p->msg.data_length == 0) return 0; + if(usb_write(t->usb, &p->data, size)) { + D("remote usb: 2 - write terminated\n"); + return -1; + } + + return 0; +} + +static void remote_close(atransport *t) +{ + usb_close(t->usb); + t->usb = 0; +} + +static void remote_kick(atransport *t) +{ + usb_kick(t->usb); +} + +void init_usb_transport(atransport *t, usb_handle *h) +{ + D("transport: usb\n"); + t->close = remote_close; + t->kick = remote_kick; + t->read_from_remote = remote_read; + t->write_to_remote = remote_write; + t->sync_token = 1; + t->connection_state = CS_OFFLINE; + t->type = kTransportUsb; + t->usb = h; + +#if ADB_HOST + HOST = 1; +#else + HOST = 0; +#endif +} + +int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol) +{ + if (vid == VENDOR_ID_GOOGLE) { + /* might support adb */ + } else if (vid == VENDOR_ID_HTC) { + /* might support adb */ + } else { + /* not supported */ + return 0; + } + + /* class:vendor (0xff) subclass:android (0x42) proto:adb (0x01) */ + if(usb_class == 0xff) { + if((usb_subclass == 0x42) && (usb_protocol == 0x01)) { + return 1; + } + } + + return 0; +} diff --git a/adb/usb_linux.c b/adb/usb_linux.c new file mode 100644 index 0000000..32ce0a9 --- /dev/null +++ b/adb/usb_linux.c @@ -0,0 +1,654 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> + +#include <linux/usbdevice_fs.h> +#include <linux/version.h> +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20) +#include <linux/usb/ch9.h> +#else +#include <linux/usb_ch9.h> +#endif +#include <asm/byteorder.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + + +/* usb scan debugging is waaaay too verbose */ +#define DBGX(x...) + +static adb_mutex_t usb_lock = ADB_MUTEX_INITIALIZER; + +struct usb_handle +{ + usb_handle *prev; + usb_handle *next; + + char fname[64]; + int desc; + unsigned char ep_in; + unsigned char ep_out; + + unsigned zero_mask; + + struct usbdevfs_urb urb_in; + struct usbdevfs_urb urb_out; + + int urb_in_busy; + int urb_out_busy; + int dead; + + adb_cond_t notify; + adb_mutex_t lock; + + // for garbage collecting disconnected devices + int mark; + + // ID of thread currently in REAPURB + pthread_t reaper_thread; +}; + +static usb_handle handle_list = { + .prev = &handle_list, + .next = &handle_list, +}; + +static int known_device(const char *dev_name) +{ + usb_handle *usb; + + adb_mutex_lock(&usb_lock); + for(usb = handle_list.next; usb != &handle_list; usb = usb->next){ + if(!strcmp(usb->fname, dev_name)) { + // set mark flag to indicate this device is still alive + usb->mark = 1; + adb_mutex_unlock(&usb_lock); + return 1; + } + } + adb_mutex_unlock(&usb_lock); + return 0; +} + +static void kick_disconnected_devices() +{ + usb_handle *usb; + + adb_mutex_lock(&usb_lock); + // kick any devices in the device list that were not found in the device scan + for(usb = handle_list.next; usb != &handle_list; usb = usb->next){ + if (usb->mark == 0) { + usb_kick(usb); + } else { + usb->mark = 0; + } + } + adb_mutex_unlock(&usb_lock); + +} + +static void register_device(const char *dev_name, unsigned char ep_in, unsigned char ep_out, + int ifc, const char *serial, unsigned zero_mask); + +static inline int badname(const char *name) +{ + while(*name) { + if(!isdigit(*name++)) return 1; + } + return 0; +} + +static int find_usb_device(const char *base, + void (*register_device_callback) (const char *, unsigned char, unsigned char, int, const char *, unsigned)) +{ + char busname[32], devname[32]; + unsigned char local_ep_in, local_ep_out; + DIR *busdir , *devdir ; + struct dirent *de; + int fd ; + int found_device = 0; + char serial[256]; + + busdir = opendir(base); + if(busdir == 0) return 0; + + while((de = readdir(busdir)) != 0) { + if(badname(de->d_name)) continue; + + snprintf(busname, sizeof busname, "%s/%s", base, de->d_name); + devdir = opendir(busname); + if(devdir == 0) continue; + +// DBGX("[ scanning %s ]\n", busname); + while((de = readdir(devdir))) { + unsigned char devdesc[256]; + unsigned char* bufptr = devdesc; + struct usb_device_descriptor* device; + struct usb_config_descriptor* config; + struct usb_interface_descriptor* interface; + struct usb_endpoint_descriptor *ep1, *ep2; + unsigned zero_mask = 0; + unsigned vid, pid; + int i, interfaces; + size_t desclength; + + if(badname(de->d_name)) continue; + snprintf(devname, sizeof devname, "%s/%s", busname, de->d_name); + + if(known_device(devname)) { + DBGX("skipping %s\n", devname); + continue; + } + +// DBGX("[ scanning %s ]\n", devname); + if((fd = unix_open(devname, O_RDWR)) < 0) { + continue; + } + + desclength = adb_read(fd, devdesc, sizeof(devdesc)); + + // should have device and configuration descriptors, and atleast two endpoints + if (desclength < USB_DT_DEVICE_SIZE + USB_DT_CONFIG_SIZE) { + D("desclength %d is too small\n", desclength); + adb_close(fd); + continue; + } + + device = (struct usb_device_descriptor*)bufptr; + bufptr += USB_DT_DEVICE_SIZE; + + if((device->bLength != USB_DT_DEVICE_SIZE) || (device->bDescriptorType != USB_DT_DEVICE)) { + adb_close(fd); + continue; + } + + vid = __le16_to_cpu(device->idVendor); + pid = __le16_to_cpu(device->idProduct); + pid = devdesc[10] | (devdesc[11] << 8); + DBGX("[ %s is V:%04x P:%04x ]\n", devname, vid, pid); + + // should have config descriptor next + config = (struct usb_config_descriptor *)bufptr; + bufptr += USB_DT_CONFIG_SIZE; + if (config->bLength != USB_DT_CONFIG_SIZE || config->bDescriptorType != USB_DT_CONFIG) { + D("usb_config_descriptor not found\n"); + adb_close(fd); + continue; + } + + // loop through all the interfaces and look for the ADB interface + interfaces = config->bNumInterfaces; + for (i = 0; i < interfaces; i++) { + if (bufptr + USB_DT_ENDPOINT_SIZE > devdesc + desclength) + break; + + interface = (struct usb_interface_descriptor *)bufptr; + bufptr += USB_DT_INTERFACE_SIZE; + if (interface->bLength != USB_DT_INTERFACE_SIZE || + interface->bDescriptorType != USB_DT_INTERFACE) { + D("usb_interface_descriptor not found\n"); + break; + } + + DBGX("bInterfaceClass: %d, bInterfaceSubClass: %d," + "bInterfaceProtocol: %d, bNumEndpoints: %d\n", + interface->bInterfaceClass, interface->bInterfaceSubClass, + interface->bInterfaceProtocol, interface->bNumEndpoints); + + if (interface->bNumEndpoints == 2 && + is_adb_interface(vid, pid, interface->bInterfaceClass, + interface->bInterfaceSubClass, interface->bInterfaceProtocol)) { + + DBGX("looking for bulk endpoints\n"); + // looks like ADB... + ep1 = (struct usb_endpoint_descriptor *)bufptr; + bufptr += USB_DT_ENDPOINT_SIZE; + ep2 = (struct usb_endpoint_descriptor *)bufptr; + bufptr += USB_DT_ENDPOINT_SIZE; + + if (bufptr > devdesc + desclength || + ep1->bLength != USB_DT_ENDPOINT_SIZE || + ep1->bDescriptorType != USB_DT_ENDPOINT || + ep2->bLength != USB_DT_ENDPOINT_SIZE || + ep2->bDescriptorType != USB_DT_ENDPOINT) { + D("endpoints not found\n"); + break; + } + + // both endpoints should be bulk + if (ep1->bmAttributes != USB_ENDPOINT_XFER_BULK || + ep2->bmAttributes != USB_ENDPOINT_XFER_BULK) { + D("bulk endpoints not found\n"); + continue; + } + + /* aproto 01 needs 0 termination */ + if(interface->bInterfaceProtocol == 0x01) { + zero_mask = ep1->wMaxPacketSize - 1; + } + + // we have a match. now we just need to figure out which is in and which is out. + if (ep1->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + local_ep_in = ep1->bEndpointAddress; + local_ep_out = ep2->bEndpointAddress; + } else { + local_ep_in = ep2->bEndpointAddress; + local_ep_out = ep1->bEndpointAddress; + } + + // read the device's serial number + serial[0] = 0; + memset(serial, 0, sizeof(serial)); + if (device->iSerialNumber) { + struct usbdevfs_ctrltransfer ctrl; + __u16 buffer[128]; + int result; + + memset(buffer, 0, sizeof(buffer)); + memset(&ctrl, 0, sizeof(ctrl)); + + ctrl.bRequestType = USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE; + ctrl.bRequest = USB_REQ_GET_DESCRIPTOR; + ctrl.wValue = (USB_DT_STRING << 8) | device->iSerialNumber; + ctrl.wIndex = 0; + ctrl.wLength = sizeof(buffer); + ctrl.data = buffer; + + result = ioctl(fd, USBDEVFS_CONTROL, &ctrl); + if (result > 0) { + int i; + // skip first word, and copy the rest to the serial string, changing shorts to bytes. + result /= 2; + for (i = 1; i < result; i++) + serial[i - 1] = buffer[i]; + serial[i - 1] = 0; + } + } + + register_device_callback(devname, local_ep_in, local_ep_out, + interface->bInterfaceNumber, serial, zero_mask); + + found_device = 1; + break; + } else { + // skip to next interface + bufptr += (interface->bNumEndpoints * USB_DT_ENDPOINT_SIZE); + } + } // end of for + + adb_close(fd); + } // end of devdir while + closedir(devdir); + } //end of busdir while + closedir(busdir); + + return found_device; +} + +void usb_cleanup() +{ +} + +static int usb_bulk_write(usb_handle *h, const void *data, int len) +{ + struct usbdevfs_urb *urb = &h->urb_out; + int res; + + memset(urb, 0, sizeof(*urb)); + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->endpoint = h->ep_out; + urb->status = -1; + urb->buffer = (void*) data; + urb->buffer_length = len; + + D("++ write ++\n"); + + adb_mutex_lock(&h->lock); + if(h->dead) { + res = -1; + goto fail; + } + do { + res = ioctl(h->desc, USBDEVFS_SUBMITURB, urb); + } while((res < 0) && (errno == EINTR)); + + if(res < 0) { + goto fail; + } + + res = -1; + h->urb_out_busy = 1; + for(;;) { + adb_cond_wait(&h->notify, &h->lock); + if(h->dead) { + break; + } + if(h->urb_out_busy == 0) { + if(urb->status == 0) { + res = urb->actual_length; + } + break; + } + } +fail: + adb_mutex_unlock(&h->lock); + D("-- write --\n"); + return res; +} + +static int usb_bulk_read(usb_handle *h, void *data, int len) +{ + struct usbdevfs_urb *urb = &h->urb_in; + struct usbdevfs_urb *out = NULL; + int res; + + memset(urb, 0, sizeof(*urb)); + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->endpoint = h->ep_in; + urb->status = -1; + urb->buffer = data; + urb->buffer_length = len; + + + adb_mutex_lock(&h->lock); + if(h->dead) { + res = -1; + goto fail; + } + do { + res = ioctl(h->desc, USBDEVFS_SUBMITURB, urb); + } while((res < 0) && (errno == EINTR)); + + if(res < 0) { + goto fail; + } + + h->urb_in_busy = 1; + for(;;) { + D("[ reap urb - wait ]\n"); + h->reaper_thread = pthread_self(); + adb_mutex_unlock(&h->lock); + res = ioctl(h->desc, USBDEVFS_REAPURB, &out); + adb_mutex_lock(&h->lock); + h->reaper_thread = 0; + if(h->dead) { + res = -1; + break; + } + if(res < 0) { + if(errno == EINTR) { + continue; + } + D("[ reap urb - error ]\n"); + break; + } + D("[ urb @%p status = %d, actual = %d ]\n", + out, out->status, out->actual_length); + + if(out == &h->urb_in) { + D("[ reap urb - IN complete ]\n"); + h->urb_in_busy = 0; + if(urb->status == 0) { + res = urb->actual_length; + } else { + res = -1; + } + break; + } + if(out == &h->urb_out) { + D("[ reap urb - OUT compelete ]\n"); + h->urb_out_busy = 0; + adb_cond_broadcast(&h->notify); + } + } +fail: + adb_mutex_unlock(&h->lock); + return res; +} + + +int usb_write(usb_handle *h, const void *_data, int len) +{ + unsigned char *data = (unsigned char*) _data; + int n; + int need_zero = 0; + + if(h->zero_mask) { + /* if we need 0-markers and our transfer + ** is an even multiple of the packet size, + ** we make note of it + */ + if(!(len & h->zero_mask)) { + need_zero = 1; + } + } + + while(len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + n = usb_bulk_write(h, data, xfer); + if(n != xfer) { + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + + len -= xfer; + data += xfer; + } + + if(need_zero){ + n = usb_bulk_write(h, _data, 0); + return n; + } + + return 0; +} + +int usb_read(usb_handle *h, void *_data, int len) +{ + unsigned char *data = (unsigned char*) _data; + int n; + + D("++ usb_read ++\n"); + while(len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + D("[ usb read %d fd = %d], fname=%s\n", xfer, h->desc, h->fname); + n = usb_bulk_read(h, data, xfer); + D("[ usb read %d ] = %d, fname=%s\n", xfer, n, h->fname); + if(n != xfer) { + if((errno == ETIMEDOUT) && (h->desc != -1)) { + D("[ timeout ]\n"); + if(n > 0){ + data += n; + len -= n; + } + continue; + } + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + + len -= xfer; + data += xfer; + } + + D("-- usb_read --\n"); + return 0; +} + +void usb_kick(usb_handle *h) +{ + D("[ kicking %p (fd = %d) ]\n", h, h->desc); + adb_mutex_lock(&h->lock); + if(h->dead == 0) { + h->dead = 1; + + /* HACK ALERT! + ** Sometimes we get stuck in ioctl(USBDEVFS_REAPURB). + ** This is a workaround for that problem. + */ + if (h->reaper_thread) { + pthread_kill(h->reaper_thread, SIGALRM); + } + + /* cancel any pending transactions + ** these will quietly fail if the txns are not active, + ** but this ensures that a reader blocked on REAPURB + ** will get unblocked + */ + ioctl(h->desc, USBDEVFS_DISCARDURB, &h->urb_in); + ioctl(h->desc, USBDEVFS_DISCARDURB, &h->urb_out); + h->urb_in.status = -ENODEV; + h->urb_out.status = -ENODEV; + h->urb_in_busy = 0; + h->urb_out_busy = 0; + adb_cond_broadcast(&h->notify); + } + adb_mutex_unlock(&h->lock); +} + +int usb_close(usb_handle *h) +{ + D("[ usb close ... ]\n"); + adb_mutex_lock(&usb_lock); + h->next->prev = h->prev; + h->prev->next = h->next; + h->prev = 0; + h->next = 0; + + adb_close(h->desc); + D("[ usb closed %p (fd = %d) ]\n", h, h->desc); + adb_mutex_unlock(&usb_lock); + + free(h); + return 0; +} + +static void register_device(const char *dev_name, + unsigned char ep_in, unsigned char ep_out, + int interface, + const char *serial, unsigned zero_mask) +{ + usb_handle* usb = 0; + int n = 0; + + /* Since Linux will not reassign the device ID (and dev_name) + ** as long as the device is open, we can add to the list here + ** once we open it and remove from the list when we're finally + ** closed and everything will work out fine. + ** + ** If we have a usb_handle on the list 'o handles with a matching + ** name, we have no further work to do. + */ + adb_mutex_lock(&usb_lock); + for(usb = handle_list.next; usb != &handle_list; usb = usb->next){ + if(!strcmp(usb->fname, dev_name)) { + adb_mutex_unlock(&usb_lock); + return; + } + } + adb_mutex_unlock(&usb_lock); + + D("[ usb located new device %s (%d/%d/%d) ]\n", + dev_name, ep_in, ep_out, interface); + usb = calloc(1, sizeof(usb_handle)); + strcpy(usb->fname, dev_name); + usb->ep_in = ep_in; + usb->ep_out = ep_out; + usb->zero_mask = zero_mask; + + adb_cond_init(&usb->notify, 0); + adb_mutex_init(&usb->lock, 0); + /* initialize mark to 1 so we don't get garbage collected after the device scan */ + usb->mark = 1; + usb->reaper_thread = 0; + + usb->desc = unix_open(usb->fname, O_RDWR); + if(usb->desc < 0) goto fail; + D("[ usb open %s fd = %d]\n", usb->fname, usb->desc); + n = ioctl(usb->desc, USBDEVFS_CLAIMINTERFACE, &interface); + if(n != 0) goto fail; + + /* add to the end of the active handles */ + adb_mutex_lock(&usb_lock); + usb->next = &handle_list; + usb->prev = handle_list.prev; + usb->prev->next = usb; + usb->next->prev = usb; + adb_mutex_unlock(&usb_lock); + + register_usb_transport(usb, serial); + return; + +fail: + D("[ usb open %s error=%d, err_str = %s]\n", + usb->fname, errno, strerror(errno)); + if(usb->desc >= 0) { + adb_close(usb->desc); + } + free(usb); +} + +void* device_poll_thread(void* unused) +{ + D("Created device thread\n"); + for(;;) { + /* XXX use inotify */ + find_usb_device("/dev/bus/usb", register_device); + kick_disconnected_devices(); + sleep(1); + } + return NULL; +} + +static void sigalrm_handler(int signo) +{ + // don't need to do anything here +} + +void usb_init() +{ + adb_thread_t tid; + struct sigaction actions; + + memset(&actions, 0, sizeof(actions)); + sigemptyset(&actions.sa_mask); + actions.sa_flags = 0; + actions.sa_handler = sigalrm_handler; + sigaction(SIGALRM,& actions, NULL); + + if(adb_thread_create(&tid, device_poll_thread, NULL)){ + fatal_errno("cannot create input thread"); + } +} + diff --git a/adb/usb_linux_client.c b/adb/usb_linux_client.c new file mode 100644 index 0000000..530bd04 --- /dev/null +++ b/adb/usb_linux_client.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <dirent.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + + +struct usb_handle +{ + int fd; + adb_cond_t notify; + adb_mutex_t lock; +}; + +void usb_cleanup() +{ + // nothing to do here +} + +static void *usb_open_thread(void *x) +{ + struct usb_handle *usb = (struct usb_handle *)x; + int fd; + + while (1) { + // wait until the USB device needs opening + adb_mutex_lock(&usb->lock); + while (usb->fd != -1) + adb_cond_wait(&usb->notify, &usb->lock); + adb_mutex_unlock(&usb->lock); + + D("[ usb_thread - opening device ]\n"); + do { + /* XXX use inotify? */ + fd = unix_open("/dev/android_adb", O_RDWR); + if (fd < 0) { + // to support older kernels + fd = unix_open("/dev/android", O_RDWR); + } + if (fd < 0) { + adb_sleep_ms(1000); + } + } while (fd < 0); + D("[ opening device succeeded ]\n"); + + close_on_exec(fd); + usb->fd = fd; + + D("[ usb_thread - registering device ]\n"); + register_usb_transport(usb, 0); + } + + // never gets here + return 0; +} + +int usb_write(usb_handle *h, const void *data, int len) +{ + int n; + + D("[ write %d ]\n", len); + n = adb_write(h->fd, data, len); + if(n != len) { + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + D("[ done ]\n"); + return 0; +} + +int usb_read(usb_handle *h, void *data, int len) +{ + int n; + + D("[ read %d ]\n", len); + n = adb_read(h->fd, data, len); + if(n != len) { + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + return 0; +} + +void usb_init() +{ + usb_handle *h; + adb_thread_t tid; + int fd; + + h = calloc(1, sizeof(usb_handle)); + h->fd = -1; + adb_cond_init(&h->notify, 0); + adb_mutex_init(&h->lock, 0); + + // Open the file /dev/android_adb_enable to trigger + // the enabling of the adb USB function in the kernel. + // We never touch this file again - just leave it open + // indefinitely so the kernel will know when we are running + // and when we are not. + fd = unix_open("/dev/android_adb_enable", O_RDWR); + if (fd < 0) { + D("failed to open /dev/android_adb_enable\n"); + } else { + close_on_exec(fd); + } + + D("[ usb_init - starting thread ]\n"); + if(adb_thread_create(&tid, usb_open_thread, h)){ + fatal_errno("cannot create usb thread"); + } +} + +void usb_kick(usb_handle *h) +{ + D("usb_kick\n"); + adb_mutex_lock(&h->lock); + adb_close(h->fd); + h->fd = -1; + + // notify usb_open_thread that we are disconnected + adb_cond_signal(&h->notify); + adb_mutex_unlock(&h->lock); +} + +int usb_close(usb_handle *h) +{ + // nothing to do here + return 0; +} diff --git a/adb/usb_osx.c b/adb/usb_osx.c new file mode 100644 index 0000000..49e1eef --- /dev/null +++ b/adb/usb_osx.c @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <CoreFoundation/CoreFoundation.h> + +#include <IOKit/IOKitLib.h> +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOMessage.h> +#include <mach/mach_port.h> + +#include "sysdeps.h" + +#include <stdio.h> + +#define TRACE_TAG TRACE_USB +#include "adb.h" + +#define DBG D + +typedef struct { + int vid; + int pid; +} VendorProduct; + +#define kSupportedDeviceCount 4 +VendorProduct kSupportedDevices[kSupportedDeviceCount] = { + { VENDOR_ID_GOOGLE, PRODUCT_ID_SOONER }, + { VENDOR_ID_GOOGLE, PRODUCT_ID_SOONER_COMP }, + { VENDOR_ID_HTC, PRODUCT_ID_DREAM }, + { VENDOR_ID_HTC, PRODUCT_ID_DREAM_COMP }, +}; + +static IONotificationPortRef notificationPort = 0; +static io_iterator_t notificationIterators[kSupportedDeviceCount]; + +struct usb_handle +{ + UInt8 bulkIn; + UInt8 bulkOut; + IOUSBInterfaceInterface **interface; + io_object_t usbNotification; + unsigned int zero_mask; +}; + +static CFRunLoopRef currentRunLoop = 0; +static pthread_mutex_t start_lock; +static pthread_cond_t start_cond; + + +static void AndroidDeviceAdded(void *refCon, io_iterator_t iterator); +static void AndroidDeviceNotify(void *refCon, io_iterator_t iterator, natural_t messageType, void *messageArgument); +static usb_handle* FindDeviceInterface(IOUSBDeviceInterface **dev, UInt16 vendor, UInt16 product); + +static int +InitUSB() +{ + CFMutableDictionaryRef matchingDict; + CFRunLoopSourceRef runLoopSource; + SInt32 vendor, product; + int i; + + //* To set up asynchronous notifications, create a notification port and + //* add its run loop event source to the program's run loop + notificationPort = IONotificationPortCreate(kIOMasterPortDefault); + runLoopSource = IONotificationPortGetRunLoopSource(notificationPort); + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode); + + memset(notificationIterators, 0, sizeof(notificationIterators)); + + //* loop through all supported vendor/product pairs + for (i = 0; i < kSupportedDeviceCount; i++) { + //* Create our matching dictionary to find the Android device + //* IOServiceAddMatchingNotification consumes the reference, so we do not need to release this + matchingDict = IOServiceMatching(kIOUSBDeviceClassName); + + if (!matchingDict) { + DBG("ERR: Couldn't create USB matching dictionary.\n"); + return -1; + } + + //* Set up two matching dictionaries, one for each product ID we support. + //* This will cause the kernel to notify us only if the vendor and product IDs match. + vendor = kSupportedDevices[i].vid; + product = kSupportedDevices[i].pid; + CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendor)); + CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &product)); + + //* Now set up two notifications: one to be called when a raw device + //* is first matched by the I/O Kit and another to be called when the + //* device is terminated. + //* we need to do this with each matching dictionary. + IOServiceAddMatchingNotification( + notificationPort, + kIOFirstMatchNotification, + matchingDict, + AndroidDeviceAdded, + NULL, + ¬ificationIterators[i]); + + //* Iterate over set of matching devices to access already-present devices + //* and to arm the notification + AndroidDeviceAdded(NULL, notificationIterators[i]); + } + + return 0; +} + +static void +AndroidDeviceAdded(void *refCon, io_iterator_t iterator) +{ + kern_return_t kr; + io_service_t usbDevice; + IOCFPlugInInterface **plugInInterface = NULL; + IOUSBDeviceInterface182 **dev = NULL; + HRESULT result; + SInt32 score; + UInt16 vendor; + UInt16 product; + UInt8 serialIndex; + char serial[256]; + + while ((usbDevice = IOIteratorNext(iterator))) { + //* Create an intermediate plugin + kr = IOCreatePlugInInterfaceForService(usbDevice, + kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, &score); + + if ((kIOReturnSuccess != kr) || (!plugInInterface)) { + DBG("ERR: Unable to create a plug-in (%08x)\n", kr); + goto continue1; + } + + //* Now create the device interface + result = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID) &dev); + + if (result || !dev) { + DBG("ERR: Couldn't create a device interface (%08x)\n", (int) result); + goto continue2; + } + + //* Check the device to see if it's ours + kr = (*dev)->GetDeviceVendor(dev, &vendor); + kr = (*dev)->GetDeviceProduct(dev, &product); + kr = (*dev)->USBGetSerialNumberStringIndex(dev, &serialIndex); + + if (serialIndex > 0) { + IOUSBDevRequest req; + UInt16 buffer[256]; + + req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); + req.bRequest = kUSBRqGetDescriptor; + req.wValue = (kUSBStringDesc << 8) | serialIndex; + req.wIndex = 0; + req.pData = buffer; + req.wLength = sizeof(buffer); + kr = (*dev)->DeviceRequest(dev, &req); + + if (kr == kIOReturnSuccess && req.wLenDone > 0) { + int i, count; + + // skip first word, and copy the rest to the serial string, changing shorts to bytes. + count = (req.wLenDone - 1) / 2; + for (i = 0; i < count; i++) + serial[i] = buffer[i + 1]; + serial[i] = 0; + } + } + + usb_handle* handle = NULL; + + //* Open the device + kr = (*dev)->USBDeviceOpen(dev); + + if (kr != kIOReturnSuccess) { + DBG("ERR: Could not open device: %08x\n", kr); + goto continue3; + } else { + //* Find an interface for the device + handle = FindDeviceInterface((IOUSBDeviceInterface**)dev, vendor, product); + } + + if (handle == NULL) { + DBG("ERR: Could not find device interface: %08x\n", kr); + (*dev)->USBDeviceClose(dev); + goto continue3; + } + + DBG("AndroidDeviceAdded calling register_usb_transport\n"); + register_usb_transport(handle, (serial[0] ? serial : NULL)); + + // Register for an interest notification of this device being removed. Pass the reference to our + // private data as the refCon for the notification. + kr = IOServiceAddInterestNotification(notificationPort, + usbDevice, + kIOGeneralInterest, + AndroidDeviceNotify, + handle, + &handle->usbNotification); + if (kIOReturnSuccess != kr) { + DBG("ERR: Unable to create interest notification (%08x)\n", kr); + } + +continue3: + (void)(*dev)->Release(dev); +continue2: + IODestroyPlugInInterface(plugInInterface); +continue1: + IOObjectRelease(usbDevice); + } +} + +static void +AndroidDeviceNotify(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) +{ + usb_handle *handle = (usb_handle *)refCon; + + if (messageType == kIOMessageServiceIsTerminated) { + DBG("AndroidDeviceNotify\n"); + IOObjectRelease(handle->usbNotification); + usb_kick(handle); + } +} + +static usb_handle* +FindDeviceInterface(IOUSBDeviceInterface **dev, UInt16 vendor, UInt16 product) +{ + usb_handle* handle = NULL; + IOReturn kr; + IOUSBFindInterfaceRequest request; + io_iterator_t iterator; + io_service_t usbInterface; + IOCFPlugInInterface **plugInInterface; + IOUSBInterfaceInterface **interface = NULL; + HRESULT result; + SInt32 score; + UInt8 interfaceNumEndpoints, interfaceClass, interfaceSubClass, interfaceProtocol; + UInt8 endpoint, configuration; + + //* Placing the constant KIOUSBFindInterfaceDontCare into the following + //* fields of the IOUSBFindInterfaceRequest structure will allow us to + //* find all of the interfaces + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + + //* SetConfiguration will kill an existing UMS connection, so let's not do this if not necessary. + configuration = 0; + (*dev)->GetConfiguration(dev, &configuration); + if (configuration != 1) + (*dev)->SetConfiguration(dev, 1); + + //* Get an iterator for the interfaces on the device + kr = (*dev)->CreateInterfaceIterator(dev, &request, &iterator); + + if (kr != kIOReturnSuccess) { + DBG("ERR: Couldn't create a device interface iterator: (%08x)\n", kr); + return NULL; + } + + while ((usbInterface = IOIteratorNext(iterator))) { + //* Create an intermediate plugin + kr = IOCreatePlugInInterfaceForService( + usbInterface, + kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, + &score); + + //* No longer need the usbInterface object now that we have the plugin + (void) IOObjectRelease(usbInterface); + + if ((kr != kIOReturnSuccess) || (!plugInInterface)) { + DBG("ERR: Unable to create plugin (%08x)\n", kr); + break; + } + + //* Now create the interface interface for the interface + result = (*plugInInterface)->QueryInterface( + plugInInterface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), + (LPVOID) &interface); + + //* No longer need the intermediate plugin + (*plugInInterface)->Release(plugInInterface); + + if (result || !interface) { + DBG("ERR: Couldn't create interface interface: (%08x)\n", + (unsigned int) result); + break; + } + + //* Now open the interface. This will cause the pipes associated with + //* the endpoints in the interface descriptor to be instantiated + kr = (*interface)->USBInterfaceOpen(interface); + + if (kr != kIOReturnSuccess) + { + DBG("ERR: Could not open interface: (%08x)\n", kr); + (void) (*interface)->Release(interface); + //* continue so we can try the next interface + continue; + } + + //* Get the number of endpoints associated with this interface + kr = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints); + + if (kr != kIOReturnSuccess) { + DBG("ERR: Unable to get number of endpoints: (%08x)\n", kr); + goto next_interface; + } + + //* Get interface class, subclass and protocol + if ((*interface)->GetInterfaceClass(interface, &interfaceClass) != kIOReturnSuccess || + (*interface)->GetInterfaceSubClass(interface, &interfaceSubClass) != kIOReturnSuccess || + (*interface)->GetInterfaceProtocol(interface, &interfaceProtocol) != kIOReturnSuccess) + { + DBG("ERR: Unable to get interface class, subclass and protocol\n"); + goto next_interface; + } + + //* check to make sure interface class, subclass and protocol match ADB + //* avoid opening mass storage endpoints + if (is_adb_interface(vendor, product, interfaceClass, interfaceSubClass, interfaceProtocol)) { + handle = calloc(1, sizeof(usb_handle)); + + //* Iterate over the endpoints for this interface and find the first + //* bulk in/out pipes available. These will be our read/write pipes. + for (endpoint = 0; endpoint <= interfaceNumEndpoints; endpoint++) { + UInt8 transferType; + UInt16 maxPacketSize; + UInt8 interval; + UInt8 number; + UInt8 direction; + + kr = (*interface)->GetPipeProperties(interface, endpoint, &direction, + &number, &transferType, &maxPacketSize, &interval); + + if (kIOReturnSuccess == kr) { + if (kUSBBulk != transferType) + continue; + + if (kUSBIn == direction) + handle->bulkIn = endpoint; + + if (kUSBOut == direction) + handle->bulkOut = endpoint; + + if (interfaceProtocol == 0x01) { + handle->zero_mask = maxPacketSize - 1; + } + + } else { + DBG("ERR: FindDeviceInterface - could not get pipe properties\n"); + } + } + + handle->interface = interface; + break; + } + +next_interface: + (*interface)->USBInterfaceClose(interface); + (*interface)->Release(interface); + } + + return handle; +} + + +void* RunLoopThread(void* unused) +{ + int i; + + InitUSB(); + + currentRunLoop = CFRunLoopGetCurrent(); + + // Signal the parent that we are running + adb_mutex_lock(&start_lock); + adb_cond_signal(&start_cond); + adb_mutex_unlock(&start_lock); + + CFRunLoopRun(); + currentRunLoop = 0; + + for (i = 0; i < kSupportedDeviceCount; i++) { + IOObjectRelease(notificationIterators[i]); + } + IONotificationPortDestroy(notificationPort); + + DBG("RunLoopThread done\n"); + return NULL; +} + + +static int initialized = 0; +void usb_init() +{ + if (!initialized) + { + adb_thread_t tid; + + adb_mutex_init(&start_lock, NULL); + adb_cond_init(&start_cond, NULL); + + if(adb_thread_create(&tid, RunLoopThread, NULL)) + fatal_errno("cannot create input thread"); + + // Wait for initialization to finish + adb_mutex_lock(&start_lock); + adb_cond_wait(&start_cond, &start_lock); + adb_mutex_unlock(&start_lock); + + adb_mutex_destroy(&start_lock); + adb_cond_destroy(&start_cond); + + initialized = 1; + } +} + +void usb_cleanup() +{ + DBG("usb_cleanup\n"); + close_usb_devices(); + if (currentRunLoop) + CFRunLoopStop(currentRunLoop); +} + +int usb_write(usb_handle *handle, const void *buf, int len) +{ + IOReturn result; + + if (!len) + return 0; + + if (!handle) + return -1; + + if (NULL == handle->interface) { + DBG("ERR: usb_write interface was null\n"); + return -1; + } + + if (0 == handle->bulkOut) { + DBG("ERR: bulkOut endpoint not assigned\n"); + return -1; + } + + result = + (*handle->interface)->WritePipe( + handle->interface, handle->bulkOut, (void *)buf, len); + + if ((result == 0) && (handle->zero_mask)) { + /* we need 0-markers and our transfer */ + if(!(len & handle->zero_mask)) { + result = + (*handle->interface)->WritePipe( + handle->interface, handle->bulkOut, (void *)buf, 0); + } + } + + if (0 == result) + return 0; + + DBG("ERR: usb_write failed with status %d\n", result); + return -1; +} + +int usb_read(usb_handle *handle, void *buf, int len) +{ + IOReturn result; + UInt32 numBytes = len; + + if (!len) { + return 0; + } + + if (!handle) { + return -1; + } + + if (NULL == handle->interface) { + DBG("ERR: usb_read interface was null\n"); + return -1; + } + + if (0 == handle->bulkIn) { + DBG("ERR: bulkIn endpoint not assigned\n"); + return -1; + } + + result = + (*handle->interface)->ReadPipe(handle->interface, + handle->bulkIn, buf, &numBytes); + + if (0 == result) + return 0; + else { + DBG("ERR: usb_read failed with status %d\n", result); + } + + return -1; +} + +int usb_close(usb_handle *handle) +{ + return 0; +} + +void usb_kick(usb_handle *handle) +{ + /* release the interface */ + if (handle->interface) + { + (*handle->interface)->USBInterfaceClose(handle->interface); + (*handle->interface)->Release(handle->interface); + handle->interface = 0; + } +} diff --git a/adb/usb_windows.c b/adb/usb_windows.c new file mode 100644 index 0000000..7ddaa0c --- /dev/null +++ b/adb/usb_windows.c @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <windows.h> +#include <winerror.h> +#include <errno.h> +#include <usb100.h> +#include <adb_api.h> +#include <stdio.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + +/** Structure usb_handle describes our connection to the usb device via + AdbWinApi.dll. This structure is returned from usb_open() routine and + is expected in each subsequent call that is accessing the device. +*/ +struct usb_handle { + /// Previous entry in the list of opened usb handles + usb_handle *prev; + + /// Next entry in the list of opened usb handles + usb_handle *next; + + /// Handle to USB interface + ADBAPIHANDLE adb_interface; + + /// Handle to USB read pipe (endpoint) + ADBAPIHANDLE adb_read_pipe; + + /// Handle to USB write pipe (endpoint) + ADBAPIHANDLE adb_write_pipe; + + /// Interface name + char* interface_name; + + /// Mask for determining when to use zero length packets + unsigned zero_mask; +}; + +/// Class ID assigned to the device by androidusb.sys +static const GUID usb_class_id = ANDROID_USB_CLASS_ID; + +/// List of opened usb handles +static usb_handle handle_list = { + .prev = &handle_list, + .next = &handle_list, +}; + +/// Locker for the list of opened usb handles +ADB_MUTEX_DEFINE( usb_lock ); + +/// Checks if there is opened usb handle in handle_list for this device. +int known_device(const char* dev_name); + +/// Checks if there is opened usb handle in handle_list for this device. +/// usb_lock mutex must be held before calling this routine. +int known_device_locked(const char* dev_name); + +/// Registers opened usb handle (adds it to handle_list). +int register_new_device(usb_handle* handle); + +/// Checks if interface (device) matches certain criteria +int recognized_device(usb_handle* handle); + +/// Enumerates present and available interfaces (devices), opens new ones and +/// registers usb transport for them. +void find_devices(); + +/// Entry point for thread that polls (every second) for new usb interfaces. +/// This routine calls find_devices in infinite loop. +void* device_poll_thread(void* unused); + +/// Initializes this module +void usb_init(); + +/// Cleans up this module +void usb_cleanup(); + +/// Opens usb interface (device) by interface (device) name. +usb_handle* do_usb_open(const wchar_t* interface_name); + +/// Writes data to the opened usb handle +int usb_write(usb_handle* handle, const void* data, int len); + +/// Reads data using the opened usb handle +int usb_read(usb_handle *handle, void* data, int len); + +/// Cleans up opened usb handle +void usb_cleanup_handle(usb_handle* handle); + +/// Cleans up (but don't close) opened usb handle +void usb_kick(usb_handle* handle); + +/// Closes opened usb handle +int usb_close(usb_handle* handle); + +/// Gets interface (device) name for an opened usb handle +const char *usb_name(usb_handle* handle); + +int known_device_locked(const char* dev_name) { + usb_handle* usb; + + if (NULL != dev_name) { + // Iterate through the list looking for the name match. + for(usb = handle_list.next; usb != &handle_list; usb = usb->next) { + // In Windows names are not case sensetive! + if((NULL != usb->interface_name) && + (0 == stricmp(usb->interface_name, dev_name))) { + return 1; + } + } + } + + return 0; +} + +int known_device(const char* dev_name) { + int ret = 0; + + if (NULL != dev_name) { + adb_mutex_lock(&usb_lock); + ret = known_device_locked(dev_name); + adb_mutex_unlock(&usb_lock); + } + + return ret; +} + +int register_new_device(usb_handle* handle) { + if (NULL == handle) + return 0; + + adb_mutex_lock(&usb_lock); + + // Check if device is already in the list + if (known_device_locked(handle->interface_name)) { + adb_mutex_unlock(&usb_lock); + return 0; + } + + // Not in the list. Add this handle to the list. + handle->next = &handle_list; + handle->prev = handle_list.prev; + handle->prev->next = handle; + handle->next->prev = handle; + + adb_mutex_unlock(&usb_lock); + + return 1; +} + +void* device_poll_thread(void* unused) { + D("Created device thread\n"); + + while(1) { + find_devices(); + adb_sleep_ms(1000); + } + + return NULL; +} + +void usb_init() { + adb_thread_t tid; + + if(adb_thread_create(&tid, device_poll_thread, NULL)) { + fatal_errno("cannot create input thread"); + } +} + +void usb_cleanup() { +} + +usb_handle* do_usb_open(const wchar_t* interface_name) { + // Allocate our handle + usb_handle* ret = (usb_handle*)malloc(sizeof(usb_handle)); + if (NULL == ret) + return NULL; + + // Set linkers back to the handle + ret->next = ret; + ret->prev = ret; + + // Create interface. + ret->adb_interface = AdbCreateInterfaceByName(interface_name); + + if (NULL == ret->adb_interface) { + free(ret); + errno = GetLastError(); + return NULL; + } + + // Open read pipe (endpoint) + ret->adb_read_pipe = + AdbOpenDefaultBulkReadEndpoint(ret->adb_interface, + AdbOpenAccessTypeReadWrite, + AdbOpenSharingModeReadWrite); + if (NULL != ret->adb_read_pipe) { + // Open write pipe (endpoint) + ret->adb_write_pipe = + AdbOpenDefaultBulkWriteEndpoint(ret->adb_interface, + AdbOpenAccessTypeReadWrite, + AdbOpenSharingModeReadWrite); + if (NULL != ret->adb_write_pipe) { + // Save interface name + unsigned long name_len = 0; + + // First get expected name length + AdbGetInterfaceName(ret->adb_interface, + NULL, + &name_len, + true); + if (0 != name_len) { + ret->interface_name = (char*)malloc(name_len); + + if (NULL != ret->interface_name) { + // Now save the name + if (AdbGetInterfaceName(ret->adb_interface, + ret->interface_name, + &name_len, + true)) { + // We're done at this point + return ret; + } + } else { + SetLastError(ERROR_OUTOFMEMORY); + } + } + } + } + + // Something went wrong. + errno = GetLastError(); + usb_cleanup_handle(ret); + free(ret); + SetLastError(errno); + + return NULL; +} + +int usb_write(usb_handle* handle, const void* data, int len) { + unsigned long time_out = 500 + len * 8; + unsigned long written = 0; + int ret; + + D("usb_write %d\n", len); + if (NULL != handle) { + // Perform write + ret = AdbWriteEndpointSync(handle->adb_write_pipe, + (void*)data, + (unsigned long)len, + &written, + time_out); + errno = GetLastError(); + + if (ret) { + // Make sure that we've written what we were asked to write + D("usb_write got: %ld, expected: %d\n", written, len); + if (written == (unsigned long)len) { + if(handle->zero_mask && (len & handle->zero_mask) == 0) { + // Send a zero length packet + AdbWriteEndpointSync(handle->adb_write_pipe, + (void*)data, + 0, + &written, + time_out); + } + return 0; + } + } else { + // assume ERROR_INVALID_HANDLE indicates we are disconnected + if (errno == ERROR_INVALID_HANDLE) + usb_kick(handle); + } + } else { + D("usb_write NULL handle\n"); + SetLastError(ERROR_INVALID_HANDLE); + } + + D("usb_write failed: %d\n", errno); + + return -1; +} + +int usb_read(usb_handle *handle, void* data, int len) { + unsigned long time_out = 500 + len * 8; + unsigned long read = 0; + int ret; + + D("usb_read %d\n", len); + if (NULL != handle) { + while (len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + ret = AdbReadEndpointSync(handle->adb_read_pipe, + (void*)data, + (unsigned long)xfer, + &read, + time_out); + errno = GetLastError(); + D("usb_write got: %ld, expected: %d, errno: %d\n", read, xfer, errno); + if (ret) { + data += read; + len -= read; + + if (len == 0) + return 0; + } else if (errno != ERROR_SEM_TIMEOUT) { + // assume ERROR_INVALID_HANDLE indicates we are disconnected + if (errno == ERROR_INVALID_HANDLE) + usb_kick(handle); + break; + } + } + } else { + D("usb_read NULL handle\n"); + SetLastError(ERROR_INVALID_HANDLE); + } + + D("usb_read failed: %d\n", errno); + + return -1; +} + +void usb_cleanup_handle(usb_handle* handle) { + if (NULL != handle) { + if (NULL != handle->interface_name) + free(handle->interface_name); + if (NULL != handle->adb_write_pipe) + AdbCloseHandle(handle->adb_write_pipe); + if (NULL != handle->adb_read_pipe) + AdbCloseHandle(handle->adb_read_pipe); + if (NULL != handle->adb_interface) + AdbCloseHandle(handle->adb_interface); + + handle->interface_name = NULL; + handle->adb_write_pipe = NULL; + handle->adb_read_pipe = NULL; + handle->adb_interface = NULL; + } +} + +void usb_kick(usb_handle* handle) { + if (NULL != handle) { + adb_mutex_lock(&usb_lock); + + usb_cleanup_handle(handle); + + adb_mutex_unlock(&usb_lock); + } else { + SetLastError(ERROR_INVALID_HANDLE); + errno = ERROR_INVALID_HANDLE; + } +} + +int usb_close(usb_handle* handle) { + D("usb_close\n"); + + if (NULL != handle) { + // Remove handle from the list + adb_mutex_lock(&usb_lock); + + if ((handle->next != handle) && (handle->prev != handle)) { + handle->next->prev = handle->prev; + handle->prev->next = handle->next; + handle->prev = handle; + handle->next = handle; + } + + adb_mutex_unlock(&usb_lock); + + // Cleanup handle + usb_cleanup_handle(handle); + free(handle); + } + + return 0; +} + +const char *usb_name(usb_handle* handle) { + if (NULL == handle) { + SetLastError(ERROR_INVALID_HANDLE); + errno = ERROR_INVALID_HANDLE; + return NULL; + } + + return (const char*)handle->interface_name; +} + +int recognized_device(usb_handle* handle) { + if (NULL == handle) + return 0; + + // Check vendor and product id first + USB_DEVICE_DESCRIPTOR device_desc; + + if (!AdbGetUsbDeviceDescriptor(handle->adb_interface, + &device_desc)) { + return 0; + } + + // Then check interface properties + USB_INTERFACE_DESCRIPTOR interf_desc; + + if (!AdbGetUsbInterfaceDescriptor(handle->adb_interface, + &interf_desc)) { + return 0; + } + + // Must have two endpoints + if (2 != interf_desc.bNumEndpoints) { + return 0; + } + + if (is_adb_interface(device_desc.idVendor, device_desc.idProduct, + interf_desc.bInterfaceClass, interf_desc.bInterfaceSubClass, interf_desc.bInterfaceProtocol)) { + + if(interf_desc.bInterfaceProtocol == 0x01) { + AdbEndpointInformation endpoint_info; + // assuming zero is a valid bulk endpoint ID + if (AdbGetEndpointInformation(handle->adb_interface, 0, &endpoint_info)) { + handle->zero_mask = endpoint_info.max_packet_size - 1; + } + } + + return 1; + } + + return 0; +} + +void find_devices() { + usb_handle* handle = NULL; + char entry_buffer[2048]; + char interf_name[2048]; + AdbInterfaceInfo* next_interface = (AdbInterfaceInfo*)(&entry_buffer[0]); + unsigned long entry_buffer_size = sizeof(entry_buffer); + char* copy_name; + + // Enumerate all present and active interfaces. + ADBAPIHANDLE enum_handle = + AdbEnumInterfaces(usb_class_id, true, true, true); + + if (NULL == enum_handle) + return; + + while (AdbNextInterface(enum_handle, next_interface, &entry_buffer_size)) { + // TODO: FIXME - temp hack converting wchar_t into char. + // It would be better to change AdbNextInterface so it will return + // interface name as single char string. + const wchar_t* wchar_name = next_interface->device_name; + for(copy_name = interf_name; + L'\0' != *wchar_name; + wchar_name++, copy_name++) { + *copy_name = (char)(*wchar_name); + } + *copy_name = '\0'; + + // Lets see if we already have this device in the list + if (!known_device(interf_name)) { + // This seems to be a new device. Open it! + handle = do_usb_open(next_interface->device_name); + if (NULL != handle) { + // Lets see if this interface (device) belongs to us + if (recognized_device(handle)) { + D("adding a new device %s\n", interf_name); + char serial_number[512]; + unsigned long serial_number_len = sizeof(serial_number); + if (AdbGetSerialNumber(handle->adb_interface, + serial_number, + &serial_number_len, + true)) { + // Lets make sure that we don't duplicate this device + if (register_new_device(handle)) { + register_usb_transport(handle, serial_number); + } else { + D("register_new_device failed for %s\n", interf_name); + usb_cleanup_handle(handle); + free(handle); + } + } else { + D("cannot get serial number\n"); + usb_cleanup_handle(handle); + free(handle); + } + } else { + usb_cleanup_handle(handle); + free(handle); + } + } + } + + entry_buffer_size = sizeof(entry_buffer); + } + + AdbCloseHandle(enum_handle); +} diff --git a/adb/utils.c b/adb/utils.c new file mode 100644 index 0000000..91518ba --- /dev/null +++ b/adb/utils.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "utils.h" +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +char* +buff_addc (char* buff, char* buffEnd, int c) +{ + int avail = buffEnd - buff; + + if (avail <= 0) /* already in overflow mode */ + return buff; + + if (avail == 1) { /* overflowing, the last byte is reserved for zero */ + buff[0] = 0; + return buff + 1; + } + + buff[0] = (char) c; /* add char and terminating zero */ + buff[1] = 0; + return buff + 1; +} + +char* +buff_adds (char* buff, char* buffEnd, const char* s) +{ + int slen = strlen(s); + + return buff_addb(buff, buffEnd, s, slen); +} + +char* +buff_addb (char* buff, char* buffEnd, const void* data, int len) +{ + int avail = (buffEnd - buff); + + if (avail <= 0 || len <= 0) /* already overflowing */ + return buff; + + if (len > avail) + len = avail; + + memcpy(buff, data, len); + + buff += len; + + /* ensure there is a terminating zero */ + if (buff >= buffEnd) { /* overflow */ + buff[-1] = 0; + } else + buff[0] = 0; + + return buff; +} + +char* +buff_add (char* buff, char* buffEnd, const char* format, ... ) +{ + int avail; + + avail = (buffEnd - buff); + + if (avail > 0) { + va_list args; + int nn; + + va_start(args, format); + nn = vsnprintf( buff, avail, format, args); + va_end(args); + + if (nn < 0) { + /* some C libraries return -1 in case of overflow, + * but they will also do that if the format spec is + * invalid. We assume ADB is not buggy enough to + * trigger that last case. */ + nn = avail; + } + else if (nn > avail) { + nn = avail; + } + + buff += nn; + + /* ensure that there is a terminating zero */ + if (buff >= buffEnd) + buff[-1] = 0; + else + buff[0] = 0; + } + return buff; +} diff --git a/adb/utils.h b/adb/utils.h new file mode 100644 index 0000000..f70ecd2 --- /dev/null +++ b/adb/utils.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _ADB_UTILS_H +#define _ADB_UTILS_H + +/* bounded buffer functions */ + +/* all these functions are used to append data to a bounded buffer. + * + * after each operation, the buffer is guaranteed to be zero-terminated, + * even in the case of an overflow. they all return the new buffer position + * which allows one to use them in succession, only checking for overflows + * at the end. For example: + * + * BUFF_DECL(temp,p,end,1024); + * char* p; + * + * p = buff_addc(temp, end, '"'); + * p = buff_adds(temp, end, string); + * p = buff_addc(temp, end, '"'); + * + * if (p >= end) { + * overflow detected. note that 'temp' is + * zero-terminated for safety. + * } + * return strdup(temp); + */ + +/* tries to add a character to the buffer, in case of overflow + * this will only write a terminating zero and return buffEnd. + */ +char* buff_addc (char* buff, char* buffEnd, int c); + +/* tries to add a string to the buffer */ +char* buff_adds (char* buff, char* buffEnd, const char* s); + +/* tries to add a bytes to the buffer. the input can contain zero bytes, + * but a terminating zero will always be appended at the end anyway + */ +char* buff_addb (char* buff, char* buffEnd, const void* data, int len); + +/* tries to add a formatted string to a bounded buffer */ +char* buff_add (char* buff, char* buffEnd, const char* format, ... ); + +/* convenience macro used to define a bounded buffer, as well as + * a 'cursor' and 'end' variables all in one go. + * + * note: this doesn't place an initial terminating zero in the buffer, + * you need to use one of the buff_ functions for this. or simply + * do _cursor[0] = 0 manually. + */ +#define BUFF_DECL(_buff,_cursor,_end,_size) \ + char _buff[_size], *_cursor=_buff, *_end = _cursor + (_size) + +#endif /* _ADB_UTILS_H */ |