diff options
39 files changed, 2135 insertions, 871 deletions
diff --git a/adb/SERVICES.TXT b/adb/SERVICES.TXT index be4d50b..d9aa09c 100644 --- a/adb/SERVICES.TXT +++ b/adb/SERVICES.TXT @@ -17,8 +17,10 @@ host:kill upgrade. host:devices +host:devices-l Ask to return the list of available Android devices and their - state. After the OKAY, this is followed by a 4-byte hex len, + state. devices-l includes the device paths in the 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 @@ -88,6 +90,9 @@ host:<request> Returns the serial number of the corresponding device/emulator. Note that emulator serial numbers are of the form "emulator-5554" +<host-prefix>:get-devpath + Returns the device path of the corresponding device/emulator. + <host-prefix>:get-state Returns the state of a given device as a string. @@ -21,6 +21,7 @@ #include <ctype.h> #include <stdarg.h> #include <errno.h> +#include <stddef.h> #include <string.h> #include <time.h> #include <sys/time.h> @@ -28,6 +29,8 @@ #include "sysdeps.h" #include "adb.h" +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + #if !ADB_HOST #include <private/android_filesystem_config.h> #include <linux/capability.h> @@ -42,7 +45,9 @@ ADB_MUTEX_DEFINE( D_lock ); int HOST = 0; +#if !ADB_HOST static const char *adb_device_banner = "device"; +#endif void fatal(const char *fmt, ...) { @@ -269,6 +274,36 @@ static void send_close(unsigned local, unsigned remote, atransport *t) send_packet(p, t); } +static size_t fill_connect_data(char *buf, size_t bufsize) +{ +#if ADB_HOST + return snprintf(buf, bufsize, "host::") + 1; +#else + static const char *cnxn_props[] = { + "ro.product.name", + "ro.product.model", + "ro.product.device", + }; + static const int num_cnxn_props = ARRAY_SIZE(cnxn_props); + int i; + size_t remaining = bufsize; + size_t len; + + len = snprintf(buf, remaining, "%s::", adb_device_banner); + remaining -= len; + buf += len; + for (i = 0; i < num_cnxn_props; i++) { + char value[PROPERTY_VALUE_MAX]; + property_get(cnxn_props[i], value, ""); + len = snprintf(buf, remaining, "%s=%s;", cnxn_props[i], value); + remaining -= len; + buf += len; + } + + return bufsize - remaining + 1; +#endif +} + static void send_connect(atransport *t) { D("Calling send_connect \n"); @@ -276,9 +311,8 @@ static void send_connect(atransport *t) 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; + cp->msg.data_length = fill_connect_data((char *)cp->data, + sizeof(cp->data)); send_packet(cp, t); #if ADB_HOST /* XXX why sleep here? */ @@ -305,29 +339,56 @@ static char *connection_state_name(atransport *t) } } +/* qual_overwrite is used to overwrite a qualifier string. dst is a + * pointer to a char pointer. It is assumed that if *dst is non-NULL, it + * was malloc'ed and needs to freed. *dst will be set to a dup of src. + */ +static void qual_overwrite(char **dst, const char *src) +{ + if (!dst) + return; + + free(*dst); + *dst = NULL; + + if (!src || !*src) + return; + + *dst = strdup(src); +} + void parse_banner(char *banner, atransport *t) { - char *type, *product, *end; + static const char *prop_seps = ";"; + static const char key_val_sep = '='; + char *cp; + char *type; 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); + cp = strchr(type, ':'); + if (cp) { + *cp++ = 0; + /* Nothing is done with second field. */ + cp = strchr(cp, ':'); + if (cp) { + char *save; + char *key; + key = adb_strtok_r(cp + 1, prop_seps, &save); + while (key) { + cp = strchr(key, key_val_sep); + if (cp) { + *cp++ = '\0'; + if (!strcmp(key, "ro.product.name")) + qual_overwrite(&t->product, cp); + else if (!strcmp(key, "ro.product.model")) + qual_overwrite(&t->model, cp); + else if (!strcmp(key, "ro.product.device")) + qual_overwrite(&t->device, cp); + } + key = adb_strtok_r(NULL, prop_seps, &save); + } + } } if(!strcmp(type, "bootloader")){ @@ -1025,7 +1086,8 @@ int adb_main(int is_daemon, int server_port) if (sscanf(value, "%d", &port) == 1 && port > 0) { // listen on TCP port specified by service.adb.tcp.port property local_init(port); - } else if (access("/dev/android_adb", F_OK) == 0) { + } else if (access(USB_ADB_PATH, F_OK) == 0 || + access(USB_FFS_ADB_EP0, F_OK) == 0) { // listen on USB usb_init(); } else { @@ -1067,7 +1129,7 @@ void connect_device(char* host, char* buffer, int buffer_size) strncpy(hostbuf, host, sizeof(hostbuf) - 1); if (portstr) { - if (portstr - host >= sizeof(hostbuf)) { + if (portstr - host >= (ptrdiff_t)sizeof(hostbuf)) { snprintf(buffer, buffer_size, "bad host name %s", host); return; } @@ -1201,16 +1263,19 @@ int handle_host_request(char *service, transport_type ttype, char* serial, int r } // return a list of all connected devices - if (!strcmp(service, "devices")) { + if (!strncmp(service, "devices", 7)) { 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; + int use_long = !strcmp(service+7, "-l"); + if (use_long || service[7] == 0) { + memset(buf, 0, sizeof(buf)); + memset(buffer, 0, sizeof(buffer)); + D("Getting device list \n"); + list_transports(buffer, sizeof(buffer), use_long); + snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer),buffer); + D("Wrote device list \n"); + writex(reply_fd, buf, strlen(buf)); + return 0; + } } // add a new TCP transport, device or emulator @@ -1276,6 +1341,16 @@ int handle_host_request(char *service, transport_type ttype, char* serial, int r writex(reply_fd, buf, strlen(buf)); return 0; } + if(!strncmp(service,"get-devpath",strlen("get-devpath"))) { + char *out = "unknown"; + transport = acquire_one_transport(CS_ANY, ttype, serial, NULL); + if (transport && transport->devpath) { + out = transport->devpath; + } + 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); @@ -190,6 +190,9 @@ struct atransport /* used to identify transports for clients */ char *serial; char *product; + char *model; + char *device; + char *devpath; int adb_port; // Use for emulators (local transport) /* a list of adisconnect callbacks called when the transport is kicked */ @@ -253,7 +256,7 @@ int adb_main(int is_daemon, int server_port); ** get_device_transport does an acquire on your behalf before returning */ void init_transport_registration(void); -int list_transports(char *buf, size_t bufsize); +int list_transports(char *buf, size_t bufsize, int long_listing); void update_transports(void); asocket* create_device_tracker(void); @@ -286,7 +289,7 @@ void register_socket_transport(int s, const char *serial, int port, int local); void unregister_transport(atransport *t); void unregister_all_tcp_transports(); -void register_usb_transport(usb_handle *h, const char *serial, unsigned writeable); +void register_usb_transport(usb_handle *h, const char *serial, const char *devpath, unsigned writeable); /* this should only be used for transports with connection_state == CS_NOPERM */ void unregister_usb_transport(usb_handle *usb); @@ -461,6 +464,17 @@ extern int SHELL_EXIT_NOTIFY_FD; #define CHUNK_SIZE (64*1024) +#if !ADB_HOST +#define USB_ADB_PATH "/dev/android_adb" + +#define USB_FFS_ADB_PATH "/dev/usb-ffs/adb/" +#define USB_FFS_ADB_EP(x) USB_FFS_ADB_PATH#x + +#define USB_FFS_ADB_EP0 USB_FFS_ADB_EP(ep0) +#define USB_FFS_ADB_OUT USB_FFS_ADB_EP(ep1) +#define USB_FFS_ADB_IN USB_FFS_ADB_EP(ep2) +#endif + int sendfailmsg(int fd, const char *reason); int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s); diff --git a/adb/commandline.c b/adb/commandline.c index 47c9bec..10b2332 100644 --- a/adb/commandline.c +++ b/adb/commandline.c @@ -84,8 +84,8 @@ void help() " 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. Overrides ANDROID_SERIAL\n" + " -s <specific device> - directs command to the device or emulator with the given\n" + " serial number or qualifier. Overrides ANDROID_SERIAL\n" " environment variable.\n" " -p <product name or path> - simple product name like 'sooner', or\n" " a relative/absolute path to a product\n" @@ -93,7 +93,8 @@ void help() " 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" + " devices [-l] - list all connected devices\n" + " ('-l' will also list device qualifiers)\n" " connect <host>[:<port>] - connect to a device via TCP/IP\n" " Port 5555 is used by default if no port number is specified.\n" " disconnect [<host>[:<port>]] - disconnect from a TCP/IP device.\n" @@ -159,6 +160,7 @@ void help() " 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 get-devpath - prints: <device-path>\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" " adb reboot [bootloader|recovery] - reboots the device, optionally into the bootloader or recovery program\n" @@ -1016,7 +1018,16 @@ top: if(!strcmp(argv[0], "devices")) { char *tmp; - snprintf(buf, sizeof buf, "host:%s", argv[0]); + char *listopt; + if (argc < 2) + listopt = ""; + else if (argc == 2 && !strcmp(argv[1], "-l")) + listopt = argv[1]; + else { + fprintf(stderr, "Usage: adb devices [-l]\n"); + return 1; + } + snprintf(buf, sizeof buf, "host:%s%s", argv[0], listopt); tmp = adb_query(buf); if(tmp) { printf("List of devices attached \n"); @@ -1298,7 +1309,8 @@ top: /* passthrough commands */ if(!strcmp(argv[0],"get-state") || - !strcmp(argv[0],"get-serialno")) + !strcmp(argv[0],"get-serialno") || + !strcmp(argv[0],"get-devpath")) { char *tmp; diff --git a/adb/protocol.txt b/adb/protocol.txt index 398d042..abd63f9 100644 --- a/adb/protocol.txt +++ b/adb/protocol.txt @@ -72,7 +72,7 @@ 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). +or identifier string. The banner is used to transmit useful properties. --- OPEN(local-id, 0, "destination") ----------------------------------- diff --git a/adb/sockets.c b/adb/sockets.c index cd31b23..305cb44 100644 --- a/adb/sockets.c +++ b/adb/sockets.c @@ -608,12 +608,30 @@ unsigned unhex(unsigned char *s, int len) return n; } +#define PREFIX(str) { str, sizeof(str) - 1 } +static const struct prefix_struct { + const char *str; + const size_t len; +} prefixes[] = { + PREFIX("usb:"), + PREFIX("product:"), + PREFIX("model:"), + PREFIX("device:"), +}; +static const int num_prefixes = (sizeof(prefixes) / sizeof(prefixes[0])); + /* skip_host_serial return the position in a string skipping over the 'serial' parameter in the ADB protocol, where parameter string may be a host:port string containing the protocol delimiter (colon). */ char *skip_host_serial(char *service) { char *first_colon, *serial_end; + int i; + + for (i = 0; i < num_prefixes; i++) { + if (!strncmp(service, prefixes[i].str, prefixes[i].len)) + return strchr(service + prefixes[i].len, ':'); + } first_colon = strchr(service, ':'); if (!first_colon) { diff --git a/adb/sysdeps.h b/adb/sysdeps.h index b518076..605fa17 100644 --- a/adb/sysdeps.h +++ b/adb/sysdeps.h @@ -254,6 +254,8 @@ static __inline__ int adb_is_absolute_host_path( const char* path ) return isalpha(path[0]) && path[1] == ':' && path[2] == '\\'; } +extern char* adb_strtok_r(char *str, const char *delim, char **saveptr); + #else /* !_WIN32 a.k.a. Unix */ #include "fdevent.h" @@ -490,6 +492,13 @@ static __inline__ int adb_is_absolute_host_path( const char* path ) return path[0] == '/'; } +static __inline__ char* adb_strtok_r(char *str, const char *delim, char **saveptr) +{ + return strtok_r(str, delim, saveptr); +} +#undef strtok_r +#define strtok_r ___xxx_strtok_r + #endif /* !_WIN32 */ #endif /* _ADB_SYSDEPS_H */ diff --git a/adb/sysdeps_win32.c b/adb/sysdeps_win32.c index c426718..d41c42c 100644 --- a/adb/sysdeps_win32.c +++ b/adb/sysdeps_win32.c @@ -2140,3 +2140,81 @@ adb_sysdeps_init( void ) InitializeCriticalSection( &_win32_lock ); } +/* Windows doesn't have strtok_r. Use the one from bionic. */ + +/* + * Copyright (c) 1988 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +char * +adb_strtok_r(char *s, const char *delim, char **last) +{ + char *spanp; + int c, sc; + char *tok; + + + if (s == NULL && (s = *last) == NULL) + return (NULL); + + /* + * Skip (span) leading delimiters (s += strspn(s, delim), sort of). + */ +cont: + c = *s++; + for (spanp = (char *)delim; (sc = *spanp++) != 0;) { + if (c == sc) + goto cont; + } + + if (c == 0) { /* no non-delimiter characters */ + *last = NULL; + return (NULL); + } + tok = s - 1; + + /* + * Scan token (scan for delimiters: s += strcspn(s, delim), sort of). + * Note that delim must have one NUL; we stop if we see that, too. + */ + for (;;) { + c = *s++; + spanp = (char *)delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) + s = NULL; + else + s[-1] = 0; + *last = s; + return (tok); + } + } while (sc != 0); + } + /* NOTREACHED */ +} diff --git a/adb/transport.c b/adb/transport.c index 2f7bd27..9fd6cc2 100644 --- a/adb/transport.c +++ b/adb/transport.c @@ -370,7 +370,7 @@ static int list_transports_msg(char* buffer, size_t bufferlen) char head[5]; int len; - len = list_transports(buffer+4, bufferlen-4); + len = list_transports(buffer+4, bufferlen-4, 0); snprintf(head, sizeof(head), "%04x", len); memcpy(buffer, head, 4); len += 4; @@ -601,6 +601,12 @@ static void transport_registration_func(int _fd, unsigned ev, void *data) free(t->product); if (t->serial) free(t->serial); + if (t->model) + free(t->model); + if (t->device) + free(t->device); + if (t->devpath) + free(t->devpath); memset(t,0xee,sizeof(atransport)); free(t); @@ -737,6 +743,45 @@ void remove_transport_disconnect(atransport* t, adisconnect* dis) dis->next = dis->prev = dis; } +static int qual_char_is_invalid(char ch) +{ + if ('A' <= ch && ch <= 'Z') + return 0; + if ('a' <= ch && ch <= 'z') + return 0; + if ('0' <= ch && ch <= '9') + return 0; + return 1; +} + +static int qual_match(const char *to_test, + const char *prefix, const char *qual, int sanitize_qual) +{ + if (!to_test || !*to_test) + /* Return true if both the qual and to_test are null strings. */ + return !qual || !*qual; + + if (!qual) + return 0; + + if (prefix) { + while (*prefix) { + if (*prefix++ != *to_test++) + return 0; + } + } + + while (*qual) { + char ch = *qual++; + if (sanitize_qual && qual_char_is_invalid(ch)) + ch = '_'; + if (ch != *to_test++) + return 0; + } + + /* Everything matched so far. Return true if *to_test is a NUL. */ + return !*to_test; +} atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out) { @@ -758,9 +803,19 @@ retry: /* check for matching serial number */ if (serial) { - if (t->serial && !strcmp(serial, t->serial)) { + if ((t->serial && !strcmp(serial, t->serial)) || + (t->devpath && !strcmp(serial, t->devpath)) || + qual_match(serial, "product:", t->product, 0) || + qual_match(serial, "model:", t->model, 1) || + qual_match(serial, "device:", t->device, 0)) { + if (result) { + if (error_out) + *error_out = "more than one device"; + ambiguous = 1; + result = NULL; + break; + } result = t; - break; } } else { if (ttype == kTransportUsb && t->type == kTransportUsb) { @@ -837,7 +892,58 @@ static const char *statename(atransport *t) } } -int list_transports(char *buf, size_t bufsize) +static void add_qual(char **buf, size_t *buf_size, + const char *prefix, const char *qual, int sanitize_qual) +{ + size_t len; + int prefix_len; + + if (!buf || !*buf || !buf_size || !*buf_size || !qual || !*qual) + return; + + len = snprintf(*buf, *buf_size, "%s%n%s", prefix, &prefix_len, qual); + + if (sanitize_qual) { + char *cp; + for (cp = *buf + prefix_len; cp < *buf + len; cp++) { + if (qual_char_is_invalid(*cp)) + *cp = '_'; + } + } + + *buf_size -= len; + *buf += len; +} + +static size_t format_transport(atransport *t, char *buf, size_t bufsize, + int long_listing) +{ + const char* serial = t->serial; + if (!serial || !serial[0]) + serial = "????????????"; + + if (!long_listing) { + return snprintf(buf, bufsize, "%s\t%s\n", serial, statename(t)); + } else { + size_t len, remaining = bufsize; + + len = snprintf(buf, remaining, "%-22s %s", serial, statename(t)); + remaining -= len; + buf += len; + + add_qual(&buf, &remaining, " ", t->devpath, 0); + add_qual(&buf, &remaining, " product:", t->product, 0); + add_qual(&buf, &remaining, " model:", t->model, 1); + add_qual(&buf, &remaining, " device:", t->device, 0); + + len = snprintf(buf, remaining, "\n"); + remaining -= len; + + return bufsize - remaining; + } +} + +int list_transports(char *buf, size_t bufsize, int long_listing) { char* p = buf; char* end = buf + bufsize; @@ -847,11 +953,7 @@ int list_transports(char *buf, size_t bufsize) /* XXX OVERRUN PROBLEMS XXX */ adb_mutex_lock(&transport_lock); for(t = transport_list.next; t != &transport_list; t = t->next) { - const char* serial = t->serial; - if (!serial || !serial[0]) - serial = "????????????"; - len = snprintf(p, end - p, "%s\t%s\n", serial, statename(t)); - + len = format_transport(t, p, end - p, long_listing); if (p + len >= end) { /* discard last line if buffer is too short */ break; @@ -956,7 +1058,7 @@ void unregister_all_tcp_transports() #endif -void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable) +void register_usb_transport(usb_handle *usb, const char *serial, const char *devpath, unsigned writeable) { atransport *t = calloc(1, sizeof(atransport)); D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb, @@ -965,6 +1067,9 @@ void register_usb_transport(usb_handle *usb, const char *serial, unsigned writea if(serial) { t->serial = strdup(serial); } + if(devpath) { + t->devpath = strdup(devpath); + } register_transport(t); } diff --git a/adb/usb_libusb.c b/adb/usb_libusb.c index 8c75266..06ff5dc 100644 --- a/adb/usb_libusb.c +++ b/adb/usb_libusb.c @@ -347,7 +347,7 @@ register_device(struct usb_handle *uh, const char *serial) adb_mutex_unlock(&usb_lock); - register_usb_transport(usb, serial, 1); + register_usb_transport(usb, serial, NULL, 1); return (1); } diff --git a/adb/usb_linux.c b/adb/usb_linux.c index 4d55b74..7bf2057 100644 --- a/adb/usb_linux.c +++ b/adb/usb_linux.c @@ -116,7 +116,8 @@ static void kick_disconnected_devices() } -static void register_device(const char *dev_name, unsigned char ep_in, unsigned char ep_out, +static void register_device(const char *dev_name, const char *devpath, + unsigned char ep_in, unsigned char ep_out, int ifc, int serial_index, unsigned zero_mask); static inline int badname(const char *name) @@ -129,7 +130,7 @@ static inline int badname(const char *name) static void find_usb_device(const char *base, void (*register_device_callback) - (const char *, unsigned char, unsigned char, int, int, unsigned)) + (const char *, const char *, unsigned char, unsigned char, int, int, unsigned)) { char busname[32], devname[32]; unsigned char local_ep_in, local_ep_out; @@ -227,6 +228,11 @@ static void find_usb_device(const char *base, is_adb_interface(vid, pid, interface->bInterfaceClass, interface->bInterfaceSubClass, interface->bInterfaceProtocol)) { + struct stat st; + char pathbuf[128]; + char link[256]; + char *devpath = NULL; + DBGX("looking for bulk endpoints\n"); // looks like ADB... ep1 = (struct usb_endpoint_descriptor *)bufptr; @@ -263,7 +269,26 @@ static void find_usb_device(const char *base, local_ep_out = ep1->bEndpointAddress; } - register_device_callback(devname, local_ep_in, local_ep_out, + // Determine the device path + if (!fstat(fd, &st) && S_ISCHR(st.st_mode)) { + char *slash; + ssize_t link_len; + snprintf(pathbuf, sizeof(pathbuf), "/sys/dev/char/%d:%d", + major(st.st_rdev), minor(st.st_rdev)); + link_len = readlink(pathbuf, link, sizeof(link) - 1); + if (link_len > 0) { + link[link_len] = '\0'; + slash = strrchr(link, '/'); + if (slash) { + snprintf(pathbuf, sizeof(pathbuf), + "usb:%s", slash + 1); + devpath = pathbuf; + } + } + } + + register_device_callback(devname, devpath, + local_ep_in, local_ep_out, interface->bInterfaceNumber, device->iSerialNumber, zero_mask); break; } @@ -532,7 +557,7 @@ int usb_close(usb_handle *h) return 0; } -static void register_device(const char *dev_name, +static void register_device(const char *dev_name, const char *devpath, unsigned char ep_in, unsigned char ep_out, int interface, int serial_index, unsigned zero_mask) { @@ -644,7 +669,7 @@ static void register_device(const char *dev_name, usb->next->prev = usb; adb_mutex_unlock(&usb_lock); - register_usb_transport(usb, serial, usb->writeable); + register_usb_transport(usb, serial, devpath, usb->writeable); return; fail: diff --git a/adb/usb_linux_client.c b/adb/usb_linux_client.c index 635fa4b..fb1dad0 100644 --- a/adb/usb_linux_client.c +++ b/adb/usb_linux_client.c @@ -19,6 +19,8 @@ #include <unistd.h> #include <string.h> +#include <linux/usb/ch9.h> +#include <linux/usb/functionfs.h> #include <sys/ioctl.h> #include <sys/types.h> #include <dirent.h> @@ -29,20 +31,122 @@ #define TRACE_TAG TRACE_USB #include "adb.h" +#define MAX_PACKET_SIZE_FS 64 +#define MAX_PACKET_SIZE_HS 512 + +#define cpu_to_le16(x) htole16(x) +#define cpu_to_le32(x) htole32(x) struct usb_handle { - int fd; adb_cond_t notify; adb_mutex_t lock; + + int (*write)(usb_handle *h, const void *data, int len); + int (*read)(usb_handle *h, void *data, int len); + void (*kick)(usb_handle *h); + + // Legacy f_adb + int fd; + + // FunctionFS + int control; + int bulk_out; /* "out" from the host's perspective => source for adbd */ + int bulk_in; /* "in" from the host's perspective => sink for adbd */ +}; + +static const struct { + struct usb_functionfs_descs_head header; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio source; + struct usb_endpoint_descriptor_no_audio sink; + } __attribute__((packed)) fs_descs, hs_descs; +} __attribute__((packed)) descriptors = { + .header = { + .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC), + .length = cpu_to_le32(sizeof(descriptors)), + .fs_count = 3, + .hs_count = 3, + }, + .fs_descs = { + .intf = { + .bLength = sizeof(descriptors.fs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = ADB_CLASS, + .bInterfaceSubClass = ADB_SUBCLASS, + .bInterfaceProtocol = ADB_PROTOCOL, + .iInterface = 1, /* first string from the provided table */ + }, + .source = { + .bLength = sizeof(descriptors.fs_descs.source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_FS, + }, + .sink = { + .bLength = sizeof(descriptors.fs_descs.sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_FS, + }, + }, + .hs_descs = { + .intf = { + .bLength = sizeof(descriptors.hs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = ADB_CLASS, + .bInterfaceSubClass = ADB_SUBCLASS, + .bInterfaceProtocol = ADB_PROTOCOL, + .iInterface = 1, /* first string from the provided table */ + }, + .source = { + .bLength = sizeof(descriptors.hs_descs.source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_HS, + }, + .sink = { + .bLength = sizeof(descriptors.hs_descs.sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_HS, + }, + }, +}; + +#define STR_INTERFACE_ "ADB Interface" + +static const struct { + struct usb_functionfs_strings_head header; + struct { + __le16 code; + const char str1[sizeof(STR_INTERFACE_)]; + } __attribute__((packed)) lang0; +} __attribute__((packed)) strings = { + .header = { + .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC), + .length = cpu_to_le32(sizeof(strings)), + .str_count = cpu_to_le32(1), + .lang_count = cpu_to_le32(1), + }, + .lang0 = { + cpu_to_le16(0x0409), /* en-us */ + STR_INTERFACE_, + }, }; -void usb_cleanup() -{ - // nothing to do here -} -static void *usb_open_thread(void *x) + +static void *usb_adb_open_thread(void *x) { struct usb_handle *usb = (struct usb_handle *)x; int fd; @@ -72,14 +176,14 @@ static void *usb_open_thread(void *x) usb->fd = fd; D("[ usb_thread - registering device ]\n"); - register_usb_transport(usb, 0, 1); + register_usb_transport(usb, 0, 0, 1); } // never gets here return 0; } -int usb_write(usb_handle *h, const void *data, int len) +static int usb_adb_write(usb_handle *h, const void *data, int len) { int n; @@ -94,7 +198,7 @@ int usb_write(usb_handle *h, const void *data, int len) return 0; } -int usb_read(usb_handle *h, void *data, int len) +static int usb_adb_read(usb_handle *h, void *data, int len) { int n; @@ -109,14 +213,31 @@ int usb_read(usb_handle *h, void *data, int len) return 0; } -void usb_init() +static void usb_adb_kick(usb_handle *h) +{ + D("usb_kick\n"); + adb_mutex_lock(&h->lock); + adb_close(h->fd); + h->fd = -1; + + // notify usb_adb_open_thread that we are disconnected + adb_cond_signal(&h->notify); + adb_mutex_unlock(&h->lock); +} + +static void usb_adb_init() { usb_handle *h; adb_thread_t tid; int fd; h = calloc(1, sizeof(usb_handle)); + + h->write = usb_adb_write; + h->read = usb_adb_read; + h->kick = usb_adb_kick; h->fd = -1; + adb_cond_init(&h->notify, 0); adb_mutex_init(&h->lock, 0); @@ -133,25 +254,239 @@ void usb_init() } D("[ usb_init - starting thread ]\n"); - if(adb_thread_create(&tid, usb_open_thread, h)){ + if(adb_thread_create(&tid, usb_adb_open_thread, h)){ fatal_errno("cannot create usb thread"); } } -void usb_kick(usb_handle *h) + +static void init_functionfs(struct usb_handle *h) { - D("usb_kick\n"); + ssize_t ret; + + D("OPENING %s\n", USB_FFS_ADB_EP0); + h->control = adb_open(USB_FFS_ADB_EP0, O_RDWR); + if (h->control < 0) { + D("[ %s: cannot open control endpoint: errno=%d]\n", USB_FFS_ADB_EP0, errno); + goto err; + } + + ret = adb_write(h->control, &descriptors, sizeof(descriptors)); + if (ret < 0) { + D("[ %s: write descriptors failed: errno=%d ]\n", USB_FFS_ADB_EP0, errno); + goto err; + } + + ret = adb_write(h->control, &strings, sizeof(strings)); + if (ret < 0) { + D("[ %s: writing strings failed: errno=%d]\n", USB_FFS_ADB_EP0, errno); + goto err; + } + + h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDWR); + if (h->bulk_out < 0) { + D("[ %s: cannot open bulk-out ep: errno=%d ]\n", USB_FFS_ADB_OUT, errno); + goto err; + } + + h->bulk_in = adb_open(USB_FFS_ADB_IN, O_RDWR); + if (h->bulk_in < 0) { + D("[ %s: cannot open bulk-in ep: errno=%d ]\n", USB_FFS_ADB_IN, errno); + goto err; + } + + return; + +err: + if (h->bulk_in > 0) { + adb_close(h->bulk_in); + h->bulk_in = -1; + } + if (h->bulk_out > 0) { + adb_close(h->bulk_out); + h->bulk_out = -1; + } + if (h->control > 0) { + adb_close(h->control); + h->control = -1; + } + return; +} + +static void *usb_ffs_open_thread(void *x) +{ + struct usb_handle *usb = (struct usb_handle *)x; + + while (1) { + // wait until the USB device needs opening + adb_mutex_lock(&usb->lock); + while (usb->control != -1) + adb_cond_wait(&usb->notify, &usb->lock); + adb_mutex_unlock(&usb->lock); + + while (1) { + init_functionfs(usb); + + if (usb->control >= 0) + break; + + adb_sleep_ms(1000); + } + + D("[ usb_thread - registering device ]\n"); + register_usb_transport(usb, 0, 0, 1); + } + + // never gets here + return 0; +} + +static int bulk_write(int bulk_in, const char *buf, size_t length) +{ + size_t count = 0; + int ret; + + do { + ret = adb_write(bulk_in, buf + count, length - count); + if (ret < 0) { + if (errno != EINTR) + return ret; + } else { + count += ret; + } + } while (count < length); + + D("[ bulk_write done fd=%d ]\n", bulk_in); + return count; +} + +static int usb_ffs_write(usb_handle *h, const void *data, int len) +{ + int n; + + D("about to write (fd=%d, len=%d)\n", h->bulk_in, len); + n = bulk_write(h->bulk_in, data, len); + if (n != len) { + D("ERROR: fd = %d, n = %d, errno = %d (%s)\n", + h->bulk_in, n, errno, strerror(errno)); + return -1; + } + D("[ done fd=%d ]\n", h->bulk_in); + return 0; +} + +static int bulk_read(int bulk_out, char *buf, size_t length) +{ + size_t count = 0; + int ret; + + do { + ret = adb_read(bulk_out, buf + count, length - count); + if (ret < 0) { + if (errno != EINTR) { + D("[ bulk_read failed fd=%d length=%d count=%d ]\n", + bulk_out, length, count); + return ret; + } + } else { + count += ret; + } + } while (count < length); + + return count; +} + +static int usb_ffs_read(usb_handle *h, void *data, int len) +{ + int n; + + D("about to read (fd=%d, len=%d)\n", h->bulk_out, len); + n = bulk_read(h->bulk_out, data, len); + if (n != len) { + D("ERROR: fd = %d, n = %d, errno = %d (%s)\n", + h->bulk_out, n, errno, strerror(errno)); + return -1; + } + D("[ done fd=%d ]\n", h->bulk_out); + return 0; +} + +static void usb_ffs_kick(usb_handle *h) +{ + int err; + + err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT); + if (err < 0) + D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in, errno); + + err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT); + if (err < 0) + D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out, errno); + adb_mutex_lock(&h->lock); - adb_close(h->fd); - h->fd = -1; + adb_close(h->control); + adb_close(h->bulk_out); + adb_close(h->bulk_in); + h->control = h->bulk_out = h->bulk_in = -1; - // notify usb_open_thread that we are disconnected + // notify usb_ffs_open_thread that we are disconnected adb_cond_signal(&h->notify); adb_mutex_unlock(&h->lock); } +static void usb_ffs_init() +{ + usb_handle *h; + adb_thread_t tid; + + D("[ usb_init - using FunctionFS ]\n"); + + h = calloc(1, sizeof(usb_handle)); + + h->write = usb_ffs_write; + h->read = usb_ffs_read; + h->kick = usb_ffs_kick; + + h->control = -1; + h->bulk_out = -1; + h->bulk_out = -1; + + adb_cond_init(&h->notify, 0); + adb_mutex_init(&h->lock, 0); + + D("[ usb_init - starting thread ]\n"); + if (adb_thread_create(&tid, usb_ffs_open_thread, h)){ + fatal_errno("[ cannot create usb thread ]\n"); + } +} + +void usb_init() +{ + if (access(USB_FFS_ADB_EP0, F_OK) == 0) + usb_ffs_init(); + else + usb_adb_init(); +} + +void usb_cleanup() +{ +} + +int usb_write(usb_handle *h, const void *data, int len) +{ + return h->write(h, data, len); +} + +int usb_read(usb_handle *h, void *data, int len) +{ + return h->read(h, data, len); +} int usb_close(usb_handle *h) { - // nothing to do here return 0; } + +void usb_kick(usb_handle *h) +{ + h->kick(h); +} diff --git a/adb/usb_osx.c b/adb/usb_osx.c index 00d02da..45ce444 100644 --- a/adb/usb_osx.c +++ b/adb/usb_osx.c @@ -125,10 +125,13 @@ AndroidInterfaceAdded(void *refCon, io_iterator_t iterator) IOUSBDeviceInterface197 **dev = NULL; HRESULT result; SInt32 score; + UInt32 locationId; UInt16 vendor; UInt16 product; UInt8 serialIndex; char serial[256]; + char devpathBuf[64]; + char *devpath = NULL; while ((usbInterface = IOIteratorNext(iterator))) { //* Create an intermediate interface plugin @@ -192,6 +195,11 @@ AndroidInterfaceAdded(void *refCon, io_iterator_t iterator) kr = (*dev)->GetDeviceVendor(dev, &vendor); kr = (*dev)->GetDeviceProduct(dev, &product); + kr = (*dev)->GetLocationID(dev, &locationId); + if (kr == 0) { + snprintf(devpathBuf, sizeof(devpathBuf), "usb:%lX", locationId); + devpath = devpathBuf; + } kr = (*dev)->USBGetSerialNumberStringIndex(dev, &serialIndex); if (serialIndex > 0) { @@ -256,7 +264,7 @@ AndroidInterfaceAdded(void *refCon, io_iterator_t iterator) } DBG("AndroidDeviceAdded calling register_usb_transport\n"); - register_usb_transport(handle, (serial[0] ? serial : NULL), 1); + register_usb_transport(handle, (serial[0] ? serial : NULL), devpath, 1); // Register for an interest notification of this device being removed. // Pass the reference to our private data as the refCon for the diff --git a/adb/usb_windows.c b/adb/usb_windows.c index 251bf17..4936b77 100644 --- a/adb/usb_windows.c +++ b/adb/usb_windows.c @@ -490,7 +490,7 @@ void find_devices() { true)) { // Lets make sure that we don't duplicate this device if (register_new_device(handle)) { - register_usb_transport(handle, serial_number, 1); + register_usb_transport(handle, serial_number, NULL, 1); } else { D("register_new_device failed for %s\n", interf_name); usb_cleanup_handle(handle); diff --git a/fastboot/engine.c b/fastboot/engine.c index 46b0828..f5215cf 100644 --- a/fastboot/engine.c +++ b/fastboot/engine.c @@ -542,6 +542,8 @@ int fb_execute_queue(usb_handle *usb) int status = 0; a = action_list; + if (!a) + return status; resp[FB_RESPONSE_SZ] = 0; double start = -1; @@ -578,3 +580,8 @@ int fb_execute_queue(usb_handle *usb) fprintf(stderr,"finished. total time: %.3fs\n", (now() - start)); return status; } + +int fb_queue_is_empty(void) +{ + return (action_list == NULL); +} diff --git a/fastboot/fastboot.c b/fastboot/fastboot.c index 848cea3..544893b 100644 --- a/fastboot/fastboot.c +++ b/fastboot/fastboot.c @@ -58,6 +58,7 @@ static const char *product = 0; static const char *cmdline = 0; static int wipe_data = 0; static unsigned short vendor_id = 0; +static int long_listing = 0; static unsigned base_addr = 0x10000000; @@ -150,6 +151,11 @@ oops: int match_fastboot(usb_ifc_info *info) { + return match_fastboot_with_serial(info, serial); +} + +int match_fastboot_with_serial(usb_ifc_info *info, const char *local_serial) +{ if(!(vendor_id && (info->dev_vendor == vendor_id)) && (info->dev_vendor != 0x18d1) && // Google (info->dev_vendor != 0x8087) && // Intel @@ -167,15 +173,16 @@ int match_fastboot(usb_ifc_info *info) if(info->ifc_class != 0xff) return -1; if(info->ifc_subclass != 0x42) return -1; if(info->ifc_protocol != 0x03) return -1; - // require matching serial number if a serial number is specified + // require matching serial number or device path if requested // at the command line with the -s option. - if (serial && strcmp(serial, info->serial_number) != 0) return -1; + if (local_serial && (strcmp(local_serial, info->serial_number) != 0 && + strcmp(local_serial, info->device_path) != 0)) return -1; return 0; } int list_devices_callback(usb_ifc_info *info) { - if (match_fastboot(info) == 0) { + if (match_fastboot_with_serial(info, NULL) == 0) { char* serial = info->serial_number; if (!info->writable) { serial = "no permissions"; // like "adb devices" @@ -184,7 +191,13 @@ int list_devices_callback(usb_ifc_info *info) serial = "????????????"; } // output compatible with "adb devices" - printf("%s\tfastboot\n", serial); + if (!long_listing) { + printf("%s\tfastboot\n", serial); + } else if (!info->device_path) { + printf("%-22s fastboot\n", serial); + } else { + printf("%-22s fastboot %s\n", serial, info->device_path); + } } return -1; @@ -238,7 +251,9 @@ void usage(void) "\n" "options:\n" " -w erase userdata and cache\n" - " -s <serial number> specify device serial number\n" + " -s <specific device> specify device serial number\n" + " or path to device port\n" + " -l with \"devices\", lists device paths\n" " -p <product> specify product name\n" " -c <cmdline> override kernel commandline\n" " -i <vendor id> specify a custom USB vendor id\n" @@ -571,6 +586,7 @@ int main(int argc, char **argv) int wants_wipe = 0; int wants_reboot = 0; int wants_reboot_bootloader = 0; + int wants_device_list = 0; void *data; unsigned sz; unsigned page_size = 2048; @@ -582,11 +598,6 @@ int main(int argc, char **argv) return 1; } - if (!strcmp(*argv, "devices")) { - list_devices(); - return 0; - } - if (!strcmp(*argv, "help")) { usage(); return 0; @@ -612,6 +623,9 @@ int main(int argc, char **argv) require(2); serial = argv[1]; skip(2); + } else if(!strcmp(*argv, "-l")) { + long_listing = 1; + skip(1); } else if(!strcmp(*argv, "-p")) { require(2); product = argv[1]; @@ -630,6 +644,9 @@ int main(int argc, char **argv) die("invalid vendor id '%s'", argv[1]); vendor_id = (unsigned short)val; skip(2); + } else if (!strcmp(*argv, "devices")) { + skip(1); + wants_device_list = 1; } else if(!strcmp(*argv, "getvar")) { require(2); fb_queue_display(argv[1], argv[1]); @@ -725,6 +742,9 @@ int main(int argc, char **argv) } } + if (wants_device_list) + list_devices(); + if (wants_wipe) { fb_queue_erase("userdata"); fb_queue_format("userdata", 1); @@ -737,6 +757,9 @@ int main(int argc, char **argv) fb_queue_command("reboot-bootloader", "rebooting into bootloader"); } + if (fb_queue_is_empty()) + return 0; + usb = open_device(); status = fb_execute_queue(usb); diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h index 1d3e2b8..90d8a6a 100644 --- a/fastboot/fastboot.h +++ b/fastboot/fastboot.h @@ -53,6 +53,7 @@ void fb_queue_command(const char *cmd, const char *msg); void fb_queue_download(const char *name, void *data, unsigned size); void fb_queue_notice(const char *notice); int fb_execute_queue(usb_handle *usb); +int fb_queue_is_empty(void); /* util stuff */ void die(const char *fmt, ...); diff --git a/fastboot/usb.h b/fastboot/usb.h index df9efde..d504ee2 100644 --- a/fastboot/usb.h +++ b/fastboot/usb.h @@ -53,6 +53,7 @@ struct usb_ifc_info unsigned char writable; char serial_number[256]; + char device_path[256]; }; typedef int (*ifc_match_func)(usb_ifc_info *ifc); diff --git a/fastboot/usb_linux.c b/fastboot/usb_linux.c index 83c6de9..b7a9ca3 100644 --- a/fastboot/usb_linux.c +++ b/fastboot/usb_linux.c @@ -32,6 +32,7 @@ #include <string.h> #include <sys/ioctl.h> +#include <sys/stat.h> #include <sys/types.h> #include <dirent.h> #include <fcntl.h> @@ -107,6 +108,9 @@ static int filter_usb_device(int fd, char *ptr, int len, int writable, int in, out; unsigned i; unsigned e; + + struct stat st; + int result; if(check(ptr, len, USB_DT_DEVICE, USB_DT_DEVICE_SIZE)) return -1; @@ -134,7 +138,6 @@ static int filter_usb_device(int fd, char *ptr, int len, int writable, // Keep it short enough because some bootloaders are borked if the URB len is > 255 // 128 is too big by 1. __u16 buffer[127]; - int result; memset(buffer, 0, sizeof(buffer)); @@ -158,6 +161,42 @@ static int filter_usb_device(int fd, char *ptr, int len, int writable, } } + /* We need to get a path that represents a particular port on a particular + * hub. We are passed an fd that was obtained by opening an entry under + * /dev/bus/usb. Unfortunately, the names of those entries change each + * time devices are plugged and unplugged. So how to get a repeatable + * path? udevadm provided the inspiration. We can get the major and + * minor of the device file, read the symlink that can be found here: + * /sys/dev/char/<major>:<minor> + * and then use the last element of that path. As a concrete example, I + * have an Android device at /dev/bus/usb/001/027 so working with bash: + * $ ls -l /dev/bus/usb/001/027 + * crw-rw-r-- 1 root plugdev 189, 26 Apr 9 11:03 /dev/bus/usb/001/027 + * $ ls -l /sys/dev/char/189:26 + * lrwxrwxrwx 1 root root 0 Apr 9 11:03 /sys/dev/char/189:26 -> + * ../../devices/pci0000:00/0000:00:1a.7/usb1/1-4/1-4.2/1-4.2.3 + * So our device_path would be 1-4.2.3 which says my device is connected + * to port 3 of a hub on port 2 of a hub on port 4 of bus 1 (per + * http://www.linux-usb.org/FAQ.html). + */ + info.device_path[0] = '\0'; + result = fstat(fd, &st); + if (!result && S_ISCHR(st.st_mode)) { + char cdev[128]; + char link[256]; + char *slash; + ssize_t link_len; + snprintf(cdev, sizeof(cdev), "/sys/dev/char/%d:%d", + major(st.st_rdev), minor(st.st_rdev)); + link_len = readlink(cdev, link, sizeof(link) - 1); + if (link_len > 0) { + link[link_len] = '\0'; + slash = strrchr(link, '/'); + if (slash) + snprintf(info.device_path, sizeof(info.device_path), "usb:%s", slash+1); + } + } + for(i = 0; i < cfg->bNumInterfaces; i++) { if(check(ptr, len, USB_DT_INTERFACE, USB_DT_INTERFACE_SIZE)) return -1; diff --git a/fastboot/usb_osx.c b/fastboot/usb_osx.c index cbce9bd..1548ba8 100644 --- a/fastboot/usb_osx.c +++ b/fastboot/usb_osx.c @@ -264,6 +264,7 @@ static int try_device(io_service_t device, usb_handle *handle) { SInt32 score; HRESULT result; UInt8 serialIndex; + UInt32 locationId; // Create an intermediate plugin. kr = IOCreatePlugInInterfaceForService(device, @@ -322,6 +323,13 @@ static int try_device(io_service_t device, usb_handle *handle) { goto error; } + kr = (*dev)->GetLocationID(dev, &locationId); + if (kr != 0) { + ERR("GetLocationId"); + goto error; + } + snprintf(handle->info.device_path, sizeof(handle->info.device_path), "usb:%lX", locationId); + kr = (*dev)->USBGetSerialNumberStringIndex(dev, &serialIndex); if (serialIndex > 0) { diff --git a/fastboot/usb_windows.c b/fastboot/usb_windows.c index 99027cc..7aa36b2 100644 --- a/fastboot/usb_windows.c +++ b/fastboot/usb_windows.c @@ -311,6 +311,8 @@ int recognized_device(usb_handle* handle, ifc_match_func callback) { info.serial_number[0] = 0; } + info.device_path[0] = 0; + if (callback(&info) == 0) { return 1; } diff --git a/include/arch/darwin-x86/AndroidConfig.h b/include/arch/darwin-x86/AndroidConfig.h index 48f8d9a..9da01c5 100644 --- a/include/arch/darwin-x86/AndroidConfig.h +++ b/include/arch/darwin-x86/AndroidConfig.h @@ -175,7 +175,7 @@ * with a memory address. If not defined, stack crawls will not have symbolic * information. */ -#define HAVE_DLADDR 0 +#define HAVE_DLADDR 1 /* * Defined if we have the cxxabi.h header for demangling C++ symbols. If diff --git a/include/corkscrew/map_info.h b/include/corkscrew/map_info.h index c5cd8f8..ea1d35f 100644 --- a/include/corkscrew/map_info.h +++ b/include/corkscrew/map_info.h @@ -21,6 +21,7 @@ #include <sys/types.h> #include <stdbool.h> +#include <stdint.h> #ifdef __cplusplus extern "C" { diff --git a/include/corkscrew/ptrace.h b/include/corkscrew/ptrace.h index 172e348..0040c22 100644 --- a/include/corkscrew/ptrace.h +++ b/include/corkscrew/ptrace.h @@ -24,6 +24,7 @@ #include <sys/types.h> #include <stdbool.h> +#include <stdint.h> #ifdef __cplusplus extern "C" { diff --git a/include/corkscrew/symbol_table.h b/include/corkscrew/symbol_table.h index 020c8b8..4998750 100644 --- a/include/corkscrew/symbol_table.h +++ b/include/corkscrew/symbol_table.h @@ -17,6 +17,7 @@ #ifndef _CORKSCREW_SYMBOL_TABLE_H #define _CORKSCREW_SYMBOL_TABLE_H +#include <stdint.h> #include <sys/types.h> #ifdef __cplusplus diff --git a/libcorkscrew/Android.mk b/libcorkscrew/Android.mk index 433f93c..9019986 100644 --- a/libcorkscrew/Android.mk +++ b/libcorkscrew/Android.mk @@ -14,9 +14,7 @@ LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ +generic_src_files := \ backtrace.c \ backtrace-helper.c \ demangle.c \ @@ -24,16 +22,24 @@ LOCAL_SRC_FILES := \ ptrace.c \ symbol_table.c -ifeq ($(TARGET_ARCH),arm) -LOCAL_SRC_FILES += \ +arm_src_files := \ arch-arm/backtrace-arm.c \ arch-arm/ptrace-arm.c + +x86_src_files := \ + arch-x86/backtrace-x86.c \ + arch-x86/ptrace-x86.c + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(generic_src_files) + +ifeq ($(TARGET_ARCH),arm) +LOCAL_SRC_FILES += $(arm_src_files) LOCAL_CFLAGS += -DCORKSCREW_HAVE_ARCH endif ifeq ($(TARGET_ARCH),x86) -LOCAL_SRC_FILES += \ - arch-x86/backtrace-x86.c \ - arch-x86/ptrace-x86.c +LOCAL_SRC_FILES += $(x86_src_files) LOCAL_CFLAGS += -DCORKSCREW_HAVE_ARCH endif @@ -44,3 +50,38 @@ LOCAL_MODULE := libcorkscrew LOCAL_MODULE_TAGS := optional include $(BUILD_SHARED_LIBRARY) + +# Build test. +include $(CLEAR_VARS) +LOCAL_SRC_FILES := test.c +LOCAL_CFLAGS += -std=gnu99 -Werror -fno-inline-small-functions +LOCAL_SHARED_LIBRARIES := libcorkscrew +LOCAL_MODULE := libcorkscrew_test +LOCAL_MODULE_TAGS := optional +include $(BUILD_EXECUTABLE) + + +ifeq ($(HOST_OS)-$(HOST_ARCH),linux-x86) + +# Build libcorkscrew. +include $(CLEAR_VARS) +LOCAL_SRC_FILES += $(generic_src_files) $(x86_src_files) +LOCAL_CFLAGS += -DCORKSCREW_HAVE_ARCH +LOCAL_SHARED_LIBRARIES += libgccdemangle +LOCAL_STATIC_LIBRARIES += libcutils +LOCAL_LDLIBS += -ldl -lrt +LOCAL_CFLAGS += -std=gnu99 -Werror +LOCAL_MODULE := libcorkscrew +LOCAL_MODULE_TAGS := optional +include $(BUILD_HOST_SHARED_LIBRARY) + +# Build test. +include $(CLEAR_VARS) +LOCAL_SRC_FILES := test.c +LOCAL_CFLAGS += -std=gnu99 -Werror -fno-inline-small-functions +LOCAL_SHARED_LIBRARIES := libcorkscrew +LOCAL_MODULE := libcorkscrew_test +LOCAL_MODULE_TAGS := optional +include $(BUILD_HOST_EXECUTABLE) + +endif # linux-x86 diff --git a/libcorkscrew/arch-x86/backtrace-x86.c b/libcorkscrew/arch-x86/backtrace-x86.c index 24fadcb..01d9ed5 100644 --- a/libcorkscrew/arch-x86/backtrace-x86.c +++ b/libcorkscrew/arch-x86/backtrace-x86.c @@ -31,40 +31,22 @@ #include <limits.h> #include <errno.h> #include <sys/ptrace.h> -#include <sys/exec_elf.h> #include <cutils/log.h> -/* Machine context at the time a signal was raised. */ -typedef struct ucontext { - uint32_t uc_flags; - struct ucontext* uc_link; - stack_t uc_stack; - struct sigcontext { - uint32_t gs; - uint32_t fs; - uint32_t es; - uint32_t ds; - uint32_t edi; - uint32_t esi; - uint32_t ebp; - uint32_t esp; - uint32_t ebx; - uint32_t edx; - uint32_t ecx; - uint32_t eax; - uint32_t trapno; - uint32_t err; - uint32_t eip; - uint32_t cs; - uint32_t efl; - uint32_t uesp; - uint32_t ss; - void* fpregs; - uint32_t oldmask; - uint32_t cr2; - } uc_mcontext; - uint32_t uc_sigmask; -} ucontext_t; +#if defined(__BIONIC__) + +// Bionic offers the Linux kernel headers. +#include <asm/sigcontext.h> +#include <asm/ucontext.h> +typedef struct ucontext ucontext_t; + +#else + +// glibc has its own renaming of the Linux kernel's structures. +#define __USE_GNU // For REG_EBP, REG_ESP, and REG_EIP. +#include <ucontext.h> + +#endif /* Unwind state. */ typedef struct { @@ -114,9 +96,15 @@ ssize_t unwind_backtrace_signal_arch(siginfo_t* siginfo, void* sigcontext, const ucontext_t* uc = (const ucontext_t*)sigcontext; unwind_state_t state; +#if defined(__BIONIC__) state.ebp = uc->uc_mcontext.ebp; - state.eip = uc->uc_mcontext.eip; state.esp = uc->uc_mcontext.esp; + state.eip = uc->uc_mcontext.eip; +#else + state.ebp = uc->uc_mcontext.gregs[REG_EBP]; + state.esp = uc->uc_mcontext.gregs[REG_ESP]; + state.eip = uc->uc_mcontext.gregs[REG_EIP]; +#endif memory_t memory; init_memory(&memory, map_info_list); diff --git a/libcorkscrew/backtrace.c b/libcorkscrew/backtrace.c index fa21574..eec53a2 100644 --- a/libcorkscrew/backtrace.c +++ b/libcorkscrew/backtrace.c @@ -27,16 +27,41 @@ #include <unistd.h> #include <signal.h> +#include <stdlib.h> +#include <string.h> #include <pthread.h> #include <unwind.h> -#include <sys/exec_elf.h> #include <cutils/log.h> #include <cutils/atomic.h> +#include <elf.h> #if HAVE_DLADDR +#define __USE_GNU // For dladdr(3) in glibc. #include <dlfcn.h> #endif +#if defined(__BIONIC__) + +// Bionic implements and exports gettid but only implements tgkill. +extern int tgkill(int tgid, int tid, int sig); + +#else + +// glibc doesn't implement or export either gettid or tgkill. + +#include <unistd.h> +#include <sys/syscall.h> + +static pid_t gettid() { + return syscall(__NR_gettid); +} + +static int tgkill(int tgid, int tid, int sig) { + return syscall(__NR_tgkill, tgid, tid, sig); +} + +#endif + typedef struct { backtrace_frame_t* backtrace; size_t ignore_depth; @@ -115,8 +140,6 @@ static void unwind_backtrace_thread_signal_handler(int n, siginfo_t* siginfo, vo } #endif -extern int tgkill(int tgid, int tid, int sig); - ssize_t unwind_backtrace_thread(pid_t tid, backtrace_frame_t* backtrace, size_t ignore_depth, size_t max_depth) { if (tid == gettid()) { diff --git a/libcorkscrew/map_info.c b/libcorkscrew/map_info.c index f33378f..3c52854 100644 --- a/libcorkscrew/map_info.c +++ b/libcorkscrew/map_info.c @@ -21,6 +21,7 @@ #include <ctype.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <limits.h> #include <pthread.h> diff --git a/libcorkscrew/ptrace.c b/libcorkscrew/ptrace.c index cbea8ca..6496d5e 100644 --- a/libcorkscrew/ptrace.c +++ b/libcorkscrew/ptrace.c @@ -21,6 +21,7 @@ #include <corkscrew/ptrace.h> #include <errno.h> +#include <stdlib.h> #include <sys/ptrace.h> #include <cutils/log.h> diff --git a/libcorkscrew/symbol_table.c b/libcorkscrew/symbol_table.c index 1b97180..29e4a79 100644 --- a/libcorkscrew/symbol_table.c +++ b/libcorkscrew/symbol_table.c @@ -19,14 +19,22 @@ #include <corkscrew/symbol_table.h> +#include <stdbool.h> #include <stdlib.h> +#include <elf.h> #include <fcntl.h> #include <string.h> #include <sys/stat.h> #include <sys/mman.h> -#include <sys/exec_elf.h> #include <cutils/log.h> +static bool is_elf(Elf32_Ehdr* e) { + return (e->e_ident[EI_MAG0] == ELFMAG0 && + e->e_ident[EI_MAG1] == ELFMAG1 && + e->e_ident[EI_MAG2] == ELFMAG2 && + e->e_ident[EI_MAG3] == ELFMAG3); +} + // Compare function for qsort static int qcompar(const void *a, const void *b) { const symbol_t* asym = (const symbol_t*)a; @@ -67,7 +75,7 @@ symbol_table_t* load_symbol_table(const char *filename) { // Parse the file header Elf32_Ehdr *hdr = (Elf32_Ehdr*)base; - if (!IS_ELF(*hdr)) { + if (!is_elf(hdr)) { goto out_close; } Elf32_Shdr *shdr = (Elf32_Shdr*)(base + hdr->e_shoff); diff --git a/libcorkscrew/test.c b/libcorkscrew/test.c new file mode 100644 index 0000000..af34c03 --- /dev/null +++ b/libcorkscrew/test.c @@ -0,0 +1,64 @@ +#include <corkscrew/backtrace.h> +#include <corkscrew/symbol_table.h> +#include <stdio.h> +#include <stdlib.h> + +void do_backtrace() { + const size_t MAX_DEPTH = 32; + backtrace_frame_t* frames = (backtrace_frame_t*) malloc(sizeof(backtrace_frame_t) * MAX_DEPTH); + ssize_t frame_count = unwind_backtrace(frames, 0, MAX_DEPTH); + fprintf(stderr, "frame_count=%d\n", (int) frame_count); + + backtrace_symbol_t* backtrace_symbols = (backtrace_symbol_t*) malloc(sizeof(backtrace_symbol_t) * frame_count); + get_backtrace_symbols(frames, frame_count, backtrace_symbols); + + for (size_t i = 0; i < (size_t) frame_count; ++i) { + char line[MAX_BACKTRACE_LINE_LENGTH]; + format_backtrace_line(i, &frames[i], &backtrace_symbols[i], + line, MAX_BACKTRACE_LINE_LENGTH); + if (backtrace_symbols[i].symbol_name != NULL) { + // get_backtrace_symbols found the symbol's name with dladdr(3). + fprintf(stderr, " %s\n", line); + } else { + // We don't have a symbol. Maybe this is a static symbol, and + // we can look it up? + symbol_table_t* symbols = NULL; + if (backtrace_symbols[i].map_name != NULL) { + symbols = load_symbol_table(backtrace_symbols[i].map_name); + } + const symbol_t* symbol = NULL; + if (symbols != NULL) { + symbol = find_symbol(symbols, frames[i].absolute_pc); + } + if (symbol != NULL) { + uintptr_t offset = frames[i].absolute_pc - symbol->start; + fprintf(stderr, " %s (%s%+d)\n", line, symbol->name, offset); + } else { + fprintf(stderr, " %s (\?\?\?)\n", line); + } + free_symbol_table(symbols); + } + } + + free_backtrace_symbols(backtrace_symbols, frame_count); + free(backtrace_symbols); + free(frames); +} + +__attribute__ ((noinline)) void g() { + fprintf(stderr, "g()\n"); + do_backtrace(); +} + +__attribute__ ((noinline)) int f(int i) { + fprintf(stderr, "f(%i)\n", i); + if (i == 0) { + g(); + return 0; + } + return f(i - 1); +} + +int main() { + return f(5); +} diff --git a/libsuspend/autosuspend_earlysuspend.c b/libsuspend/autosuspend_earlysuspend.c index 2c2aa36..b440128 100644 --- a/libsuspend/autosuspend_earlysuspend.c +++ b/libsuspend/autosuspend_earlysuspend.c @@ -35,7 +35,56 @@ static int sPowerStatefd; static const char *pwr_state_mem = "mem"; static const char *pwr_state_on = "on"; +static pthread_t earlysuspend_thread; +int wait_for_fb_wake(void) +{ + int err = 0; + char buf; + int fd = open(EARLYSUSPEND_WAIT_FOR_FB_WAKE, O_RDONLY, 0); + // if the file doesn't exist, the error will be caught in read() below + do { + err = read(fd, &buf, 1); + } while (err < 0 && errno == EINTR); + ALOGE_IF(err < 0, + "*** ANDROID_WAIT_FOR_FB_WAKE failed (%s)", strerror(errno)); + close(fd); + return err < 0 ? err : 0; +} + +static int wait_for_fb_sleep(void) +{ + int err = 0; + char buf; + int fd = open(EARLYSUSPEND_WAIT_FOR_FB_SLEEP, O_RDONLY, 0); + // if the file doesn't exist, the error will be caught in read() below + do { + err = read(fd, &buf, 1); + } while (err < 0 && errno == EINTR); + ALOGE_IF(err < 0, + "*** ANDROID_WAIT_FOR_FB_SLEEP failed (%s)", strerror(errno)); + close(fd); + return err < 0 ? err : 0; +} + +static void *earlysuspend_thread_func(void *arg) +{ + char buf[80]; + char wakeup_count[20]; + int wakeup_count_len; + int ret; + + while (1) { + if (wait_for_fb_sleep()) { + ALOGE("Failed reading wait_for_fb_sleep, exiting earlysuspend thread\n"); + return NULL; + } + if (wait_for_fb_wake()) { + ALOGE("Failed reading wait_for_fb_wake, exiting earlysuspend thread\n"); + return NULL; + } + } +} static int autosuspend_earlysuspend_enable(void) { char buf[80]; @@ -85,29 +134,56 @@ struct autosuspend_ops autosuspend_earlysuspend_ops = { .disable = autosuspend_earlysuspend_disable, }; -struct autosuspend_ops *autosuspend_earlysuspend_init(void) +void start_earlysuspend_thread(void) { char buf[80]; int ret; ret = access(EARLYSUSPEND_WAIT_FOR_FB_SLEEP, F_OK); if (ret < 0) { - return NULL; + return; } ret = access(EARLYSUSPEND_WAIT_FOR_FB_WAKE, F_OK); if (ret < 0) { - return NULL; + return; } + ALOGI("Starting early suspend unblocker thread\n"); + ret = pthread_create(&earlysuspend_thread, NULL, earlysuspend_thread_func, NULL); + if (ret) { + strerror_r(ret, buf, sizeof(buf)); + ALOGE("Error creating thread: %s\n", buf); + } +} + +struct autosuspend_ops *autosuspend_earlysuspend_init(void) +{ + char buf[80]; + int ret; + sPowerStatefd = open(EARLYSUSPEND_SYS_POWER_STATE, O_RDWR); if (sPowerStatefd < 0) { strerror_r(errno, buf, sizeof(buf)); - ALOGE("Error opening %s: %s\n", EARLYSUSPEND_SYS_POWER_STATE, buf); + ALOGW("Error opening %s: %s\n", EARLYSUSPEND_SYS_POWER_STATE, buf); return NULL; } + ret = write(sPowerStatefd, "on", 2); + if (ret < 0) { + strerror_r(errno, buf, sizeof(buf)); + ALOGW("Error writing 'on' to %s: %s\n", EARLYSUSPEND_SYS_POWER_STATE, buf); + goto err_write; + } + ALOGI("Selected early suspend\n"); + + start_earlysuspend_thread(); + return &autosuspend_earlysuspend_ops; + +err_write: + close(sPowerStatefd); + return NULL; } diff --git a/logcat/event.logtags b/logcat/event.logtags index 2e814ff..09640e1 100644 --- a/logcat/event.logtags +++ b/logcat/event.logtags @@ -134,5 +134,21 @@ 70200 aggregation (aggregation time|2|3) 70201 aggregation_test (field1|1|2),(field2|1|2),(field3|1|2),(field4|1|2),(field5|1|2) +# libc failure logging +80100 bionic_event_memcpy_buffer_overflow (uid|1) +80105 bionic_event_strcat_buffer_overflow (uid|1) +80110 bionic_event_memmov_buffer_overflow (uid|1) +80115 bionic_event_strncat_buffer_overflow (uid|1) +80120 bionic_event_strncpy_buffer_overflow (uid|1) +80125 bionic_event_memset_buffer_overflow (uid|1) +80130 bionic_event_strcpy_buffer_overflow (uid|1) + +80200 bionic_event_strcat_integer_overflow (uid|1) +80205 bionic_event_strncat_integer_overflow (uid|1) + +80300 bionic_event_resolver_old_response (uid|1) +80305 bionic_event_resolver_wrong_server (uid|1) +80310 bionic_event_resolver_wrong_query (uid|1) + # NOTE - the range 1000000-2000000 is reserved for partners and others who # want to define their own log tags without conflicting with the core platform. diff --git a/rootdir/init.rc b/rootdir/init.rc index bb7e4ea..24200d0 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -241,6 +241,7 @@ on boot chown radio system /sys/android_power/acquire_full_wake_lock chown radio system /sys/android_power/acquire_partial_wake_lock chown radio system /sys/android_power/release_wake_lock + chown system system /sys/power/autosleep chown system system /sys/power/state chown system system /sys/power/wakeup_count chown radio system /sys/power/wake_lock diff --git a/sdcard/Android.mk b/sdcard/Android.mk index c430ac8..fb04d6d 100644 --- a/sdcard/Android.mk +++ b/sdcard/Android.mk @@ -4,6 +4,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= sdcard.c LOCAL_MODULE:= sdcard +LOCAL_CFLAGS := -Wall -Wno-unused-parameter LOCAL_SHARED_LIBRARIES := libc diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c index a95513c..316588c 100644 --- a/sdcard/sdcard.c +++ b/sdcard/sdcard.c @@ -25,7 +25,9 @@ #include <sys/statfs.h> #include <sys/uio.h> #include <dirent.h> +#include <limits.h> #include <ctype.h> +#include <pthread.h> #include <private/android_filesystem_config.h> @@ -72,28 +74,42 @@ #define MOUNT_POINT "/storage/sdcard0" +/* Maximum number of bytes to write in one request. */ +#define MAX_WRITE (256 * 1024) + +/* Maximum number of bytes to read in one request. */ +#define MAX_READ (128 * 1024) + +/* Largest possible request. + * The request size is bounded by the maximum size of a FUSE_WRITE request because it has + * the largest possible data payload. */ +#define MAX_REQUEST_SIZE (sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE) + +/* Default number of threads. */ +#define DEFAULT_NUM_THREADS 2 + +/* Pseudo-error constant used to indicate that no fuse status is needed + * or that a reply has already been written. */ +#define NO_STATUS 1 + struct handle { - struct node *node; int fd; }; struct dirhandle { - struct node *node; DIR *d; }; struct node { + __u32 refcount; __u64 nid; __u64 gen; struct node *next; /* per-dir sibling list */ struct node *child; /* first contained file by this dir */ - struct node *all; /* global node list */ struct node *parent; /* containing directory */ - __u32 refcount; - __u32 namelen; - + size_t namelen; char *name; /* If non-null, this is the real name of the file in the underlying storage. * This may differ from the field "name" only by case. @@ -103,98 +119,166 @@ struct node { char *actual_name; }; +/* Global data structure shared by all fuse handlers. */ struct fuse { - __u64 next_generation; - __u64 next_node_id; + pthread_mutex_t lock; + __u64 next_generation; int fd; - - struct node *all; - struct node root; - char rootpath[1024]; + char rootpath[PATH_MAX]; +}; + +/* Private data used by a single fuse handler. */ +struct fuse_handler { + struct fuse* fuse; + int token; + + /* To save memory, we never use the contents of the request buffer and the read + * buffer at the same time. This allows us to share the underlying storage. */ + union { + __u8 request_buffer[MAX_REQUEST_SIZE]; + __u8 read_buffer[MAX_READ]; + }; }; -static unsigned uid = -1; -static unsigned gid = -1; +static inline void *id_to_ptr(__u64 nid) +{ + return (void *) (uintptr_t) nid; +} -#define PATH_BUFFER_SIZE 1024 +static inline __u64 ptr_to_id(void *ptr) +{ + return (__u64) (uintptr_t) ptr; +} -#define NO_CASE_SENSITIVE_MATCH 0 -#define CASE_SENSITIVE_MATCH 1 +static void acquire_node_locked(struct node* node) +{ + node->refcount++; + TRACE("ACQUIRE %p (%s) rc=%d\n", node, node->name, node->refcount); +} -/* - * Get the real-life absolute path to a node. - * node: start at this node - * buf: storage for returned string - * name: append this string to path if set +static void remove_node_from_parent_locked(struct node* node); + +static void release_node_locked(struct node* node) +{ + TRACE("RELEASE %p (%s) rc=%d\n", node, node->name, node->refcount); + if (node->refcount > 0) { + node->refcount--; + if (!node->refcount) { + TRACE("DESTROY %p (%s)\n", node, node->name); + remove_node_from_parent_locked(node); + + /* TODO: remove debugging - poison memory */ + memset(node->name, 0xef, node->namelen); + free(node->name); + free(node->actual_name); + memset(node, 0xfc, sizeof(*node)); + free(node); + } + } else { + ERROR("Zero refcnt %p\n", node); + } +} + +static void add_node_to_parent_locked(struct node *node, struct node *parent) { + node->parent = parent; + node->next = parent->child; + parent->child = node; + acquire_node_locked(parent); +} + +static void remove_node_from_parent_locked(struct node* node) +{ + if (node->parent) { + if (node->parent->child == node) { + node->parent->child = node->parent->child->next; + } else { + struct node *node2; + node2 = node->parent->child; + while (node2->next != node) + node2 = node2->next; + node2->next = node->next; + } + release_node_locked(node->parent); + node->parent = NULL; + node->next = NULL; + } +} + +/* Gets the absolute path to a node into the provided buffer. + * + * Populates 'buf' with the path and returns the length of the path on success, + * or returns -1 if the path is too long for the provided buffer. */ -char *do_node_get_path(struct node *node, char *buf, const char *name, int match_case_insensitive) +static ssize_t get_node_path_locked(struct node* node, char* buf, size_t bufsize) { - struct node *in_node = node; - const char *in_name = name; - char *out = buf + PATH_BUFFER_SIZE - 1; - int len; - out[0] = 0; - - if (name) { - len = strlen(name); - goto start; - } - - while (node) { - name = (node->actual_name ? node->actual_name : node->name); - len = node->namelen; - node = node->parent; - start: - if ((len + 1) > (out - buf)) - return 0; - out -= len; - memcpy(out, name, len); - /* avoid double slash at beginning of path */ - if (out[0] != '/') { - out --; - out[0] = '/'; + size_t namelen = node->namelen; + if (bufsize < namelen + 1) { + return -1; + } + + ssize_t pathlen = 0; + if (node->parent) { + pathlen = get_node_path_locked(node->parent, buf, bufsize - namelen - 2); + if (pathlen < 0) { + return -1; } + buf[pathlen++] = '/'; } - /* If we are searching for a file within node (rather than computing node's path) - * and fail, then we need to look for a case insensitive match. - */ - if (in_name && match_case_insensitive && access(out, F_OK) != 0) { - char *path, buffer[PATH_BUFFER_SIZE]; - DIR* dir; + const char* name = node->actual_name ? node->actual_name : node->name; + memcpy(buf + pathlen, name, namelen + 1); /* include trailing \0 */ + return pathlen + namelen; +} + +/* Finds the absolute path of a file within a given directory. + * Performs a case-insensitive search for the file and sets the buffer to the path + * of the first matching file. If 'search' is zero or if no match is found, sets + * the buffer to the path that the file would have, assuming the name were case-sensitive. + * + * Populates 'buf' with the path and returns the actual name (within 'buf') on success, + * or returns NULL if the path is too long for the provided buffer. + */ +static char* find_file_within(const char* path, const char* name, + char* buf, size_t bufsize, int search) +{ + size_t pathlen = strlen(path); + size_t namelen = strlen(name); + size_t childlen = pathlen + namelen + 1; + char* actual; + + if (bufsize <= childlen) { + return NULL; + } + + memcpy(buf, path, pathlen); + buf[pathlen] = '/'; + actual = buf + pathlen + 1; + memcpy(actual, name, namelen + 1); + + if (search && access(buf, F_OK)) { struct dirent* entry; - path = do_node_get_path(in_node, buffer, NULL, NO_CASE_SENSITIVE_MATCH); - dir = opendir(path); + DIR* dir = opendir(path); if (!dir) { ERROR("opendir %s failed: %s", path, strerror(errno)); - return out; + return actual; } - while ((entry = readdir(dir))) { - if (!strcasecmp(entry->d_name, in_name)) { - /* we have a match - replace the name */ - len = strlen(in_name); - memcpy(buf + PATH_BUFFER_SIZE - len - 1, entry->d_name, len); + if (!strcasecmp(entry->d_name, name)) { + /* we have a match - replace the name, don't need to copy the null again */ + memcpy(actual, entry->d_name, namelen); break; } } closedir(dir); } - - return out; + return actual; } -char *node_get_path(struct node *node, char *buf, const char *name) +static void attr_from_stat(struct fuse_attr *attr, const struct stat *s, __u64 nid) { - /* We look for case insensitive matches by default */ - return do_node_get_path(node, buf, name, CASE_SENSITIVE_MATCH); -} - -void attr_from_stat(struct fuse_attr *attr, struct stat *s) -{ - attr->ino = s->st_ino; + attr->ino = nid; attr->size = s->st_size; attr->blocks = s->st_blocks; attr->atime = s->st_atime; @@ -221,759 +305,1020 @@ void attr_from_stat(struct fuse_attr *attr, struct stat *s) attr->gid = AID_SDCARD_RW; } -int node_get_attr(struct node *node, struct fuse_attr *attr) +struct node *create_node_locked(struct fuse* fuse, + struct node *parent, const char *name, const char* actual_name) { - int res; - struct stat s; - char *path, buffer[PATH_BUFFER_SIZE]; + struct node *node; + size_t namelen = strlen(name); - path = node_get_path(node, buffer, 0); - res = lstat(path, &s); - if (res < 0) { - ERROR("lstat('%s') errno %d\n", path, errno); - return -1; + node = calloc(1, sizeof(struct node)); + if (!node) { + return NULL; } - - attr_from_stat(attr, &s); - attr->ino = node->nid; - - return 0; -} - -static void add_node_to_parent(struct node *node, struct node *parent) { - node->parent = parent; - node->next = parent->child; - parent->child = node; - parent->refcount++; + node->name = malloc(namelen + 1); + if (!node->name) { + free(node); + return NULL; + } + memcpy(node->name, name, namelen + 1); + if (strcmp(name, actual_name)) { + node->actual_name = malloc(namelen + 1); + if (!node->actual_name) { + free(node->name); + free(node); + return NULL; + } + memcpy(node->actual_name, actual_name, namelen + 1); + } + node->namelen = namelen; + node->nid = ptr_to_id(node); + node->gen = fuse->next_generation++; + acquire_node_locked(node); + add_node_to_parent_locked(node, parent); + return node; } -/* Check to see if our parent directory already has a file with a name - * that differs only by case. If we find one, store it in the actual_name - * field so node_get_path will map it to this file in the underlying storage. - */ -static void node_find_actual_name(struct node *node) +static int rename_node_locked(struct node *node, const char *name, + const char* actual_name) { - char *path, buffer[PATH_BUFFER_SIZE]; - const char *node_name = node->name; - DIR* dir; - struct dirent* entry; - - if (!node->parent) return; - - path = node_get_path(node->parent, buffer, 0); - dir = opendir(path); - if (!dir) { - ERROR("opendir %s failed: %s", path, strerror(errno)); - return; + size_t namelen = strlen(name); + int need_actual_name = strcmp(name, actual_name); + + /* make the storage bigger without actually changing the name + * in case an error occurs part way */ + if (namelen > node->namelen) { + char* new_name = realloc(node->name, namelen + 1); + if (!new_name) { + return -ENOMEM; + } + node->name = new_name; + if (need_actual_name && node->actual_name) { + char* new_actual_name = realloc(node->actual_name, namelen + 1); + if (!new_actual_name) { + return -ENOMEM; + } + node->actual_name = new_actual_name; + } } - while ((entry = readdir(dir))) { - const char *test_name = entry->d_name; - if (strcmp(test_name, node_name) && !strcasecmp(test_name, node_name)) { - /* we have a match - differs but only by case */ - node->actual_name = strdup(test_name); + /* update the name, taking care to allocate storage before overwriting the old name */ + if (need_actual_name) { + if (!node->actual_name) { + node->actual_name = malloc(namelen + 1); if (!node->actual_name) { - ERROR("strdup failed - out of memory\n"); - exit(1); + return -ENOMEM; } - break; } + memcpy(node->actual_name, actual_name, namelen + 1); + } else { + free(node->actual_name); + node->actual_name = NULL; } - closedir(dir); + memcpy(node->name, name, namelen + 1); + node->namelen = namelen; + return 0; } -struct node *node_create(struct node *parent, const char *name, __u64 nid, __u64 gen) +static struct node *lookup_node_by_id_locked(struct fuse *fuse, __u64 nid) { - struct node *node; - int namelen = strlen(name); - - node = calloc(1, sizeof(struct node)); - if (node == 0) { - return 0; - } - node->name = malloc(namelen + 1); - if (node->name == 0) { - free(node); - return 0; + if (nid == FUSE_ROOT_ID) { + return &fuse->root; + } else { + return id_to_ptr(nid); } +} - node->nid = nid; - node->gen = gen; - add_node_to_parent(node, parent); - memcpy(node->name, name, namelen + 1); - node->namelen = namelen; - node_find_actual_name(node); +static struct node* lookup_node_and_path_by_id_locked(struct fuse* fuse, __u64 nid, + char* buf, size_t bufsize) +{ + struct node* node = lookup_node_by_id_locked(fuse, nid); + if (node && get_node_path_locked(node, buf, bufsize) < 0) { + node = NULL; + } return node; } -static char *rename_node(struct node *node, const char *name) +static struct node *lookup_child_by_name_locked(struct node *node, const char *name) { - node->namelen = strlen(name); - char *newname = realloc(node->name, node->namelen + 1); - if (newname == 0) - return 0; - node->name = newname; - memcpy(node->name, name, node->namelen + 1); - node_find_actual_name(node); - return node->name; + for (node = node->child; node; node = node->next) { + /* use exact string comparison, nodes that differ by case + * must be considered distinct even if they refer to the same + * underlying file as otherwise operations such as "mv x x" + * will not work because the source and target nodes are the same. */ + if (!strcmp(name, node->name)) { + return node; + } + } + return 0; +} + +static struct node* acquire_or_create_child_locked( + struct fuse* fuse, struct node* parent, + const char* name, const char* actual_name) +{ + struct node* child = lookup_child_by_name_locked(parent, name); + if (child) { + acquire_node_locked(child); + } else { + child = create_node_locked(fuse, parent, name, actual_name); + } + return child; } -void fuse_init(struct fuse *fuse, int fd, const char *path) +static void fuse_init(struct fuse *fuse, int fd, const char *path) { + pthread_mutex_init(&fuse->lock, NULL); + fuse->fd = fd; - fuse->next_node_id = 2; fuse->next_generation = 0; - fuse->all = &fuse->root; - memset(&fuse->root, 0, sizeof(fuse->root)); fuse->root.nid = FUSE_ROOT_ID; /* 1 */ fuse->root.refcount = 2; - rename_node(&fuse->root, path); + fuse->root.namelen = strlen(path); + fuse->root.name = strdup(path); } -static inline void *id_to_ptr(__u64 nid) +static void fuse_status(struct fuse *fuse, __u64 unique, int err) { - return (void *) nid; + struct fuse_out_header hdr; + hdr.len = sizeof(hdr); + hdr.error = err; + hdr.unique = unique; + write(fuse->fd, &hdr, sizeof(hdr)); } -static inline __u64 ptr_to_id(void *ptr) +static void fuse_reply(struct fuse *fuse, __u64 unique, void *data, int len) { - return (__u64) ptr; + struct fuse_out_header hdr; + struct iovec vec[2]; + int res; + + hdr.len = len + sizeof(hdr); + hdr.error = 0; + hdr.unique = unique; + + vec[0].iov_base = &hdr; + vec[0].iov_len = sizeof(hdr); + vec[1].iov_base = data; + vec[1].iov_len = len; + + res = writev(fuse->fd, vec, 2); + if (res < 0) { + ERROR("*** REPLY FAILED *** %d\n", errno); + } } +static int fuse_reply_entry(struct fuse* fuse, __u64 unique, + struct node* parent, const char* name, const char* actual_name, + const char* path) +{ + struct node* node; + struct fuse_entry_out out; + struct stat s; + + if (lstat(path, &s) < 0) { + return -errno; + } -struct node *lookup_by_inode(struct fuse *fuse, __u64 nid) + pthread_mutex_lock(&fuse->lock); + node = acquire_or_create_child_locked(fuse, parent, name, actual_name); + if (!node) { + pthread_mutex_unlock(&fuse->lock); + return -ENOMEM; + } + memset(&out, 0, sizeof(out)); + attr_from_stat(&out.attr, &s, node->nid); + out.attr_valid = 10; + out.entry_valid = 10; + out.nodeid = node->nid; + out.generation = node->gen; + pthread_mutex_unlock(&fuse->lock); + fuse_reply(fuse, unique, &out, sizeof(out)); + return NO_STATUS; +} + +static int fuse_reply_attr(struct fuse* fuse, __u64 unique, __u64 nid, + const char* path) { - if (nid == FUSE_ROOT_ID) { - return &fuse->root; - } else { - return id_to_ptr(nid); + struct fuse_attr_out out; + struct stat s; + + if (lstat(path, &s) < 0) { + return -errno; } + memset(&out, 0, sizeof(out)); + attr_from_stat(&out.attr, &s, nid); + out.attr_valid = 10; + fuse_reply(fuse, unique, &out, sizeof(out)); + return NO_STATUS; } -struct node *lookup_child_by_name(struct node *node, const char *name) +static int handle_lookup(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header *hdr, const char* name) { - for (node = node->child; node; node = node->next) { - if (!strcmp(name, node->name)) { - return node; - } + struct node* parent_node; + char parent_path[PATH_MAX]; + char child_path[PATH_MAX]; + const char* actual_name; + + pthread_mutex_lock(&fuse->lock); + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, + parent_path, sizeof(parent_path)); + TRACE("[%d] LOOKUP %s @ %llx (%s)\n", handler->token, name, hdr->nodeid, + parent_node ? parent_node->name : "?"); + pthread_mutex_unlock(&fuse->lock); + + if (!parent_node || !(actual_name = find_file_within(parent_path, name, + child_path, sizeof(child_path), 1))) { + return -ENOENT; } - return 0; + return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path); } -struct node *lookup_child_by_inode(struct node *node, __u64 nid) +static int handle_forget(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header *hdr, const struct fuse_forget_in *req) { - for (node = node->child; node; node = node->next) { - if (node->nid == nid) { - return node; + struct node* node; + + pthread_mutex_lock(&fuse->lock); + node = lookup_node_by_id_locked(fuse, hdr->nodeid); + TRACE("[%d] FORGET #%lld @ %llx (%s)\n", handler->token, req->nlookup, + hdr->nodeid, node ? node->name : "?"); + if (node) { + __u64 n = req->nlookup; + while (n--) { + release_node_locked(node); } } - return 0; + pthread_mutex_unlock(&fuse->lock); + return NO_STATUS; /* no reply */ } -static void dec_refcount(struct node *node) { - if (node->refcount > 0) { - node->refcount--; - TRACE("dec_refcount %p(%s) -> %d\n", node, node->name, node->refcount); - } else { - ERROR("Zero refcnt %p\n", node); +static int handle_getattr(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header *hdr, const struct fuse_getattr_in *req) +{ + struct node* node; + char path[PATH_MAX]; + + pthread_mutex_lock(&fuse->lock); + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); + TRACE("[%d] GETATTR flags=%x fh=%llx @ %llx (%s)\n", handler->token, + req->getattr_flags, req->fh, hdr->nodeid, node ? node->name : "?"); + pthread_mutex_unlock(&fuse->lock); + + if (!node) { + return -ENOENT; } - } + return fuse_reply_attr(fuse, hdr->unique, hdr->nodeid, path); +} -static struct node *remove_child(struct node *parent, __u64 nid) +static int handle_setattr(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header *hdr, const struct fuse_setattr_in *req) { - struct node *prev = 0; - struct node *node; + struct node* node; + char path[PATH_MAX]; + struct timespec times[2]; + + pthread_mutex_lock(&fuse->lock); + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); + TRACE("[%d] SETATTR fh=%llx valid=%x @ %llx (%s)\n", handler->token, + req->fh, req->valid, hdr->nodeid, node ? node->name : "?"); + pthread_mutex_unlock(&fuse->lock); - for (node = parent->child; node; node = node->next) { - if (node->nid == nid) { - if (prev) { - prev->next = node->next; + if (!node) { + return -ENOENT; + } + + /* XXX: incomplete implementation on purpose. + * chmod/chown should NEVER be implemented.*/ + + if ((req->valid & FATTR_SIZE) && truncate(path, req->size) < 0) { + return -errno; + } + + /* Handle changing atime and mtime. If FATTR_ATIME_and FATTR_ATIME_NOW + * are both set, then set it to the current time. Else, set it to the + * time specified in the request. Same goes for mtime. Use utimensat(2) + * as it allows ATIME and MTIME to be changed independently, and has + * nanosecond resolution which fuse also has. + */ + if (req->valid & (FATTR_ATIME | FATTR_MTIME)) { + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_nsec = UTIME_OMIT; + if (req->valid & FATTR_ATIME) { + if (req->valid & FATTR_ATIME_NOW) { + times[0].tv_nsec = UTIME_NOW; } else { - parent->child = node->next; + times[0].tv_sec = req->atime; + times[0].tv_nsec = req->atimensec; + } + } + if (req->valid & FATTR_MTIME) { + if (req->valid & FATTR_MTIME_NOW) { + times[1].tv_nsec = UTIME_NOW; + } else { + times[1].tv_sec = req->mtime; + times[1].tv_nsec = req->mtimensec; } - node->next = 0; - node->parent = 0; - dec_refcount(parent); - return node; } - prev = node; + TRACE("[%d] Calling utimensat on %s with atime %ld, mtime=%ld\n", + handler->token, path, times[0].tv_sec, times[1].tv_sec); + if (utimensat(-1, path, times, 0) < 0) { + return -errno; + } + } + return fuse_reply_attr(fuse, hdr->unique, hdr->nodeid, path); +} + +static int handle_mknod(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_mknod_in* req, const char* name) +{ + struct node* parent_node; + char parent_path[PATH_MAX]; + char child_path[PATH_MAX]; + const char* actual_name; + + pthread_mutex_lock(&fuse->lock); + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, + parent_path, sizeof(parent_path)); + TRACE("[%d] MKNOD %s 0%o @ %llx (%s)\n", handler->token, + name, req->mode, hdr->nodeid, parent_node ? parent_node->name : "?"); + pthread_mutex_unlock(&fuse->lock); + + if (!parent_node || !(actual_name = find_file_within(parent_path, name, + child_path, sizeof(child_path), 1))) { + return -ENOENT; + } + __u32 mode = (req->mode & (~0777)) | 0664; + if (mknod(child_path, mode, req->rdev) < 0) { + return -errno; + } + return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path); +} + +static int handle_mkdir(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_mkdir_in* req, const char* name) +{ + struct node* parent_node; + char parent_path[PATH_MAX]; + char child_path[PATH_MAX]; + const char* actual_name; + + pthread_mutex_lock(&fuse->lock); + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, + parent_path, sizeof(parent_path)); + TRACE("[%d] MKDIR %s 0%o @ %llx (%s)\n", handler->token, + name, req->mode, hdr->nodeid, parent_node ? parent_node->name : "?"); + pthread_mutex_unlock(&fuse->lock); + + if (!parent_node || !(actual_name = find_file_within(parent_path, name, + child_path, sizeof(child_path), 1))) { + return -ENOENT; + } + __u32 mode = (req->mode & (~0777)) | 0775; + if (mkdir(child_path, mode) < 0) { + return -errno; + } + return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path); +} + +static int handle_unlink(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const char* name) +{ + struct node* parent_node; + char parent_path[PATH_MAX]; + char child_path[PATH_MAX]; + + pthread_mutex_lock(&fuse->lock); + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, + parent_path, sizeof(parent_path)); + TRACE("[%d] UNLINK %s @ %llx (%s)\n", handler->token, + name, hdr->nodeid, parent_node ? parent_node->name : "?"); + pthread_mutex_unlock(&fuse->lock); + + if (!parent_node || !find_file_within(parent_path, name, + child_path, sizeof(child_path), 1)) { + return -ENOENT; + } + if (unlink(child_path) < 0) { + return -errno; } return 0; } -struct node *node_lookup(struct fuse *fuse, struct node *parent, const char *name, - struct fuse_attr *attr) +static int handle_rmdir(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const char* name) { + struct node* parent_node; + char parent_path[PATH_MAX]; + char child_path[PATH_MAX]; + + pthread_mutex_lock(&fuse->lock); + parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, + parent_path, sizeof(parent_path)); + TRACE("[%d] RMDIR %s @ %llx (%s)\n", handler->token, + name, hdr->nodeid, parent_node ? parent_node->name : "?"); + pthread_mutex_unlock(&fuse->lock); + + if (!parent_node || !find_file_within(parent_path, name, + child_path, sizeof(child_path), 1)) { + return -ENOENT; + } + if (rmdir(child_path) < 0) { + return -errno; + } + return 0; +} + +static int handle_rename(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_rename_in* req, + const char* old_name, const char* new_name) +{ + struct node* old_parent_node; + struct node* new_parent_node; + struct node* child_node; + char old_parent_path[PATH_MAX]; + char new_parent_path[PATH_MAX]; + char old_child_path[PATH_MAX]; + char new_child_path[PATH_MAX]; + const char* new_actual_name; int res; - struct stat s; - char *path, buffer[PATH_BUFFER_SIZE]; - struct node *node; - path = node_get_path(parent, buffer, name); - /* XXX error? */ + pthread_mutex_lock(&fuse->lock); + old_parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, + old_parent_path, sizeof(old_parent_path)); + new_parent_node = lookup_node_and_path_by_id_locked(fuse, req->newdir, + new_parent_path, sizeof(new_parent_path)); + TRACE("[%d] RENAME %s->%s @ %llx (%s) -> %llx (%s)\n", handler->token, + old_name, new_name, + hdr->nodeid, old_parent_node ? old_parent_node->name : "?", + req->newdir, new_parent_node ? new_parent_node->name : "?"); + if (!old_parent_node || !new_parent_node) { + res = -ENOENT; + goto lookup_error; + } + child_node = lookup_child_by_name_locked(old_parent_node, old_name); + if (!child_node || get_node_path_locked(child_node, + old_child_path, sizeof(old_child_path)) < 0) { + res = -ENOENT; + goto lookup_error; + } + acquire_node_locked(child_node); + pthread_mutex_unlock(&fuse->lock); - res = lstat(path, &s); - if (res < 0) - return 0; - - node = lookup_child_by_name(parent, name); - if (!node) { - node = node_create(parent, name, fuse->next_node_id++, fuse->next_generation++); - if (!node) - return 0; - node->nid = ptr_to_id(node); - node->all = fuse->all; - fuse->all = node; + /* Special case for renaming a file where destination is same path + * differing only by case. In this case we don't want to look for a case + * insensitive match. This allows commands like "mv foo FOO" to work as expected. + */ + int search = old_parent_node != new_parent_node + || strcasecmp(old_name, new_name); + if (!(new_actual_name = find_file_within(new_parent_path, new_name, + new_child_path, sizeof(new_child_path), search))) { + res = -ENOENT; + goto io_error; } - attr_from_stat(attr, &s); - attr->ino = node->nid; + TRACE("[%d] RENAME %s->%s\n", handler->token, old_child_path, new_child_path); + res = rename(old_child_path, new_child_path); + if (res < 0) { + res = -errno; + goto io_error; + } - return node; + pthread_mutex_lock(&fuse->lock); + res = rename_node_locked(child_node, new_name, new_actual_name); + if (!res) { + remove_node_from_parent_locked(child_node); + add_node_to_parent_locked(child_node, new_parent_node); + } + goto done; + +io_error: + pthread_mutex_lock(&fuse->lock); +done: + release_node_locked(child_node); +lookup_error: + pthread_mutex_unlock(&fuse->lock); + return res; } -void node_release(struct node *node) +static int handle_open(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_open_in* req) { - TRACE("RELEASE %p (%s) rc=%d\n", node, node->name, node->refcount); - dec_refcount(node); - if (node->refcount == 0) { - if (node->parent->child == node) { - node->parent->child = node->parent->child->next; - } else { - struct node *node2; + struct node* node; + char path[PATH_MAX]; + struct fuse_open_out out; + struct handle *h; - node2 = node->parent->child; - while (node2->next != node) - node2 = node2->next; - node2->next = node->next; - } + pthread_mutex_lock(&fuse->lock); + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); + TRACE("[%d] OPEN 0%o @ %llx (%s)\n", handler->token, + req->flags, hdr->nodeid, node ? node->name : "?"); + pthread_mutex_unlock(&fuse->lock); - TRACE("DESTROY %p (%s)\n", node, node->name); + if (!node) { + return -ENOENT; + } + h = malloc(sizeof(*h)); + if (!h) { + return -ENOMEM; + } + TRACE("[%d] OPEN %s\n", handler->token, path); + h->fd = open(path, req->flags); + if (h->fd < 0) { + free(h); + return -errno; + } + out.fh = ptr_to_id(h); + out.open_flags = 0; + out.padding = 0; + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return NO_STATUS; +} - node_release(node->parent); +static int handle_read(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_read_in* req) +{ + struct handle *h = id_to_ptr(req->fh); + __u64 unique = hdr->unique; + __u32 size = req->size; + __u64 offset = req->offset; + int res; - node->parent = 0; - node->next = 0; + /* Don't access any other fields of hdr or req beyond this point, the read buffer + * overlaps the request buffer and will clobber data in the request. This + * saves us 128KB per request handler thread at the cost of this scary comment. */ - /* TODO: remove debugging - poison memory */ - memset(node->name, 0xef, node->namelen); - free(node->name); - free(node->actual_name); - memset(node, 0xfc, sizeof(*node)); - free(node); + TRACE("[%d] READ %p(%d) %u@%llu\n", handler->token, + h, h->fd, size, offset); + if (size > sizeof(handler->read_buffer)) { + return -EINVAL; + } + res = pread64(h->fd, handler->read_buffer, size, offset); + if (res < 0) { + return -errno; } + fuse_reply(fuse, unique, handler->read_buffer, res); + return NO_STATUS; } -void fuse_status(struct fuse *fuse, __u64 unique, int err) +static int handle_write(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_write_in* req, + const void* buffer) { - struct fuse_out_header hdr; - hdr.len = sizeof(hdr); - hdr.error = err; - hdr.unique = unique; - if (err) { -// ERROR("*** %d ***\n", err); + struct fuse_write_out out; + struct handle *h = id_to_ptr(req->fh); + int res; + + TRACE("[%d] WRITE %p(%d) %u@%llu\n", handler->token, + h, h->fd, req->size, req->offset); + res = pwrite64(h->fd, buffer, req->size, req->offset); + if (res < 0) { + return -errno; } - write(fuse->fd, &hdr, sizeof(hdr)); + out.size = res; + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return NO_STATUS; } -void fuse_reply(struct fuse *fuse, __u64 unique, void *data, int len) +static int handle_statfs(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr) { - struct fuse_out_header hdr; - struct iovec vec[2]; + char path[PATH_MAX]; + struct statfs stat; + struct fuse_statfs_out out; int res; - hdr.len = len + sizeof(hdr); - hdr.error = 0; - hdr.unique = unique; + pthread_mutex_lock(&fuse->lock); + TRACE("[%d] STATFS\n", handler->token); + res = get_node_path_locked(&fuse->root, path, sizeof(path)); + pthread_mutex_unlock(&fuse->lock); + if (res < 0) { + return -ENOENT; + } + if (statfs(fuse->root.name, &stat) < 0) { + return -errno; + } + memset(&out, 0, sizeof(out)); + out.st.blocks = stat.f_blocks; + out.st.bfree = stat.f_bfree; + out.st.bavail = stat.f_bavail; + out.st.files = stat.f_files; + out.st.ffree = stat.f_ffree; + out.st.bsize = stat.f_bsize; + out.st.namelen = stat.f_namelen; + out.st.frsize = stat.f_frsize; + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return NO_STATUS; +} - vec[0].iov_base = &hdr; - vec[0].iov_len = sizeof(hdr); - vec[1].iov_base = data; - vec[1].iov_len = len; +static int handle_release(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_release_in* req) +{ + struct handle *h = id_to_ptr(req->fh); - res = writev(fuse->fd, vec, 2); + TRACE("[%d] RELEASE %p(%d)\n", handler->token, h, h->fd); + close(h->fd); + free(h); + return 0; +} + +static int handle_fsync(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_fsync_in* req) +{ + int is_data_sync = req->fsync_flags & 1; + struct handle *h = id_to_ptr(req->fh); + int res; + + TRACE("[%d] FSYNC %p(%d) is_data_sync=%d\n", handler->token, + h, h->fd, is_data_sync); + res = is_data_sync ? fdatasync(h->fd) : fsync(h->fd); if (res < 0) { - ERROR("*** REPLY FAILED *** %d\n", errno); + return -errno; } + return 0; } -void lookup_entry(struct fuse *fuse, struct node *node, - const char *name, __u64 unique) +static int handle_flush(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr) { - struct fuse_entry_out out; - - memset(&out, 0, sizeof(out)); + TRACE("[%d] FLUSH\n", handler->token); + return 0; +} + +static int handle_opendir(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_open_in* req) +{ + struct node* node; + char path[PATH_MAX]; + struct fuse_open_out out; + struct dirhandle *h; + + pthread_mutex_lock(&fuse->lock); + node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid, path, sizeof(path)); + TRACE("[%d] OPENDIR @ %llx (%s)\n", handler->token, + hdr->nodeid, node ? node->name : "?"); + pthread_mutex_unlock(&fuse->lock); - node = node_lookup(fuse, node, name, &out.attr); if (!node) { - fuse_status(fuse, unique, -ENOENT); - return; + return -ENOENT; } - - node->refcount++; -// fprintf(stderr,"ACQUIRE %p (%s) rc=%d\n", node, node->name, node->refcount); - out.nodeid = node->nid; - out.generation = node->gen; - out.entry_valid = 10; - out.attr_valid = 10; - - fuse_reply(fuse, unique, &out, sizeof(out)); + h = malloc(sizeof(*h)); + if (!h) { + return -ENOMEM; + } + TRACE("[%d] OPENDIR %s\n", handler->token, path); + h->d = opendir(path); + if (!h->d) { + free(h); + return -errno; + } + out.fh = ptr_to_id(h); + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return NO_STATUS; } -void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *data, unsigned len) +static int handle_readdir(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_read_in* req) { - struct node *node; - - if ((len < sizeof(*hdr)) || (hdr->len != len)) { - ERROR("malformed header\n"); - return; + char buffer[8192]; + struct fuse_dirent *fde = (struct fuse_dirent*) buffer; + struct dirent *de; + struct dirhandle *h = id_to_ptr(req->fh); + + TRACE("[%d] READDIR %p\n", handler->token, h); + if (req->offset == 0) { + /* rewinddir() might have been called above us, so rewind here too */ + TRACE("[%d] calling rewinddir()\n", handler->token); + rewinddir(h->d); + } + de = readdir(h->d); + if (!de) { + return 0; } + fde->ino = FUSE_UNKNOWN_INO; + /* increment the offset so we can detect when rewinddir() seeks back to the beginning */ + fde->off = req->offset + 1; + fde->type = de->d_type; + fde->namelen = strlen(de->d_name); + memcpy(fde->name, de->d_name, fde->namelen + 1); + fuse_reply(fuse, hdr->unique, fde, + FUSE_DIRENT_ALIGN(sizeof(struct fuse_dirent) + fde->namelen)); + return NO_STATUS; +} - len -= hdr->len; +static int handle_releasedir(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_release_in* req) +{ + struct dirhandle *h = id_to_ptr(req->fh); - if (hdr->nodeid) { - node = lookup_by_inode(fuse, hdr->nodeid); - if (!node) { - fuse_status(fuse, hdr->unique, -ENOENT); - return; - } - } else { - node = 0; - } + TRACE("[%d] RELEASEDIR %p\n", handler->token, h); + closedir(h->d); + free(h); + return 0; +} +static int handle_init(struct fuse* fuse, struct fuse_handler* handler, + const struct fuse_in_header* hdr, const struct fuse_init_in* req) +{ + struct fuse_init_out out; + + TRACE("[%d] INIT ver=%d.%d maxread=%d flags=%x\n", + handler->token, req->major, req->minor, req->max_readahead, req->flags); + out.major = FUSE_KERNEL_VERSION; + out.minor = FUSE_KERNEL_MINOR_VERSION; + out.max_readahead = req->max_readahead; + out.flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES; + out.max_background = 32; + out.congestion_threshold = 32; + out.max_write = MAX_WRITE; + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return NO_STATUS; +} + +static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler, + const struct fuse_in_header *hdr, const void *data, size_t data_len) +{ switch (hdr->opcode) { case FUSE_LOOKUP: { /* bytez[] -> entry_out */ - TRACE("LOOKUP %llx %s\n", hdr->nodeid, (char*) data); - lookup_entry(fuse, node, (char*) data, hdr->unique); - return; + const char* name = data; + return handle_lookup(fuse, handler, hdr, name); } + case FUSE_FORGET: { - struct fuse_forget_in *req = data; - TRACE("FORGET %llx (%s) #%lld\n", hdr->nodeid, node->name, req->nlookup); - /* no reply */ - while (req->nlookup--) - node_release(node); - return; + const struct fuse_forget_in *req = data; + return handle_forget(fuse, handler, hdr, req); } - case FUSE_GETATTR: { /* getattr_in -> attr_out */ - struct fuse_getattr_in *req = data; - struct fuse_attr_out out; - - TRACE("GETATTR flags=%x fh=%llx\n", req->getattr_flags, req->fh); - - memset(&out, 0, sizeof(out)); - node_get_attr(node, &out.attr); - out.attr_valid = 10; - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); - return; + case FUSE_GETATTR: { /* getattr_in -> attr_out */ + const struct fuse_getattr_in *req = data; + return handle_getattr(fuse, handler, hdr, req); } - case FUSE_SETATTR: { /* setattr_in -> attr_out */ - struct fuse_setattr_in *req = data; - struct fuse_attr_out out; - char *path, buffer[PATH_BUFFER_SIZE]; - int res = 0; - struct timespec times[2]; - - TRACE("SETATTR fh=%llx id=%llx valid=%x\n", - req->fh, hdr->nodeid, req->valid); - - /* XXX: incomplete implementation on purpose. chmod/chown - * should NEVER be implemented.*/ - - path = node_get_path(node, buffer, 0); - if (req->valid & FATTR_SIZE) - res = truncate(path, req->size); - if (res) - goto getout; - - /* Handle changing atime and mtime. If FATTR_ATIME_and FATTR_ATIME_NOW - * are both set, then set it to the current time. Else, set it to the - * time specified in the request. Same goes for mtime. Use utimensat(2) - * as it allows ATIME and MTIME to be changed independently, and has - * nanosecond resolution which fuse also has. - */ - if (req->valid & (FATTR_ATIME | FATTR_MTIME)) { - times[0].tv_nsec = UTIME_OMIT; - times[1].tv_nsec = UTIME_OMIT; - if (req->valid & FATTR_ATIME) { - if (req->valid & FATTR_ATIME_NOW) { - times[0].tv_nsec = UTIME_NOW; - } else { - times[0].tv_sec = req->atime; - times[0].tv_nsec = req->atimensec; - } - } - if (req->valid & FATTR_MTIME) { - if (req->valid & FATTR_MTIME_NOW) { - times[1].tv_nsec = UTIME_NOW; - } else { - times[1].tv_sec = req->mtime; - times[1].tv_nsec = req->mtimensec; - } - } - TRACE("Calling utimensat on %s with atime %ld, mtime=%ld\n", path, times[0].tv_sec, times[1].tv_sec); - res = utimensat(-1, path, times, 0); - } - - getout: - memset(&out, 0, sizeof(out)); - node_get_attr(node, &out.attr); - out.attr_valid = 10; - if (res) - fuse_status(fuse, hdr->unique, -errno); - else - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); - return; + case FUSE_SETATTR: { /* setattr_in -> attr_out */ + const struct fuse_setattr_in *req = data; + return handle_setattr(fuse, handler, hdr, req); } + // case FUSE_READLINK: // case FUSE_SYMLINK: case FUSE_MKNOD: { /* mknod_in, bytez[] -> entry_out */ - struct fuse_mknod_in *req = data; - char *path, buffer[PATH_BUFFER_SIZE]; - char *name = ((char*) data) + sizeof(*req); - int res; - - TRACE("MKNOD %s @ %llx\n", name, hdr->nodeid); - path = node_get_path(node, buffer, name); - - req->mode = (req->mode & (~0777)) | 0664; - res = mknod(path, req->mode, req->rdev); /* XXX perm?*/ - if (res < 0) { - fuse_status(fuse, hdr->unique, -errno); - } else { - lookup_entry(fuse, node, name, hdr->unique); - } - return; + const struct fuse_mknod_in *req = data; + const char *name = ((const char*) data) + sizeof(*req); + return handle_mknod(fuse, handler, hdr, req, name); } + case FUSE_MKDIR: { /* mkdir_in, bytez[] -> entry_out */ - struct fuse_mkdir_in *req = data; - struct fuse_entry_out out; - char *path, buffer[PATH_BUFFER_SIZE]; - char *name = ((char*) data) + sizeof(*req); - int res; - - TRACE("MKDIR %s @ %llx 0%o\n", name, hdr->nodeid, req->mode); - path = node_get_path(node, buffer, name); - - req->mode = (req->mode & (~0777)) | 0775; - res = mkdir(path, req->mode); - if (res < 0) { - fuse_status(fuse, hdr->unique, -errno); - } else { - lookup_entry(fuse, node, name, hdr->unique); - } - return; + const struct fuse_mkdir_in *req = data; + const char *name = ((const char*) data) + sizeof(*req); + return handle_mkdir(fuse, handler, hdr, req, name); } + case FUSE_UNLINK: { /* bytez[] -> */ - char *path, buffer[PATH_BUFFER_SIZE]; - int res; - TRACE("UNLINK %s @ %llx\n", (char*) data, hdr->nodeid); - path = node_get_path(node, buffer, (char*) data); - res = unlink(path); - fuse_status(fuse, hdr->unique, res ? -errno : 0); - return; + const char* name = data; + return handle_unlink(fuse, handler, hdr, name); } + case FUSE_RMDIR: { /* bytez[] -> */ - char *path, buffer[PATH_BUFFER_SIZE]; - int res; - TRACE("RMDIR %s @ %llx\n", (char*) data, hdr->nodeid); - path = node_get_path(node, buffer, (char*) data); - res = rmdir(path); - fuse_status(fuse, hdr->unique, res ? -errno : 0); - return; + const char* name = data; + return handle_rmdir(fuse, handler, hdr, name); } - case FUSE_RENAME: { /* rename_in, oldname, newname -> */ - struct fuse_rename_in *req = data; - char *oldname = ((char*) data) + sizeof(*req); - char *newname = oldname + strlen(oldname) + 1; - char *oldpath, oldbuffer[PATH_BUFFER_SIZE]; - char *newpath, newbuffer[PATH_BUFFER_SIZE]; - struct node *target; - struct node *newparent; - int res; - - TRACE("RENAME %s->%s @ %llx\n", oldname, newname, hdr->nodeid); - - target = lookup_child_by_name(node, oldname); - if (!target) { - fuse_status(fuse, hdr->unique, -ENOENT); - return; - } - oldpath = node_get_path(node, oldbuffer, oldname); - - newparent = lookup_by_inode(fuse, req->newdir); - if (!newparent) { - fuse_status(fuse, hdr->unique, -ENOENT); - return; - } - if (newparent == node) { - /* Special case for renaming a file where destination - * is same path differing only by case. - * In this case we don't want to look for a case insensitive match. - * This allows commands like "mv foo FOO" to work as expected. - */ - newpath = do_node_get_path(newparent, newbuffer, newname, NO_CASE_SENSITIVE_MATCH); - } else { - newpath = node_get_path(newparent, newbuffer, newname); - } - - if (!remove_child(node, target->nid)) { - ERROR("RENAME remove_child not found"); - fuse_status(fuse, hdr->unique, -ENOENT); - return; - } - if (!rename_node(target, newname)) { - fuse_status(fuse, hdr->unique, -ENOMEM); - return; - } - add_node_to_parent(target, newparent); - res = rename(oldpath, newpath); - TRACE("RENAME result %d\n", res); - - fuse_status(fuse, hdr->unique, res ? -errno : 0); - return; + case FUSE_RENAME: { /* rename_in, oldname, newname -> */ + const struct fuse_rename_in *req = data; + const char *old_name = ((const char*) data) + sizeof(*req); + const char *new_name = old_name + strlen(old_name) + 1; + return handle_rename(fuse, handler, hdr, req, old_name, new_name); } -// case FUSE_LINK: - case FUSE_OPEN: { /* open_in -> open_out */ - struct fuse_open_in *req = data; - struct fuse_open_out out; - char *path, buffer[PATH_BUFFER_SIZE]; - struct handle *h; - - h = malloc(sizeof(*h)); - if (!h) { - fuse_status(fuse, hdr->unique, -ENOMEM); - return; - } - path = node_get_path(node, buffer, 0); - TRACE("OPEN %llx '%s' 0%o fh=%p\n", hdr->nodeid, path, req->flags, h); - h->fd = open(path, req->flags); - if (h->fd < 0) { - ERROR("ERROR\n"); - fuse_status(fuse, hdr->unique, -errno); - free(h); - return; - } - out.fh = ptr_to_id(h); - out.open_flags = 0; - out.padding = 0; - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); - return; +// case FUSE_LINK: + case FUSE_OPEN: { /* open_in -> open_out */ + const struct fuse_open_in *req = data; + return handle_open(fuse, handler, hdr, req); } + case FUSE_READ: { /* read_in -> byte[] */ - char buffer[128 * 1024]; - struct fuse_read_in *req = data; - struct handle *h = id_to_ptr(req->fh); - int res; - TRACE("READ %p(%d) %u@%llu\n", h, h->fd, req->size, req->offset); - if (req->size > sizeof(buffer)) { - fuse_status(fuse, hdr->unique, -EINVAL); - return; - } - res = pread64(h->fd, buffer, req->size, req->offset); - if (res < 0) { - fuse_status(fuse, hdr->unique, -errno); - return; - } - fuse_reply(fuse, hdr->unique, buffer, res); - return; + const struct fuse_read_in *req = data; + return handle_read(fuse, handler, hdr, req); } + case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */ - struct fuse_write_in *req = data; - struct fuse_write_out out; - struct handle *h = id_to_ptr(req->fh); - int res; - TRACE("WRITE %p(%d) %u@%llu\n", h, h->fd, req->size, req->offset); - res = pwrite64(h->fd, ((char*) data) + sizeof(*req), req->size, req->offset); - if (res < 0) { - fuse_status(fuse, hdr->unique, -errno); - return; - } - out.size = res; - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); - goto oops; + const struct fuse_write_in *req = data; + const void* buffer = (const __u8*)data + sizeof(*req); + return handle_write(fuse, handler, hdr, req, buffer); } - case FUSE_STATFS: { /* getattr_in -> attr_out */ - struct statfs stat; - struct fuse_statfs_out out; - int res; - - TRACE("STATFS\n"); - - if (statfs(fuse->root.name, &stat)) { - fuse_status(fuse, hdr->unique, -errno); - return; - } - memset(&out, 0, sizeof(out)); - out.st.blocks = stat.f_blocks; - out.st.bfree = stat.f_bfree; - out.st.bavail = stat.f_bavail; - out.st.files = stat.f_files; - out.st.ffree = stat.f_ffree; - out.st.bsize = stat.f_bsize; - out.st.namelen = stat.f_namelen; - out.st.frsize = stat.f_frsize; - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); - return; + case FUSE_STATFS: { /* getattr_in -> attr_out */ + return handle_statfs(fuse, handler, hdr); } + case FUSE_RELEASE: { /* release_in -> */ - struct fuse_release_in *req = data; - struct handle *h = id_to_ptr(req->fh); - TRACE("RELEASE %p(%d)\n", h, h->fd); - close(h->fd); - free(h); - fuse_status(fuse, hdr->unique, 0); - return; + const struct fuse_release_in *req = data; + return handle_release(fuse, handler, hdr, req); } -// case FUSE_FSYNC: + + case FUSE_FSYNC: { + const struct fuse_fsync_in *req = data; + return handle_fsync(fuse, handler, hdr, req); + } + // case FUSE_SETXATTR: // case FUSE_GETXATTR: // case FUSE_LISTXATTR: // case FUSE_REMOVEXATTR: - case FUSE_FLUSH: - fuse_status(fuse, hdr->unique, 0); - return; - case FUSE_OPENDIR: { /* open_in -> open_out */ - struct fuse_open_in *req = data; - struct fuse_open_out out; - char *path, buffer[PATH_BUFFER_SIZE]; - struct dirhandle *h; - - h = malloc(sizeof(*h)); - if (!h) { - fuse_status(fuse, hdr->unique, -ENOMEM); - return; - } + case FUSE_FLUSH: { + return handle_flush(fuse, handler, hdr); + } - path = node_get_path(node, buffer, 0); - TRACE("OPENDIR %llx '%s'\n", hdr->nodeid, path); - h->d = opendir(path); - if (h->d == 0) { - ERROR("ERROR\n"); - fuse_status(fuse, hdr->unique, -errno); - free(h); - return; - } - out.fh = ptr_to_id(h); - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); - return; + case FUSE_OPENDIR: { /* open_in -> open_out */ + const struct fuse_open_in *req = data; + return handle_opendir(fuse, handler, hdr, req); } + case FUSE_READDIR: { - struct fuse_read_in *req = data; - char buffer[8192]; - struct fuse_dirent *fde = (struct fuse_dirent*) buffer; - struct dirent *de; - struct dirhandle *h = id_to_ptr(req->fh); - TRACE("READDIR %p\n", h); - if (req->offset == 0) { - /* rewinddir() might have been called above us, so rewind here too */ - TRACE("calling rewinddir()\n"); - rewinddir(h->d); - } - de = readdir(h->d); - if (!de) { - fuse_status(fuse, hdr->unique, 0); - return; - } - fde->ino = FUSE_UNKNOWN_INO; - /* increment the offset so we can detect when rewinddir() seeks back to the beginning */ - fde->off = req->offset + 1; - fde->type = de->d_type; - fde->namelen = strlen(de->d_name); - memcpy(fde->name, de->d_name, fde->namelen + 1); - fuse_reply(fuse, hdr->unique, fde, - FUSE_DIRENT_ALIGN(sizeof(struct fuse_dirent) + fde->namelen)); - return; + const struct fuse_read_in *req = data; + return handle_readdir(fuse, handler, hdr, req); } + case FUSE_RELEASEDIR: { /* release_in -> */ - struct fuse_release_in *req = data; - struct dirhandle *h = id_to_ptr(req->fh); - TRACE("RELEASEDIR %p\n",h); - closedir(h->d); - free(h); - fuse_status(fuse, hdr->unique, 0); - return; + const struct fuse_release_in *req = data; + return handle_releasedir(fuse, handler, hdr, req); } + // case FUSE_FSYNCDIR: case FUSE_INIT: { /* init_in -> init_out */ - struct fuse_init_in *req = data; - struct fuse_init_out out; - - TRACE("INIT ver=%d.%d maxread=%d flags=%x\n", - req->major, req->minor, req->max_readahead, req->flags); - - out.major = FUSE_KERNEL_VERSION; - out.minor = FUSE_KERNEL_MINOR_VERSION; - out.max_readahead = req->max_readahead; - out.flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES; - out.max_background = 32; - out.congestion_threshold = 32; - out.max_write = 256 * 1024; - - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); - return; + const struct fuse_init_in *req = data; + return handle_init(fuse, handler, hdr, req); } + default: { - struct fuse_out_header h; - ERROR("NOTIMPL op=%d uniq=%llx nid=%llx\n", - hdr->opcode, hdr->unique, hdr->nodeid); - - oops: - h.len = sizeof(h); - h.error = -ENOSYS; - h.unique = hdr->unique; - write(fuse->fd, &h, sizeof(h)); - break; - } - } + TRACE("[%d] NOTIMPL op=%d uniq=%llx nid=%llx\n", + handler->token, hdr->opcode, hdr->unique, hdr->nodeid); + return -ENOSYS; + } + } } -void handle_fuse_requests(struct fuse *fuse) +static void handle_fuse_requests(struct fuse_handler* handler) { - unsigned char req[256 * 1024 + 128]; - int len; - + struct fuse* fuse = handler->fuse; for (;;) { - len = read(fuse->fd, req, sizeof(req)); + ssize_t len = read(fuse->fd, + handler->request_buffer, sizeof(handler->request_buffer)); if (len < 0) { - if (errno == EINTR) - continue; - ERROR("handle_fuse_requests: errno=%d\n", errno); - return; + if (errno != EINTR) { + ERROR("[%d] handle_fuse_requests: errno=%d\n", handler->token, errno); + } + continue; + } + + if ((size_t)len < sizeof(struct fuse_in_header)) { + ERROR("[%d] request too short: len=%zu\n", handler->token, (size_t)len); + continue; + } + + const struct fuse_in_header *hdr = (void*)handler->request_buffer; + if (hdr->len != (size_t)len) { + ERROR("[%d] malformed header: len=%zu, hdr->len=%u\n", + handler->token, (size_t)len, hdr->len); + continue; + } + + const void *data = handler->request_buffer + sizeof(struct fuse_in_header); + size_t data_len = len - sizeof(struct fuse_in_header); + __u64 unique = hdr->unique; + int res = handle_fuse_request(fuse, handler, hdr, data, data_len); + + /* We do not access the request again after this point because the underlying + * buffer storage may have been reused while processing the request. */ + + if (res != NO_STATUS) { + if (res) { + TRACE("[%d] ERROR %d\n", handler->token, res); + } + fuse_status(fuse, unique, res); } - handle_fuse_request(fuse, (void*) req, (void*) (req + sizeof(struct fuse_in_header)), len); } } +static void* start_handler(void* data) +{ + struct fuse_handler* handler = data; + handle_fuse_requests(handler); + return NULL; +} + +static int ignite_fuse(struct fuse* fuse, int num_threads) +{ + struct fuse_handler* handlers; + int i; + + handlers = malloc(num_threads * sizeof(struct fuse_handler)); + if (!handlers) { + ERROR("cannot allocate storage for threads"); + return -ENOMEM; + } + + for (i = 0; i < num_threads; i++) { + handlers[i].fuse = fuse; + handlers[i].token = i; + } + + for (i = 1; i < num_threads; i++) { + pthread_t thread; + int res = pthread_create(&thread, NULL, start_handler, &handlers[i]); + if (res) { + ERROR("failed to start thread #%d, error=%d", i, res); + goto quit; + } + } + handle_fuse_requests(&handlers[0]); + ERROR("terminated prematurely"); + + /* don't bother killing all of the other threads or freeing anything, + * should never get here anyhow */ +quit: + exit(1); +} + static int usage() { - ERROR("usage: sdcard [-l -f] <path> <uid> <gid>\n\n\t-l force file names to lower case when creating new files\n\t-f fix up file system before starting (repairs bad file name case and group ownership)\n"); - return -1; + ERROR("usage: sdcard [-t<threads>] <path> <uid> <gid>\n" + " -t<threads>: specify number of threads to use, default -t%d\n" + "\n", DEFAULT_NUM_THREADS); + return 1; } -int main(int argc, char **argv) +static int run(const char* path, uid_t uid, gid_t gid, int num_threads) { - struct fuse fuse; - char opts[256]; int fd; + char opts[256]; + int res; + struct fuse fuse; + + /* cleanup from previous instance, if necessary */ + umount2(MOUNT_POINT, 2); + + fd = open("/dev/fuse", O_RDWR); + if (fd < 0){ + ERROR("cannot open fuse device (error %d)\n", errno); + return -1; + } + + snprintf(opts, sizeof(opts), + "fd=%i,rootmode=40000,default_permissions,allow_other,user_id=%d,group_id=%d", + fd, uid, gid); + + res = mount("/dev/fuse", MOUNT_POINT, "fuse", MS_NOSUID | MS_NODEV, opts); + if (res < 0) { + ERROR("cannot mount fuse filesystem (error %d)\n", errno); + goto error; + } + + res = setgid(gid); + if (res < 0) { + ERROR("cannot setgid (error %d)\n", errno); + goto error; + } + + res = setuid(uid); + if (res < 0) { + ERROR("cannot setuid (error %d)\n", errno); + goto error; + } + + fuse_init(&fuse, fd, path); + + umask(0); + res = ignite_fuse(&fuse, num_threads); + + /* we do not attempt to umount the file system here because we are no longer + * running as the root user */ + +error: + close(fd); + return res; +} + +int main(int argc, char **argv) +{ int res; const char *path = NULL; + uid_t uid = 0; + gid_t gid = 0; + int num_threads = DEFAULT_NUM_THREADS; int i; for (i = 1; i < argc; i++) { char* arg = argv[i]; - if (!path) + if (!strncmp(arg, "-t", 2)) + num_threads = strtoul(arg + 2, 0, 10); + else if (!path) path = arg; - else if (uid == -1) + else if (!uid) uid = strtoul(arg, 0, 10); - else if (gid == -1) + else if (!gid) gid = strtoul(arg, 0, 10); else { ERROR("too many arguments\n"); @@ -985,42 +1330,15 @@ int main(int argc, char **argv) ERROR("no path specified\n"); return usage(); } - if (uid <= 0 || gid <= 0) { + if (!uid || !gid) { ERROR("uid and gid must be nonzero\n"); return usage(); } - - /* cleanup from previous instance, if necessary */ - umount2(MOUNT_POINT, 2); - - fd = open("/dev/fuse", O_RDWR); - if (fd < 0){ - ERROR("cannot open fuse device (%d)\n", errno); - return -1; - } - - sprintf(opts, "fd=%i,rootmode=40000,default_permissions,allow_other," - "user_id=%d,group_id=%d", fd, uid, gid); - - res = mount("/dev/fuse", MOUNT_POINT, "fuse", MS_NOSUID | MS_NODEV, opts); - if (res < 0) { - ERROR("cannot mount fuse filesystem (%d)\n", errno); - return -1; - } - - if (setgid(gid) < 0) { - ERROR("cannot setgid!\n"); - return -1; - } - if (setuid(uid) < 0) { - ERROR("cannot setuid!\n"); - return -1; + if (num_threads < 1) { + ERROR("number of threads must be at least 1\n"); + return usage(); } - fuse_init(&fuse, fd, path); - - umask(0); - handle_fuse_requests(&fuse); - - return 0; + res = run(path, uid, gid, num_threads); + return res < 0 ? 1 : 0; } diff --git a/toolbox/dd.c b/toolbox/dd.c index c6af3ea..53a6206 100644 --- a/toolbox/dd.c +++ b/toolbox/dd.c @@ -64,7 +64,7 @@ __RCSID("$NetBSD: dd.c,v 1.37 2004/01/17 21:00:16 dbj Exp $"); #include "dd.h" -#define NO_CONV +//#define NO_CONV //#include "extern.h" void block(void); @@ -91,12 +91,9 @@ extern u_int ddflags; extern u_int files_cnt; extern int progress; extern const u_char *ctab; -extern const u_char a2e_32V[], a2e_POSIX[]; -extern const u_char e2a_32V[], e2a_POSIX[]; -extern const u_char a2ibm_32V[], a2ibm_POSIX[]; -extern u_char casetab[]; +#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) static void dd_close(void); @@ -243,42 +240,6 @@ setup(void) if ((ddflags & (C_OF | C_SEEK | C_NOTRUNC)) == (C_OF | C_SEEK)) (void)ftruncate(out.fd, (off_t)out.offset * out.dbsz); - /* - * If converting case at the same time as another conversion, build a - * table that does both at once. If just converting case, use the - * built-in tables. - */ - if (ddflags & (C_LCASE|C_UCASE)) { -#ifdef NO_CONV - /* Should not get here, but just in case... */ - fprintf(stderr, "case conv and -DNO_CONV\n"); - exit(1); - /* NOTREACHED */ -#else /* NO_CONV */ - u_int cnt; - - if (ddflags & C_ASCII || ddflags & C_EBCDIC) { - if (ddflags & C_LCASE) { - for (cnt = 0; cnt < 0377; ++cnt) - casetab[cnt] = tolower(ctab[cnt]); - } else { - for (cnt = 0; cnt < 0377; ++cnt) - casetab[cnt] = toupper(ctab[cnt]); - } - } else { - if (ddflags & C_LCASE) { - for (cnt = 0; cnt < 0377; ++cnt) - casetab[cnt] = tolower(cnt); - } else { - for (cnt = 0; cnt < 0377; ++cnt) - casetab[cnt] = toupper(cnt); - } - } - - ctab = casetab; -#endif /* NO_CONV */ - } - (void)gettimeofday(&st.start, NULL); /* Statistics timestamp. */ } @@ -796,6 +757,9 @@ def(void) void def_close(void) { + if (ddflags & C_FDATASYNC) { + fdatasync(out.fd); + } /* Just update the count, everything is already in the buffer. */ if (in.dbcnt) @@ -1301,21 +1265,14 @@ static const struct conv { u_int set, noset; const u_char *ctab; } clist[] = { - { "ascii", C_ASCII, C_EBCDIC, e2a_POSIX }, { "block", C_BLOCK, C_UNBLOCK, NULL }, - { "ebcdic", C_EBCDIC, C_ASCII, a2e_POSIX }, - { "ibm", C_EBCDIC, C_ASCII, a2ibm_POSIX }, - { "lcase", C_LCASE, C_UCASE, NULL }, + { "fdatasync", C_FDATASYNC, 0, NULL }, { "noerror", C_NOERROR, 0, NULL }, { "notrunc", C_NOTRUNC, 0, NULL }, - { "oldascii", C_ASCII, C_EBCDIC, e2a_32V }, - { "oldebcdic", C_EBCDIC, C_ASCII, a2e_32V }, - { "oldibm", C_EBCDIC, C_ASCII, a2ibm_32V }, { "osync", C_OSYNC, C_BS, NULL }, { "sparse", C_SPARSE, 0, NULL }, { "swab", C_SWAB, 0, NULL }, { "sync", C_SYNC, 0, NULL }, - { "ucase", C_UCASE, C_LCASE, NULL }, { "unblock", C_UNBLOCK, C_BLOCK, NULL }, /* If you add items to this table, be sure to add the * conversions to the C_BS check in the jcl routine above. diff --git a/toolbox/dd.h b/toolbox/dd.h index cca1024..89f2833 100644 --- a/toolbox/dd.h +++ b/toolbox/dd.h @@ -91,3 +91,4 @@ typedef struct { #define C_UNBLOCK 0x80000 #define C_OSYNC 0x100000 #define C_SPARSE 0x200000 +#define C_FDATASYNC 0x400000 |