diff options
author | PaulK <contact@paulk.fr> | 2012-04-07 15:58:09 +0200 |
---|---|---|
committer | PaulK <contact@paulk.fr> | 2012-04-07 15:58:09 +0200 |
commit | 901efaa4660df6cd547a92f919a19c9e1578d7c5 (patch) | |
tree | dd31a34ab184affd1404c2afc8a72c65c1771db5 | |
download | adb-autotools-901efaa4660df6cd547a92f919a19c9e1578d7c5.tar.gz adb-autotools-901efaa4660df6cd547a92f919a19c9e1578d7c5.tar.bz2 adb-autotools-901efaa4660df6cd547a92f919a19c9e1578d7c5.zip |
Initial commit: added autotoolized adb (from Android 2.3.7 tree)
153 files changed, 38432 insertions, 0 deletions
@@ -0,0 +1 @@ +The Android Open Source Project
\ No newline at end of file @@ -0,0 +1,23 @@ + Copyright 2008, The Android Open Source Project + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Google Inc. 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 Google Inc. ``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 Google Inc. 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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,302 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006, 2007, 2008, 2009 Free Software Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 6. Often, you can also type `make uninstall' to remove the installed + files again. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `<wchar.h>' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *Note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..4521681 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,10 @@ +SUBDIRS = libzipfile libcutils adb + +MAINTAINERCLEANFILES = Makefile.in aclocal.m4 config.guess \ + config.h.in config.sub configure install-sh \ + ltconfig ltmain.sh missing mkinstalldirs \ + stamp-h.in acconfig.h depcomp + +EXTRA_DIST = README AUTHORS COPYING autogen.sh + + diff --git a/adb/Android.mk b/adb/Android.mk new file mode 100644 index 0000000..eafd36d --- /dev/null +++ b/adb/Android.mk @@ -0,0 +1,166 @@ +# Copyright 2005 The Android Open Source Project +# +# Android.mk for adb +# + +LOCAL_PATH:= $(call my-dir) + +# adb host tool +# ========================================================= +include $(CLEAR_VARS) + +# Default to a virtual (sockets) usb interface +USB_SRCS := +EXTRA_SRCS := + +ifeq ($(HOST_OS),linux) + USB_SRCS := usb_linux.c + EXTRA_SRCS := get_my_path_linux.c + LOCAL_LDLIBS += -lrt -lpthread +endif + +ifeq ($(HOST_OS),darwin) + USB_SRCS := usb_osx.c + EXTRA_SRCS := get_my_path_darwin.c + LOCAL_LDLIBS += -lpthread -framework CoreFoundation -framework IOKit -framework Carbon +endif + +ifeq ($(HOST_OS),freebsd) + USB_SRCS := usb_libusb.c + EXTRA_SRCS := get_my_path_freebsd.c + LOCAL_LDLIBS += -lpthread -lusb +endif + +ifeq ($(HOST_OS),windows) + USB_SRCS := usb_windows.c + EXTRA_SRCS := get_my_path_windows.c + EXTRA_STATIC_LIBS := AdbWinApi + ifneq ($(strip $(USE_CYGWIN)),) + # Pure cygwin case + LOCAL_LDLIBS += -lpthread + LOCAL_C_INCLUDES += /usr/include/w32api/ddk + endif + ifneq ($(strip $(USE_MINGW)),) + # MinGW under Linux case + LOCAL_LDLIBS += -lws2_32 + USE_SYSDEPS_WIN32 := 1 + LOCAL_C_INCLUDES += /usr/i586-mingw32msvc/include/ddk + endif + LOCAL_C_INCLUDES += development/host/windows/usb/api/ +endif + +LOCAL_SRC_FILES := \ + adb.c \ + console.c \ + transport.c \ + transport_local.c \ + transport_usb.c \ + commandline.c \ + adb_client.c \ + sockets.c \ + services.c \ + file_sync_client.c \ + $(EXTRA_SRCS) \ + $(USB_SRCS) \ + utils.c \ + usb_vendors.c + + +ifneq ($(USE_SYSDEPS_WIN32),) + LOCAL_SRC_FILES += sysdeps_win32.c +else + LOCAL_SRC_FILES += fdevent.c +endif + +LOCAL_CFLAGS += -O2 -g -DADB_HOST=1 -Wall -Wno-unused-parameter +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE +LOCAL_MODULE := adb + +LOCAL_STATIC_LIBRARIES := libzipfile libunz $(EXTRA_STATIC_LIBS) +ifeq ($(USE_SYSDEPS_WIN32),) + LOCAL_STATIC_LIBRARIES += libcutils +endif + +include $(BUILD_HOST_EXECUTABLE) + +$(call dist-for-goals,droid,$(LOCAL_BUILT_MODULE)) + +ifeq ($(HOST_OS),windows) +$(LOCAL_INSTALLED_MODULE): \ + $(HOST_OUT_EXECUTABLES)/AdbWinApi.dll \ + $(HOST_OUT_EXECUTABLES)/AdbWinUsbApi.dll +endif + + +# adbd device daemon +# ========================================================= + +# build adbd in all non-simulator builds +BUILD_ADBD := false +ifneq ($(TARGET_SIMULATOR),true) + BUILD_ADBD := true +endif + +# build adbd for the Linux simulator build +# so we can use it to test the adb USB gadget driver on x86 +#ifeq ($(HOST_OS),linux) +# BUILD_ADBD := true +#endif + + +ifeq ($(BUILD_ADBD),true) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + adb.c \ + fdevent.c \ + transport.c \ + transport_local.c \ + transport_usb.c \ + sockets.c \ + services.c \ + file_sync_service.c \ + jdwp_service.c \ + framebuffer_service.c \ + remount_service.c \ + usb_linux_client.c \ + log_service.c \ + utils.c + +LOCAL_CFLAGS := -O2 -g -DADB_HOST=0 -Wall -Wno-unused-parameter +LOCAL_CFLAGS += -D_XOPEN_SOURCE -D_GNU_SOURCE + +# TODO: This should probably be board specific, whether or not the kernel has +# the gadget driver; rather than relying on the architecture type. +ifeq ($(TARGET_ARCH),arm) +LOCAL_CFLAGS += -DANDROID_GADGET=1 +endif + +ifneq ($(TARGET_RECOVERY_PRE_COMMAND),) + LOCAL_CFLAGS += -DRECOVERY_PRE_COMMAND='$(TARGET_RECOVERY_PRE_COMMAND)' +endif + +ifeq ($(BOARD_USE_SCREENCAP),true) + LOCAL_CFLAGS += -DUSE_SCREENCAP +endif + +ifeq ($(BOARD_ALWAYS_INSECURE),true) + LOCAL_CFLAGS += -DBOARD_ALWAYS_INSECURE +endif + +LOCAL_MODULE := adbd + +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT_SBIN) +LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_SBIN_UNSTRIPPED) + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_STATIC_LIBRARIES := libcutils + LOCAL_LDLIBS += -lpthread + include $(BUILD_HOST_EXECUTABLE) +else + LOCAL_STATIC_LIBRARIES := libcutils libc + include $(BUILD_EXECUTABLE) +endif + +endif diff --git a/adb/MODULE_LICENSE_APACHE2 b/adb/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/adb/MODULE_LICENSE_APACHE2 diff --git a/adb/Makefile.am b/adb/Makefile.am new file mode 100644 index 0000000..a603860 --- /dev/null +++ b/adb/Makefile.am @@ -0,0 +1,34 @@ +AUTOMAKE_OPTIONS = 1.4 foreign +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I. \ +-I$(top_builddir) \ +-I$(top_srcdir) \ +-I$(top_srcdir)/include \ +-I$(top_builddir)/include + +bin_PROGRAMS = adb + +adb_SOURCES = \ +adb.c \ +console.c \ +transport.c \ +transport_local.c \ +transport_usb.c \ +commandline.c \ +adb_client.c \ +sockets.c \ +services.c \ +fdevent.c \ +file_sync_client.c \ +usb_linux.c \ +get_my_path_linux.c \ +utils.c \ +usb_vendors.c + +adb_LDADD = $(top_builddir)/libzipfile/libzipfile.la \ + $(top_builddir)/libcutils/libcutils.la +adb_CFLAGS = -DADB_HOST=1 -DHAVE_FORKEXEC +adb_LDFLAGS = -lpthread + diff --git a/adb/NOTICE b/adb/NOTICE new file mode 100644 index 0000000..9ffcc08 --- /dev/null +++ b/adb/NOTICE @@ -0,0 +1,191 @@ + + Copyright (c) 2006-2009, The Android Open Source Project + Copyright 2006, Brian Swetland <swetland@frotz.net> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/adb/OVERVIEW.TXT b/adb/OVERVIEW.TXT new file mode 100644 index 0000000..c40695a --- /dev/null +++ b/adb/OVERVIEW.TXT @@ -0,0 +1,139 @@ +Implementation notes regarding ADB. + +I. General Overview: + +The Android Debug Bridge (ADB) is used to: + +- keep track of all Android devices and emulators instances + connected to or running on a given host developer machine + +- implement various control commands (e.g. "adb shell", "adb pull", etc..) + for the benefit of clients (command-line users, or helper programs like + DDMS). These commands are what is called a 'service' in ADB. + +As a whole, everything works through the following components: + + 1. The ADB server + + This is a background process that runs on the host machine. Its purpose + if to sense the USB ports to know when devices are attached/removed, + as well as when emulator instances start/stop. + + It thus maintains a list of "connected devices" and assigns a 'state' + to each one of them: OFFLINE, BOOTLOADER, RECOVERY or ONLINE (more on + this below). + + The ADB server is really one giant multiplexing loop whose purpose is + to orchestrate the exchange of data (packets, really) between clients, + services and devices. + + + 2. The ADB daemon (adbd) + + The 'adbd' program runs as a background process within an Android device + or emulated system. Its purpose is to connect to the ADB server + (through USB for devices, through TCP for emulators) and provide a + few services for clients that run on the host. + + The ADB server considers that a device is ONLINE when it has successfully + connected to the adbd program within it. Otherwise, the device is OFFLINE, + meaning that the ADB server detected a new device/emulator, but could not + connect to the adbd daemon. + + the BOOTLOADER and RECOVERY states correspond to alternate states of + devices when they are in the bootloader or recovery mode. + + 3. The ADB command-line client + + The 'adb' command-line program is used to run adb commands from a shell + or a script. It first tries to locate the ADB server on the host machine, + and will start one automatically if none is found. + + then, the client sends its service requests to the ADB server. It doesn't + need to know. + + Currently, a single 'adb' binary is used for both the server and client. + this makes distribution and starting the server easier. + + + 4. Services + + There are essentially two kinds of services that a client can talk to. + + Host Services: + these services run within the ADB Server and thus do not need to + communicate with a device at all. A typical example is "adb devices" + which is used to return the list of currently known devices and their + state. They are a few couple other services though. + + Local Services: + these services either run within the adbd daemon, or are started by + it on the device. The ADB server is used to multiplex streams + between the client and the service running in adbd. In this case + its role is to initiate the connection, then of being a pass-through + for the data. + + +II. Protocol details: + + 1. Client <-> Server protocol: + + This details the protocol used between ADB clients and the ADB + server itself. The ADB server listens on TCP:localhost:5037. + + A client sends a request using the following format: + + 1. A 4-byte hexadecimal string giving the length of the payload + 2. Followed by the payload itself. + + For example, to query the ADB server for its internal version number, + the client will do the following: + + 1. Connect to tcp:localhost:5037 + 2. Send the string "000Chost:version" to the corresponding socket + + The 'host:' prefix is used to indicate that the request is addressed + to the server itself (we will talk about other kinds of requests later). + The content length is encoded in ASCII for easier debugging. + + The server should answer a request with one of the following: + + 1. For success, the 4-byte "OKAY" string + + 2. For failure, the 4-byte "FAIL" string, followed by a + 4-byte hex length, followed by a string giving the reason + for failure. + + 3. As a special exception, for 'host:version', a 4-byte + hex string corresponding to the server's internal version number + + Note that the connection is still alive after an OKAY, which allows the + client to make other requests. But in certain cases, an OKAY will even + change the state of the connection. + + For example, the case of the 'host:transport:<serialnumber>' request, + where '<serialnumber>' is used to identify a given device/emulator; after + the "OKAY" answer, all further requests made by the client will go + directly to the corresponding adbd daemon. + + The file SERVICES.TXT lists all services currently implemented by ADB. + + + 2. Transports: + + An ADB transport models a connection between the ADB server and one device + or emulator. There are currently two kinds of transports: + + - USB transports, for physical devices through USB + + - Local transports, for emulators running on the host, connected to + the server through TCP + + In theory, it should be possible to write a local transport that proxies + a connection between an ADB server and a device/emulator connected to/ + running on another machine. This hasn't been done yet though. + + Each transport can carry one or more multiplexed streams between clients + and the device/emulator they point to. The ADB server must handle + unexpected transport disconnections (e.g. when a device is physically + unplugged) properly. diff --git a/adb/SERVICES.TXT b/adb/SERVICES.TXT new file mode 100644 index 0000000..be4d50b --- /dev/null +++ b/adb/SERVICES.TXT @@ -0,0 +1,236 @@ +This file tries to document all requests a client can make +to the ADB server of an adbd daemon. See the OVERVIEW.TXT document +to understand what's going on here. + +HOST SERVICES: + +host:version + Ask the ADB server for its internal version number. + + As a special exception, the server will respond with a 4-byte + hex string corresponding to its internal version number, without + any OKAY or FAIL. + +host:kill + Ask the ADB server to quit immediately. This is used when the + ADB client detects that an obsolete server is running after an + upgrade. + +host:devices + Ask to return the list of available Android devices and their + state. After the OKAY, this is followed by a 4-byte hex len, + and a string that will be dumped as-is by the client, then + the connection is closed + +host:track-devices + This is a variant of host:devices which doesn't close the + connection. Instead, a new device list description is sent + each time a device is added/removed or the state of a given + device changes (hex4 + content). This allows tools like DDMS + to track the state of connected devices in real-time without + polling the server repeatedly. + +host:emulator:<port> + This is a special query that is sent to the ADB server when a + new emulator starts up. <port> is a decimal number corresponding + to the emulator's ADB control port, i.e. the TCP port that the + emulator will forward automatically to the adbd daemon running + in the emulator system. + + This mechanism allows the ADB server to know when new emulator + instances start. + +host:transport:<serial-number> + Ask to switch the connection to the device/emulator identified by + <serial-number>. After the OKAY response, every client request will + be sent directly to the adbd daemon running on the device. + (Used to implement the -s option) + +host:transport-usb + Ask to switch the connection to one device connected through USB + to the host machine. This will fail if there are more than one such + devices. (Used to implement the -d convenience option) + +host:transport-local + Ask to switch the connection to one emulator connected through TCP. + This will fail if there is more than one such emulator instance + running. (Used to implement the -e convenience option) + +host:transport-any + Another host:transport variant. Ask to switch the connection to + either the device or emulator connect to/running on the host. + Will fail if there is more than one such device/emulator available. + (Used when neither -s, -d or -e are provided) + +host-serial:<serial-number>:<request> + This is a special form of query, where the 'host-serial:<serial-number>:' + prefix can be used to indicate that the client is asking the ADB server + for information related to a specific device. <request> can be in one + of the format described below. + +host-usb:<request> + A variant of host-serial used to target the single USB device connected + to the host. This will fail if there is none or more than one. + +host-local:<request> + A variant of host-serial used to target the single emulator instance + running on the host. This will fail if there is none or more than one. + +host:<request> + When asking for information related to a device, 'host:' can also be + interpreted as 'any single device or emulator connected to/running on + the host'. + +<host-prefix>:get-product + XXX + +<host-prefix>:get-serialno + Returns the serial number of the corresponding device/emulator. + Note that emulator serial numbers are of the form "emulator-5554" + +<host-prefix>:get-state + Returns the state of a given device as a string. + +<host-prefix>:forward:<local>;<remote> + Asks the ADB server to forward local connections from <local> + to the <remote> address on a given device. + + There, <host-prefix> can be one of the + host-serial/host-usb/host-local/host prefixes as described previously + and indicates which device/emulator to target. + + the format of <local> is one of: + + tcp:<port> -> TCP connection on localhost:<port> + local:<path> -> Unix local domain socket on <path> + + the format of <remote> is one of: + + tcp:<port> -> TCP localhost:<port> on device + local:<path> -> Unix local domain socket on device + jdwp:<pid> -> JDWP thread on VM process <pid> + + or even any one of the local services described below. + + + +LOCAL SERVICES: + +All the queries below assumed that you already switched the transport +to a real device, or that you have used a query prefix as described +above. + +shell:command arg1 arg2 ... + Run 'command arg1 arg2 ...' in a shell on the device, and return + its output and error streams. Note that arguments must be separated + by spaces. If an argument contains a space, it must be quoted with + double-quotes. Arguments cannot contain double quotes or things + will go very wrong. + + Note that this is the non-interactive version of "adb shell" + +shell: + Start an interactive shell session on the device. Redirect + stdin/stdout/stderr as appropriate. Note that the ADB server uses + this to implement "adb shell", but will also cook the input before + sending it to the device (see interactive_shell() in commandline.c) + +remount: + Ask adbd to remount the device's filesystem in read-write mode, + instead of read-only. This is usually necessary before performing + an "adb sync" or "adb push" request. + + This request may not succeed on certain builds which do not allow + that. + +dev:<path> + Opens a device file and connects the client directly to it for + read/write purposes. Useful for debugging, but may require special + privileges and thus may not run on all devices. <path> is a full + path from the root of the filesystem. + +tcp:<port> + Tries to connect to tcp port <port> on localhost. + +tcp:<port>:<server-name> + Tries to connect to tcp port <port> on machine <server-name> from + the device. This can be useful to debug some networking/proxy + issues that can only be revealed on the device itself. + +local:<path> + Tries to connect to a Unix domain socket <path> on the device + +localreserved:<path> +localabstract:<path> +localfilesystem:<path> + Variants of local:<path> that are used to access other Android + socket namespaces. + +log:<name> + Opens one of the system logs (/dev/log/<name>) and allows the client + to read them directly. Used to implement 'adb logcat'. The stream + will be read-only for the client. + +framebuffer: + This service is used to send snapshots of the framebuffer to a client. + It requires sufficient privileges but works as follow: + + After the OKAY, the service sends 16-byte binary structure + containing the following fields (little-endian format): + + depth: uint32_t: framebuffer depth + size: uint32_t: framebuffer size in bytes + width: uint32_t: framebuffer width in pixels + height: uint32_t: framebuffer height in pixels + + With the current implementation, depth is always 16, and + size is always width*height*2 + + Then, each time the client wants a snapshot, it should send + one byte through the channel, which will trigger the service + to send it 'size' bytes of framebuffer data. + + If the adbd daemon doesn't have sufficient privileges to open + the framebuffer device, the connection is simply closed immediately. + +dns:<server-name> + This service is an exception because it only runs within the ADB server. + It is used to implement USB networking, i.e. to provide a network connection + to the device through the host machine (note: this is the exact opposite of + network tethering). + + It is used to perform a gethostbyname(<address>) on the host and return + the corresponding IP address as a 4-byte string. + +recover:<size> + This service is used to upload a recovery image to the device. <size> + must be a number corresponding to the size of the file. The service works + by: + + - creating a file named /tmp/update + - reading 'size' bytes from the client and writing them to /tmp/update + - when everything is read successfully, create a file named /tmp/update.start + + This service can only work when the device is in recovery mode. Otherwise, + the /tmp directory doesn't exist and the connection will be closed immediately. + +jdwp:<pid> + Connects to the JDWP thread running in the VM of process <pid>. + +track-jdwp + This is used to send the list of JDWP pids periodically to the client. + The format of the returned data is the following: + + <hex4>: the length of all content as a 4-char hexadecimal string + <content>: a series of ASCII lines of the following format: + <pid> "\n" + + This service is used by DDMS to know which debuggable processes are running + on the device/emulator. + + Note that there is no single-shot service to retrieve the list only once. + +sync: + This starts the file synchronisation service, used to implement "adb push" + and "adb pull". Since this service is pretty complex, it will be detailed + in a companion document named SYNC.TXT diff --git a/adb/adb.c b/adb/adb.c new file mode 100644 index 0000000..e2e1f95 --- /dev/null +++ b/adb/adb.c @@ -0,0 +1,1282 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define TRACE_TAG TRACE_ADB + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/time.h> + +#include "sysdeps.h" +#include "adb.h" + +#if !ADB_HOST +#include <private/android_filesystem_config.h> +#include <linux/capability.h> +#include <linux/prctl.h> +#else +#include "usb_vendors.h" +#endif + + +int HOST = 0; + +static const char *adb_device_banner = "device"; + +void fatal(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(-1); +} + +void fatal_errno(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "error: %s: ", strerror(errno)); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(-1); +} + +int adb_trace_mask; + +/* read a comma/space/colum/semi-column separated list of tags + * from the ADB_TRACE environment variable and build the trace + * mask from it. note that '1' and 'all' are special cases to + * enable all tracing + */ +void adb_trace_init(void) +{ + const char* p = getenv("ADB_TRACE"); + const char* q; + + static const struct { + const char* tag; + int flag; + } tags[] = { + { "1", 0 }, + { "all", 0 }, + { "adb", TRACE_ADB }, + { "sockets", TRACE_SOCKETS }, + { "packets", TRACE_PACKETS }, + { "rwx", TRACE_RWX }, + { "usb", TRACE_USB }, + { "sync", TRACE_SYNC }, + { "sysdeps", TRACE_SYSDEPS }, + { "transport", TRACE_TRANSPORT }, + { "jdwp", TRACE_JDWP }, + { NULL, 0 } + }; + + if (p == NULL) + return; + + /* use a comma/column/semi-colum/space separated list */ + while (*p) { + int len, tagn; + + q = strpbrk(p, " ,:;"); + if (q == NULL) { + q = p + strlen(p); + } + len = q - p; + + for (tagn = 0; tags[tagn].tag != NULL; tagn++) + { + int taglen = strlen(tags[tagn].tag); + + if (len == taglen && !memcmp(tags[tagn].tag, p, len) ) + { + int flag = tags[tagn].flag; + if (flag == 0) { + adb_trace_mask = ~0; + return; + } + adb_trace_mask |= (1 << flag); + break; + } + } + p = q; + if (*p) + p++; + } +} + + +apacket *get_apacket(void) +{ + apacket *p = malloc(sizeof(apacket)); + if(p == 0) fatal("failed to allocate an apacket"); + memset(p, 0, sizeof(apacket) - MAX_PAYLOAD); + return p; +} + +void put_apacket(apacket *p) +{ + free(p); +} + +void handle_online(void) +{ + D("adb: online\n"); +} + +void handle_offline(atransport *t) +{ + D("adb: offline\n"); + //Close the associated usb + run_transport_disconnects(t); +} + +#if TRACE_PACKETS +#define DUMPMAX 32 +void print_packet(const char *label, apacket *p) +{ + char *tag; + char *x; + unsigned count; + + switch(p->msg.command){ + case A_SYNC: tag = "SYNC"; break; + case A_CNXN: tag = "CNXN" ; break; + case A_OPEN: tag = "OPEN"; break; + case A_OKAY: tag = "OKAY"; break; + case A_CLSE: tag = "CLSE"; break; + case A_WRTE: tag = "WRTE"; break; + default: tag = "????"; break; + } + + fprintf(stderr, "%s: %s %08x %08x %04x \"", + label, tag, p->msg.arg0, p->msg.arg1, p->msg.data_length); + count = p->msg.data_length; + x = (char*) p->data; + if(count > DUMPMAX) { + count = DUMPMAX; + tag = "\n"; + } else { + tag = "\"\n"; + } + while(count-- > 0){ + if((*x >= ' ') && (*x < 127)) { + fputc(*x, stderr); + } else { + fputc('.', stderr); + } + x++; + } + fprintf(stderr, tag); +} +#endif + +static void send_ready(unsigned local, unsigned remote, atransport *t) +{ + D("Calling send_ready \n"); + apacket *p = get_apacket(); + p->msg.command = A_OKAY; + p->msg.arg0 = local; + p->msg.arg1 = remote; + send_packet(p, t); +} + +static void send_close(unsigned local, unsigned remote, atransport *t) +{ + D("Calling send_close \n"); + apacket *p = get_apacket(); + p->msg.command = A_CLSE; + p->msg.arg0 = local; + p->msg.arg1 = remote; + send_packet(p, t); +} + +static void send_connect(atransport *t) +{ + D("Calling send_connect \n"); + apacket *cp = get_apacket(); + cp->msg.command = A_CNXN; + cp->msg.arg0 = A_VERSION; + cp->msg.arg1 = MAX_PAYLOAD; + snprintf((char*) cp->data, sizeof cp->data, "%s::", + HOST ? "host" : adb_device_banner); + cp->msg.data_length = strlen((char*) cp->data) + 1; + send_packet(cp, t); +#if ADB_HOST + /* XXX why sleep here? */ + // allow the device some time to respond to the connect message + adb_sleep_ms(1000); +#endif +} + +static char *connection_state_name(atransport *t) +{ + if (t == NULL) { + return "unknown"; + } + + switch(t->connection_state) { + case CS_BOOTLOADER: + return "bootloader"; + case CS_DEVICE: + return "device"; + case CS_OFFLINE: + return "offline"; + default: + return "unknown"; + } +} + +void parse_banner(char *banner, atransport *t) +{ + char *type, *product, *end; + + D("parse_banner: %s\n", banner); + type = banner; + product = strchr(type, ':'); + if(product) { + *product++ = 0; + } else { + product = ""; + } + + /* remove trailing ':' */ + end = strchr(product, ':'); + if(end) *end = 0; + + /* save product name in device structure */ + if (t->product == NULL) { + t->product = strdup(product); + } else if (strcmp(product, t->product) != 0) { + free(t->product); + t->product = strdup(product); + } + + if(!strcmp(type, "bootloader")){ + D("setting connection_state to CS_BOOTLOADER\n"); + t->connection_state = CS_BOOTLOADER; + update_transports(); + return; + } + + if(!strcmp(type, "device")) { + D("setting connection_state to CS_DEVICE\n"); + t->connection_state = CS_DEVICE; + update_transports(); + return; + } + + if(!strcmp(type, "recovery")) { + D("setting connection_state to CS_RECOVERY\n"); + t->connection_state = CS_RECOVERY; + update_transports(); + return; + } + + t->connection_state = CS_HOST; +} + +void handle_packet(apacket *p, atransport *t) +{ + asocket *s; + + D("handle_packet() %d\n", p->msg.command); + + print_packet("recv", p); + + switch(p->msg.command){ + case A_SYNC: + if(p->msg.arg0){ + send_packet(p, t); + if(HOST) send_connect(t); + } else { + t->connection_state = CS_OFFLINE; + handle_offline(t); + send_packet(p, t); + } + return; + + case A_CNXN: /* CONNECT(version, maxdata, "system-id-string") */ + /* XXX verify version, etc */ + if(t->connection_state != CS_OFFLINE) { + t->connection_state = CS_OFFLINE; + handle_offline(t); + } + parse_banner((char*) p->data, t); + handle_online(); + if(!HOST) send_connect(t); + break; + + case A_OPEN: /* OPEN(local-id, 0, "destination") */ + if(t->connection_state != CS_OFFLINE) { + char *name = (char*) p->data; + name[p->msg.data_length > 0 ? p->msg.data_length - 1 : 0] = 0; + s = create_local_service_socket(name); + if(s == 0) { + send_close(0, p->msg.arg0, t); + } else { + s->peer = create_remote_socket(p->msg.arg0, t); + s->peer->peer = s; + send_ready(s->id, s->peer->id, t); + s->ready(s); + } + } + break; + + case A_OKAY: /* READY(local-id, remote-id, "") */ + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + if(s->peer == 0) { + s->peer = create_remote_socket(p->msg.arg0, t); + s->peer->peer = s; + } + s->ready(s); + } + } + break; + + case A_CLSE: /* CLOSE(local-id, remote-id, "") */ + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + s->close(s); + } + } + break; + + case A_WRTE: + if(t->connection_state != CS_OFFLINE) { + if((s = find_local_socket(p->msg.arg1))) { + unsigned rid = p->msg.arg0; + p->len = p->msg.data_length; + + if(s->enqueue(s, p) == 0) { + D("Enqueue the socket\n"); + send_ready(s->id, rid, t); + } + return; + } + } + break; + + default: + printf("handle_packet: what is %08x?!\n", p->msg.command); + } + + put_apacket(p); +} + +alistener listener_list = { + .next = &listener_list, + .prev = &listener_list, +}; + +static void ss_listener_event_func(int _fd, unsigned ev, void *_l) +{ + asocket *s; + + if(ev & FDE_READ) { + struct sockaddr addr; + socklen_t alen; + int fd; + + alen = sizeof(addr); + fd = adb_socket_accept(_fd, &addr, &alen); + if(fd < 0) return; + + adb_socket_setbufsize(fd, CHUNK_SIZE); + + s = create_local_socket(fd); + if(s) { + connect_to_smartsocket(s); + return; + } + + adb_close(fd); + } +} + +static void listener_event_func(int _fd, unsigned ev, void *_l) +{ + alistener *l = _l; + asocket *s; + + if(ev & FDE_READ) { + struct sockaddr addr; + socklen_t alen; + int fd; + + alen = sizeof(addr); + fd = adb_socket_accept(_fd, &addr, &alen); + if(fd < 0) return; + + s = create_local_socket(fd); + if(s) { + s->transport = l->transport; + connect_to_remote(s, l->connect_to); + return; + } + + adb_close(fd); + } +} + +static void free_listener(alistener* l) +{ + if (l->next) { + l->next->prev = l->prev; + l->prev->next = l->next; + l->next = l->prev = l; + } + + // closes the corresponding fd + fdevent_remove(&l->fde); + + if (l->local_name) + free((char*)l->local_name); + + if (l->connect_to) + free((char*)l->connect_to); + + if (l->transport) { + remove_transport_disconnect(l->transport, &l->disconnect); + } + free(l); +} + +static void listener_disconnect(void* _l, atransport* t) +{ + alistener* l = _l; + + free_listener(l); +} + +int local_name_to_fd(const char *name) +{ + int port; + + if(!strncmp("tcp:", name, 4)){ + int ret; + port = atoi(name + 4); + ret = socket_loopback_server(port, SOCK_STREAM); + return ret; + } +#ifndef HAVE_WIN32_IPC /* no Unix-domain sockets on Win32 */ + // It's non-sensical to support the "reserved" space on the adb host side + if(!strncmp(name, "local:", 6)) { + return socket_local_server(name + 6, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + } else if(!strncmp(name, "localabstract:", 14)) { + return socket_local_server(name + 14, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + } else if(!strncmp(name, "localfilesystem:", 16)) { + return socket_local_server(name + 16, + ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); + } + +#endif + printf("unknown local portname '%s'\n", name); + return -1; +} + +static int remove_listener(const char *local_name, const char *connect_to, atransport* transport) +{ + alistener *l; + + for (l = listener_list.next; l != &listener_list; l = l->next) { + if (!strcmp(local_name, l->local_name) && + !strcmp(connect_to, l->connect_to) && + l->transport && l->transport == transport) { + + listener_disconnect(l, transport); + return 0; + } + } + + return -1; +} + +static int install_listener(const char *local_name, const char *connect_to, atransport* transport) +{ + alistener *l; + + //printf("install_listener('%s','%s')\n", local_name, connect_to); + + for(l = listener_list.next; l != &listener_list; l = l->next){ + if(strcmp(local_name, l->local_name) == 0) { + char *cto; + + /* can't repurpose a smartsocket */ + if(l->connect_to[0] == '*') { + return -1; + } + + cto = strdup(connect_to); + if(cto == 0) { + return -1; + } + + //printf("rebinding '%s' to '%s'\n", local_name, connect_to); + free((void*) l->connect_to); + l->connect_to = cto; + if (l->transport != transport) { + remove_transport_disconnect(l->transport, &l->disconnect); + l->transport = transport; + add_transport_disconnect(l->transport, &l->disconnect); + } + return 0; + } + } + + if((l = calloc(1, sizeof(alistener))) == 0) goto nomem; + if((l->local_name = strdup(local_name)) == 0) goto nomem; + if((l->connect_to = strdup(connect_to)) == 0) goto nomem; + + + l->fd = local_name_to_fd(local_name); + if(l->fd < 0) { + free((void*) l->local_name); + free((void*) l->connect_to); + free(l); + printf("cannot bind '%s'\n", local_name); + return -2; + } + + close_on_exec(l->fd); + if(!strcmp(l->connect_to, "*smartsocket*")) { + fdevent_install(&l->fde, l->fd, ss_listener_event_func, l); + } else { + fdevent_install(&l->fde, l->fd, listener_event_func, l); + } + fdevent_set(&l->fde, FDE_READ); + + l->next = &listener_list; + l->prev = listener_list.prev; + l->next->prev = l; + l->prev->next = l; + l->transport = transport; + + if (transport) { + l->disconnect.opaque = l; + l->disconnect.func = listener_disconnect; + add_transport_disconnect(transport, &l->disconnect); + } + return 0; + +nomem: + fatal("cannot allocate listener"); + return 0; +} + +#ifdef HAVE_FORKEXEC +static void sigchld_handler(int n) +{ + int status; + while(waitpid(-1, &status, WNOHANG) > 0) ; +} +#endif + +#ifdef HAVE_WIN32_PROC +static BOOL WINAPI ctrlc_handler(DWORD type) +{ + exit(STATUS_CONTROL_C_EXIT); + return TRUE; +} +#endif + +static void adb_cleanup(void) +{ + usb_cleanup(); +} + +void start_logging(void) +{ +#ifdef HAVE_WIN32_PROC + char temp[ MAX_PATH ]; + FILE* fnul; + FILE* flog; + + GetTempPath( sizeof(temp) - 8, temp ); + strcat( temp, "adb.log" ); + + /* Win32 specific redirections */ + fnul = fopen( "NUL", "rt" ); + if (fnul != NULL) + stdin[0] = fnul[0]; + + flog = fopen( temp, "at" ); + if (flog == NULL) + flog = fnul; + + setvbuf( flog, NULL, _IONBF, 0 ); + + stdout[0] = flog[0]; + stderr[0] = flog[0]; + fprintf(stderr,"--- adb starting (pid %d) ---\n", getpid()); +#else + int fd; + + fd = unix_open("/dev/null", O_RDONLY); + dup2(fd, 0); + + fd = unix_open("/tmp/adb.log", O_WRONLY | O_CREAT | O_APPEND, 0640); + if(fd < 0) { + fd = unix_open("/dev/null", O_WRONLY); + } + dup2(fd, 1); + dup2(fd, 2); + fprintf(stderr,"--- adb starting (pid %d) ---\n", getpid()); +#endif +} + +#if !ADB_HOST +void start_device_log(void) +{ + int fd; + char path[PATH_MAX]; + struct tm now; + time_t t; + char value[PROPERTY_VALUE_MAX]; + + // read the trace mask from persistent property persist.adb.trace_mask + // give up if the property is not set or cannot be parsed + property_get("persist.adb.trace_mask", value, ""); + if (sscanf(value, "%x", &adb_trace_mask) != 1) + return; + + adb_mkdir("/data/adb", 0775); + tzset(); + time(&t); + localtime_r(&t, &now); + strftime(path, sizeof(path), + "/data/adb/adb-%Y-%m-%d-%H-%M-%S.txt", + &now); + fd = unix_open(path, O_WRONLY | O_CREAT | O_TRUNC, 0640); + if (fd < 0) + return; + + // redirect stdout and stderr to the log file + dup2(fd, 1); + dup2(fd, 2); + fprintf(stderr,"--- adb starting (pid %d) ---\n", getpid()); + + fd = unix_open("/dev/null", O_RDONLY); + dup2(fd, 0); +} +#endif + +#if ADB_HOST +int launch_server(int server_port) +{ +#ifdef HAVE_WIN32_PROC + /* we need to start the server in the background */ + /* we create a PIPE that will be used to wait for the server's "OK" */ + /* message since the pipe handles must be inheritable, we use a */ + /* security attribute */ + HANDLE pipe_read, pipe_write; + SECURITY_ATTRIBUTES sa; + STARTUPINFO startup; + PROCESS_INFORMATION pinfo; + char program_path[ MAX_PATH ]; + int ret; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + /* create pipe, and ensure its read handle isn't inheritable */ + ret = CreatePipe( &pipe_read, &pipe_write, &sa, 0 ); + if (!ret) { + fprintf(stderr, "CreatePipe() failure, error %ld\n", GetLastError() ); + return -1; + } + + SetHandleInformation( pipe_read, HANDLE_FLAG_INHERIT, 0 ); + + ZeroMemory( &startup, sizeof(startup) ); + startup.cb = sizeof(startup); + startup.hStdInput = GetStdHandle( STD_INPUT_HANDLE ); + startup.hStdOutput = pipe_write; + startup.hStdError = GetStdHandle( STD_ERROR_HANDLE ); + startup.dwFlags = STARTF_USESTDHANDLES; + + ZeroMemory( &pinfo, sizeof(pinfo) ); + + /* get path of current program */ + GetModuleFileName( NULL, program_path, sizeof(program_path) ); + + ret = CreateProcess( + program_path, /* program path */ + "adb fork-server server", + /* the fork-server argument will set the + debug = 2 in the child */ + NULL, /* process handle is not inheritable */ + NULL, /* thread handle is not inheritable */ + TRUE, /* yes, inherit some handles */ + DETACHED_PROCESS, /* the new process doesn't have a console */ + NULL, /* use parent's environment block */ + NULL, /* use parent's starting directory */ + &startup, /* startup info, i.e. std handles */ + &pinfo ); + + CloseHandle( pipe_write ); + + if (!ret) { + fprintf(stderr, "CreateProcess failure, error %ld\n", GetLastError() ); + CloseHandle( pipe_read ); + return -1; + } + + CloseHandle( pinfo.hProcess ); + CloseHandle( pinfo.hThread ); + + /* wait for the "OK\n" message */ + { + char temp[3]; + DWORD count; + + ret = ReadFile( pipe_read, temp, 3, &count, NULL ); + CloseHandle( pipe_read ); + if ( !ret ) { + fprintf(stderr, "could not read ok from ADB Server, error = %ld\n", GetLastError() ); + return -1; + } + if (count != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') { + fprintf(stderr, "ADB server didn't ACK\n" ); + return -1; + } + } +#elif defined(HAVE_FORKEXEC) + char path[PATH_MAX]; + int fd[2]; + + // set up a pipe so the child can tell us when it is ready. + // fd[0] will be parent's end, and fd[1] will get mapped to stderr in the child. + if (pipe(fd)) { + fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno); + return -1; + } + get_my_path(path, PATH_MAX); + pid_t pid = fork(); + if(pid < 0) return -1; + + if (pid == 0) { + // child side of the fork + + // redirect stderr to the pipe + // we use stderr instead of stdout due to stdout's buffering behavior. + adb_close(fd[0]); + dup2(fd[1], STDERR_FILENO); + adb_close(fd[1]); + + // child process + int result = execl(path, "adb", "fork-server", "server", NULL); + // this should not return + fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno); + } else { + // parent side of the fork + + char temp[3]; + + temp[0] = 'A'; temp[1] = 'B'; temp[2] = 'C'; + // wait for the "OK\n" message + adb_close(fd[1]); + int ret = adb_read(fd[0], temp, 3); + adb_close(fd[0]); + if (ret < 0) { + fprintf(stderr, "could not read ok from ADB Server, errno = %d\n", errno); + return -1; + } + if (ret != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') { + fprintf(stderr, "ADB server didn't ACK\n" ); + return -1; + } + + setsid(); + } +#else +#error "cannot implement background server start on this platform" +#endif + return 0; +} +#endif + +/* Constructs a local name of form tcp:port. + * target_str points to the target string, it's content will be overwritten. + * target_size is the capacity of the target string. + * server_port is the port number to use for the local name. + */ +void build_local_name(char* target_str, size_t target_size, int server_port) +{ + snprintf(target_str, target_size, "tcp:%d", server_port); +} + +int adb_main(int is_daemon, int server_port) +{ +#if !ADB_HOST + int secure = 0; + int port; + char value[PROPERTY_VALUE_MAX]; +#endif + + atexit(adb_cleanup); +#ifdef HAVE_WIN32_PROC + SetConsoleCtrlHandler( ctrlc_handler, TRUE ); +#elif defined(HAVE_FORKEXEC) + signal(SIGCHLD, sigchld_handler); + signal(SIGPIPE, SIG_IGN); +#endif + + init_transport_registration(); + + +#if ADB_HOST + HOST = 1; + usb_vendors_init(); + usb_init(); + local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT); + + char local_name[30]; + build_local_name(local_name, sizeof(local_name), server_port); + if(install_listener(local_name, "*smartsocket*", NULL)) { + exit(1); + } +#else + /* run adbd in secure mode if ro.secure is set and + ** we are not in the emulator + */ + property_get("ro.kernel.qemu", value, ""); + if (strcmp(value, "1") != 0) { + property_get("ro.secure", value, ""); + if (strcmp(value, "1") == 0) { + // don't run as root if ro.secure is set... +#ifndef BOARD_ALWAYS_INSECURE + secure = 1; +#endif + + // ... except we allow running as root in userdebug builds if the + // service.adb.root property has been set by the "adb root" command + property_get("ro.debuggable", value, ""); + if (strcmp(value, "1") == 0) { + property_get("service.adb.root", value, ""); + if (strcmp(value, "1") == 0) { + secure = 0; + } + } + } + } + + /* don't listen on a port (default 5037) if running in secure mode */ + /* don't run as root if we are running in secure mode */ + if (secure) { + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) != 0) { + exit(1); + } + + /* add extra groups: + ** AID_ADB to access the USB driver + ** AID_LOG to read system logs (adb logcat) + ** AID_INPUT to diagnose input issues (getevent) + ** AID_INET to diagnose network issues (netcfg, ping) + ** AID_GRAPHICS to access the frame buffer + ** AID_NET_BT and AID_NET_BT_ADMIN to diagnose bluetooth (hcidump) + ** AID_SDCARD_RW to allow writing to the SD card + ** AID_MOUNT to allow unmounting the SD card before rebooting + */ + gid_t groups[] = { AID_ADB, AID_LOG, AID_INPUT, AID_INET, AID_GRAPHICS, + AID_NET_BT, AID_NET_BT_ADMIN, AID_SDCARD_RW, AID_MOUNT }; + if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) { + exit(1); + } + + /* then switch user and group to "shell" */ + if (setgid(AID_SHELL) != 0) { + exit(1); + } + if (setuid(AID_SHELL) != 0) { + exit(1); + } + + /* set CAP_SYS_BOOT capability, so "adb reboot" will succeed */ + header.version = _LINUX_CAPABILITY_VERSION; + header.pid = 0; + cap.effective = cap.permitted = (1 << CAP_SYS_BOOT); + cap.inheritable = 0; + capset(&header, &cap); + + D("Local port disabled\n"); + } else { + char local_name[30]; + build_local_name(local_name, sizeof(local_name), server_port); + if(install_listener(local_name, "*smartsocket*", NULL)) { + exit(1); + } + } + + /* for the device, start the usb transport if the + ** android usb device exists and the "service.adb.tcp.port" and + ** "persist.adb.tcp.port" properties are not set. + ** Otherwise start the network transport. + */ + property_get("service.adb.tcp.port", value, ""); + if (!value[0]) + property_get("persist.adb.tcp.port", value, ""); + 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) { + // listen on USB + usb_init(); + } else { + // listen on default port + local_init(DEFAULT_ADB_LOCAL_TRANSPORT_PORT); + } + init_jdwp(); +#endif + + if (is_daemon) + { + // inform our parent that we are up and running. +#ifdef HAVE_WIN32_PROC + DWORD count; + WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), "OK\n", 3, &count, NULL ); +#elif defined(HAVE_FORKEXEC) + fprintf(stderr, "OK\n"); +#endif + start_logging(); + } + + fdevent_loop(); + + usb_cleanup(); + + return 0; +} + +#if ADB_HOST +void connect_device(char* host, char* buffer, int buffer_size) +{ + int port, fd; + char* portstr = strchr(host, ':'); + char hostbuf[100]; + char serial[100]; + + strncpy(hostbuf, host, sizeof(hostbuf) - 1); + if (portstr) { + if (portstr - host >= sizeof(hostbuf)) { + snprintf(buffer, buffer_size, "bad host name %s", host); + return; + } + // zero terminate the host at the point we found the colon + hostbuf[portstr - host] = 0; + if (sscanf(portstr + 1, "%d", &port) == 0) { + snprintf(buffer, buffer_size, "bad port number %s", portstr); + return; + } + } else { + port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT; + } + + snprintf(serial, sizeof(serial), "%s:%d", hostbuf, port); + if (find_transport(serial)) { + snprintf(buffer, buffer_size, "already connected to %s", serial); + return; + } + + fd = socket_network_client(hostbuf, port, SOCK_STREAM); + if (fd < 0) { + snprintf(buffer, buffer_size, "unable to connect to %s:%d", host, port); + return; + } + + D("client: connected on remote on fd %d\n", fd); + close_on_exec(fd); + disable_tcp_nagle(fd); + register_socket_transport(fd, serial, port, 0); + snprintf(buffer, buffer_size, "connected to %s", serial); +} + +void connect_emulator(char* port_spec, char* buffer, int buffer_size) +{ + char* port_separator = strchr(port_spec, ','); + if (!port_separator) { + snprintf(buffer, buffer_size, + "unable to parse '%s' as <console port>,<adb port>", + port_spec); + return; + } + + // Zero-terminate console port and make port_separator point to 2nd port. + *port_separator++ = 0; + int console_port = strtol(port_spec, NULL, 0); + int adb_port = strtol(port_separator, NULL, 0); + if (!(console_port > 0 && adb_port > 0)) { + *(port_separator - 1) = ','; + snprintf(buffer, buffer_size, + "Invalid port numbers: Expected positive numbers, got '%s'", + port_spec); + return; + } + + /* Check if the emulator is already known. + * Note: There's a small but harmless race condition here: An emulator not + * present just yet could be registered by another invocation right + * after doing this check here. However, local_connect protects + * against double-registration too. From here, a better error message + * can be produced. In the case of the race condition, the very specific + * error message won't be shown, but the data doesn't get corrupted. */ + atransport* known_emulator = find_emulator_transport_by_adb_port(adb_port); + if (known_emulator != NULL) { + snprintf(buffer, buffer_size, + "Emulator on port %d already registered.", adb_port); + return; + } + + /* Check if more emulators can be registered. Similar unproblematic + * race condition as above. */ + int candidate_slot = get_available_local_transport_index(); + if (candidate_slot < 0) { + snprintf(buffer, buffer_size, "Cannot accept more emulators."); + return; + } + + /* Preconditions met, try to connect to the emulator. */ + if (!local_connect_arbitrary_ports(console_port, adb_port)) { + snprintf(buffer, buffer_size, + "Connected to emulator on ports %d,%d", console_port, adb_port); + } else { + snprintf(buffer, buffer_size, + "Could not connect to emulator on ports %d,%d", + console_port, adb_port); + } +} +#endif + +int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s) +{ + atransport *transport = NULL; + char buf[4096]; + + if(!strcmp(service, "kill")) { + fprintf(stderr,"adb server killed by remote request\n"); + fflush(stdout); + adb_write(reply_fd, "OKAY", 4); + usb_cleanup(); + exit(0); + } + +#if ADB_HOST + // "transport:" is used for switching transport with a specified serial number + // "transport-usb:" is used for switching transport to the only USB transport + // "transport-local:" is used for switching transport to the only local transport + // "transport-any:" is used for switching transport to the only transport + if (!strncmp(service, "transport", strlen("transport"))) { + char* error_string = "unknown failure"; + transport_type type = kTransportAny; + + if (!strncmp(service, "transport-usb", strlen("transport-usb"))) { + type = kTransportUsb; + } else if (!strncmp(service, "transport-local", strlen("transport-local"))) { + type = kTransportLocal; + } else if (!strncmp(service, "transport-any", strlen("transport-any"))) { + type = kTransportAny; + } else if (!strncmp(service, "transport:", strlen("transport:"))) { + service += strlen("transport:"); + serial = strdup(service); + } + + transport = acquire_one_transport(CS_ANY, type, serial, &error_string); + + if (transport) { + s->transport = transport; + adb_write(reply_fd, "OKAY", 4); + } else { + sendfailmsg(reply_fd, error_string); + } + return 1; + } + + // return a list of all connected devices + if (!strcmp(service, "devices")) { + char buffer[4096]; + memset(buf, 0, sizeof(buf)); + memset(buffer, 0, sizeof(buffer)); + D("Getting device list \n"); + list_transports(buffer, sizeof(buffer)); + snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer),buffer); + D("Wrote device list \n"); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + + // add a new TCP transport, device or emulator + if (!strncmp(service, "connect:", 8)) { + char buffer[4096]; + char* host = service + 8; + if (!strncmp(host, "emu:", 4)) { + connect_emulator(host + 4, buffer, sizeof(buffer)); + } else { + connect_device(host, buffer, sizeof(buffer)); + } + // Send response for emulator and device + snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer), buffer); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + + // remove TCP transport + if (!strncmp(service, "disconnect:", 11)) { + char buffer[4096]; + memset(buffer, 0, sizeof(buffer)); + char* serial = service + 11; + if (serial[0] == 0) { + // disconnect from all TCP devices + unregister_all_tcp_transports(); + } else { + char hostbuf[100]; + // assume port 5555 if no port is specified + if (!strchr(serial, ':')) { + snprintf(hostbuf, sizeof(hostbuf) - 1, "%s:5555", serial); + serial = hostbuf; + } + atransport *t = find_transport(serial); + + if (t) { + unregister_transport(t); + } else { + snprintf(buffer, sizeof(buffer), "No such device %s", serial); + } + } + + snprintf(buf, sizeof(buf), "OKAY%04x%s",(unsigned)strlen(buffer), buffer); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + + // returns our value for ADB_SERVER_VERSION + if (!strcmp(service, "version")) { + char version[12]; + snprintf(version, sizeof version, "%04x", ADB_SERVER_VERSION); + snprintf(buf, sizeof buf, "OKAY%04x%s", (unsigned)strlen(version), version); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + + if(!strncmp(service,"get-serialno",strlen("get-serialno"))) { + char *out = "unknown"; + transport = acquire_one_transport(CS_ANY, ttype, serial, NULL); + if (transport && transport->serial) { + out = transport->serial; + } + snprintf(buf, sizeof buf, "OKAY%04x%s",(unsigned)strlen(out),out); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + // indicates a new emulator instance has started + if (!strncmp(service,"emulator:",9)) { + int port = atoi(service+9); + local_connect(port); + /* we don't even need to send a reply */ + return 0; + } +#endif // ADB_HOST + + if(!strncmp(service,"forward:",8) || !strncmp(service,"killforward:",12)) { + char *local, *remote, *err; + int r; + atransport *transport; + + int createForward = strncmp(service,"kill",4); + + local = service + (createForward ? 8 : 12); + remote = strchr(local,';'); + if(remote == 0) { + sendfailmsg(reply_fd, "malformed forward spec"); + return 0; + } + + *remote++ = 0; + if((local[0] == 0) || (remote[0] == 0) || (remote[0] == '*')){ + sendfailmsg(reply_fd, "malformed forward spec"); + return 0; + } + + transport = acquire_one_transport(CS_ANY, ttype, serial, &err); + if (!transport) { + sendfailmsg(reply_fd, err); + return 0; + } + + if (createForward) { + r = install_listener(local, remote, transport); + } else { + r = remove_listener(local, remote, transport); + } + if(r == 0) { + /* 1st OKAY is connect, 2nd OKAY is status */ + writex(reply_fd, "OKAYOKAY", 8); + return 0; + } + + if (createForward) { + sendfailmsg(reply_fd, (r == -1) ? "cannot rebind smartsocket" : "cannot bind socket"); + } else { + sendfailmsg(reply_fd, "cannot remove listener"); + } + return 0; + } + + if(!strncmp(service,"get-state",strlen("get-state"))) { + transport = acquire_one_transport(CS_ANY, ttype, serial, NULL); + char *state = connection_state_name(transport); + snprintf(buf, sizeof buf, "OKAY%04x%s",(unsigned)strlen(state),state); + writex(reply_fd, buf, strlen(buf)); + return 0; + } + return -1; +} + +#if !ADB_HOST +int recovery_mode = 0; +#endif + +int main(int argc, char **argv) +{ + adb_trace_init(); +#if ADB_HOST + adb_sysdeps_init(); + return adb_commandline(argc - 1, argv + 1); +#else + if((argc > 1) && (!strcmp(argv[1],"recovery"))) { + adb_device_banner = "recovery"; + recovery_mode = 1; + } + + start_device_log(); + return adb_main(0, DEFAULT_ADB_PORT); +#endif +} diff --git a/adb/adb.h b/adb/adb.h new file mode 100644 index 0000000..3d2a77b --- /dev/null +++ b/adb/adb.h @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __ADB_H +#define __ADB_H + +#include <limits.h> + +#define MAX_PAYLOAD 4096 + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 + +#define A_VERSION 0x01000000 // ADB protocol version + +#define ADB_VERSION_MAJOR 1 // Used for help/version information +#define ADB_VERSION_MINOR 0 // Used for help/version information + +#define ADB_SERVER_VERSION 26 // Increment this when we want to force users to start a new adb server + +typedef struct amessage amessage; +typedef struct apacket apacket; +typedef struct asocket asocket; +typedef struct alistener alistener; +typedef struct aservice aservice; +typedef struct atransport atransport; +typedef struct adisconnect adisconnect; +typedef struct usb_handle usb_handle; + +struct amessage { + unsigned command; /* command identifier constant */ + unsigned arg0; /* first argument */ + unsigned arg1; /* second argument */ + unsigned data_length; /* length of payload (0 is allowed) */ + unsigned data_check; /* checksum of data payload */ + unsigned magic; /* command ^ 0xffffffff */ +}; + +struct apacket +{ + apacket *next; + + unsigned len; + unsigned char *ptr; + + amessage msg; + unsigned char data[MAX_PAYLOAD]; +}; + +/* An asocket represents one half of a connection between a local and +** remote entity. A local asocket is bound to a file descriptor. A +** remote asocket is bound to the protocol engine. +*/ +struct asocket { + /* chain pointers for the local/remote list of + ** asockets that this asocket lives in + */ + asocket *next; + asocket *prev; + + /* the unique identifier for this asocket + */ + unsigned id; + + /* flag: set when the socket's peer has closed + ** but packets are still queued for delivery + */ + int closing; + + /* the asocket we are connected to + */ + + asocket *peer; + + /* For local asockets, the fde is used to bind + ** us to our fd event system. For remote asockets + ** these fields are not used. + */ + fdevent fde; + int fd; + + /* queue of apackets waiting to be written + */ + apacket *pkt_first; + apacket *pkt_last; + + /* enqueue is called by our peer when it has data + ** for us. It should return 0 if we can accept more + ** data or 1 if not. If we return 1, we must call + ** peer->ready() when we once again are ready to + ** receive data. + */ + int (*enqueue)(asocket *s, apacket *pkt); + + /* ready is called by the peer when it is ready for + ** us to send data via enqueue again + */ + void (*ready)(asocket *s); + + /* close is called by the peer when it has gone away. + ** we are not allowed to make any further calls on the + ** peer once our close method is called. + */ + void (*close)(asocket *s); + + /* socket-type-specific extradata */ + void *extra; + + /* A socket is bound to atransport */ + atransport *transport; +}; + + +/* the adisconnect structure is used to record a callback that +** will be called whenever a transport is disconnected (e.g. by the user) +** this should be used to cleanup objects that depend on the +** transport (e.g. remote sockets, listeners, etc...) +*/ +struct adisconnect +{ + void (*func)(void* opaque, atransport* t); + void* opaque; + adisconnect* next; + adisconnect* prev; +}; + + +/* a transport object models the connection to a remote device or emulator +** there is one transport per connected device/emulator. a "local transport" +** connects through TCP (for the emulator), while a "usb transport" through +** USB (for real devices) +** +** note that kTransportHost doesn't really correspond to a real transport +** object, it's a special value used to indicate that a client wants to +** connect to a service implemented within the ADB server itself. +*/ +typedef enum transport_type { + kTransportUsb, + kTransportLocal, + kTransportAny, + kTransportHost, +} transport_type; + +struct atransport +{ + atransport *next; + atransport *prev; + + int (*read_from_remote)(apacket *p, atransport *t); + int (*write_to_remote)(apacket *p, atransport *t); + void (*close)(atransport *t); + void (*kick)(atransport *t); + + int fd; + int transport_socket; + fdevent transport_fde; + int ref_count; + unsigned sync_token; + int connection_state; + transport_type type; + + /* usb handle or socket fd as needed */ + usb_handle *usb; + int sfd; + + /* used to identify transports for clients */ + char *serial; + char *product; + int adb_port; // Use for emulators (local transport) + + /* a list of adisconnect callbacks called when the transport is kicked */ + int kicked; + adisconnect disconnects; +}; + + +/* A listener is an entity which binds to a local port +** and, upon receiving a connection on that port, creates +** an asocket to connect the new local connection to a +** specific remote service. +** +** TODO: some listeners read from the new connection to +** determine what exact service to connect to on the far +** side. +*/ +struct alistener +{ + alistener *next; + alistener *prev; + + fdevent fde; + int fd; + + const char *local_name; + const char *connect_to; + atransport *transport; + adisconnect disconnect; +}; + + +void print_packet(const char *label, apacket *p); + +asocket *find_local_socket(unsigned id); +void install_local_socket(asocket *s); +void remove_socket(asocket *s); +void close_all_sockets(atransport *t); + +#define LOCAL_CLIENT_PREFIX "emulator-" + +asocket *create_local_socket(int fd); +asocket *create_local_service_socket(const char *destination); + +asocket *create_remote_socket(unsigned id, atransport *t); +void connect_to_remote(asocket *s, const char *destination); +void connect_to_smartsocket(asocket *s); + +void fatal(const char *fmt, ...); +void fatal_errno(const char *fmt, ...); + +void handle_packet(apacket *p, atransport *t); +void send_packet(apacket *p, atransport *t); + +void get_my_path(char *s, size_t maxLen); +int launch_server(int server_port); +int adb_main(int is_daemon, int server_port); + + +/* transports are ref-counted +** get_device_transport does an acquire on your behalf before returning +*/ +void init_transport_registration(void); +int list_transports(char *buf, size_t bufsize); +void update_transports(void); + +asocket* create_device_tracker(void); + +/* Obtain a transport from the available transports. +** If state is != CS_ANY, only transports in that state are considered. +** If serial is non-NULL then only the device with that serial will be chosen. +** If no suitable transport is found, error is set. +*/ +atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char **error_out); +void add_transport_disconnect( atransport* t, adisconnect* dis ); +void remove_transport_disconnect( atransport* t, adisconnect* dis ); +void run_transport_disconnects( atransport* t ); +void kick_transport( atransport* t ); + +/* initialize a transport object's func pointers and state */ +#if ADB_HOST +int get_available_local_transport_index(); +#endif +int init_socket_transport(atransport *t, int s, int port, int local); +void init_usb_transport(atransport *t, usb_handle *usb, int state); + +/* for MacOS X cleanup */ +void close_usb_devices(); + +/* cause new transports to be init'd and added to the list */ +void register_socket_transport(int s, const char *serial, int port, int local); + +/* these should only be used for the "adb disconnect" command */ +void unregister_transport(atransport *t); +void unregister_all_tcp_transports(); + +void register_usb_transport(usb_handle *h, const char *serial, unsigned writeable); + +/* this should only be used for transports with connection_state == CS_NOPERM */ +void unregister_usb_transport(usb_handle *usb); + +atransport *find_transport(const char *serial); +#if ADB_HOST +atransport* find_emulator_transport_by_adb_port(int adb_port); +#endif + +int service_to_fd(const char *name); +#if ADB_HOST +asocket *host_service_to_socket(const char* name, const char *serial); +#endif + +#if !ADB_HOST +int init_jdwp(void); +asocket* create_jdwp_service_socket(); +asocket* create_jdwp_tracker_service_socket(); +int create_jdwp_connection_fd(int jdwp_pid); +#endif + +#if !ADB_HOST +void framebuffer_service(int fd, void *cookie); +void log_service(int fd, void *cookie); +void remount_service(int fd, void *cookie); +char * get_log_file_path(const char * log_name); +#endif + +/* packet allocator */ +apacket *get_apacket(void); +void put_apacket(apacket *p); + +int check_header(apacket *p); +int check_data(apacket *p); + +/* convenience wrappers around read/write that will retry on +** EINTR and/or short read/write. Returns 0 on success, -1 +** on error or EOF. +*/ +int readx(int fd, void *ptr, size_t len); +int writex(int fd, const void *ptr, size_t len); + +/* define ADB_TRACE to 1 to enable tracing support, or 0 to disable it */ + +#define ADB_TRACE 1 + +/* IMPORTANT: if you change the following list, don't + * forget to update the corresponding 'tags' table in + * the adb_trace_init() function implemented in adb.c + */ +typedef enum { + TRACE_ADB = 0, + TRACE_SOCKETS, + TRACE_PACKETS, + TRACE_TRANSPORT, + TRACE_RWX, + TRACE_USB, + TRACE_SYNC, + TRACE_SYSDEPS, + TRACE_JDWP, +} AdbTrace; + +#if ADB_TRACE + + int adb_trace_mask; + + void adb_trace_init(void); + +# define ADB_TRACING ((adb_trace_mask & (1 << TRACE_TAG)) != 0) + + /* you must define TRACE_TAG before using this macro */ + #define D(...) \ + do { \ + if (ADB_TRACING) \ + fprintf(stderr, __VA_ARGS__ ); \ + } while (0) +#else +# define D(...) ((void)0) +# define ADB_TRACING 0 +#endif + + +#if !TRACE_PACKETS +#define print_packet(tag,p) do {} while (0) +#endif + +#define DEFAULT_ADB_PORT 5037 +#define DEFAULT_ADB_LOCAL_TRANSPORT_PORT 5555 + +#define ADB_CLASS 0xff +#define ADB_SUBCLASS 0x42 +#define ADB_PROTOCOL 0x1 + + +void local_init(int port); +int local_connect(int port); +int local_connect_arbitrary_ports(int console_port, int adb_port); + +/* usb host/client interface */ +void usb_init(); +void usb_cleanup(); +int usb_write(usb_handle *h, const void *data, int len); +int usb_read(usb_handle *h, void *data, int len); +int usb_close(usb_handle *h); +void usb_kick(usb_handle *h); + +/* used for USB device detection */ +#if ADB_HOST +int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol); +#endif + +unsigned host_to_le32(unsigned n); +int adb_commandline(int argc, char **argv); + +int connection_state(atransport *t); + +#define CS_ANY -1 +#define CS_OFFLINE 0 +#define CS_BOOTLOADER 1 +#define CS_DEVICE 2 +#define CS_HOST 3 +#define CS_RECOVERY 4 +#define CS_NOPERM 5 /* Insufficient permissions to communicate with the device */ + +extern int HOST; + +#define CHUNK_SIZE (64*1024) + +int sendfailmsg(int fd, const char *reason); +int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s); + +#endif diff --git a/adb/adb_client.c b/adb/adb_client.c new file mode 100644 index 0000000..882810a --- /dev/null +++ b/adb/adb_client.c @@ -0,0 +1,324 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <zipfile/zipfile.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_ADB +#include "adb_client.h" + +static transport_type __adb_transport = kTransportAny; +static const char* __adb_serial = NULL; + +static int __adb_server_port = DEFAULT_ADB_PORT; + +void adb_set_transport(transport_type type, const char* serial) +{ + __adb_transport = type; + __adb_serial = serial; +} + +void adb_set_tcp_specifics(int server_port) +{ + __adb_server_port = server_port; +} + +int adb_get_emulator_console_port(void) +{ + const char* serial = __adb_serial; + int port; + + if (serial == NULL) { + /* if no specific device was specified, we need to look at */ + /* the list of connected devices, and extract an emulator */ + /* name from it. two emulators is an error */ + char* tmp = adb_query("host:devices"); + char* p = tmp; + if(!tmp) { + printf("no emulator connected\n"); + return -1; + } + while (*p) { + char* q = strchr(p, '\n'); + if (q != NULL) + *q++ = 0; + else + q = p + strlen(p); + + if (!memcmp(p, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1)) { + if (serial != NULL) { /* more than one emulator listed */ + free(tmp); + return -2; + } + serial = p; + } + + p = q; + } + free(tmp); + + if (serial == NULL) + return -1; /* no emulator found */ + } + else { + if (memcmp(serial, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1) != 0) + return -1; /* not an emulator */ + } + + serial += sizeof(LOCAL_CLIENT_PREFIX)-1; + port = strtol(serial, NULL, 10); + return port; +} + +static char __adb_error[256] = { 0 }; + +const char *adb_error(void) +{ + return __adb_error; +} + +static int switch_socket_transport(int fd) +{ + char service[64]; + char tmp[5]; + int len; + + if (__adb_serial) + snprintf(service, sizeof service, "host:transport:%s", __adb_serial); + else { + char* transport_type = "???"; + + switch (__adb_transport) { + case kTransportUsb: + transport_type = "transport-usb"; + break; + case kTransportLocal: + transport_type = "transport-local"; + break; + case kTransportAny: + transport_type = "transport-any"; + break; + case kTransportHost: + // no switch necessary + return 0; + break; + } + + snprintf(service, sizeof service, "host:%s", transport_type); + } + len = strlen(service); + snprintf(tmp, sizeof tmp, "%04x", len); + + if(writex(fd, tmp, 4) || writex(fd, service, len)) { + strcpy(__adb_error, "write failure during connection"); + adb_close(fd); + return -1; + } + D("Switch transport in progress\n"); + + if(adb_status(fd)) { + adb_close(fd); + D("Switch transport failed\n"); + return -1; + } + D("Switch transport success\n"); + return 0; +} + +int adb_status(int fd) +{ + unsigned char buf[5]; + unsigned len; + + if(readx(fd, buf, 4)) { + strcpy(__adb_error, "protocol fault (no status)"); + return -1; + } + + if(!memcmp(buf, "OKAY", 4)) { + return 0; + } + + if(memcmp(buf, "FAIL", 4)) { + sprintf(__adb_error, + "protocol fault (status %02x %02x %02x %02x?!)", + buf[0], buf[1], buf[2], buf[3]); + return -1; + } + + if(readx(fd, buf, 4)) { + strcpy(__adb_error, "protocol fault (status len)"); + return -1; + } + buf[4] = 0; + len = strtoul((char*)buf, 0, 16); + if(len > 255) len = 255; + if(readx(fd, __adb_error, len)) { + strcpy(__adb_error, "protocol fault (status read)"); + return -1; + } + __adb_error[len] = 0; + return -1; +} + +int _adb_connect(const char *service) +{ + char tmp[5]; + int len; + int fd; + + D("_adb_connect: %s\n", service); + len = strlen(service); + if((len < 1) || (len > 1024)) { + strcpy(__adb_error, "service name too long"); + return -1; + } + snprintf(tmp, sizeof tmp, "%04x", len); + + fd = socket_loopback_client(__adb_server_port, SOCK_STREAM); + if(fd < 0) { + strcpy(__adb_error, "cannot connect to daemon"); + return -2; + } + + if (memcmp(service,"host",4) != 0 && switch_socket_transport(fd)) { + return -1; + } + + if(writex(fd, tmp, 4) || writex(fd, service, len)) { + strcpy(__adb_error, "write failure during connection"); + adb_close(fd); + return -1; + } + + if(adb_status(fd)) { + adb_close(fd); + return -1; + } + + return fd; +} + +int adb_connect(const char *service) +{ + // first query the adb server's version + int fd = _adb_connect("host:version"); + + if(fd == -2) { + fprintf(stdout,"* daemon not running. starting it now on port %d *\n", + __adb_server_port); + start_server: + if(launch_server(__adb_server_port)) { + fprintf(stderr,"* failed to start daemon *\n"); + return -1; + } else { + fprintf(stdout,"* daemon started successfully *\n"); + } + /* give the server some time to start properly and detect devices */ + adb_sleep_ms(3000); + // fall through to _adb_connect + } else { + // if server was running, check its version to make sure it is not out of date + char buf[100]; + int n; + int version = ADB_SERVER_VERSION - 1; + + // if we have a file descriptor, then parse version result + if(fd >= 0) { + if(readx(fd, buf, 4)) goto error; + + buf[4] = 0; + n = strtoul(buf, 0, 16); + if(n > (int)sizeof(buf)) goto error; + if(readx(fd, buf, n)) goto error; + adb_close(fd); + + if (sscanf(buf, "%04x", &version) != 1) goto error; + } else { + // if fd is -1, then check for "unknown host service", + // which would indicate a version of adb that does not support the version command + if (strcmp(__adb_error, "unknown host service") != 0) + return fd; + } + + if(version != ADB_SERVER_VERSION) { + printf("adb server is out of date. killing...\n"); + fd = _adb_connect("host:kill"); + adb_close(fd); + + /* XXX can we better detect its death? */ + adb_sleep_ms(2000); + goto start_server; + } + } + + // if the command is start-server, we are done. + if (!strcmp(service, "host:start-server")) + return 0; + + fd = _adb_connect(service); + if(fd == -2) { + fprintf(stderr,"** daemon still not running"); + } + + return fd; +error: + adb_close(fd); + return -1; +} + + +int adb_command(const char *service) +{ + int fd = adb_connect(service); + if(fd < 0) { + return -1; + } + + if(adb_status(fd)) { + adb_close(fd); + return -1; + } + + return 0; +} + +char *adb_query(const char *service) +{ + char buf[5]; + unsigned n; + char *tmp; + + D("adb_query: %s\n", service); + int fd = adb_connect(service); + if(fd < 0) { + fprintf(stderr,"error: %s\n", __adb_error); + return 0; + } + + if(readx(fd, buf, 4)) goto oops; + + buf[4] = 0; + n = strtoul(buf, 0, 16); + if(n > 1024) goto oops; + + tmp = malloc(n + 1); + if(tmp == 0) goto oops; + + if(readx(fd, tmp, n) == 0) { + tmp[n] = 0; + adb_close(fd); + return tmp; + } + free(tmp); + +oops: + adb_close(fd); + return 0; +} diff --git a/adb/adb_client.h b/adb/adb_client.h new file mode 100644 index 0000000..40ab189 --- /dev/null +++ b/adb/adb_client.h @@ -0,0 +1,53 @@ +#ifndef _ADB_CLIENT_H_ +#define _ADB_CLIENT_H_ + +#include "adb.h" + +/* connect to adb, connect to the named service, and return +** a valid fd for interacting with that service upon success +** or a negative number on failure +*/ +int adb_connect(const char *service); +int _adb_connect(const char *service); + +/* connect to adb, connect to the named service, return 0 if +** the connection succeeded AND the service returned OKAY +*/ +int adb_command(const char *service); + +/* connect to adb, connect to the named service, return +** a malloc'd string of its response upon success or NULL +** on failure. +*/ +char *adb_query(const char *service); + +/* Set the preferred transport to connect to. +*/ +void adb_set_transport(transport_type type, const char* serial); + +/* Set TCP specifics of the transport to use +*/ +void adb_set_tcp_specifics(int server_port); + +/* Return the console port of the currently connected emulator (if any) + * of -1 if there is no emulator, and -2 if there is more than one. + * assumes adb_set_transport() was alled previously... + */ +int adb_get_emulator_console_port(void); + +/* send commands to the current emulator instance. will fail if there + * is zero, or more than one emulator connected (or if you use -s <serial> + * with a <serial> that does not designate an emulator) + */ +int adb_send_emulator_command(int argc, char** argv); + +/* return verbose error string from last operation */ +const char *adb_error(void); + +/* read a standard adb status response (OKAY|FAIL) and +** return 0 in the event of OKAY, -1 in the event of FAIL +** or protocol error +*/ +int adb_status(int fd); + +#endif diff --git a/adb/commandline.c b/adb/commandline.c new file mode 100644 index 0000000..dcba83b --- /dev/null +++ b/adb/commandline.c @@ -0,0 +1,1272 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <limits.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <ctype.h> +#include <assert.h> + +#include "sysdeps.h" + +#ifdef HAVE_TERMIO_H +#include <termios.h> +#endif + +#define TRACE_TAG TRACE_ADB +#include "adb.h" +#include "adb_client.h" +#include "file_sync_service.h" + +enum { + IGNORE_DATA, + WIPE_DATA, + FLASH_DATA +}; + +static int do_cmd(transport_type ttype, char* serial, char *cmd, ...); + +void get_my_path(char *s, size_t maxLen); +int find_sync_dirs(const char *srcarg, + char **android_srcdir_out, char **data_srcdir_out); +int install_app(transport_type transport, char* serial, int argc, char** argv); +int uninstall_app(transport_type transport, char* serial, int argc, char** argv); + +static const char *gProductOutPath = NULL; + +static char *product_file(const char *extra) +{ + int n; + char *x; + + if (gProductOutPath == NULL) { + fprintf(stderr, "adb: Product directory not specified; " + "use -p or define ANDROID_PRODUCT_OUT\n"); + exit(1); + } + + n = strlen(gProductOutPath) + strlen(extra) + 2; + x = malloc(n); + if (x == 0) { + fprintf(stderr, "adb: Out of memory (product_file())\n"); + exit(1); + } + + snprintf(x, (size_t)n, "%s" OS_PATH_SEPARATOR_STR "%s", gProductOutPath, extra); + return x; +} + +void version(FILE * out) { + fprintf(out, "Android Debug Bridge version %d.%d.%d\n", + ADB_VERSION_MAJOR, ADB_VERSION_MINOR, ADB_SERVER_VERSION); +} + +void help() +{ + version(stderr); + + fprintf(stderr, + "\n" + " -d - directs command to the only connected USB device\n" + " returns an error if more than one USB device is present.\n" + " -e - directs command to the only running emulator.\n" + " returns an error if more than one emulator is running.\n" + " -s <serial number> - directs command to the USB device or emulator with\n" + " the given serial number. 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" + " out directory like 'out/target/product/sooner'.\n" + " If -p is not specified, the ANDROID_PRODUCT_OUT\n" + " environment variable is used, which must\n" + " be an absolute path.\n" + " devices - list all connected devices\n" + " 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" + " Port 5555 is used by default if no port number is specified.\n" + " Using this ocmmand with no additional arguments\n" + " will disconnect from all connected TCP/IP devices.\n" + "\n" + "device commands:\n" + " adb push <local> <remote> - copy file/dir to device\n" + " adb pull <remote> [<local>] - copy file/dir from device\n" + " adb sync [ <directory> ] - copy host->device only if changed\n" + " (-l means list but don't copy)\n" + " (see 'adb help all')\n" + " adb shell - run remote shell interactively\n" + " adb shell <command> - run remote shell command\n" + " adb emu <command> - run emulator console command\n" + " adb logcat [ <filter-spec> ] - View device log\n" + " adb forward <local> <remote> - forward socket connections\n" + " forward specs are one of: \n" + " tcp:<port>\n" + " localabstract:<unix domain socket name>\n" + " localreserved:<unix domain socket name>\n" + " localfilesystem:<unix domain socket name>\n" + " dev:<character device name>\n" + " jdwp:<process pid> (remote only)\n" + " adb jdwp - list PIDs of processes hosting a JDWP transport\n" + " adb install [-l] [-r] [-s] <file> - push this package file to the device and install it\n" + " ('-l' means forward-lock the app)\n" + " ('-r' means reinstall the app, keeping its data)\n" + " ('-s' means install on SD card instead of internal storage)\n" + " adb uninstall [-k] <package> - remove this app package from the device\n" + " ('-k' means keep the data and cache directories)\n" + " adb bugreport - return all information from the device\n" + " that should be included in a bug report.\n" + "\n" + " adb help - show this help message\n" + " adb version - show version num\n" + "\n" + "DATAOPTS:\n" + " (no option) - don't touch the data partition\n" + " -w - wipe the data partition\n" + " -d - flash the data partition\n" + "\n" + "scripting:\n" + " adb wait-for-device - block until device is online\n" + " adb start-server - ensure that there is a server running\n" + " adb kill-server - kill the server if it is running\n" + " adb get-state - prints: offline | bootloader | device\n" + " adb get-serialno - prints: <serial-number>\n" + " adb status-window - continuously print device status for a specified device\n" + " adb remount - remounts the /system partition on the device read-write\n" + " adb reboot [bootloader|recovery] - reboots the device, optionally into the bootloader or recovery program\n" + " adb reboot-bootloader - reboots the device into the bootloader\n" + " adb root - restarts the adbd daemon with root permissions\n" + " adb usb - restarts the adbd daemon listening on USB\n" + " adb tcpip <port> - restarts the adbd daemon listening on TCP on the specified port" + "\n" + "networking:\n" + " adb ppp <tty> [parameters] - Run PPP over USB.\n" + " Note: you should not automatically start a PPP connection.\n" + " <tty> refers to the tty for PPP stream. Eg. dev:/dev/omap_csmi_tty1\n" + " [parameters] - Eg. defaultroute debug dump local notty usepeerdns\n" + "\n" + "adb sync notes: adb sync [ <directory> ]\n" + " <localdir> can be interpreted in several ways:\n" + "\n" + " - If <directory> is not specified, both /system and /data partitions will be updated.\n" + "\n" + " - If it is \"system\" or \"data\", only the corresponding partition\n" + " is updated.\n" + "\n" + "environmental variables:\n" + " ADB_TRACE - Print debug information. A comma separated list of the following values\n" + " 1 or all, adb, sockets, packets, rwx, usb, sync, sysdeps, transport, jdwp\n" + " ANDROID_SERIAL - The serial number to connect to. -s takes priority over this if given.\n" + " ANDROID_LOG_TAGS - When used with the logcat option, only these debug tags are printed.\n" + ); +} + +int usage() +{ + help(); + return 1; +} + +#ifdef HAVE_TERMIO_H +static struct termios tio_save; + +static void stdin_raw_init(int fd) +{ + struct termios tio; + + if(tcgetattr(fd, &tio)) return; + if(tcgetattr(fd, &tio_save)) return; + + tio.c_lflag = 0; /* disable CANON, ECHO*, etc */ + + /* no timeout but request at least one character per read */ + tio.c_cc[VTIME] = 0; + tio.c_cc[VMIN] = 1; + + tcsetattr(fd, TCSANOW, &tio); + tcflush(fd, TCIFLUSH); +} + +static void stdin_raw_restore(int fd) +{ + tcsetattr(fd, TCSANOW, &tio_save); + tcflush(fd, TCIFLUSH); +} +#endif + +static void read_and_dump(int fd) +{ + char buf[4096]; + int len; + + while(fd >= 0) { + len = adb_read(fd, buf, 4096); + if(len == 0) { + break; + } + + if(len < 0) { + if(errno == EINTR) continue; + break; + } + fwrite(buf, 1, len, stdout); + fflush(stdout); + } +} + +static void *stdin_read_thread(void *x) +{ + int fd, fdi; + unsigned char buf[1024]; + int r, n; + int state = 0; + + int *fds = (int*) x; + fd = fds[0]; + fdi = fds[1]; + free(fds); + + for(;;) { + /* fdi is really the client's stdin, so use read, not adb_read here */ + r = unix_read(fdi, buf, 1024); + if(r == 0) break; + if(r < 0) { + if(errno == EINTR) continue; + break; + } + for(n = 0; n < r; n++){ + switch(buf[n]) { + case '\n': + state = 1; + break; + case '\r': + state = 1; + break; + case '~': + if(state == 1) state++; + break; + case '.': + if(state == 2) { + fprintf(stderr,"\n* disconnect *\n"); +#ifdef HAVE_TERMIO_H + stdin_raw_restore(fdi); +#endif + exit(0); + } + default: + state = 0; + } + } + r = adb_write(fd, buf, r); + if(r <= 0) { + break; + } + } + return 0; +} + +int interactive_shell(void) +{ + adb_thread_t thr; + int fdi, fd; + int *fds; + + fd = adb_connect("shell:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + fdi = 0; //dup(0); + + fds = malloc(sizeof(int) * 2); + fds[0] = fd; + fds[1] = fdi; + +#ifdef HAVE_TERMIO_H + stdin_raw_init(fdi); +#endif + adb_thread_create(&thr, stdin_read_thread, fds); + read_and_dump(fd); +#ifdef HAVE_TERMIO_H + stdin_raw_restore(fdi); +#endif + return 0; +} + + +static void format_host_command(char* buffer, size_t buflen, const char* command, transport_type ttype, const char* serial) +{ + if (serial) { + snprintf(buffer, buflen, "host-serial:%s:%s", serial, command); + } else { + const char* prefix = "host"; + if (ttype == kTransportUsb) + prefix = "host-usb"; + else if (ttype == kTransportLocal) + prefix = "host-local"; + + snprintf(buffer, buflen, "%s:%s", prefix, command); + } +} + +static void status_window(transport_type ttype, const char* serial) +{ + char command[4096]; + char *state = 0; + char *laststate = 0; + + /* silence stderr */ +#ifdef _WIN32 + /* XXX: TODO */ +#else + int fd; + fd = unix_open("/dev/null", O_WRONLY); + dup2(fd, 2); + adb_close(fd); +#endif + + format_host_command(command, sizeof command, "get-state", ttype, serial); + + for(;;) { + adb_sleep_ms(250); + + if(state) { + free(state); + state = 0; + } + + state = adb_query(command); + + if(state) { + if(laststate && !strcmp(state,laststate)){ + continue; + } else { + if(laststate) free(laststate); + laststate = strdup(state); + } + } + + printf("%c[2J%c[2H", 27, 27); + printf("Android Debug Bridge\n"); + printf("State: %s\n", state ? state : "offline"); + fflush(stdout); + } +} + +/** duplicate string and quote all \ " ( ) chars + space character. */ +static char * +dupAndQuote(const char *s) +{ + const char *ts; + size_t alloc_len; + char *ret; + char *dest; + + ts = s; + + alloc_len = 0; + + for( ;*ts != '\0'; ts++) { + alloc_len++; + if (*ts == ' ' || *ts == '"' || *ts == '\\' || *ts == '(' || *ts == ')') { + alloc_len++; + } + } + + ret = (char *)malloc(alloc_len + 1); + + ts = s; + dest = ret; + + for ( ;*ts != '\0'; ts++) { + if (*ts == ' ' || *ts == '"' || *ts == '\\' || *ts == '(' || *ts == ')') { + *dest++ = '\\'; + } + + *dest++ = *ts; + } + + *dest++ = '\0'; + + return ret; +} + +/** + * Run ppp in "notty" mode against a resource listed as the first parameter + * eg: + * + * ppp dev:/dev/omap_csmi_tty0 <ppp options> + * + */ +int ppp(int argc, char **argv) +{ +#ifdef HAVE_WIN32_PROC + fprintf(stderr, "error: adb %s not implemented on Win32\n", argv[0]); + return -1; +#else + char *adb_service_name; + pid_t pid; + int fd; + + if (argc < 2) { + fprintf(stderr, "usage: adb %s <adb service name> [ppp opts]\n", + argv[0]); + + return 1; + } + + adb_service_name = argv[1]; + + fd = adb_connect(adb_service_name); + + if(fd < 0) { + fprintf(stderr,"Error: Could not open adb service: %s. Error: %s\n", + adb_service_name, adb_error()); + return 1; + } + + pid = fork(); + + if (pid < 0) { + perror("from fork()"); + return 1; + } else if (pid == 0) { + int err; + int i; + const char **ppp_args; + + // copy args + ppp_args = (const char **) alloca(sizeof(char *) * argc + 1); + ppp_args[0] = "pppd"; + for (i = 2 ; i < argc ; i++) { + //argv[2] and beyond become ppp_args[1] and beyond + ppp_args[i - 1] = argv[i]; + } + ppp_args[i-1] = NULL; + + // child side + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + adb_close(STDERR_FILENO); + adb_close(fd); + + err = execvp("pppd", (char * const *)ppp_args); + + if (err < 0) { + perror("execing pppd"); + } + exit(-1); + } else { + // parent side + + adb_close(fd); + return 0; + } +#endif /* !HAVE_WIN32_PROC */ +} + +static int send_shellcommand(transport_type transport, char* serial, char* buf) +{ + int fd, ret; + + for(;;) { + fd = adb_connect(buf); + if(fd >= 0) + break; + fprintf(stderr,"- waiting for device -\n"); + adb_sleep_ms(1000); + do_cmd(transport, serial, "wait-for-device", 0); + } + + read_and_dump(fd); + ret = adb_close(fd); + if (ret) + perror("close"); + + return ret; +} + +static int logcat(transport_type transport, char* serial, int argc, char **argv) +{ + char buf[4096]; + + char *log_tags; + char *quoted_log_tags; + + log_tags = getenv("ANDROID_LOG_TAGS"); + quoted_log_tags = dupAndQuote(log_tags == NULL ? "" : log_tags); + + snprintf(buf, sizeof(buf), + "shell:export ANDROID_LOG_TAGS=\"\%s\" ; exec logcat", + quoted_log_tags); + + free(quoted_log_tags); + + argc -= 1; + argv += 1; + while(argc-- > 0) { + char *quoted; + + quoted = dupAndQuote (*argv++); + + strncat(buf, " ", sizeof(buf)-1); + strncat(buf, quoted, sizeof(buf)-1); + free(quoted); + } + + send_shellcommand(transport, serial, buf); + return 0; +} + +#define SENTINEL_FILE "config" OS_PATH_SEPARATOR_STR "envsetup.make" +static int top_works(const char *top) +{ + if (top != NULL && adb_is_absolute_host_path(top)) { + char path_buf[PATH_MAX]; + snprintf(path_buf, sizeof(path_buf), + "%s" OS_PATH_SEPARATOR_STR SENTINEL_FILE, top); + return access(path_buf, F_OK) == 0; + } + return 0; +} + +static char *find_top_from(const char *indir, char path_buf[PATH_MAX]) +{ + strcpy(path_buf, indir); + while (1) { + if (top_works(path_buf)) { + return path_buf; + } + char *s = adb_dirstop(path_buf); + if (s != NULL) { + *s = '\0'; + } else { + path_buf[0] = '\0'; + return NULL; + } + } +} + +static char *find_top(char path_buf[PATH_MAX]) +{ + char *top = getenv("ANDROID_BUILD_TOP"); + if (top != NULL && top[0] != '\0') { + if (!top_works(top)) { + fprintf(stderr, "adb: bad ANDROID_BUILD_TOP value \"%s\"\n", top); + return NULL; + } + } else { + top = getenv("TOP"); + if (top != NULL && top[0] != '\0') { + if (!top_works(top)) { + fprintf(stderr, "adb: bad TOP value \"%s\"\n", top); + return NULL; + } + } else { + top = NULL; + } + } + + if (top != NULL) { + /* The environment pointed to a top directory that works. + */ + strcpy(path_buf, top); + return path_buf; + } + + /* The environment didn't help. Walk up the tree from the CWD + * to see if we can find the top. + */ + char dir[PATH_MAX]; + top = find_top_from(getcwd(dir, sizeof(dir)), path_buf); + if (top == NULL) { + /* If the CWD isn't under a good-looking top, see if the + * executable is. + */ + get_my_path(dir, PATH_MAX); + top = find_top_from(dir, path_buf); + } + return top; +} + +/* <hint> may be: + * - A simple product name + * e.g., "sooner" +TODO: debug? sooner-debug, sooner:debug? + * - A relative path from the CWD to the ANDROID_PRODUCT_OUT dir + * e.g., "out/target/product/sooner" + * - An absolute path to the PRODUCT_OUT dir + * e.g., "/src/device/out/target/product/sooner" + * + * Given <hint>, try to construct an absolute path to the + * ANDROID_PRODUCT_OUT dir. + */ +static const char *find_product_out_path(const char *hint) +{ + static char path_buf[PATH_MAX]; + + if (hint == NULL || hint[0] == '\0') { + return NULL; + } + + /* If it's already absolute, don't bother doing any work. + */ + if (adb_is_absolute_host_path(hint)) { + strcpy(path_buf, hint); + return path_buf; + } + + /* If there are any slashes in it, assume it's a relative path; + * make it absolute. + */ + if (adb_dirstart(hint) != NULL) { + if (getcwd(path_buf, sizeof(path_buf)) == NULL) { + fprintf(stderr, "adb: Couldn't get CWD: %s\n", strerror(errno)); + return NULL; + } + if (strlen(path_buf) + 1 + strlen(hint) >= sizeof(path_buf)) { + fprintf(stderr, "adb: Couldn't assemble path\n"); + return NULL; + } + strcat(path_buf, OS_PATH_SEPARATOR_STR); + strcat(path_buf, hint); + return path_buf; + } + + /* It's a string without any slashes. Try to do something with it. + * + * Try to find the root of the build tree, and build a PRODUCT_OUT + * path from there. + */ + char top_buf[PATH_MAX]; + const char *top = find_top(top_buf); + if (top == NULL) { + fprintf(stderr, "adb: Couldn't find top of build tree\n"); + return NULL; + } +//TODO: if we have a way to indicate debug, look in out/debug/target/... + snprintf(path_buf, sizeof(path_buf), + "%s" OS_PATH_SEPARATOR_STR + "out" OS_PATH_SEPARATOR_STR + "target" OS_PATH_SEPARATOR_STR + "product" OS_PATH_SEPARATOR_STR + "%s", top_buf, hint); + if (access(path_buf, F_OK) < 0) { + fprintf(stderr, "adb: Couldn't find a product dir " + "based on \"-p %s\"; \"%s\" doesn't exist\n", hint, path_buf); + return NULL; + } + return path_buf; +} + +int adb_commandline(int argc, char **argv) +{ + char buf[4096]; + int no_daemon = 0; + int is_daemon = 0; + int persist = 0; + int r; + int quote; + transport_type ttype = kTransportAny; + char* serial = NULL; + char* server_port_str = NULL; + + /* If defined, this should be an absolute path to + * the directory containing all of the various system images + * for a particular product. If not defined, and the adb + * command requires this information, then the user must + * specify the path using "-p". + */ + gProductOutPath = getenv("ANDROID_PRODUCT_OUT"); + if (gProductOutPath == NULL || gProductOutPath[0] == '\0') { + gProductOutPath = NULL; + } + // TODO: also try TARGET_PRODUCT/TARGET_DEVICE as a hint + + serial = getenv("ANDROID_SERIAL"); + + /* Validate and assign the server port */ + server_port_str = getenv("ANDROID_ADB_SERVER_PORT"); + int server_port = DEFAULT_ADB_PORT; + if (server_port_str && strlen(server_port_str) > 0) { + server_port = (int) strtol(server_port_str, NULL, 0); + if (server_port <= 0) { + fprintf(stderr, + "adb: Env var ANDROID_ADB_SERVER_PORT must be a positive number. Got \"%s\"\n", + server_port_str); + return usage(); + } + } + + /* modifiers and flags */ + while(argc > 0) { + if(!strcmp(argv[0],"nodaemon")) { + no_daemon = 1; + } else if (!strcmp(argv[0], "fork-server")) { + /* this is a special flag used only when the ADB client launches the ADB Server */ + is_daemon = 1; + } else if(!strcmp(argv[0],"persist")) { + persist = 1; + } else if(!strncmp(argv[0], "-p", 2)) { + const char *product = NULL; + if (argv[0][2] == '\0') { + if (argc < 2) return usage(); + product = argv[1]; + argc--; + argv++; + } else { + product = argv[1] + 2; + } + gProductOutPath = find_product_out_path(product); + if (gProductOutPath == NULL) { + fprintf(stderr, "adb: could not resolve \"-p %s\"\n", + product); + return usage(); + } + } else if (argv[0][0]=='-' && argv[0][1]=='s') { + if (isdigit(argv[0][2])) { + serial = argv[0] + 2; + } else { + if(argc < 2 || argv[0][2] != '\0') return usage(); + serial = argv[1]; + argc--; + argv++; + } + } else if (!strcmp(argv[0],"-d")) { + ttype = kTransportUsb; + } else if (!strcmp(argv[0],"-e")) { + ttype = kTransportLocal; + } else { + /* out of recognized modifiers and flags */ + break; + } + argc--; + argv++; + } + + adb_set_transport(ttype, serial); + adb_set_tcp_specifics(server_port); + + if ((argc > 0) && (!strcmp(argv[0],"server"))) { + if (no_daemon || is_daemon) { + r = adb_main(is_daemon, server_port); + } else { + r = launch_server(server_port); + } + if(r) { + fprintf(stderr,"* could not start server *\n"); + } + return r; + } + +top: + if(argc == 0) { + return usage(); + } + + /* adb_connect() commands */ + + if(!strcmp(argv[0], "devices")) { + char *tmp; + snprintf(buf, sizeof buf, "host:%s", argv[0]); + tmp = adb_query(buf); + if(tmp) { + printf("List of devices attached \n"); + printf("%s\n", tmp); + return 0; + } else { + return 1; + } + } + + if(!strcmp(argv[0], "connect")) { + char *tmp; + if (argc != 2) { + fprintf(stderr, "Usage: adb connect <host>[:<port>]\n"); + return 1; + } + snprintf(buf, sizeof buf, "host:connect:%s", argv[1]); + tmp = adb_query(buf); + if(tmp) { + printf("%s\n", tmp); + return 0; + } else { + return 1; + } + } + + if(!strcmp(argv[0], "disconnect")) { + char *tmp; + if (argc > 2) { + fprintf(stderr, "Usage: adb disconnect [<host>[:<port>]]\n"); + return 1; + } + if (argc == 2) { + snprintf(buf, sizeof buf, "host:disconnect:%s", argv[1]); + } else { + snprintf(buf, sizeof buf, "host:disconnect:"); + } + tmp = adb_query(buf); + if(tmp) { + printf("%s\n", tmp); + return 0; + } else { + return 1; + } + } + + if (!strcmp(argv[0], "emu")) { + return adb_send_emulator_command(argc, argv); + } + + if(!strcmp(argv[0], "shell")) { + int r; + int fd; + + if(argc < 2) { + return interactive_shell(); + } + + snprintf(buf, sizeof buf, "shell:%s", argv[1]); + argc -= 2; + argv += 2; + while(argc-- > 0) { + strcat(buf, " "); + + /* quote empty strings and strings with spaces */ + quote = (**argv == 0 || strchr(*argv, ' ')); + if (quote) + strcat(buf, "\""); + strcat(buf, *argv++); + if (quote) + strcat(buf, "\""); + } + + for(;;) { + fd = adb_connect(buf); + if(fd >= 0) { + read_and_dump(fd); + adb_close(fd); + r = 0; + } else { + fprintf(stderr,"error: %s\n", adb_error()); + r = -1; + } + + if(persist) { + fprintf(stderr,"\n- waiting for device -\n"); + adb_sleep_ms(1000); + do_cmd(ttype, serial, "wait-for-device", 0); + } else { + return r; + } + } + } + + if(!strcmp(argv[0], "kill-server")) { + int fd; + fd = _adb_connect("host:kill"); + if(fd == -1) { + fprintf(stderr,"* server not running *\n"); + return 1; + } + return 0; + } + + if(!strcmp(argv[0], "remount") || !strcmp(argv[0], "reboot") + || !strcmp(argv[0], "reboot-bootloader") + || !strcmp(argv[0], "tcpip") || !strcmp(argv[0], "usb") + || !strcmp(argv[0], "root")) { + char command[100]; + if (!strcmp(argv[0], "reboot-bootloader")) + snprintf(command, sizeof(command), "reboot:bootloader"); + else if (argc > 1) + snprintf(command, sizeof(command), "%s:%s", argv[0], argv[1]); + else + snprintf(command, sizeof(command), "%s:", argv[0]); + int fd = adb_connect(command); + if(fd >= 0) { + read_and_dump(fd); + adb_close(fd); + return 0; + } + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(!strcmp(argv[0], "bugreport")) { + if (argc != 1) return usage(); + do_cmd(ttype, serial, "shell", "bugreport", 0); + return 0; + } + + /* adb_command() wrapper commands */ + + if(!strncmp(argv[0], "wait-for-", strlen("wait-for-"))) { + char* service = argv[0]; + if (!strncmp(service, "wait-for-device", strlen("wait-for-device"))) { + if (ttype == kTransportUsb) { + service = "wait-for-usb"; + } else if (ttype == kTransportLocal) { + service = "wait-for-local"; + } else { + service = "wait-for-any"; + } + } + + format_host_command(buf, sizeof buf, service, ttype, serial); + + if (adb_command(buf)) { + D("failure: %s *\n",adb_error()); + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + /* Allow a command to be run after wait-for-device, + * e.g. 'adb wait-for-device shell'. + */ + if(argc > 1) { + argc--; + argv++; + goto top; + } + return 0; + } + + if(!strcmp(argv[0], "forward")) { + if(argc != 3) return usage(); + if (serial) { + snprintf(buf, sizeof buf, "host-serial:%s:forward:%s;%s",serial, argv[1], argv[2]); + } else if (ttype == kTransportUsb) { + snprintf(buf, sizeof buf, "host-usb:forward:%s;%s", argv[1], argv[2]); + } else if (ttype == kTransportLocal) { + snprintf(buf, sizeof buf, "host-local:forward:%s;%s", argv[1], argv[2]); + } else { + snprintf(buf, sizeof buf, "host:forward:%s;%s", argv[1], argv[2]); + } + if(adb_command(buf)) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + return 0; + } + + /* do_sync_*() commands */ + + if(!strcmp(argv[0], "ls")) { + if(argc != 2) return usage(); + return do_sync_ls(argv[1]); + } + + if(!strcmp(argv[0], "push")) { + if(argc != 3) return usage(); + return do_sync_push(argv[1], argv[2], 0 /* no verify APK */); + } + + if(!strcmp(argv[0], "pull")) { + if (argc == 2) { + return do_sync_pull(argv[1], "."); + } else if (argc == 3) { + return do_sync_pull(argv[1], argv[2]); + } else { + return usage(); + } + } + + if(!strcmp(argv[0], "install")) { + if (argc < 2) return usage(); + return install_app(ttype, serial, argc, argv); + } + + if(!strcmp(argv[0], "uninstall")) { + if (argc < 2) return usage(); + return uninstall_app(ttype, serial, argc, argv); + } + + if(!strcmp(argv[0], "sync")) { + char *srcarg, *android_srcpath, *data_srcpath; + int listonly = 0; + + int ret; + if(argc < 2) { + /* No local path was specified. */ + srcarg = NULL; + } else if (argc >= 2 && strcmp(argv[1], "-l") == 0) { + listonly = 1; + if (argc == 3) { + srcarg = argv[2]; + } else { + srcarg = NULL; + } + } else if(argc == 2) { + /* A local path or "android"/"data" arg was specified. */ + srcarg = argv[1]; + } else { + return usage(); + } + ret = find_sync_dirs(srcarg, &android_srcpath, &data_srcpath); + if(ret != 0) return usage(); + + if(android_srcpath != NULL) + ret = do_sync_sync(android_srcpath, "/system", listonly); + if(ret == 0 && data_srcpath != NULL) + ret = do_sync_sync(data_srcpath, "/data", listonly); + + free(android_srcpath); + free(data_srcpath); + return ret; + } + + /* passthrough commands */ + + if(!strcmp(argv[0],"get-state") || + !strcmp(argv[0],"get-serialno")) + { + char *tmp; + + format_host_command(buf, sizeof buf, argv[0], ttype, serial); + tmp = adb_query(buf); + if(tmp) { + printf("%s\n", tmp); + return 0; + } else { + return 1; + } + } + + /* other commands */ + + if(!strcmp(argv[0],"status-window")) { + status_window(ttype, serial); + return 0; + } + + if(!strcmp(argv[0],"logcat") || !strcmp(argv[0],"lolcat")) { + return logcat(ttype, serial, argc, argv); + } + + if(!strcmp(argv[0],"ppp")) { + return ppp(argc, argv); + } + + if (!strcmp(argv[0], "start-server")) { + return adb_connect("host:start-server"); + } + + if (!strcmp(argv[0], "jdwp")) { + int fd = adb_connect("jdwp"); + if (fd >= 0) { + read_and_dump(fd); + adb_close(fd); + return 0; + } else { + fprintf(stderr, "error: %s\n", adb_error()); + return -1; + } + } + + /* "adb /?" is a common idiom under Windows */ + if(!strcmp(argv[0], "help") || !strcmp(argv[0], "/?")) { + help(); + return 0; + } + + if(!strcmp(argv[0], "version")) { + version(stdout); + return 0; + } + + usage(); + return 1; +} + +static int do_cmd(transport_type ttype, char* serial, char *cmd, ...) +{ + char *argv[16]; + int argc; + va_list ap; + + va_start(ap, cmd); + argc = 0; + + if (serial) { + argv[argc++] = "-s"; + argv[argc++] = serial; + } else if (ttype == kTransportUsb) { + argv[argc++] = "-d"; + } else if (ttype == kTransportLocal) { + argv[argc++] = "-e"; + } + + argv[argc++] = cmd; + while((argv[argc] = va_arg(ap, char*)) != 0) argc++; + va_end(ap); + +#if 0 + int n; + fprintf(stderr,"argc = %d\n",argc); + for(n = 0; n < argc; n++) { + fprintf(stderr,"argv[%d] = \"%s\"\n", n, argv[n]); + } +#endif + + return adb_commandline(argc, argv); +} + +int find_sync_dirs(const char *srcarg, + char **android_srcdir_out, char **data_srcdir_out) +{ + char *android_srcdir, *data_srcdir; + + if(srcarg == NULL) { + android_srcdir = product_file("system"); + data_srcdir = product_file("data"); + } else { + /* srcarg may be "data", "system" or NULL. + * if srcarg is NULL, then both data and system are synced + */ + if(strcmp(srcarg, "system") == 0) { + android_srcdir = product_file("system"); + data_srcdir = NULL; + } else if(strcmp(srcarg, "data") == 0) { + android_srcdir = NULL; + data_srcdir = product_file("data"); + } else { + /* It's not "system" or "data". + */ + return 1; + } + } + + if(android_srcdir_out != NULL) + *android_srcdir_out = android_srcdir; + else + free(android_srcdir); + + if(data_srcdir_out != NULL) + *data_srcdir_out = data_srcdir; + else + free(data_srcdir); + + return 0; +} + +static int pm_command(transport_type transport, char* serial, + int argc, char** argv) +{ + char buf[4096]; + + snprintf(buf, sizeof(buf), "shell:pm"); + + while(argc-- > 0) { + char *quoted; + + quoted = dupAndQuote(*argv++); + + strncat(buf, " ", sizeof(buf)-1); + strncat(buf, quoted, sizeof(buf)-1); + free(quoted); + } + + send_shellcommand(transport, serial, buf); + return 0; +} + +int uninstall_app(transport_type transport, char* serial, int argc, char** argv) +{ + /* if the user choose the -k option, we refuse to do it until devices are + out with the option to uninstall the remaining data somehow (adb/ui) */ + if (argc == 3 && strcmp(argv[1], "-k") == 0) + { + printf( + "The -k option uninstalls the application while retaining the data/cache.\n" + "At the moment, there is no way to remove the remaining data.\n" + "You will have to reinstall the application with the same signature, and fully uninstall it.\n" + "If you truly wish to continue, execute 'adb shell pm uninstall -k %s'\n", argv[2]); + return -1; + } + + /* 'adb uninstall' takes the same arguments as 'pm uninstall' on device */ + return pm_command(transport, serial, argc, argv); +} + +static int delete_file(transport_type transport, char* serial, char* filename) +{ + char buf[4096]; + char* quoted; + + snprintf(buf, sizeof(buf), "shell:rm "); + quoted = dupAndQuote(filename); + strncat(buf, quoted, sizeof(buf)-1); + free(quoted); + + send_shellcommand(transport, serial, buf); + return 0; +} + +int install_app(transport_type transport, char* serial, int argc, char** argv) +{ + struct stat st; + int err; + const char *const DATA_DEST = "/data/local/tmp/%s"; + const char *const SD_DEST = "/sdcard/tmp/%s"; + const char* where = DATA_DEST; + char to[PATH_MAX]; + char* filename = argv[argc - 1]; + const char* p; + int i; + + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-s")) + where = SD_DEST; + } + + p = adb_dirstop(filename); + if (p) { + p++; + snprintf(to, sizeof to, where, p); + } else { + snprintf(to, sizeof to, where, filename); + } + if (p[0] == '\0') { + } + + err = stat(filename, &st); + if (err != 0) { + fprintf(stderr, "can't find '%s' to install\n", filename); + return 1; + } + if (!S_ISREG(st.st_mode)) { + fprintf(stderr, "can't install '%s' because it's not a file\n", + filename); + return 1; + } + + if (!(err = do_sync_push(filename, to, 1 /* verify APK */))) { + /* file in place; tell the Package Manager to install it */ + argv[argc - 1] = to; /* destination name, not source location */ + pm_command(transport, serial, argc, argv); + delete_file(transport, serial, to); + } + + return err; +} diff --git a/adb/console.c b/adb/console.c new file mode 100644 index 0000000..b813d33 --- /dev/null +++ b/adb/console.c @@ -0,0 +1,45 @@ +#include "sysdeps.h" +#include "adb.h" +#include "adb_client.h" +#include <stdio.h> + +static int connect_to_console(void) +{ + int fd, port; + + port = adb_get_emulator_console_port(); + if (port < 0) { + if (port == -2) + fprintf(stderr, "error: more than one emulator detected. use -s option\n"); + else + fprintf(stderr, "error: no emulator detected\n"); + return -1; + } + fd = socket_loopback_client( port, SOCK_STREAM ); + if (fd < 0) { + fprintf(stderr, "error: could not connect to TCP port %d\n", port); + return -1; + } + return fd; +} + + +int adb_send_emulator_command(int argc, char** argv) +{ + int fd, nn; + + fd = connect_to_console(); + if (fd < 0) + return 1; + +#define QUIT "quit\n" + + for (nn = 1; nn < argc; nn++) { + adb_write( fd, argv[nn], strlen(argv[nn]) ); + adb_write( fd, (nn == argc-1) ? "\n" : " ", 1 ); + } + adb_write( fd, QUIT, sizeof(QUIT)-1 ); + adb_close(fd); + + return 0; +} diff --git a/adb/fdevent.c b/adb/fdevent.c new file mode 100644 index 0000000..c179b20 --- /dev/null +++ b/adb/fdevent.c @@ -0,0 +1,506 @@ +/* http://frotznet.googlecode.com/svn/trunk/utils/fdevent.c +** +** Copyright 2006, Brian Swetland <swetland@frotz.net> +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include <fcntl.h> + +#include <stdarg.h> +#include <stddef.h> + +#include "fdevent.h" + +#define TRACE(x...) fprintf(stderr,x) + +#define DEBUG 0 + +static void fatal(const char *fn, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:", fn); + vfprintf(stderr, fmt, ap); + va_end(ap); + abort(); +} + +#define FATAL(x...) fatal(__FUNCTION__, x) + +#if DEBUG +static void dump_fde(fdevent *fde, const char *info) +{ + fprintf(stderr,"FDE #%03d %c%c%c %s\n", fde->fd, + fde->state & FDE_READ ? 'R' : ' ', + fde->state & FDE_WRITE ? 'W' : ' ', + fde->state & FDE_ERROR ? 'E' : ' ', + info); +} +#else +#define dump_fde(fde, info) do { } while(0) +#endif + +#define FDE_EVENTMASK 0x00ff +#define FDE_STATEMASK 0xff00 + +#define FDE_ACTIVE 0x0100 +#define FDE_PENDING 0x0200 +#define FDE_CREATED 0x0400 + +static void fdevent_plist_enqueue(fdevent *node); +static void fdevent_plist_remove(fdevent *node); +static fdevent *fdevent_plist_dequeue(void); + +static fdevent list_pending = { + .next = &list_pending, + .prev = &list_pending, +}; + +static fdevent **fd_table = 0; +static int fd_table_max = 0; + +#ifdef CRAPTASTIC +//HAVE_EPOLL + +#include <sys/epoll.h> + +static int epoll_fd = -1; + +static void fdevent_init() +{ + /* XXX: what's a good size for the passed in hint? */ + epoll_fd = epoll_create(256); + + if(epoll_fd < 0) { + perror("epoll_create() failed"); + exit(1); + } + + /* mark for close-on-exec */ + fcntl(epoll_fd, F_SETFD, FD_CLOEXEC); +} + +static void fdevent_connect(fdevent *fde) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = 0; + ev.data.ptr = fde; + +#if 0 + if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } +#endif +} + +static void fdevent_disconnect(fdevent *fde) +{ + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = 0; + ev.data.ptr = fde; + + /* technically we only need to delete if we + ** were actively monitoring events, but let's + ** be aggressive and do it anyway, just in case + ** something's out of sync + */ + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev); +} + +static void fdevent_update(fdevent *fde, unsigned events) +{ + struct epoll_event ev; + int active; + + active = (fde->state & FDE_EVENTMASK) != 0; + + memset(&ev, 0, sizeof(ev)); + ev.events = 0; + ev.data.ptr = fde; + + if(events & FDE_READ) ev.events |= EPOLLIN; + if(events & FDE_WRITE) ev.events |= EPOLLOUT; + if(events & FDE_ERROR) ev.events |= (EPOLLERR | EPOLLHUP); + + fde->state = (fde->state & FDE_STATEMASK) | events; + + if(active) { + /* we're already active. if we're changing to *no* + ** events being monitored, we need to delete, otherwise + ** we need to just modify + */ + if(ev.events) { + if(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } + } else { + if(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } + } + } else { + /* we're not active. if we're watching events, we need + ** to add, otherwise we can just do nothing + */ + if(ev.events) { + if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fde->fd, &ev)) { + perror("epoll_ctl() failed\n"); + exit(1); + } + } + } +} + +static void fdevent_process() +{ + struct epoll_event events[256]; + fdevent *fde; + int i, n; + + n = epoll_wait(epoll_fd, events, 256, -1); + + if(n < 0) { + if(errno == EINTR) return; + perror("epoll_wait"); + exit(1); + } + + for(i = 0; i < n; i++) { + struct epoll_event *ev = events + i; + fde = ev->data.ptr; + + if(ev->events & EPOLLIN) { + fde->events |= FDE_READ; + } + if(ev->events & EPOLLOUT) { + fde->events |= FDE_WRITE; + } + if(ev->events & (EPOLLERR | EPOLLHUP)) { + fde->events |= FDE_ERROR; + } + if(fde->events) { + if(fde->state & FDE_PENDING) continue; + fde->state |= FDE_PENDING; + fdevent_plist_enqueue(fde); + } + } +} + +#else /* USE_SELECT */ + +#ifdef HAVE_WINSOCK +#include <winsock2.h> +#else +#include <sys/select.h> +#endif + +static fd_set read_fds; +static fd_set write_fds; +static fd_set error_fds; + +static int select_n = 0; + +static void fdevent_init(void) +{ + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + FD_ZERO(&error_fds); +} + +static void fdevent_connect(fdevent *fde) +{ + if(fde->fd >= select_n) { + select_n = fde->fd + 1; + } +} + +static void fdevent_disconnect(fdevent *fde) +{ + int i, n; + + FD_CLR(fde->fd, &read_fds); + FD_CLR(fde->fd, &write_fds); + FD_CLR(fde->fd, &error_fds); + + for(n = 0, i = 0; i < select_n; i++) { + if(fd_table[i] != 0) n = i; + } + select_n = n + 1; +} + +static void fdevent_update(fdevent *fde, unsigned events) +{ + if(events & FDE_READ) { + FD_SET(fde->fd, &read_fds); + } else { + FD_CLR(fde->fd, &read_fds); + } + if(events & FDE_WRITE) { + FD_SET(fde->fd, &write_fds); + } else { + FD_CLR(fde->fd, &write_fds); + } + if(events & FDE_ERROR) { + FD_SET(fde->fd, &error_fds); + } else { + FD_CLR(fde->fd, &error_fds); + } + + fde->state = (fde->state & FDE_STATEMASK) | events; +} + +static void fdevent_process() +{ + int i, n; + fdevent *fde; + unsigned events; + fd_set rfd, wfd, efd; + + memcpy(&rfd, &read_fds, sizeof(fd_set)); + memcpy(&wfd, &write_fds, sizeof(fd_set)); + memcpy(&efd, &error_fds, sizeof(fd_set)); + + n = select(select_n, &rfd, &wfd, &efd, 0); + + if(n < 0) { + if(errno == EINTR) return; + perror("select"); + return; + } + + for(i = 0; (i < select_n) && (n > 0); i++) { + events = 0; + if(FD_ISSET(i, &rfd)) events |= FDE_READ; + if(FD_ISSET(i, &wfd)) events |= FDE_WRITE; + if(FD_ISSET(i, &efd)) events |= FDE_ERROR; + + if(events) { + n--; + + fde = fd_table[i]; + if(fde == 0) FATAL("missing fde for fd %d\n", i); + + fde->events |= events; + + if(fde->state & FDE_PENDING) continue; + fde->state |= FDE_PENDING; + fdevent_plist_enqueue(fde); + } + } +} + +#endif + +static void fdevent_register(fdevent *fde) +{ + if(fde->fd < 0) { + FATAL("bogus negative fd (%d)\n", fde->fd); + } + + if(fde->fd >= fd_table_max) { + int oldmax = fd_table_max; + if(fde->fd > 32000) { + FATAL("bogus huuuuge fd (%d)\n", fde->fd); + } + if(fd_table_max == 0) { + fdevent_init(); + fd_table_max = 256; + } + while(fd_table_max <= fde->fd) { + fd_table_max *= 2; + } + fd_table = realloc(fd_table, sizeof(fdevent*) * fd_table_max); + if(fd_table == 0) { + FATAL("could not expand fd_table to %d entries\n", fd_table_max); + } + memset(fd_table + oldmax, 0, sizeof(int) * (fd_table_max - oldmax)); + } + + fd_table[fde->fd] = fde; +} + +static void fdevent_unregister(fdevent *fde) +{ + if((fde->fd < 0) || (fde->fd >= fd_table_max)) { + FATAL("fd out of range (%d)\n", fde->fd); + } + + if(fd_table[fde->fd] != fde) { + FATAL("fd_table out of sync"); + } + + fd_table[fde->fd] = 0; + + if(!(fde->state & FDE_DONT_CLOSE)) { + dump_fde(fde, "close"); + close(fde->fd); + } +} + +static void fdevent_plist_enqueue(fdevent *node) +{ + fdevent *list = &list_pending; + + node->next = list; + node->prev = list->prev; + node->prev->next = node; + list->prev = node; +} + +static void fdevent_plist_remove(fdevent *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; +} + +static fdevent *fdevent_plist_dequeue(void) +{ + fdevent *list = &list_pending; + fdevent *node = list->next; + + if(node == list) return 0; + + list->next = node->next; + list->next->prev = list; + node->next = 0; + node->prev = 0; + + return node; +} + +fdevent *fdevent_create(int fd, fd_func func, void *arg) +{ + fdevent *fde = (fdevent*) malloc(sizeof(fdevent)); + if(fde == 0) return 0; + fdevent_install(fde, fd, func, arg); + fde->state |= FDE_CREATED; + return fde; +} + +void fdevent_destroy(fdevent *fde) +{ + if(fde == 0) return; + if(!(fde->state & FDE_CREATED)) { + FATAL("fde %p not created by fdevent_create()\n", fde); + } + fdevent_remove(fde); +} + +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg) +{ + memset(fde, 0, sizeof(fdevent)); + fde->state = FDE_ACTIVE; + fde->fd = fd; + fde->func = func; + fde->arg = arg; + +#ifndef HAVE_WINSOCK + fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + fdevent_register(fde); + dump_fde(fde, "connect"); + fdevent_connect(fde); + fde->state |= FDE_ACTIVE; +} + +void fdevent_remove(fdevent *fde) +{ + if(fde->state & FDE_PENDING) { + fdevent_plist_remove(fde); + } + + if(fde->state & FDE_ACTIVE) { + fdevent_disconnect(fde); + dump_fde(fde, "disconnect"); + fdevent_unregister(fde); + } + + fde->state = 0; + fde->events = 0; +} + + +void fdevent_set(fdevent *fde, unsigned events) +{ + events &= FDE_EVENTMASK; + + if((fde->state & FDE_EVENTMASK) == events) return; + + if(fde->state & FDE_ACTIVE) { + fdevent_update(fde, events); + dump_fde(fde, "update"); + } + + fde->state = (fde->state & FDE_STATEMASK) | events; + + if(fde->state & FDE_PENDING) { + /* if we're pending, make sure + ** we don't signal an event that + ** is no longer wanted. + */ + fde->events &= (~events); + if(fde->events == 0) { + fdevent_plist_remove(fde); + fde->state &= (~FDE_PENDING); + } + } +} + +void fdevent_add(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) | (events & FDE_EVENTMASK)); +} + +void fdevent_del(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) & (~(events & FDE_EVENTMASK))); +} + +void fdevent_loop() +{ + fdevent *fde; + + for(;;) { +#if DEBUG + fprintf(stderr,"--- ---- waiting for events\n"); +#endif + fdevent_process(); + + while((fde = fdevent_plist_dequeue())) { + unsigned events = fde->events; + fde->events = 0; + fde->state &= (~FDE_PENDING); + dump_fde(fde, "callback"); + fde->func(fde->fd, events, fde->arg); + } + } +} + diff --git a/adb/fdevent.h b/adb/fdevent.h new file mode 100644 index 0000000..6b7e7ec --- /dev/null +++ b/adb/fdevent.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FDEVENT_H +#define __FDEVENT_H + +#include <stdint.h> /* for int64_t */ + +/* events that may be observed */ +#define FDE_READ 0x0001 +#define FDE_WRITE 0x0002 +#define FDE_ERROR 0x0004 +#define FDE_TIMEOUT 0x0008 + +/* features that may be set (via the events set/add/del interface) */ +#define FDE_DONT_CLOSE 0x0080 + +typedef struct fdevent fdevent; + +typedef void (*fd_func)(int fd, unsigned events, void *userdata); + +/* Allocate and initialize a new fdevent object + * Note: use FD_TIMER as 'fd' to create a fd-less object + * (used to implement timers). +*/ +fdevent *fdevent_create(int fd, fd_func func, void *arg); + +/* Uninitialize and deallocate an fdevent object that was +** created by fdevent_create() +*/ +void fdevent_destroy(fdevent *fde); + +/* Initialize an fdevent object that was externally allocated +*/ +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg); + +/* Uninitialize an fdevent object that was initialized by +** fdevent_install() +*/ +void fdevent_remove(fdevent *item); + +/* Change which events should cause notifications +*/ +void fdevent_set(fdevent *fde, unsigned events); +void fdevent_add(fdevent *fde, unsigned events); +void fdevent_del(fdevent *fde, unsigned events); + +void fdevent_set_timeout(fdevent *fde, int64_t timeout_ms); + +/* loop forever, handling events. +*/ +void fdevent_loop(); + +struct fdevent +{ + fdevent *next; + fdevent *prev; + + int fd; + unsigned short state; + unsigned short events; + + fd_func func; + void *arg; +}; + + +#endif diff --git a/adb/file_sync_client.c b/adb/file_sync_client.c new file mode 100644 index 0000000..da25ae8 --- /dev/null +++ b/adb/file_sync_client.c @@ -0,0 +1,1023 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> +#include <dirent.h> +#include <limits.h> +#include <sys/types.h> +#include <zipfile/zipfile.h> + +#include "sysdeps.h" +#include "adb.h" +#include "adb_client.h" +#include "file_sync_service.h" + + +static unsigned total_bytes; +static long long start_time; + +static long long NOW() +{ + struct timeval tv; + gettimeofday(&tv, 0); + return ((long long) tv.tv_usec) + + 1000000LL * ((long long) tv.tv_sec); +} + +static void BEGIN() +{ + total_bytes = 0; + start_time = NOW(); +} + +static void END() +{ + long long t = NOW() - start_time; + if(total_bytes == 0) return; + + if (t == 0) /* prevent division by 0 :-) */ + t = 1000000; + + fprintf(stderr,"%lld KB/s (%d bytes in %lld.%03llds)\n", + ((((long long) total_bytes) * 1000000LL) / t) / 1024LL, + total_bytes, (t / 1000000LL), (t % 1000000LL) / 1000LL); +} + +void sync_quit(int fd) +{ + syncmsg msg; + + msg.req.id = ID_QUIT; + msg.req.namelen = 0; + + writex(fd, &msg.req, sizeof(msg.req)); +} + +typedef void (*sync_ls_cb)(unsigned mode, unsigned size, unsigned time, const char *name, void *cookie); + +int sync_ls(int fd, const char *path, sync_ls_cb func, void *cookie) +{ + syncmsg msg; + char buf[257]; + int len; + + len = strlen(path); + if(len > 1024) goto fail; + + msg.req.id = ID_LIST; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + goto fail; + } + + for(;;) { + if(readx(fd, &msg.dent, sizeof(msg.dent))) break; + if(msg.dent.id == ID_DONE) return 0; + if(msg.dent.id != ID_DENT) break; + + len = ltohl(msg.dent.namelen); + if(len > 256) break; + + if(readx(fd, buf, len)) break; + buf[len] = 0; + + func(ltohl(msg.dent.mode), + ltohl(msg.dent.size), + ltohl(msg.dent.time), + buf, cookie); + } + +fail: + adb_close(fd); + return -1; +} + +typedef struct syncsendbuf syncsendbuf; + +struct syncsendbuf { + unsigned id; + unsigned size; + char data[SYNC_DATA_MAX]; +}; + +static syncsendbuf send_buffer; + +int sync_readtime(int fd, const char *path, unsigned *timestamp) +{ + syncmsg msg; + int len = strlen(path); + + msg.req.id = ID_STAT; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + return -1; + } + + if(readx(fd, &msg.stat, sizeof(msg.stat))) { + return -1; + } + + if(msg.stat.id != ID_STAT) { + return -1; + } + + *timestamp = ltohl(msg.stat.time); + return 0; +} + +static int sync_start_readtime(int fd, const char *path) +{ + syncmsg msg; + int len = strlen(path); + + msg.req.id = ID_STAT; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + return -1; + } + + return 0; +} + +static int sync_finish_readtime(int fd, unsigned int *timestamp, + unsigned int *mode, unsigned int *size) +{ + syncmsg msg; + + if(readx(fd, &msg.stat, sizeof(msg.stat))) + return -1; + + if(msg.stat.id != ID_STAT) + return -1; + + *timestamp = ltohl(msg.stat.time); + *mode = ltohl(msg.stat.mode); + *size = ltohl(msg.stat.size); + + return 0; +} + +int sync_readmode(int fd, const char *path, unsigned *mode) +{ + syncmsg msg; + int len = strlen(path); + + msg.req.id = ID_STAT; + msg.req.namelen = htoll(len); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, path, len)) { + return -1; + } + + if(readx(fd, &msg.stat, sizeof(msg.stat))) { + return -1; + } + + if(msg.stat.id != ID_STAT) { + return -1; + } + + *mode = ltohl(msg.stat.mode); + return 0; +} + +static int write_data_file(int fd, const char *path, syncsendbuf *sbuf) +{ + int lfd, err = 0; + + lfd = adb_open(path, O_RDONLY); + if(lfd < 0) { + fprintf(stderr,"cannot open '%s': %s\n", path, strerror(errno)); + return -1; + } + + sbuf->id = ID_DATA; + for(;;) { + int ret; + + ret = adb_read(lfd, sbuf->data, SYNC_DATA_MAX); + if(!ret) + break; + + if(ret < 0) { + if(errno == EINTR) + continue; + fprintf(stderr,"cannot read '%s': %s\n", path, strerror(errno)); + break; + } + + sbuf->size = htoll(ret); + if(writex(fd, sbuf, sizeof(unsigned) * 2 + ret)){ + err = -1; + break; + } + total_bytes += ret; + } + + adb_close(lfd); + return err; +} + +static int write_data_buffer(int fd, char* file_buffer, int size, syncsendbuf *sbuf) +{ + int err = 0; + int total = 0; + + sbuf->id = ID_DATA; + while (total < size) { + int count = size - total; + if (count > SYNC_DATA_MAX) { + count = SYNC_DATA_MAX; + } + + memcpy(sbuf->data, &file_buffer[total], count); + sbuf->size = htoll(count); + if(writex(fd, sbuf, sizeof(unsigned) * 2 + count)){ + err = -1; + break; + } + total += count; + total_bytes += count; + } + + return err; +} + +#ifdef HAVE_SYMLINKS +static int write_data_link(int fd, const char *path, syncsendbuf *sbuf) +{ + int len, ret; + + len = readlink(path, sbuf->data, SYNC_DATA_MAX-1); + if(len < 0) { + fprintf(stderr, "error reading link '%s': %s\n", path, strerror(errno)); + return -1; + } + sbuf->data[len] = '\0'; + + sbuf->size = htoll(len + 1); + sbuf->id = ID_DATA; + + ret = writex(fd, sbuf, sizeof(unsigned) * 2 + len + 1); + if(ret) + return -1; + + total_bytes += len + 1; + + return 0; +} +#endif + +static int sync_send(int fd, const char *lpath, const char *rpath, + unsigned mtime, mode_t mode, int verifyApk) +{ + syncmsg msg; + int len, r; + syncsendbuf *sbuf = &send_buffer; + char* file_buffer = NULL; + int size = 0; + char tmp[64]; + + len = strlen(rpath); + if(len > 1024) goto fail; + + snprintf(tmp, sizeof(tmp), ",%d", mode); + r = strlen(tmp); + + if (verifyApk) { + int lfd; + zipfile_t zip; + zipentry_t entry; + int amt; + + // if we are transferring an APK file, then sanity check to make sure + // we have a real zip file that contains an AndroidManifest.xml + // this requires that we read the entire file into memory. + lfd = adb_open(lpath, O_RDONLY); + if(lfd < 0) { + fprintf(stderr,"cannot open '%s': %s\n", lpath, strerror(errno)); + return -1; + } + + size = adb_lseek(lfd, 0, SEEK_END); + if (size == -1 || -1 == adb_lseek(lfd, 0, SEEK_SET)) { + fprintf(stderr, "error seeking in file '%s'\n", lpath); + adb_close(lfd); + return 1; + } + + file_buffer = (char *)malloc(size); + if (file_buffer == NULL) { + fprintf(stderr, "could not allocate buffer for '%s'\n", + lpath); + adb_close(lfd); + return 1; + } + amt = adb_read(lfd, file_buffer, size); + if (amt != size) { + fprintf(stderr, "error reading from file: '%s'\n", lpath); + adb_close(lfd); + free(file_buffer); + return 1; + } + + adb_close(lfd); + + zip = init_zipfile(file_buffer, size); + if (zip == NULL) { + fprintf(stderr, "file '%s' is not a valid zip file\n", + lpath); + free(file_buffer); + return 1; + } + + entry = lookup_zipentry(zip, "AndroidManifest.xml"); + release_zipfile(zip); + if (entry == NULL) { + fprintf(stderr, "file '%s' does not contain AndroidManifest.xml\n", + lpath); + free(file_buffer); + return 1; + } + } + + msg.req.id = ID_SEND; + msg.req.namelen = htoll(len + r); + + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, rpath, len) || writex(fd, tmp, r)) { + free(file_buffer); + goto fail; + } + + if (file_buffer) { + write_data_buffer(fd, file_buffer, size, sbuf); + free(file_buffer); + } else if (S_ISREG(mode)) + write_data_file(fd, lpath, sbuf); +#ifdef HAVE_SYMLINKS + else if (S_ISLNK(mode)) + write_data_link(fd, lpath, sbuf); +#endif + else + goto fail; + + msg.data.id = ID_DONE; + msg.data.size = htoll(mtime); + if(writex(fd, &msg.data, sizeof(msg.data))) + goto fail; + + if(readx(fd, &msg.status, sizeof(msg.status))) + return -1; + + if(msg.status.id != ID_OKAY) { + if(msg.status.id == ID_FAIL) { + len = ltohl(msg.status.msglen); + if(len > 256) len = 256; + if(readx(fd, sbuf->data, len)) { + return -1; + } + sbuf->data[len] = 0; + } else + strcpy(sbuf->data, "unknown reason"); + + fprintf(stderr,"failed to copy '%s' to '%s': %s\n", lpath, rpath, sbuf->data); + return -1; + } + + return 0; + +fail: + fprintf(stderr,"protocol failure\n"); + adb_close(fd); + return -1; +} + +static int mkdirs(char *name) +{ + int ret; + char *x = name + 1; + + for(;;) { + x = adb_dirstart(x); + if(x == 0) return 0; + *x = 0; + ret = adb_mkdir(name, 0775); + *x = OS_PATH_SEPARATOR; + if((ret < 0) && (errno != EEXIST)) { + return ret; + } + x++; + } + return 0; +} + +int sync_recv(int fd, const char *rpath, const char *lpath) +{ + syncmsg msg; + int len; + int lfd = -1; + char *buffer = send_buffer.data; + unsigned id; + + len = strlen(rpath); + if(len > 1024) return -1; + + msg.req.id = ID_RECV; + msg.req.namelen = htoll(len); + if(writex(fd, &msg.req, sizeof(msg.req)) || + writex(fd, rpath, len)) { + return -1; + } + + if(readx(fd, &msg.data, sizeof(msg.data))) { + return -1; + } + id = msg.data.id; + + if((id == ID_DATA) || (id == ID_DONE)) { + adb_unlink(lpath); + mkdirs((char *)lpath); + lfd = adb_creat(lpath, 0644); + if(lfd < 0) { + fprintf(stderr,"cannot create '%s': %s\n", lpath, strerror(errno)); + return -1; + } + goto handle_data; + } else { + goto remote_error; + } + + for(;;) { + if(readx(fd, &msg.data, sizeof(msg.data))) { + return -1; + } + id = msg.data.id; + + handle_data: + len = ltohl(msg.data.size); + if(id == ID_DONE) break; + if(id != ID_DATA) goto remote_error; + if(len > SYNC_DATA_MAX) { + fprintf(stderr,"data overrun\n"); + adb_close(lfd); + return -1; + } + + if(readx(fd, buffer, len)) { + adb_close(lfd); + return -1; + } + + if(writex(lfd, buffer, len)) { + fprintf(stderr,"cannot write '%s': %s\n", rpath, strerror(errno)); + adb_close(lfd); + return -1; + } + + total_bytes += len; + } + + adb_close(lfd); + return 0; + +remote_error: + adb_close(lfd); + adb_unlink(lpath); + + if(id == ID_FAIL) { + len = ltohl(msg.data.size); + if(len > 256) len = 256; + if(readx(fd, buffer, len)) { + return -1; + } + buffer[len] = 0; + } else { + memcpy(buffer, &id, 4); + buffer[4] = 0; +// strcpy(buffer,"unknown reason"); + } + fprintf(stderr,"failed to copy '%s' to '%s': %s\n", rpath, lpath, buffer); + return 0; +} + + + +/* --- */ + + +static void do_sync_ls_cb(unsigned mode, unsigned size, unsigned time, + const char *name, void *cookie) +{ + printf("%08x %08x %08x %s\n", mode, size, time, name); +} + +int do_sync_ls(const char *path) +{ + int fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(sync_ls(fd, path, do_sync_ls_cb, 0)) { + return 1; + } else { + sync_quit(fd); + return 0; + } +} + +typedef struct copyinfo copyinfo; + +struct copyinfo +{ + copyinfo *next; + const char *src; + const char *dst; + unsigned int time; + unsigned int mode; + unsigned int size; + int flag; + //char data[0]; +}; + +copyinfo *mkcopyinfo(const char *spath, const char *dpath, + const char *name, int isdir) +{ + int slen = strlen(spath); + int dlen = strlen(dpath); + int nlen = strlen(name); + int ssize = slen + nlen + 2; + int dsize = dlen + nlen + 2; + + copyinfo *ci = malloc(sizeof(copyinfo) + ssize + dsize); + if(ci == 0) { + fprintf(stderr,"out of memory\n"); + abort(); + } + + ci->next = 0; + ci->time = 0; + ci->mode = 0; + ci->size = 0; + ci->flag = 0; + ci->src = (const char*)(ci + 1); + ci->dst = ci->src + ssize; + snprintf((char*) ci->src, ssize, isdir ? "%s%s/" : "%s%s", spath, name); + snprintf((char*) ci->dst, dsize, isdir ? "%s%s/" : "%s%s", dpath, name); + +// fprintf(stderr,"mkcopyinfo('%s','%s')\n", ci->src, ci->dst); + return ci; +} + + +static int local_build_list(copyinfo **filelist, + const char *lpath, const char *rpath) +{ + DIR *d; + struct dirent *de; + struct stat st; + copyinfo *dirlist = 0; + copyinfo *ci, *next; + +// fprintf(stderr,"local_build_list('%s','%s')\n", lpath, rpath); + + d = opendir(lpath); + if(d == 0) { + fprintf(stderr,"cannot open '%s': %s\n", lpath, strerror(errno)); + return -1; + } + + while((de = readdir(d))) { + char stat_path[PATH_MAX]; + char *name = de->d_name; + + if(name[0] == '.') { + if(name[1] == 0) continue; + if((name[1] == '.') && (name[2] == 0)) continue; + } + + /* + * We could use d_type if HAVE_DIRENT_D_TYPE is defined, but reiserfs + * always returns DT_UNKNOWN, so we just use stat() for all cases. + */ + if (strlen(lpath) + strlen(de->d_name) + 1 > sizeof(stat_path)) + continue; + strcpy(stat_path, lpath); + strcat(stat_path, de->d_name); + stat(stat_path, &st); + + if (S_ISDIR(st.st_mode)) { + ci = mkcopyinfo(lpath, rpath, name, 1); + ci->next = dirlist; + dirlist = ci; + } else { + ci = mkcopyinfo(lpath, rpath, name, 0); + if(lstat(ci->src, &st)) { + closedir(d); + fprintf(stderr,"cannot stat '%s': %s\n", ci->src, strerror(errno)); + return -1; + } + if(!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { + fprintf(stderr, "skipping special file '%s'\n", ci->src); + free(ci); + } else { + ci->time = st.st_mtime; + ci->mode = st.st_mode; + ci->size = st.st_size; + ci->next = *filelist; + *filelist = ci; + } + } + } + + closedir(d); + + for(ci = dirlist; ci != 0; ci = next) { + next = ci->next; + local_build_list(filelist, ci->src, ci->dst); + free(ci); + } + + return 0; +} + + +static int copy_local_dir_remote(int fd, const char *lpath, const char *rpath, int checktimestamps, int listonly) +{ + copyinfo *filelist = 0; + copyinfo *ci, *next; + int pushed = 0; + int skipped = 0; + + if((lpath[0] == 0) || (rpath[0] == 0)) return -1; + if(lpath[strlen(lpath) - 1] != '/') { + int tmplen = strlen(lpath)+2; + char *tmp = malloc(tmplen); + if(tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/",lpath); + lpath = tmp; + } + if(rpath[strlen(rpath) - 1] != '/') { + int tmplen = strlen(rpath)+2; + char *tmp = malloc(tmplen); + if(tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/",rpath); + rpath = tmp; + } + + if(local_build_list(&filelist, lpath, rpath)) { + return -1; + } + + if(checktimestamps){ + for(ci = filelist; ci != 0; ci = ci->next) { + if(sync_start_readtime(fd, ci->dst)) { + return 1; + } + } + for(ci = filelist; ci != 0; ci = ci->next) { + unsigned int timestamp, mode, size; + if(sync_finish_readtime(fd, ×tamp, &mode, &size)) + return 1; + if(size == ci->size) { + /* for links, we cannot update the atime/mtime */ + if((S_ISREG(ci->mode & mode) && timestamp == ci->time) || + (S_ISLNK(ci->mode & mode) && timestamp >= ci->time)) + ci->flag = 1; + } + } + } + for(ci = filelist; ci != 0; ci = next) { + next = ci->next; + if(ci->flag == 0) { + fprintf(stderr,"%spush: %s -> %s\n", listonly ? "would " : "", ci->src, ci->dst); + if(!listonly && + sync_send(fd, ci->src, ci->dst, ci->time, ci->mode, 0 /* no verify APK */)){ + return 1; + } + pushed++; + } else { + skipped++; + } + free(ci); + } + + fprintf(stderr,"%d file%s pushed. %d file%s skipped.\n", + pushed, (pushed == 1) ? "" : "s", + skipped, (skipped == 1) ? "" : "s"); + + return 0; +} + + +int do_sync_push(const char *lpath, const char *rpath, int verifyApk) +{ + struct stat st; + unsigned mode; + int fd; + + fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(stat(lpath, &st)) { + fprintf(stderr,"cannot stat '%s': %s\n", lpath, strerror(errno)); + sync_quit(fd); + return 1; + } + + if(S_ISDIR(st.st_mode)) { + BEGIN(); + if(copy_local_dir_remote(fd, lpath, rpath, 0, 0)) { + return 1; + } else { + END(); + sync_quit(fd); + } + } else { + if(sync_readmode(fd, rpath, &mode)) { + return 1; + } + if((mode != 0) && S_ISDIR(mode)) { + /* if we're copying a local file to a remote directory, + ** we *really* want to copy to remotedir + "/" + localfilename + */ + const char *name = adb_dirstop(lpath); + if(name == 0) { + name = lpath; + } else { + name++; + } + int tmplen = strlen(name) + strlen(rpath) + 2; + char *tmp = malloc(strlen(name) + strlen(rpath) + 2); + if(tmp == 0) return 1; + snprintf(tmp, tmplen, "%s/%s", rpath, name); + rpath = tmp; + } + BEGIN(); + if(sync_send(fd, lpath, rpath, st.st_mtime, st.st_mode, verifyApk)) { + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } + } + + return 0; +} + + +typedef struct { + copyinfo **filelist; + copyinfo **dirlist; + const char *rpath; + const char *lpath; +} sync_ls_build_list_cb_args; + +void +sync_ls_build_list_cb(unsigned mode, unsigned size, unsigned time, + const char *name, void *cookie) +{ + sync_ls_build_list_cb_args *args = (sync_ls_build_list_cb_args *)cookie; + copyinfo *ci; + + if (S_ISDIR(mode)) { + copyinfo **dirlist = args->dirlist; + + /* Don't try recursing down "." or ".." */ + if (name[0] == '.') { + if (name[1] == '\0') return; + if ((name[1] == '.') && (name[2] == '\0')) return; + } + + ci = mkcopyinfo(args->rpath, args->lpath, name, 1); + ci->next = *dirlist; + *dirlist = ci; + } else if (S_ISREG(mode) || S_ISLNK(mode)) { + copyinfo **filelist = args->filelist; + + ci = mkcopyinfo(args->rpath, args->lpath, name, 0); + ci->time = time; + ci->mode = mode; + ci->size = size; + ci->next = *filelist; + *filelist = ci; + } else { + fprintf(stderr, "skipping special file '%s'\n", name); + } +} + +static int remote_build_list(int syncfd, copyinfo **filelist, + const char *rpath, const char *lpath) +{ + copyinfo *dirlist = NULL; + sync_ls_build_list_cb_args args; + + args.filelist = filelist; + args.dirlist = &dirlist; + args.rpath = rpath; + args.lpath = lpath; + + /* Put the files/dirs in rpath on the lists. */ + if (sync_ls(syncfd, rpath, sync_ls_build_list_cb, (void *)&args)) { + return 1; + } + + /* Recurse into each directory we found. */ + while (dirlist != NULL) { + copyinfo *next = dirlist->next; + if (remote_build_list(syncfd, filelist, dirlist->src, dirlist->dst)) { + return 1; + } + free(dirlist); + dirlist = next; + } + + return 0; +} + +static int copy_remote_dir_local(int fd, const char *rpath, const char *lpath, + int checktimestamps) +{ + copyinfo *filelist = 0; + copyinfo *ci, *next; + int pulled = 0; + int skipped = 0; + + /* Make sure that both directory paths end in a slash. */ + if (rpath[0] == 0 || lpath[0] == 0) return -1; + if (rpath[strlen(rpath) - 1] != '/') { + int tmplen = strlen(rpath) + 2; + char *tmp = malloc(tmplen); + if (tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/", rpath); + rpath = tmp; + } + if (lpath[strlen(lpath) - 1] != '/') { + int tmplen = strlen(lpath) + 2; + char *tmp = malloc(tmplen); + if (tmp == 0) return -1; + snprintf(tmp, tmplen, "%s/", lpath); + lpath = tmp; + } + + fprintf(stderr, "pull: building file list...\n"); + /* Recursively build the list of files to copy. */ + if (remote_build_list(fd, &filelist, rpath, lpath)) { + return -1; + } + +#if 0 + if (checktimestamps) { + for (ci = filelist; ci != 0; ci = ci->next) { + if (sync_start_readtime(fd, ci->dst)) { + return 1; + } + } + for (ci = filelist; ci != 0; ci = ci->next) { + unsigned int timestamp, mode, size; + if (sync_finish_readtime(fd, ×tamp, &mode, &size)) + return 1; + if (size == ci->size) { + /* for links, we cannot update the atime/mtime */ + if ((S_ISREG(ci->mode & mode) && timestamp == ci->time) || + (S_ISLNK(ci->mode & mode) && timestamp >= ci->time)) + ci->flag = 1; + } + } + } +#endif + for (ci = filelist; ci != 0; ci = next) { + next = ci->next; + if (ci->flag == 0) { + fprintf(stderr, "pull: %s -> %s\n", ci->src, ci->dst); + if (sync_recv(fd, ci->src, ci->dst)) { + return 1; + } + pulled++; + } else { + skipped++; + } + free(ci); + } + + fprintf(stderr, "%d file%s pulled. %d file%s skipped.\n", + pulled, (pulled == 1) ? "" : "s", + skipped, (skipped == 1) ? "" : "s"); + + return 0; +} + +int do_sync_pull(const char *rpath, const char *lpath) +{ + unsigned mode; + struct stat st; + + int fd; + + fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + if(sync_readmode(fd, rpath, &mode)) { + return 1; + } + if(mode == 0) { + fprintf(stderr,"remote object '%s' does not exist\n", rpath); + return 1; + } + + if(S_ISREG(mode) || S_ISLNK(mode) || S_ISCHR(mode) || S_ISBLK(mode)) { + if(stat(lpath, &st) == 0) { + if(S_ISDIR(st.st_mode)) { + /* if we're copying a remote file to a local directory, + ** we *really* want to copy to localdir + "/" + remotefilename + */ + const char *name = adb_dirstop(rpath); + if(name == 0) { + name = rpath; + } else { + name++; + } + int tmplen = strlen(name) + strlen(lpath) + 2; + char *tmp = malloc(tmplen); + if(tmp == 0) return 1; + snprintf(tmp, tmplen, "%s/%s", lpath, name); + lpath = tmp; + } + } + BEGIN(); + if(sync_recv(fd, rpath, lpath)) { + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } + } else if(S_ISDIR(mode)) { + BEGIN(); + if (copy_remote_dir_local(fd, rpath, lpath, 0)) { + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } + } else { + fprintf(stderr,"remote object '%s' not a file or directory\n", rpath); + return 1; + } +} + +int do_sync_sync(const char *lpath, const char *rpath, int listonly) +{ + fprintf(stderr,"syncing %s...\n",rpath); + + int fd = adb_connect("sync:"); + if(fd < 0) { + fprintf(stderr,"error: %s\n", adb_error()); + return 1; + } + + BEGIN(); + if(copy_local_dir_remote(fd, lpath, rpath, 1, listonly)){ + return 1; + } else { + END(); + sync_quit(fd); + return 0; + } +} diff --git a/adb/file_sync_service.c b/adb/file_sync_service.c new file mode 100644 index 0000000..a231e93 --- /dev/null +++ b/adb/file_sync_service.c @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> +#include <utime.h> + +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_SYNC +#include "adb.h" +#include "file_sync_service.h" + +static int mkdirs(char *name) +{ + int ret; + char *x = name + 1; + + if(name[0] != '/') return -1; + + for(;;) { + x = adb_dirstart(x); + if(x == 0) return 0; + *x = 0; + ret = adb_mkdir(name, 0775); + if((ret < 0) && (errno != EEXIST)) { + D("mkdir(\"%s\") -> %s\n", name, strerror(errno)); + *x = '/'; + return ret; + } + *x++ = '/'; + } + return 0; +} + +static int do_stat(int s, const char *path) +{ + syncmsg msg; + struct stat st; + + msg.stat.id = ID_STAT; + + if(lstat(path, &st)) { + msg.stat.mode = 0; + msg.stat.size = 0; + msg.stat.time = 0; + } else { + msg.stat.mode = htoll(st.st_mode); + msg.stat.size = htoll(st.st_size); + msg.stat.time = htoll(st.st_mtime); + } + + return writex(s, &msg.stat, sizeof(msg.stat)); +} + +static int do_list(int s, const char *path) +{ + DIR *d; + struct dirent *de; + struct stat st; + syncmsg msg; + int len; + + char tmp[1024 + 256 + 1]; + char *fname; + + len = strlen(path); + memcpy(tmp, path, len); + tmp[len] = '/'; + fname = tmp + len + 1; + + msg.dent.id = ID_DENT; + + d = opendir(path); + if(d == 0) goto done; + + while((de = readdir(d))) { + int len = strlen(de->d_name); + + /* not supposed to be possible, but + if it does happen, let's not buffer overrun */ + if(len > 256) continue; + + strcpy(fname, de->d_name); + if(lstat(tmp, &st) == 0) { + msg.dent.mode = htoll(st.st_mode); + msg.dent.size = htoll(st.st_size); + msg.dent.time = htoll(st.st_mtime); + msg.dent.namelen = htoll(len); + + if(writex(s, &msg.dent, sizeof(msg.dent)) || + writex(s, de->d_name, len)) { + return -1; + } + } + } + + closedir(d); + +done: + msg.dent.id = ID_DONE; + msg.dent.mode = 0; + msg.dent.size = 0; + msg.dent.time = 0; + msg.dent.namelen = 0; + return writex(s, &msg.dent, sizeof(msg.dent)); +} + +static int fail_message(int s, const char *reason) +{ + syncmsg msg; + int len = strlen(reason); + + D("sync: failure: %s\n", reason); + + msg.data.id = ID_FAIL; + msg.data.size = htoll(len); + if(writex(s, &msg.data, sizeof(msg.data)) || + writex(s, reason, len)) { + return -1; + } else { + return 0; + } +} + +static int fail_errno(int s) +{ + return fail_message(s, strerror(errno)); +} + +static int handle_send_file(int s, char *path, mode_t mode, char *buffer) +{ + syncmsg msg; + unsigned int timestamp = 0; + int fd; + + fd = adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL, mode); + if(fd < 0 && errno == ENOENT) { + mkdirs(path); + fd = adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL, mode); + } + if(fd < 0 && errno == EEXIST) { + fd = adb_open_mode(path, O_WRONLY, mode); + } + if(fd < 0) { + if(fail_errno(s)) + return -1; + fd = -1; + } + + for(;;) { + unsigned int len; + + if(readx(s, &msg.data, sizeof(msg.data))) + goto fail; + + if(msg.data.id != ID_DATA) { + if(msg.data.id == ID_DONE) { + timestamp = ltohl(msg.data.size); + break; + } + fail_message(s, "invalid data message"); + goto fail; + } + len = ltohl(msg.data.size); + if(len > SYNC_DATA_MAX) { + fail_message(s, "oversize data message"); + goto fail; + } + if(readx(s, buffer, len)) + goto fail; + + if(fd < 0) + continue; + if(writex(fd, buffer, len)) { + adb_close(fd); + adb_unlink(path); + fd = -1; + if(fail_errno(s)) return -1; + } + } + + if(fd >= 0) { + struct utimbuf u; + adb_close(fd); + u.actime = timestamp; + u.modtime = timestamp; + utime(path, &u); + + msg.status.id = ID_OKAY; + msg.status.msglen = 0; + if(writex(s, &msg.status, sizeof(msg.status))) + return -1; + } + return 0; + +fail: + if(fd >= 0) + adb_close(fd); + adb_unlink(path); + return -1; +} + +#ifdef HAVE_SYMLINKS +static int handle_send_link(int s, char *path, char *buffer) +{ + syncmsg msg; + unsigned int len; + int ret; + + if(readx(s, &msg.data, sizeof(msg.data))) + return -1; + + if(msg.data.id != ID_DATA) { + fail_message(s, "invalid data message: expected ID_DATA"); + return -1; + } + + len = ltohl(msg.data.size); + if(len > SYNC_DATA_MAX) { + fail_message(s, "oversize data message"); + return -1; + } + if(readx(s, buffer, len)) + return -1; + + ret = symlink(buffer, path); + if(ret && errno == ENOENT) { + mkdirs(path); + ret = symlink(buffer, path); + } + if(ret) { + fail_errno(s); + return -1; + } + + if(readx(s, &msg.data, sizeof(msg.data))) + return -1; + + if(msg.data.id == ID_DONE) { + msg.status.id = ID_OKAY; + msg.status.msglen = 0; + if(writex(s, &msg.status, sizeof(msg.status))) + return -1; + } else { + fail_message(s, "invalid data message: expected ID_DONE"); + return -1; + } + + return 0; +} +#endif /* HAVE_SYMLINKS */ + +static int do_send(int s, char *path, char *buffer) +{ + char *tmp; + mode_t mode; + int is_link, ret; + + tmp = strrchr(path,','); + if(tmp) { + *tmp = 0; + errno = 0; + mode = strtoul(tmp + 1, NULL, 0); +#ifndef HAVE_SYMLINKS + is_link = 0; +#else + is_link = S_ISLNK(mode); +#endif + mode &= 0777; + } + if(!tmp || errno) { + mode = 0644; + is_link = 0; + } + + adb_unlink(path); + + +#ifdef HAVE_SYMLINKS + if(is_link) + ret = handle_send_link(s, path, buffer); + else { +#else + { +#endif + /* copy user permission bits to "group" and "other" permissions */ + mode |= ((mode >> 3) & 0070); + mode |= ((mode >> 3) & 0007); + + ret = handle_send_file(s, path, mode, buffer); + } + + return ret; +} + +static int do_recv(int s, const char *path, char *buffer) +{ + syncmsg msg; + int fd, r; + + fd = adb_open(path, O_RDONLY); + if(fd < 0) { + if(fail_errno(s)) return -1; + return 0; + } + + msg.data.id = ID_DATA; + for(;;) { + r = adb_read(fd, buffer, SYNC_DATA_MAX); + if(r <= 0) { + if(r == 0) break; + if(errno == EINTR) continue; + r = fail_errno(s); + adb_close(fd); + return r; + } + msg.data.size = htoll(r); + if(writex(s, &msg.data, sizeof(msg.data)) || + writex(s, buffer, r)) { + adb_close(fd); + return -1; + } + } + + adb_close(fd); + + msg.data.id = ID_DONE; + msg.data.size = 0; + if(writex(s, &msg.data, sizeof(msg.data))) { + return -1; + } + + return 0; +} + +void file_sync_service(int fd, void *cookie) +{ + syncmsg msg; + char name[1025]; + unsigned namelen; + + char *buffer = malloc(SYNC_DATA_MAX); + if(buffer == 0) goto fail; + + for(;;) { + D("sync: waiting for command\n"); + + if(readx(fd, &msg.req, sizeof(msg.req))) { + fail_message(fd, "command read failure"); + break; + } + namelen = ltohl(msg.req.namelen); + if(namelen > 1024) { + fail_message(fd, "invalid namelen"); + break; + } + if(readx(fd, name, namelen)) { + fail_message(fd, "filename read failure"); + break; + } + name[namelen] = 0; + + msg.req.namelen = 0; + D("sync: '%s' '%s'\n", (char*) &msg.req, name); + + switch(msg.req.id) { + case ID_STAT: + if(do_stat(fd, name)) goto fail; + break; + case ID_LIST: + if(do_list(fd, name)) goto fail; + break; + case ID_SEND: + if(do_send(fd, name, buffer)) goto fail; + break; + case ID_RECV: + if(do_recv(fd, name, buffer)) goto fail; + break; + case ID_QUIT: + goto fail; + default: + fail_message(fd, "unknown command"); + goto fail; + } + } + +fail: + if(buffer != 0) free(buffer); + D("sync: done\n"); + adb_close(fd); +} diff --git a/adb/file_sync_service.h b/adb/file_sync_service.h new file mode 100644 index 0000000..11ea06b --- /dev/null +++ b/adb/file_sync_service.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _FILE_SYNC_SERVICE_H_ +#define _FILE_SYNC_SERVICE_H_ + +#ifdef __ppc__ +static inline unsigned __swap_uint32(unsigned x) +{ + return (((x) & 0xFF000000) >> 24) + | (((x) & 0x00FF0000) >> 8) + | (((x) & 0x0000FF00) << 8) + | (((x) & 0x000000FF) << 24); +} +#define htoll(x) __swap_uint32(x) +#define ltohl(x) __swap_uint32(x) +#define MKID(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((a) << 24)) +#else +#define htoll(x) (x) +#define ltohl(x) (x) +#define MKID(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)) +#endif + +#define ID_STAT MKID('S','T','A','T') +#define ID_LIST MKID('L','I','S','T') +#define ID_ULNK MKID('U','L','N','K') +#define ID_SEND MKID('S','E','N','D') +#define ID_RECV MKID('R','E','C','V') +#define ID_DENT MKID('D','E','N','T') +#define ID_DONE MKID('D','O','N','E') +#define ID_DATA MKID('D','A','T','A') +#define ID_OKAY MKID('O','K','A','Y') +#define ID_FAIL MKID('F','A','I','L') +#define ID_QUIT MKID('Q','U','I','T') + +typedef union { + unsigned id; + struct { + unsigned id; + unsigned namelen; + } req; + struct { + unsigned id; + unsigned mode; + unsigned size; + unsigned time; + } stat; + struct { + unsigned id; + unsigned mode; + unsigned size; + unsigned time; + unsigned namelen; + } dent; + struct { + unsigned id; + unsigned size; + } data; + struct { + unsigned id; + unsigned msglen; + } status; +} syncmsg; + + +void file_sync_service(int fd, void *cookie); +int do_sync_ls(const char *path); +int do_sync_push(const char *lpath, const char *rpath, int verifyApk); +int do_sync_sync(const char *lpath, const char *rpath, int listonly); +int do_sync_pull(const char *rpath, const char *lpath); + +#define SYNC_DATA_MAX (64*1024) + +#endif diff --git a/adb/framebuffer_service.c b/adb/framebuffer_service.c new file mode 100644 index 0000000..6d50f8e --- /dev/null +++ b/adb/framebuffer_service.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#ifdef USE_SCREENCAP +#include <errno.h> +#include <hardware/hardware.h> + +#include "sysdeps.h" +#endif +#include "fdevent.h" +#include "adb.h" + +#ifndef USE_SCREENCAP +#include <linux/fb.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#endif + +/* TODO: +** - sync with vsync to avoid tearing +*/ +/* This version number defines the format of the fbinfo struct. + It must match versioning in ddms where this data is consumed. */ +#define DDMS_RAWIMAGE_VERSION 1 +struct fbinfo { + unsigned int version; + unsigned int bpp; + unsigned int size; + unsigned int width; + unsigned int height; + unsigned int red_offset; + unsigned int red_length; + unsigned int blue_offset; + unsigned int blue_length; + unsigned int green_offset; + unsigned int green_length; + unsigned int alpha_offset; + unsigned int alpha_length; +} __attribute__((packed)); + +#ifdef USE_SCREENCAP +static int fill_format(struct fbinfo* info, int format) +{ + // bpp, red, green, blue, alpha + + static const int format_map[][9] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0}, // INVALID + {32, 0, 8, 8, 8, 16, 8, 24, 8}, // HAL_PIXEL_FORMAT_RGBA_8888 + {32, 0, 8, 8, 8, 16, 8, 0, 0}, // HAL_PIXEL_FORMAT_RGBX_8888 + {24, 16, 8, 8, 8, 0, 8, 0, 0}, // HAL_PIXEL_FORMAT_RGB_888 + {16, 11, 5, 5, 6, 0, 5, 0, 0}, // HAL_PIXEL_FORMAT_RGB_565 + {32, 16, 8, 8, 8, 0, 8, 24, 8}, // HAL_PIXEL_FORMAT_BGRA_8888 + {16, 11, 5, 6, 5, 1, 5, 0, 1}, // HAL_PIXEL_FORMAT_RGBA_5551 + {16, 12, 4, 8, 4, 4, 4, 0, 4} // HAL_PIXEL_FORMAT_RGBA_4444 + }; + const int *p; + + if (format == 0 || format > HAL_PIXEL_FORMAT_RGBA_4444) + return -ENOTSUP; + + p = format_map[format]; + + info->bpp = *(p++); + info->red_offset = *(p++); + info->red_length = *(p++); + info->green_offset = *(p++); + info->green_length = *(p++); + info->blue_offset = *(p++); + info->blue_length = *(p++); + info->alpha_offset = *(p++); + info->alpha_length = *(p++); + + return 0; +} + +void framebuffer_service(int fd, void *cookie) +{ + char x[512]; + + struct fbinfo fbinfo; + int w, h, f; + int s[2]; + int fb; + unsigned int i; + int rv; + + if(adb_socketpair(s)) { + printf("cannot create service socket pair\n"); + goto done; + } + + pid_t pid = fork(); + if (pid < 0) { + printf("- fork failed: %s -\n", strerror(errno)); + goto done; + } + + if (pid == 0) { + dup2(s[1], STDOUT_FILENO); + const char* cmd_path = "/system/bin/screencap"; + const char* cmd = "screencap"; + execl(cmd_path, cmd, NULL); + exit(1); + } + + fb = s[0]; + + /* read w, h, f */ + if (readx(fb, &w, 4)) goto done; + if (readx(fb, &h, 4)) goto done; + if (readx(fb, &f, 4)) goto done; + + fbinfo.version = DDMS_RAWIMAGE_VERSION; + fbinfo.size = w * h * 4; + fbinfo.width = w; + fbinfo.height = h; + rv = fill_format(&fbinfo, f); + if (rv != 0) goto done; + + if(writex(fd, &fbinfo, sizeof(fbinfo))) goto done; + + for(i = 0; i < fbinfo.size; i += sizeof(x)) { + if(readx(fb, &x, sizeof(x))) goto done; + if(writex(fd, &x, sizeof(x))) goto done; + } + + if(readx(fb, &x, fbinfo.size % sizeof(x))) goto done; + if(writex(fd, &x, fbinfo.size % sizeof(x))) goto done; + +done: + adb_close(s[0]); + adb_close(s[1]); + adb_close(fd); +} +#else +void framebuffer_service(int fd, void *cookie) +{ + struct fb_var_screeninfo vinfo; + int fb, offset; + char x[256]; + + struct fbinfo fbinfo; + unsigned i, bytespp; + + fb = open("/dev/graphics/fb0", O_RDONLY); + if(fb < 0) goto done; + + if(ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0) goto done; + fcntl(fb, F_SETFD, FD_CLOEXEC); + + bytespp = vinfo.bits_per_pixel / 8; + + fbinfo.version = DDMS_RAWIMAGE_VERSION; + fbinfo.bpp = vinfo.bits_per_pixel; + fbinfo.size = vinfo.xres * vinfo.yres * bytespp; + fbinfo.width = vinfo.xres; + fbinfo.height = vinfo.yres; + fbinfo.red_offset = vinfo.red.offset; + fbinfo.red_length = vinfo.red.length; + fbinfo.green_offset = vinfo.green.offset; + fbinfo.green_length = vinfo.green.length; + fbinfo.blue_offset = vinfo.blue.offset; + fbinfo.blue_length = vinfo.blue.length; + fbinfo.alpha_offset = vinfo.transp.offset; + fbinfo.alpha_length = vinfo.transp.length; + + /* HACK: for several of our 3d cores a specific alignment + * is required so the start of the fb may not be an integer number of lines + * from the base. As a result we are storing the additional offset in + * xoffset. This is not the correct usage for xoffset, it should be added + * to each line, not just once at the beginning */ + offset = vinfo.xoffset * bytespp; + + offset += vinfo.xres * vinfo.yoffset * bytespp; + + if(writex(fd, &fbinfo, sizeof(fbinfo))) goto done; + + lseek(fb, offset, SEEK_SET); + for(i = 0; i < fbinfo.size; i += 256) { + if(readx(fb, &x, 256)) goto done; + if(writex(fd, &x, 256)) goto done; + } + + if(readx(fb, &x, fbinfo.size % 256)) goto done; + if(writex(fd, &x, fbinfo.size % 256)) goto done; + +done: + if(fb >= 0) close(fb); + close(fd); +} +#endif diff --git a/adb/get_my_path_darwin.c b/adb/get_my_path_darwin.c new file mode 100644 index 0000000..5b95d15 --- /dev/null +++ b/adb/get_my_path_darwin.c @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Carbon/Carbon.h> +#include <unistd.h> + +void get_my_path(char *s, size_t maxLen) +{ + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + CFDictionaryRef dict; + dict = ProcessInformationCopyDictionary(&psn, 0xffffffff); + CFStringRef value = (CFStringRef)CFDictionaryGetValue(dict, + CFSTR("CFBundleExecutable")); + CFStringGetCString(value, s, maxLen, kCFStringEncodingUTF8); +} + diff --git a/adb/get_my_path_freebsd.c b/adb/get_my_path_freebsd.c new file mode 100644 index 0000000..b06ec66 --- /dev/null +++ b/adb/get_my_path_freebsd.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009 bsdroid project + * Alexey Tarasov <tarasov@dodologics.com> + * + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <limits.h> +#include <stdio.h> + +void +get_my_path(char *exe, size_t maxLen) +{ + char proc[64]; + + snprintf(proc, sizeof(proc), "/proc/%d/file", getpid()); + + int err = readlink(proc, exe, maxLen - 1); + + exe[err > 0 ? err : 0] = '\0'; +} + diff --git a/adb/get_my_path_linux.c b/adb/get_my_path_linux.c new file mode 100644 index 0000000..179c3dd --- /dev/null +++ b/adb/get_my_path_linux.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <limits.h> +#include <stdio.h> + +void get_my_path(char *exe, size_t maxLen) +{ + char proc[64]; + snprintf(proc, sizeof proc, "/proc/%d/exe", getpid()); + int err = readlink(proc, exe, maxLen - 1); + if(err > 0) { + exe[err] = '\0'; + } else { + exe[0] = '\0'; + } +} + diff --git a/adb/get_my_path_windows.c b/adb/get_my_path_windows.c new file mode 100644 index 0000000..ddf2816 --- /dev/null +++ b/adb/get_my_path_windows.c @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <limits.h> +#include <assert.h> +#include <windows.h> + +void get_my_path(char *exe, size_t maxLen) +{ + char *r; + + /* XXX: should be GetModuleFileNameA */ + if (GetModuleFileName(NULL, exe, maxLen) > 0) { + r = strrchr(exe, '\\'); + if (r != NULL) + *r = '\0'; + } else { + exe[0] = '\0'; + } +} + diff --git a/adb/jdwp_service.c b/adb/jdwp_service.c new file mode 100644 index 0000000..0c26f7b --- /dev/null +++ b/adb/jdwp_service.c @@ -0,0 +1,709 @@ +/* implement the "debug-ports" and "track-debug-ports" device services */ +#include "sysdeps.h" +#define TRACE_TAG TRACE_JDWP +#include "adb.h" +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/* here's how these things work. + + when adbd starts, it creates a unix server socket + named @vm-debug-control (@ is a shortcut for "first byte is zero" + to use the private namespace instead of the file system) + + when a new JDWP daemon thread starts in a new VM process, it creates + a connection to @vm-debug-control to announce its availability. + + + JDWP thread @vm-debug-control + | | + |-------------------------------> | + | hello I'm in process <pid> | + | | + | | + + the connection is kept alive. it will be closed automatically if + the JDWP process terminates (this allows adbd to detect dead + processes). + + adbd thus maintains a list of "active" JDWP processes. it can send + its content to clients through the "device:debug-ports" service, + or even updates through the "device:track-debug-ports" service. + + when a debugger wants to connect, it simply runs the command + equivalent to "adb forward tcp:<hostport> jdwp:<pid>" + + "jdwp:<pid>" is a new forward destination format used to target + a given JDWP process on the device. when sutch a request arrives, + adbd does the following: + + - first, it calls socketpair() to create a pair of equivalent + sockets. + + - it attaches the first socket in the pair to a local socket + which is itself attached to the transport's remote socket: + + + - it sends the file descriptor of the second socket directly + to the JDWP process with the help of sendmsg() + + + JDWP thread @vm-debug-control + | | + | <----------------------| + | OK, try this file descriptor | + | | + | | + + then, the JDWP thread uses this new socket descriptor as its + pass-through connection to the debugger (and receives the + JDWP-Handshake message, answers to it, etc...) + + this gives the following graphics: + ____________________________________ + | | + | ADB Server (host) | + | | + Debugger <---> LocalSocket <----> RemoteSocket | + | ^^ | + |___________________________||_______| + || + Transport || + (TCP for emulator - USB for device) || + || + ___________________________||_______ + | || | + | ADBD (device) || | + | VV | + JDWP <======> LocalSocket <----> RemoteSocket | + | | + |____________________________________| + + due to the way adb works, this doesn't need a special socket + type or fancy handling of socket termination if either the debugger + or the JDWP process closes the connection. + + THIS IS THE SIMPLEST IMPLEMENTATION I COULD FIND, IF YOU HAPPEN + TO HAVE A BETTER IDEA, LET ME KNOW - Digit + +**********************************************************************/ + +/** JDWP PID List Support Code + ** for each JDWP process, we record its pid and its connected socket + **/ + +#define MAX_OUT_FDS 4 + +#if !ADB_HOST + +#include <sys/socket.h> +#include <sys/un.h> + +typedef struct JdwpProcess JdwpProcess; +struct JdwpProcess { + JdwpProcess* next; + JdwpProcess* prev; + int pid; + int socket; + fdevent* fde; + + char in_buff[4]; /* input character to read PID */ + int in_len; /* number from JDWP process */ + + int out_fds[MAX_OUT_FDS]; /* output array of file descriptors */ + int out_count; /* to send to the JDWP process */ +}; + +static JdwpProcess _jdwp_list; + +static int +jdwp_process_list( char* buffer, int bufferlen ) +{ + char* end = buffer + bufferlen; + char* p = buffer; + JdwpProcess* proc = _jdwp_list.next; + + for ( ; proc != &_jdwp_list; proc = proc->next ) { + int len; + + /* skip transient connections */ + if (proc->pid < 0) + continue; + + len = snprintf(p, end-p, "%d\n", proc->pid); + if (p + len >= end) + break; + p += len; + } + p[0] = 0; + return (p - buffer); +} + + +static int +jdwp_process_list_msg( char* buffer, int bufferlen ) +{ + char head[5]; + int len = jdwp_process_list( buffer+4, bufferlen-4 ); + snprintf(head, sizeof head, "%04x", len); + memcpy(buffer, head, 4); + return len + 4; +} + + +static void jdwp_process_list_updated(void); + +static void +jdwp_process_free( JdwpProcess* proc ) +{ + if (proc) { + int n; + + proc->prev->next = proc->next; + proc->next->prev = proc->prev; + + if (proc->socket >= 0) { + adb_shutdown(proc->socket); + adb_close(proc->socket); + proc->socket = -1; + } + + if (proc->fde != NULL) { + fdevent_destroy(proc->fde); + proc->fde = NULL; + } + proc->pid = -1; + + for (n = 0; n < proc->out_count; n++) { + adb_close(proc->out_fds[n]); + } + proc->out_count = 0; + + free(proc); + + jdwp_process_list_updated(); + } +} + + +static void jdwp_process_event(int, unsigned, void*); /* forward */ + + +static JdwpProcess* +jdwp_process_alloc( int socket ) +{ + JdwpProcess* proc = calloc(1,sizeof(*proc)); + + if (proc == NULL) { + D("not enough memory to create new JDWP process\n"); + return NULL; + } + + proc->socket = socket; + proc->pid = -1; + proc->next = proc; + proc->prev = proc; + + proc->fde = fdevent_create( socket, jdwp_process_event, proc ); + if (proc->fde == NULL) { + D("could not create fdevent for new JDWP process\n" ); + free(proc); + return NULL; + } + + proc->fde->state |= FDE_DONT_CLOSE; + proc->in_len = 0; + proc->out_count = 0; + + /* append to list */ + proc->next = &_jdwp_list; + proc->prev = proc->next->prev; + + proc->prev->next = proc; + proc->next->prev = proc; + + /* start by waiting for the PID */ + fdevent_add(proc->fde, FDE_READ); + + return proc; +} + + +static void +jdwp_process_event( int socket, unsigned events, void* _proc ) +{ + JdwpProcess* proc = _proc; + + if (events & FDE_READ) { + if (proc->pid < 0) { + /* read the PID as a 4-hexchar string */ + char* p = proc->in_buff + proc->in_len; + int size = 4 - proc->in_len; + char temp[5]; + while (size > 0) { + int len = recv( socket, p, size, 0 ); + if (len < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + return; + /* this can fail here if the JDWP process crashes very fast */ + D("weird unknown JDWP process failure: %s\n", + strerror(errno)); + + goto CloseProcess; + } + if (len == 0) { /* end of stream ? */ + D("weird end-of-stream from unknown JDWP process\n"); + goto CloseProcess; + } + p += len; + proc->in_len += len; + size -= len; + } + /* we have read 4 characters, now decode the pid */ + memcpy(temp, proc->in_buff, 4); + temp[4] = 0; + + if (sscanf( temp, "%04x", &proc->pid ) != 1) { + D("could not decode JDWP %p PID number: '%s'\n", proc, temp); + goto CloseProcess; + } + + /* all is well, keep reading to detect connection closure */ + D("Adding pid %d to jdwp process list\n", proc->pid); + jdwp_process_list_updated(); + } + else + { + /* the pid was read, if we get there it's probably because the connection + * was closed (e.g. the JDWP process exited or crashed) */ + char buf[32]; + + for (;;) { + int len = recv(socket, buf, sizeof(buf), 0); + + if (len <= 0) { + if (len < 0 && errno == EINTR) + continue; + if (len < 0 && errno == EAGAIN) + return; + else { + D("terminating JDWP %d connection: %s\n", proc->pid, + strerror(errno)); + break; + } + } + else { + D( "ignoring unexpected JDWP %d control socket activity (%d bytes)\n", + proc->pid, len ); + } + } + + CloseProcess: + if (proc->pid >= 0) + D( "remove pid %d to jdwp process list\n", proc->pid ); + jdwp_process_free(proc); + return; + } + } + + if (events & FDE_WRITE) { + D("trying to write to JDWP pid controli (count=%d first=%d) %d\n", + proc->pid, proc->out_count, proc->out_fds[0]); + if (proc->out_count > 0) { + int fd = proc->out_fds[0]; + int n, ret; + struct cmsghdr* cmsg; + struct msghdr msg; + struct iovec iov; + char dummy = '!'; + char buffer[sizeof(struct cmsghdr) + sizeof(int)]; + + iov.iov_base = &dummy; + iov.iov_len = 1; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = buffer; + msg.msg_controllen = sizeof(buffer); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = msg.msg_controllen; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + ((int*)CMSG_DATA(cmsg))[0] = fd; + + for (;;) { + ret = sendmsg(proc->socket, &msg, 0); + if (ret >= 0) + break; + if (errno == EINTR) + continue; + D("sending new file descriptor to JDWP %d failed: %s\n", + proc->pid, strerror(errno)); + goto CloseProcess; + } + + D("sent file descriptor %d to JDWP process %d\n", + fd, proc->pid); + + for (n = 1; n < proc->out_count; n++) + proc->out_fds[n-1] = proc->out_fds[n]; + + if (--proc->out_count == 0) + fdevent_del( proc->fde, FDE_WRITE ); + } + } +} + + +int +create_jdwp_connection_fd(int pid) +{ + JdwpProcess* proc = _jdwp_list.next; + + D("looking for pid %d in JDWP process list\n", pid); + for ( ; proc != &_jdwp_list; proc = proc->next ) { + if (proc->pid == pid) { + goto FoundIt; + } + } + D("search failed !!\n"); + return -1; + +FoundIt: + { + int fds[2]; + + if (proc->out_count >= MAX_OUT_FDS) { + D("%s: too many pending JDWP connection for pid %d\n", + __FUNCTION__, pid); + return -1; + } + + if (adb_socketpair(fds) < 0) { + D("%s: socket pair creation failed: %s\n", + __FUNCTION__, strerror(errno)); + return -1; + } + + proc->out_fds[ proc->out_count ] = fds[1]; + if (++proc->out_count == 1) + fdevent_add( proc->fde, FDE_WRITE ); + + return fds[0]; + } +} + +/** VM DEBUG CONTROL SOCKET + ** + ** we do implement a custom asocket to receive the data + **/ + +/* name of the debug control Unix socket */ +#define JDWP_CONTROL_NAME "\0jdwp-control" +#define JDWP_CONTROL_NAME_LEN (sizeof(JDWP_CONTROL_NAME)-1) + +typedef struct { + int listen_socket; + fdevent* fde; + +} JdwpControl; + + +static void +jdwp_control_event(int s, unsigned events, void* user); + + +static int +jdwp_control_init( JdwpControl* control, + const char* sockname, + int socknamelen ) +{ + struct sockaddr_un addr; + socklen_t addrlen; + int s; + int maxpath = sizeof(addr.sun_path); + int pathlen = socknamelen; + + if (pathlen >= maxpath) { + D( "vm debug control socket name too long (%d extra chars)\n", + pathlen+1-maxpath ); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, sockname, socknamelen); + + s = socket( AF_UNIX, SOCK_STREAM, 0 ); + if (s < 0) { + D( "could not create vm debug control socket. %d: %s\n", + errno, strerror(errno)); + return -1; + } + + addrlen = (pathlen + sizeof(addr.sun_family)); + + if (bind(s, (struct sockaddr*)&addr, addrlen) < 0) { + D( "could not bind vm debug control socket: %d: %s\n", + errno, strerror(errno) ); + adb_close(s); + return -1; + } + + if ( listen(s, 4) < 0 ) { + D("listen failed in jdwp control socket: %d: %s\n", + errno, strerror(errno)); + adb_close(s); + return -1; + } + + control->listen_socket = s; + + control->fde = fdevent_create(s, jdwp_control_event, control); + if (control->fde == NULL) { + D( "could not create fdevent for jdwp control socket\n" ); + adb_close(s); + return -1; + } + + /* only wait for incoming connections */ + fdevent_add(control->fde, FDE_READ); + + D("jdwp control socket started (%d)\n", control->listen_socket); + return 0; +} + + +static void +jdwp_control_event( int s, unsigned events, void* _control ) +{ + JdwpControl* control = (JdwpControl*) _control; + + if (events & FDE_READ) { + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + int s = -1; + JdwpProcess* proc; + + do { + s = adb_socket_accept( control->listen_socket, &addr, &addrlen ); + if (s < 0) { + if (errno == EINTR) + continue; + if (errno == ECONNABORTED) { + /* oops, the JDWP process died really quick */ + D("oops, the JDWP process died really quick\n"); + return; + } + /* the socket is probably closed ? */ + D( "weird accept() failed on jdwp control socket: %s\n", + strerror(errno) ); + return; + } + } + while (s < 0); + + proc = jdwp_process_alloc( s ); + if (proc == NULL) + return; + } +} + + +static JdwpControl _jdwp_control; + +/** "jdwp" local service implementation + ** this simply returns the list of known JDWP process pids + **/ + +typedef struct { + asocket socket; + int pass; +} JdwpSocket; + +static void +jdwp_socket_close( asocket* s ) +{ + asocket* peer = s->peer; + + remove_socket(s); + + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + free(s); +} + +static int +jdwp_socket_enqueue( asocket* s, apacket* p ) +{ + /* you can't write to this asocket */ + put_apacket(p); + s->peer->close(s->peer); + return -1; +} + + +static void +jdwp_socket_ready( asocket* s ) +{ + JdwpSocket* jdwp = (JdwpSocket*)s; + asocket* peer = jdwp->socket.peer; + + /* on the first call, send the list of pids, + * on the second one, close the connection + */ + if (jdwp->pass == 0) { + apacket* p = get_apacket(); + p->len = jdwp_process_list((char*)p->data, MAX_PAYLOAD); + peer->enqueue(peer, p); + jdwp->pass = 1; + } + else { + peer->close(peer); + } +} + +asocket* +create_jdwp_service_socket( void ) +{ + JdwpSocket* s = calloc(sizeof(*s),1); + + if (s == NULL) + return NULL; + + install_local_socket(&s->socket); + + s->socket.ready = jdwp_socket_ready; + s->socket.enqueue = jdwp_socket_enqueue; + s->socket.close = jdwp_socket_close; + s->pass = 0; + + return &s->socket; +} + +/** "track-jdwp" local service implementation + ** this periodically sends the list of known JDWP process pids + ** to the client... + **/ + +typedef struct JdwpTracker JdwpTracker; + +struct JdwpTracker { + asocket socket; + JdwpTracker* next; + JdwpTracker* prev; + int need_update; +}; + +static JdwpTracker _jdwp_trackers_list; + + +static void +jdwp_process_list_updated(void) +{ + char buffer[1024]; + int len; + JdwpTracker* t = _jdwp_trackers_list.next; + + len = jdwp_process_list_msg(buffer, sizeof(buffer)); + + for ( ; t != &_jdwp_trackers_list; t = t->next ) { + apacket* p = get_apacket(); + asocket* peer = t->socket.peer; + memcpy(p->data, buffer, len); + p->len = len; + peer->enqueue( peer, p ); + } +} + +static void +jdwp_tracker_close( asocket* s ) +{ + JdwpTracker* tracker = (JdwpTracker*) s; + asocket* peer = s->peer; + + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + + remove_socket(s); + + tracker->prev->next = tracker->next; + tracker->next->prev = tracker->prev; + + free(s); +} + +static void +jdwp_tracker_ready( asocket* s ) +{ + JdwpTracker* t = (JdwpTracker*) s; + + if (t->need_update) { + apacket* p = get_apacket(); + t->need_update = 0; + p->len = jdwp_process_list_msg((char*)p->data, sizeof(p->data)); + s->peer->enqueue(s->peer, p); + } +} + +static int +jdwp_tracker_enqueue( asocket* s, apacket* p ) +{ + /* you can't write to this socket */ + put_apacket(p); + s->peer->close(s->peer); + return -1; +} + + +asocket* +create_jdwp_tracker_service_socket( void ) +{ + JdwpTracker* t = calloc(sizeof(*t),1); + + if (t == NULL) + return NULL; + + t->next = &_jdwp_trackers_list; + t->prev = t->next->prev; + + t->next->prev = t; + t->prev->next = t; + + install_local_socket(&t->socket); + + t->socket.ready = jdwp_tracker_ready; + t->socket.enqueue = jdwp_tracker_enqueue; + t->socket.close = jdwp_tracker_close; + t->need_update = 1; + + return &t->socket; +} + + +int +init_jdwp(void) +{ + _jdwp_list.next = &_jdwp_list; + _jdwp_list.prev = &_jdwp_list; + + _jdwp_trackers_list.next = &_jdwp_trackers_list; + _jdwp_trackers_list.prev = &_jdwp_trackers_list; + + return jdwp_control_init( &_jdwp_control, + JDWP_CONTROL_NAME, + JDWP_CONTROL_NAME_LEN ); +} + +#endif /* !ADB_HOST */ + diff --git a/adb/log_service.c b/adb/log_service.c new file mode 100644 index 0000000..6e9bdee --- /dev/null +++ b/adb/log_service.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/socket.h> +#include <cutils/logger.h> +#include "sysdeps.h" +#include "adb.h" + +#define LOG_FILE_DIR "/dev/log/" + +void write_log_entry(int fd, struct logger_entry *buf); + +void log_service(int fd, void *cookie) +{ + /* get the name of the log filepath to read */ + char * log_filepath = cookie; + + /* open the log file. */ + int logfd = unix_open(log_filepath, O_RDONLY); + if (logfd < 0) { + goto done; + } + + // temp buffer to read the entries + unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1] __attribute__((aligned(4))); + struct logger_entry *entry = (struct logger_entry *) buf; + + while (1) { + int ret; + + ret = unix_read(logfd, entry, LOGGER_ENTRY_MAX_LEN); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + // perror("logcat read"); + goto done; + } + else if (!ret) { + // fprintf(stderr, "read: Unexpected EOF!\n"); + goto done; + } + + /* NOTE: driver guarantees we read exactly one full entry */ + + entry->msg[entry->len] = '\0'; + + write_log_entry(fd, entry); + } + +done: + unix_close(fd); + free(log_filepath); +} + +/* returns the full path to the log file in a newly allocated string */ +char * get_log_file_path(const char * log_name) { + char *log_device = malloc(strlen(LOG_FILE_DIR) + strlen(log_name) + 1); + + strcpy(log_device, LOG_FILE_DIR); + strcat(log_device, log_name); + + return log_device; +} + + +/* prints one log entry into the file descriptor fd */ +void write_log_entry(int fd, struct logger_entry *buf) +{ + size_t size = sizeof(struct logger_entry) + buf->len; + + writex(fd, buf, size); +} diff --git a/adb/mutex_list.h b/adb/mutex_list.h new file mode 100644 index 0000000..eebe0df --- /dev/null +++ b/adb/mutex_list.h @@ -0,0 +1,14 @@ +/* the list of mutexes used by addb */ +#ifndef ADB_MUTEX +#error ADB_MUTEX not defined when including this file +#endif + +ADB_MUTEX(dns_lock) +ADB_MUTEX(socket_list_lock) +ADB_MUTEX(transport_lock) +#if ADB_HOST +ADB_MUTEX(local_transports_lock) +#endif +ADB_MUTEX(usb_lock) + +#undef ADB_MUTEX diff --git a/adb/protocol.txt b/adb/protocol.txt new file mode 100644 index 0000000..398d042 --- /dev/null +++ b/adb/protocol.txt @@ -0,0 +1,252 @@ + +--- a replacement for aproto ------------------------------------------- + +When it comes down to it, aproto's primary purpose is to forward +various streams between the host computer and client device (in either +direction). + +This replacement further simplifies the concept, reducing the protocol +to an extremely straightforward model optimized to accomplish the +forwarding of these streams and removing additional state or +complexity. + +The host side becomes a simple comms bridge with no "UI", which will +be used by either commandline or interactive tools to communicate with +a device or emulator that is connected to the bridge. + +The protocol is designed to be straightforward and well-defined enough +that if it needs to be reimplemented in another environment (Java +perhaps), there should not problems ensuring perfect interoperability. + +The protocol discards the layering aproto has and should allow the +implementation to be much more robust. + + +--- protocol overview and basics --------------------------------------- + +The transport layer deals in "messages", which consist of a 24 byte +header followed (optionally) by a payload. The header consists of 6 +32 bit words which are sent across the wire in little endian format. + +struct message { + unsigned command; /* command identifier constant */ + unsigned arg0; /* first argument */ + unsigned arg1; /* second argument */ + unsigned data_length; /* length of payload (0 is allowed) */ + unsigned data_crc32; /* crc32 of data payload */ + unsigned magic; /* command ^ 0xffffffff */ +}; + +Receipt of an invalid message header, corrupt message payload, or an +unrecognized command MUST result in the closing of the remote +connection. The protocol depends on shared state and any break in the +message stream will result in state getting out of sync. + +The following sections describe the six defined message types in +detail. Their format is COMMAND(arg0, arg1, payload) where the payload +is represented by a quoted string or an empty string if none should be +sent. + +The identifiers "local-id" and "remote-id" are always relative to the +*sender* of the message, so for a receiver, the meanings are effectively +reversed. + + + +--- CONNECT(version, maxdata, "system-identity-string") ---------------- + +The CONNECT message establishes the presence of a remote system. +The version is used to ensure protocol compatibility and maxdata +declares the maximum message body size that the remote system +is willing to accept. + +Currently, version=0x01000000 and maxdata=4096 + +Both sides send a CONNECT message when the connection between them is +established. Until a CONNECT message is received no other messages may +be sent. Any messages received before a CONNECT message MUST be ignored. + +If a CONNECT message is received with an unknown version or insufficiently +large maxdata value, the connection with the other side must be closed. + +The system identity string should be "<systemtype>:<serialno>:<banner>" +where systemtype is "bootloader", "device", or "host", serialno is some +kind of unique ID (or empty), and banner is a human-readable version +or identifier string (informational only). + + +--- OPEN(local-id, 0, "destination") ----------------------------------- + +The OPEN message informs the recipient that the sender has a stream +identified by local-id that it wishes to connect to the named +destination in the message payload. The local-id may not be zero. + +The OPEN message MUST result in either a READY message indicating that +the connection has been established (and identifying the other end) or +a CLOSE message, indicating failure. An OPEN message also implies +a READY message sent at the same time. + +Common destination naming conventions include: + +* "tcp:<host>:<port>" - host may be omitted to indicate localhost +* "udp:<host>:<port>" - host may be omitted to indicate localhost +* "local-dgram:<identifier>" +* "local-stream:<identifier>" +* "shell" - local shell service +* "upload" - service for pushing files across (like aproto's /sync) +* "fs-bridge" - FUSE protocol filesystem bridge + + +--- READY(local-id, remote-id, "") ------------------------------------- + +The READY message informs the recipient that the sender's stream +identified by local-id is ready for write messages and that it is +connected to the recipient's stream identified by remote-id. + +Neither the local-id nor the remote-id may be zero. + +A READY message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have been +closed while this message was in-flight. + +The local-id is ignored on all but the first READY message (where it +is used to establish the connection). Nonetheless, the local-id MUST +not change on later READY messages sent to the same stream. + + + +--- WRITE(0, remote-id, "data") ---------------------------------------- + +The WRITE message sends data to the recipient's stream identified by +remote-id. The payload MUST be <= maxdata in length. + +A WRITE message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have been +closed while this message was in-flight. + +A WRITE message may not be sent until a READY message is received. +Once a WRITE message is sent, an additional WRITE message may not be +sent until another READY message has been received. Recipients of +a WRITE message that is in violation of this requirement will CLOSE +the connection. + + +--- CLOSE(local-id, remote-id, "") ------------------------------------- + +The CLOSE message informs recipient that the connection between the +sender's stream (local-id) and the recipient's stream (remote-id) is +broken. The remote-id MUST not be zero, but the local-id MAY be zero +if this CLOSE indicates a failed OPEN. + +A CLOSE message containing a remote-id which does not map to an open +stream on the recipient's side is ignored. The stream may have +already been closed by the recipient while this message was in-flight. + +The recipient should not respond to a CLOSE message in any way. The +recipient should cancel pending WRITEs or CLOSEs, but this is not a +requirement, since they will be ignored. + + +--- SYNC(online, sequence, "") ----------------------------------------- + +The SYNC message is used by the io pump to make sure that stale +outbound messages are discarded when the connection to the remote side +is broken. It is only used internally to the bridge and never valid +to send across the wire. + +* when the connection to the remote side goes offline, the io pump + sends a SYNC(0, 0) and starts discarding all messages +* when the connection to the remote side is established, the io pump + sends a SYNC(1, token) and continues to discard messages +* when the io pump receives a matching SYNC(1, token), it once again + starts accepting messages to forward to the remote side + + +--- message command constants ------------------------------------------ + +#define A_SYNC 0x434e5953 +#define A_CNXN 0x4e584e43 +#define A_OPEN 0x4e45504f +#define A_OKAY 0x59414b4f +#define A_CLSE 0x45534c43 +#define A_WRTE 0x45545257 + + + +--- implementation details --------------------------------------------- + +The core of the bridge program will use three threads. One thread +will be a select/epoll loop to handle io between various inbound and +outbound connections and the connection to the remote side. + +The remote side connection will be implemented as two threads (one for +reading, one for writing) and a datagram socketpair to provide the +channel between the main select/epoll thread and the remote connection +threadpair. The reason for this is that for usb connections, the +kernel interface on linux and osx does not allow you to do meaningful +nonblocking IO. + +The endian swapping for the message headers will happen (as needed) in +the remote connection threadpair and that the rest of the program will +always treat message header values as native-endian. + +The bridge program will be able to have a number of mini-servers +compiled in. They will be published under known names (examples +"shell", "fs-bridge", etc) and upon receiving an OPEN() to such a +service, the bridge program will create a stream socketpair and spawn +a thread or subprocess to handle the io. + + +--- simplified / embedded implementation ------------------------------- + +For limited environments, like the bootloader, it is allowable to +support a smaller, fixed number of channels using pre-assigned channel +ID numbers such that only one stream may be connected to a bootloader +endpoint at any given time. The protocol remains unchanged, but the +"embedded" version of it is less dynamic. + +The bootloader will support two streams. A "bootloader:debug" stream, +which may be opened to get debug messages from the bootloader and a +"bootloader:control", stream which will support the set of basic +bootloader commands. + +Example command stream dialogues: + "flash_kernel,2515049,........\n" "okay\n" + "flash_ramdisk,5038,........\n" "fail,flash write error\n" + "bogus_command......" <CLOSE> + + +--- future expansion --------------------------------------------------- + +I plan on providing either a message or a special control stream so that +the client device could ask the host computer to setup inbound socket +translations on the fly on behalf of the client device. + + +The initial design does handshaking to provide flow control, with a +message flow that looks like: + + >OPEN <READY >WRITE <READY >WRITE <READY >WRITE <CLOSE + +The far side may choose to issue the READY message as soon as it receives +a WRITE or it may defer the READY until the write to the local stream +succeeds. A future version may want to do some level of windowing where +multiple WRITEs may be sent without requiring individual READY acks. + +------------------------------------------------------------------------ + +--- smartsockets ------------------------------------------------------- + +Port 5037 is used for smart sockets which allow a client on the host +side to request access to a service in the host adb daemon or in the +remote (device) daemon. The service is requested by ascii name, +preceeded by a 4 digit hex length. Upon successful connection an +"OKAY" response is sent, otherwise a "FAIL" message is returned. Once +connected the client is talking to that (remote or local) service. + +client: <hex4> <service-name> +server: "OKAY" + +client: <hex4> <service-name> +server: "FAIL" <hex4> <reason> + diff --git a/adb/remount_service.c b/adb/remount_service.c new file mode 100644 index 0000000..4cb41e7 --- /dev/null +++ b/adb/remount_service.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/mount.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_ADB +#include "adb.h" + + +static int system_ro = 1; + +/* Returns the device used to mount a directory in /proc/mounts */ +static char *find_mount(const char *dir) +{ + int fd; + int res; + int size; + char *token = NULL; + const char delims[] = "\n"; + char buf[4096]; + + fd = unix_open("/proc/mounts", O_RDONLY); + if (fd < 0) + return NULL; + + buf[sizeof(buf) - 1] = '\0'; + size = adb_read(fd, buf, sizeof(buf) - 1); + adb_close(fd); + + token = strtok(buf, delims); + + while (token) { + char mount_dev[256]; + char mount_dir[256]; + int mount_freq; + int mount_passno; + + res = sscanf(token, "%255s %255s %*s %*s %d %d\n", + mount_dev, mount_dir, &mount_freq, &mount_passno); + mount_dev[255] = 0; + mount_dir[255] = 0; + if (res == 4 && (strcmp(dir, mount_dir) == 0)) + return strdup(mount_dev); + + token = strtok(NULL, delims); + } + return NULL; +} + +/* Init mounts /system as read only, remount to enable writes. */ +static int remount_system() +{ + char *dev; + + if (system_ro == 0) { + return 0; + } + + dev = find_mount("/system"); + + if (!dev) + return -1; + + system_ro = mount(dev, "/system", "none", MS_REMOUNT, NULL); + + free(dev); + + return system_ro; +} + +static void write_string(int fd, const char* str) +{ + writex(fd, str, strlen(str)); +} + +void remount_service(int fd, void *cookie) +{ + int ret = remount_system(); + + if (!ret) + write_string(fd, "remount succeeded\n"); + else { + char buffer[200]; + snprintf(buffer, sizeof(buffer), "remount failed: %s\n", strerror(errno)); + write_string(fd, buffer); + } + + adb_close(fd); +} + diff --git a/adb/services.c b/adb/services.c new file mode 100644 index 0000000..a4edb44 --- /dev/null +++ b/adb/services.c @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_ADB +#include "adb.h" +#include "file_sync_service.h" + +#if ADB_HOST +# ifndef HAVE_WINSOCK +# include <netinet/in.h> +# include <netdb.h> +# endif +#else +# include <sys/reboot.h> +#endif + +typedef struct stinfo stinfo; + +struct stinfo { + void (*func)(int fd, void *cookie); + int fd; + void *cookie; +}; + + +void *service_bootstrap_func(void *x) +{ + stinfo *sti = x; + sti->func(sti->fd, sti->cookie); + free(sti); + return 0; +} + +#if ADB_HOST +ADB_MUTEX_DEFINE( dns_lock ); + +static void dns_service(int fd, void *cookie) +{ + char *hostname = cookie; + struct hostent *hp; + unsigned zero = 0; + + adb_mutex_lock(&dns_lock); + hp = gethostbyname(hostname); + free(cookie); + if(hp == 0) { + writex(fd, &zero, 4); + } else { + writex(fd, hp->h_addr, 4); + } + adb_mutex_unlock(&dns_lock); + adb_close(fd); +} +#else +extern int recovery_mode; + +static void recover_service(int s, void *cookie) +{ + unsigned char buf[4096]; + unsigned count = (unsigned) cookie; + int fd; + + fd = adb_creat("/tmp/update", 0644); + if(fd < 0) { + adb_close(s); + return; + } + + while(count > 0) { + unsigned xfer = (count > 4096) ? 4096 : count; + if(readx(s, buf, xfer)) break; + if(writex(fd, buf, xfer)) break; + count -= xfer; + } + + if(count == 0) { + writex(s, "OKAY", 4); + } else { + writex(s, "FAIL", 4); + } + adb_close(fd); + adb_close(s); + + fd = adb_creat("/tmp/update.begin", 0644); + adb_close(fd); +} + +void restart_root_service(int fd, void *cookie) +{ + char buf[100]; + char value[PROPERTY_VALUE_MAX]; + + if (getuid() == 0) { + snprintf(buf, sizeof(buf), "adbd is already running as root\n"); + writex(fd, buf, strlen(buf)); + adb_close(fd); + } else { + property_get("ro.debuggable", value, ""); + if (strcmp(value, "1") != 0) { + snprintf(buf, sizeof(buf), "adbd cannot run as root in production builds\n"); + writex(fd, buf, strlen(buf)); + adb_close(fd); + return; + } + + property_set("service.adb.root", "1"); + snprintf(buf, sizeof(buf), "restarting adbd as root\n"); + writex(fd, buf, strlen(buf)); + adb_close(fd); + + // quit, and init will restart us as root + sleep(1); + exit(1); + } +} + +void restart_tcp_service(int fd, void *cookie) +{ + char buf[100]; + char value[PROPERTY_VALUE_MAX]; + int port = (int)cookie; + + if (port <= 0) { + snprintf(buf, sizeof(buf), "invalid port\n"); + writex(fd, buf, strlen(buf)); + adb_close(fd); + return; + } + + snprintf(value, sizeof(value), "%d", port); + property_set("service.adb.tcp.port", value); + snprintf(buf, sizeof(buf), "restarting in TCP mode port: %d\n", port); + writex(fd, buf, strlen(buf)); + adb_close(fd); + + // quit, and init will restart us in TCP mode + sleep(1); + exit(1); +} + +void restart_usb_service(int fd, void *cookie) +{ + char buf[100]; + + property_set("service.adb.tcp.port", "0"); + snprintf(buf, sizeof(buf), "restarting in USB mode\n"); + writex(fd, buf, strlen(buf)); + adb_close(fd); + + // quit, and init will restart us in USB mode + sleep(1); + exit(1); +} + +void reboot_service(int fd, void *arg) +{ + char buf[100]; + int pid, ret; + +#ifdef RECOVERY_PRE_COMMAND + if (!strncmp((char *)arg,"recovery",8)) + system( RECOVERY_PRE_COMMAND ); +#endif + + sync(); + + /* Attempt to unmount the SD card first. + * No need to bother checking for errors. + */ + pid = fork(); + if (pid == 0) { + /* ask vdc to unmount it */ + execl("/system/bin/vdc", "/system/bin/vdc", "volume", "unmount", + getenv("EXTERNAL_STORAGE"), "force", NULL); + } else if (pid > 0) { + /* wait until vdc succeeds or fails */ + waitpid(pid, &ret, 0); + } + ret = __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, + LINUX_REBOOT_CMD_RESTART2, (char *)arg); + if (ret < 0) { + snprintf(buf, sizeof(buf), "reboot failed: %s\n", strerror(errno)); + writex(fd, buf, strlen(buf)); + } + free(arg); + adb_close(fd); +} + +#endif + +#if 0 +static void echo_service(int fd, void *cookie) +{ + char buf[4096]; + int r; + char *p; + int c; + + for(;;) { + r = read(fd, buf, 4096); + if(r == 0) goto done; + if(r < 0) { + if(errno == EINTR) continue; + else goto done; + } + + c = r; + p = buf; + while(c > 0) { + r = write(fd, p, c); + if(r > 0) { + c -= r; + p += r; + continue; + } + if((r < 0) && (errno == EINTR)) continue; + goto done; + } + } +done: + close(fd); +} +#endif + +static int create_service_thread(void (*func)(int, void *), void *cookie) +{ + stinfo *sti; + adb_thread_t t; + int s[2]; + + if(adb_socketpair(s)) { + printf("cannot create service socket pair\n"); + return -1; + } + + sti = malloc(sizeof(stinfo)); + if(sti == 0) fatal("cannot allocate stinfo"); + sti->func = func; + sti->cookie = cookie; + sti->fd = s[1]; + + if(adb_thread_create( &t, service_bootstrap_func, sti)){ + free(sti); + adb_close(s[0]); + adb_close(s[1]); + printf("cannot create service thread\n"); + return -1; + } + + D("service thread started, %d:%d\n",s[0], s[1]); + return s[0]; +} + +static int create_subprocess(const char *cmd, const char *arg0, const char *arg1) +{ +#ifdef HAVE_WIN32_PROC + fprintf(stderr, "error: create_subprocess not implemented on Win32 (%s %s %s)\n", cmd, arg0, arg1); + return -1; +#else /* !HAVE_WIN32_PROC */ + char *devname; + int ptm; + pid_t pid; + + ptm = unix_open("/dev/ptmx", O_RDWR); // | O_NOCTTY); + if(ptm < 0){ + printf("[ cannot open /dev/ptmx - %s ]\n",strerror(errno)); + return -1; + } + fcntl(ptm, F_SETFD, FD_CLOEXEC); + + if(grantpt(ptm) || unlockpt(ptm) || + ((devname = (char*) ptsname(ptm)) == 0)){ + printf("[ trouble with /dev/ptmx - %s ]\n", strerror(errno)); + return -1; + } + + pid = fork(); + if(pid < 0) { + printf("- fork failed: %s -\n", strerror(errno)); + return -1; + } + + if(pid == 0){ + int pts; + + setsid(); + + pts = unix_open(devname, O_RDWR); + if(pts < 0) exit(-1); + + dup2(pts, 0); + dup2(pts, 1); + dup2(pts, 2); + + adb_close(ptm); + + execl(cmd, cmd, arg0, arg1, NULL); + fprintf(stderr, "- exec '%s' failed: %s (%d) -\n", + cmd, strerror(errno), errno); + exit(-1); + } else { +#if !ADB_HOST + // set child's OOM adjustment to zero + char text[64]; + snprintf(text, sizeof text, "/proc/%d/oom_adj", pid); + int fd = adb_open(text, O_WRONLY); + if (fd >= 0) { + adb_write(fd, "0", 1); + adb_close(fd); + } else { + D("adb: unable to open %s\n", text); + } +#endif + return ptm; + } +#endif /* !HAVE_WIN32_PROC */ +} + +#if ADB_HOST +#define SHELL_COMMAND "/bin/sh" +#define ALTERNATE_SHELL_COMMAND "" +#else +#define SHELL_COMMAND "/system/bin/sh" +#define ALTERNATE_SHELL_COMMAND "/sbin/sh" +#endif + +int service_to_fd(const char *name) +{ + int ret = -1; + + if(!strncmp(name, "tcp:", 4)) { + int port = atoi(name + 4); + name = strchr(name + 4, ':'); + if(name == 0) { + ret = socket_loopback_client(port, SOCK_STREAM); + if (ret >= 0) + disable_tcp_nagle(ret); + } else { +#if ADB_HOST + adb_mutex_lock(&dns_lock); + ret = socket_network_client(name + 1, port, SOCK_STREAM); + adb_mutex_unlock(&dns_lock); +#else + return -1; +#endif + } +#ifndef HAVE_WINSOCK /* winsock doesn't implement unix domain sockets */ + } else if(!strncmp(name, "local:", 6)) { + ret = socket_local_client(name + 6, + ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + } else if(!strncmp(name, "localreserved:", 14)) { + ret = socket_local_client(name + 14, + ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + } else if(!strncmp(name, "localabstract:", 14)) { + ret = socket_local_client(name + 14, + ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); + } else if(!strncmp(name, "localfilesystem:", 16)) { + ret = socket_local_client(name + 16, + ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); +#endif +#if ADB_HOST + } else if(!strncmp("dns:", name, 4)){ + char *n = strdup(name + 4); + if(n == 0) return -1; + ret = create_service_thread(dns_service, n); +#else /* !ADB_HOST */ + } else if(!strncmp("dev:", name, 4)) { + ret = unix_open(name + 4, O_RDWR); + } else if(!strncmp(name, "framebuffer:", 12)) { + ret = create_service_thread(framebuffer_service, 0); + } else if(recovery_mode && !strncmp(name, "recover:", 8)) { + ret = create_service_thread(recover_service, (void*) atoi(name + 8)); + } else if (!strncmp(name, "jdwp:", 5)) { + ret = create_jdwp_connection_fd(atoi(name+5)); + } else if (!strncmp(name, "log:", 4)) { + ret = create_service_thread(log_service, get_log_file_path(name + 4)); +#endif + } else if(!HOST && !strncmp(name, "shell:", 6)) { + if(name[6]) { + struct stat filecheck; + ret = -1; + if (stat(ALTERNATE_SHELL_COMMAND, &filecheck) == 0) { + ret = create_subprocess(ALTERNATE_SHELL_COMMAND, "-c", name + 6); + } + if (ret == -1) { + ret = create_subprocess(SHELL_COMMAND, "-c", name + 6); + } + } else { + struct stat filecheck; + ret = -1; + if (stat(ALTERNATE_SHELL_COMMAND, &filecheck) == 0) { + ret = create_subprocess(ALTERNATE_SHELL_COMMAND, "-", 0); + } + if (ret == -1) { + ret = create_subprocess(SHELL_COMMAND, "-", 0); + } + } +#if !ADB_HOST + } else if(!strncmp(name, "sync:", 5)) { + ret = create_service_thread(file_sync_service, NULL); + } else if(!strncmp(name, "remount:", 8)) { + ret = create_service_thread(remount_service, NULL); + } else if(!strncmp(name, "reboot:", 7)) { + void* arg = strdup(name + 7); + if(arg == 0) return -1; + ret = create_service_thread(reboot_service, arg); + } else if(!strncmp(name, "root:", 5)) { + ret = create_service_thread(restart_root_service, NULL); + } else if(!strncmp(name, "tcpip:", 6)) { + int port; + if (sscanf(name + 6, "%d", &port) == 0) { + port = 0; + } + ret = create_service_thread(restart_tcp_service, (void *)port); + } else if(!strncmp(name, "usb:", 4)) { + ret = create_service_thread(restart_usb_service, NULL); +#endif +#if 0 + } else if(!strncmp(name, "echo:", 5)){ + ret = create_service_thread(echo_service, 0); +#endif + } + if (ret >= 0) { + close_on_exec(ret); + } + return ret; +} + +#if ADB_HOST +struct state_info { + transport_type transport; + char* serial; + int state; +}; + +static void wait_for_state(int fd, void* cookie) +{ + struct state_info* sinfo = cookie; + char* err = "unknown error"; + + D("wait_for_state %d\n", sinfo->state); + + atransport *t = acquire_one_transport(sinfo->state, sinfo->transport, sinfo->serial, &err); + if(t != 0) { + writex(fd, "OKAY", 4); + } else { + sendfailmsg(fd, err); + } + + if (sinfo->serial) + free(sinfo->serial); + free(sinfo); + adb_close(fd); + D("wait_for_state is done\n"); +} +#endif + +#if ADB_HOST +asocket* host_service_to_socket(const char* name, const char *serial) +{ + if (!strcmp(name,"track-devices")) { + return create_device_tracker(); + } else if (!strncmp(name, "wait-for-", strlen("wait-for-"))) { + struct state_info* sinfo = malloc(sizeof(struct state_info)); + + if (serial) + sinfo->serial = strdup(serial); + else + sinfo->serial = NULL; + + name += strlen("wait-for-"); + + if (!strncmp(name, "local", strlen("local"))) { + sinfo->transport = kTransportLocal; + sinfo->state = CS_DEVICE; + } else if (!strncmp(name, "usb", strlen("usb"))) { + sinfo->transport = kTransportUsb; + sinfo->state = CS_DEVICE; + } else if (!strncmp(name, "any", strlen("any"))) { + sinfo->transport = kTransportAny; + sinfo->state = CS_DEVICE; + } else { + free(sinfo); + return NULL; + } + + int fd = create_service_thread(wait_for_state, sinfo); + return create_local_socket(fd); + } + return NULL; +} +#endif /* ADB_HOST */ diff --git a/adb/sockets.c b/adb/sockets.c new file mode 100644 index 0000000..68d640e --- /dev/null +++ b/adb/sockets.c @@ -0,0 +1,790 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_SOCKETS +#include "adb.h" + +ADB_MUTEX_DEFINE( socket_list_lock ); + +static void local_socket_close_locked(asocket *s); + +int sendfailmsg(int fd, const char *reason) +{ + char buf[9]; + int len; + len = strlen(reason); + if(len > 0xffff) len = 0xffff; + snprintf(buf, sizeof buf, "FAIL%04x", len); + if(writex(fd, buf, 8)) return -1; + return writex(fd, reason, len); +} + +//extern int online; + +static unsigned local_socket_next_id = 1; + +static asocket local_socket_list = { + .next = &local_socket_list, + .prev = &local_socket_list, +}; + +/* the the list of currently closing local sockets. +** these have no peer anymore, but still packets to +** write to their fd. +*/ +static asocket local_socket_closing_list = { + .next = &local_socket_closing_list, + .prev = &local_socket_closing_list, +}; + +asocket *find_local_socket(unsigned id) +{ + asocket *s; + asocket *result = NULL; + + adb_mutex_lock(&socket_list_lock); + for(s = local_socket_list.next; s != &local_socket_list && !result; s = s->next) { + if(s->id == id) result = s; + } + adb_mutex_unlock(&socket_list_lock); + + return result; +} + +static void +insert_local_socket(asocket* s, asocket* list) +{ + s->next = list; + s->prev = s->next->prev; + s->prev->next = s; + s->next->prev = s; +} + + +void install_local_socket(asocket *s) +{ + adb_mutex_lock(&socket_list_lock); + + s->id = local_socket_next_id++; + insert_local_socket(s, &local_socket_list); + + adb_mutex_unlock(&socket_list_lock); +} + +void remove_socket(asocket *s) +{ + // socket_list_lock should already be held + if (s->prev && s->next) + { + s->prev->next = s->next; + s->next->prev = s->prev; + s->next = 0; + s->prev = 0; + s->id = 0; + } +} + +void close_all_sockets(atransport *t) +{ + asocket *s; + + /* this is a little gross, but since s->close() *will* modify + ** the list out from under you, your options are limited. + */ + adb_mutex_lock(&socket_list_lock); +restart: + for(s = local_socket_list.next; s != &local_socket_list; s = s->next){ + if(s->transport == t || (s->peer && s->peer->transport == t)) { + local_socket_close_locked(s); + goto restart; + } + } + adb_mutex_unlock(&socket_list_lock); +} + +static int local_socket_enqueue(asocket *s, apacket *p) +{ + D("LS(%d): enqueue %d\n", s->id, p->len); + + p->ptr = p->data; + + /* if there is already data queue'd, we will receive + ** events when it's time to write. just add this to + ** the tail + */ + if(s->pkt_first) { + goto enqueue; + } + + /* write as much as we can, until we + ** would block or there is an error/eof + */ + while(p->len > 0) { + int r = adb_write(s->fd, p->ptr, p->len); + if(r > 0) { + p->len -= r; + p->ptr += r; + continue; + } + if((r == 0) || (errno != EAGAIN)) { + D( "LS(%d): not ready, errno=%d: %s\n", s->id, errno, strerror(errno) ); + s->close(s); + return 1; /* not ready (error) */ + } else { + break; + } + } + + if(p->len == 0) { + put_apacket(p); + return 0; /* ready for more data */ + } + +enqueue: + p->next = 0; + if(s->pkt_first) { + s->pkt_last->next = p; + } else { + s->pkt_first = p; + } + s->pkt_last = p; + + /* make sure we are notified when we can drain the queue */ + fdevent_add(&s->fde, FDE_WRITE); + + return 1; /* not ready (backlog) */ +} + +static void local_socket_ready(asocket *s) +{ + /* far side is ready for data, pay attention to + readable events */ + fdevent_add(&s->fde, FDE_READ); +// D("LS(%d): ready()\n", s->id); +} + +static void local_socket_close(asocket *s) +{ + adb_mutex_lock(&socket_list_lock); + local_socket_close_locked(s); + adb_mutex_unlock(&socket_list_lock); +} + +// be sure to hold the socket list lock when calling this +static void local_socket_destroy(asocket *s) +{ + apacket *p, *n; + + /* IMPORTANT: the remove closes the fd + ** that belongs to this socket + */ + fdevent_remove(&s->fde); + + /* dispose of any unwritten data */ + for(p = s->pkt_first; p; p = n) { + D("LS(%d): discarding %d bytes\n", s->id, p->len); + n = p->next; + put_apacket(p); + } + remove_socket(s); + free(s); +} + + +static void local_socket_close_locked(asocket *s) +{ + if(s->peer) { + s->peer->peer = 0; + // tweak to avoid deadlock + if (s->peer->close == local_socket_close) { + local_socket_close_locked(s->peer); + } else { + s->peer->close(s->peer); + } + s->peer = 0; + } + + /* If we are already closing, or if there are no + ** pending packets, destroy immediately + */ + if (s->closing || s->pkt_first == NULL) { + int id = s->id; + local_socket_destroy(s); + D("LS(%d): closed\n", id); + return; + } + + /* otherwise, put on the closing list + */ + D("LS(%d): closing\n", s->id); + s->closing = 1; + fdevent_del(&s->fde, FDE_READ); + remove_socket(s); + insert_local_socket(s, &local_socket_closing_list); +} + +static void local_socket_event_func(int fd, unsigned ev, void *_s) +{ + asocket *s = _s; + + /* put the FDE_WRITE processing before the FDE_READ + ** in order to simplify the code. + */ + if(ev & FDE_WRITE){ + apacket *p; + + while((p = s->pkt_first) != 0) { + while(p->len > 0) { + int r = adb_write(fd, p->ptr, p->len); + if(r > 0) { + p->ptr += r; + p->len -= r; + continue; + } + if(r < 0) { + /* returning here is ok because FDE_READ will + ** be processed in the next iteration loop + */ + if(errno == EAGAIN) return; + if(errno == EINTR) continue; + } + s->close(s); + return; + } + + if(p->len == 0) { + s->pkt_first = p->next; + if(s->pkt_first == 0) s->pkt_last = 0; + put_apacket(p); + } + } + + /* if we sent the last packet of a closing socket, + ** we can now destroy it. + */ + if (s->closing) { + s->close(s); + return; + } + + /* no more packets queued, so we can ignore + ** writable events again and tell our peer + ** to resume writing + */ + fdevent_del(&s->fde, FDE_WRITE); + s->peer->ready(s->peer); + } + + + if(ev & FDE_READ){ + apacket *p = get_apacket(); + unsigned char *x = p->data; + size_t avail = MAX_PAYLOAD; + int r; + int is_eof = 0; + + while(avail > 0) { + r = adb_read(fd, x, avail); + if(r > 0) { + avail -= r; + x += r; + continue; + } + if(r < 0) { + if(errno == EAGAIN) break; + if(errno == EINTR) continue; + } + + /* r = 0 or unhandled error */ + is_eof = 1; + break; + } + + if((avail == MAX_PAYLOAD) || (s->peer == 0)) { + put_apacket(p); + } else { + p->len = MAX_PAYLOAD - avail; + + r = s->peer->enqueue(s->peer, p); + + if(r < 0) { + /* error return means they closed us as a side-effect + ** and we must return immediately. + ** + ** note that if we still have buffered packets, the + ** socket will be placed on the closing socket list. + ** this handler function will be called again + ** to process FDE_WRITE events. + */ + return; + } + + if(r > 0) { + /* if the remote cannot accept further events, + ** we disable notification of READs. They'll + ** be enabled again when we get a call to ready() + */ + fdevent_del(&s->fde, FDE_READ); + } + } + + if(is_eof) { + s->close(s); + } + } + + if(ev & FDE_ERROR){ + /* this should be caught be the next read or write + ** catching it here means we may skip the last few + ** bytes of readable data. + */ +// s->close(s); + return; + } +} + +asocket *create_local_socket(int fd) +{ + asocket *s = calloc(1, sizeof(asocket)); + if(s == 0) fatal("cannot allocate socket"); + install_local_socket(s); + s->fd = fd; + s->enqueue = local_socket_enqueue; + s->ready = local_socket_ready; + s->close = local_socket_close; + + fdevent_install(&s->fde, fd, local_socket_event_func, s); +/* fdevent_add(&s->fde, FDE_ERROR); */ + //fprintf(stderr, "Created local socket in create_local_socket \n"); + D("LS(%d): created (fd=%d)\n", s->id, s->fd); + return s; +} + +asocket *create_local_service_socket(const char *name) +{ + asocket *s; + int fd; + +#if !ADB_HOST + if (!strcmp(name,"jdwp")) { + return create_jdwp_service_socket(); + } + if (!strcmp(name,"track-jdwp")) { + return create_jdwp_tracker_service_socket(); + } +#endif + fd = service_to_fd(name); + if(fd < 0) return 0; + + s = create_local_socket(fd); + D("LS(%d): bound to '%s'\n", s->id, name); + return s; +} + +#if ADB_HOST +static asocket *create_host_service_socket(const char *name, const char* serial) +{ + asocket *s; + + s = host_service_to_socket(name, serial); + + if (s != NULL) { + D("LS(%d) bound to '%s'\n", s->id, name); + return s; + } + + return s; +} +#endif /* ADB_HOST */ + +/* a Remote socket is used to send/receive data to/from a given transport object +** it needs to be closed when the transport is forcibly destroyed by the user +*/ +typedef struct aremotesocket { + asocket socket; + adisconnect disconnect; +} aremotesocket; + +static int remote_socket_enqueue(asocket *s, apacket *p) +{ + D("Calling remote_socket_enqueue\n"); + p->msg.command = A_WRTE; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + p->msg.data_length = p->len; + send_packet(p, s->transport); + return 1; +} + +static void remote_socket_ready(asocket *s) +{ + D("Calling remote_socket_ready\n"); + apacket *p = get_apacket(); + p->msg.command = A_OKAY; + p->msg.arg0 = s->peer->id; + p->msg.arg1 = s->id; + send_packet(p, s->transport); +} + +static void remote_socket_close(asocket *s) +{ + D("Calling remote_socket_close\n"); + apacket *p = get_apacket(); + p->msg.command = A_CLSE; + if(s->peer) { + p->msg.arg0 = s->peer->id; + s->peer->peer = 0; + s->peer->close(s->peer); + } + p->msg.arg1 = s->id; + send_packet(p, s->transport); + D("RS(%d): closed\n", s->id); + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +static void remote_socket_disconnect(void* _s, atransport* t) +{ + asocket* s = _s; + asocket* peer = s->peer; + + D("remote_socket_disconnect RS(%d)\n", s->id); + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + remove_transport_disconnect( s->transport, &((aremotesocket*)s)->disconnect ); + free(s); +} + +asocket *create_remote_socket(unsigned id, atransport *t) +{ + asocket *s = calloc(1, sizeof(aremotesocket)); + adisconnect* dis = &((aremotesocket*)s)->disconnect; + + if(s == 0) fatal("cannot allocate socket"); + s->id = id; + s->enqueue = remote_socket_enqueue; + s->ready = remote_socket_ready; + s->close = remote_socket_close; + s->transport = t; + + dis->func = remote_socket_disconnect; + dis->opaque = s; + add_transport_disconnect( t, dis ); + D("RS(%d): created\n", s->id); + return s; +} + +void connect_to_remote(asocket *s, const char *destination) +{ + D("Connect_to_remote call \n"); + apacket *p = get_apacket(); + int len = strlen(destination) + 1; + + if(len > (MAX_PAYLOAD-1)) { + fatal("destination oversized"); + } + + D("LS(%d): connect('%s')\n", s->id, destination); + p->msg.command = A_OPEN; + p->msg.arg0 = s->id; + p->msg.data_length = len; + strcpy((char*) p->data, destination); + send_packet(p, s->transport); +} + + +/* this is used by magic sockets to rig local sockets to + send the go-ahead message when they connect */ +static void local_socket_ready_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + adb_write(s->fd, "OKAY", 4); + s->ready(s); +} + +/* this is used by magic sockets to rig local sockets to + send the failure message if they are closed before + connected (to avoid closing them without a status message) */ +static void local_socket_close_notify(asocket *s) +{ + s->ready = local_socket_ready; + s->close = local_socket_close; + sendfailmsg(s->fd, "closed"); + s->close(s); +} + +unsigned unhex(unsigned char *s, int len) +{ + unsigned n = 0, c; + + while(len-- > 0) { + switch((c = *s++)) { + case '0': case '1': case '2': + case '3': case '4': case '5': + case '6': case '7': case '8': + case '9': + c -= '0'; + break; + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + c = c - 'a' + 10; + break; + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + c = c - 'A' + 10; + break; + default: + return 0xffffffff; + } + + n = (n << 4) | c; + } + + return n; +} + +static int smart_socket_enqueue(asocket *s, apacket *p) +{ + unsigned len; +#if ADB_HOST + char *service = NULL; + char* serial = NULL; + transport_type ttype = kTransportAny; +#endif + + D("SS(%d): enqueue %d\n", s->id, p->len); + + if(s->pkt_first == 0) { + s->pkt_first = p; + s->pkt_last = p; + } else { + if((s->pkt_first->len + p->len) > MAX_PAYLOAD) { + D("SS(%d): overflow\n", s->id); + put_apacket(p); + goto fail; + } + + memcpy(s->pkt_first->data + s->pkt_first->len, + p->data, p->len); + s->pkt_first->len += p->len; + put_apacket(p); + + p = s->pkt_first; + } + + /* don't bother if we can't decode the length */ + if(p->len < 4) return 0; + + len = unhex(p->data, 4); + if((len < 1) || (len > 1024)) { + D("SS(%d): bad size (%d)\n", s->id, len); + goto fail; + } + + D("SS(%d): len is %d\n", s->id, len ); + /* can't do anything until we have the full header */ + if((len + 4) > p->len) { + D("SS(%d): waiting for %d more bytes\n", s->id, len+4 - p->len); + return 0; + } + + p->data[len + 4] = 0; + + D("SS(%d): '%s'\n", s->id, (char*) (p->data + 4)); + +#if ADB_HOST + service = (char *)p->data + 4; + if(!strncmp(service, "host-serial:", strlen("host-serial:"))) { + char* serial_end; + service += strlen("host-serial:"); + + // serial number should follow "host:" + serial_end = strchr(service, ':'); + if (serial_end) { + *serial_end = 0; // terminate string + serial = service; + service = serial_end + 1; + } + } else if (!strncmp(service, "host-usb:", strlen("host-usb:"))) { + ttype = kTransportUsb; + service += strlen("host-usb:"); + } else if (!strncmp(service, "host-local:", strlen("host-local:"))) { + ttype = kTransportLocal; + service += strlen("host-local:"); + } else if (!strncmp(service, "host:", strlen("host:"))) { + ttype = kTransportAny; + service += strlen("host:"); + } else { + service = NULL; + } + + if (service) { + asocket *s2; + + /* some requests are handled immediately -- in that + ** case the handle_host_request() routine has sent + ** the OKAY or FAIL message and all we have to do + ** is clean up. + */ + if(handle_host_request(service, ttype, serial, s->peer->fd, s) == 0) { + /* XXX fail message? */ + D( "SS(%d): handled host service '%s'\n", s->id, service ); + goto fail; + } + if (!strncmp(service, "transport", strlen("transport"))) { + D( "SS(%d): okay transport\n", s->id ); + p->len = 0; + return 0; + } + + /* try to find a local service with this name. + ** if no such service exists, we'll fail out + ** and tear down here. + */ + s2 = create_host_service_socket(service, serial); + if(s2 == 0) { + D( "SS(%d): couldn't create host service '%s'\n", s->id, service ); + sendfailmsg(s->peer->fd, "unknown host service"); + goto fail; + } + + /* we've connected to a local host service, + ** so we make our peer back into a regular + ** local socket and bind it to the new local + ** service socket, acknowledge the successful + ** connection, and close this smart socket now + ** that its work is done. + */ + adb_write(s->peer->fd, "OKAY", 4); + + s->peer->ready = local_socket_ready; + s->peer->close = local_socket_close; + s->peer->peer = s2; + s2->peer = s->peer; + s->peer = 0; + D( "SS(%d): okay\n", s->id ); + s->close(s); + + /* initial state is "ready" */ + s2->ready(s2); + return 0; + } +#else /* !ADB_HOST */ + if (s->transport == NULL) { + char* error_string = "unknown failure"; + s->transport = acquire_one_transport (CS_ANY, + kTransportAny, NULL, &error_string); + + if (s->transport == NULL) { + sendfailmsg(s->peer->fd, error_string); + goto fail; + } + } +#endif + + if(!(s->transport) || (s->transport->connection_state == CS_OFFLINE)) { + /* if there's no remote we fail the connection + ** right here and terminate it + */ + sendfailmsg(s->peer->fd, "device offline (x)"); + goto fail; + } + + + /* instrument our peer to pass the success or fail + ** message back once it connects or closes, then + ** detach from it, request the connection, and + ** tear down + */ + s->peer->ready = local_socket_ready_notify; + s->peer->close = local_socket_close_notify; + s->peer->peer = 0; + /* give him our transport and upref it */ + s->peer->transport = s->transport; + + connect_to_remote(s->peer, (char*) (p->data + 4)); + s->peer = 0; + s->close(s); + return 1; + +fail: + /* we're going to close our peer as a side-effect, so + ** return -1 to signal that state to the local socket + ** who is enqueueing against us + */ + s->close(s); + return -1; +} + +static void smart_socket_ready(asocket *s) +{ + D("SS(%d): ready\n", s->id); +} + +static void smart_socket_close(asocket *s) +{ + D("SS(%d): closed\n", s->id); + if(s->pkt_first){ + put_apacket(s->pkt_first); + } + if(s->peer) { + s->peer->peer = 0; + s->peer->close(s->peer); + s->peer = 0; + } + free(s); +} + +asocket *create_smart_socket(void (*action_cb)(asocket *s, const char *act)) +{ + D("Creating smart socket \n"); + asocket *s = calloc(1, sizeof(asocket)); + if(s == 0) fatal("cannot allocate socket"); + s->id = 0; + s->enqueue = smart_socket_enqueue; + s->ready = smart_socket_ready; + s->close = smart_socket_close; + s->extra = action_cb; + + D("SS(%d): created %p\n", s->id, action_cb); + return s; +} + +void smart_socket_action(asocket *s, const char *act) +{ + +} + +void connect_to_smartsocket(asocket *s) +{ + D("Connecting to smart socket \n"); + asocket *ss = create_smart_socket(smart_socket_action); + s->peer = ss; + ss->peer = s; + s->ready(s); +} diff --git a/adb/sockets.dia b/adb/sockets.dia Binary files differnew file mode 100644 index 0000000..c626f20 --- /dev/null +++ b/adb/sockets.dia diff --git a/adb/sysdeps.h b/adb/sysdeps.h new file mode 100644 index 0000000..6372649 --- /dev/null +++ b/adb/sysdeps.h @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* this file contains system-dependent definitions used by ADB + * they're related to threads, sockets and file descriptors + */ +#ifndef _ADB_SYSDEPS_H +#define _ADB_SYSDEPS_H + +#ifdef __CYGWIN__ +# undef _WIN32 +#endif + +#ifdef _WIN32 + +#include <windows.h> +#include <winsock2.h> +#include <ws2tcpip.h> +#include <process.h> +#include <fcntl.h> +#include <io.h> +#include <sys/stat.h> +#include <errno.h> +#include <ctype.h> + +#define OS_PATH_SEPARATOR '\\' +#define OS_PATH_SEPARATOR_STR "\\" + +typedef CRITICAL_SECTION adb_mutex_t; + +#define ADB_MUTEX_DEFINE(x) adb_mutex_t x + +/* declare all mutexes */ +#define ADB_MUTEX(x) extern adb_mutex_t x; +#include "mutex_list.h" + +extern void adb_sysdeps_init(void); + +static __inline__ void adb_mutex_lock( adb_mutex_t* lock ) +{ + EnterCriticalSection( lock ); +} + +static __inline__ void adb_mutex_unlock( adb_mutex_t* lock ) +{ + LeaveCriticalSection( lock ); +} + +typedef struct { unsigned tid; } adb_thread_t; + +typedef void* (*adb_thread_func_t)(void* arg); + +typedef void (*win_thread_func_t)(void* arg); + +static __inline__ int adb_thread_create( adb_thread_t *thread, adb_thread_func_t func, void* arg) +{ + thread->tid = _beginthread( (win_thread_func_t)func, 0, arg ); + if (thread->tid == (unsigned)-1L) { + return -1; + } + return 0; +} + +static __inline__ void close_on_exec(int fd) +{ + /* nothing really */ +} + +extern void disable_tcp_nagle(int fd); + +#define lstat stat /* no symlinks on Win32 */ + +#define S_ISLNK(m) 0 /* no symlinks on Win32 */ + +static __inline__ int adb_unlink(const char* path) +{ + int rc = unlink(path); + + if (rc == -1 && errno == EACCES) { + /* unlink returns EACCES when the file is read-only, so we first */ + /* try to make it writable, then unlink again... */ + rc = chmod(path, _S_IREAD|_S_IWRITE ); + if (rc == 0) + rc = unlink(path); + } + return rc; +} +#undef unlink +#define unlink ___xxx_unlink + +static __inline__ int adb_mkdir(const char* path, int mode) +{ + return _mkdir(path); +} +#undef mkdir +#define mkdir ___xxx_mkdir + +extern int adb_open(const char* path, int options); +extern int adb_creat(const char* path, int mode); +extern int adb_read(int fd, void* buf, int len); +extern int adb_write(int fd, const void* buf, int len); +extern int adb_lseek(int fd, int pos, int where); +extern int adb_shutdown(int fd); +extern int adb_close(int fd); + +static __inline__ int unix_close(int fd) +{ + return close(fd); +} +#undef close +#define close ____xxx_close + +static __inline__ int unix_read(int fd, void* buf, size_t len) +{ + return read(fd, buf, len); +} +#undef read +#define read ___xxx_read + +static __inline__ int unix_write(int fd, const void* buf, size_t len) +{ + return write(fd, buf, len); +} +#undef write +#define write ___xxx_write + +static __inline__ int adb_open_mode(const char* path, int options, int mode) +{ + return adb_open(path, options); +} + +static __inline__ int unix_open(const char* path, int options,...) +{ + if ((options & O_CREAT) == 0) + { + return open(path, options); + } + else + { + int mode; + va_list args; + va_start( args, options ); + mode = va_arg( args, int ); + va_end( args ); + return open(path, options, mode); + } +} +#define open ___xxx_unix_open + + +/* normally provided by <cutils/misc.h> */ +extern void* load_file(const char* pathname, unsigned* psize); + +/* normally provided by <cutils/sockets.h> */ +extern int socket_loopback_client(int port, int type); +extern int socket_network_client(const char *host, int port, int type); +extern int socket_loopback_server(int port, int type); +extern int socket_inaddr_any_server(int port, int type); + +/* normally provided by "fdevent.h" */ + +#define FDE_READ 0x0001 +#define FDE_WRITE 0x0002 +#define FDE_ERROR 0x0004 +#define FDE_DONT_CLOSE 0x0080 + +typedef struct fdevent fdevent; + +typedef void (*fd_func)(int fd, unsigned events, void *userdata); + +fdevent *fdevent_create(int fd, fd_func func, void *arg); +void fdevent_destroy(fdevent *fde); +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg); +void fdevent_remove(fdevent *item); +void fdevent_set(fdevent *fde, unsigned events); +void fdevent_add(fdevent *fde, unsigned events); +void fdevent_del(fdevent *fde, unsigned events); +void fdevent_loop(); + +struct fdevent { + fdevent *next; + fdevent *prev; + + int fd; + unsigned short state; + unsigned short events; + + fd_func func; + void *arg; +}; + +static __inline__ void adb_sleep_ms( int mseconds ) +{ + Sleep( mseconds ); +} + +extern int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen); + +#undef accept +#define accept ___xxx_accept + +static __inline__ int adb_socket_setbufsize( int fd, int bufsize ) +{ + int opt = bufsize; + return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char*)&opt, sizeof(opt)); +} + +extern int adb_socketpair( int sv[2] ); + +static __inline__ char* adb_dirstart( const char* path ) +{ + char* p = strchr(path, '/'); + char* p2 = strchr(path, '\\'); + + if ( !p ) + p = p2; + else if ( p2 && p2 > p ) + p = p2; + + return p; +} + +static __inline__ char* adb_dirstop( const char* path ) +{ + char* p = strrchr(path, '/'); + char* p2 = strrchr(path, '\\'); + + if ( !p ) + p = p2; + else if ( p2 && p2 > p ) + p = p2; + + return p; +} + +static __inline__ int adb_is_absolute_host_path( const char* path ) +{ + return isalpha(path[0]) && path[1] == ':' && path[2] == '\\'; +} + +#else /* !_WIN32 a.k.a. Unix */ + +#include "fdevent.h" +#include <cutils/sockets.h> +#include <cutils/properties.h> +#include <cutils/misc.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdarg.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <string.h> + +#define OS_PATH_SEPARATOR '/' +#define OS_PATH_SEPARATOR_STR "/" + +typedef pthread_mutex_t adb_mutex_t; +#define ADB_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +#define adb_mutex_init pthread_mutex_init +#define adb_mutex_lock pthread_mutex_lock +#define adb_mutex_unlock pthread_mutex_unlock +#define adb_mutex_destroy pthread_mutex_destroy + +#define ADB_MUTEX_DEFINE(m) static adb_mutex_t m = PTHREAD_MUTEX_INITIALIZER + +#define adb_cond_t pthread_cond_t +#define adb_cond_init pthread_cond_init +#define adb_cond_wait pthread_cond_wait +#define adb_cond_broadcast pthread_cond_broadcast +#define adb_cond_signal pthread_cond_signal +#define adb_cond_destroy pthread_cond_destroy + +static __inline__ void close_on_exec(int fd) +{ + fcntl( fd, F_SETFD, FD_CLOEXEC ); +} + +static __inline__ int unix_open(const char* path, int options,...) +{ + if ((options & O_CREAT) == 0) + { + return open(path, options); + } + else + { + int mode; + va_list args; + va_start( args, options ); + mode = va_arg( args, int ); + va_end( args ); + return open(path, options, mode); + } +} + +static __inline__ int adb_open_mode( const char* pathname, int options, int mode ) +{ + return open( pathname, options, mode ); +} + + +static __inline__ int adb_open( const char* pathname, int options ) +{ + int fd = open( pathname, options ); + if (fd < 0) + return -1; + close_on_exec( fd ); + return fd; +} +#undef open +#define open ___xxx_open + +static __inline__ int adb_shutdown(int fd) +{ + return shutdown(fd, SHUT_RDWR); +} +#undef shutdown +#define shutdown ____xxx_shutdown + +static __inline__ int adb_close(int fd) +{ + return close(fd); +} +#undef close +#define close ____xxx_close + + +static __inline__ int adb_read(int fd, void* buf, size_t len) +{ + return read(fd, buf, len); +} + +#undef read +#define read ___xxx_read + +static __inline__ int adb_write(int fd, const void* buf, size_t len) +{ + return write(fd, buf, len); +} +#undef write +#define write ___xxx_write + +static __inline__ int adb_lseek(int fd, int pos, int where) +{ + return lseek(fd, pos, where); +} +#undef lseek +#define lseek ___xxx_lseek + +static __inline__ int adb_unlink(const char* path) +{ + return unlink(path); +} +#undef unlink +#define unlink ___xxx_unlink + +static __inline__ int adb_creat(const char* path, int mode) +{ + int fd = creat(path, mode); + + if ( fd < 0 ) + return -1; + + close_on_exec(fd); + return fd; +} +#undef creat +#define creat ___xxx_creat + +static __inline__ int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen) +{ + return accept( serverfd, addr, addrlen ); +} + +#undef accept +#define accept ___xxx_accept + +#define unix_read adb_read +#define unix_write adb_write +#define unix_close adb_close + +typedef pthread_t adb_thread_t; + +typedef void* (*adb_thread_func_t)( void* arg ); + +static __inline__ int adb_thread_create( adb_thread_t *pthread, adb_thread_func_t start, void* arg ) +{ + pthread_attr_t attr; + + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + + return pthread_create( pthread, &attr, start, arg ); +} + +static __inline__ int adb_socket_setbufsize( int fd, int bufsize ) +{ + int opt = bufsize; + return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); +} + +static __inline__ void disable_tcp_nagle(int fd) +{ + int on = 1; + setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on) ); +} + + +static __inline__ int unix_socketpair( int d, int type, int protocol, int sv[2] ) +{ + return socketpair( d, type, protocol, sv ); +} + +static __inline__ int adb_socketpair( int sv[2] ) +{ + int rc; + + rc = unix_socketpair( AF_UNIX, SOCK_STREAM, 0, sv ); + if (rc < 0) + return -1; + + close_on_exec( sv[0] ); + close_on_exec( sv[1] ); + return 0; +} + +#undef socketpair +#define socketpair ___xxx_socketpair + +static __inline__ void adb_sleep_ms( int mseconds ) +{ + usleep( mseconds*1000 ); +} + +static __inline__ int adb_mkdir(const char* path, int mode) +{ + return mkdir(path, mode); +} +#undef mkdir +#define mkdir ___xxx_mkdir + +static __inline__ void adb_sysdeps_init(void) +{ +} + +static __inline__ char* adb_dirstart(const char* path) +{ + return strchr(path, '/'); +} + +static __inline__ char* adb_dirstop(const char* path) +{ + return strrchr(path, '/'); +} + +static __inline__ int adb_is_absolute_host_path( const char* path ) +{ + return path[0] == '/'; +} + +#endif /* !_WIN32 */ + +#endif /* _ADB_SYSDEPS_H */ diff --git a/adb/sysdeps_win32.c b/adb/sysdeps_win32.c new file mode 100644 index 0000000..ced91e8 --- /dev/null +++ b/adb/sysdeps_win32.c @@ -0,0 +1,1967 @@ +#include "sysdeps.h" +#include <windows.h> +#include <winsock2.h> +#include <stdio.h> +#include <errno.h> +#define TRACE_TAG TRACE_SYSDEPS +#include "adb.h" + +extern void fatal(const char *fmt, ...); + +#define assert(cond) do { if (!(cond)) fatal( "assertion failed '%s' on %s:%ld\n", #cond, __FILE__, __LINE__ ); } while (0) + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** replaces libs/cutils/load_file.c *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +void *load_file(const char *fn, unsigned *_sz) +{ + HANDLE file; + char *data; + DWORD file_size; + + file = CreateFile( fn, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + 0, + NULL ); + + if (file == INVALID_HANDLE_VALUE) + return NULL; + + file_size = GetFileSize( file, NULL ); + data = NULL; + + if (file_size > 0) { + data = (char*) malloc( file_size + 1 ); + if (data == NULL) { + D("load_file: could not allocate %ld bytes\n", file_size ); + file_size = 0; + } else { + DWORD out_bytes; + + if ( !ReadFile( file, data, file_size, &out_bytes, NULL ) || + out_bytes != file_size ) + { + D("load_file: could not read %ld bytes from '%s'\n", file_size, fn); + free(data); + data = NULL; + file_size = 0; + } + } + } + CloseHandle( file ); + + *_sz = (unsigned) file_size; + return data; +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** common file descriptor handling *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +typedef const struct FHClassRec_* FHClass; + +typedef struct FHRec_* FH; + +typedef struct EventHookRec_* EventHook; + +typedef struct FHClassRec_ +{ + void (*_fh_init) ( FH f ); + int (*_fh_close)( FH f ); + int (*_fh_lseek)( FH f, int pos, int origin ); + int (*_fh_read) ( FH f, void* buf, int len ); + int (*_fh_write)( FH f, const void* buf, int len ); + void (*_fh_hook) ( FH f, int events, EventHook hook ); + +} FHClassRec; + +/* used to emulate unix-domain socket pairs */ +typedef struct SocketPairRec_* SocketPair; + +typedef struct FHRec_ +{ + FHClass clazz; + int used; + int eof; + union { + HANDLE handle; + SOCKET socket; + SocketPair pair; + } u; + + HANDLE event; + int mask; + + char name[32]; + +} FHRec; + +#define fh_handle u.handle +#define fh_socket u.socket +#define fh_pair u.pair + +#define WIN32_FH_BASE 100 + +#define WIN32_MAX_FHS 128 + +static adb_mutex_t _win32_lock; +static FHRec _win32_fhs[ WIN32_MAX_FHS ]; +static int _win32_fh_count; + +static FH +_fh_from_int( int fd ) +{ + FH f; + + fd -= WIN32_FH_BASE; + + if (fd < 0 || fd >= _win32_fh_count) { + D( "_fh_from_int: invalid fd %d\n", fd + WIN32_FH_BASE ); + errno = EBADF; + return NULL; + } + + f = &_win32_fhs[fd]; + + if (f->used == 0) { + D( "_fh_from_int: invalid fd %d\n", fd + WIN32_FH_BASE ); + errno = EBADF; + return NULL; + } + + return f; +} + + +static int +_fh_to_int( FH f ) +{ + if (f && f->used && f >= _win32_fhs && f < _win32_fhs + WIN32_MAX_FHS) + return (int)(f - _win32_fhs) + WIN32_FH_BASE; + + return -1; +} + +static FH +_fh_alloc( FHClass clazz ) +{ + int nn; + FH f = NULL; + + adb_mutex_lock( &_win32_lock ); + + if (_win32_fh_count < WIN32_MAX_FHS) { + f = &_win32_fhs[ _win32_fh_count++ ]; + goto Exit; + } + + for (nn = 0; nn < WIN32_MAX_FHS; nn++) { + if ( _win32_fhs[nn].clazz == NULL) { + f = &_win32_fhs[nn]; + goto Exit; + } + } + D( "_fh_alloc: no more free file descriptors\n" ); +Exit: + if (f) { + f->clazz = clazz; + f->used = 1; + f->eof = 0; + clazz->_fh_init(f); + } + adb_mutex_unlock( &_win32_lock ); + return f; +} + + +static int +_fh_close( FH f ) +{ + if ( f->used ) { + f->clazz->_fh_close( f ); + f->used = 0; + f->eof = 0; + f->clazz = NULL; + } + return 0; +} + +/* forward definitions */ +static const FHClassRec _fh_file_class; +static const FHClassRec _fh_socket_class; + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** file-based descriptor handling *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +static void +_fh_file_init( FH f ) +{ + f->fh_handle = INVALID_HANDLE_VALUE; +} + +static int +_fh_file_close( FH f ) +{ + CloseHandle( f->fh_handle ); + f->fh_handle = INVALID_HANDLE_VALUE; + return 0; +} + +static int +_fh_file_read( FH f, void* buf, int len ) +{ + DWORD read_bytes; + + if ( !ReadFile( f->fh_handle, buf, (DWORD)len, &read_bytes, NULL ) ) { + D( "adb_read: could not read %d bytes from %s\n", len, f->name ); + errno = EIO; + return -1; + } else if (read_bytes < (DWORD)len) { + f->eof = 1; + } + return (int)read_bytes; +} + +static int +_fh_file_write( FH f, const void* buf, int len ) +{ + DWORD wrote_bytes; + + if ( !WriteFile( f->fh_handle, buf, (DWORD)len, &wrote_bytes, NULL ) ) { + D( "adb_file_write: could not write %d bytes from %s\n", len, f->name ); + errno = EIO; + return -1; + } else if (wrote_bytes < (DWORD)len) { + f->eof = 1; + } + return (int)wrote_bytes; +} + +static int +_fh_file_lseek( FH f, int pos, int origin ) +{ + DWORD method; + DWORD result; + + switch (origin) + { + case SEEK_SET: method = FILE_BEGIN; break; + case SEEK_CUR: method = FILE_CURRENT; break; + case SEEK_END: method = FILE_END; break; + default: + errno = EINVAL; + return -1; + } + + result = SetFilePointer( f->fh_handle, pos, NULL, method ); + if (result == INVALID_SET_FILE_POINTER) { + errno = EIO; + return -1; + } else { + f->eof = 0; + } + return (int)result; +} + +static void _fh_file_hook( FH f, int event, EventHook eventhook ); /* forward */ + +static const FHClassRec _fh_file_class = +{ + _fh_file_init, + _fh_file_close, + _fh_file_lseek, + _fh_file_read, + _fh_file_write, + _fh_file_hook +}; + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** file-based descriptor handling *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +int adb_open(const char* path, int options) +{ + FH f; + + DWORD desiredAccess = 0; + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + switch (options) { + case O_RDONLY: + desiredAccess = GENERIC_READ; + break; + case O_WRONLY: + desiredAccess = GENERIC_WRITE; + break; + case O_RDWR: + desiredAccess = GENERIC_READ | GENERIC_WRITE; + break; + default: + D("adb_open: invalid options (0x%0x)\n", options); + errno = EINVAL; + return -1; + } + + f = _fh_alloc( &_fh_file_class ); + if ( !f ) { + errno = ENOMEM; + return -1; + } + + f->fh_handle = CreateFile( path, desiredAccess, shareMode, NULL, OPEN_EXISTING, + 0, NULL ); + + if ( f->fh_handle == INVALID_HANDLE_VALUE ) { + _fh_close(f); + D( "adb_open: could not open '%s':", path ); + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + D( "file not found\n" ); + errno = ENOENT; + return -1; + + case ERROR_PATH_NOT_FOUND: + D( "path not found\n" ); + errno = ENOTDIR; + return -1; + + default: + D( "unknown error\n" ); + errno = ENOENT; + return -1; + } + } + + snprintf( f->name, sizeof(f->name), "%d(%s)", _fh_to_int(f), path ); + D( "adb_open: '%s' => fd %d\n", path, _fh_to_int(f) ); + return _fh_to_int(f); +} + +/* ignore mode on Win32 */ +int adb_creat(const char* path, int mode) +{ + FH f; + + f = _fh_alloc( &_fh_file_class ); + if ( !f ) { + errno = ENOMEM; + return -1; + } + + f->fh_handle = CreateFile( path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + NULL ); + + if ( f->fh_handle == INVALID_HANDLE_VALUE ) { + _fh_close(f); + D( "adb_creat: could not open '%s':", path ); + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + D( "file not found\n" ); + errno = ENOENT; + return -1; + + case ERROR_PATH_NOT_FOUND: + D( "path not found\n" ); + errno = ENOTDIR; + return -1; + + default: + D( "unknown error\n" ); + errno = ENOENT; + return -1; + } + } + snprintf( f->name, sizeof(f->name), "%d(%s)", _fh_to_int(f), path ); + D( "adb_creat: '%s' => fd %d\n", path, _fh_to_int(f) ); + return _fh_to_int(f); +} + + +int adb_read(int fd, void* buf, int len) +{ + FH f = _fh_from_int(fd); + + if (f == NULL) { + return -1; + } + + return f->clazz->_fh_read( f, buf, len ); +} + + +int adb_write(int fd, const void* buf, int len) +{ + FH f = _fh_from_int(fd); + + if (f == NULL) { + return -1; + } + + return f->clazz->_fh_write(f, buf, len); +} + + +int adb_lseek(int fd, int pos, int where) +{ + FH f = _fh_from_int(fd); + + if (!f) { + return -1; + } + + return f->clazz->_fh_lseek(f, pos, where); +} + + +int adb_shutdown(int fd) +{ + FH f = _fh_from_int(fd); + + if (!f) { + return -1; + } + + D( "adb_shutdown: %s\n", f->name); + shutdown( f->fh_socket, SD_BOTH ); + return 0; +} + + +int adb_close(int fd) +{ + FH f = _fh_from_int(fd); + + if (!f) { + return -1; + } + + D( "adb_close: %s\n", f->name); + _fh_close(f); + return 0; +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** socket-based file descriptors *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +static void +_socket_set_errno( void ) +{ + switch (WSAGetLastError()) { + case 0: errno = 0; break; + case WSAEWOULDBLOCK: errno = EAGAIN; break; + case WSAEINTR: errno = EINTR; break; + default: + D( "_socket_set_errno: unhandled value %d\n", WSAGetLastError() ); + errno = EINVAL; + } +} + +static void +_fh_socket_init( FH f ) +{ + f->fh_socket = INVALID_SOCKET; + f->event = WSACreateEvent(); + f->mask = 0; +} + +static int +_fh_socket_close( FH f ) +{ + /* gently tell any peer that we're closing the socket */ + shutdown( f->fh_socket, SD_BOTH ); + closesocket( f->fh_socket ); + f->fh_socket = INVALID_SOCKET; + CloseHandle( f->event ); + f->mask = 0; + return 0; +} + +static int +_fh_socket_lseek( FH f, int pos, int origin ) +{ + errno = EPIPE; + return -1; +} + +static int +_fh_socket_read( FH f, void* buf, int len ) +{ + int result = recv( f->fh_socket, buf, len, 0 ); + if (result == SOCKET_ERROR) { + _socket_set_errno(); + result = -1; + } + return result; +} + +static int +_fh_socket_write( FH f, const void* buf, int len ) +{ + int result = send( f->fh_socket, buf, len, 0 ); + if (result == SOCKET_ERROR) { + _socket_set_errno(); + result = -1; + } + return result; +} + +static void _fh_socket_hook( FH f, int event, EventHook hook ); /* forward */ + +static const FHClassRec _fh_socket_class = +{ + _fh_socket_init, + _fh_socket_close, + _fh_socket_lseek, + _fh_socket_read, + _fh_socket_write, + _fh_socket_hook +}; + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** replacement for libs/cutils/socket_xxxx.c *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +#include <winsock2.h> + +static int _winsock_init; + +static void +_cleanup_winsock( void ) +{ + WSACleanup(); +} + +static void +_init_winsock( void ) +{ + if (!_winsock_init) { + WSADATA wsaData; + int rc = WSAStartup( MAKEWORD(2,2), &wsaData); + if (rc != 0) { + fatal( "adb: could not initialize Winsock\n" ); + } + atexit( _cleanup_winsock ); + _winsock_init = 1; + } +} + +int socket_loopback_client(int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct sockaddr_in addr; + SOCKET s; + + if (!f) + return -1; + + if (!_winsock_init) + _init_winsock(); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket(AF_INET, type, 0); + if(s == INVALID_SOCKET) { + D("socket_loopback_client: could not create socket\n" ); + _fh_close(f); + return -1; + } + + f->fh_socket = s; + if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + D("socket_loopback_client: could not connect to %s:%d\n", type != SOCK_STREAM ? "udp" : "tcp", port ); + _fh_close(f); + return -1; + } + snprintf( f->name, sizeof(f->name), "%d(lo-client:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_loopback_client: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + +#define LISTEN_BACKLOG 4 + +int socket_loopback_server(int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct sockaddr_in addr; + SOCKET s; + int n; + + if (!f) { + return -1; + } + + if (!_winsock_init) + _init_winsock(); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket(AF_INET, type, 0); + if(s == INVALID_SOCKET) return -1; + + f->fh_socket = s; + + n = 1; + setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&n, sizeof(n)); + + if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + _fh_close(f); + return -1; + } + if (type == SOCK_STREAM) { + int ret; + + ret = listen(s, LISTEN_BACKLOG); + if (ret < 0) { + _fh_close(f); + return -1; + } + } + snprintf( f->name, sizeof(f->name), "%d(lo-server:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_loopback_server: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + + +int socket_network_client(const char *host, int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct hostent *hp; + struct sockaddr_in addr; + SOCKET s; + + if (!f) + return -1; + + if (!_winsock_init) + _init_winsock(); + + hp = gethostbyname(host); + if(hp == 0) { + _fh_close(f); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = hp->h_addrtype; + addr.sin_port = htons(port); + memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); + + s = socket(hp->h_addrtype, type, 0); + if(s == INVALID_SOCKET) { + _fh_close(f); + return -1; + } + f->fh_socket = s; + + if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + _fh_close(f); + return -1; + } + + snprintf( f->name, sizeof(f->name), "%d(net-client:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_network_client: host '%s' port %d type %s => fd %d\n", host, port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + + +int socket_inaddr_any_server(int port, int type) +{ + FH f = _fh_alloc( &_fh_socket_class ); + struct sockaddr_in addr; + SOCKET s; + int n; + + if (!f) + return -1; + + if (!_winsock_init) + _init_winsock(); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + s = socket(AF_INET, type, 0); + if(s == INVALID_SOCKET) { + _fh_close(f); + return -1; + } + + f->fh_socket = s; + n = 1; + setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&n, sizeof(n)); + + if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + _fh_close(f); + return -1; + } + + if (type == SOCK_STREAM) { + int ret; + + ret = listen(s, LISTEN_BACKLOG); + if (ret < 0) { + _fh_close(f); + return -1; + } + } + snprintf( f->name, sizeof(f->name), "%d(any-server:%s%d)", _fh_to_int(f), type != SOCK_STREAM ? "udp:" : "", port ); + D( "socket_inaddr_server: port %d type %s => fd %d\n", port, type != SOCK_STREAM ? "udp" : "tcp", _fh_to_int(f) ); + return _fh_to_int(f); +} + +#undef accept +int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen) +{ + FH serverfh = _fh_from_int(serverfd); + FH fh; + + if ( !serverfh || serverfh->clazz != &_fh_socket_class ) { + D( "adb_socket_accept: invalid fd %d\n", serverfd ); + return -1; + } + + fh = _fh_alloc( &_fh_socket_class ); + if (!fh) { + D( "adb_socket_accept: not enough memory to allocate accepted socket descriptor\n" ); + return -1; + } + + fh->fh_socket = accept( serverfh->fh_socket, addr, addrlen ); + if (fh->fh_socket == INVALID_SOCKET) { + _fh_close( fh ); + D( "adb_socket_accept: accept on fd %d return error %ld\n", serverfd, GetLastError() ); + return -1; + } + + snprintf( fh->name, sizeof(fh->name), "%d(accept:%s)", _fh_to_int(fh), serverfh->name ); + D( "adb_socket_accept on fd %d returns fd %d\n", serverfd, _fh_to_int(fh) ); + return _fh_to_int(fh); +} + + +void disable_tcp_nagle(int fd) +{ + FH fh = _fh_from_int(fd); + int on; + + if ( !fh || fh->clazz != &_fh_socket_class ) + return; + + setsockopt( fh->fh_socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&on, sizeof(on) ); +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** emulated socketpairs *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +/* we implement socketpairs directly in use space for the following reasons: + * - it avoids copying data from/to the Nt kernel + * - it allows us to implement fdevent hooks easily and cheaply, something + * that is not possible with standard Win32 pipes !! + * + * basically, we use two circular buffers, each one corresponding to a given + * direction. + * + * each buffer is implemented as two regions: + * + * region A which is (a_start,a_end) + * region B which is (0, b_end) with b_end <= a_start + * + * an empty buffer has: a_start = a_end = b_end = 0 + * + * a_start is the pointer where we start reading data + * a_end is the pointer where we start writing data, unless it is BUFFER_SIZE, + * then you start writing at b_end + * + * the buffer is full when b_end == a_start && a_end == BUFFER_SIZE + * + * there is room when b_end < a_start || a_end < BUFER_SIZE + * + * when reading, a_start is incremented, it a_start meets a_end, then + * we do: a_start = 0, a_end = b_end, b_end = 0, and keep going on.. + */ + +#define BIP_BUFFER_SIZE 4096 + +#if 0 +#include <stdio.h> +# define BIPD(x) D x +# define BIPDUMP bip_dump_hex + +static void bip_dump_hex( const unsigned char* ptr, size_t len ) +{ + int nn, len2 = len; + + if (len2 > 8) len2 = 8; + + for (nn = 0; nn < len2; nn++) + printf("%02x", ptr[nn]); + printf(" "); + + for (nn = 0; nn < len2; nn++) { + int c = ptr[nn]; + if (c < 32 || c > 127) + c = '.'; + printf("%c", c); + } + printf("\n"); + fflush(stdout); +} + +#else +# define BIPD(x) do {} while (0) +# define BIPDUMP(p,l) BIPD(p) +#endif + +typedef struct BipBufferRec_ +{ + int a_start; + int a_end; + int b_end; + int fdin; + int fdout; + int closed; + int can_write; /* boolean */ + HANDLE evt_write; /* event signaled when one can write to a buffer */ + int can_read; /* boolean */ + HANDLE evt_read; /* event signaled when one can read from a buffer */ + CRITICAL_SECTION lock; + unsigned char buff[ BIP_BUFFER_SIZE ]; + +} BipBufferRec, *BipBuffer; + +static void +bip_buffer_init( BipBuffer buffer ) +{ + D( "bit_buffer_init %p\n", buffer ); + buffer->a_start = 0; + buffer->a_end = 0; + buffer->b_end = 0; + buffer->can_write = 1; + buffer->can_read = 0; + buffer->fdin = 0; + buffer->fdout = 0; + buffer->closed = 0; + buffer->evt_write = CreateEvent( NULL, TRUE, TRUE, NULL ); + buffer->evt_read = CreateEvent( NULL, TRUE, FALSE, NULL ); + InitializeCriticalSection( &buffer->lock ); +} + +static void +bip_buffer_close( BipBuffer bip ) +{ + bip->closed = 1; + + if (!bip->can_read) { + SetEvent( bip->evt_read ); + } + if (!bip->can_write) { + SetEvent( bip->evt_write ); + } +} + +static void +bip_buffer_done( BipBuffer bip ) +{ + BIPD(( "bip_buffer_done: %d->%d\n", bip->fdin, bip->fdout )); + CloseHandle( bip->evt_read ); + CloseHandle( bip->evt_write ); + DeleteCriticalSection( &bip->lock ); +} + +static int +bip_buffer_write( BipBuffer bip, const void* src, int len ) +{ + int avail, count = 0; + + if (len <= 0) + return 0; + + BIPD(( "bip_buffer_write: enter %d->%d len %d\n", bip->fdin, bip->fdout, len )); + BIPDUMP( src, len ); + + EnterCriticalSection( &bip->lock ); + + while (!bip->can_write) { + int ret; + LeaveCriticalSection( &bip->lock ); + + if (bip->closed) { + errno = EPIPE; + return -1; + } + /* spinlocking here is probably unfair, but let's live with it */ + ret = WaitForSingleObject( bip->evt_write, INFINITE ); + if (ret != WAIT_OBJECT_0) { /* buffer probably closed */ + D( "bip_buffer_write: error %d->%d WaitForSingleObject returned %d, error %ld\n", bip->fdin, bip->fdout, ret, GetLastError() ); + return 0; + } + if (bip->closed) { + errno = EPIPE; + return -1; + } + EnterCriticalSection( &bip->lock ); + } + + BIPD(( "bip_buffer_write: exec %d->%d len %d\n", bip->fdin, bip->fdout, len )); + + avail = BIP_BUFFER_SIZE - bip->a_end; + if (avail > 0) + { + /* we can append to region A */ + if (avail > len) + avail = len; + + memcpy( bip->buff + bip->a_end, src, avail ); + src += avail; + count += avail; + len -= avail; + + bip->a_end += avail; + if (bip->a_end == BIP_BUFFER_SIZE && bip->a_start == 0) { + bip->can_write = 0; + ResetEvent( bip->evt_write ); + goto Exit; + } + } + + if (len == 0) + goto Exit; + + avail = bip->a_start - bip->b_end; + assert( avail > 0 ); /* since can_write is TRUE */ + + if (avail > len) + avail = len; + + memcpy( bip->buff + bip->b_end, src, avail ); + count += avail; + bip->b_end += avail; + + if (bip->b_end == bip->a_start) { + bip->can_write = 0; + ResetEvent( bip->evt_write ); + } + +Exit: + assert( count > 0 ); + + if ( !bip->can_read ) { + bip->can_read = 1; + SetEvent( bip->evt_read ); + } + + BIPD(( "bip_buffer_write: exit %d->%d count %d (as=%d ae=%d be=%d cw=%d cr=%d\n", + bip->fdin, bip->fdout, count, bip->a_start, bip->a_end, bip->b_end, bip->can_write, bip->can_read )); + LeaveCriticalSection( &bip->lock ); + + return count; + } + +static int +bip_buffer_read( BipBuffer bip, void* dst, int len ) +{ + int avail, count = 0; + + if (len <= 0) + return 0; + + BIPD(( "bip_buffer_read: enter %d->%d len %d\n", bip->fdin, bip->fdout, len )); + + EnterCriticalSection( &bip->lock ); + while ( !bip->can_read ) + { +#if 0 + LeaveCriticalSection( &bip->lock ); + errno = EAGAIN; + return -1; +#else + int ret; + LeaveCriticalSection( &bip->lock ); + + if (bip->closed) { + errno = EPIPE; + return -1; + } + + ret = WaitForSingleObject( bip->evt_read, INFINITE ); + if (ret != WAIT_OBJECT_0) { /* probably closed buffer */ + D( "bip_buffer_read: error %d->%d WaitForSingleObject returned %d, error %ld\n", bip->fdin, bip->fdout, ret, GetLastError()); + return 0; + } + if (bip->closed) { + errno = EPIPE; + return -1; + } + EnterCriticalSection( &bip->lock ); +#endif + } + + BIPD(( "bip_buffer_read: exec %d->%d len %d\n", bip->fdin, bip->fdout, len )); + + avail = bip->a_end - bip->a_start; + assert( avail > 0 ); /* since can_read is TRUE */ + + if (avail > len) + avail = len; + + memcpy( dst, bip->buff + bip->a_start, avail ); + dst += avail; + count += avail; + len -= avail; + + bip->a_start += avail; + if (bip->a_start < bip->a_end) + goto Exit; + + bip->a_start = 0; + bip->a_end = bip->b_end; + bip->b_end = 0; + + avail = bip->a_end; + if (avail > 0) { + if (avail > len) + avail = len; + memcpy( dst, bip->buff, avail ); + count += avail; + bip->a_start += avail; + + if ( bip->a_start < bip->a_end ) + goto Exit; + + bip->a_start = bip->a_end = 0; + } + + bip->can_read = 0; + ResetEvent( bip->evt_read ); + +Exit: + assert( count > 0 ); + + if (!bip->can_write ) { + bip->can_write = 1; + SetEvent( bip->evt_write ); + } + + BIPDUMP( (const unsigned char*)dst - count, count ); + BIPD(( "bip_buffer_read: exit %d->%d count %d (as=%d ae=%d be=%d cw=%d cr=%d\n", + bip->fdin, bip->fdout, count, bip->a_start, bip->a_end, bip->b_end, bip->can_write, bip->can_read )); + LeaveCriticalSection( &bip->lock ); + + return count; +} + +typedef struct SocketPairRec_ +{ + BipBufferRec a2b_bip; + BipBufferRec b2a_bip; + FH a_fd; + int used; + +} SocketPairRec; + +void _fh_socketpair_init( FH f ) +{ + f->fh_pair = NULL; +} + +static int +_fh_socketpair_close( FH f ) +{ + if ( f->fh_pair ) { + SocketPair pair = f->fh_pair; + + if ( f == pair->a_fd ) { + pair->a_fd = NULL; + } + + bip_buffer_close( &pair->b2a_bip ); + bip_buffer_close( &pair->a2b_bip ); + + if ( --pair->used == 0 ) { + bip_buffer_done( &pair->b2a_bip ); + bip_buffer_done( &pair->a2b_bip ); + free( pair ); + } + f->fh_pair = NULL; + } + return 0; +} + +static int +_fh_socketpair_lseek( FH f, int pos, int origin ) +{ + errno = ESPIPE; + return -1; +} + +static int +_fh_socketpair_read( FH f, void* buf, int len ) +{ + SocketPair pair = f->fh_pair; + BipBuffer bip; + + if (!pair) + return -1; + + if ( f == pair->a_fd ) + bip = &pair->b2a_bip; + else + bip = &pair->a2b_bip; + + return bip_buffer_read( bip, buf, len ); +} + +static int +_fh_socketpair_write( FH f, const void* buf, int len ) +{ + SocketPair pair = f->fh_pair; + BipBuffer bip; + + if (!pair) + return -1; + + if ( f == pair->a_fd ) + bip = &pair->a2b_bip; + else + bip = &pair->b2a_bip; + + return bip_buffer_write( bip, buf, len ); +} + + +static void _fh_socketpair_hook( FH f, int event, EventHook hook ); /* forward */ + +static const FHClassRec _fh_socketpair_class = +{ + _fh_socketpair_init, + _fh_socketpair_close, + _fh_socketpair_lseek, + _fh_socketpair_read, + _fh_socketpair_write, + _fh_socketpair_hook +}; + + +int adb_socketpair( int sv[2] ) +{ + FH fa, fb; + SocketPair pair; + + fa = _fh_alloc( &_fh_socketpair_class ); + fb = _fh_alloc( &_fh_socketpair_class ); + + if (!fa || !fb) + goto Fail; + + pair = malloc( sizeof(*pair) ); + if (pair == NULL) { + D("adb_socketpair: not enough memory to allocate pipes\n" ); + goto Fail; + } + + bip_buffer_init( &pair->a2b_bip ); + bip_buffer_init( &pair->b2a_bip ); + + fa->fh_pair = pair; + fb->fh_pair = pair; + pair->used = 2; + pair->a_fd = fa; + + sv[0] = _fh_to_int(fa); + sv[1] = _fh_to_int(fb); + + pair->a2b_bip.fdin = sv[0]; + pair->a2b_bip.fdout = sv[1]; + pair->b2a_bip.fdin = sv[1]; + pair->b2a_bip.fdout = sv[0]; + + snprintf( fa->name, sizeof(fa->name), "%d(pair:%d)", sv[0], sv[1] ); + snprintf( fb->name, sizeof(fb->name), "%d(pair:%d)", sv[1], sv[0] ); + D( "adb_socketpair: returns (%d, %d)\n", sv[0], sv[1] ); + return 0; + +Fail: + _fh_close(fb); + _fh_close(fa); + return -1; +} + +/**************************************************************************/ +/**************************************************************************/ +/***** *****/ +/***** fdevents emulation *****/ +/***** *****/ +/***** this is a very simple implementation, we rely on the fact *****/ +/***** that ADB doesn't use FDE_ERROR. *****/ +/***** *****/ +/**************************************************************************/ +/**************************************************************************/ + +#define FATAL(x...) fatal(__FUNCTION__, x) + +#if DEBUG +static void dump_fde(fdevent *fde, const char *info) +{ + fprintf(stderr,"FDE #%03d %c%c%c %s\n", fde->fd, + fde->state & FDE_READ ? 'R' : ' ', + fde->state & FDE_WRITE ? 'W' : ' ', + fde->state & FDE_ERROR ? 'E' : ' ', + info); +} +#else +#define dump_fde(fde, info) do { } while(0) +#endif + +#define FDE_EVENTMASK 0x00ff +#define FDE_STATEMASK 0xff00 + +#define FDE_ACTIVE 0x0100 +#define FDE_PENDING 0x0200 +#define FDE_CREATED 0x0400 + +static void fdevent_plist_enqueue(fdevent *node); +static void fdevent_plist_remove(fdevent *node); +static fdevent *fdevent_plist_dequeue(void); + +static fdevent list_pending = { + .next = &list_pending, + .prev = &list_pending, +}; + +static fdevent **fd_table = 0; +static int fd_table_max = 0; + +typedef struct EventLooperRec_* EventLooper; + +typedef struct EventHookRec_ +{ + EventHook next; + FH fh; + HANDLE h; + int wanted; /* wanted event flags */ + int ready; /* ready event flags */ + void* aux; + void (*prepare)( EventHook hook ); + int (*start) ( EventHook hook ); + void (*stop) ( EventHook hook ); + int (*check) ( EventHook hook ); + int (*peek) ( EventHook hook ); +} EventHookRec; + +static EventHook _free_hooks; + +static EventHook +event_hook_alloc( FH fh ) +{ + EventHook hook = _free_hooks; + if (hook != NULL) + _free_hooks = hook->next; + else { + hook = malloc( sizeof(*hook) ); + if (hook == NULL) + fatal( "could not allocate event hook\n" ); + } + hook->next = NULL; + hook->fh = fh; + hook->wanted = 0; + hook->ready = 0; + hook->h = INVALID_HANDLE_VALUE; + hook->aux = NULL; + + hook->prepare = NULL; + hook->start = NULL; + hook->stop = NULL; + hook->check = NULL; + hook->peek = NULL; + + return hook; +} + +static void +event_hook_free( EventHook hook ) +{ + hook->fh = NULL; + hook->wanted = 0; + hook->ready = 0; + hook->next = _free_hooks; + _free_hooks = hook; +} + + +static void +event_hook_signal( EventHook hook ) +{ + FH f = hook->fh; + int fd = _fh_to_int(f); + fdevent* fde = fd_table[ fd - WIN32_FH_BASE ]; + + if (fde != NULL && fde->fd == fd) { + if ((fde->state & FDE_PENDING) == 0) { + fde->state |= FDE_PENDING; + fdevent_plist_enqueue( fde ); + } + fde->events |= hook->wanted; + } +} + + +#define MAX_LOOPER_HANDLES WIN32_MAX_FHS + +typedef struct EventLooperRec_ +{ + EventHook hooks; + HANDLE htab[ MAX_LOOPER_HANDLES ]; + int htab_count; + +} EventLooperRec; + +static EventHook* +event_looper_find_p( EventLooper looper, FH fh ) +{ + EventHook *pnode = &looper->hooks; + EventHook node = *pnode; + for (;;) { + if ( node == NULL || node->fh == fh ) + break; + pnode = &node->next; + node = *pnode; + } + return pnode; +} + +static void +event_looper_hook( EventLooper looper, int fd, int events ) +{ + FH f = _fh_from_int(fd); + EventHook *pnode; + EventHook node; + + if (f == NULL) /* invalid arg */ { + D("event_looper_hook: invalid fd=%d\n", fd); + return; + } + + pnode = event_looper_find_p( looper, f ); + node = *pnode; + if ( node == NULL ) { + node = event_hook_alloc( f ); + node->next = *pnode; + *pnode = node; + } + + if ( (node->wanted & events) != events ) { + /* this should update start/stop/check/peek */ + D("event_looper_hook: call hook for %d (new=%x, old=%x)\n", + fd, node->wanted, events); + f->clazz->_fh_hook( f, events & ~node->wanted, node ); + node->wanted |= events; + } else { + D("event_looper_hook: ignoring events %x for %d wanted=%x)\n", + events, fd, node->wanted); + } +} + +static void +event_looper_unhook( EventLooper looper, int fd, int events ) +{ + FH fh = _fh_from_int(fd); + EventHook *pnode = event_looper_find_p( looper, fh ); + EventHook node = *pnode; + + if (node != NULL) { + int events2 = events & node->wanted; + if ( events2 == 0 ) { + D( "event_looper_unhook: events %x not registered for fd %d\n", events, fd ); + return; + } + node->wanted &= ~events2; + if (!node->wanted) { + *pnode = node->next; + event_hook_free( node ); + } + } +} + +static EventLooperRec win32_looper; + +static void fdevent_init(void) +{ + win32_looper.htab_count = 0; + win32_looper.hooks = NULL; +} + +static void fdevent_connect(fdevent *fde) +{ + EventLooper looper = &win32_looper; + int events = fde->state & FDE_EVENTMASK; + + if (events != 0) + event_looper_hook( looper, fde->fd, events ); +} + +static void fdevent_disconnect(fdevent *fde) +{ + EventLooper looper = &win32_looper; + int events = fde->state & FDE_EVENTMASK; + + if (events != 0) + event_looper_unhook( looper, fde->fd, events ); +} + +static void fdevent_update(fdevent *fde, unsigned events) +{ + EventLooper looper = &win32_looper; + unsigned events0 = fde->state & FDE_EVENTMASK; + + if (events != events0) { + int removes = events0 & ~events; + int adds = events & ~events0; + if (removes) { + D("fdevent_update: remove %x from %d\n", removes, fde->fd); + event_looper_unhook( looper, fde->fd, removes ); + } + if (adds) { + D("fdevent_update: add %x to %d\n", adds, fde->fd); + event_looper_hook ( looper, fde->fd, adds ); + } + } +} + +static void fdevent_process() +{ + EventLooper looper = &win32_looper; + EventHook hook; + int gotone = 0; + + /* if we have at least one ready hook, execute it/them */ + for (hook = looper->hooks; hook; hook = hook->next) { + hook->ready = 0; + if (hook->prepare) { + hook->prepare(hook); + if (hook->ready != 0) { + event_hook_signal( hook ); + gotone = 1; + } + } + } + + /* nothing's ready yet, so wait for something to happen */ + if (!gotone) + { + looper->htab_count = 0; + + for (hook = looper->hooks; hook; hook = hook->next) + { + if (hook->start && !hook->start(hook)) { + D( "fdevent_process: error when starting a hook\n" ); + return; + } + if (hook->h != INVALID_HANDLE_VALUE) { + int nn; + + for (nn = 0; nn < looper->htab_count; nn++) + { + if ( looper->htab[nn] == hook->h ) + goto DontAdd; + } + looper->htab[ looper->htab_count++ ] = hook->h; + DontAdd: + ; + } + } + + if (looper->htab_count == 0) { + D( "fdevent_process: nothing to wait for !!\n" ); + return; + } + + do + { + int wait_ret; + + D( "adb_win32: waiting for %d events\n", looper->htab_count ); + if (looper->htab_count > MAXIMUM_WAIT_OBJECTS) { + D("handle count %d exceeds MAXIMUM_WAIT_OBJECTS, aborting!\n", looper->htab_count); + abort(); + } + wait_ret = WaitForMultipleObjects( looper->htab_count, looper->htab, FALSE, INFINITE ); + if (wait_ret == (int)WAIT_FAILED) { + D( "adb_win32: wait failed, error %ld\n", GetLastError() ); + } else { + D( "adb_win32: got one (index %d)\n", wait_ret ); + + /* according to Cygwin, some objects like consoles wake up on "inappropriate" events + * like mouse movements. we need to filter these with the "check" function + */ + if ((unsigned)wait_ret < (unsigned)looper->htab_count) + { + for (hook = looper->hooks; hook; hook = hook->next) + { + if ( looper->htab[wait_ret] == hook->h && + (!hook->check || hook->check(hook)) ) + { + D( "adb_win32: signaling %s for %x\n", hook->fh->name, hook->ready ); + event_hook_signal( hook ); + gotone = 1; + break; + } + } + } + } + } + while (!gotone); + + for (hook = looper->hooks; hook; hook = hook->next) { + if (hook->stop) + hook->stop( hook ); + } + } + + for (hook = looper->hooks; hook; hook = hook->next) { + if (hook->peek && hook->peek(hook)) + event_hook_signal( hook ); + } +} + + +static void fdevent_register(fdevent *fde) +{ + int fd = fde->fd - WIN32_FH_BASE; + + if(fd < 0) { + FATAL("bogus negative fd (%d)\n", fde->fd); + } + + if(fd >= fd_table_max) { + int oldmax = fd_table_max; + if(fde->fd > 32000) { + FATAL("bogus huuuuge fd (%d)\n", fde->fd); + } + if(fd_table_max == 0) { + fdevent_init(); + fd_table_max = 256; + } + while(fd_table_max <= fd) { + fd_table_max *= 2; + } + fd_table = realloc(fd_table, sizeof(fdevent*) * fd_table_max); + if(fd_table == 0) { + FATAL("could not expand fd_table to %d entries\n", fd_table_max); + } + memset(fd_table + oldmax, 0, sizeof(int) * (fd_table_max - oldmax)); + } + + fd_table[fd] = fde; +} + +static void fdevent_unregister(fdevent *fde) +{ + int fd = fde->fd - WIN32_FH_BASE; + + if((fd < 0) || (fd >= fd_table_max)) { + FATAL("fd out of range (%d)\n", fde->fd); + } + + if(fd_table[fd] != fde) { + FATAL("fd_table out of sync"); + } + + fd_table[fd] = 0; + + if(!(fde->state & FDE_DONT_CLOSE)) { + dump_fde(fde, "close"); + adb_close(fde->fd); + } +} + +static void fdevent_plist_enqueue(fdevent *node) +{ + fdevent *list = &list_pending; + + node->next = list; + node->prev = list->prev; + node->prev->next = node; + list->prev = node; +} + +static void fdevent_plist_remove(fdevent *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; +} + +static fdevent *fdevent_plist_dequeue(void) +{ + fdevent *list = &list_pending; + fdevent *node = list->next; + + if(node == list) return 0; + + list->next = node->next; + list->next->prev = list; + node->next = 0; + node->prev = 0; + + return node; +} + +fdevent *fdevent_create(int fd, fd_func func, void *arg) +{ + fdevent *fde = (fdevent*) malloc(sizeof(fdevent)); + if(fde == 0) return 0; + fdevent_install(fde, fd, func, arg); + fde->state |= FDE_CREATED; + return fde; +} + +void fdevent_destroy(fdevent *fde) +{ + if(fde == 0) return; + if(!(fde->state & FDE_CREATED)) { + FATAL("fde %p not created by fdevent_create()\n", fde); + } + fdevent_remove(fde); +} + +void fdevent_install(fdevent *fde, int fd, fd_func func, void *arg) +{ + memset(fde, 0, sizeof(fdevent)); + fde->state = FDE_ACTIVE; + fde->fd = fd; + fde->func = func; + fde->arg = arg; + + fdevent_register(fde); + dump_fde(fde, "connect"); + fdevent_connect(fde); + fde->state |= FDE_ACTIVE; +} + +void fdevent_remove(fdevent *fde) +{ + if(fde->state & FDE_PENDING) { + fdevent_plist_remove(fde); + } + + if(fde->state & FDE_ACTIVE) { + fdevent_disconnect(fde); + dump_fde(fde, "disconnect"); + fdevent_unregister(fde); + } + + fde->state = 0; + fde->events = 0; +} + + +void fdevent_set(fdevent *fde, unsigned events) +{ + events &= FDE_EVENTMASK; + + if((fde->state & FDE_EVENTMASK) == (int)events) return; + + if(fde->state & FDE_ACTIVE) { + fdevent_update(fde, events); + dump_fde(fde, "update"); + } + + fde->state = (fde->state & FDE_STATEMASK) | events; + + if(fde->state & FDE_PENDING) { + /* if we're pending, make sure + ** we don't signal an event that + ** is no longer wanted. + */ + fde->events &= (~events); + if(fde->events == 0) { + fdevent_plist_remove(fde); + fde->state &= (~FDE_PENDING); + } + } +} + +void fdevent_add(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) | (events & FDE_EVENTMASK)); +} + +void fdevent_del(fdevent *fde, unsigned events) +{ + fdevent_set( + fde, (fde->state & FDE_EVENTMASK) & (~(events & FDE_EVENTMASK))); +} + +void fdevent_loop() +{ + fdevent *fde; + + for(;;) { +#if DEBUG + fprintf(stderr,"--- ---- waiting for events\n"); +#endif + fdevent_process(); + + while((fde = fdevent_plist_dequeue())) { + unsigned events = fde->events; + fde->events = 0; + fde->state &= (~FDE_PENDING); + dump_fde(fde, "callback"); + fde->func(fde->fd, events, fde->arg); + } + } +} + +/** FILE EVENT HOOKS + **/ + +static void _event_file_prepare( EventHook hook ) +{ + if (hook->wanted & (FDE_READ|FDE_WRITE)) { + /* we can always read/write */ + hook->ready |= hook->wanted & (FDE_READ|FDE_WRITE); + } +} + +static int _event_file_peek( EventHook hook ) +{ + return (hook->wanted & (FDE_READ|FDE_WRITE)); +} + +static void _fh_file_hook( FH f, int events, EventHook hook ) +{ + hook->h = f->fh_handle; + hook->prepare = _event_file_prepare; + hook->peek = _event_file_peek; +} + +/** SOCKET EVENT HOOKS + **/ + +static void _event_socket_verify( EventHook hook, WSANETWORKEVENTS* evts ) +{ + if ( evts->lNetworkEvents & (FD_READ|FD_ACCEPT|FD_CLOSE) ) { + if (hook->wanted & FDE_READ) + hook->ready |= FDE_READ; + if ((evts->iErrorCode[FD_READ] != 0) && hook->wanted & FDE_ERROR) + hook->ready |= FDE_ERROR; + } + if ( evts->lNetworkEvents & (FD_WRITE|FD_CONNECT|FD_CLOSE) ) { + if (hook->wanted & FDE_WRITE) + hook->ready |= FDE_WRITE; + if ((evts->iErrorCode[FD_WRITE] != 0) && hook->wanted & FDE_ERROR) + hook->ready |= FDE_ERROR; + } + if ( evts->lNetworkEvents & FD_OOB ) { + if (hook->wanted & FDE_ERROR) + hook->ready |= FDE_ERROR; + } +} + +static void _event_socket_prepare( EventHook hook ) +{ + WSANETWORKEVENTS evts; + + /* look if some of the events we want already happened ? */ + if (!WSAEnumNetworkEvents( hook->fh->fh_socket, NULL, &evts )) + _event_socket_verify( hook, &evts ); +} + +static int _socket_wanted_to_flags( int wanted ) +{ + int flags = 0; + if (wanted & FDE_READ) + flags |= FD_READ | FD_ACCEPT | FD_CLOSE; + + if (wanted & FDE_WRITE) + flags |= FD_WRITE | FD_CONNECT | FD_CLOSE; + + if (wanted & FDE_ERROR) + flags |= FD_OOB; + + return flags; +} + +static int _event_socket_start( EventHook hook ) +{ + /* create an event which we're going to wait for */ + FH fh = hook->fh; + long flags = _socket_wanted_to_flags( hook->wanted ); + + hook->h = fh->event; + if (hook->h == INVALID_HANDLE_VALUE) { + D( "_event_socket_start: no event for %s\n", fh->name ); + return 0; + } + + if ( flags != fh->mask ) { + D( "_event_socket_start: hooking %s for %x (flags %ld)\n", hook->fh->name, hook->wanted, flags ); + if ( WSAEventSelect( fh->fh_socket, hook->h, flags ) ) { + D( "_event_socket_start: WSAEventSelect() for %s failed, error %d\n", hook->fh->name, WSAGetLastError() ); + CloseHandle( hook->h ); + hook->h = INVALID_HANDLE_VALUE; + exit(1); + return 0; + } + fh->mask = flags; + } + return 1; +} + +static void _event_socket_stop( EventHook hook ) +{ + hook->h = INVALID_HANDLE_VALUE; +} + +static int _event_socket_check( EventHook hook ) +{ + int result = 0; + FH fh = hook->fh; + WSANETWORKEVENTS evts; + + if (!WSAEnumNetworkEvents( fh->fh_socket, hook->h, &evts ) ) { + _event_socket_verify( hook, &evts ); + result = (hook->ready != 0); + if (result) { + ResetEvent( hook->h ); + } + } + D( "_event_socket_check %s returns %d\n", fh->name, result ); + return result; +} + +static int _event_socket_peek( EventHook hook ) +{ + WSANETWORKEVENTS evts; + FH fh = hook->fh; + + /* look if some of the events we want already happened ? */ + if (!WSAEnumNetworkEvents( fh->fh_socket, NULL, &evts )) { + _event_socket_verify( hook, &evts ); + if (hook->ready) + ResetEvent( hook->h ); + } + + return hook->ready != 0; +} + + + +static void _fh_socket_hook( FH f, int events, EventHook hook ) +{ + hook->prepare = _event_socket_prepare; + hook->start = _event_socket_start; + hook->stop = _event_socket_stop; + hook->check = _event_socket_check; + hook->peek = _event_socket_peek; + + _event_socket_start( hook ); +} + +/** SOCKETPAIR EVENT HOOKS + **/ + +static void _event_socketpair_prepare( EventHook hook ) +{ + FH fh = hook->fh; + SocketPair pair = fh->fh_pair; + BipBuffer rbip = (pair->a_fd == fh) ? &pair->b2a_bip : &pair->a2b_bip; + BipBuffer wbip = (pair->a_fd == fh) ? &pair->a2b_bip : &pair->b2a_bip; + + if (hook->wanted & FDE_READ && rbip->can_read) + hook->ready |= FDE_READ; + + if (hook->wanted & FDE_WRITE && wbip->can_write) + hook->ready |= FDE_WRITE; + } + + static int _event_socketpair_start( EventHook hook ) + { + FH fh = hook->fh; + SocketPair pair = fh->fh_pair; + BipBuffer rbip = (pair->a_fd == fh) ? &pair->b2a_bip : &pair->a2b_bip; + BipBuffer wbip = (pair->a_fd == fh) ? &pair->a2b_bip : &pair->b2a_bip; + + if (hook->wanted == FDE_READ) + hook->h = rbip->evt_read; + + else if (hook->wanted == FDE_WRITE) + hook->h = wbip->evt_write; + + else { + D("_event_socketpair_start: can't handle FDE_READ+FDE_WRITE\n" ); + return 0; + } + D( "_event_socketpair_start: hook %s for %x wanted=%x\n", + hook->fh->name, _fh_to_int(fh), hook->wanted); + return 1; +} + +static int _event_socketpair_peek( EventHook hook ) +{ + _event_socketpair_prepare( hook ); + return hook->ready != 0; +} + +static void _fh_socketpair_hook( FH fh, int events, EventHook hook ) +{ + hook->prepare = _event_socketpair_prepare; + hook->start = _event_socketpair_start; + hook->peek = _event_socketpair_peek; +} + + +void +adb_sysdeps_init( void ) +{ +#define ADB_MUTEX(x) InitializeCriticalSection( & x ); +#include "mutex_list.h" + InitializeCriticalSection( &_win32_lock ); +} + diff --git a/adb/test_track_devices.c b/adb/test_track_devices.c new file mode 100644 index 0000000..77b3ad9 --- /dev/null +++ b/adb/test_track_devices.c @@ -0,0 +1,97 @@ +/* a simple test program, connects to ADB server, and opens a track-devices session */ +#include <netdb.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <memory.h> + +static void +panic( const char* msg ) +{ + fprintf(stderr, "PANIC: %s: %s\n", msg, strerror(errno)); + exit(1); +} + +static int +unix_write( int fd, const char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = write(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + +static int +unix_read( int fd, char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = read(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + + +int main( void ) +{ + int ret, s; + struct sockaddr_in server; + char buffer[1024]; + const char* request = "host:track-devices"; + int len; + + memset( &server, 0, sizeof(server) ); + server.sin_family = AF_INET; + server.sin_port = htons(5037); + server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket( PF_INET, SOCK_STREAM, 0 ); + ret = connect( s, (struct sockaddr*) &server, sizeof(server) ); + if (ret < 0) panic( "could not connect to server" ); + + /* send the request */ + len = snprintf( buffer, sizeof buffer, "%04x%s", strlen(request), request ); + if (unix_write(s, buffer, len) < 0) + panic( "could not send request" ); + + /* read the OKAY answer */ + if (unix_read(s, buffer, 4) != 4) + panic( "could not read request" ); + + printf( "server answer: %.*s\n", 4, buffer ); + + /* now loop */ + for (;;) { + char head[5] = "0000"; + + if (unix_read(s, head, 4) < 0) + panic("could not read length"); + + if ( sscanf( head, "%04x", &len ) != 1 ) + panic("could not decode length"); + + if (unix_read(s, buffer, len) != len) + panic("could not read data"); + + printf( "received header %.*s (%d bytes):\n%.*s", 4, head, len, len, buffer ); + } + close(s); +} diff --git a/adb/test_track_jdwp.c b/adb/test_track_jdwp.c new file mode 100644 index 0000000..8ecc6b8 --- /dev/null +++ b/adb/test_track_jdwp.c @@ -0,0 +1,97 @@ +/* a simple test program, connects to ADB server, and opens a track-devices session */ +#include <netdb.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <memory.h> + +static void +panic( const char* msg ) +{ + fprintf(stderr, "PANIC: %s: %s\n", msg, strerror(errno)); + exit(1); +} + +static int +unix_write( int fd, const char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = write(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + +static int +unix_read( int fd, char* buf, int len ) +{ + int result = 0; + while (len > 0) { + int len2 = read(fd, buf, len); + if (len2 < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + result += len2; + len -= len2; + buf += len2; + } + return result; +} + + +int main( void ) +{ + int ret, s; + struct sockaddr_in server; + char buffer[1024]; + const char* request = "track-jdwp"; + int len; + + memset( &server, 0, sizeof(server) ); + server.sin_family = AF_INET; + server.sin_port = htons(5037); + server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket( PF_INET, SOCK_STREAM, 0 ); + ret = connect( s, (struct sockaddr*) &server, sizeof(server) ); + if (ret < 0) panic( "could not connect to server" ); + + /* send the request */ + len = snprintf( buffer, sizeof buffer, "%04x%s", strlen(request), request ); + if (unix_write(s, buffer, len) < 0) + panic( "could not send request" ); + + /* read the OKAY answer */ + if (unix_read(s, buffer, 4) != 4) + panic( "could not read request" ); + + printf( "server answer: %.*s\n", 4, buffer ); + + /* now loop */ + for (;;) { + char head[5] = "0000"; + + if (unix_read(s, head, 4) < 0) + panic("could not read length"); + + if ( sscanf( head, "%04x", &len ) != 1 ) + panic("could not decode length"); + + if (unix_read(s, buffer, len) != len) + panic("could not read data"); + + printf( "received header %.*s (%d bytes):\n%.*s", 4, head, len, len, buffer ); + } + close(s); +} diff --git a/adb/transport.c b/adb/transport.c new file mode 100644 index 0000000..62bdfdb --- /dev/null +++ b/adb/transport.c @@ -0,0 +1,1042 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +static void transport_unref(atransport *t); + +static atransport transport_list = { + .next = &transport_list, + .prev = &transport_list, +}; + +ADB_MUTEX_DEFINE( transport_lock ); + +#if ADB_TRACE +static void dump_hex( const unsigned char* ptr, size_t len ) +{ + int nn, len2 = len; + + if (len2 > 16) len2 = 16; + + for (nn = 0; nn < len2; nn++) + D("%02x", ptr[nn]); + D(" "); + + for (nn = 0; nn < len2; nn++) { + int c = ptr[nn]; + if (c < 32 || c > 127) + c = '.'; + D("%c", c); + } + D("\n"); + fflush(stdout); +} +#endif + +void +kick_transport(atransport* t) +{ + if (t && !t->kicked) + { + int kicked; + + adb_mutex_lock(&transport_lock); + kicked = t->kicked; + if (!kicked) + t->kicked = 1; + adb_mutex_unlock(&transport_lock); + + if (!kicked) + t->kick(t); + } +} + +void +run_transport_disconnects(atransport* t) +{ + adisconnect* dis = t->disconnects.next; + + D("run_transport_disconnects: %p (%s)\n", t, t->serial ? t->serial : "unknown" ); + while (dis != &t->disconnects) { + adisconnect* next = dis->next; + dis->func( dis->opaque, t ); + dis = next; + } +} + +static int +read_packet(int fd, apacket** ppacket) +{ + char *p = (char*)ppacket; /* really read a packet address */ + int r; + int len = sizeof(*ppacket); + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("read_packet: %d error %d %d\n", fd, r, errno); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + +#if ADB_TRACE + if (ADB_TRACING) + { + unsigned command = (*ppacket)->msg.command; + int len = (*ppacket)->msg.data_length; + char cmd[5]; + int n; + + for (n = 0; n < 4; n++) { + int b = (command >> (n*8)) & 255; + if (b >= 32 && b < 127) + cmd[n] = (char)b; + else + cmd[n] = '.'; + } + cmd[4] = 0; + + D("read_packet: %d ok: [%08x %s] %08x %08x (%d) ", + fd, command, cmd, (*ppacket)->msg.arg0, (*ppacket)->msg.arg1, len); + dump_hex((*ppacket)->data, len); + } +#endif + return 0; +} + +static int +write_packet(int fd, apacket** ppacket) +{ + char *p = (char*) ppacket; /* we really write the packet address */ + int r, len = sizeof(ppacket); + +#if ADB_TRACE + if (ADB_TRACING) + { + unsigned command = (*ppacket)->msg.command; + int len = (*ppacket)->msg.data_length; + char cmd[5]; + int n; + + for (n = 0; n < 4; n++) { + int b = (command >> (n*8)) & 255; + if (b >= 32 && b < 127) + cmd[n] = (char)b; + else + cmd[n] = '.'; + } + cmd[4] = 0; + + D("write_packet: %d [%08x %s] %08x %08x (%d) ", + fd, command, cmd, (*ppacket)->msg.arg0, (*ppacket)->msg.arg1, len); + dump_hex((*ppacket)->data, len); + } +#endif + len = sizeof(ppacket); + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("write_packet: %d error %d %d\n", fd, r, errno); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + return 0; +} + +static void transport_socket_events(int fd, unsigned events, void *_t) +{ + if(events & FDE_READ){ + apacket *p = 0; + if(read_packet(fd, &p)){ + D("failed to read packet from transport socket on fd %d\n", fd); + } else { + handle_packet(p, (atransport *) _t); + } + } +} + +void send_packet(apacket *p, atransport *t) +{ + unsigned char *x; + unsigned sum; + unsigned count; + + p->msg.magic = p->msg.command ^ 0xffffffff; + + count = p->msg.data_length; + x = (unsigned char *) p->data; + sum = 0; + while(count-- > 0){ + sum += *x++; + } + p->msg.data_check = sum; + + print_packet("send", p); + + if (t == NULL) { + fatal_errno("Transport is null"); + D("Transport is null \n"); + } + + if(write_packet(t->transport_socket, &p)){ + fatal_errno("cannot enqueue packet on transport socket"); + } +} + +/* The transport is opened by transport_register_func before +** the input and output threads are started. +** +** The output thread issues a SYNC(1, token) message to let +** the input thread know to start things up. In the event +** of transport IO failure, the output thread will post a +** SYNC(0,0) message to ensure shutdown. +** +** The transport will not actually be closed until both +** threads exit, but the input thread will kick the transport +** on its way out to disconnect the underlying device. +*/ + +static void *output_thread(void *_t) +{ + atransport *t = _t; + apacket *p; + + D("from_remote: starting thread for transport %p, on fd %d\n", t, t->fd ); + + D("from_remote: transport %p SYNC online (%d)\n", t, t->sync_token + 1); + p = get_apacket(); + p->msg.command = A_SYNC; + p->msg.arg0 = 1; + p->msg.arg1 = ++(t->sync_token); + p->msg.magic = A_SYNC ^ 0xffffffff; + if(write_packet(t->fd, &p)) { + put_apacket(p); + D("from_remote: failed to write SYNC apacket to transport %p", t); + goto oops; + } + + D("from_remote: data pump for transport %p\n", t); + for(;;) { + p = get_apacket(); + + if(t->read_from_remote(p, t) == 0){ + D("from_remote: received remote packet, sending to transport %p\n", + t); + if(write_packet(t->fd, &p)){ + put_apacket(p); + D("from_remote: failed to write apacket to transport %p", t); + goto oops; + } + } else { + D("from_remote: remote read failed for transport %p\n", p); + put_apacket(p); + break; + } + } + + D("from_remote: SYNC offline for transport %p\n", t); + p = get_apacket(); + p->msg.command = A_SYNC; + p->msg.arg0 = 0; + p->msg.arg1 = 0; + p->msg.magic = A_SYNC ^ 0xffffffff; + if(write_packet(t->fd, &p)) { + put_apacket(p); + D("from_remote: failed to write SYNC apacket to transport %p", t); + } + +oops: + D("from_remote: thread is exiting for transport %p\n", t); + kick_transport(t); + transport_unref(t); + return 0; +} + +static void *input_thread(void *_t) +{ + atransport *t = _t; + apacket *p; + int active = 0; + + D("to_remote: starting input_thread for %p, reading from fd %d\n", + t, t->fd); + + for(;;){ + if(read_packet(t->fd, &p)) { + D("to_remote: failed to read apacket from transport %p on fd %d\n", + t, t->fd ); + break; + } + if(p->msg.command == A_SYNC){ + if(p->msg.arg0 == 0) { + D("to_remote: transport %p SYNC offline\n", t); + put_apacket(p); + break; + } else { + if(p->msg.arg1 == t->sync_token) { + D("to_remote: transport %p SYNC online\n", t); + active = 1; + } else { + D("to_remote: trandport %p ignoring SYNC %d != %d\n", + t, p->msg.arg1, t->sync_token); + } + } + } else { + if(active) { + D("to_remote: transport %p got packet, sending to remote\n", t); + t->write_to_remote(p, t); + } else { + D("to_remote: transport %p ignoring packet while offline\n", t); + } + } + + put_apacket(p); + } + + // this is necessary to avoid a race condition that occured when a transport closes + // while a client socket is still active. + close_all_sockets(t); + + D("to_remote: thread is exiting for transport %p, fd %d\n", t, t->fd); + kick_transport(t); + transport_unref(t); + return 0; +} + + +static int transport_registration_send = -1; +static int transport_registration_recv = -1; +static fdevent transport_registration_fde; + + +#if ADB_HOST +static int list_transports_msg(char* buffer, size_t bufferlen) +{ + char head[5]; + int len; + + len = list_transports(buffer+4, bufferlen-4); + snprintf(head, sizeof(head), "%04x", len); + memcpy(buffer, head, 4); + len += 4; + return len; +} + +/* this adds support required by the 'track-devices' service. + * this is used to send the content of "list_transport" to any + * number of client connections that want it through a single + * live TCP connection + */ +typedef struct device_tracker device_tracker; +struct device_tracker { + asocket socket; + int update_needed; + device_tracker* next; +}; + +/* linked list of all device trackers */ +static device_tracker* device_tracker_list; + +static void +device_tracker_remove( device_tracker* tracker ) +{ + device_tracker** pnode = &device_tracker_list; + device_tracker* node = *pnode; + + adb_mutex_lock( &transport_lock ); + while (node) { + if (node == tracker) { + *pnode = node->next; + break; + } + pnode = &node->next; + node = *pnode; + } + adb_mutex_unlock( &transport_lock ); +} + +static void +device_tracker_close( asocket* socket ) +{ + device_tracker* tracker = (device_tracker*) socket; + asocket* peer = socket->peer; + + D( "device tracker %p removed\n", tracker); + if (peer) { + peer->peer = NULL; + peer->close(peer); + } + device_tracker_remove(tracker); + free(tracker); +} + +static int +device_tracker_enqueue( asocket* socket, apacket* p ) +{ + /* you can't read from a device tracker, close immediately */ + put_apacket(p); + device_tracker_close(socket); + return -1; +} + +static int +device_tracker_send( device_tracker* tracker, + const char* buffer, + int len ) +{ + apacket* p = get_apacket(); + asocket* peer = tracker->socket.peer; + + memcpy(p->data, buffer, len); + p->len = len; + return peer->enqueue( peer, p ); +} + + +static void +device_tracker_ready( asocket* socket ) +{ + device_tracker* tracker = (device_tracker*) socket; + + /* we want to send the device list when the tracker connects + * for the first time, even if no update occured */ + if (tracker->update_needed > 0) { + char buffer[1024]; + int len; + + tracker->update_needed = 0; + + len = list_transports_msg(buffer, sizeof(buffer)); + device_tracker_send(tracker, buffer, len); + } +} + + +asocket* +create_device_tracker(void) +{ + device_tracker* tracker = calloc(1,sizeof(*tracker)); + + if(tracker == 0) fatal("cannot allocate device tracker"); + + D( "device tracker %p created\n", tracker); + + tracker->socket.enqueue = device_tracker_enqueue; + tracker->socket.ready = device_tracker_ready; + tracker->socket.close = device_tracker_close; + tracker->update_needed = 1; + + tracker->next = device_tracker_list; + device_tracker_list = tracker; + + return &tracker->socket; +} + + +/* call this function each time the transport list has changed */ +void update_transports(void) +{ + char buffer[1024]; + int len; + device_tracker* tracker; + + len = list_transports_msg(buffer, sizeof(buffer)); + + tracker = device_tracker_list; + while (tracker != NULL) { + device_tracker* next = tracker->next; + /* note: this may destroy the tracker if the connection is closed */ + device_tracker_send(tracker, buffer, len); + tracker = next; + } +} +#else +void update_transports(void) +{ + // nothing to do on the device side +} +#endif // ADB_HOST + +typedef struct tmsg tmsg; +struct tmsg +{ + atransport *transport; + int action; +}; + +static int +transport_read_action(int fd, struct tmsg* m) +{ + char *p = (char*)m; + int len = sizeof(*m); + int r; + + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if((r < 0) && (errno == EINTR)) continue; + D("transport_read_action: on fd %d, error %d: %s\n", + fd, errno, strerror(errno)); + return -1; + } + } + return 0; +} + +static int +transport_write_action(int fd, struct tmsg* m) +{ + char *p = (char*)m; + int len = sizeof(*m); + int r; + + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + if((r < 0) && (errno == EINTR)) continue; + D("transport_write_action: on fd %d, error %d: %s\n", + fd, errno, strerror(errno)); + return -1; + } + } + return 0; +} + +static void transport_registration_func(int _fd, unsigned ev, void *data) +{ + tmsg m; + adb_thread_t output_thread_ptr; + adb_thread_t input_thread_ptr; + int s[2]; + atransport *t; + + if(!(ev & FDE_READ)) { + return; + } + + if(transport_read_action(_fd, &m)) { + fatal_errno("cannot read transport registration socket"); + } + + t = m.transport; + + if(m.action == 0){ + D("transport: %p removing and free'ing %d\n", t, t->transport_socket); + + /* IMPORTANT: the remove closes one half of the + ** socket pair. The close closes the other half. + */ + fdevent_remove(&(t->transport_fde)); + adb_close(t->fd); + + adb_mutex_lock(&transport_lock); + t->next->prev = t->prev; + t->prev->next = t->next; + adb_mutex_unlock(&transport_lock); + + run_transport_disconnects(t); + + if (t->product) + free(t->product); + if (t->serial) + free(t->serial); + + memset(t,0xee,sizeof(atransport)); + free(t); + + update_transports(); + return; + } + + /* don't create transport threads for inaccessible devices */ + if (t->connection_state != CS_NOPERM) { + /* initial references are the two threads */ + t->ref_count = 2; + + if(adb_socketpair(s)) { + fatal_errno("cannot open transport socketpair"); + } + + D("transport: %p (%d,%d) starting\n", t, s[0], s[1]); + + t->transport_socket = s[0]; + t->fd = s[1]; + + D("transport: %p install %d\n", t, t->transport_socket ); + fdevent_install(&(t->transport_fde), + t->transport_socket, + transport_socket_events, + t); + + fdevent_set(&(t->transport_fde), FDE_READ); + + if(adb_thread_create(&input_thread_ptr, input_thread, t)){ + fatal_errno("cannot create input thread"); + } + + if(adb_thread_create(&output_thread_ptr, output_thread, t)){ + fatal_errno("cannot create output thread"); + } + } + + /* put us on the master device list */ + adb_mutex_lock(&transport_lock); + t->next = &transport_list; + t->prev = transport_list.prev; + t->next->prev = t; + t->prev->next = t; + adb_mutex_unlock(&transport_lock); + + t->disconnects.next = t->disconnects.prev = &t->disconnects; + + update_transports(); +} + +void init_transport_registration(void) +{ + int s[2]; + + if(adb_socketpair(s)){ + fatal_errno("cannot open transport registration socketpair"); + } + + transport_registration_send = s[0]; + transport_registration_recv = s[1]; + + fdevent_install(&transport_registration_fde, + transport_registration_recv, + transport_registration_func, + 0); + + fdevent_set(&transport_registration_fde, FDE_READ); +} + +/* the fdevent select pump is single threaded */ +static void register_transport(atransport *transport) +{ + tmsg m; + m.transport = transport; + m.action = 1; + D("transport: %p registered\n", transport); + if(transport_write_action(transport_registration_send, &m)) { + fatal_errno("cannot write transport registration socket\n"); + } +} + +static void remove_transport(atransport *transport) +{ + tmsg m; + m.transport = transport; + m.action = 0; + D("transport: %p removed\n", transport); + if(transport_write_action(transport_registration_send, &m)) { + fatal_errno("cannot write transport registration socket\n"); + } +} + + +static void transport_unref_locked(atransport *t) +{ + t->ref_count--; + D("transport: %p R- (ref=%d)\n", t, t->ref_count); + if (t->ref_count == 0) { + D("transport: %p kicking and closing\n", t); + if (!t->kicked) { + t->kicked = 1; + t->kick(t); + } + t->close(t); + remove_transport(t); + } +} + +static void transport_unref(atransport *t) +{ + if (t) { + adb_mutex_lock(&transport_lock); + transport_unref_locked(t); + adb_mutex_unlock(&transport_lock); + } +} + +void add_transport_disconnect(atransport* t, adisconnect* dis) +{ + adb_mutex_lock(&transport_lock); + dis->next = &t->disconnects; + dis->prev = dis->next->prev; + dis->prev->next = dis; + dis->next->prev = dis; + adb_mutex_unlock(&transport_lock); +} + +void remove_transport_disconnect(atransport* t, adisconnect* dis) +{ + dis->prev->next = dis->next; + dis->next->prev = dis->prev; + dis->next = dis->prev = dis; +} + + +atransport *acquire_one_transport(int state, transport_type ttype, const char* serial, char** error_out) +{ + atransport *t; + atransport *result = NULL; + int ambiguous = 0; + +retry: + if (error_out) + *error_out = "device not found"; + + adb_mutex_lock(&transport_lock); + for (t = transport_list.next; t != &transport_list; t = t->next) { + if (t->connection_state == CS_NOPERM) { + if (error_out) + *error_out = "insufficient permissions for device"; + continue; + } + + /* check for matching serial number */ + if (serial) { + if (t->serial && !strcmp(serial, t->serial)) { + result = t; + break; + } + } else { + if (ttype == kTransportUsb && t->type == kTransportUsb) { + if (result) { + if (error_out) + *error_out = "more than one device"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } else if (ttype == kTransportLocal && t->type == kTransportLocal) { + if (result) { + if (error_out) + *error_out = "more than one emulator"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } else if (ttype == kTransportAny) { + if (result) { + if (error_out) + *error_out = "more than one device and emulator"; + ambiguous = 1; + result = NULL; + break; + } + result = t; + } + } + } + adb_mutex_unlock(&transport_lock); + + if (result) { + /* offline devices are ignored -- they are either being born or dying */ + if (result && result->connection_state == CS_OFFLINE) { + if (error_out) + *error_out = "device offline"; + result = NULL; + } + /* check for required connection state */ + if (result && state != CS_ANY && result->connection_state != state) { + if (error_out) + *error_out = "invalid device state"; + result = NULL; + } + } + + if (result) { + /* found one that we can take */ + if (error_out) + *error_out = NULL; + } else if (state != CS_ANY && (serial || !ambiguous)) { + adb_sleep_ms(1000); + goto retry; + } + + return result; +} + +#if ADB_HOST +static const char *statename(atransport *t) +{ + switch(t->connection_state){ + case CS_OFFLINE: return "offline"; + case CS_BOOTLOADER: return "bootloader"; + case CS_DEVICE: return "device"; + case CS_HOST: return "host"; + case CS_RECOVERY: return "recovery"; + case CS_NOPERM: return "no permissions"; + default: return "unknown"; + } +} + +int list_transports(char *buf, size_t bufsize) +{ + char* p = buf; + char* end = buf + bufsize; + int len; + atransport *t; + + /* XXX OVERRUN PROBLEMS XXX */ + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + const char* serial = t->serial; + if (!serial || !serial[0]) + serial = "????????????"; + len = snprintf(p, end - p, "%s\t%s\n", serial, statename(t)); + + if (p + len >= end) { + /* discard last line if buffer is too short */ + break; + } + p += len; + } + p[0] = 0; + adb_mutex_unlock(&transport_lock); + return p - buf; +} + + +/* hack for osx */ +void close_usb_devices() +{ + atransport *t; + + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + if ( !t->kicked ) { + t->kicked = 1; + t->kick(t); + } + } + adb_mutex_unlock(&transport_lock); +} +#endif // ADB_HOST + +void register_socket_transport(int s, const char *serial, int port, int local) +{ + atransport *t = calloc(1, sizeof(atransport)); + D("transport: %p init'ing for socket %d, on port %d\n", t, s, port); + if ( init_socket_transport(t, s, port, local) < 0 ) { + adb_close(s); + free(t); + return; + } + if(serial) { + t->serial = strdup(serial); + } + register_transport(t); +} + +#if ADB_HOST +atransport *find_transport(const char *serial) +{ + atransport *t; + + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + if (t->serial && !strcmp(serial, t->serial)) { + break; + } + } + adb_mutex_unlock(&transport_lock); + + if (t != &transport_list) + return t; + else + return 0; +} + +void unregister_transport(atransport *t) +{ + adb_mutex_lock(&transport_lock); + t->next->prev = t->prev; + t->prev->next = t->next; + adb_mutex_unlock(&transport_lock); + + kick_transport(t); + transport_unref(t); +} + +// unregisters all non-emulator TCP transports +void unregister_all_tcp_transports() +{ + atransport *t, *next; + adb_mutex_lock(&transport_lock); + for (t = transport_list.next; t != &transport_list; t = next) { + next = t->next; + if (t->type == kTransportLocal && t->adb_port == 0) { + t->next->prev = t->prev; + t->prev->next = next; + // we cannot call kick_transport when holding transport_lock + if (!t->kicked) + { + t->kicked = 1; + t->kick(t); + } + transport_unref_locked(t); + } + } + + adb_mutex_unlock(&transport_lock); +} + +#endif + +void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable) +{ + atransport *t = calloc(1, sizeof(atransport)); + D("transport: %p init'ing for usb_handle %p (sn='%s')\n", t, usb, + serial ? serial : ""); + init_usb_transport(t, usb, (writeable ? CS_OFFLINE : CS_NOPERM)); + if(serial) { + t->serial = strdup(serial); + } + register_transport(t); +} + +/* this should only be used for transports with connection_state == CS_NOPERM */ +void unregister_usb_transport(usb_handle *usb) +{ + atransport *t; + adb_mutex_lock(&transport_lock); + for(t = transport_list.next; t != &transport_list; t = t->next) { + if (t->usb == usb && t->connection_state == CS_NOPERM) { + t->next->prev = t->prev; + t->prev->next = t->next; + break; + } + } + adb_mutex_unlock(&transport_lock); +} + +#undef TRACE_TAG +#define TRACE_TAG TRACE_RWX + +int readx(int fd, void *ptr, size_t len) +{ + char *p = ptr; + int r; +#if ADB_TRACE + int len0 = len; +#endif + D("readx: %d %p %d\n", fd, ptr, (int)len); + while(len > 0) { + r = adb_read(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("readx: %d %d %s\n", fd, r, strerror(errno)); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + +#if ADB_TRACE + D("readx: %d ok: ", fd); + dump_hex( ptr, len0 ); +#endif + return 0; +} + +int writex(int fd, const void *ptr, size_t len) +{ + char *p = (char*) ptr; + int r; + +#if ADB_TRACE + D("writex: %d %p %d: ", fd, ptr, (int)len); + dump_hex( ptr, len ); +#endif + while(len > 0) { + r = adb_write(fd, p, len); + if(r > 0) { + len -= r; + p += r; + } else { + D("writex: %d %d %s\n", fd, r, strerror(errno)); + if((r < 0) && (errno == EINTR)) continue; + return -1; + } + } + + D("writex: %d ok\n", fd); + return 0; +} + +int check_header(apacket *p) +{ + if(p->msg.magic != (p->msg.command ^ 0xffffffff)) { + D("check_header(): invalid magic\n"); + return -1; + } + + if(p->msg.data_length > MAX_PAYLOAD) { + D("check_header(): %d > MAX_PAYLOAD\n", p->msg.data_length); + return -1; + } + + return 0; +} + +int check_data(apacket *p) +{ + unsigned count, sum; + unsigned char *x; + + count = p->msg.data_length; + x = p->data; + sum = 0; + while(count-- > 0) { + sum += *x++; + } + + if(sum != p->msg.data_check) { + return -1; + } else { + return 0; + } +} + diff --git a/adb/transport_local.c b/adb/transport_local.c new file mode 100644 index 0000000..8dfc98d --- /dev/null +++ b/adb/transport_local.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "sysdeps.h" +#include <sys/types.h> + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +#ifdef __ppc__ +#define H4(x) (((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24) +static inline void fix_endians(apacket *p) +{ + p->msg.command = H4(p->msg.command); + p->msg.arg0 = H4(p->msg.arg0); + p->msg.arg1 = H4(p->msg.arg1); + p->msg.data_length = H4(p->msg.data_length); + p->msg.data_check = H4(p->msg.data_check); + p->msg.magic = H4(p->msg.magic); +} +#else +#define fix_endians(p) do {} while (0) +#endif + +#if ADB_HOST +/* we keep a list of opened transports. The atransport struct knows to which + * local transport it is connected. The list is used to detect when we're + * trying to connect twice to a given local transport. + */ +#define ADB_LOCAL_TRANSPORT_MAX 16 + +ADB_MUTEX_DEFINE( local_transports_lock ); + +static atransport* local_transports[ ADB_LOCAL_TRANSPORT_MAX ]; +#endif /* ADB_HOST */ + +static int remote_read(apacket *p, atransport *t) +{ + if(readx(t->sfd, &p->msg, sizeof(amessage))){ + D("remote local: read terminated (message)\n"); + return -1; + } + + fix_endians(p); + +#if 0 && defined __ppc__ + D("read remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n", + p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic); +#endif + if(check_header(p)) { + D("bad header: terminated (data)\n"); + return -1; + } + + if(readx(t->sfd, p->data, p->msg.data_length)){ + D("remote local: terminated (data)\n"); + return -1; + } + + if(check_data(p)) { + D("bad data: terminated (data)\n"); + return -1; + } + + return 0; +} + +static int remote_write(apacket *p, atransport *t) +{ + int length = p->msg.data_length; + + fix_endians(p); + +#if 0 && defined __ppc__ + D("write remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n", + p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic); +#endif + if(writex(t->sfd, &p->msg, sizeof(amessage) + length)) { + D("remote local: write terminated\n"); + return -1; + } + + return 0; +} + + +int local_connect(int port) { + return local_connect_arbitrary_ports(port-1, port); +} + +int local_connect_arbitrary_ports(int console_port, int adb_port) +{ + char buf[64]; + int fd = -1; + +#if ADB_HOST + const char *host = getenv("ADBHOST"); + if (host) { + fd = socket_network_client(host, adb_port, SOCK_STREAM); + } +#endif + if (fd < 0) { + fd = socket_loopback_client(adb_port, SOCK_STREAM); + } + + if (fd >= 0) { + D("client: connected on remote on fd %d\n", fd); + close_on_exec(fd); + disable_tcp_nagle(fd); + snprintf(buf, sizeof buf, "%s%d", LOCAL_CLIENT_PREFIX, console_port); + register_socket_transport(fd, buf, adb_port, 1); + return 0; + } + return -1; +} + + +static void *client_socket_thread(void *x) +{ +#if ADB_HOST + int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT; + int count = ADB_LOCAL_TRANSPORT_MAX; + + D("transport: client_socket_thread() starting\n"); + + /* try to connect to any number of running emulator instances */ + /* this is only done when ADB starts up. later, each new emulator */ + /* will send a message to ADB to indicate that is is starting up */ + for ( ; count > 0; count--, port += 2 ) { + (void) local_connect(port); + } +#endif + return 0; +} + +static void *server_socket_thread(void * arg) +{ + int serverfd, fd; + struct sockaddr addr; + socklen_t alen; + int port = (int)arg; + + D("transport: server_socket_thread() starting\n"); + serverfd = -1; + for(;;) { + if(serverfd == -1) { + serverfd = socket_inaddr_any_server(port, SOCK_STREAM); + if(serverfd < 0) { + D("server: cannot bind socket yet\n"); + adb_sleep_ms(1000); + continue; + } + close_on_exec(serverfd); + } + + alen = sizeof(addr); + D("server: trying to get new connection from %d\n", port); + fd = adb_socket_accept(serverfd, &addr, &alen); + if(fd >= 0) { + D("server: new connection on fd %d\n", fd); + close_on_exec(fd); + disable_tcp_nagle(fd); + register_socket_transport(fd, "host", port, 1); + } + } + D("transport: server_socket_thread() exiting\n"); + return 0; +} + +void local_init(int port) +{ + adb_thread_t thr; + void* (*func)(void *); + + if(HOST) { + func = client_socket_thread; + } else { + func = server_socket_thread; + } + + D("transport: local %s init\n", HOST ? "client" : "server"); + + if(adb_thread_create(&thr, func, (void *)port)) { + fatal_errno("cannot create local socket %s thread", + HOST ? "client" : "server"); + } +} + +static void remote_kick(atransport *t) +{ + int fd = t->sfd; + t->sfd = -1; + adb_shutdown(fd); + adb_close(fd); + +#if ADB_HOST + if(HOST) { + int nn; + adb_mutex_lock( &local_transports_lock ); + for (nn = 0; nn < ADB_LOCAL_TRANSPORT_MAX; nn++) { + if (local_transports[nn] == t) { + local_transports[nn] = NULL; + break; + } + } + adb_mutex_unlock( &local_transports_lock ); + } +#endif +} + +static void remote_close(atransport *t) +{ + adb_close(t->fd); +} + + +#if ADB_HOST +/* Only call this function if you already hold local_transports_lock. */ +atransport* find_emulator_transport_by_adb_port_locked(int adb_port) +{ + int i; + for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) { + if (local_transports[i] && local_transports[i]->adb_port == adb_port) { + return local_transports[i]; + } + } + return NULL; +} + +atransport* find_emulator_transport_by_adb_port(int adb_port) +{ + adb_mutex_lock( &local_transports_lock ); + atransport* result = find_emulator_transport_by_adb_port_locked(adb_port); + adb_mutex_unlock( &local_transports_lock ); + return result; +} + +/* Only call this function if you already hold local_transports_lock. */ +int get_available_local_transport_index_locked() +{ + int i; + for (i = 0; i < ADB_LOCAL_TRANSPORT_MAX; i++) { + if (local_transports[i] == NULL) { + return i; + } + } + return -1; +} + +int get_available_local_transport_index() +{ + adb_mutex_lock( &local_transports_lock ); + int result = get_available_local_transport_index_locked(); + adb_mutex_unlock( &local_transports_lock ); + return result; +} +#endif + +int init_socket_transport(atransport *t, int s, int adb_port, int local) +{ + int fail = 0; + + t->kick = remote_kick; + t->close = remote_close; + t->read_from_remote = remote_read; + t->write_to_remote = remote_write; + t->sfd = s; + t->sync_token = 1; + t->connection_state = CS_OFFLINE; + t->type = kTransportLocal; + t->adb_port = 0; + +#if ADB_HOST + if (HOST && local) { + adb_mutex_lock( &local_transports_lock ); + { + t->adb_port = adb_port; + atransport* existing_transport = + find_emulator_transport_by_adb_port_locked(adb_port); + int index = get_available_local_transport_index_locked(); + if (existing_transport != NULL) { + D("local transport for port %d already registered (%p)?\n", + adb_port, existing_transport); + fail = -1; + } else if (index < 0) { + // Too many emulators. + D("cannot register more emulators. Maximum is %d\n", + ADB_LOCAL_TRANSPORT_MAX); + fail = -1; + } else { + local_transports[index] = t; + } + } + adb_mutex_unlock( &local_transports_lock ); + } +#endif + return fail; +} diff --git a/adb/transport_usb.c b/adb/transport_usb.c new file mode 100644 index 0000000..2584163 --- /dev/null +++ b/adb/transport_usb.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sysdeps.h> + +#define TRACE_TAG TRACE_TRANSPORT +#include "adb.h" + +#if ADB_HOST +#include "usb_vendors.h" +#endif + +/* XXX better define? */ +#ifdef __ppc__ +#define H4(x) (((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24) +static inline void fix_endians(apacket *p) +{ + p->msg.command = H4(p->msg.command); + p->msg.arg0 = H4(p->msg.arg0); + p->msg.arg1 = H4(p->msg.arg1); + p->msg.data_length = H4(p->msg.data_length); + p->msg.data_check = H4(p->msg.data_check); + p->msg.magic = H4(p->msg.magic); +} +unsigned host_to_le32(unsigned n) +{ + return H4(n); +} +#else +#define fix_endians(p) do {} while (0) +unsigned host_to_le32(unsigned n) +{ + return n; +} +#endif + +static int remote_read(apacket *p, atransport *t) +{ + if(usb_read(t->usb, &p->msg, sizeof(amessage))){ + D("remote usb: read terminated (message)\n"); + return -1; + } + + fix_endians(p); + + if(check_header(p)) { + D("remote usb: check_header failed\n"); + return -1; + } + + if(p->msg.data_length) { + if(usb_read(t->usb, p->data, p->msg.data_length)){ + D("remote usb: terminated (data)\n"); + return -1; + } + } + + if(check_data(p)) { + D("remote usb: check_data failed\n"); + return -1; + } + + return 0; +} + +static int remote_write(apacket *p, atransport *t) +{ + unsigned size = p->msg.data_length; + + fix_endians(p); + + if(usb_write(t->usb, &p->msg, sizeof(amessage))) { + D("remote usb: 1 - write terminated\n"); + return -1; + } + if(p->msg.data_length == 0) return 0; + if(usb_write(t->usb, &p->data, size)) { + D("remote usb: 2 - write terminated\n"); + return -1; + } + + return 0; +} + +static void remote_close(atransport *t) +{ + usb_close(t->usb); + t->usb = 0; +} + +static void remote_kick(atransport *t) +{ + usb_kick(t->usb); +} + +void init_usb_transport(atransport *t, usb_handle *h, int state) +{ + D("transport: usb\n"); + t->close = remote_close; + t->kick = remote_kick; + t->read_from_remote = remote_read; + t->write_to_remote = remote_write; + t->sync_token = 1; + t->connection_state = state; + t->type = kTransportUsb; + t->usb = h; + +#if ADB_HOST + HOST = 1; +#else + HOST = 0; +#endif +} + +#if ADB_HOST +int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol) +{ + unsigned i; + for (i = 0; i < vendorIdCount; i++) { + if (vid == vendorIds[i]) { + if (usb_class == ADB_CLASS && usb_subclass == ADB_SUBCLASS && + usb_protocol == ADB_PROTOCOL) { + return 1; + } + + return 0; + } + } + + return 0; +} +#endif diff --git a/adb/usb_libusb.c b/adb/usb_libusb.c new file mode 100644 index 0000000..8c75266 --- /dev/null +++ b/adb/usb_libusb.c @@ -0,0 +1,657 @@ +/* + * Copyright (C) 2009 bsdroid project + * Alexey Tarasov <tarasov@dodologics.com> + * + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/endian.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/uio.h> + +#include <err.h> +#include <errno.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <libusb.h> +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + +static adb_mutex_t usb_lock = ADB_MUTEX_INITIALIZER; +static libusb_context *ctx = NULL; + +struct usb_handle +{ + usb_handle *prev; + usb_handle *next; + + libusb_device *dev; + libusb_device_handle *devh; + int interface; + uint8_t dev_bus; + uint8_t dev_addr; + + int zero_mask; + unsigned char end_point_address[2]; + char serial[128]; + + adb_cond_t notify; + adb_mutex_t lock; +}; + +static struct usb_handle handle_list = { + .prev = &handle_list, + .next = &handle_list, +}; + +void +usb_cleanup() +{ + libusb_exit(ctx); +} + +void +report_bulk_libusb_error(int r) +{ + switch (r) { + case LIBUSB_ERROR_TIMEOUT: + D("Transfer timeout\n"); + break; + + case LIBUSB_ERROR_PIPE: + D("Control request is not supported\n"); + break; + + case LIBUSB_ERROR_OVERFLOW: + D("Device offered more data\n"); + break; + + case LIBUSB_ERROR_NO_DEVICE : + D("Device was disconnected\n"); + break; + + default: + D("Error %d during transfer\n", r); + break; + }; +} + +static int +usb_bulk_write(usb_handle *uh, const void *data, int len) +{ + int r = 0; + int transferred = 0; + + r = libusb_bulk_transfer(uh->devh, uh->end_point_address[1], (void *)data, len, + &transferred, 0); + + if (r != 0) { + D("usb_bulk_write(): "); + report_bulk_libusb_error(r); + return r; + } + + return (transferred); +} + +static int +usb_bulk_read(usb_handle *uh, void *data, int len) +{ + int r = 0; + int transferred = 0; + + r = libusb_bulk_transfer(uh->devh, uh->end_point_address[0], data, len, + &transferred, 0); + + if (r != 0) { + D("usb_bulk_read(): "); + report_bulk_libusb_error(r); + return r; + } + + return (transferred); +} + +int +usb_write(struct usb_handle *uh, const void *_data, int len) +{ + unsigned char *data = (unsigned char*) _data; + int n; + int need_zero = 0; + + if (uh->zero_mask == 1) { + if (!(len & uh->zero_mask)) { + need_zero = 1; + } + } + + D("usb_write(): %p:%d -> transport %p\n", _data, len, uh); + + while (len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + n = usb_bulk_write(uh, data, xfer); + + if (n != xfer) { + D("usb_write(): failed for transport %p (%d bytes left)\n", uh, len); + return -1; + } + + len -= xfer; + data += xfer; + } + + if (need_zero){ + n = usb_bulk_write(uh, _data, 0); + + if (n < 0) { + D("usb_write(): failed to finish operation for transport %p\n", uh); + } + return n; + } + + return 0; +} + +int +usb_read(struct usb_handle *uh, void *_data, int len) +{ + unsigned char *data = (unsigned char*) _data; + int n; + + D("usb_read(): %p:%d <- transport %p\n", _data, len, uh); + + while (len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + n = usb_bulk_read(uh, data, xfer); + + if (n != xfer) { + if (n > 0) { + data += n; + len -= n; + continue; + } + + D("usb_read(): failed for transport %p (%d bytes left)\n", uh, len); + return -1; + } + + len -= xfer; + data += xfer; + } + + return 0; + } + +int +usb_close(struct usb_handle *h) +{ + D("usb_close(): closing transport %p\n", h); + adb_mutex_lock(&usb_lock); + + h->next->prev = h->prev; + h->prev->next = h->next; + h->prev = NULL; + h->next = NULL; + + libusb_release_interface(h->devh, h->interface); + libusb_close(h->devh); + libusb_unref_device(h->dev); + + adb_mutex_unlock(&usb_lock); + + free(h); + + return (0); +} + +void usb_kick(struct usb_handle *h) +{ + D("usb_cick(): kicking transport %p\n", h); + + adb_mutex_lock(&h->lock); + unregister_usb_transport(h); + adb_mutex_unlock(&h->lock); + + h->next->prev = h->prev; + h->prev->next = h->next; + h->prev = NULL; + h->next = NULL; + + libusb_release_interface(h->devh, h->interface); + libusb_close(h->devh); + libusb_unref_device(h->dev); + free(h); +} + +int +check_usb_interface(libusb_interface *interface, + libusb_device_descriptor *desc, + struct usb_handle *uh) +{ + int e; + + if (interface->num_altsetting == 0) { + D("check_usb_interface(): No interface settings\n"); + return -1; + } + + libusb_interface_descriptor *idesc = &interface->altsetting[0]; + + if (idesc->bNumEndpoints != 2) { + D("check_usb_interface(): Interface have not 2 endpoints, ignoring\n"); + return -1; + } + + for (e = 0; e < idesc->bNumEndpoints; e++) { + libusb_endpoint_descriptor *edesc = &idesc->endpoint[e]; + + if (edesc->bmAttributes != LIBUSB_TRANSFER_TYPE_BULK) { + D("check_usb_interface(): Endpoint (%u) is not bulk (%u), ignoring\n", + edesc->bmAttributes, LIBUSB_TRANSFER_TYPE_BULK); + return -1; + } + + if (edesc->bEndpointAddress & LIBUSB_ENDPOINT_IN) + uh->end_point_address[0] = edesc->bEndpointAddress; + else + uh->end_point_address[1] = edesc->bEndpointAddress; + + /* aproto 01 needs 0 termination */ + if (idesc->bInterfaceProtocol == 0x01) { + uh->zero_mask = edesc->wMaxPacketSize - 1; + D("check_usb_interface(): Forced Android interface protocol v.1\n"); + } + } + + D("check_usb_interface(): Device: %04x:%04x " + "iclass: %x, isclass: %x, iproto: %x ep: %x/%x-> ", + desc->idVendor, desc->idProduct, idesc->bInterfaceClass, + idesc->bInterfaceSubClass, idesc->bInterfaceProtocol, + uh->end_point_address[0], uh->end_point_address[1]); + + if (!is_adb_interface(desc->idVendor, desc->idProduct, + idesc->bInterfaceClass, idesc->bInterfaceSubClass, + idesc->bInterfaceProtocol)) + { + D("not matches\n"); + return -1; + } + + D("matches\n"); + return 1; +} + +int +check_usb_interfaces(libusb_config_descriptor *config, + libusb_device_descriptor *desc, struct usb_handle *uh) +{ + int i; + + for (i = 0; i < config->bNumInterfaces; ++i) { + if (check_usb_interface(&config->interface[i], desc, uh) != -1) { + /* found some interface and saved information about it */ + D("check_usb_interfaces(): Interface %d of %04x:%04x " + "matches Android device\n", i, desc->idVendor, + desc->idProduct); + + return i; + } + } + + return -1; +} + +int +register_device(struct usb_handle *uh, const char *serial) +{ + D("register_device(): Registering %p [%s] as USB transport\n", + uh, serial); + + struct usb_handle *usb= NULL; + + usb = calloc(1, sizeof(struct usb_handle)); + memcpy(usb, uh, sizeof(struct usb_handle)); + strcpy(usb->serial, uh->serial); + + adb_cond_init(&usb->notify, 0); + adb_mutex_init(&usb->lock, 0); + + adb_mutex_lock(&usb_lock); + + usb->next = &handle_list; + usb->prev = handle_list.prev; + usb->prev->next = usb; + usb->next->prev = usb; + + adb_mutex_unlock(&usb_lock); + + register_usb_transport(usb, serial, 1); + + return (1); +} + +int +already_registered(usb_handle *uh) +{ + struct usb_handle *usb= NULL; + int exists = 0; + + adb_mutex_lock(&usb_lock); + + for (usb = handle_list.next; usb != &handle_list; usb = usb->next) { + if ((usb->dev_bus == uh->dev_bus) && + (usb->dev_addr == uh->dev_addr)) + { + exists = 1; + break; + } + } + + adb_mutex_unlock(&usb_lock); + + return exists; +} + +void +check_device(libusb_device *dev) +{ + struct usb_handle uh; + int i = 0; + int found = -1; + char serial[256] = {0}; + + libusb_device_descriptor desc; + libusb_config_descriptor *config = NULL; + + int r = libusb_get_device_descriptor(dev, &desc); + + if (r != LIBUSB_SUCCESS) { + D("check_device(): Failed to get device descriptor\n"); + return; + } + + if ((desc.idVendor == 0) && (desc.idProduct == 0)) + return; + + D("check_device(): Probing usb device %04x:%04x\n", + desc.idVendor, desc.idProduct); + + if (!is_adb_interface (desc.idVendor, desc.idProduct, + ADB_CLASS, ADB_SUBCLASS, ADB_PROTOCOL)) + { + D("check_device(): Ignored due unknown vendor id\n"); + return; + } + + uh.dev_bus = libusb_get_bus_number(dev); + uh.dev_addr = libusb_get_device_address(dev); + + if (already_registered(&uh)) { + D("check_device(): Device (bus: %d, address: %d) " + "is already registered\n", uh.dev_bus, uh.dev_addr); + return; + } + + D("check_device(): Device bus: %d, address: %d\n", + uh.dev_bus, uh.dev_addr); + + r = libusb_get_active_config_descriptor(dev, &config); + + if (r != 0) { + if (r == LIBUSB_ERROR_NOT_FOUND) { + D("check_device(): Device %4x:%4x is unconfigured\n", + desc.idVendor, desc.idProduct); + return; + } + + D("check_device(): Failed to get configuration for %4x:%4x\n", + desc.idVendor, desc.idProduct); + return; + } + + if (config == NULL) { + D("check_device(): Sanity check failed after " + "getting active config\n"); + return; + } + + if (config->interface != NULL) { + found = check_usb_interfaces(config, &desc, &uh); + } + + /* not needed anymore */ + libusb_free_config_descriptor(config); + + r = libusb_open(dev, &uh.devh); + uh.dev = dev; + + if (r != 0) { + switch (r) { + case LIBUSB_ERROR_NO_MEM: + D("check_device(): Memory allocation problem\n"); + break; + + case LIBUSB_ERROR_ACCESS: + D("check_device(): Permissions problem, " + "current user priveleges are messed up?\n"); + break; + + case LIBUSB_ERROR_NO_DEVICE: + D("check_device(): Device disconected, bad cable?\n"); + break; + + default: + D("check_device(): libusb triggered error %d\n", r); + } + // skip rest + found = -1; + } + + if (found >= 0) { + D("check_device(): Device matches Android interface\n"); + // read the device's serial number + memset(serial, 0, sizeof(serial)); + uh.interface = found; + + r = libusb_claim_interface(uh.devh, uh.interface); + + if (r < 0) { + D("check_device(): Failed to claim interface %d\n", + uh.interface); + + goto fail; + } + + if (desc.iSerialNumber) { + // reading serial + uint16_t buffer[128] = {0}; + uint16_t languages[128] = {0}; + int languageCount = 0; + + memset(languages, 0, sizeof(languages)); + r = libusb_control_transfer(uh.devh, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE, + LIBUSB_REQUEST_GET_DESCRIPTOR, LIBUSB_DT_STRING << 8, + 0, (uint8_t *)languages, sizeof(languages), 0); + + if (r <= 0) { + D("check_device(): Failed to get languages count\n"); + goto fail; + } + + languageCount = (r - 2) / 2; + + for (i = 1; i <= languageCount; ++i) { + memset(buffer, 0, sizeof(buffer)); + + r = libusb_control_transfer(uh.devh, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE, + LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_STRING << 8) | desc.iSerialNumber, + languages[i], (uint8_t *)buffer, sizeof(buffer), 0); + + if (r > 0) { /* converting serial */ + int j = 0; + r /= 2; + + for (j = 1; j < r; ++j) + serial[j - 1] = buffer[j]; + + serial[j - 1] = '\0'; + break; /* languagesCount cycle */ + } + } + + if (register_device(&uh, serial) == 0) { + D("check_device(): Failed to register device\n"); + goto fail_interface; + } + + libusb_ref_device(dev); + } + } + + return; + +fail_interface: + libusb_release_interface(uh.devh, uh.interface); + +fail: + libusb_close(uh.devh); + uh.devh = NULL; +} + +int +check_device_connected(struct usb_handle *uh) +{ + int r = libusb_kernel_driver_active(uh->devh, uh->interface); + + if (r == LIBUSB_ERROR_NO_DEVICE) + return 0; + + if (r < 0) + return -1; + + return 1; +} + +void +kick_disconnected() +{ + struct usb_handle *usb= NULL; + + adb_mutex_lock(&usb_lock); + + for (usb = handle_list.next; usb != &handle_list; usb = usb->next) { + + if (check_device_connected(usb) == 0) { + D("kick_disconnected(): Transport %p is not online anymore\n", + usb); + + usb_kick(usb); + } + } + + adb_mutex_unlock(&usb_lock); +} + +void +scan_usb_devices() +{ + D("scan_usb_devices(): started\n"); + + libusb_device **devs= NULL; + libusb_device *dev= NULL; + ssize_t cnt = libusb_get_device_list(ctx, &devs); + + if (cnt < 0) { + D("scan_usb_devices(): Failed to get device list (error: %d)\n", + cnt); + + return; + } + + int i = 0; + + while ((dev = devs[i++]) != NULL) { + check_device(dev); + } + + libusb_free_device_list(devs, 1); +} + +void * +device_poll_thread(void* unused) +{ + D("device_poll_thread(): Created USB scan thread\n"); + + for (;;) { + sleep(5); + kick_disconnected(); + scan_usb_devices(); + } + + /* never reaching this point */ + return (NULL); +} + +static void +sigalrm_handler(int signo) +{ + /* nothing */ +} + +void +usb_init() +{ + D("usb_init(): started\n"); + adb_thread_t tid; + struct sigaction actions; + + int r = libusb_init(&ctx); + + if (r != LIBUSB_SUCCESS) { + err(EX_IOERR, "Failed to init libusb\n"); + } + + memset(&actions, 0, sizeof(actions)); + + sigemptyset(&actions.sa_mask); + + actions.sa_flags = 0; + actions.sa_handler = sigalrm_handler; + + sigaction(SIGALRM, &actions, NULL); + + /* initial device scan */ + scan_usb_devices(); + + /* starting USB event polling thread */ + if (adb_thread_create(&tid, device_poll_thread, NULL)) { + err(EX_IOERR, "cannot create USB scan thread\n"); + } + + D("usb_init(): finished\n"); +} + diff --git a/adb/usb_linux.c b/adb/usb_linux.c new file mode 100644 index 0000000..bb86813 --- /dev/null +++ b/adb/usb_linux.c @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/time.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> + +#include <linux/usbdevice_fs.h> +#include <linux/version.h> +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20) +#include <linux/usb/ch9.h> +#else +#include <linux/usb_ch9.h> +#endif +#include <asm/byteorder.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + + +/* usb scan debugging is waaaay too verbose */ +#define DBGX(x...) + +static adb_mutex_t usb_lock = ADB_MUTEX_INITIALIZER; + +struct usb_handle +{ + usb_handle *prev; + usb_handle *next; + + char fname[64]; + int desc; + unsigned char ep_in; + unsigned char ep_out; + + unsigned zero_mask; + unsigned writeable; + + struct usbdevfs_urb urb_in; + struct usbdevfs_urb urb_out; + + int urb_in_busy; + int urb_out_busy; + int dead; + + adb_cond_t notify; + adb_mutex_t lock; + + // for garbage collecting disconnected devices + int mark; + + // ID of thread currently in REAPURB + pthread_t reaper_thread; +}; + +static usb_handle handle_list = { + .prev = &handle_list, + .next = &handle_list, +}; + +static int known_device(const char *dev_name) +{ + usb_handle *usb; + + adb_mutex_lock(&usb_lock); + for(usb = handle_list.next; usb != &handle_list; usb = usb->next){ + if(!strcmp(usb->fname, dev_name)) { + // set mark flag to indicate this device is still alive + usb->mark = 1; + adb_mutex_unlock(&usb_lock); + return 1; + } + } + adb_mutex_unlock(&usb_lock); + return 0; +} + +static void kick_disconnected_devices() +{ + usb_handle *usb; + + adb_mutex_lock(&usb_lock); + // kick any devices in the device list that were not found in the device scan + for(usb = handle_list.next; usb != &handle_list; usb = usb->next){ + if (usb->mark == 0) { + usb_kick(usb); + } else { + usb->mark = 0; + } + } + adb_mutex_unlock(&usb_lock); + +} + +static void register_device(const char *dev_name, unsigned char ep_in, unsigned char ep_out, + int ifc, int serial_index, unsigned zero_mask); + +static inline int badname(const char *name) +{ + while(*name) { + if(!isdigit(*name++)) return 1; + } + return 0; +} + +static void find_usb_device(const char *base, + void (*register_device_callback) + (const char *, unsigned char, unsigned char, int, int, unsigned)) +{ + char busname[32], devname[32]; + unsigned char local_ep_in, local_ep_out; + DIR *busdir , *devdir ; + struct dirent *de; + int fd ; + + busdir = opendir(base); + if(busdir == 0) return; + + while((de = readdir(busdir)) != 0) { + if(badname(de->d_name)) continue; + + snprintf(busname, sizeof busname, "%s/%s", base, de->d_name); + devdir = opendir(busname); + if(devdir == 0) continue; + +// DBGX("[ scanning %s ]\n", busname); + while((de = readdir(devdir))) { + unsigned char devdesc[256]; + unsigned char* bufptr = devdesc; + unsigned char* bufend; + struct usb_device_descriptor* device; + struct usb_config_descriptor* config; + struct usb_interface_descriptor* interface; + struct usb_endpoint_descriptor *ep1, *ep2; + unsigned zero_mask = 0; + unsigned vid, pid; + size_t desclength; + + if(badname(de->d_name)) continue; + snprintf(devname, sizeof devname, "%s/%s", busname, de->d_name); + + if(known_device(devname)) { + DBGX("skipping %s\n", devname); + continue; + } + +// DBGX("[ scanning %s ]\n", devname); + if((fd = unix_open(devname, O_RDONLY)) < 0) { + continue; + } + + desclength = adb_read(fd, devdesc, sizeof(devdesc)); + bufend = bufptr + desclength; + + // should have device and configuration descriptors, and atleast two endpoints + if (desclength < USB_DT_DEVICE_SIZE + USB_DT_CONFIG_SIZE) { + D("desclength %d is too small\n", desclength); + adb_close(fd); + continue; + } + + device = (struct usb_device_descriptor*)bufptr; + bufptr += USB_DT_DEVICE_SIZE; + + if((device->bLength != USB_DT_DEVICE_SIZE) || (device->bDescriptorType != USB_DT_DEVICE)) { + adb_close(fd); + continue; + } + + vid = __le16_to_cpu(device->idVendor); + pid = __le16_to_cpu(device->idProduct); + pid = devdesc[10] | (devdesc[11] << 8); + DBGX("[ %s is V:%04x P:%04x ]\n", devname, vid, pid); + + // should have config descriptor next + config = (struct usb_config_descriptor *)bufptr; + bufptr += USB_DT_CONFIG_SIZE; + if (config->bLength != USB_DT_CONFIG_SIZE || config->bDescriptorType != USB_DT_CONFIG) { + D("usb_config_descriptor not found\n"); + adb_close(fd); + continue; + } + + // loop through all the descriptors and look for the ADB interface + while (bufptr < bufend) { + unsigned char length = bufptr[0]; + unsigned char type = bufptr[1]; + + if (type == USB_DT_INTERFACE) { + interface = (struct usb_interface_descriptor *)bufptr; + bufptr += length; + + if (length != USB_DT_INTERFACE_SIZE) { + D("interface descriptor has wrong size\n"); + break; + } + + DBGX("bInterfaceClass: %d, bInterfaceSubClass: %d," + "bInterfaceProtocol: %d, bNumEndpoints: %d\n", + interface->bInterfaceClass, interface->bInterfaceSubClass, + interface->bInterfaceProtocol, interface->bNumEndpoints); + + if (interface->bNumEndpoints == 2 && + is_adb_interface(vid, pid, interface->bInterfaceClass, + interface->bInterfaceSubClass, interface->bInterfaceProtocol)) { + + DBGX("looking for bulk endpoints\n"); + // looks like ADB... + ep1 = (struct usb_endpoint_descriptor *)bufptr; + bufptr += USB_DT_ENDPOINT_SIZE; + ep2 = (struct usb_endpoint_descriptor *)bufptr; + bufptr += USB_DT_ENDPOINT_SIZE; + + if (bufptr > devdesc + desclength || + ep1->bLength != USB_DT_ENDPOINT_SIZE || + ep1->bDescriptorType != USB_DT_ENDPOINT || + ep2->bLength != USB_DT_ENDPOINT_SIZE || + ep2->bDescriptorType != USB_DT_ENDPOINT) { + D("endpoints not found\n"); + break; + } + + // both endpoints should be bulk + if (ep1->bmAttributes != USB_ENDPOINT_XFER_BULK || + ep2->bmAttributes != USB_ENDPOINT_XFER_BULK) { + D("bulk endpoints not found\n"); + continue; + } + /* aproto 01 needs 0 termination */ + if(interface->bInterfaceProtocol == 0x01) { + zero_mask = ep1->wMaxPacketSize - 1; + } + + // we have a match. now we just need to figure out which is in and which is out. + if (ep1->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + local_ep_in = ep1->bEndpointAddress; + local_ep_out = ep2->bEndpointAddress; + } else { + local_ep_in = ep2->bEndpointAddress; + local_ep_out = ep1->bEndpointAddress; + } + + register_device_callback(devname, local_ep_in, local_ep_out, + interface->bInterfaceNumber, device->iSerialNumber, zero_mask); + break; + } + } else { + bufptr += length; + } + } // end of while + + adb_close(fd); + } // end of devdir while + closedir(devdir); + } //end of busdir while + closedir(busdir); +} + +void usb_cleanup() +{ +} + +static int usb_bulk_write(usb_handle *h, const void *data, int len) +{ + struct usbdevfs_urb *urb = &h->urb_out; + int res; + struct timeval tv; + struct timespec ts; + + memset(urb, 0, sizeof(*urb)); + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->endpoint = h->ep_out; + urb->status = -1; + urb->buffer = (void*) data; + urb->buffer_length = len; + + D("++ write ++\n"); + + adb_mutex_lock(&h->lock); + if(h->dead) { + res = -1; + goto fail; + } + do { + res = ioctl(h->desc, USBDEVFS_SUBMITURB, urb); + } while((res < 0) && (errno == EINTR)); + + if(res < 0) { + goto fail; + } + + res = -1; + h->urb_out_busy = 1; + for(;;) { + /* time out after five seconds */ + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + 5; + ts.tv_nsec = tv.tv_usec * 1000L; + res = pthread_cond_timedwait(&h->notify, &h->lock, &ts); + if(res < 0 || h->dead) { + break; + } + if(h->urb_out_busy == 0) { + if(urb->status == 0) { + res = urb->actual_length; + } + break; + } + } +fail: + adb_mutex_unlock(&h->lock); + D("-- write --\n"); + return res; +} + +static int usb_bulk_read(usb_handle *h, void *data, int len) +{ + struct usbdevfs_urb *urb = &h->urb_in; + struct usbdevfs_urb *out = NULL; + int res; + + memset(urb, 0, sizeof(*urb)); + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->endpoint = h->ep_in; + urb->status = -1; + urb->buffer = data; + urb->buffer_length = len; + + + adb_mutex_lock(&h->lock); + if(h->dead) { + res = -1; + goto fail; + } + do { + res = ioctl(h->desc, USBDEVFS_SUBMITURB, urb); + } while((res < 0) && (errno == EINTR)); + + if(res < 0) { + goto fail; + } + + h->urb_in_busy = 1; + for(;;) { + D("[ reap urb - wait ]\n"); + h->reaper_thread = pthread_self(); + adb_mutex_unlock(&h->lock); + res = ioctl(h->desc, USBDEVFS_REAPURB, &out); + adb_mutex_lock(&h->lock); + h->reaper_thread = 0; + if(h->dead) { + res = -1; + break; + } + if(res < 0) { + if(errno == EINTR) { + continue; + } + D("[ reap urb - error ]\n"); + break; + } + D("[ urb @%p status = %d, actual = %d ]\n", + out, out->status, out->actual_length); + + if(out == &h->urb_in) { + D("[ reap urb - IN complete ]\n"); + h->urb_in_busy = 0; + if(urb->status == 0) { + res = urb->actual_length; + } else { + res = -1; + } + break; + } + if(out == &h->urb_out) { + D("[ reap urb - OUT compelete ]\n"); + h->urb_out_busy = 0; + adb_cond_broadcast(&h->notify); + } + } +fail: + adb_mutex_unlock(&h->lock); + return res; +} + + +int usb_write(usb_handle *h, const void *_data, int len) +{ + unsigned char *data = (unsigned char*) _data; + int n; + int need_zero = 0; + + if(h->zero_mask) { + /* if we need 0-markers and our transfer + ** is an even multiple of the packet size, + ** we make note of it + */ + if(!(len & h->zero_mask)) { + need_zero = 1; + } + } + + while(len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + n = usb_bulk_write(h, data, xfer); + if(n != xfer) { + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + + len -= xfer; + data += xfer; + } + + if(need_zero){ + n = usb_bulk_write(h, _data, 0); + return n; + } + + return 0; +} + +int usb_read(usb_handle *h, void *_data, int len) +{ + unsigned char *data = (unsigned char*) _data; + int n; + + D("++ usb_read ++\n"); + while(len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + D("[ usb read %d fd = %d], fname=%s\n", xfer, h->desc, h->fname); + n = usb_bulk_read(h, data, xfer); + D("[ usb read %d ] = %d, fname=%s\n", xfer, n, h->fname); + if(n != xfer) { + if((errno == ETIMEDOUT) && (h->desc != -1)) { + D("[ timeout ]\n"); + if(n > 0){ + data += n; + len -= n; + } + continue; + } + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + + len -= xfer; + data += xfer; + } + + D("-- usb_read --\n"); + return 0; +} + +void usb_kick(usb_handle *h) +{ + D("[ kicking %p (fd = %d) ]\n", h, h->desc); + adb_mutex_lock(&h->lock); + if(h->dead == 0) { + h->dead = 1; + + if (h->writeable) { + /* HACK ALERT! + ** Sometimes we get stuck in ioctl(USBDEVFS_REAPURB). + ** This is a workaround for that problem. + */ + if (h->reaper_thread) { + pthread_kill(h->reaper_thread, SIGALRM); + } + + /* cancel any pending transactions + ** these will quietly fail if the txns are not active, + ** but this ensures that a reader blocked on REAPURB + ** will get unblocked + */ + ioctl(h->desc, USBDEVFS_DISCARDURB, &h->urb_in); + ioctl(h->desc, USBDEVFS_DISCARDURB, &h->urb_out); + h->urb_in.status = -ENODEV; + h->urb_out.status = -ENODEV; + h->urb_in_busy = 0; + h->urb_out_busy = 0; + adb_cond_broadcast(&h->notify); + } else { + unregister_usb_transport(h); + } + } + adb_mutex_unlock(&h->lock); +} + +int usb_close(usb_handle *h) +{ + D("[ usb close ... ]\n"); + adb_mutex_lock(&usb_lock); + h->next->prev = h->prev; + h->prev->next = h->next; + h->prev = 0; + h->next = 0; + + adb_close(h->desc); + D("[ usb closed %p (fd = %d) ]\n", h, h->desc); + adb_mutex_unlock(&usb_lock); + + free(h); + return 0; +} + +static void register_device(const char *dev_name, + unsigned char ep_in, unsigned char ep_out, + int interface, int serial_index, unsigned zero_mask) +{ + usb_handle* usb = 0; + int n = 0; + char serial[256]; + + /* Since Linux will not reassign the device ID (and dev_name) + ** as long as the device is open, we can add to the list here + ** once we open it and remove from the list when we're finally + ** closed and everything will work out fine. + ** + ** If we have a usb_handle on the list 'o handles with a matching + ** name, we have no further work to do. + */ + adb_mutex_lock(&usb_lock); + for(usb = handle_list.next; usb != &handle_list; usb = usb->next){ + if(!strcmp(usb->fname, dev_name)) { + adb_mutex_unlock(&usb_lock); + return; + } + } + adb_mutex_unlock(&usb_lock); + + D("[ usb located new device %s (%d/%d/%d) ]\n", + dev_name, ep_in, ep_out, interface); + usb = calloc(1, sizeof(usb_handle)); + strcpy(usb->fname, dev_name); + usb->ep_in = ep_in; + usb->ep_out = ep_out; + usb->zero_mask = zero_mask; + usb->writeable = 1; + + adb_cond_init(&usb->notify, 0); + adb_mutex_init(&usb->lock, 0); + /* initialize mark to 1 so we don't get garbage collected after the device scan */ + usb->mark = 1; + usb->reaper_thread = 0; + + usb->desc = unix_open(usb->fname, O_RDWR); + if(usb->desc < 0) { + /* if we fail, see if have read-only access */ + usb->desc = unix_open(usb->fname, O_RDONLY); + if(usb->desc < 0) goto fail; + usb->writeable = 0; + D("[ usb open read-only %s fd = %d]\n", usb->fname, usb->desc); + } else { + D("[ usb open %s fd = %d]\n", usb->fname, usb->desc); + n = ioctl(usb->desc, USBDEVFS_CLAIMINTERFACE, &interface); + if(n != 0) goto fail; + } + + /* read the device's serial number */ + serial[0] = 0; + memset(serial, 0, sizeof(serial)); + if (serial_index) { + struct usbdevfs_ctrltransfer ctrl; + __u16 buffer[128]; + __u16 languages[128]; + int i, result; + int languageCount = 0; + + memset(languages, 0, sizeof(languages)); + memset(&ctrl, 0, sizeof(ctrl)); + + // read list of supported languages + ctrl.bRequestType = USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE; + ctrl.bRequest = USB_REQ_GET_DESCRIPTOR; + ctrl.wValue = (USB_DT_STRING << 8) | 0; + ctrl.wIndex = 0; + ctrl.wLength = sizeof(languages); + ctrl.data = languages; + + result = ioctl(usb->desc, USBDEVFS_CONTROL, &ctrl); + if (result > 0) + languageCount = (result - 2) / 2; + + for (i = 1; i <= languageCount; i++) { + memset(buffer, 0, sizeof(buffer)); + memset(&ctrl, 0, sizeof(ctrl)); + + ctrl.bRequestType = USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE; + ctrl.bRequest = USB_REQ_GET_DESCRIPTOR; + ctrl.wValue = (USB_DT_STRING << 8) | serial_index; + ctrl.wIndex = languages[i]; + ctrl.wLength = sizeof(buffer); + ctrl.data = buffer; + + result = ioctl(usb->desc, USBDEVFS_CONTROL, &ctrl); + if (result > 0) { + int i; + // skip first word, and copy the rest to the serial string, changing shorts to bytes. + result /= 2; + for (i = 1; i < result; i++) + serial[i - 1] = buffer[i]; + serial[i - 1] = 0; + break; + } + } + } + + /* add to the end of the active handles */ + adb_mutex_lock(&usb_lock); + usb->next = &handle_list; + usb->prev = handle_list.prev; + usb->prev->next = usb; + usb->next->prev = usb; + adb_mutex_unlock(&usb_lock); + + register_usb_transport(usb, serial, usb->writeable); + return; + +fail: + D("[ usb open %s error=%d, err_str = %s]\n", + usb->fname, errno, strerror(errno)); + if(usb->desc >= 0) { + adb_close(usb->desc); + } + free(usb); +} + +void* device_poll_thread(void* unused) +{ + D("Created device thread\n"); + for(;;) { + /* XXX use inotify */ + find_usb_device("/dev/bus/usb", register_device); + kick_disconnected_devices(); + sleep(1); + } + return NULL; +} + +static void sigalrm_handler(int signo) +{ + // don't need to do anything here +} + +void usb_init() +{ + adb_thread_t tid; + struct sigaction actions; + + memset(&actions, 0, sizeof(actions)); + sigemptyset(&actions.sa_mask); + actions.sa_flags = 0; + actions.sa_handler = sigalrm_handler; + sigaction(SIGALRM,& actions, NULL); + + if(adb_thread_create(&tid, device_poll_thread, NULL)){ + fatal_errno("cannot create input thread"); + } +} + diff --git a/adb/usb_linux_client.c b/adb/usb_linux_client.c new file mode 100644 index 0000000..0a21c6f --- /dev/null +++ b/adb/usb_linux_client.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <dirent.h> +#include <errno.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + + +struct usb_handle +{ + int fd; + adb_cond_t notify; + adb_mutex_t lock; +}; + +void usb_cleanup() +{ + // nothing to do here +} + +static void *usb_open_thread(void *x) +{ + struct usb_handle *usb = (struct usb_handle *)x; + int fd; + + while (1) { + // wait until the USB device needs opening + adb_mutex_lock(&usb->lock); + while (usb->fd != -1) + adb_cond_wait(&usb->notify, &usb->lock); + adb_mutex_unlock(&usb->lock); + + D("[ usb_thread - opening device ]\n"); + do { + /* XXX use inotify? */ + fd = unix_open("/dev/android_adb", O_RDWR); + if (fd < 0) { + // to support older kernels + fd = unix_open("/dev/android", O_RDWR); + } + if (fd < 0) { + adb_sleep_ms(1000); + } + } while (fd < 0); + D("[ opening device succeeded ]\n"); + + close_on_exec(fd); + usb->fd = fd; + + D("[ usb_thread - registering device ]\n"); + register_usb_transport(usb, 0, 1); + } + + // never gets here + return 0; +} + +int usb_write(usb_handle *h, const void *data, int len) +{ + int n; + + D("[ write %d ]\n", len); + n = adb_write(h->fd, data, len); + if(n != len) { + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + D("[ done ]\n"); + return 0; +} + +int usb_read(usb_handle *h, void *data, int len) +{ + int n; + + D("[ read %d ]\n", len); + n = adb_read(h->fd, data, len); + if(n != len) { + D("ERROR: n = %d, errno = %d (%s)\n", + n, errno, strerror(errno)); + return -1; + } + return 0; +} + +void usb_init() +{ + usb_handle *h; + adb_thread_t tid; + int fd; + + h = calloc(1, sizeof(usb_handle)); + h->fd = -1; + adb_cond_init(&h->notify, 0); + adb_mutex_init(&h->lock, 0); + + // Open the file /dev/android_adb_enable to trigger + // the enabling of the adb USB function in the kernel. + // We never touch this file again - just leave it open + // indefinitely so the kernel will know when we are running + // and when we are not. + fd = unix_open("/dev/android_adb_enable", O_RDWR); + if (fd < 0) { + D("failed to open /dev/android_adb_enable\n"); + } else { + close_on_exec(fd); + } + + D("[ usb_init - starting thread ]\n"); + if(adb_thread_create(&tid, usb_open_thread, h)){ + fatal_errno("cannot create usb thread"); + } +} + +void usb_kick(usb_handle *h) +{ + D("usb_kick\n"); + adb_mutex_lock(&h->lock); + adb_close(h->fd); + h->fd = -1; + + // notify usb_open_thread that we are disconnected + adb_cond_signal(&h->notify); + adb_mutex_unlock(&h->lock); +} + +int usb_close(usb_handle *h) +{ + // nothing to do here + return 0; +} diff --git a/adb/usb_osx.c b/adb/usb_osx.c new file mode 100644 index 0000000..00d02da --- /dev/null +++ b/adb/usb_osx.c @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <CoreFoundation/CoreFoundation.h> + +#include <IOKit/IOKitLib.h> +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOMessage.h> +#include <mach/mach_port.h> + +#include "sysdeps.h" + +#include <stdio.h> + +#define TRACE_TAG TRACE_USB +#include "adb.h" +#include "usb_vendors.h" + +#define DBG D + +static IONotificationPortRef notificationPort = 0; +static io_iterator_t* notificationIterators; + +struct usb_handle +{ + UInt8 bulkIn; + UInt8 bulkOut; + IOUSBInterfaceInterface **interface; + io_object_t usbNotification; + unsigned int zero_mask; +}; + +static CFRunLoopRef currentRunLoop = 0; +static pthread_mutex_t start_lock; +static pthread_cond_t start_cond; + + +static void AndroidInterfaceAdded(void *refCon, io_iterator_t iterator); +static void AndroidInterfaceNotify(void *refCon, io_iterator_t iterator, + natural_t messageType, + void *messageArgument); +static usb_handle* CheckInterface(IOUSBInterfaceInterface **iface, + UInt16 vendor, UInt16 product); + +static int +InitUSB() +{ + CFMutableDictionaryRef matchingDict; + CFRunLoopSourceRef runLoopSource; + SInt32 vendor, if_subclass, if_protocol; + unsigned i; + + //* To set up asynchronous notifications, create a notification port and + //* add its run loop event source to the program's run loop + notificationPort = IONotificationPortCreate(kIOMasterPortDefault); + runLoopSource = IONotificationPortGetRunLoopSource(notificationPort); + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode); + + memset(notificationIterators, 0, sizeof(notificationIterators)); + + //* loop through all supported vendors + for (i = 0; i < vendorIdCount; i++) { + //* Create our matching dictionary to find the Android device's + //* adb interface + //* IOServiceAddMatchingNotification consumes the reference, so we do + //* not need to release this + matchingDict = IOServiceMatching(kIOUSBInterfaceClassName); + + if (!matchingDict) { + DBG("ERR: Couldn't create USB matching dictionary.\n"); + return -1; + } + + //* Match based on vendor id, interface subclass and protocol + vendor = vendorIds[i]; + if_subclass = ADB_SUBCLASS; + if_protocol = ADB_PROTOCOL; + CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), + CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt32Type, &vendor)); + CFDictionarySetValue(matchingDict, CFSTR(kUSBInterfaceSubClass), + CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt32Type, &if_subclass)); + CFDictionarySetValue(matchingDict, CFSTR(kUSBInterfaceProtocol), + CFNumberCreate(kCFAllocatorDefault, + kCFNumberSInt32Type, &if_protocol)); + IOServiceAddMatchingNotification( + notificationPort, + kIOFirstMatchNotification, + matchingDict, + AndroidInterfaceAdded, + NULL, + ¬ificationIterators[i]); + + //* Iterate over set of matching interfaces to access already-present + //* devices and to arm the notification + AndroidInterfaceAdded(NULL, notificationIterators[i]); + } + + return 0; +} + +static void +AndroidInterfaceAdded(void *refCon, io_iterator_t iterator) +{ + kern_return_t kr; + io_service_t usbDevice; + io_service_t usbInterface; + IOCFPlugInInterface **plugInInterface = NULL; + IOUSBInterfaceInterface220 **iface = NULL; + IOUSBDeviceInterface197 **dev = NULL; + HRESULT result; + SInt32 score; + UInt16 vendor; + UInt16 product; + UInt8 serialIndex; + char serial[256]; + + while ((usbInterface = IOIteratorNext(iterator))) { + //* Create an intermediate interface plugin + kr = IOCreatePlugInInterfaceForService(usbInterface, + kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, &score); + IOObjectRelease(usbInterface); + if ((kIOReturnSuccess != kr) || (!plugInInterface)) { + DBG("ERR: Unable to create an interface plug-in (%08x)\n", kr); + continue; + } + + //* This gets us the interface object + result = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (LPVOID) + &iface); + //* We only needed the plugin to get the interface, so discard it + (*plugInInterface)->Release(plugInInterface); + if (result || !iface) { + DBG("ERR: Couldn't query the interface (%08x)\n", (int) result); + continue; + } + + //* this gets us an ioservice, with which we will find the actual + //* device; after getting a plugin, and querying the interface, of + //* course. + //* Gotta love OS X + kr = (*iface)->GetDevice(iface, &usbDevice); + if (kIOReturnSuccess != kr || !usbDevice) { + DBG("ERR: Couldn't grab device from interface (%08x)\n", kr); + continue; + } + + plugInInterface = NULL; + score = 0; + //* create an intermediate device plugin + kr = IOCreatePlugInInterfaceForService(usbDevice, + kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, &score); + //* only needed this to find the plugin + (void)IOObjectRelease(usbDevice); + if ((kIOReturnSuccess != kr) || (!plugInInterface)) { + DBG("ERR: Unable to create a device plug-in (%08x)\n", kr); + continue; + } + + result = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID) &dev); + //* only needed this to query the plugin + (*plugInInterface)->Release(plugInInterface); + if (result || !dev) { + DBG("ERR: Couldn't create a device interface (%08x)\n", + (int) result); + continue; + } + + //* Now after all that, we actually have a ref to the device and + //* the interface that matched our criteria + + kr = (*dev)->GetDeviceVendor(dev, &vendor); + kr = (*dev)->GetDeviceProduct(dev, &product); + kr = (*dev)->USBGetSerialNumberStringIndex(dev, &serialIndex); + + if (serialIndex > 0) { + IOUSBDevRequest req; + UInt16 buffer[256]; + UInt16 languages[128]; + + memset(languages, 0, sizeof(languages)); + + req.bmRequestType = + USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); + req.bRequest = kUSBRqGetDescriptor; + req.wValue = (kUSBStringDesc << 8) | 0; + req.wIndex = 0; + req.pData = languages; + req.wLength = sizeof(languages); + kr = (*dev)->DeviceRequest(dev, &req); + + if (kr == kIOReturnSuccess && req.wLenDone > 0) { + + int langCount = (req.wLenDone - 2) / 2, lang; + + for (lang = 1; lang <= langCount; lang++) { + + memset(buffer, 0, sizeof(buffer)); + memset(&req, 0, sizeof(req)); + + req.bmRequestType = + USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); + req.bRequest = kUSBRqGetDescriptor; + req.wValue = (kUSBStringDesc << 8) | serialIndex; + req.wIndex = languages[lang]; + req.pData = buffer; + req.wLength = sizeof(buffer); + kr = (*dev)->DeviceRequest(dev, &req); + + if (kr == kIOReturnSuccess && req.wLenDone > 0) { + int i, count; + + // skip first word, and copy the rest to the serial string, + // changing shorts to bytes. + count = (req.wLenDone - 1) / 2; + for (i = 0; i < count; i++) + serial[i] = buffer[i + 1]; + serial[i] = 0; + break; + } + } + } + } + (*dev)->Release(dev); + + DBG("INFO: Found vid=%04x pid=%04x serial=%s\n", vendor, product, + serial); + + usb_handle* handle = CheckInterface((IOUSBInterfaceInterface**)iface, + vendor, product); + if (handle == NULL) { + DBG("ERR: Could not find device interface: %08x\n", kr); + (*iface)->Release(iface); + continue; + } + + DBG("AndroidDeviceAdded calling register_usb_transport\n"); + register_usb_transport(handle, (serial[0] ? serial : NULL), 1); + + // Register for an interest notification of this device being removed. + // Pass the reference to our private data as the refCon for the + // notification. + kr = IOServiceAddInterestNotification(notificationPort, + usbInterface, + kIOGeneralInterest, + AndroidInterfaceNotify, + handle, + &handle->usbNotification); + + if (kIOReturnSuccess != kr) { + DBG("ERR: Unable to create interest notification (%08x)\n", kr); + } + } +} + +static void +AndroidInterfaceNotify(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) +{ + usb_handle *handle = (usb_handle *)refCon; + + if (messageType == kIOMessageServiceIsTerminated) { + if (!handle) { + DBG("ERR: NULL handle\n"); + return; + } + DBG("AndroidInterfaceNotify\n"); + IOObjectRelease(handle->usbNotification); + usb_kick(handle); + } +} + +//* TODO: simplify this further since we only register to get ADB interface +//* subclass+protocol events +static usb_handle* +CheckInterface(IOUSBInterfaceInterface **interface, UInt16 vendor, UInt16 product) +{ + usb_handle* handle = NULL; + IOReturn kr; + UInt8 interfaceNumEndpoints, interfaceClass, interfaceSubClass, interfaceProtocol; + UInt8 endpoint; + + + //* Now open the interface. This will cause the pipes associated with + //* the endpoints in the interface descriptor to be instantiated + kr = (*interface)->USBInterfaceOpen(interface); + if (kr != kIOReturnSuccess) { + DBG("ERR: Could not open interface: (%08x)\n", kr); + return NULL; + } + + //* Get the number of endpoints associated with this interface + kr = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints); + if (kr != kIOReturnSuccess) { + DBG("ERR: Unable to get number of endpoints: (%08x)\n", kr); + goto err_get_num_ep; + } + + //* Get interface class, subclass and protocol + if ((*interface)->GetInterfaceClass(interface, &interfaceClass) != kIOReturnSuccess || + (*interface)->GetInterfaceSubClass(interface, &interfaceSubClass) != kIOReturnSuccess || + (*interface)->GetInterfaceProtocol(interface, &interfaceProtocol) != kIOReturnSuccess) { + DBG("ERR: Unable to get interface class, subclass and protocol\n"); + goto err_get_interface_class; + } + + //* check to make sure interface class, subclass and protocol match ADB + //* avoid opening mass storage endpoints + if (!is_adb_interface(vendor, product, interfaceClass, + interfaceSubClass, interfaceProtocol)) + goto err_bad_adb_interface; + + handle = calloc(1, sizeof(usb_handle)); + + //* Iterate over the endpoints for this interface and find the first + //* bulk in/out pipes available. These will be our read/write pipes. + for (endpoint = 0; endpoint <= interfaceNumEndpoints; endpoint++) { + UInt8 transferType; + UInt16 maxPacketSize; + UInt8 interval; + UInt8 number; + UInt8 direction; + + kr = (*interface)->GetPipeProperties(interface, endpoint, &direction, + &number, &transferType, &maxPacketSize, &interval); + + if (kIOReturnSuccess == kr) { + if (kUSBBulk != transferType) + continue; + + if (kUSBIn == direction) + handle->bulkIn = endpoint; + + if (kUSBOut == direction) + handle->bulkOut = endpoint; + + handle->zero_mask = maxPacketSize - 1; + } else { + DBG("ERR: FindDeviceInterface - could not get pipe properties\n"); + goto err_get_pipe_props; + } + } + + handle->interface = interface; + return handle; + +err_get_pipe_props: + free(handle); +err_bad_adb_interface: +err_get_interface_class: +err_get_num_ep: + (*interface)->USBInterfaceClose(interface); + return NULL; +} + + +void* RunLoopThread(void* unused) +{ + unsigned i; + + InitUSB(); + + currentRunLoop = CFRunLoopGetCurrent(); + + // Signal the parent that we are running + adb_mutex_lock(&start_lock); + adb_cond_signal(&start_cond); + adb_mutex_unlock(&start_lock); + + CFRunLoopRun(); + currentRunLoop = 0; + + for (i = 0; i < vendorIdCount; i++) { + IOObjectRelease(notificationIterators[i]); + } + IONotificationPortDestroy(notificationPort); + + DBG("RunLoopThread done\n"); + return NULL; +} + + +static int initialized = 0; +void usb_init() +{ + if (!initialized) + { + adb_thread_t tid; + + notificationIterators = (io_iterator_t*)malloc( + vendorIdCount * sizeof(io_iterator_t)); + + adb_mutex_init(&start_lock, NULL); + adb_cond_init(&start_cond, NULL); + + if(adb_thread_create(&tid, RunLoopThread, NULL)) + fatal_errno("cannot create input thread"); + + // Wait for initialization to finish + adb_mutex_lock(&start_lock); + adb_cond_wait(&start_cond, &start_lock); + adb_mutex_unlock(&start_lock); + + adb_mutex_destroy(&start_lock); + adb_cond_destroy(&start_cond); + + initialized = 1; + } +} + +void usb_cleanup() +{ + DBG("usb_cleanup\n"); + close_usb_devices(); + if (currentRunLoop) + CFRunLoopStop(currentRunLoop); + + if (notificationIterators != NULL) { + free(notificationIterators); + notificationIterators = NULL; + } +} + +int usb_write(usb_handle *handle, const void *buf, int len) +{ + IOReturn result; + + if (!len) + return 0; + + if (!handle) + return -1; + + if (NULL == handle->interface) { + DBG("ERR: usb_write interface was null\n"); + return -1; + } + + if (0 == handle->bulkOut) { + DBG("ERR: bulkOut endpoint not assigned\n"); + return -1; + } + + result = + (*handle->interface)->WritePipe( + handle->interface, handle->bulkOut, (void *)buf, len); + + if ((result == 0) && (handle->zero_mask)) { + /* we need 0-markers and our transfer */ + if(!(len & handle->zero_mask)) { + result = + (*handle->interface)->WritePipe( + handle->interface, handle->bulkOut, (void *)buf, 0); + } + } + + if (0 == result) + return 0; + + DBG("ERR: usb_write failed with status %d\n", result); + return -1; +} + +int usb_read(usb_handle *handle, void *buf, int len) +{ + IOReturn result; + UInt32 numBytes = len; + + if (!len) { + return 0; + } + + if (!handle) { + return -1; + } + + if (NULL == handle->interface) { + DBG("ERR: usb_read interface was null\n"); + return -1; + } + + if (0 == handle->bulkIn) { + DBG("ERR: bulkIn endpoint not assigned\n"); + return -1; + } + + result = + (*handle->interface)->ReadPipe(handle->interface, + handle->bulkIn, buf, &numBytes); + + if (0 == result) + return 0; + else { + DBG("ERR: usb_read failed with status %d\n", result); + } + + return -1; +} + +int usb_close(usb_handle *handle) +{ + return 0; +} + +void usb_kick(usb_handle *handle) +{ + /* release the interface */ + if (!handle) + return; + + if (handle->interface) + { + (*handle->interface)->USBInterfaceClose(handle->interface); + (*handle->interface)->Release(handle->interface); + handle->interface = 0; + } +} diff --git a/adb/usb_vendors.c b/adb/usb_vendors.c new file mode 100644 index 0000000..cbc2464 --- /dev/null +++ b/adb/usb_vendors.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "usb_vendors.h" + +#include <stdio.h> + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include "windows.h" +# include "shlobj.h" +#else +# include <unistd.h> +# include <sys/stat.h> +#endif + +#include "sysdeps.h" +#include "adb.h" + +#define ANDROID_PATH ".android" +#define ANDROID_ADB_INI "adb_usb.ini" + +#define TRACE_TAG TRACE_USB + +// Google's USB Vendor ID +#define VENDOR_ID_GOOGLE 0x18d1 +// HTC's USB Vendor ID +#define VENDOR_ID_HTC 0x0bb4 +// Samsung's USB Vendor ID +#define VENDOR_ID_SAMSUNG 0x04e8 +// Motorola's USB Vendor ID +#define VENDOR_ID_MOTOROLA 0x22b8 +// LG's USB Vendor ID +#define VENDOR_ID_LGE 0x1004 +// Huawei's USB Vendor ID +#define VENDOR_ID_HUAWEI 0x12D1 +// Acer's USB Vendor ID +#define VENDOR_ID_ACER 0x0502 +// Sony Ericsson's USB Vendor ID +#define VENDOR_ID_SONY_ERICSSON 0x0FCE +// Foxconn's USB Vendor ID +#define VENDOR_ID_FOXCONN 0x0489 +// Dell's USB Vendor ID +#define VENDOR_ID_DELL 0x413c +// Nvidia's USB Vendor ID +#define VENDOR_ID_NVIDIA 0x0955 +// Garmin-Asus's USB Vendor ID +#define VENDOR_ID_GARMIN_ASUS 0x091E +// Sharp's USB Vendor ID +#define VENDOR_ID_SHARP 0x04dd +// ZTE's USB Vendor ID +#define VENDOR_ID_ZTE 0x19D2 +// Kyocera's USB Vendor ID +#define VENDOR_ID_KYOCERA 0x0482 +// Pantech's USB Vendor ID +#define VENDOR_ID_PANTECH 0x10A9 +// Qualcomm's USB Vendor ID +#define VENDOR_ID_QUALCOMM 0x05c6 +// NEC's USB Vendor ID +#define VENDOR_ID_NEC 0x0409 +// Panasonic Mobile Communication's USB Vendor ID +#define VENDOR_ID_PMC 0x04DA +// Toshiba's USB Vendor ID +#define VENDOR_ID_TOSHIBA 0x0930 +// SK Telesys's USB Vendor ID +#define VENDOR_ID_SK_TELESYS 0x1F53 +// KT Tech's USB Vendor ID +#define VENDOR_ID_KT_TECH 0x2116 +// Asus's USB Vendor ID +#define VENDOR_ID_ASUS 0x0b05 +// Philips's USB Vendor ID +#define VENDOR_ID_PHILIPS 0x0471 + + +/** built-in vendor list */ +int builtInVendorIds[] = { + VENDOR_ID_GOOGLE, + VENDOR_ID_HTC, + VENDOR_ID_SAMSUNG, + VENDOR_ID_MOTOROLA, + VENDOR_ID_LGE, + VENDOR_ID_HUAWEI, + VENDOR_ID_ACER, + VENDOR_ID_SONY_ERICSSON, + VENDOR_ID_FOXCONN, + VENDOR_ID_DELL, + VENDOR_ID_NVIDIA, + VENDOR_ID_GARMIN_ASUS, + VENDOR_ID_SHARP, + VENDOR_ID_ZTE, + VENDOR_ID_KYOCERA, + VENDOR_ID_PANTECH, + VENDOR_ID_QUALCOMM, + VENDOR_ID_NEC, + VENDOR_ID_PMC, + VENDOR_ID_TOSHIBA, + VENDOR_ID_SK_TELESYS, + VENDOR_ID_KT_TECH, + VENDOR_ID_ASUS, + VENDOR_ID_PHILIPS, +}; + +#define BUILT_IN_VENDOR_COUNT (sizeof(builtInVendorIds)/sizeof(builtInVendorIds[0])) + +/* max number of supported vendor ids (built-in + 3rd party). increase as needed */ +#define VENDOR_COUNT_MAX 128 + +int vendorIds[VENDOR_COUNT_MAX]; +unsigned vendorIdCount = 0; + +int get_adb_usb_ini(char* buff, size_t len); + +void usb_vendors_init(void) +{ + if (VENDOR_COUNT_MAX < BUILT_IN_VENDOR_COUNT) { + fprintf(stderr, "VENDOR_COUNT_MAX not big enough for built-in vendor list.\n"); + exit(2); + } + + /* add the built-in vendors at the beginning of the array */ + memcpy(vendorIds, builtInVendorIds, sizeof(builtInVendorIds)); + + /* default array size is the number of built-in vendors */ + vendorIdCount = BUILT_IN_VENDOR_COUNT; + + if (VENDOR_COUNT_MAX == BUILT_IN_VENDOR_COUNT) + return; + + char temp[PATH_MAX]; + if (get_adb_usb_ini(temp, sizeof(temp)) == 0) { + FILE * f = fopen(temp, "rt"); + + if (f != NULL) { + /* The vendor id file is pretty basic. 1 vendor id per line. + Lines starting with # are comments */ + while (fgets(temp, sizeof(temp), f) != NULL) { + if (temp[0] == '#') + continue; + + long value = strtol(temp, NULL, 0); + if (errno == EINVAL || errno == ERANGE || value > INT_MAX || value < 0) { + fprintf(stderr, "Invalid content in %s. Quitting.\n", ANDROID_ADB_INI); + exit(2); + } + + vendorIds[vendorIdCount++] = (int)value; + + /* make sure we don't go beyond the array */ + if (vendorIdCount == VENDOR_COUNT_MAX) { + break; + } + } + } + } +} + +/* Utils methods */ + +/* builds the path to the adb vendor id file. returns 0 if success */ +int build_path(char* buff, size_t len, const char* format, const char* home) +{ + if (snprintf(buff, len, format, home, ANDROID_PATH, ANDROID_ADB_INI) >= len) { + return 1; + } + + return 0; +} + +/* fills buff with the path to the adb vendor id file. returns 0 if success */ +int get_adb_usb_ini(char* buff, size_t len) +{ +#ifdef _WIN32 + const char* home = getenv("ANDROID_SDK_HOME"); + if (home != NULL) { + return build_path(buff, len, "%s\\%s\\%s", home); + } else { + char path[MAX_PATH]; + SHGetFolderPath( NULL, CSIDL_PROFILE, NULL, 0, path); + return build_path(buff, len, "%s\\%s\\%s", path); + } +#else + const char* home = getenv("HOME"); + if (home == NULL) + home = "/tmp"; + + return build_path(buff, len, "%s/%s/%s", home); +#endif +} diff --git a/adb/usb_vendors.h b/adb/usb_vendors.h new file mode 100644 index 0000000..43790b9 --- /dev/null +++ b/adb/usb_vendors.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __USB_VENDORS_H +#define __USB_VENDORS_H + +extern int vendorIds[]; +extern unsigned vendorIdCount; + +void usb_vendors_init(void); + +#endif
\ No newline at end of file diff --git a/adb/usb_windows.c b/adb/usb_windows.c new file mode 100644 index 0000000..38c4cf4 --- /dev/null +++ b/adb/usb_windows.c @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <windows.h> +#include <winerror.h> +#include <errno.h> +#include <usb100.h> +#include <adb_api.h> +#include <stdio.h> + +#include "sysdeps.h" + +#define TRACE_TAG TRACE_USB +#include "adb.h" + +/** Structure usb_handle describes our connection to the usb device via + AdbWinApi.dll. This structure is returned from usb_open() routine and + is expected in each subsequent call that is accessing the device. +*/ +struct usb_handle { + /// Previous entry in the list of opened usb handles + usb_handle *prev; + + /// Next entry in the list of opened usb handles + usb_handle *next; + + /// Handle to USB interface + ADBAPIHANDLE adb_interface; + + /// Handle to USB read pipe (endpoint) + ADBAPIHANDLE adb_read_pipe; + + /// Handle to USB write pipe (endpoint) + ADBAPIHANDLE adb_write_pipe; + + /// Interface name + char* interface_name; + + /// Mask for determining when to use zero length packets + unsigned zero_mask; +}; + +/// Class ID assigned to the device by androidusb.sys +static const GUID usb_class_id = ANDROID_USB_CLASS_ID; + +/// List of opened usb handles +static usb_handle handle_list = { + .prev = &handle_list, + .next = &handle_list, +}; + +/// Locker for the list of opened usb handles +ADB_MUTEX_DEFINE( usb_lock ); + +/// Checks if there is opened usb handle in handle_list for this device. +int known_device(const char* dev_name); + +/// Checks if there is opened usb handle in handle_list for this device. +/// usb_lock mutex must be held before calling this routine. +int known_device_locked(const char* dev_name); + +/// Registers opened usb handle (adds it to handle_list). +int register_new_device(usb_handle* handle); + +/// Checks if interface (device) matches certain criteria +int recognized_device(usb_handle* handle); + +/// Enumerates present and available interfaces (devices), opens new ones and +/// registers usb transport for them. +void find_devices(); + +/// Entry point for thread that polls (every second) for new usb interfaces. +/// This routine calls find_devices in infinite loop. +void* device_poll_thread(void* unused); + +/// Initializes this module +void usb_init(); + +/// Cleans up this module +void usb_cleanup(); + +/// Opens usb interface (device) by interface (device) name. +usb_handle* do_usb_open(const wchar_t* interface_name); + +/// Writes data to the opened usb handle +int usb_write(usb_handle* handle, const void* data, int len); + +/// Reads data using the opened usb handle +int usb_read(usb_handle *handle, void* data, int len); + +/// Cleans up opened usb handle +void usb_cleanup_handle(usb_handle* handle); + +/// Cleans up (but don't close) opened usb handle +void usb_kick(usb_handle* handle); + +/// Closes opened usb handle +int usb_close(usb_handle* handle); + +/// Gets interface (device) name for an opened usb handle +const char *usb_name(usb_handle* handle); + +int known_device_locked(const char* dev_name) { + usb_handle* usb; + + if (NULL != dev_name) { + // Iterate through the list looking for the name match. + for(usb = handle_list.next; usb != &handle_list; usb = usb->next) { + // In Windows names are not case sensetive! + if((NULL != usb->interface_name) && + (0 == stricmp(usb->interface_name, dev_name))) { + return 1; + } + } + } + + return 0; +} + +int known_device(const char* dev_name) { + int ret = 0; + + if (NULL != dev_name) { + adb_mutex_lock(&usb_lock); + ret = known_device_locked(dev_name); + adb_mutex_unlock(&usb_lock); + } + + return ret; +} + +int register_new_device(usb_handle* handle) { + if (NULL == handle) + return 0; + + adb_mutex_lock(&usb_lock); + + // Check if device is already in the list + if (known_device_locked(handle->interface_name)) { + adb_mutex_unlock(&usb_lock); + return 0; + } + + // Not in the list. Add this handle to the list. + handle->next = &handle_list; + handle->prev = handle_list.prev; + handle->prev->next = handle; + handle->next->prev = handle; + + adb_mutex_unlock(&usb_lock); + + return 1; +} + +void* device_poll_thread(void* unused) { + D("Created device thread\n"); + + while(1) { + find_devices(); + adb_sleep_ms(1000); + } + + return NULL; +} + +void usb_init() { + adb_thread_t tid; + + if(adb_thread_create(&tid, device_poll_thread, NULL)) { + fatal_errno("cannot create input thread"); + } +} + +void usb_cleanup() { +} + +usb_handle* do_usb_open(const wchar_t* interface_name) { + // Allocate our handle + usb_handle* ret = (usb_handle*)malloc(sizeof(usb_handle)); + if (NULL == ret) + return NULL; + + // Set linkers back to the handle + ret->next = ret; + ret->prev = ret; + + // Create interface. + ret->adb_interface = AdbCreateInterfaceByName(interface_name); + + if (NULL == ret->adb_interface) { + free(ret); + errno = GetLastError(); + return NULL; + } + + // Open read pipe (endpoint) + ret->adb_read_pipe = + AdbOpenDefaultBulkReadEndpoint(ret->adb_interface, + AdbOpenAccessTypeReadWrite, + AdbOpenSharingModeReadWrite); + if (NULL != ret->adb_read_pipe) { + // Open write pipe (endpoint) + ret->adb_write_pipe = + AdbOpenDefaultBulkWriteEndpoint(ret->adb_interface, + AdbOpenAccessTypeReadWrite, + AdbOpenSharingModeReadWrite); + if (NULL != ret->adb_write_pipe) { + // Save interface name + unsigned long name_len = 0; + + // First get expected name length + AdbGetInterfaceName(ret->adb_interface, + NULL, + &name_len, + true); + if (0 != name_len) { + ret->interface_name = (char*)malloc(name_len); + + if (NULL != ret->interface_name) { + // Now save the name + if (AdbGetInterfaceName(ret->adb_interface, + ret->interface_name, + &name_len, + true)) { + // We're done at this point + return ret; + } + } else { + SetLastError(ERROR_OUTOFMEMORY); + } + } + } + } + + // Something went wrong. + errno = GetLastError(); + usb_cleanup_handle(ret); + free(ret); + SetLastError(errno); + + return NULL; +} + +int usb_write(usb_handle* handle, const void* data, int len) { + unsigned long time_out = 500 + len * 8; + unsigned long written = 0; + int ret; + + D("usb_write %d\n", len); + if (NULL != handle) { + // Perform write + ret = AdbWriteEndpointSync(handle->adb_write_pipe, + (void*)data, + (unsigned long)len, + &written, + time_out); + errno = GetLastError(); + + if (ret) { + // Make sure that we've written what we were asked to write + D("usb_write got: %ld, expected: %d\n", written, len); + if (written == (unsigned long)len) { + if(handle->zero_mask && (len & handle->zero_mask) == 0) { + // Send a zero length packet + AdbWriteEndpointSync(handle->adb_write_pipe, + (void*)data, + 0, + &written, + time_out); + } + return 0; + } + } else { + // assume ERROR_INVALID_HANDLE indicates we are disconnected + if (errno == ERROR_INVALID_HANDLE) + usb_kick(handle); + } + } else { + D("usb_write NULL handle\n"); + SetLastError(ERROR_INVALID_HANDLE); + } + + D("usb_write failed: %d\n", errno); + + return -1; +} + +int usb_read(usb_handle *handle, void* data, int len) { + unsigned long time_out = 500 + len * 8; + unsigned long read = 0; + int ret; + + D("usb_read %d\n", len); + if (NULL != handle) { + while (len > 0) { + int xfer = (len > 4096) ? 4096 : len; + + ret = AdbReadEndpointSync(handle->adb_read_pipe, + (void*)data, + (unsigned long)xfer, + &read, + time_out); + errno = GetLastError(); + D("usb_write got: %ld, expected: %d, errno: %d\n", read, xfer, errno); + if (ret) { + data += read; + len -= read; + + if (len == 0) + return 0; + } else if (errno != ERROR_SEM_TIMEOUT) { + // assume ERROR_INVALID_HANDLE indicates we are disconnected + if (errno == ERROR_INVALID_HANDLE) + usb_kick(handle); + break; + } + } + } else { + D("usb_read NULL handle\n"); + SetLastError(ERROR_INVALID_HANDLE); + } + + D("usb_read failed: %d\n", errno); + + return -1; +} + +void usb_cleanup_handle(usb_handle* handle) { + if (NULL != handle) { + if (NULL != handle->interface_name) + free(handle->interface_name); + if (NULL != handle->adb_write_pipe) + AdbCloseHandle(handle->adb_write_pipe); + if (NULL != handle->adb_read_pipe) + AdbCloseHandle(handle->adb_read_pipe); + if (NULL != handle->adb_interface) + AdbCloseHandle(handle->adb_interface); + + handle->interface_name = NULL; + handle->adb_write_pipe = NULL; + handle->adb_read_pipe = NULL; + handle->adb_interface = NULL; + } +} + +void usb_kick(usb_handle* handle) { + if (NULL != handle) { + adb_mutex_lock(&usb_lock); + + usb_cleanup_handle(handle); + + adb_mutex_unlock(&usb_lock); + } else { + SetLastError(ERROR_INVALID_HANDLE); + errno = ERROR_INVALID_HANDLE; + } +} + +int usb_close(usb_handle* handle) { + D("usb_close\n"); + + if (NULL != handle) { + // Remove handle from the list + adb_mutex_lock(&usb_lock); + + if ((handle->next != handle) && (handle->prev != handle)) { + handle->next->prev = handle->prev; + handle->prev->next = handle->next; + handle->prev = handle; + handle->next = handle; + } + + adb_mutex_unlock(&usb_lock); + + // Cleanup handle + usb_cleanup_handle(handle); + free(handle); + } + + return 0; +} + +const char *usb_name(usb_handle* handle) { + if (NULL == handle) { + SetLastError(ERROR_INVALID_HANDLE); + errno = ERROR_INVALID_HANDLE; + return NULL; + } + + return (const char*)handle->interface_name; +} + +int recognized_device(usb_handle* handle) { + if (NULL == handle) + return 0; + + // Check vendor and product id first + USB_DEVICE_DESCRIPTOR device_desc; + + if (!AdbGetUsbDeviceDescriptor(handle->adb_interface, + &device_desc)) { + return 0; + } + + // Then check interface properties + USB_INTERFACE_DESCRIPTOR interf_desc; + + if (!AdbGetUsbInterfaceDescriptor(handle->adb_interface, + &interf_desc)) { + return 0; + } + + // Must have two endpoints + if (2 != interf_desc.bNumEndpoints) { + return 0; + } + + if (is_adb_interface(device_desc.idVendor, device_desc.idProduct, + interf_desc.bInterfaceClass, interf_desc.bInterfaceSubClass, interf_desc.bInterfaceProtocol)) { + + if(interf_desc.bInterfaceProtocol == 0x01) { + AdbEndpointInformation endpoint_info; + // assuming zero is a valid bulk endpoint ID + if (AdbGetEndpointInformation(handle->adb_interface, 0, &endpoint_info)) { + handle->zero_mask = endpoint_info.max_packet_size - 1; + } + } + + return 1; + } + + return 0; +} + +void find_devices() { + usb_handle* handle = NULL; + char entry_buffer[2048]; + char interf_name[2048]; + AdbInterfaceInfo* next_interface = (AdbInterfaceInfo*)(&entry_buffer[0]); + unsigned long entry_buffer_size = sizeof(entry_buffer); + char* copy_name; + + // Enumerate all present and active interfaces. + ADBAPIHANDLE enum_handle = + AdbEnumInterfaces(usb_class_id, true, true, true); + + if (NULL == enum_handle) + return; + + while (AdbNextInterface(enum_handle, next_interface, &entry_buffer_size)) { + // TODO: FIXME - temp hack converting wchar_t into char. + // It would be better to change AdbNextInterface so it will return + // interface name as single char string. + const wchar_t* wchar_name = next_interface->device_name; + for(copy_name = interf_name; + L'\0' != *wchar_name; + wchar_name++, copy_name++) { + *copy_name = (char)(*wchar_name); + } + *copy_name = '\0'; + + // Lets see if we already have this device in the list + if (!known_device(interf_name)) { + // This seems to be a new device. Open it! + handle = do_usb_open(next_interface->device_name); + if (NULL != handle) { + // Lets see if this interface (device) belongs to us + if (recognized_device(handle)) { + D("adding a new device %s\n", interf_name); + char serial_number[512]; + unsigned long serial_number_len = sizeof(serial_number); + if (AdbGetSerialNumber(handle->adb_interface, + serial_number, + &serial_number_len, + true)) { + // Lets make sure that we don't duplicate this device + if (register_new_device(handle)) { + register_usb_transport(handle, serial_number, 1); + } else { + D("register_new_device failed for %s\n", interf_name); + usb_cleanup_handle(handle); + free(handle); + } + } else { + D("cannot get serial number\n"); + usb_cleanup_handle(handle); + free(handle); + } + } else { + usb_cleanup_handle(handle); + free(handle); + } + } + } + + entry_buffer_size = sizeof(entry_buffer); + } + + AdbCloseHandle(enum_handle); +} diff --git a/adb/utils.c b/adb/utils.c new file mode 100644 index 0000000..91518ba --- /dev/null +++ b/adb/utils.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "utils.h" +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +char* +buff_addc (char* buff, char* buffEnd, int c) +{ + int avail = buffEnd - buff; + + if (avail <= 0) /* already in overflow mode */ + return buff; + + if (avail == 1) { /* overflowing, the last byte is reserved for zero */ + buff[0] = 0; + return buff + 1; + } + + buff[0] = (char) c; /* add char and terminating zero */ + buff[1] = 0; + return buff + 1; +} + +char* +buff_adds (char* buff, char* buffEnd, const char* s) +{ + int slen = strlen(s); + + return buff_addb(buff, buffEnd, s, slen); +} + +char* +buff_addb (char* buff, char* buffEnd, const void* data, int len) +{ + int avail = (buffEnd - buff); + + if (avail <= 0 || len <= 0) /* already overflowing */ + return buff; + + if (len > avail) + len = avail; + + memcpy(buff, data, len); + + buff += len; + + /* ensure there is a terminating zero */ + if (buff >= buffEnd) { /* overflow */ + buff[-1] = 0; + } else + buff[0] = 0; + + return buff; +} + +char* +buff_add (char* buff, char* buffEnd, const char* format, ... ) +{ + int avail; + + avail = (buffEnd - buff); + + if (avail > 0) { + va_list args; + int nn; + + va_start(args, format); + nn = vsnprintf( buff, avail, format, args); + va_end(args); + + if (nn < 0) { + /* some C libraries return -1 in case of overflow, + * but they will also do that if the format spec is + * invalid. We assume ADB is not buggy enough to + * trigger that last case. */ + nn = avail; + } + else if (nn > avail) { + nn = avail; + } + + buff += nn; + + /* ensure that there is a terminating zero */ + if (buff >= buffEnd) + buff[-1] = 0; + else + buff[0] = 0; + } + return buff; +} diff --git a/adb/utils.h b/adb/utils.h new file mode 100644 index 0000000..f70ecd2 --- /dev/null +++ b/adb/utils.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _ADB_UTILS_H +#define _ADB_UTILS_H + +/* bounded buffer functions */ + +/* all these functions are used to append data to a bounded buffer. + * + * after each operation, the buffer is guaranteed to be zero-terminated, + * even in the case of an overflow. they all return the new buffer position + * which allows one to use them in succession, only checking for overflows + * at the end. For example: + * + * BUFF_DECL(temp,p,end,1024); + * char* p; + * + * p = buff_addc(temp, end, '"'); + * p = buff_adds(temp, end, string); + * p = buff_addc(temp, end, '"'); + * + * if (p >= end) { + * overflow detected. note that 'temp' is + * zero-terminated for safety. + * } + * return strdup(temp); + */ + +/* tries to add a character to the buffer, in case of overflow + * this will only write a terminating zero and return buffEnd. + */ +char* buff_addc (char* buff, char* buffEnd, int c); + +/* tries to add a string to the buffer */ +char* buff_adds (char* buff, char* buffEnd, const char* s); + +/* tries to add a bytes to the buffer. the input can contain zero bytes, + * but a terminating zero will always be appended at the end anyway + */ +char* buff_addb (char* buff, char* buffEnd, const void* data, int len); + +/* tries to add a formatted string to a bounded buffer */ +char* buff_add (char* buff, char* buffEnd, const char* format, ... ); + +/* convenience macro used to define a bounded buffer, as well as + * a 'cursor' and 'end' variables all in one go. + * + * note: this doesn't place an initial terminating zero in the buffer, + * you need to use one of the buff_ functions for this. or simply + * do _cursor[0] = 0 manually. + */ +#define BUFF_DECL(_buff,_cursor,_end,_size) \ + char _buff[_size], *_cursor=_buff, *_end = _cursor + (_size) + +#endif /* _ADB_UTILS_H */ diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..0846992 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +rm -rf autom4te.cache +rm -f aclocal.m4 ltmain.sh + +touch README + +echo "Running aclocal..." ; aclocal $ACLOCAL_FLAGS || exit 1 +echo "Running autoheader..." ; autoheader || exit 1 +echo "Running autoconf..." ; autoconf || exit 1 +echo "Running libtoolize..." ; (libtoolize --copy --automake || glibtoolize --automake) || exit 1 +echo "Running automake..." ; automake --add-missing --copy --gnu || exit 1 + +if [ -z "$NOCONFIGURE" ]; then + ./configure "$@" +fi diff --git a/bionic/dlmalloc.c b/bionic/dlmalloc.c new file mode 100644 index 0000000..d30ec30 --- /dev/null +++ b/bionic/dlmalloc.c @@ -0,0 +1,5469 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 + * COPYRIGHT OWNER 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. + */ +/* + This is a version (aka dlmalloc) of malloc/free/realloc written by + Doug Lea and released to the public domain, as explained at + http://creativecommons.org/licenses/publicdomain. Send questions, + comments, complaints, performance data, etc to dl@cs.oswego.edu + +* Version 2.8.3 Thu Sep 22 11:16:15 2005 Doug Lea (dl at gee) + + Note: There may be an updated version of this malloc obtainable at + ftp://gee.cs.oswego.edu/pub/misc/malloc.c + Check before installing! + +* Quickstart + + This library is all in one file to simplify the most common usage: + ftp it, compile it (-O3), and link it into another program. All of + the compile-time options default to reasonable values for use on + most platforms. You might later want to step through various + compile-time and dynamic tuning options. + + For convenience, an include file for code using this malloc is at: + ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.3.h + You don't really need this .h file unless you call functions not + defined in your system include files. The .h file contains only the + excerpts from this file needed for using this malloc on ANSI C/C++ + systems, so long as you haven't changed compile-time options about + naming and tuning parameters. If you do, then you can create your + own malloc.h that does include all settings by cutting at the point + indicated below. Note that you may already by default be using a C + library containing a malloc that is based on some version of this + malloc (for example in linux). You might still want to use the one + in this file to customize settings or to avoid overheads associated + with library versions. + +* Vital statistics: + + Supported pointer/size_t representation: 4 or 8 bytes + size_t MUST be an unsigned type of the same width as + pointers. (If you are using an ancient system that declares + size_t as a signed type, or need it to be a different width + than pointers, you can use a previous release of this malloc + (e.g. 2.7.2) supporting these.) + + Alignment: 8 bytes (default) + This suffices for nearly all current machines and C compilers. + However, you can define MALLOC_ALIGNMENT to be wider than this + if necessary (up to 128bytes), at the expense of using more space. + + Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes) + 8 or 16 bytes (if 8byte sizes) + Each malloced chunk has a hidden word of overhead holding size + and status information, and additional cross-check word + if FOOTERS is defined. + + Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead) + 8-byte ptrs: 32 bytes (including overhead) + + Even a request for zero bytes (i.e., malloc(0)) returns a + pointer to something of the minimum allocatable size. + The maximum overhead wastage (i.e., number of extra bytes + allocated than were requested in malloc) is less than or equal + to the minimum size, except for requests >= mmap_threshold that + are serviced via mmap(), where the worst case wastage is about + 32 bytes plus the remainder from a system page (the minimal + mmap unit); typically 4096 or 8192 bytes. + + Security: static-safe; optionally more or less + The "security" of malloc refers to the ability of malicious + code to accentuate the effects of errors (for example, freeing + space that is not currently malloc'ed or overwriting past the + ends of chunks) in code that calls malloc. This malloc + guarantees not to modify any memory locations below the base of + heap, i.e., static variables, even in the presence of usage + errors. The routines additionally detect most improper frees + and reallocs. All this holds as long as the static bookkeeping + for malloc itself is not corrupted by some other means. This + is only one aspect of security -- these checks do not, and + cannot, detect all possible programming errors. + + If FOOTERS is defined nonzero, then each allocated chunk + carries an additional check word to verify that it was malloced + from its space. These check words are the same within each + execution of a program using malloc, but differ across + executions, so externally crafted fake chunks cannot be + freed. This improves security by rejecting frees/reallocs that + could corrupt heap memory, in addition to the checks preventing + writes to statics that are always on. This may further improve + security at the expense of time and space overhead. (Note that + FOOTERS may also be worth using with MSPACES.) + + By default detected errors cause the program to abort (calling + "abort()"). You can override this to instead proceed past + errors by defining PROCEED_ON_ERROR. In this case, a bad free + has no effect, and a malloc that encounters a bad address + caused by user overwrites will ignore the bad address by + dropping pointers and indices to all known memory. This may + be appropriate for programs that should continue if at all + possible in the face of programming errors, although they may + run out of memory because dropped memory is never reclaimed. + + If you don't like either of these options, you can define + CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything + else. And if if you are sure that your program using malloc has + no errors or vulnerabilities, you can define INSECURE to 1, + which might (or might not) provide a small performance improvement. + + Thread-safety: NOT thread-safe unless USE_LOCKS defined + When USE_LOCKS is defined, each public call to malloc, free, + etc is surrounded with either a pthread mutex or a win32 + spinlock (depending on WIN32). This is not especially fast, and + can be a major bottleneck. It is designed only to provide + minimal protection in concurrent environments, and to provide a + basis for extensions. If you are using malloc in a concurrent + program, consider instead using ptmalloc, which is derived from + a version of this malloc. (See http://www.malloc.de). + + System requirements: Any combination of MORECORE and/or MMAP/MUNMAP + This malloc can use unix sbrk or any emulation (invoked using + the CALL_MORECORE macro) and/or mmap/munmap or any emulation + (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system + memory. On most unix systems, it tends to work best if both + MORECORE and MMAP are enabled. On Win32, it uses emulations + based on VirtualAlloc. It also uses common C library functions + like memset. + + Compliance: I believe it is compliant with the Single Unix Specification + (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably + others as well. + +* Overview of algorithms + + This is not the fastest, most space-conserving, most portable, or + most tunable malloc ever written. However it is among the fastest + while also being among the most space-conserving, portable and + tunable. Consistent balance across these factors results in a good + general-purpose allocator for malloc-intensive programs. + + In most ways, this malloc is a best-fit allocator. Generally, it + chooses the best-fitting existing chunk for a request, with ties + broken in approximately least-recently-used order. (This strategy + normally maintains low fragmentation.) However, for requests less + than 256bytes, it deviates from best-fit when there is not an + exactly fitting available chunk by preferring to use space adjacent + to that used for the previous small request, as well as by breaking + ties in approximately most-recently-used order. (These enhance + locality of series of small allocations.) And for very large requests + (>= 256Kb by default), it relies on system memory mapping + facilities, if supported. (This helps avoid carrying around and + possibly fragmenting memory used only for large chunks.) + + All operations (except malloc_stats and mallinfo) have execution + times that are bounded by a constant factor of the number of bits in + a size_t, not counting any clearing in calloc or copying in realloc, + or actions surrounding MORECORE and MMAP that have times + proportional to the number of non-contiguous regions returned by + system allocation routines, which is often just 1. + + The implementation is not very modular and seriously overuses + macros. Perhaps someday all C compilers will do as good a job + inlining modular code as can now be done by brute-force expansion, + but now, enough of them seem not to. + + Some compilers issue a lot of warnings about code that is + dead/unreachable only on some platforms, and also about intentional + uses of negation on unsigned types. All known cases of each can be + ignored. + + For a longer but out of date high-level description, see + http://gee.cs.oswego.edu/dl/html/malloc.html + +* MSPACES + If MSPACES is defined, then in addition to malloc, free, etc., + this file also defines mspace_malloc, mspace_free, etc. These + are versions of malloc routines that take an "mspace" argument + obtained using create_mspace, to control all internal bookkeeping. + If ONLY_MSPACES is defined, only these versions are compiled. + So if you would like to use this allocator for only some allocations, + and your system malloc for others, you can compile with + ONLY_MSPACES and then do something like... + static mspace mymspace = create_mspace(0,0); // for example + #define mymalloc(bytes) mspace_malloc(mymspace, bytes) + + (Note: If you only need one instance of an mspace, you can instead + use "USE_DL_PREFIX" to relabel the global malloc.) + + You can similarly create thread-local allocators by storing + mspaces as thread-locals. For example: + static __thread mspace tlms = 0; + void* tlmalloc(size_t bytes) { + if (tlms == 0) tlms = create_mspace(0, 0); + return mspace_malloc(tlms, bytes); + } + void tlfree(void* mem) { mspace_free(tlms, mem); } + + Unless FOOTERS is defined, each mspace is completely independent. + You cannot allocate from one and free to another (although + conformance is only weakly checked, so usage errors are not always + caught). If FOOTERS is defined, then each chunk carries around a tag + indicating its originating mspace, and frees are directed to their + originating spaces. + + ------------------------- Compile-time options --------------------------- + +Be careful in setting #define values for numerical constants of type +size_t. On some systems, literal values are not automatically extended +to size_t precision unless they are explicitly casted. + +WIN32 default: defined if _WIN32 defined + Defining WIN32 sets up defaults for MS environment and compilers. + Otherwise defaults are for unix. + +MALLOC_ALIGNMENT default: (size_t)8 + Controls the minimum alignment for malloc'ed chunks. It must be a + power of two and at least 8, even on machines for which smaller + alignments would suffice. It may be defined as larger than this + though. Note however that code and data structures are optimized for + the case of 8-byte alignment. + +MSPACES default: 0 (false) + If true, compile in support for independent allocation spaces. + This is only supported if HAVE_MMAP is true. + +ONLY_MSPACES default: 0 (false) + If true, only compile in mspace versions, not regular versions. + +USE_LOCKS default: 0 (false) + Causes each call to each public routine to be surrounded with + pthread or WIN32 mutex lock/unlock. (If set true, this can be + overridden on a per-mspace basis for mspace versions.) + +FOOTERS default: 0 + If true, provide extra checking and dispatching by placing + information in the footers of allocated chunks. This adds + space and time overhead. + +INSECURE default: 0 + If true, omit checks for usage errors and heap space overwrites. + +USE_DL_PREFIX default: NOT defined + Causes compiler to prefix all public routines with the string 'dl'. + This can be useful when you only want to use this malloc in one part + of a program, using your regular system malloc elsewhere. + +ABORT default: defined as abort() + Defines how to abort on failed checks. On most systems, a failed + check cannot die with an "assert" or even print an informative + message, because the underlying print routines in turn call malloc, + which will fail again. Generally, the best policy is to simply call + abort(). It's not very useful to do more than this because many + errors due to overwriting will show up as address faults (null, odd + addresses etc) rather than malloc-triggered checks, so will also + abort. Also, most compilers know that abort() does not return, so + can better optimize code conditionally calling it. + +PROCEED_ON_ERROR default: defined as 0 (false) + Controls whether detected bad addresses cause them to bypassed + rather than aborting. If set, detected bad arguments to free and + realloc are ignored. And all bookkeeping information is zeroed out + upon a detected overwrite of freed heap space, thus losing the + ability to ever return it from malloc again, but enabling the + application to proceed. If PROCEED_ON_ERROR is defined, the + static variable malloc_corruption_error_count is compiled in + and can be examined to see if errors have occurred. This option + generates slower code than the default abort policy. + +DEBUG default: NOT defined + The DEBUG setting is mainly intended for people trying to modify + this code or diagnose problems when porting to new platforms. + However, it may also be able to better isolate user errors than just + using runtime checks. The assertions in the check routines spell + out in more detail the assumptions and invariants underlying the + algorithms. The checking is fairly extensive, and will slow down + execution noticeably. Calling malloc_stats or mallinfo with DEBUG + set will attempt to check every non-mmapped allocated and free chunk + in the course of computing the summaries. + +ABORT_ON_ASSERT_FAILURE default: defined as 1 (true) + Debugging assertion failures can be nearly impossible if your + version of the assert macro causes malloc to be called, which will + lead to a cascade of further failures, blowing the runtime stack. + ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(), + which will usually make debugging easier. + +MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32 + The action to take before "return 0" when malloc fails to be able to + return memory because there is none available. + +HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES + True if this system supports sbrk or an emulation of it. + +MORECORE default: sbrk + The name of the sbrk-style system routine to call to obtain more + memory. See below for guidance on writing custom MORECORE + functions. The type of the argument to sbrk/MORECORE varies across + systems. It cannot be size_t, because it supports negative + arguments, so it is normally the signed type of the same width as + size_t (sometimes declared as "intptr_t"). It doesn't much matter + though. Internally, we only call it with arguments less than half + the max value of a size_t, which should work across all reasonable + possibilities, although sometimes generating compiler warnings. See + near the end of this file for guidelines for creating a custom + version of MORECORE. + +MORECORE_CONTIGUOUS default: 1 (true) + If true, take advantage of fact that consecutive calls to MORECORE + with positive arguments always return contiguous increasing + addresses. This is true of unix sbrk. It does not hurt too much to + set it true anyway, since malloc copes with non-contiguities. + Setting it false when definitely non-contiguous saves time + and possibly wasted space it would take to discover this though. + +MORECORE_CANNOT_TRIM default: NOT defined + True if MORECORE cannot release space back to the system when given + negative arguments. This is generally necessary only if you are + using a hand-crafted MORECORE function that cannot handle negative + arguments. + +HAVE_MMAP default: 1 (true) + True if this system supports mmap or an emulation of it. If so, and + HAVE_MORECORE is not true, MMAP is used for all system + allocation. If set and HAVE_MORECORE is true as well, MMAP is + primarily used to directly allocate very large blocks. It is also + used as a backup strategy in cases where MORECORE fails to provide + space from system. Note: A single call to MUNMAP is assumed to be + able to unmap memory that may have be allocated using multiple calls + to MMAP, so long as they are adjacent. + +HAVE_MREMAP default: 1 on linux, else 0 + If true realloc() uses mremap() to re-allocate large blocks and + extend or shrink allocation spaces. + +MMAP_CLEARS default: 1 on unix + True if mmap clears memory so calloc doesn't need to. This is true + for standard unix mmap using /dev/zero. + +USE_BUILTIN_FFS default: 0 (i.e., not used) + Causes malloc to use the builtin ffs() function to compute indices. + Some compilers may recognize and intrinsify ffs to be faster than the + supplied C version. Also, the case of x86 using gcc is special-cased + to an asm instruction, so is already as fast as it can be, and so + this setting has no effect. (On most x86s, the asm version is only + slightly faster than the C version.) + +malloc_getpagesize default: derive from system includes, or 4096. + The system page size. To the extent possible, this malloc manages + memory from the system in page-size units. This may be (and + usually is) a function rather than a constant. This is ignored + if WIN32, where page size is determined using getSystemInfo during + initialization. + +USE_DEV_RANDOM default: 0 (i.e., not used) + Causes malloc to use /dev/random to initialize secure magic seed for + stamping footers. Otherwise, the current time is used. + +NO_MALLINFO default: 0 + If defined, don't compile "mallinfo". This can be a simple way + of dealing with mismatches between system declarations and + those in this file. + +MALLINFO_FIELD_TYPE default: size_t + The type of the fields in the mallinfo struct. This was originally + defined as "int" in SVID etc, but is more usefully defined as + size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set + +REALLOC_ZERO_BYTES_FREES default: not defined + This should be set if a call to realloc with zero bytes should + be the same as a call to free. Some people think it should. Otherwise, + since this malloc returns a unique pointer for malloc(0), so does + realloc(p, 0). + +LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H +LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H +LACKS_STDLIB_H default: NOT defined unless on WIN32 + Define these if your system does not have these header files. + You might need to manually insert some of the declarations they provide. + +DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, + system_info.dwAllocationGranularity in WIN32, + otherwise 64K. + Also settable using mallopt(M_GRANULARITY, x) + The unit for allocating and deallocating memory from the system. On + most systems with contiguous MORECORE, there is no reason to + make this more than a page. However, systems with MMAP tend to + either require or encourage larger granularities. You can increase + this value to prevent system allocation functions to be called so + often, especially if they are slow. The value must be at least one + page and must be a power of two. Setting to 0 causes initialization + to either page size or win32 region size. (Note: In previous + versions of malloc, the equivalent of this option was called + "TOP_PAD") + +DEFAULT_TRIM_THRESHOLD default: 2MB + Also settable using mallopt(M_TRIM_THRESHOLD, x) + The maximum amount of unused top-most memory to keep before + releasing via malloc_trim in free(). Automatic trimming is mainly + useful in long-lived programs using contiguous MORECORE. Because + trimming via sbrk can be slow on some systems, and can sometimes be + wasteful (in cases where programs immediately afterward allocate + more large chunks) the value should be high enough so that your + overall system performance would improve by releasing this much + memory. As a rough guide, you might set to a value close to the + average size of a process (program) running on your system. + Releasing this much memory would allow such a process to run in + memory. Generally, it is worth tuning trim thresholds when a + program undergoes phases where several large chunks are allocated + and released in ways that can reuse each other's storage, perhaps + mixed with phases where there are no such chunks at all. The trim + value must be greater than page size to have any useful effect. To + disable trimming completely, you can set to MAX_SIZE_T. Note that the trick + some people use of mallocing a huge space and then freeing it at + program startup, in an attempt to reserve system memory, doesn't + have the intended effect under automatic trimming, since that memory + will immediately be returned to the system. + +DEFAULT_MMAP_THRESHOLD default: 256K + Also settable using mallopt(M_MMAP_THRESHOLD, x) + The request size threshold for using MMAP to directly service a + request. Requests of at least this size that cannot be allocated + using already-existing space will be serviced via mmap. (If enough + normal freed space already exists it is used instead.) Using mmap + segregates relatively large chunks of memory so that they can be + individually obtained and released from the host system. A request + serviced through mmap is never reused by any other request (at least + not directly; the system may just so happen to remap successive + requests to the same locations). Segregating space in this way has + the benefits that: Mmapped space can always be individually released + back to the system, which helps keep the system level memory demands + of a long-lived program low. Also, mapped memory doesn't become + `locked' between other chunks, as can happen with normally allocated + chunks, which means that even trimming via malloc_trim would not + release them. However, it has the disadvantage that the space + cannot be reclaimed, consolidated, and then used to service later + requests, as happens with normal chunks. The advantages of mmap + nearly always outweigh disadvantages for "large" chunks, but the + value of "large" may vary across systems. The default is an + empirically derived value that works well in most systems. You can + disable mmap by setting to MAX_SIZE_T. + +*/ + +#ifdef ANDROID +#define USE_BUILTIN_FFS 1 +#ifdef __arm__ +#include <machine/cpu-features.h> +#endif +#endif /* ANDROID */ + +#ifndef WIN32 +#ifdef _WIN32 +#define WIN32 1 +#endif /* _WIN32 */ +#endif /* WIN32 */ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#define HAVE_MMAP 1 +#define HAVE_MORECORE 0 +#define LACKS_UNISTD_H +#define LACKS_SYS_PARAM_H +#define LACKS_SYS_MMAN_H +#define LACKS_STRING_H +#define LACKS_STRINGS_H +#define LACKS_SYS_TYPES_H +#define LACKS_ERRNO_H +#define MALLOC_FAILURE_ACTION +#define MMAP_CLEARS 0 /* WINCE and some others apparently don't clear */ +#endif /* WIN32 */ + +#if defined(DARWIN) || defined(_DARWIN) +/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */ +#ifndef HAVE_MORECORE +#define HAVE_MORECORE 0 +#define HAVE_MMAP 1 +#endif /* HAVE_MORECORE */ +#endif /* DARWIN */ + +#ifndef LACKS_SYS_TYPES_H +#include <sys/types.h> /* For size_t */ +#endif /* LACKS_SYS_TYPES_H */ + +/* The maximum possible size_t value has all bits set */ +#define MAX_SIZE_T (~(size_t)0) + +#ifndef ONLY_MSPACES +#define ONLY_MSPACES 0 +#endif /* ONLY_MSPACES */ +#ifndef MSPACES +#if ONLY_MSPACES +#define MSPACES 1 +#else /* ONLY_MSPACES */ +#define MSPACES 0 +#endif /* ONLY_MSPACES */ +#endif /* MSPACES */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)8U) +#endif /* MALLOC_ALIGNMENT */ +#ifndef FOOTERS +#define FOOTERS 0 +#endif /* FOOTERS */ +#ifndef USE_MAX_ALLOWED_FOOTPRINT +#define USE_MAX_ALLOWED_FOOTPRINT 0 +#endif +#ifndef ABORT +#define ABORT abort() +#endif /* ABORT */ +#ifndef ABORT_ON_ASSERT_FAILURE +#define ABORT_ON_ASSERT_FAILURE 1 +#endif /* ABORT_ON_ASSERT_FAILURE */ +#ifndef PROCEED_ON_ERROR +#define PROCEED_ON_ERROR 0 +#endif /* PROCEED_ON_ERROR */ +#ifndef USE_LOCKS +#define USE_LOCKS 0 +#endif /* USE_LOCKS */ +#ifndef INSECURE +#define INSECURE 0 +#endif /* INSECURE */ +#ifndef HAVE_MMAP +#define HAVE_MMAP 1 +#endif /* HAVE_MMAP */ +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 1 +#endif /* MMAP_CLEARS */ +#ifndef HAVE_MREMAP +#ifdef linux +#define HAVE_MREMAP 1 +#else /* linux */ +#define HAVE_MREMAP 0 +#endif /* linux */ +#endif /* HAVE_MREMAP */ +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION errno = ENOMEM; +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef HAVE_MORECORE +#if ONLY_MSPACES +#define HAVE_MORECORE 0 +#else /* ONLY_MSPACES */ +#define HAVE_MORECORE 1 +#endif /* ONLY_MSPACES */ +#endif /* HAVE_MORECORE */ +#if !HAVE_MORECORE +#define MORECORE_CONTIGUOUS 0 +#else /* !HAVE_MORECORE */ +#ifndef MORECORE +#define MORECORE sbrk +#endif /* MORECORE */ +#ifndef MORECORE_CONTIGUOUS +#define MORECORE_CONTIGUOUS 1 +#endif /* MORECORE_CONTIGUOUS */ +#endif /* HAVE_MORECORE */ +#ifndef DEFAULT_GRANULARITY +#if MORECORE_CONTIGUOUS +#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ +#else /* MORECORE_CONTIGUOUS */ +#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) +#endif /* MORECORE_CONTIGUOUS */ +#endif /* DEFAULT_GRANULARITY */ +#ifndef DEFAULT_TRIM_THRESHOLD +#ifndef MORECORE_CANNOT_TRIM +#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) +#else /* MORECORE_CANNOT_TRIM */ +#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T +#endif /* MORECORE_CANNOT_TRIM */ +#endif /* DEFAULT_TRIM_THRESHOLD */ +#ifndef DEFAULT_MMAP_THRESHOLD +#if HAVE_MMAP +#define DEFAULT_MMAP_THRESHOLD ((size_t)64U * (size_t)1024U) +#else /* HAVE_MMAP */ +#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* DEFAULT_MMAP_THRESHOLD */ +#ifndef USE_BUILTIN_FFS +#define USE_BUILTIN_FFS 0 +#endif /* USE_BUILTIN_FFS */ +#ifndef USE_DEV_RANDOM +#define USE_DEV_RANDOM 0 +#endif /* USE_DEV_RANDOM */ +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif /* NO_MALLINFO */ +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ + +/* + mallopt tuning options. SVID/XPG defines four standard parameter + numbers for mallopt, normally defined in malloc.h. None of these + are used in this malloc, so setting them has no effect. But this + malloc does support the following options. +*/ + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + +/* ------------------------ Mallinfo declarations ------------------------ */ + +#if !NO_MALLINFO +/* + This version of malloc supports the standard SVID/XPG mallinfo + routine that returns a struct containing usage properties and + statistics. It should work on any system that has a + /usr/include/malloc.h defining struct mallinfo. The main + declaration needed is the mallinfo struct that is returned (by-copy) + by mallinfo(). The malloinfo struct contains a bunch of fields that + are not even meaningful in this version of malloc. These fields are + are instead filled by mallinfo() with other numbers that might be of + interest. + + HAVE_USR_INCLUDE_MALLOC_H should be set if you have a + /usr/include/malloc.h file that includes a declaration of struct + mallinfo. If so, it is included; else a compliant version is + declared below. These must be precisely the same for mallinfo() to + work. The original SVID version of this struct, defined on most + systems with mallinfo, declares all fields as ints. But some others + define as unsigned long. If your system defines the fields using a + type of different width than listed here, you MUST #include your + system version and #define HAVE_USR_INCLUDE_MALLOC_H. +*/ + +/* #define HAVE_USR_INCLUDE_MALLOC_H */ + +#if !ANDROID +#ifdef HAVE_USR_INCLUDE_MALLOC_H +#include "/usr/include/malloc.h" +#else /* HAVE_USR_INCLUDE_MALLOC_H */ + +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; + +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ +#endif /* NO_MALLINFO */ +#endif /* ANDROID */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#if !ONLY_MSPACES + +/* ------------------- Declarations of public routines ------------------- */ + +/* Check an additional macro for the five primary functions */ +#ifndef USE_DL_PREFIX +#define dlcalloc calloc +#define dlfree free +#define dlmalloc malloc +#define dlmemalign memalign +#define dlrealloc realloc +#endif + +#ifndef USE_DL_PREFIX +#define dlvalloc valloc +#define dlpvalloc pvalloc +#define dlmallinfo mallinfo +#define dlmallopt mallopt +#define dlmalloc_trim malloc_trim +#define dlmalloc_walk_free_pages \ + malloc_walk_free_pages +#define dlmalloc_walk_heap \ + malloc_walk_heap +#define dlmalloc_stats malloc_stats +#define dlmalloc_usable_size malloc_usable_size +#define dlmalloc_footprint malloc_footprint +#define dlmalloc_max_allowed_footprint \ + malloc_max_allowed_footprint +#define dlmalloc_set_max_allowed_footprint \ + malloc_set_max_allowed_footprint +#define dlmalloc_max_footprint malloc_max_footprint +#define dlindependent_calloc independent_calloc +#define dlindependent_comalloc independent_comalloc +#endif /* USE_DL_PREFIX */ + + +/* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. +*/ +void* dlmalloc(size_t); + +/* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cause the current program to abort. +*/ +void dlfree(void*); + +/* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. +*/ +void* dlcalloc(size_t, size_t); + +/* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. +*/ + +void* dlrealloc(void*, size_t); + +/* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. +*/ +void* dlmemalign(size_t, size_t); + +/* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. +*/ +void* dlvalloc(size_t); + +/* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt. See below for details. Briefly, supported + parameters are as follows (listed defaults are for "typical" + configurations). + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (MAX_SIZE_T disables) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) +*/ +int dlmallopt(int, int); + +/* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. +*/ +size_t dlmalloc_footprint(void); + +#if USE_MAX_ALLOWED_FOOTPRINT +/* + malloc_max_allowed_footprint(); + Returns the number of bytes that the heap is allowed to obtain + from the system. malloc_footprint() should always return a + size less than or equal to max_allowed_footprint, unless the + max_allowed_footprint was set to a value smaller than the + footprint at the time. +*/ +size_t dlmalloc_max_allowed_footprint(); + +/* + malloc_set_max_allowed_footprint(); + Set the maximum number of bytes that the heap is allowed to + obtain from the system. The size will be rounded up to a whole + page, and the rounded number will be returned from future calls + to malloc_max_allowed_footprint(). If the new max_allowed_footprint + is larger than the current footprint, the heap will never grow + larger than max_allowed_footprint. If the new max_allowed_footprint + is smaller than the current footprint, the heap will not grow + further. + + TODO: try to force the heap to give up memory in the shrink case, + and update this comment once that happens. +*/ +void dlmalloc_set_max_allowed_footprint(size_t bytes); +#endif /* USE_MAX_ALLOWED_FOOTPRINT */ + +/* + malloc_max_footprint(); + Returns the maximum number of bytes obtained from the system. This + value will be greater than current footprint if deallocated space + has been reclaimed by the system. The peak number of bytes allocated + by malloc, realloc etc., is less than this value. Unlike mallinfo, + this function returns only a precomputed result, so can be called + frequently to monitor memory consumption. Even if locks are + otherwise defined, this function does not use them, so results might + not be up to date. +*/ +size_t dlmalloc_max_footprint(void); + +#if !NO_MALLINFO +/* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. +*/ +struct mallinfo dlmallinfo(void); +#endif /* NO_MALLINFO */ + +/* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use regular calloc and assign pointers into this + space to represent elements. (In this case though, you cannot + independently free elements.) + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } +*/ +void** dlindependent_calloc(size_t, size_t, void**); + +/* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use a single regular malloc, and assign pointers at + particular offsets in the aggregate space. (In this case though, you + cannot independently free elements.) + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. +*/ +void** dlindependent_comalloc(size_t, size_t*, void**); + + +/* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ +void* dlpvalloc(size_t); + +/* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. +*/ +int dlmalloc_trim(size_t); + +/* + malloc_walk_free_pages(handler, harg) + + Calls the provided handler on each free region in the heap. The + memory between start and end are guaranteed not to contain any + important data, so the handler is free to alter the contents + in any way. This can be used to advise the OS that large free + regions may be swapped out. + + The value in harg will be passed to each call of the handler. + */ +void dlmalloc_walk_free_pages(void(*)(void*, void*, void*), void*); + +/* + malloc_walk_heap(handler, harg) + + Calls the provided handler on each object or free region in the + heap. The handler will receive the chunk pointer and length, the + object pointer and length, and the value in harg on each call. + */ +void dlmalloc_walk_heap(void(*)(const void*, size_t, + const void*, size_t, void*), + void*); + +/* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); +*/ +size_t dlmalloc_usable_size(void*); + +/* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. +*/ +void dlmalloc_stats(void); + +#endif /* ONLY_MSPACES */ + +#if MSPACES + +/* + mspace is an opaque type representing an independent + region of space that supports mspace_malloc, etc. +*/ +typedef void* mspace; + +/* + create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). +*/ +mspace create_mspace(size_t capacity, int locked); + +/* + destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. +*/ +size_t destroy_mspace(mspace msp); + +/* + create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. +*/ +mspace create_mspace_with_base(void* base, size_t capacity, int locked); + +/* + mspace_malloc behaves as malloc, but operates within + the given space. +*/ +void* mspace_malloc(mspace msp, size_t bytes); + +/* + mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_free is not actually needed. + free may be called instead of mspace_free because freed chunks from + any space are handled by their originating spaces. +*/ +void mspace_free(mspace msp, void* mem); + +/* + mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_realloc is not actually + needed. realloc may be called instead of mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. +*/ +void* mspace_realloc(mspace msp, void* mem, size_t newsize); + +#if ANDROID /* Added for Android, not part of dlmalloc as released */ +/* + mspace_merge_objects will merge allocated memory mema and memb + together, provided memb immediately follows mema. It is roughly as + if memb has been freed and mema has been realloced to a larger size. + On successfully merging, mema will be returned. If either argument + is null or memb does not immediately follow mema, null will be + returned. + + Both mema and memb should have been previously allocated using + malloc or a related routine such as realloc. If either mema or memb + was not malloced or was previously freed, the result is undefined, + but like mspace_free, the default is to abort the program. +*/ +void* mspace_merge_objects(mspace msp, void* mema, void* memb); +#endif + +/* + mspace_calloc behaves as calloc, but operates within + the given space. +*/ +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + +/* + mspace_memalign behaves as memalign, but operates within + the given space. +*/ +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes); + +/* + mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. +*/ +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + +/* + mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. +*/ +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + +/* + mspace_footprint() returns the number of bytes obtained from the + system for this space. +*/ +size_t mspace_footprint(mspace msp); + +/* + mspace_max_footprint() returns the peak number of bytes obtained from the + system for this space. +*/ +size_t mspace_max_footprint(mspace msp); + + +#if !NO_MALLINFO +/* + mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. +*/ +struct mallinfo mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + +/* + mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. +*/ +void mspace_malloc_stats(mspace msp); + +/* + mspace_trim behaves as malloc_trim, but + operates within the given space. +*/ +int mspace_trim(mspace msp, size_t pad); + +/* + An alias for mallopt. +*/ +int mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif /* __cplusplus */ + +/* + ======================================================================== + To make a fully customizable malloc.h header file, cut everything + above this line, put into file malloc.h, edit to suit, and #include it + on the next line, as well as in programs that use this malloc. + ======================================================================== +*/ + +/* #include "malloc.h" */ + +/*------------------------------ internal #includes ---------------------- */ + +#ifdef WIN32 +#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ +#endif /* WIN32 */ + +#include <stdio.h> /* for printing in malloc_stats */ + +#ifndef LACKS_ERRNO_H +#include <errno.h> /* for MALLOC_FAILURE_ACTION */ +#endif /* LACKS_ERRNO_H */ +#if FOOTERS +#include <time.h> /* for magic initialization */ +#endif /* FOOTERS */ +#ifndef LACKS_STDLIB_H +#include <stdlib.h> /* for abort() */ +#endif /* LACKS_STDLIB_H */ +#ifdef DEBUG +#if ABORT_ON_ASSERT_FAILURE +#define assert(x) if(!(x)) ABORT +#else /* ABORT_ON_ASSERT_FAILURE */ +#include <assert.h> +#endif /* ABORT_ON_ASSERT_FAILURE */ +#else /* DEBUG */ +#define assert(x) +#endif /* DEBUG */ +#ifndef LACKS_STRING_H +#include <string.h> /* for memset etc */ +#endif /* LACKS_STRING_H */ +#if USE_BUILTIN_FFS +#ifndef LACKS_STRINGS_H +#include <strings.h> /* for ffs */ +#endif /* LACKS_STRINGS_H */ +#endif /* USE_BUILTIN_FFS */ +#if HAVE_MMAP +#ifndef LACKS_SYS_MMAN_H +#include <sys/mman.h> /* for mmap */ +#endif /* LACKS_SYS_MMAN_H */ +#ifndef LACKS_FCNTL_H +#include <fcntl.h> +#endif /* LACKS_FCNTL_H */ +#endif /* HAVE_MMAP */ +#if HAVE_MORECORE +#ifndef LACKS_UNISTD_H +#include <unistd.h> /* for sbrk */ +#else /* LACKS_UNISTD_H */ +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +extern void* sbrk(ptrdiff_t); +#endif /* FreeBSD etc */ +#endif /* LACKS_UNISTD_H */ +#endif /* HAVE_MMAP */ + +#ifndef WIN32 +#ifndef malloc_getpagesize +# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ +# ifndef _SC_PAGE_SIZE +# define _SC_PAGE_SIZE _SC_PAGESIZE +# endif +# endif +# ifdef _SC_PAGE_SIZE +# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) +# else +# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) + extern size_t getpagesize(); +# define malloc_getpagesize getpagesize() +# else +# ifdef WIN32 /* use supplied emulation of getpagesize */ +# define malloc_getpagesize getpagesize() +# else +# ifndef LACKS_SYS_PARAM_H +# include <sys/param.h> +# endif +# ifdef EXEC_PAGESIZE +# define malloc_getpagesize EXEC_PAGESIZE +# else +# ifdef NBPG +# ifndef CLSIZE +# define malloc_getpagesize NBPG +# else +# define malloc_getpagesize (NBPG * CLSIZE) +# endif +# else +# ifdef NBPC +# define malloc_getpagesize NBPC +# else +# ifdef PAGESIZE +# define malloc_getpagesize PAGESIZE +# else /* just guess */ +# define malloc_getpagesize ((size_t)4096U) +# endif +# endif +# endif +# endif +# endif +# endif +# endif +#endif +#endif + +/* ------------------- size_t and alignment properties -------------------- */ + +/* The byte and bit size of a size_t */ +#define SIZE_T_SIZE (sizeof(size_t)) +#define SIZE_T_BITSIZE (sizeof(size_t) << 3) + +/* Some constants coerced to size_t */ +/* Annoying but necessary to avoid errors on some plaftorms */ +#define SIZE_T_ZERO ((size_t)0) +#define SIZE_T_ONE ((size_t)1) +#define SIZE_T_TWO ((size_t)2) +#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1) +#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2) +#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES) +#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) + +/* The bit mask value corresponding to MALLOC_ALIGNMENT */ +#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) + +/* True if address a has acceptable alignment */ +#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0) + +/* the number of bytes to offset an address to align it */ +#define align_offset(A)\ + ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\ + ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK)) + +/* -------------------------- MMAP preliminaries ------------------------- */ + +/* + If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and + checks to fail so compiler optimizer can delete code rather than + using so many "#if"s. +*/ + + +/* MORECORE and MMAP must return MFAIL on failure */ +#define MFAIL ((void*)(MAX_SIZE_T)) +#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ + +#if !HAVE_MMAP +#define IS_MMAPPED_BIT (SIZE_T_ZERO) +#define USE_MMAP_BIT (SIZE_T_ZERO) +#define CALL_MMAP(s) MFAIL +#define CALL_MUNMAP(a, s) (-1) +#define DIRECT_MMAP(s) MFAIL + +#else /* HAVE_MMAP */ +#define IS_MMAPPED_BIT (SIZE_T_ONE) +#define USE_MMAP_BIT (SIZE_T_ONE) + +#ifndef WIN32 +#define CALL_MUNMAP(a, s) munmap((a), (s)) +#define MMAP_PROT (PROT_READ|PROT_WRITE) +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +#define MAP_ANONYMOUS MAP_ANON +#endif /* MAP_ANON */ +#ifdef MAP_ANONYMOUS +#define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) +#define CALL_MMAP(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0) +#else /* MAP_ANONYMOUS */ +/* + Nearly all versions of mmap support MAP_ANONYMOUS, so the following + is unlikely to be needed, but is supplied just in case. +*/ +#define MMAP_FLAGS (MAP_PRIVATE) +static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ +#define CALL_MMAP(s) ((dev_zero_fd < 0) ? \ + (dev_zero_fd = open("/dev/zero", O_RDWR), \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) +#endif /* MAP_ANONYMOUS */ + +#define DIRECT_MMAP(s) CALL_MMAP(s) +#else /* WIN32 */ + +/* Win32 MMAP via VirtualAlloc */ +static void* win32mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ +static void* win32direct_mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, + PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* This function supports releasing coalesed segments */ +static int win32munmap(void* ptr, size_t size) { + MEMORY_BASIC_INFORMATION minfo; + char* cptr = ptr; + while (size) { + if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) + return -1; + if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || + minfo.State != MEM_COMMIT || minfo.RegionSize > size) + return -1; + if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) + return -1; + cptr += minfo.RegionSize; + size -= minfo.RegionSize; + } + return 0; +} + +#define CALL_MMAP(s) win32mmap(s) +#define CALL_MUNMAP(a, s) win32munmap((a), (s)) +#define DIRECT_MMAP(s) win32direct_mmap(s) +#endif /* WIN32 */ +#endif /* HAVE_MMAP */ + +#if HAVE_MMAP && HAVE_MREMAP +#define CALL_MREMAP(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) +#else /* HAVE_MMAP && HAVE_MREMAP */ +#define CALL_MREMAP(addr, osz, nsz, mv) MFAIL +#endif /* HAVE_MMAP && HAVE_MREMAP */ + +#if HAVE_MORECORE +#define CALL_MORECORE(S) MORECORE(S) +#else /* HAVE_MORECORE */ +#define CALL_MORECORE(S) MFAIL +#endif /* HAVE_MORECORE */ + +/* mstate bit set if continguous morecore disabled or failed */ +#define USE_NONCONTIGUOUS_BIT (4U) + +/* segment bit set in create_mspace_with_base */ +#define EXTERN_BIT (8U) + + +/* --------------------------- Lock preliminaries ------------------------ */ + +#if USE_LOCKS + +/* + When locks are defined, there are up to two global locks: + + * If HAVE_MORECORE, morecore_mutex protects sequences of calls to + MORECORE. In many cases sys_alloc requires two calls, that should + not be interleaved with calls by other threads. This does not + protect against direct calls to MORECORE by other threads not + using this lock, so there is still code to cope the best we can on + interference. + + * magic_init_mutex ensures that mparams.magic and other + unique mparams values are initialized only once. +*/ + +#ifndef WIN32 +/* By default use posix locks */ +#include <pthread.h> +#define MLOCK_T pthread_mutex_t +#define INITIAL_LOCK(l) pthread_mutex_init(l, NULL) +#define ACQUIRE_LOCK(l) pthread_mutex_lock(l) +#define RELEASE_LOCK(l) pthread_mutex_unlock(l) + +#if HAVE_MORECORE +static MLOCK_T morecore_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif /* HAVE_MORECORE */ + +static MLOCK_T magic_init_mutex = PTHREAD_MUTEX_INITIALIZER; + +#else /* WIN32 */ +/* + Because lock-protected regions have bounded times, and there + are no recursive lock calls, we can use simple spinlocks. +*/ + +#define MLOCK_T long +static int win32_acquire_lock (MLOCK_T *sl) { + for (;;) { +#ifdef InterlockedCompareExchangePointer + if (!InterlockedCompareExchange(sl, 1, 0)) + return 0; +#else /* Use older void* version */ + if (!InterlockedCompareExchange((void**)sl, (void*)1, (void*)0)) + return 0; +#endif /* InterlockedCompareExchangePointer */ + Sleep (0); + } +} + +static void win32_release_lock (MLOCK_T *sl) { + InterlockedExchange (sl, 0); +} + +#define INITIAL_LOCK(l) *(l)=0 +#define ACQUIRE_LOCK(l) win32_acquire_lock(l) +#define RELEASE_LOCK(l) win32_release_lock(l) +#if HAVE_MORECORE +static MLOCK_T morecore_mutex; +#endif /* HAVE_MORECORE */ +static MLOCK_T magic_init_mutex; +#endif /* WIN32 */ + +#define USE_LOCK_BIT (2U) +#else /* USE_LOCKS */ +#define USE_LOCK_BIT (0U) +#define INITIAL_LOCK(l) +#endif /* USE_LOCKS */ + +#if USE_LOCKS && HAVE_MORECORE +#define ACQUIRE_MORECORE_LOCK() ACQUIRE_LOCK(&morecore_mutex); +#define RELEASE_MORECORE_LOCK() RELEASE_LOCK(&morecore_mutex); +#else /* USE_LOCKS && HAVE_MORECORE */ +#define ACQUIRE_MORECORE_LOCK() +#define RELEASE_MORECORE_LOCK() +#endif /* USE_LOCKS && HAVE_MORECORE */ + +#if USE_LOCKS +#define ACQUIRE_MAGIC_INIT_LOCK() ACQUIRE_LOCK(&magic_init_mutex); +#define RELEASE_MAGIC_INIT_LOCK() RELEASE_LOCK(&magic_init_mutex); +#else /* USE_LOCKS */ +#define ACQUIRE_MAGIC_INIT_LOCK() +#define RELEASE_MAGIC_INIT_LOCK() +#endif /* USE_LOCKS */ + + +/* ----------------------- Chunk representations ------------------------ */ + +/* + (The following includes lightly edited explanations by Colin Plumb.) + + The malloc_chunk declaration below is misleading (but accurate and + necessary). It declares a "view" into memory allowing access to + necessary fields at known offsets from a given base. + + Chunks of memory are maintained using a `boundary tag' method as + originally described by Knuth. (See the paper by Paul Wilson + ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such + techniques.) Sizes of free chunks are stored both in the front of + each chunk and at the end. This makes consolidating fragmented + chunks into bigger chunks fast. The head fields also hold bits + representing whether chunks are free or in use. + + Here are some pictures to make it clearer. They are "exploded" to + show that the state of a chunk can be thought of as extending from + the high 31 bits of the head field of its header through the + prev_foot and PINUSE_BIT bit of the following chunk header. + + A chunk that's in use looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk (if P = 1) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 1| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + +- -+ + | | + +- -+ + | : + +- size - sizeof(size_t) available payload bytes -+ + : | + chunk-> +- -+ + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| + | Size of next chunk (may or may not be in use) | +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + And if it's free, it looks like this: + + chunk-> +- -+ + | User payload (must be in use, or we would have merged!) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 0| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Prev pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- size - sizeof(struct chunk) unused bytes -+ + : | + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| + | Size of next chunk (must be in use, or we would have merged)| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- User payload -+ + : | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| + +-+ + Note that since we always merge adjacent free chunks, the chunks + adjacent to a free chunk must be in use. + + Given a pointer to a chunk (which can be derived trivially from the + payload pointer) we can, in O(1) time, find out whether the adjacent + chunks are free, and if so, unlink them from the lists that they + are on and merge them with the current chunk. + + Chunks always begin on even word boundaries, so the mem portion + (which is returned to the user) is also on an even word boundary, and + thus at least double-word aligned. + + The P (PINUSE_BIT) bit, stored in the unused low-order bit of the + chunk size (which is always a multiple of two words), is an in-use + bit for the *previous* chunk. If that bit is *clear*, then the + word before the current chunk size contains the previous chunk + size, and can be used to find the front of the previous chunk. + The very first chunk allocated always has this bit set, preventing + access to non-existent (or non-owned) memory. If pinuse is set for + any given chunk, then you CANNOT determine the size of the + previous chunk, and might even get a memory addressing fault when + trying to do so. + + The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of + the chunk size redundantly records whether the current chunk is + inuse. This redundancy enables usage checks within free and realloc, + and reduces indirection when freeing and consolidating chunks. + + Each freshly allocated chunk must have both cinuse and pinuse set. + That is, each allocated chunk borders either a previously allocated + and still in-use chunk, or the base of its memory arena. This is + ensured by making all allocations from the the `lowest' part of any + found chunk. Further, no free chunk physically borders another one, + so each free chunk is known to be preceded and followed by either + inuse chunks or the ends of memory. + + Note that the `foot' of the current chunk is actually represented + as the prev_foot of the NEXT chunk. This makes it easier to + deal with alignments etc but can be very confusing when trying + to extend or adapt this code. + + The exceptions to all this are + + 1. The special chunk `top' is the top-most available chunk (i.e., + the one bordering the end of available memory). It is treated + specially. Top is never included in any bin, is used only if + no other chunk is available, and is released back to the + system if it is very large (see M_TRIM_THRESHOLD). In effect, + the top chunk is treated as larger (and thus less well + fitting) than any other available chunk. The top chunk + doesn't update its trailing size field since there is no next + contiguous chunk that would have to index off it. However, + space is still allocated for it (TOP_FOOT_SIZE) to enable + separation or merging when space is extended. + + 3. Chunks allocated via mmap, which have the lowest-order bit + (IS_MMAPPED_BIT) set in their prev_foot fields, and do not set + PINUSE_BIT in their head fields. Because they are allocated + one-by-one, each must carry its own prev_foot field, which is + also used to hold the offset this chunk has within its mmapped + region, which is needed to preserve alignment. Each mmapped + chunk is trailed by the first two fields of a fake next-chunk + for sake of usage checks. + +*/ + +struct malloc_chunk { + size_t prev_foot; /* Size of previous chunk (if free). */ + size_t head; /* Size and inuse bits. */ + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; +}; + +typedef struct malloc_chunk mchunk; +typedef struct malloc_chunk* mchunkptr; +typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */ +typedef unsigned int bindex_t; /* Described below */ +typedef unsigned int binmap_t; /* Described below */ +typedef unsigned int flag_t; /* The type of various bit flag sets */ + +/* ------------------- Chunks sizes and alignments ----------------------- */ + +#define MCHUNK_SIZE (sizeof(mchunk)) + +#if FOOTERS +#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +#else /* FOOTERS */ +#define CHUNK_OVERHEAD (SIZE_T_SIZE) +#endif /* FOOTERS */ + +/* MMapped chunks need a second word of overhead ... */ +#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +/* ... and additional padding for fake next-chunk at foot */ +#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES) + +/* The smallest size we can malloc is an aligned minimal chunk */ +#define MIN_CHUNK_SIZE\ + ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* conversion from malloc headers to user pointers, and back */ +#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES)) +#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES)) +/* chunk associated with aligned address A */ +#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +/* Bounds on request (not chunk) sizes. */ +#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2) +#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE) + +/* pad request bytes into a usable size */ +#define pad_request(req) \ + (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* pad request, checking for minimum (but not maximum) */ +#define request2size(req) \ + (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req)) + + +/* ------------------ Operations on head and foot fields ----------------- */ + +/* + The head field of a chunk is or'ed with PINUSE_BIT when previous + adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in + use. If the chunk was obtained with mmap, the prev_foot field has + IS_MMAPPED_BIT set, otherwise holding the offset of the base of the + mmapped region to the base of the chunk. +*/ + +#define PINUSE_BIT (SIZE_T_ONE) +#define CINUSE_BIT (SIZE_T_TWO) +#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT) + +/* Head value for fenceposts */ +#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE) + +/* extraction of fields from head words */ +#define cinuse(p) ((p)->head & CINUSE_BIT) +#define pinuse(p) ((p)->head & PINUSE_BIT) +#define chunksize(p) ((p)->head & ~(INUSE_BITS)) + +#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) +#define clear_cinuse(p) ((p)->head &= ~CINUSE_BIT) + +/* Treat space at ptr +/- offset as a chunk */ +#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) +#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) + +/* Ptr to next or previous physical malloc_chunk. */ +#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~INUSE_BITS))) +#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) )) + +/* extract next chunk's pinuse bit */ +#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) + +/* Get/set size at footer */ +#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) +#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) + +/* Set size, pinuse bit, and foot */ +#define set_size_and_pinuse_of_free_chunk(p, s)\ + ((p)->head = (s|PINUSE_BIT), set_foot(p, s)) + +/* Set size, pinuse bit, foot, and clear next pinuse */ +#define set_free_with_pinuse(p, s, n)\ + (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) + +#define is_mmapped(p)\ + (!((p)->head & PINUSE_BIT) && ((p)->prev_foot & IS_MMAPPED_BIT)) + +/* Get the internal overhead associated with chunk p */ +#define overhead_for(p)\ + (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) + +/* Return true if malloced space is not necessarily cleared */ +#if MMAP_CLEARS +#define calloc_must_clear(p) (!is_mmapped(p)) +#else /* MMAP_CLEARS */ +#define calloc_must_clear(p) (1) +#endif /* MMAP_CLEARS */ + +/* ---------------------- Overlaid data structures ----------------------- */ + +/* + When chunks are not in use, they are treated as nodes of either + lists or trees. + + "Small" chunks are stored in circular doubly-linked lists, and look + like this: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space (may be 0 bytes long) . + . . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Larger chunks are kept in a form of bitwise digital trees (aka + tries) keyed on chunksizes. Because malloc_tree_chunks are only for + free chunks greater than 256 bytes, their size doesn't impose any + constraints on user chunk sizes. Each node looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to left child (child[0]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to right child (child[1]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to parent | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | bin index of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Each tree holding treenodes is a tree of unique chunk sizes. Chunks + of the same size are arranged in a circularly-linked list, with only + the oldest chunk (the next to be used, in our FIFO ordering) + actually in the tree. (Tree members are distinguished by a non-null + parent pointer.) If a chunk with the same size an an existing node + is inserted, it is linked off the existing node using pointers that + work in the same way as fd/bk pointers of small chunks. + + Each tree contains a power of 2 sized range of chunk sizes (the + smallest is 0x100 <= x < 0x180), which is is divided in half at each + tree level, with the chunks in the smaller half of the range (0x100 + <= x < 0x140 for the top nose) in the left subtree and the larger + half (0x140 <= x < 0x180) in the right subtree. This is, of course, + done by inspecting individual bits. + + Using these rules, each node's left subtree contains all smaller + sizes than its right subtree. However, the node at the root of each + subtree has no particular ordering relationship to either. (The + dividing line between the subtree sizes is based on trie relation.) + If we remove the last chunk of a given size from the interior of the + tree, we need to replace it with a leaf node. The tree ordering + rules permit a node to be replaced by any leaf below it. + + The smallest chunk in a tree (a common operation in a best-fit + allocator) can be found by walking a path to the leftmost leaf in + the tree. Unlike a usual binary tree, where we follow left child + pointers until we reach a null, here we follow the right child + pointer any time the left one is null, until we reach a leaf with + both child pointers null. The smallest chunk in the tree will be + somewhere along that path. + + The worst case number of steps to add, find, or remove a node is + bounded by the number of bits differentiating chunks within + bins. Under current bin calculations, this ranges from 6 up to 21 + (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case + is of course much better. +*/ + +struct malloc_tree_chunk { + /* The first four fields must be compatible with malloc_chunk */ + size_t prev_foot; + size_t head; + struct malloc_tree_chunk* fd; + struct malloc_tree_chunk* bk; + + struct malloc_tree_chunk* child[2]; + struct malloc_tree_chunk* parent; + bindex_t index; +}; + +typedef struct malloc_tree_chunk tchunk; +typedef struct malloc_tree_chunk* tchunkptr; +typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */ + +/* A little helper macro for trees */ +#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1]) + +/* ----------------------------- Segments -------------------------------- */ + +/* + Each malloc space may include non-contiguous segments, held in a + list headed by an embedded malloc_segment record representing the + top-most space. Segments also include flags holding properties of + the space. Large chunks that are directly allocated by mmap are not + included in this list. They are instead independently created and + destroyed without otherwise keeping track of them. + + Segment management mainly comes into play for spaces allocated by + MMAP. Any call to MMAP might or might not return memory that is + adjacent to an existing segment. MORECORE normally contiguously + extends the current space, so this space is almost always adjacent, + which is simpler and faster to deal with. (This is why MORECORE is + used preferentially to MMAP when both are available -- see + sys_alloc.) When allocating using MMAP, we don't use any of the + hinting mechanisms (inconsistently) supported in various + implementations of unix mmap, or distinguish reserving from + committing memory. Instead, we just ask for space, and exploit + contiguity when we get it. It is probably possible to do + better than this on some systems, but no general scheme seems + to be significantly better. + + Management entails a simpler variant of the consolidation scheme + used for chunks to reduce fragmentation -- new adjacent memory is + normally prepended or appended to an existing segment. However, + there are limitations compared to chunk consolidation that mostly + reflect the fact that segment processing is relatively infrequent + (occurring only when getting memory from system) and that we + don't expect to have huge numbers of segments: + + * Segments are not indexed, so traversal requires linear scans. (It + would be possible to index these, but is not worth the extra + overhead and complexity for most programs on most platforms.) + * New segments are only appended to old ones when holding top-most + memory; if they cannot be prepended to others, they are held in + different segments. + + Except for the top-most segment of an mstate, each segment record + is kept at the tail of its segment. Segments are added by pushing + segment records onto the list headed by &mstate.seg for the + containing mstate. + + Segment flags control allocation/merge/deallocation policies: + * If EXTERN_BIT set, then we did not allocate this segment, + and so should not try to deallocate or merge with others. + (This currently holds only for the initial segment passed + into create_mspace_with_base.) + * If IS_MMAPPED_BIT set, the segment may be merged with + other surrounding mmapped segments and trimmed/de-allocated + using munmap. + * If neither bit is set, then the segment was obtained using + MORECORE so can be merged with surrounding MORECORE'd segments + and deallocated/trimmed using MORECORE with negative arguments. +*/ + +struct malloc_segment { + char* base; /* base address */ + size_t size; /* allocated size */ + struct malloc_segment* next; /* ptr to next segment */ + flag_t sflags; /* mmap and extern flag */ +}; + +#define is_mmapped_segment(S) ((S)->sflags & IS_MMAPPED_BIT) +#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT) + +typedef struct malloc_segment msegment; +typedef struct malloc_segment* msegmentptr; + +/* ---------------------------- malloc_state ----------------------------- */ + +/* + A malloc_state holds all of the bookkeeping for a space. + The main fields are: + + Top + The topmost chunk of the currently active segment. Its size is + cached in topsize. The actual size of topmost space is + topsize+TOP_FOOT_SIZE, which includes space reserved for adding + fenceposts and segment records if necessary when getting more + space from the system. The size at which to autotrim top is + cached from mparams in trim_check, except that it is disabled if + an autotrim fails. + + Designated victim (dv) + This is the preferred chunk for servicing small requests that + don't have exact fits. It is normally the chunk split off most + recently to service another small request. Its size is cached in + dvsize. The link fields of this chunk are not maintained since it + is not kept in a bin. + + SmallBins + An array of bin headers for free chunks. These bins hold chunks + with sizes less than MIN_LARGE_SIZE bytes. Each bin contains + chunks of all the same size, spaced 8 bytes apart. To simplify + use in double-linked lists, each bin header acts as a malloc_chunk + pointing to the real first node, if it exists (else pointing to + itself). This avoids special-casing for headers. But to avoid + waste, we allocate only the fd/bk pointers of bins, and then use + repositioning tricks to treat these as the fields of a chunk. + + TreeBins + Treebins are pointers to the roots of trees holding a range of + sizes. There are 2 equally spaced treebins for each power of two + from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything + larger. + + Bin maps + There is one bit map for small bins ("smallmap") and one for + treebins ("treemap). Each bin sets its bit when non-empty, and + clears the bit when empty. Bit operations are then used to avoid + bin-by-bin searching -- nearly all "search" is done without ever + looking at bins that won't be selected. The bit maps + conservatively use 32 bits per map word, even if on 64bit system. + For a good description of some of the bit-based techniques used + here, see Henry S. Warren Jr's book "Hacker's Delight" (and + supplement at http://hackersdelight.org/). Many of these are + intended to reduce the branchiness of paths through malloc etc, as + well as to reduce the number of memory locations read or written. + + Segments + A list of segments headed by an embedded malloc_segment record + representing the initial space. + + Address check support + The least_addr field is the least address ever obtained from + MORECORE or MMAP. Attempted frees and reallocs of any address less + than this are trapped (unless INSECURE is defined). + + Magic tag + A cross-check field that should always hold same value as mparams.magic. + + Flags + Bits recording whether to use MMAP, locks, or contiguous MORECORE + + Statistics + Each space keeps track of current and maximum system memory + obtained via MORECORE or MMAP. + + Locking + If USE_LOCKS is defined, the "mutex" lock is acquired and released + around every public call using this mspace. +*/ + +/* Bin types, widths and sizes */ +#define NSMALLBINS (32U) +#define NTREEBINS (32U) +#define SMALLBIN_SHIFT (3U) +#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT) +#define TREEBIN_SHIFT (8U) +#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT) +#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE) +#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD) + +struct malloc_state { + binmap_t smallmap; + binmap_t treemap; + size_t dvsize; + size_t topsize; + char* least_addr; + mchunkptr dv; + mchunkptr top; + size_t trim_check; + size_t magic; + mchunkptr smallbins[(NSMALLBINS+1)*2]; + tbinptr treebins[NTREEBINS]; + size_t footprint; +#if USE_MAX_ALLOWED_FOOTPRINT + size_t max_allowed_footprint; +#endif + size_t max_footprint; + flag_t mflags; +#if USE_LOCKS + MLOCK_T mutex; /* locate lock among fields that rarely change */ +#endif /* USE_LOCKS */ + msegment seg; +}; + +typedef struct malloc_state* mstate; + +/* ------------- Global malloc_state and malloc_params ------------------- */ + +/* + malloc_params holds global properties, including those that can be + dynamically set using mallopt. There is a single instance, mparams, + initialized in init_mparams. +*/ + +struct malloc_params { + size_t magic; + size_t page_size; + size_t granularity; + size_t mmap_threshold; + size_t trim_threshold; + flag_t default_mflags; +}; + +static struct malloc_params mparams; + +/* The global malloc_state used for all non-"mspace" calls */ +static struct malloc_state _gm_ +#if USE_MAX_ALLOWED_FOOTPRINT + = { .max_allowed_footprint = MAX_SIZE_T }; +#else + ; +#endif + +#define gm (&_gm_) +#define is_global(M) ((M) == &_gm_) +#define is_initialized(M) ((M)->top != 0) + +/* -------------------------- system alloc setup ------------------------- */ + +/* Operations on mflags */ + +#define use_lock(M) ((M)->mflags & USE_LOCK_BIT) +#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT) +#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT) + +#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) +#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) +#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) + +#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) +#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) + +#define set_lock(M,L)\ + ((M)->mflags = (L)?\ + ((M)->mflags | USE_LOCK_BIT) :\ + ((M)->mflags & ~USE_LOCK_BIT)) + +/* page-align a size */ +#define page_align(S)\ + (((S) + (mparams.page_size)) & ~(mparams.page_size - SIZE_T_ONE)) + +/* granularity-align a size */ +#define granularity_align(S)\ + (((S) + (mparams.granularity)) & ~(mparams.granularity - SIZE_T_ONE)) + +#define is_page_aligned(S)\ + (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0) +#define is_granularity_aligned(S)\ + (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0) + +/* True if segment S holds address A */ +#define segment_holds(S, A)\ + ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) + +/* Return segment holding given address */ +static msegmentptr segment_holding(mstate m, char* addr) { + msegmentptr sp = &m->seg; + for (;;) { + if (addr >= sp->base && addr < sp->base + sp->size) + return sp; + if ((sp = sp->next) == 0) + return 0; + } +} + +/* Return true if segment contains a segment link */ +static int has_segment_link(mstate m, msegmentptr ss) { + msegmentptr sp = &m->seg; + for (;;) { + if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size) + return 1; + if ((sp = sp->next) == 0) + return 0; + } +} + +#ifndef MORECORE_CANNOT_TRIM +#define should_trim(M,s) ((s) > (M)->trim_check) +#else /* MORECORE_CANNOT_TRIM */ +#define should_trim(M,s) (0) +#endif /* MORECORE_CANNOT_TRIM */ + +/* + TOP_FOOT_SIZE is padding at the end of a segment, including space + that may be needed to place segment records and fenceposts when new + noncontiguous segments are added. +*/ +#define TOP_FOOT_SIZE\ + (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE) + + +/* ------------------------------- Hooks -------------------------------- */ + +/* + PREACTION should be defined to return 0 on success, and nonzero on + failure. If you are not using locking, you can redefine these to do + anything you like. +*/ + +#if USE_LOCKS + +/* Ensure locks are initialized */ +#define GLOBALLY_INITIALIZE() (mparams.page_size == 0 && init_mparams()) + +#define PREACTION(M) ((GLOBALLY_INITIALIZE() || use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0) +#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); } +#else /* USE_LOCKS */ + +#ifndef PREACTION +#define PREACTION(M) (0) +#endif /* PREACTION */ + +#ifndef POSTACTION +#define POSTACTION(M) +#endif /* POSTACTION */ + +#endif /* USE_LOCKS */ + +/* + CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses. + USAGE_ERROR_ACTION is triggered on detected bad frees and + reallocs. The argument p is an address that might have triggered the + fault. It is ignored by the two predefined actions, but might be + useful in custom actions that try to help diagnose errors. +*/ + +#if PROCEED_ON_ERROR + +/* A count of the number of corruption errors causing resets */ +int malloc_corruption_error_count; + +/* default corruption action */ +static void reset_on_error(mstate m); + +#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m) +#define USAGE_ERROR_ACTION(m, p) + +#else /* PROCEED_ON_ERROR */ + +#ifndef CORRUPTION_ERROR_ACTION +#define CORRUPTION_ERROR_ACTION(m) ABORT +#endif /* CORRUPTION_ERROR_ACTION */ + +#ifndef USAGE_ERROR_ACTION +#define USAGE_ERROR_ACTION(m,p) ABORT +#endif /* USAGE_ERROR_ACTION */ + +#endif /* PROCEED_ON_ERROR */ + +/* -------------------------- Debugging setup ---------------------------- */ + +#if ! DEBUG + +#define check_free_chunk(M,P) +#define check_inuse_chunk(M,P) +#define check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) +#define check_malloc_state(M) +#define check_top_chunk(M,P) + +#else /* DEBUG */ +#define check_free_chunk(M,P) do_check_free_chunk(M,P) +#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P) +#define check_top_chunk(M,P) do_check_top_chunk(M,P) +#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P) +#define check_malloc_state(M) do_check_malloc_state(M) + +static void do_check_any_chunk(mstate m, mchunkptr p); +static void do_check_top_chunk(mstate m, mchunkptr p); +static void do_check_mmapped_chunk(mstate m, mchunkptr p); +static void do_check_inuse_chunk(mstate m, mchunkptr p); +static void do_check_free_chunk(mstate m, mchunkptr p); +static void do_check_malloced_chunk(mstate m, void* mem, size_t s); +static void do_check_tree(mstate m, tchunkptr t); +static void do_check_treebin(mstate m, bindex_t i); +static void do_check_smallbin(mstate m, bindex_t i); +static void do_check_malloc_state(mstate m); +static int bin_find(mstate m, mchunkptr x); +static size_t traverse_and_check(mstate m); +#endif /* DEBUG */ + +/* ---------------------------- Indexing Bins ---------------------------- */ + +#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS) +#define small_index(s) ((s) >> SMALLBIN_SHIFT) +#define small_index2size(i) ((i) << SMALLBIN_SHIFT) +#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE)) + +/* addressing by index. See above about smallbin repositioning */ +#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1]))) +#define treebin_at(M,i) (&((M)->treebins[i])) + +/* assign tree index for size S to variable I */ +#if defined(__GNUC__) && defined(i386) +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + __asm__("bsrl %1,%0\n\t" : "=r" (K) : "rm" (X));\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} +#else /* GNUC */ +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int Y = (unsigned int)X;\ + unsigned int N = ((Y - 0x100) >> 16) & 8;\ + unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\ + N += K;\ + N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\ + K = 14 - N + ((Y <<= K) >> 15);\ + I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\ + }\ +} +#endif /* GNUC */ + +/* Bit representing maximum resolved size in a treebin at i */ +#define bit_for_tree_index(i) \ + (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2) + +/* Shift placing maximum resolved bit in a treebin at i as sign bit */ +#define leftshift_for_tree_index(i) \ + ((i == NTREEBINS-1)? 0 : \ + ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2))) + +/* The size of the smallest chunk held in bin with index i */ +#define minsize_for_tree_index(i) \ + ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \ + (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1))) + + +/* ------------------------ Operations on bin maps ----------------------- */ + +/* bit corresponding to given index */ +#define idx2bit(i) ((binmap_t)(1) << (i)) + +/* Mark/Clear bits with given index */ +#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i)) +#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i)) +#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i)) + +#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i)) +#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i)) +#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i)) + +/* index corresponding to given bit */ + +#if defined(__GNUC__) && defined(i386) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + __asm__("bsfl %1,%0\n\t" : "=r" (J) : "rm" (X));\ + I = (bindex_t)J;\ +} + +#else /* GNUC */ +#if defined(__ARM_ARCH__) && __ARM_ARCH__ >= 7 +#define compute_bit2idx(X, I) \ +{ \ + unsigned int J; \ + __asm__ ("rbit %0, %1\n" \ + "clz %0, %0" \ + : "=r" (J) : "r" (X)); \ + I = (bindex_t) J; \ +} + +#else /* ARM_ARCH */ +#if USE_BUILTIN_FFS +#define compute_bit2idx(X, I) I = ffs(X)-1 + +#else /* USE_BUILTIN_FFS */ +#define compute_bit2idx(X, I)\ +{\ + unsigned int Y = X - 1;\ + unsigned int K = Y >> (16-4) & 16;\ + unsigned int N = K; Y >>= K;\ + N += K = Y >> (8-3) & 8; Y >>= K;\ + N += K = Y >> (4-2) & 4; Y >>= K;\ + N += K = Y >> (2-1) & 2; Y >>= K;\ + N += K = Y >> (1-0) & 1; Y >>= K;\ + I = (bindex_t)(N + Y);\ +} +#endif /* ARM_ARCH */ +#endif /* USE_BUILTIN_FFS */ +#endif /* GNUC */ + +/* isolate the least set bit of a bitmap */ +#define least_bit(x) ((x) & -(x)) + +/* mask with all bits to left of least bit of x on */ +#define left_bits(x) ((x<<1) | -(x<<1)) + +/* mask with all bits to left of or equal to least bit of x on */ +#define same_or_left_bits(x) ((x) | -(x)) + + +/* ----------------------- Runtime Check Support ------------------------- */ + +/* + For security, the main invariant is that malloc/free/etc never + writes to a static address other than malloc_state, unless static + malloc_state itself has been corrupted, which cannot occur via + malloc (because of these checks). In essence this means that we + believe all pointers, sizes, maps etc held in malloc_state, but + check all of those linked or offsetted from other embedded data + structures. These checks are interspersed with main code in a way + that tends to minimize their run-time cost. + + When FOOTERS is defined, in addition to range checking, we also + verify footer fields of inuse chunks, which can be used guarantee + that the mstate controlling malloc/free is intact. This is a + streamlined version of the approach described by William Robertson + et al in "Run-time Detection of Heap-based Overflows" LISA'03 + http://www.usenix.org/events/lisa03/tech/robertson.html The footer + of an inuse chunk holds the xor of its mstate and a random seed, + that is checked upon calls to free() and realloc(). This is + (probablistically) unguessable from outside the program, but can be + computed by any code successfully malloc'ing any chunk, so does not + itself provide protection against code that has already broken + security through some other means. Unlike Robertson et al, we + always dynamically check addresses of all offset chunks (previous, + next, etc). This turns out to be cheaper than relying on hashes. +*/ + +#if !INSECURE +/* Check if address a is at least as high as any from MORECORE or MMAP */ +#define ok_address(M, a) ((char*)(a) >= (M)->least_addr) +/* Check if address of next chunk n is higher than base chunk p */ +#define ok_next(p, n) ((char*)(p) < (char*)(n)) +/* Check if p has its cinuse bit on */ +#define ok_cinuse(p) cinuse(p) +/* Check if p has its pinuse bit on */ +#define ok_pinuse(p) pinuse(p) + +#else /* !INSECURE */ +#define ok_address(M, a) (1) +#define ok_next(b, n) (1) +#define ok_cinuse(p) (1) +#define ok_pinuse(p) (1) +#endif /* !INSECURE */ + +#if (FOOTERS && !INSECURE) +/* Check if (alleged) mstate m has expected magic field */ +#define ok_magic(M) ((M)->magic == mparams.magic) +#else /* (FOOTERS && !INSECURE) */ +#define ok_magic(M) (1) +#endif /* (FOOTERS && !INSECURE) */ + + +/* In gcc, use __builtin_expect to minimize impact of checks */ +#if !INSECURE +#if defined(__GNUC__) && __GNUC__ >= 3 +#define RTCHECK(e) __builtin_expect(e, 1) +#else /* GNUC */ +#define RTCHECK(e) (e) +#endif /* GNUC */ +#else /* !INSECURE */ +#define RTCHECK(e) (1) +#endif /* !INSECURE */ + +/* macros to set up inuse chunks with or without footers */ + +#if !FOOTERS + +#define mark_inuse_foot(M,p,s) + +/* Set cinuse bit and pinuse bit of next chunk */ +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set cinuse and pinuse of this chunk and pinuse of next chunk */ +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set size, cinuse and pinuse bit of this chunk */ +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT)) + +#else /* FOOTERS */ + +/* Set foot of inuse chunk to be xor of mstate and seed */ +#define mark_inuse_foot(M,p,s)\ + (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) + +#define get_mstate_for(p)\ + ((mstate)(((mchunkptr)((char*)(p) +\ + (chunksize(p))))->prev_foot ^ mparams.magic)) + +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \ + mark_inuse_foot(M,p,s)) + +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\ + mark_inuse_foot(M,p,s)) + +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + mark_inuse_foot(M, p, s)) + +#endif /* !FOOTERS */ + +/* ---------------------------- setting mparams -------------------------- */ + +/* Initialize mparams */ +static int init_mparams(void) { + if (mparams.page_size == 0) { + size_t s; + + mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; + mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; +#if MORECORE_CONTIGUOUS + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; +#else /* MORECORE_CONTIGUOUS */ + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; +#endif /* MORECORE_CONTIGUOUS */ + +#if (FOOTERS && !INSECURE) + { +#if USE_DEV_RANDOM + int fd; + unsigned char buf[sizeof(size_t)]; + /* Try to use /dev/urandom, else fall back on using time */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 && + read(fd, buf, sizeof(buf)) == sizeof(buf)) { + s = *((size_t *) buf); + close(fd); + } + else +#endif /* USE_DEV_RANDOM */ + s = (size_t)(time(0) ^ (size_t)0x55555555U); + + s |= (size_t)8U; /* ensure nonzero */ + s &= ~(size_t)7U; /* improve chances of fault for bad values */ + + } +#else /* (FOOTERS && !INSECURE) */ + s = (size_t)0x58585858U; +#endif /* (FOOTERS && !INSECURE) */ + ACQUIRE_MAGIC_INIT_LOCK(); + if (mparams.magic == 0) { + mparams.magic = s; + /* Set up lock for main malloc area */ + INITIAL_LOCK(&gm->mutex); + gm->mflags = mparams.default_mflags; + } + RELEASE_MAGIC_INIT_LOCK(); + +#ifndef WIN32 + mparams.page_size = malloc_getpagesize; + mparams.granularity = ((DEFAULT_GRANULARITY != 0)? + DEFAULT_GRANULARITY : mparams.page_size); +#else /* WIN32 */ + { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + mparams.page_size = system_info.dwPageSize; + mparams.granularity = system_info.dwAllocationGranularity; + } +#endif /* WIN32 */ + + /* Sanity-check configuration: + size_t must be unsigned and as wide as pointer type. + ints must be at least 4 bytes. + alignment must be at least 8. + Alignment, min chunk size, and page size must all be powers of 2. + */ + if ((sizeof(size_t) != sizeof(char*)) || + (MAX_SIZE_T < MIN_CHUNK_SIZE) || + (sizeof(int) < 4) || + (MALLOC_ALIGNMENT < (size_t)8U) || + ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) || + ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) || + ((mparams.granularity & (mparams.granularity-SIZE_T_ONE)) != 0) || + ((mparams.page_size & (mparams.page_size-SIZE_T_ONE)) != 0)) + ABORT; + } + return 0; +} + +/* support for mallopt */ +static int change_mparam(int param_number, int value) { + size_t val = (size_t)value; + init_mparams(); + switch(param_number) { + case M_TRIM_THRESHOLD: + mparams.trim_threshold = val; + return 1; + case M_GRANULARITY: + if (val >= mparams.page_size && ((val & (val-1)) == 0)) { + mparams.granularity = val; + return 1; + } + else + return 0; + case M_MMAP_THRESHOLD: + mparams.mmap_threshold = val; + return 1; + default: + return 0; + } +} + +#if DEBUG +/* ------------------------- Debugging Support --------------------------- */ + +/* Check properties of any chunk, whether free, inuse, mmapped etc */ +static void do_check_any_chunk(mstate m, mchunkptr p) { + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); +} + +/* Check properties of top chunk */ +static void do_check_top_chunk(mstate m, mchunkptr p) { + msegmentptr sp = segment_holding(m, (char*)p); + size_t sz = chunksize(p); + assert(sp != 0); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(sz == m->topsize); + assert(sz > 0); + assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE); + assert(pinuse(p)); + assert(!next_pinuse(p)); +} + +/* Check properties of (inuse) mmapped chunks */ +static void do_check_mmapped_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + size_t len = (sz + (p->prev_foot & ~IS_MMAPPED_BIT) + MMAP_FOOT_PAD); + assert(is_mmapped(p)); + assert(use_mmap(m)); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(!is_small(sz)); + assert((len & (mparams.page_size-SIZE_T_ONE)) == 0); + assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD); + assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0); +} + +/* Check properties of inuse chunks */ +static void do_check_inuse_chunk(mstate m, mchunkptr p) { + do_check_any_chunk(m, p); + assert(cinuse(p)); + assert(next_pinuse(p)); + /* If not pinuse and not mmapped, previous chunk has OK offset */ + assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p); + if (is_mmapped(p)) + do_check_mmapped_chunk(m, p); +} + +/* Check properties of free chunks */ +static void do_check_free_chunk(mstate m, mchunkptr p) { + size_t sz = p->head & ~(PINUSE_BIT|CINUSE_BIT); + mchunkptr next = chunk_plus_offset(p, sz); + do_check_any_chunk(m, p); + assert(!cinuse(p)); + assert(!next_pinuse(p)); + assert (!is_mmapped(p)); + if (p != m->dv && p != m->top) { + if (sz >= MIN_CHUNK_SIZE) { + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(is_aligned(chunk2mem(p))); + assert(next->prev_foot == sz); + assert(pinuse(p)); + assert (next == m->top || cinuse(next)); + assert(p->fd->bk == p); + assert(p->bk->fd == p); + } + else /* markers are always of size SIZE_T_SIZE */ + assert(sz == SIZE_T_SIZE); + } +} + +/* Check properties of malloced chunks at the point they are malloced */ +static void do_check_malloced_chunk(mstate m, void* mem, size_t s) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t sz = p->head & ~(PINUSE_BIT|CINUSE_BIT); + do_check_inuse_chunk(m, p); + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(sz >= MIN_CHUNK_SIZE); + assert(sz >= s); + /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */ + assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE)); + } +} + +/* Check a tree and its subtrees. */ +static void do_check_tree(mstate m, tchunkptr t) { + tchunkptr head = 0; + tchunkptr u = t; + bindex_t tindex = t->index; + size_t tsize = chunksize(t); + bindex_t idx; + compute_tree_index(tsize, idx); + assert(tindex == idx); + assert(tsize >= MIN_LARGE_SIZE); + assert(tsize >= minsize_for_tree_index(idx)); + assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1)))); + + do { /* traverse through chain of same-sized nodes */ + do_check_any_chunk(m, ((mchunkptr)u)); + assert(u->index == tindex); + assert(chunksize(u) == tsize); + assert(!cinuse(u)); + assert(!next_pinuse(u)); + assert(u->fd->bk == u); + assert(u->bk->fd == u); + if (u->parent == 0) { + assert(u->child[0] == 0); + assert(u->child[1] == 0); + } + else { + assert(head == 0); /* only one node on chain has parent */ + head = u; + assert(u->parent != u); + assert (u->parent->child[0] == u || + u->parent->child[1] == u || + *((tbinptr*)(u->parent)) == u); + if (u->child[0] != 0) { + assert(u->child[0]->parent == u); + assert(u->child[0] != u); + do_check_tree(m, u->child[0]); + } + if (u->child[1] != 0) { + assert(u->child[1]->parent == u); + assert(u->child[1] != u); + do_check_tree(m, u->child[1]); + } + if (u->child[0] != 0 && u->child[1] != 0) { + assert(chunksize(u->child[0]) < chunksize(u->child[1])); + } + } + u = u->fd; + } while (u != t); + assert(head != 0); +} + +/* Check all the chunks in a treebin. */ +static void do_check_treebin(mstate m, bindex_t i) { + tbinptr* tb = treebin_at(m, i); + tchunkptr t = *tb; + int empty = (m->treemap & (1U << i)) == 0; + if (t == 0) + assert(empty); + if (!empty) + do_check_tree(m, t); +} + +/* Check all the chunks in a smallbin. */ +static void do_check_smallbin(mstate m, bindex_t i) { + sbinptr b = smallbin_at(m, i); + mchunkptr p = b->bk; + unsigned int empty = (m->smallmap & (1U << i)) == 0; + if (p == b) + assert(empty); + if (!empty) { + for (; p != b; p = p->bk) { + size_t size = chunksize(p); + mchunkptr q; + /* each chunk claims to be free */ + do_check_free_chunk(m, p); + /* chunk belongs in bin */ + assert(small_index(size) == i); + assert(p->bk == b || chunksize(p->bk) == chunksize(p)); + /* chunk is followed by an inuse chunk */ + q = next_chunk(p); + if (q->head != FENCEPOST_HEAD) + do_check_inuse_chunk(m, q); + } + } +} + +/* Find x in a bin. Used in other check functions. */ +static int bin_find(mstate m, mchunkptr x) { + size_t size = chunksize(x); + if (is_small(size)) { + bindex_t sidx = small_index(size); + sbinptr b = smallbin_at(m, sidx); + if (smallmap_is_marked(m, sidx)) { + mchunkptr p = b; + do { + if (p == x) + return 1; + } while ((p = p->fd) != b); + } + } + else { + bindex_t tidx; + compute_tree_index(size, tidx); + if (treemap_is_marked(m, tidx)) { + tchunkptr t = *treebin_at(m, tidx); + size_t sizebits = size << leftshift_for_tree_index(tidx); + while (t != 0 && chunksize(t) != size) { + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + sizebits <<= 1; + } + if (t != 0) { + tchunkptr u = t; + do { + if (u == (tchunkptr)x) + return 1; + } while ((u = u->fd) != t); + } + } + } + return 0; +} + +/* Traverse each chunk and check it; return total */ +static size_t traverse_and_check(mstate m) { + size_t sum = 0; + if (is_initialized(m)) { + msegmentptr s = &m->seg; + sum += m->topsize + TOP_FOOT_SIZE; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + mchunkptr lastq = 0; + assert(pinuse(q)); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + sum += chunksize(q); + if (cinuse(q)) { + assert(!bin_find(m, q)); + do_check_inuse_chunk(m, q); + } + else { + assert(q == m->dv || bin_find(m, q)); + assert(lastq == 0 || cinuse(lastq)); /* Not 2 consecutive free */ + do_check_free_chunk(m, q); + } + lastq = q; + q = next_chunk(q); + } + s = s->next; + } + } + return sum; +} + +/* Check all properties of malloc_state. */ +static void do_check_malloc_state(mstate m) { + bindex_t i; + size_t total; + /* check bins */ + for (i = 0; i < NSMALLBINS; ++i) + do_check_smallbin(m, i); + for (i = 0; i < NTREEBINS; ++i) + do_check_treebin(m, i); + + if (m->dvsize != 0) { /* check dv chunk */ + do_check_any_chunk(m, m->dv); + assert(m->dvsize == chunksize(m->dv)); + assert(m->dvsize >= MIN_CHUNK_SIZE); + assert(bin_find(m, m->dv) == 0); + } + + if (m->top != 0) { /* check top chunk */ + do_check_top_chunk(m, m->top); + assert(m->topsize == chunksize(m->top)); + assert(m->topsize > 0); + assert(bin_find(m, m->top) == 0); + } + + total = traverse_and_check(m); + assert(total <= m->footprint); + assert(m->footprint <= m->max_footprint); +#if USE_MAX_ALLOWED_FOOTPRINT + //TODO: change these assertions if we allow for shrinking. + assert(m->footprint <= m->max_allowed_footprint); + assert(m->max_footprint <= m->max_allowed_footprint); +#endif +} +#endif /* DEBUG */ + +/* ----------------------------- statistics ------------------------------ */ + +#if !NO_MALLINFO +static struct mallinfo internal_mallinfo(mstate m) { + struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + if (!PREACTION(m)) { + check_malloc_state(m); + if (is_initialized(m)) { + size_t nfree = SIZE_T_ONE; /* top always free */ + size_t mfree = m->topsize + TOP_FOOT_SIZE; + size_t sum = mfree; + msegmentptr s = &m->seg; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + size_t sz = chunksize(q); + sum += sz; + if (!cinuse(q)) { + mfree += sz; + ++nfree; + } + q = next_chunk(q); + } + s = s->next; + } + + nm.arena = sum; + nm.ordblks = nfree; + nm.hblkhd = m->footprint - sum; + nm.usmblks = m->max_footprint; + nm.uordblks = m->footprint - mfree; + nm.fordblks = mfree; + nm.keepcost = m->topsize; + } + + POSTACTION(m); + } + return nm; +} +#endif /* !NO_MALLINFO */ + +static void internal_malloc_stats(mstate m) { + if (!PREACTION(m)) { + size_t maxfp = 0; + size_t fp = 0; + size_t used = 0; + check_malloc_state(m); + if (is_initialized(m)) { + msegmentptr s = &m->seg; + maxfp = m->max_footprint; + fp = m->footprint; + used = fp - (m->topsize + TOP_FOOT_SIZE); + + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + if (!cinuse(q)) + used -= chunksize(q); + q = next_chunk(q); + } + s = s->next; + } + } + + fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp)); + fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp)); + fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used)); + + POSTACTION(m); + } +} + +/* ----------------------- Operations on smallbins ----------------------- */ + +/* + Various forms of linking and unlinking are defined as macros. Even + the ones for trees, which are very long but have very short typical + paths. This is ugly but reduces reliance on inlining support of + compilers. +*/ + +/* Link a free chunk into a smallbin */ +#define insert_small_chunk(M, P, S) {\ + bindex_t I = small_index(S);\ + mchunkptr B = smallbin_at(M, I);\ + mchunkptr F = B;\ + assert(S >= MIN_CHUNK_SIZE);\ + if (!smallmap_is_marked(M, I))\ + mark_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, B->fd)))\ + F = B->fd;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + B->fd = P;\ + F->bk = P;\ + P->fd = F;\ + P->bk = B;\ +} + +/* Unlink a chunk from a smallbin + * Added check: if F->bk != P or B->fd != P, we have double linked list + * corruption, and abort. + */ +#define unlink_small_chunk(M, P, S) {\ + mchunkptr F = P->fd;\ + mchunkptr B = P->bk;\ + bindex_t I = small_index(S);\ + if (__builtin_expect (F->bk != P || B->fd != P, 0))\ + CORRUPTION_ERROR_ACTION(M);\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (F == B)\ + clear_smallmap(M, I);\ + else if (RTCHECK((F == smallbin_at(M,I) || ok_address(M, F)) &&\ + (B == smallbin_at(M,I) || ok_address(M, B)))) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Unlink the first chunk from a smallbin + * Added check: if F->bk != P or B->fd != P, we have double linked list + * corruption, and abort. + */ +#define unlink_first_small_chunk(M, B, P, I) {\ + mchunkptr F = P->fd;\ + if (__builtin_expect (F->bk != P || B->fd != P, 0))\ + CORRUPTION_ERROR_ACTION(M);\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (B == F)\ + clear_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, F))) {\ + B->fd = F;\ + F->bk = B;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Replace dv node, binning the old one */ +/* Used only when dvsize known to be small */ +#define replace_dv(M, P, S) {\ + size_t DVS = M->dvsize;\ + if (DVS != 0) {\ + mchunkptr DV = M->dv;\ + assert(is_small(DVS));\ + insert_small_chunk(M, DV, DVS);\ + }\ + M->dvsize = S;\ + M->dv = P;\ +} + +/* ------------------------- Operations on trees ------------------------- */ + +/* Insert chunk into tree */ +#define insert_large_chunk(M, X, S) {\ + tbinptr* H;\ + bindex_t I;\ + compute_tree_index(S, I);\ + H = treebin_at(M, I);\ + X->index = I;\ + X->child[0] = X->child[1] = 0;\ + if (!treemap_is_marked(M, I)) {\ + mark_treemap(M, I);\ + *H = X;\ + X->parent = (tchunkptr)H;\ + X->fd = X->bk = X;\ + }\ + else {\ + tchunkptr T = *H;\ + size_t K = S << leftshift_for_tree_index(I);\ + for (;;) {\ + if (chunksize(T) != S) {\ + tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\ + K <<= 1;\ + if (*C != 0)\ + T = *C;\ + else if (RTCHECK(ok_address(M, C))) {\ + *C = X;\ + X->parent = T;\ + X->fd = X->bk = X;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + else {\ + tchunkptr F = T->fd;\ + if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\ + T->fd = F->bk = X;\ + X->fd = F;\ + X->bk = T;\ + X->parent = 0;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + }\ + }\ +} + +/* + Unlink steps: + + 1. If x is a chained node, unlink it from its same-sized fd/bk links + and choose its bk node as its replacement. + 2. If x was the last node of its size, but not a leaf node, it must + be replaced with a leaf node (not merely one with an open left or + right), to make sure that lefts and rights of descendents + correspond properly to bit masks. We use the rightmost descendent + of x. We could use any other leaf, but this is easy to locate and + tends to counteract removal of leftmosts elsewhere, and so keeps + paths shorter than minimally guaranteed. This doesn't loop much + because on average a node in a tree is near the bottom. + 3. If x is the base of a chain (i.e., has parent links) relink + x's parent and children to x's replacement (or null if none). + + Added check: if F->bk != X or R->fd != X, we have double linked list + corruption, and abort. +*/ + +#define unlink_large_chunk(M, X) {\ + tchunkptr XP = X->parent;\ + tchunkptr R;\ + if (X->bk != X) {\ + tchunkptr F = X->fd;\ + R = X->bk;\ + if (__builtin_expect (F->bk != X || R->fd != X, 0))\ + CORRUPTION_ERROR_ACTION(M);\ + if (RTCHECK(ok_address(M, F))) {\ + F->bk = R;\ + R->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + tchunkptr* RP;\ + if (((R = *(RP = &(X->child[1]))) != 0) ||\ + ((R = *(RP = &(X->child[0]))) != 0)) {\ + tchunkptr* CP;\ + while ((*(CP = &(R->child[1])) != 0) ||\ + (*(CP = &(R->child[0])) != 0)) {\ + R = *(RP = CP);\ + }\ + if (RTCHECK(ok_address(M, RP)))\ + *RP = 0;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + }\ + if (XP != 0) {\ + tbinptr* H = treebin_at(M, X->index);\ + if (X == *H) {\ + if ((*H = R) == 0) \ + clear_treemap(M, X->index);\ + }\ + else if (RTCHECK(ok_address(M, XP))) {\ + if (XP->child[0] == X) \ + XP->child[0] = R;\ + else \ + XP->child[1] = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + if (R != 0) {\ + if (RTCHECK(ok_address(M, R))) {\ + tchunkptr C0, C1;\ + R->parent = XP;\ + if ((C0 = X->child[0]) != 0) {\ + if (RTCHECK(ok_address(M, C0))) {\ + R->child[0] = C0;\ + C0->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + if ((C1 = X->child[1]) != 0) {\ + if (RTCHECK(ok_address(M, C1))) {\ + R->child[1] = C1;\ + C1->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ +} + +/* Relays to large vs small bin operations */ + +#define insert_chunk(M, P, S)\ + if (is_small(S)) insert_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); } + +#define unlink_chunk(M, P, S)\ + if (is_small(S)) unlink_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); } + + +/* Relays to internal calls to malloc/free from realloc, memalign etc */ + +#if ONLY_MSPACES +#define internal_malloc(m, b) mspace_malloc(m, b) +#define internal_free(m, mem) mspace_free(m,mem); +#else /* ONLY_MSPACES */ +#if MSPACES +#define internal_malloc(m, b)\ + (m == gm)? dlmalloc(b) : mspace_malloc(m, b) +#define internal_free(m, mem)\ + if (m == gm) dlfree(mem); else mspace_free(m,mem); +#else /* MSPACES */ +#define internal_malloc(m, b) dlmalloc(b) +#define internal_free(m, mem) dlfree(mem) +#endif /* MSPACES */ +#endif /* ONLY_MSPACES */ + +/* ----------------------- Direct-mmapping chunks ----------------------- */ + +/* + Directly mmapped chunks are set up with an offset to the start of + the mmapped region stored in the prev_foot field of the chunk. This + allows reconstruction of the required argument to MUNMAP when freed, + and also allows adjustment of the returned chunk to meet alignment + requirements (especially in memalign). There is also enough space + allocated to hold a fake next chunk of size SIZE_T_SIZE to maintain + the PINUSE bit so frees can be checked. +*/ + +/* Malloc using mmap */ +static void* mmap_alloc(mstate m, size_t nb) { + size_t mmsize = granularity_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); +#if USE_MAX_ALLOWED_FOOTPRINT + size_t new_footprint = m->footprint + mmsize; + if (new_footprint <= m->footprint || /* Check for wrap around 0 */ + new_footprint > m->max_allowed_footprint) + return 0; +#endif + if (mmsize > nb) { /* Check for wrap around 0 */ + char* mm = (char*)(DIRECT_MMAP(mmsize)); + if (mm != CMFAIL) { + size_t offset = align_offset(chunk2mem(mm)); + size_t psize = mmsize - offset - MMAP_FOOT_PAD; + mchunkptr p = (mchunkptr)(mm + offset); + p->prev_foot = offset | IS_MMAPPED_BIT; + (p)->head = (psize|CINUSE_BIT); + mark_inuse_foot(m, p, psize); + chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0; + + if (mm < m->least_addr) + m->least_addr = mm; + if ((m->footprint += mmsize) > m->max_footprint) + m->max_footprint = m->footprint; + assert(is_aligned(chunk2mem(p))); + check_mmapped_chunk(m, p); + return chunk2mem(p); + } + } + return 0; +} + +/* Realloc using mmap */ +static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb) { + size_t oldsize = chunksize(oldp); + if (is_small(nb)) /* Can't shrink mmap regions below small size */ + return 0; + /* Keep old chunk if big enough but not too big */ + if (oldsize >= nb + SIZE_T_SIZE && + (oldsize - nb) <= (mparams.granularity << 1)) + return oldp; + else { + size_t offset = oldp->prev_foot & ~IS_MMAPPED_BIT; + size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD; + size_t newmmsize = granularity_align(nb + SIX_SIZE_T_SIZES + + CHUNK_ALIGN_MASK); + char* cp = (char*)CALL_MREMAP((char*)oldp - offset, + oldmmsize, newmmsize, 1); + if (cp != CMFAIL) { + mchunkptr newp = (mchunkptr)(cp + offset); + size_t psize = newmmsize - offset - MMAP_FOOT_PAD; + newp->head = (psize|CINUSE_BIT); + mark_inuse_foot(m, newp, psize); + chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0; + + if (cp < m->least_addr) + m->least_addr = cp; + if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint) + m->max_footprint = m->footprint; + check_mmapped_chunk(m, newp); + return newp; + } + } + return 0; +} + +/* -------------------------- mspace management -------------------------- */ + +/* Initialize top chunk and its size */ +static void init_top(mstate m, mchunkptr p, size_t psize) { + /* Ensure alignment */ + size_t offset = align_offset(chunk2mem(p)); + p = (mchunkptr)((char*)p + offset); + psize -= offset; + + m->top = p; + m->topsize = psize; + p->head = psize | PINUSE_BIT; + /* set size of fake trailing chunk holding overhead space only once */ + chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE; + m->trim_check = mparams.trim_threshold; /* reset on each update */ +} + +/* Initialize bins for a new mstate that is otherwise zeroed out */ +static void init_bins(mstate m) { + /* Establish circular links for smallbins */ + bindex_t i; + for (i = 0; i < NSMALLBINS; ++i) { + sbinptr bin = smallbin_at(m,i); + bin->fd = bin->bk = bin; + } +} + +#if PROCEED_ON_ERROR + +/* default corruption action */ +static void reset_on_error(mstate m) { + int i; + ++malloc_corruption_error_count; + /* Reinitialize fields to forget about all memory */ + m->smallbins = m->treebins = 0; + m->dvsize = m->topsize = 0; + m->seg.base = 0; + m->seg.size = 0; + m->seg.next = 0; + m->top = m->dv = 0; + for (i = 0; i < NTREEBINS; ++i) + *treebin_at(m, i) = 0; + init_bins(m); +} +#endif /* PROCEED_ON_ERROR */ + +/* Allocate chunk and prepend remainder with chunk in successor base. */ +static void* prepend_alloc(mstate m, char* newbase, char* oldbase, + size_t nb) { + mchunkptr p = align_as_chunk(newbase); + mchunkptr oldfirst = align_as_chunk(oldbase); + size_t psize = (char*)oldfirst - (char*)p; + mchunkptr q = chunk_plus_offset(p, nb); + size_t qsize = psize - nb; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + + assert((char*)oldfirst > (char*)q); + assert(pinuse(oldfirst)); + assert(qsize >= MIN_CHUNK_SIZE); + + /* consolidate remainder with first chunk of old base */ + if (oldfirst == m->top) { + size_t tsize = m->topsize += qsize; + m->top = q; + q->head = tsize | PINUSE_BIT; + check_top_chunk(m, q); + } + else if (oldfirst == m->dv) { + size_t dsize = m->dvsize += qsize; + m->dv = q; + set_size_and_pinuse_of_free_chunk(q, dsize); + } + else { + if (!cinuse(oldfirst)) { + size_t nsize = chunksize(oldfirst); + unlink_chunk(m, oldfirst, nsize); + oldfirst = chunk_plus_offset(oldfirst, nsize); + qsize += nsize; + } + set_free_with_pinuse(q, qsize, oldfirst); + insert_chunk(m, q, qsize); + check_free_chunk(m, q); + } + + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); +} + + +/* Add a segment to hold a new noncontiguous region */ +static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) { + /* Determine locations and sizes of segment, fenceposts, old top */ + char* old_top = (char*)m->top; + msegmentptr oldsp = segment_holding(m, old_top); + char* old_end = oldsp->base + oldsp->size; + size_t ssize = pad_request(sizeof(struct malloc_segment)); + char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + size_t offset = align_offset(chunk2mem(rawsp)); + char* asp = rawsp + offset; + char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; + mchunkptr sp = (mchunkptr)csp; + msegmentptr ss = (msegmentptr)(chunk2mem(sp)); + mchunkptr tnext = chunk_plus_offset(sp, ssize); + mchunkptr p = tnext; + int nfences = 0; + + /* reset top to new space */ + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + + /* Set up segment record */ + assert(is_aligned(ss)); + set_size_and_pinuse_of_inuse_chunk(m, sp, ssize); + *ss = m->seg; /* Push current record */ + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmapped; + m->seg.next = ss; + + /* Insert trailing fenceposts */ + for (;;) { + mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE); + p->head = FENCEPOST_HEAD; + ++nfences; + if ((char*)(&(nextp->head)) < old_end) + p = nextp; + else + break; + } + assert(nfences >= 2); + + /* Insert the rest of old top into a bin as an ordinary free chunk */ + if (csp != old_top) { + mchunkptr q = (mchunkptr)old_top; + size_t psize = csp - old_top; + mchunkptr tn = chunk_plus_offset(q, psize); + set_free_with_pinuse(q, psize, tn); + insert_chunk(m, q, psize); + } + + check_top_chunk(m, m->top); +} + +/* -------------------------- System allocation -------------------------- */ + +/* Get memory from system using MORECORE or MMAP */ +static void* sys_alloc(mstate m, size_t nb) { + char* tbase = CMFAIL; + size_t tsize = 0; + flag_t mmap_flag = 0; + + init_mparams(); + + /* Directly map large chunks */ + if (use_mmap(m) && nb >= mparams.mmap_threshold) { + void* mem = mmap_alloc(m, nb); + if (mem != 0) + return mem; + } + +#if USE_MAX_ALLOWED_FOOTPRINT + /* Make sure the footprint doesn't grow past max_allowed_footprint. + * This covers all cases except for where we need to page align, below. + */ + { + size_t new_footprint = m->footprint + + granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE); + if (new_footprint <= m->footprint || /* Check for wrap around 0 */ + new_footprint > m->max_allowed_footprint) + return 0; + } +#endif + + /* + Try getting memory in any of three ways (in most-preferred to + least-preferred order): + 1. A call to MORECORE that can normally contiguously extend memory. + (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or + or main space is mmapped or a previous contiguous call failed) + 2. A call to MMAP new space (disabled if not HAVE_MMAP). + Note that under the default settings, if MORECORE is unable to + fulfill a request, and HAVE_MMAP is true, then mmap is + used as a noncontiguous system allocator. This is a useful backup + strategy for systems with holes in address spaces -- in this case + sbrk cannot contiguously expand the heap, but mmap may be able to + find space. + 3. A call to MORECORE that cannot usually contiguously extend memory. + (disabled if not HAVE_MORECORE) + */ + + if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { + char* br = CMFAIL; + msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); + size_t asize = 0; + ACQUIRE_MORECORE_LOCK(); + + if (ss == 0) { /* First time through or recovery */ + char* base = (char*)CALL_MORECORE(0); + if (base != CMFAIL) { + asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE); + /* Adjust to end on a page boundary */ + if (!is_page_aligned(base)) { + asize += (page_align((size_t)base) - (size_t)base); +#if USE_MAX_ALLOWED_FOOTPRINT + /* If the alignment pushes us over max_allowed_footprint, + * poison the upcoming call to MORECORE and continue. + */ + { + size_t new_footprint = m->footprint + asize; + if (new_footprint <= m->footprint || /* Check for wrap around 0 */ + new_footprint > m->max_allowed_footprint) { + asize = HALF_MAX_SIZE_T; + } + } +#endif + } + /* Can't call MORECORE if size is negative when treated as signed */ + if (asize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(asize))) == base) { + tbase = base; + tsize = asize; + } + } + } + else { + /* Subtract out existing available top space from MORECORE request. */ + asize = granularity_align(nb - m->topsize + TOP_FOOT_SIZE + SIZE_T_ONE); + /* Use mem here only if it did continuously extend old space */ + if (asize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) { + tbase = br; + tsize = asize; + } + } + + if (tbase == CMFAIL) { /* Cope with partial failure */ + if (br != CMFAIL) { /* Try to use/extend the space we did get */ + if (asize < HALF_MAX_SIZE_T && + asize < nb + TOP_FOOT_SIZE + SIZE_T_ONE) { + size_t esize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE - asize); + if (esize < HALF_MAX_SIZE_T) { + char* end = (char*)CALL_MORECORE(esize); + if (end != CMFAIL) + asize += esize; + else { /* Can't use; try to release */ + CALL_MORECORE(-asize); + br = CMFAIL; + } + } + } + } + if (br != CMFAIL) { /* Use the space we did get */ + tbase = br; + tsize = asize; + } + else + disable_contiguous(m); /* Don't try contiguous path in the future */ + } + + RELEASE_MORECORE_LOCK(); + } + + if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ + size_t req = nb + TOP_FOOT_SIZE + SIZE_T_ONE; + size_t rsize = granularity_align(req); + if (rsize > nb) { /* Fail if wraps around zero */ + char* mp = (char*)(CALL_MMAP(rsize)); + if (mp != CMFAIL) { + tbase = mp; + tsize = rsize; + mmap_flag = IS_MMAPPED_BIT; + } + } + } + + if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ + size_t asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE); + if (asize < HALF_MAX_SIZE_T) { + char* br = CMFAIL; + char* end = CMFAIL; + ACQUIRE_MORECORE_LOCK(); + br = (char*)(CALL_MORECORE(asize)); + end = (char*)(CALL_MORECORE(0)); + RELEASE_MORECORE_LOCK(); + if (br != CMFAIL && end != CMFAIL && br < end) { + size_t ssize = end - br; + if (ssize > nb + TOP_FOOT_SIZE) { + tbase = br; + tsize = ssize; + } + } + } + } + + if (tbase != CMFAIL) { + + if ((m->footprint += tsize) > m->max_footprint) + m->max_footprint = m->footprint; + + if (!is_initialized(m)) { /* first-time initialization */ + m->seg.base = m->least_addr = tbase; + m->seg.size = tsize; + m->seg.sflags = mmap_flag; + m->magic = mparams.magic; + init_bins(m); + if (is_global(m)) + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + else { + /* Offset top by embedded malloc_state */ + mchunkptr mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE); + } + } + + else { + /* Try to merge with an existing segment */ + msegmentptr sp = &m->seg; + while (sp != 0 && tbase != sp->base + sp->size) + sp = sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & IS_MMAPPED_BIT) == mmap_flag && + segment_holds(sp, m->top)) { /* append */ + sp->size += tsize; + init_top(m, m->top, m->topsize + tsize); + } + else { + if (tbase < m->least_addr) + m->least_addr = tbase; + sp = &m->seg; + while (sp != 0 && sp->base != tbase + tsize) + sp = sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) { + char* oldbase = sp->base; + sp->base = tbase; + sp->size += tsize; + return prepend_alloc(m, tbase, oldbase, nb); + } + else + add_segment(m, tbase, tsize, mmap_flag); + } + } + + if (nb < m->topsize) { /* Allocate from new or extended top space */ + size_t rsize = m->topsize -= nb; + mchunkptr p = m->top; + mchunkptr r = m->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + check_top_chunk(m, m->top); + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); + } + } + + MALLOC_FAILURE_ACTION; + return 0; +} + +/* ----------------------- system deallocation -------------------------- */ + +/* Unmap and unlink any mmapped segments that don't contain used chunks */ +static size_t release_unused_segments(mstate m) { + size_t released = 0; + msegmentptr pred = &m->seg; + msegmentptr sp = pred->next; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + msegmentptr next = sp->next; + if (is_mmapped_segment(sp) && !is_extern_segment(sp)) { + mchunkptr p = align_as_chunk(base); + size_t psize = chunksize(p); + /* Can unmap if first chunk holds entire segment and not pinned */ + if (!cinuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) { + tchunkptr tp = (tchunkptr)p; + assert(segment_holds(sp, (char*)sp)); + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + else { + unlink_large_chunk(m, tp); + } + if (CALL_MUNMAP(base, size) == 0) { + released += size; + m->footprint -= size; + /* unlink obsoleted record */ + sp = pred; + sp->next = next; + } + else { /* back out if cannot unmap */ + insert_large_chunk(m, tp, psize); + } + } + } + pred = sp; + sp = next; + } + return released; +} + +static int sys_trim(mstate m, size_t pad) { + size_t released = 0; + if (pad < MAX_REQUEST && is_initialized(m)) { + pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ + + if (m->topsize > pad) { + /* Shrink top space in granularity-size units, keeping at least one */ + size_t unit = mparams.granularity; + size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - + SIZE_T_ONE) * unit; + msegmentptr sp = segment_holding(m, (char*)m->top); + + if (!is_extern_segment(sp)) { + if (is_mmapped_segment(sp)) { + if (HAVE_MMAP && + sp->size >= extra && + !has_segment_link(m, sp)) { /* can't shrink if pinned */ + size_t newsize = sp->size - extra; + /* Prefer mremap, fall back to munmap */ + if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) || + (CALL_MUNMAP(sp->base + newsize, extra) == 0)) { + released = extra; + } + } + } + else if (HAVE_MORECORE) { + if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ + extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; + ACQUIRE_MORECORE_LOCK(); + { + /* Make sure end of memory is where we last set it. */ + char* old_br = (char*)(CALL_MORECORE(0)); + if (old_br == sp->base + sp->size) { + char* rel_br = (char*)(CALL_MORECORE(-extra)); + char* new_br = (char*)(CALL_MORECORE(0)); + if (rel_br != CMFAIL && new_br < old_br) + released = old_br - new_br; + } + } + RELEASE_MORECORE_LOCK(); + } + } + + if (released != 0) { + sp->size -= released; + m->footprint -= released; + init_top(m, m->top, m->topsize - released); + check_top_chunk(m, m->top); + } + } + + /* Unmap any unused mmapped segments */ + if (HAVE_MMAP) + released += release_unused_segments(m); + + /* On failure, disable autotrim to avoid repeated failed future calls */ + if (released == 0) + m->trim_check = MAX_SIZE_T; + } + + return (released != 0)? 1 : 0; +} + +/* ---------------------------- malloc support --------------------------- */ + +/* allocate a large request from the best fitting chunk in a treebin */ +static void* tmalloc_large(mstate m, size_t nb) { + tchunkptr v = 0; + size_t rsize = -nb; /* Unsigned negation */ + tchunkptr t; + bindex_t idx; + compute_tree_index(nb, idx); + + if ((t = *treebin_at(m, idx)) != 0) { + /* Traverse tree for this bin looking for node with size == nb */ + size_t sizebits = nb << leftshift_for_tree_index(idx); + tchunkptr rst = 0; /* The deepest untaken right subtree */ + for (;;) { + tchunkptr rt; + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + v = t; + if ((rsize = trem) == 0) + break; + } + rt = t->child[1]; + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + if (rt != 0 && rt != t) + rst = rt; + if (t == 0) { + t = rst; /* set t to least subtree holding sizes > nb */ + break; + } + sizebits <<= 1; + } + } + + if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */ + binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap; + if (leftbits != 0) { + bindex_t i; + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + t = *treebin_at(m, i); + } + } + + while (t != 0) { /* find smallest of tree or subtree */ + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + t = leftmost_child(t); + } + + /* If dv is a better fit, return 0 so malloc will use it */ + if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { + if (RTCHECK(ok_address(m, v))) { /* split */ + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + insert_chunk(m, r, rsize); + } + return chunk2mem(v); + } + } + CORRUPTION_ERROR_ACTION(m); + } + return 0; +} + +/* allocate a small request from the best fitting chunk in a treebin */ +static void* tmalloc_small(mstate m, size_t nb) { + tchunkptr t, v; + size_t rsize; + bindex_t i; + binmap_t leastbit = least_bit(m->treemap); + compute_bit2idx(leastbit, i); + + v = t = *treebin_at(m, i); + rsize = chunksize(t) - nb; + + while ((t = leftmost_child(t)) != 0) { + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + } + + if (RTCHECK(ok_address(m, v))) { + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(m, r, rsize); + } + return chunk2mem(v); + } + } + + CORRUPTION_ERROR_ACTION(m); + return 0; +} + +/* --------------------------- realloc support --------------------------- */ + +static void* internal_realloc(mstate m, void* oldmem, size_t bytes) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + return 0; + } + if (!PREACTION(m)) { + mchunkptr oldp = mem2chunk(oldmem); + size_t oldsize = chunksize(oldp); + mchunkptr next = chunk_plus_offset(oldp, oldsize); + mchunkptr newp = 0; + void* extra = 0; + + /* Try to either shrink or extend into top. Else malloc-copy-free */ + + if (RTCHECK(ok_address(m, oldp) && ok_cinuse(oldp) && + ok_next(oldp, next) && ok_pinuse(next))) { + size_t nb = request2size(bytes); + if (is_mmapped(oldp)) + newp = mmap_resize(m, oldp, nb); + else if (oldsize >= nb) { /* already big enough */ + size_t rsize = oldsize - nb; + newp = oldp; + if (rsize >= MIN_CHUNK_SIZE) { + mchunkptr remainder = chunk_plus_offset(newp, nb); + set_inuse(m, newp, nb); + set_inuse(m, remainder, rsize); + extra = chunk2mem(remainder); + } + } + else if (next == m->top && oldsize + m->topsize > nb) { + /* Expand into top */ + size_t newsize = oldsize + m->topsize; + size_t newtopsize = newsize - nb; + mchunkptr newtop = chunk_plus_offset(oldp, nb); + set_inuse(m, oldp, nb); + newtop->head = newtopsize |PINUSE_BIT; + m->top = newtop; + m->topsize = newtopsize; + newp = oldp; + } + } + else { + USAGE_ERROR_ACTION(m, oldmem); + POSTACTION(m); + return 0; + } + + POSTACTION(m); + + if (newp != 0) { + if (extra != 0) { + internal_free(m, extra); + } + check_inuse_chunk(m, newp); + return chunk2mem(newp); + } + else { + void* newmem = internal_malloc(m, bytes); + if (newmem != 0) { + size_t oc = oldsize - overhead_for(oldp); + memcpy(newmem, oldmem, (oc < bytes)? oc : bytes); + internal_free(m, oldmem); + } + return newmem; + } + } + return 0; +} + +/* --------------------------- memalign support -------------------------- */ + +static void* internal_memalign(mstate m, size_t alignment, size_t bytes) { + if (alignment <= MALLOC_ALIGNMENT) /* Can just use malloc */ + return internal_malloc(m, bytes); + if (alignment < MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */ + alignment = MIN_CHUNK_SIZE; + if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */ + size_t a = MALLOC_ALIGNMENT << 1; + while (a < alignment) a <<= 1; + alignment = a; + } + + if (bytes >= MAX_REQUEST - alignment) { + if (m != 0) { /* Test isn't needed but avoids compiler warning */ + MALLOC_FAILURE_ACTION; + } + } + else { + size_t nb = request2size(bytes); + size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD; + char* mem = (char*)internal_malloc(m, req); + if (mem != 0) { + void* leader = 0; + void* trailer = 0; + mchunkptr p = mem2chunk(mem); + + if (PREACTION(m)) return 0; + if ((((size_t)(mem)) % alignment) != 0) { /* misaligned */ + /* + Find an aligned spot inside chunk. Since we need to give + back leading space in a chunk of at least MIN_CHUNK_SIZE, if + the first calculation places us at a spot with less than + MIN_CHUNK_SIZE leader, we can move to the next aligned spot. + We've allocated enough total room so that this is always + possible. + */ + char* br = (char*)mem2chunk((size_t)(((size_t)(mem + + alignment - + SIZE_T_ONE)) & + -alignment)); + char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)? + br : br+alignment; + mchunkptr newp = (mchunkptr)pos; + size_t leadsize = pos - (char*)(p); + size_t newsize = chunksize(p) - leadsize; + + if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */ + newp->prev_foot = p->prev_foot + leadsize; + newp->head = (newsize|CINUSE_BIT); + } + else { /* Otherwise, give back leader, use the rest */ + set_inuse(m, newp, newsize); + set_inuse(m, p, leadsize); + leader = chunk2mem(p); + } + p = newp; + } + + /* Give back spare room at the end */ + if (!is_mmapped(p)) { + size_t size = chunksize(p); + if (size > nb + MIN_CHUNK_SIZE) { + size_t remainder_size = size - nb; + mchunkptr remainder = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, remainder, remainder_size); + trailer = chunk2mem(remainder); + } + } + + assert (chunksize(p) >= nb); + assert((((size_t)(chunk2mem(p))) % alignment) == 0); + check_inuse_chunk(m, p); + POSTACTION(m); + if (leader != 0) { + internal_free(m, leader); + } + if (trailer != 0) { + internal_free(m, trailer); + } + return chunk2mem(p); + } + } + return 0; +} + +/* ------------------------ comalloc/coalloc support --------------------- */ + +static void** ialloc(mstate m, + size_t n_elements, + size_t* sizes, + int opts, + void* chunks[]) { + /* + This provides common support for independent_X routines, handling + all of the combinations that can result. + + The opts arg has: + bit 0 set if all elements are same size (using sizes[0]) + bit 1 set if elements should be zeroed + */ + + size_t element_size; /* chunksize of each element, if all same */ + size_t contents_size; /* total size of elements */ + size_t array_size; /* request size of pointer array */ + void* mem; /* malloced aggregate space */ + mchunkptr p; /* corresponding chunk */ + size_t remainder_size; /* remaining bytes while splitting */ + void** marray; /* either "chunks" or malloced ptr array */ + mchunkptr array_chunk; /* chunk for malloced ptr array */ + flag_t was_enabled; /* to disable mmap */ + size_t size; + size_t i; + + /* compute array length, if needed */ + if (chunks != 0) { + if (n_elements == 0) + return chunks; /* nothing to do */ + marray = chunks; + array_size = 0; + } + else { + /* if empty req, must still return chunk representing empty array */ + if (n_elements == 0) + return (void**)internal_malloc(m, 0); + marray = 0; + array_size = request2size(n_elements * (sizeof(void*))); + } + + /* compute total element size */ + if (opts & 0x1) { /* all-same-size */ + element_size = request2size(*sizes); + contents_size = n_elements * element_size; + } + else { /* add up all the sizes */ + element_size = 0; + contents_size = 0; + for (i = 0; i != n_elements; ++i) + contents_size += request2size(sizes[i]); + } + + size = contents_size + array_size; + + /* + Allocate the aggregate chunk. First disable direct-mmapping so + malloc won't use it, since we would not be able to later + free/realloc space internal to a segregated mmap region. + */ + was_enabled = use_mmap(m); + disable_mmap(m); + mem = internal_malloc(m, size - CHUNK_OVERHEAD); + if (was_enabled) + enable_mmap(m); + if (mem == 0) + return 0; + + if (PREACTION(m)) return 0; + p = mem2chunk(mem); + remainder_size = chunksize(p); + + assert(!is_mmapped(p)); + + if (opts & 0x2) { /* optionally clear the elements */ + memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size); + } + + /* If not provided, allocate the pointer array as final part of chunk */ + if (marray == 0) { + size_t array_chunk_size; + array_chunk = chunk_plus_offset(p, contents_size); + array_chunk_size = remainder_size - contents_size; + marray = (void**) (chunk2mem(array_chunk)); + set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size); + remainder_size = contents_size; + } + + /* split out elements */ + for (i = 0; ; ++i) { + marray[i] = chunk2mem(p); + if (i != n_elements-1) { + if (element_size != 0) + size = element_size; + else + size = request2size(sizes[i]); + remainder_size -= size; + set_size_and_pinuse_of_inuse_chunk(m, p, size); + p = chunk_plus_offset(p, size); + } + else { /* the final element absorbs any overallocation slop */ + set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size); + break; + } + } + +#if DEBUG + if (marray != chunks) { + /* final element must have exactly exhausted chunk */ + if (element_size != 0) { + assert(remainder_size == element_size); + } + else { + assert(remainder_size == request2size(sizes[i])); + } + check_inuse_chunk(m, mem2chunk(marray)); + } + for (i = 0; i != n_elements; ++i) + check_inuse_chunk(m, mem2chunk(marray[i])); + +#endif /* DEBUG */ + + POSTACTION(m); + return marray; +} + + +/* -------------------------- public routines ---------------------------- */ + +#if !ONLY_MSPACES + +void* dlmalloc(size_t bytes) { + /* + Basic algorithm: + If a small request (< 256 bytes minus per-chunk overhead): + 1. If one exists, use a remainderless chunk in associated smallbin. + (Remainderless means that there are too few excess bytes to + represent as a chunk.) + 2. If it is big enough, use the dv chunk, which is normally the + chunk adjacent to the one used for the most recent small request. + 3. If one exists, split the smallest available chunk in a bin, + saving remainder in dv. + 4. If it is big enough, use the top chunk. + 5. If available, get memory from system and use it + Otherwise, for a large request: + 1. Find the smallest available binned chunk that fits, and use it + if it is better fitting than dv chunk, splitting if necessary. + 2. If better fitting than any binned chunk, use the dv chunk. + 3. If it is big enough, use the top chunk. + 4. If request size >= mmap threshold, try to directly mmap this chunk. + 5. If available, get memory from system and use it + + The ugly goto's here ensure that postaction occurs along all paths. + */ + + if (!PREACTION(gm)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = gm->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(gm, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(gm, b, p, idx); + set_inuse_and_pinuse(gm, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb > gm->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(gm, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(gm, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(gm, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(gm, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + + if (nb <= gm->dvsize) { + size_t rsize = gm->dvsize - nb; + mchunkptr p = gm->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = gm->dv = chunk_plus_offset(p, nb); + gm->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + } + else { /* exhaust dv */ + size_t dvs = gm->dvsize; + gm->dvsize = 0; + gm->dv = 0; + set_inuse_and_pinuse(gm, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb < gm->topsize) { /* Split top */ + size_t rsize = gm->topsize -= nb; + mchunkptr p = gm->top; + mchunkptr r = gm->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + mem = chunk2mem(p); + check_top_chunk(gm, gm->top); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + mem = sys_alloc(gm, nb); + + postaction: + POSTACTION(gm); + return mem; + } + + return 0; +} + +void dlfree(void* mem) { + /* + Consolidate freed chunks with preceeding or succeeding bordering + free chunks, if they exist, and then place in a bin. Intermixed + with special cases for top, dv, mmapped chunks, and usage errors. + */ + + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } +#else /* FOOTERS */ +#define fm gm +#endif /* FOOTERS */ + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if ((prevsize & IS_MMAPPED_BIT) != 0) { + prevsize &= ~IS_MMAPPED_BIT; + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + insert_chunk(fm, p, psize); + check_free_chunk(fm, p); + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +#if !FOOTERS +#undef fm +#endif /* FOOTERS */ +} + +void* dlcalloc(size_t n_elements, size_t elem_size) { + void *mem; + if (n_elements && MAX_SIZE_T / n_elements < elem_size) { + /* Fail on overflow */ + MALLOC_FAILURE_ACTION; + return NULL; + } + elem_size *= n_elements; + mem = dlmalloc(elem_size); + if (mem && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, elem_size); + return mem; +} + +void* dlrealloc(void* oldmem, size_t bytes) { + if (oldmem == 0) + return dlmalloc(bytes); +#ifdef REALLOC_ZERO_BYTES_FREES + if (bytes == 0) { + dlfree(oldmem); + return 0; + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(mem2chunk(oldmem)); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + return internal_realloc(m, oldmem, bytes); + } +} + +void* dlmemalign(size_t alignment, size_t bytes) { + return internal_memalign(gm, alignment, bytes); +} + +void** dlindependent_calloc(size_t n_elements, size_t elem_size, + void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + return ialloc(gm, n_elements, &sz, 3, chunks); +} + +void** dlindependent_comalloc(size_t n_elements, size_t sizes[], + void* chunks[]) { + return ialloc(gm, n_elements, sizes, 0, chunks); +} + +void* dlvalloc(size_t bytes) { + size_t pagesz; + init_mparams(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, bytes); +} + +void* dlpvalloc(size_t bytes) { + size_t pagesz; + init_mparams(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); +} + +int dlmalloc_trim(size_t pad) { + int result = 0; + if (!PREACTION(gm)) { + result = sys_trim(gm, pad); + POSTACTION(gm); + } + return result; +} + +size_t dlmalloc_footprint(void) { + return gm->footprint; +} + +#if USE_MAX_ALLOWED_FOOTPRINT +size_t dlmalloc_max_allowed_footprint(void) { + return gm->max_allowed_footprint; +} + +void dlmalloc_set_max_allowed_footprint(size_t bytes) { + if (bytes > gm->footprint) { + /* Increase the size in multiples of the granularity, + * which is the smallest unit we request from the system. + */ + gm->max_allowed_footprint = gm->footprint + + granularity_align(bytes - gm->footprint); + } + else { + //TODO: allow for reducing the max footprint + gm->max_allowed_footprint = gm->footprint; + } +} +#endif + +size_t dlmalloc_max_footprint(void) { + return gm->max_footprint; +} + +#if !NO_MALLINFO +struct mallinfo dlmallinfo(void) { + return internal_mallinfo(gm); +} +#endif /* NO_MALLINFO */ + +void dlmalloc_stats() { + internal_malloc_stats(gm); +} + +size_t dlmalloc_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (cinuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +int dlmallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* !ONLY_MSPACES */ + +/* ----------------------------- user mspaces ---------------------------- */ + +#if MSPACES + +static mstate init_user_mstate(char* tbase, size_t tsize) { + size_t msize = pad_request(sizeof(struct malloc_state)); + mchunkptr mn; + mchunkptr msp = align_as_chunk(tbase); + mstate m = (mstate)(chunk2mem(msp)); + memset(m, 0, msize); + INITIAL_LOCK(&m->mutex); + msp->head = (msize|PINUSE_BIT|CINUSE_BIT); + m->seg.base = m->least_addr = tbase; + m->seg.size = m->footprint = m->max_footprint = tsize; +#if USE_MAX_ALLOWED_FOOTPRINT + m->max_allowed_footprint = MAX_SIZE_T; +#endif + m->magic = mparams.magic; + m->mflags = mparams.default_mflags; + disable_contiguous(m); + init_bins(m); + mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE); + check_top_chunk(m, m->top); + return m; +} + +mspace create_mspace(size_t capacity, int locked) { + mstate m = 0; + size_t msize = pad_request(sizeof(struct malloc_state)); + init_mparams(); /* Ensure pagesize etc initialized */ + + if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + size_t rs = ((capacity == 0)? mparams.granularity : + (capacity + TOP_FOOT_SIZE + msize)); + size_t tsize = granularity_align(rs); + char* tbase = (char*)(CALL_MMAP(tsize)); + if (tbase != CMFAIL) { + m = init_user_mstate(tbase, tsize); + m->seg.sflags = IS_MMAPPED_BIT; + set_lock(m, locked); + } + } + return (mspace)m; +} + +mspace create_mspace_with_base(void* base, size_t capacity, int locked) { + mstate m = 0; + size_t msize = pad_request(sizeof(struct malloc_state)); + init_mparams(); /* Ensure pagesize etc initialized */ + + if (capacity > msize + TOP_FOOT_SIZE && + capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + m = init_user_mstate((char*)base, capacity); + m->seg.sflags = EXTERN_BIT; + set_lock(m, locked); + } + return (mspace)m; +} + +size_t destroy_mspace(mspace msp) { + size_t freed = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + msegmentptr sp = &ms->seg; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + flag_t flag = sp->sflags; + sp = sp->next; + if ((flag & IS_MMAPPED_BIT) && !(flag & EXTERN_BIT) && + CALL_MUNMAP(base, size) == 0) + freed += size; + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return freed; +} + +/* + mspace versions of routines are near-clones of the global + versions. This is not so nice but better than the alternatives. +*/ + + +void* mspace_malloc(mspace msp, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (!PREACTION(ms)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = ms->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(ms, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(ms, b, p, idx); + set_inuse_and_pinuse(ms, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb > ms->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(ms, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(ms, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(ms, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(ms, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + + if (nb <= ms->dvsize) { + size_t rsize = ms->dvsize - nb; + mchunkptr p = ms->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = ms->dv = chunk_plus_offset(p, nb); + ms->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + } + else { /* exhaust dv */ + size_t dvs = ms->dvsize; + ms->dvsize = 0; + ms->dv = 0; + set_inuse_and_pinuse(ms, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb < ms->topsize) { /* Split top */ + size_t rsize = ms->topsize -= nb; + mchunkptr p = ms->top; + mchunkptr r = ms->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + mem = chunk2mem(p); + check_top_chunk(ms, ms->top); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + mem = sys_alloc(ms, nb); + + postaction: + POSTACTION(ms); + return mem; + } + + return 0; +} + +void mspace_free(mspace msp, void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); +#else /* FOOTERS */ + mstate fm = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if ((prevsize & IS_MMAPPED_BIT) != 0) { + prevsize &= ~IS_MMAPPED_BIT; + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + insert_chunk(fm, p, psize); + check_free_chunk(fm, p); + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +} + +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) { + void *mem; + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (n_elements && MAX_SIZE_T / n_elements < elem_size) { + /* Fail on overflow */ + MALLOC_FAILURE_ACTION; + return NULL; + } + elem_size *= n_elements; + mem = internal_malloc(ms, elem_size); + if (mem && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, elem_size); + return mem; +} + +void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) { + if (oldmem == 0) + return mspace_malloc(msp, bytes); +#ifdef REALLOC_ZERO_BYTES_FREES + if (bytes == 0) { + mspace_free(msp, oldmem); + return 0; + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { +#if FOOTERS + mchunkptr p = mem2chunk(oldmem); + mstate ms = get_mstate_for(p); +#else /* FOOTERS */ + mstate ms = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return internal_realloc(ms, oldmem, bytes); + } +} + +#if ANDROID +void* mspace_merge_objects(mspace msp, void* mema, void* memb) +{ + /* PREACTION/POSTACTION aren't necessary because we are only + modifying fields of inuse chunks owned by the current thread, in + which case no other malloc operations can touch them. + */ + if (mema == NULL || memb == NULL) { + return NULL; + } + mchunkptr pa = mem2chunk(mema); + mchunkptr pb = mem2chunk(memb); + +#if FOOTERS + mstate fm = get_mstate_for(pa); +#else /* FOOTERS */ + mstate fm = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, pa); + return NULL; + } + check_inuse_chunk(fm, pa); + if (RTCHECK(ok_address(fm, pa) && ok_cinuse(pa))) { + if (next_chunk(pa) != pb) { + /* Since pb may not be in fm, we can't check ok_address(fm, pb); + since ok_cinuse(pb) would be unsafe before an address check, + return NULL rather than invoke USAGE_ERROR_ACTION if pb is not + in use or is a bogus address. + */ + return NULL; + } + /* Since b follows a, they share the mspace. */ +#if FOOTERS + assert(fm == get_mstate_for(pb)); +#endif /* FOOTERS */ + check_inuse_chunk(fm, pb); + if (RTCHECK(ok_address(fm, pb) && ok_cinuse(pb))) { + size_t sz = chunksize(pb); + pa->head += sz; + /* Make sure pa still passes. */ + check_inuse_chunk(fm, pa); + return mema; + } + else { + USAGE_ERROR_ACTION(fm, pb); + return NULL; + } + } + else { + USAGE_ERROR_ACTION(fm, pa); + return NULL; + } +} +#endif /* ANDROID */ + +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return internal_memalign(ms, alignment, bytes); +} + +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, &sz, 3, chunks); +} + +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, sizes, 0, chunks); +} + +int mspace_trim(mspace msp, size_t pad) { + int result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + result = sys_trim(ms, pad); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +void mspace_malloc_stats(mspace msp) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + internal_malloc_stats(ms); + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} + +size_t mspace_footprint(mspace msp) { + size_t result; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +#if USE_MAX_ALLOWED_FOOTPRINT +size_t mspace_max_allowed_footprint(mspace msp) { + size_t result; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->max_allowed_footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +void mspace_set_max_allowed_footprint(mspace msp, size_t bytes) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (bytes > ms->footprint) { + /* Increase the size in multiples of the granularity, + * which is the smallest unit we request from the system. + */ + ms->max_allowed_footprint = ms->footprint + + granularity_align(bytes - ms->footprint); + } + else { + //TODO: allow for reducing the max footprint + ms->max_allowed_footprint = ms->footprint; + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} +#endif + +size_t mspace_max_footprint(mspace msp) { + size_t result; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->max_footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + + +#if !NO_MALLINFO +struct mallinfo mspace_mallinfo(mspace msp) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + } + return internal_mallinfo(ms); +} +#endif /* NO_MALLINFO */ + +int mspace_mallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* MSPACES */ + +#if MSPACES && ONLY_MSPACES +void mspace_walk_free_pages(mspace msp, + void(*handler)(void *start, void *end, void *arg), void *harg) +{ + mstate m = (mstate)msp; + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m,m); + return; + } +#else +void dlmalloc_walk_free_pages(void(*handler)(void *start, void *end, void *arg), + void *harg) +{ + mstate m = (mstate)gm; +#endif + if (!PREACTION(m)) { + if (is_initialized(m)) { + msegmentptr s = &m->seg; + while (s != 0) { + mchunkptr p = align_as_chunk(s->base); + while (segment_holds(s, p) && + p != m->top && p->head != FENCEPOST_HEAD) { + void *chunkptr, *userptr; + size_t chunklen, userlen; + chunkptr = p; + chunklen = chunksize(p); + if (!cinuse(p)) { + void *start; + if (is_small(chunklen)) { + start = (void *)(p + 1); + } + else { + start = (void *)((tchunkptr)p + 1); + } + handler(start, next_chunk(p), harg); + } + p = next_chunk(p); + } + if (p == m->top) { + handler((void *)(p + 1), next_chunk(p), harg); + } + s = s->next; + } + } + POSTACTION(m); + } +} + + +#if MSPACES && ONLY_MSPACES +void mspace_walk_heap(mspace msp, + void(*handler)(const void *chunkptr, size_t chunklen, + const void *userptr, size_t userlen, + void *arg), + void *harg) +{ + msegmentptr s; + mstate m = (mstate)msp; + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m,m); + return; + } +#else +void dlmalloc_walk_heap(void(*handler)(const void *chunkptr, size_t chunklen, + const void *userptr, size_t userlen, + void *arg), + void *harg) +{ + msegmentptr s; + mstate m = (mstate)gm; +#endif + + s = &m->seg; + while (s != 0) { + mchunkptr p = align_as_chunk(s->base); + while (segment_holds(s, p) && + p != m->top && p->head != FENCEPOST_HEAD) { + void *chunkptr, *userptr; + size_t chunklen, userlen; + chunkptr = p; + chunklen = chunksize(p); + if (cinuse(p)) { + userptr = chunk2mem(p); + userlen = chunklen - overhead_for(p); + } + else { + userptr = NULL; + userlen = 0; + } + handler(chunkptr, chunklen, userptr, userlen, harg); + p = next_chunk(p); + } + if (p == m->top) { + /* The top chunk is just a big free chunk for our purposes. + */ + handler(m->top, m->topsize, NULL, 0, harg); + } + s = s->next; + } +} + +/* -------------------- Alternative MORECORE functions ------------------- */ + +/* + Guidelines for creating a custom version of MORECORE: + + * For best performance, MORECORE should allocate in multiples of pagesize. + * MORECORE may allocate more memory than requested. (Or even less, + but this will usually result in a malloc failure.) + * MORECORE must not allocate memory when given argument zero, but + instead return one past the end address of memory from previous + nonzero call. + * For best performance, consecutive calls to MORECORE with positive + arguments should return increasing addresses, indicating that + space has been contiguously extended. + * Even though consecutive calls to MORECORE need not return contiguous + addresses, it must be OK for malloc'ed chunks to span multiple + regions in those cases where they do happen to be contiguous. + * MORECORE need not handle negative arguments -- it may instead + just return MFAIL when given negative arguments. + Negative arguments are always multiples of pagesize. MORECORE + must not misinterpret negative args as large positive unsigned + args. You can suppress all such calls from even occurring by defining + MORECORE_CANNOT_TRIM, + + As an example alternative MORECORE, here is a custom allocator + kindly contributed for pre-OSX macOS. It uses virtually but not + necessarily physically contiguous non-paged memory (locked in, + present and won't get swapped out). You can use it by uncommenting + this section, adding some #includes, and setting up the appropriate + defines above: + + #define MORECORE osMoreCore + + There is also a shutdown routine that should somehow be called for + cleanup upon program exit. + + #define MAX_POOL_ENTRIES 100 + #define MINIMUM_MORECORE_SIZE (64 * 1024U) + static int next_os_pool; + void *our_os_pools[MAX_POOL_ENTRIES]; + + void *osMoreCore(int size) + { + void *ptr = 0; + static void *sbrk_top = 0; + + if (size > 0) + { + if (size < MINIMUM_MORECORE_SIZE) + size = MINIMUM_MORECORE_SIZE; + if (CurrentExecutionLevel() == kTaskLevel) + ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); + if (ptr == 0) + { + return (void *) MFAIL; + } + // save ptrs so they can be freed during cleanup + our_os_pools[next_os_pool] = ptr; + next_os_pool++; + ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); + sbrk_top = (char *) ptr + size; + return ptr; + } + else if (size < 0) + { + // we don't currently support shrink behavior + return (void *) MFAIL; + } + else + { + return sbrk_top; + } + } + + // cleanup any allocated memory pools + // called as last thing before shutting down driver + + void osCleanupMem(void) + { + void **ptr; + + for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) + if (*ptr) + { + PoolDeallocate(*ptr); + *ptr = 0; + } + } + +*/ + + +/* ----------------------------------------------------------------------- +History: + V2.8.3 Thu Sep 22 11:16:32 2005 Doug Lea (dl at gee) + * Add max_footprint functions + * Ensure all appropriate literals are size_t + * Fix conditional compilation problem for some #define settings + * Avoid concatenating segments with the one provided + in create_mspace_with_base + * Rename some variables to avoid compiler shadowing warnings + * Use explicit lock initialization. + * Better handling of sbrk interference. + * Simplify and fix segment insertion, trimming and mspace_destroy + * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x + * Thanks especially to Dennis Flanagan for help on these. + + V2.8.2 Sun Jun 12 16:01:10 2005 Doug Lea (dl at gee) + * Fix memalign brace error. + + V2.8.1 Wed Jun 8 16:11:46 2005 Doug Lea (dl at gee) + * Fix improper #endif nesting in C++ + * Add explicit casts needed for C++ + + V2.8.0 Mon May 30 14:09:02 2005 Doug Lea (dl at gee) + * Use trees for large bins + * Support mspaces + * Use segments to unify sbrk-based and mmap-based system allocation, + removing need for emulation on most platforms without sbrk. + * Default safety checks + * Optional footer checks. Thanks to William Robertson for the idea. + * Internal code refactoring + * Incorporate suggestions and platform-specific changes. + Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas, + Aaron Bachmann, Emery Berger, and others. + * Speed up non-fastbin processing enough to remove fastbins. + * Remove useless cfree() to avoid conflicts with other apps. + * Remove internal memcpy, memset. Compilers handle builtins better. + * Remove some options that no one ever used and rename others. + + V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) + * Fix malloc_state bitmap array misdeclaration + + V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) + * Allow tuning of FIRST_SORTED_BIN_SIZE + * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. + * Better detection and support for non-contiguousness of MORECORE. + Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger + * Bypass most of malloc if no frees. Thanks To Emery Berger. + * Fix freeing of old top non-contiguous chunk im sysmalloc. + * Raised default trim and map thresholds to 256K. + * Fix mmap-related #defines. Thanks to Lubos Lunak. + * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. + * Branch-free bin calculation + * Default trim and mmap thresholds now 256K. + + V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) + * Introduce independent_comalloc and independent_calloc. + Thanks to Michael Pachos for motivation and help. + * Make optional .h file available + * Allow > 2GB requests on 32bit systems. + * new WIN32 sbrk, mmap, munmap, lock code from <Walter@GeNeSys-e.de>. + Thanks also to Andreas Mueller <a.mueller at paradatec.de>, + and Anonymous. + * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for + helping test this.) + * memalign: check alignment arg + * realloc: don't try to shift chunks backwards, since this + leads to more fragmentation in some programs and doesn't + seem to help in any others. + * Collect all cases in malloc requiring system memory into sysmalloc + * Use mmap as backup to sbrk + * Place all internal state in malloc_state + * Introduce fastbins (although similar to 2.5.1) + * Many minor tunings and cosmetic improvements + * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK + * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS + Thanks to Tony E. Bennett <tbennett@nvidia.com> and others. + * Include errno.h to support default failure action. + + V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) + * return null for negative arguments + * Added Several WIN32 cleanups from Martin C. Fong <mcfong at yahoo.com> + * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' + (e.g. WIN32 platforms) + * Cleanup header file inclusion for WIN32 platforms + * Cleanup code to avoid Microsoft Visual C++ compiler complaints + * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing + memory allocation routines + * Set 'malloc_getpagesize' for WIN32 platforms (needs more work) + * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to + usage of 'assert' in non-WIN32 code + * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to + avoid infinite loop + * Always call 'fREe()' rather than 'free()' + + V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) + * Fixed ordering problem with boundary-stamping + + V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) + * Added pvalloc, as recommended by H.J. Liu + * Added 64bit pointer support mainly from Wolfram Gloger + * Added anonymously donated WIN32 sbrk emulation + * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen + * malloc_extend_top: fix mask error that caused wastage after + foreign sbrks + * Add linux mremap support code from HJ Liu + + V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) + * Integrated most documentation with the code. + * Add support for mmap, with help from + Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Use last_remainder in more cases. + * Pack bins using idea from colin@nyx10.cs.du.edu + * Use ordered bins instead of best-fit threshhold + * Eliminate block-local decls to simplify tracing and debugging. + * Support another case of realloc via move into top + * Fix error occuring when initial sbrk_base not word-aligned. + * Rely on page size for units instead of SBRK_UNIT to + avoid surprises about sbrk alignment conventions. + * Add mallinfo, mallopt. Thanks to Raymond Nijssen + (raymond@es.ele.tue.nl) for the suggestion. + * Add `pad' argument to malloc_trim and top_pad mallopt parameter. + * More precautions for cases where other routines call sbrk, + courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Added macros etc., allowing use in linux libc from + H.J. Lu (hjl@gnu.ai.mit.edu) + * Inverted this history list + + V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) + * Re-tuned and fixed to behave more nicely with V2.6.0 changes. + * Removed all preallocation code since under current scheme + the work required to undo bad preallocations exceeds + the work saved in good cases for most test programs. + * No longer use return list or unconsolidated bins since + no scheme using them consistently outperforms those that don't + given above changes. + * Use best fit for very large chunks to prevent some worst-cases. + * Added some support for debugging + + V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) + * Removed footers when chunks are in use. Thanks to + Paul Wilson (wilson@cs.texas.edu) for the suggestion. + + V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) + * Added malloc_trim, with help from Wolfram Gloger + (wmglo@Dent.MED.Uni-Muenchen.DE). + + V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) + + V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) + * realloc: try to expand in both directions + * malloc: swap order of clean-bin strategy; + * realloc: only conditionally expand backwards + * Try not to scavenge used bins + * Use bin counts as a guide to preallocation + * Occasionally bin return list chunks in first scan + * Add a few optimizations from colin@nyx10.cs.du.edu + + V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) + * faster bin computation & slightly different binning + * merged all consolidations to one part of malloc proper + (eliminating old malloc_find_space & malloc_clean_bin) + * Scan 2 returns chunks (not just 1) + * Propagate failure in realloc if malloc returns 0 + * Add stuff to allow compilation on non-ANSI compilers + from kpv@research.att.com + + V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) + * removed potential for odd address access in prev_chunk + * removed dependency on getpagesize.h + * misc cosmetics and a bit more internal documentation + * anticosmetics: mangled names in macros to evade debugger strangeness + * tested on sparc, hp-700, dec-mips, rs6000 + with gcc & native cc (hp, dec only) allowing + Detlefs & Zorn comparison study (in SIGPLAN Notices.) + + Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) + * Based loosely on libg++-1.2X malloc. (It retains some of the overall + structure of old version, but most details differ.) + +*/ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..a638d63 --- /dev/null +++ b/configure.ac @@ -0,0 +1,32 @@ +AC_PREREQ(2.53) +AC_INIT(adb, 2.3.7, http://source.android.com/) +AM_INIT_AUTOMAKE() +AM_CONFIG_HEADER(config.h) +AM_MAINTAINER_MODE + +AC_GNU_SOURCE +AC_ISC_POSIX +AC_PROG_CC +AC_STDC_HEADERS +AC_PROG_LIBTOOL +AC_LIBTOOL_DLOPEN + +dnl Check for zlib. +AC_CHECK_HEADER( + zlib.h, + AC_CHECK_LIB( + z, + inflateEnd, + LIBS="-lz $LIBS", + AC_MSG_ERROR(Required library zlib missing.)), + AC_MSG_ERROR(Header file for required library zlib missing.)) + +# output stuff +AC_CONFIG_FILES([ +Makefile +libzipfile/Makefile +libcutils/Makefile +adb/Makefile +]) + +AC_OUTPUT diff --git a/include/android/log.h b/include/android/log.h new file mode 100644 index 0000000..0ea4c29 --- /dev/null +++ b/include/android/log.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_LOG_H +#define _ANDROID_LOG_H + +/****************************************************************** + * + * IMPORTANT NOTICE: + * + * This file is part of Android's set of stable system headers + * exposed by the Android NDK (Native Development Kit) since + * platform release 1.5 + * + * Third-party source AND binary code relies on the definitions + * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES. + * + * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES) + * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS + * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY + * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES + */ + +/* + * Support routines to send messages to the Android in-kernel log buffer, + * which can later be accessed through the 'logcat' utility. + * + * Each log message must have + * - a priority + * - a log tag + * - some text + * + * The tag normally corresponds to the component that emits the log message, + * and should be reasonably small. + * + * Log message text may be truncated to less than an implementation-specific + * limit (e.g. 1023 characters max). + * + * Note that a newline character ("\n") will be appended automatically to your + * log message, if not already there. It is not possible to send several messages + * and have them appear on a single line in logcat. + * + * PLEASE USE LOGS WITH MODERATION: + * + * - Sending log messages eats CPU and slow down your application and the + * system. + * + * - The circular log buffer is pretty small (<64KB), sending many messages + * might push off other important log messages from the rest of the system. + * + * - In release builds, only send log messages to account for exceptional + * conditions. + * + * NOTE: These functions MUST be implemented by /system/lib/liblog.so + */ + +#include <stdarg.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Android log priority values, in ascending priority order. + */ +typedef enum android_LogPriority { + ANDROID_LOG_UNKNOWN = 0, + ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ + ANDROID_LOG_VERBOSE, + ANDROID_LOG_DEBUG, + ANDROID_LOG_INFO, + ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, + ANDROID_LOG_FATAL, + ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ +} android_LogPriority; + +/* + * Send a simple string to the log. + */ +int __android_log_write(int prio, const char *tag, const char *text); + +/* + * Send a formatted string to the log, used like printf(fmt,...) + */ +int __android_log_print(int prio, const char *tag, const char *fmt, ...) +#if defined(__GNUC__) + __attribute__ ((format(printf, 3, 4))) +#endif + ; + +/* + * A variant of __android_log_print() that takes a va_list to list + * additional parameters. + */ +int __android_log_vprint(int prio, const char *tag, + const char *fmt, va_list ap); + +/* + * Log an assertion failure and SIGTRAP the process to have a chance + * to inspect it, if a debugger is attached. This uses the FATAL priority. + */ +void __android_log_assert(const char *cond, const char *tag, + const char *fmt, ...) +#if defined(__GNUC__) + __attribute__ ((noreturn)) + __attribute__ ((format(printf, 3, 4))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* _ANDROID_LOG_H */ diff --git a/include/cutils/abort_socket.h b/include/cutils/abort_socket.h new file mode 100644 index 0000000..fbb1112 --- /dev/null +++ b/include/cutils/abort_socket.h @@ -0,0 +1,103 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Helper to perform abortable blocking operations on a socket: + * asocket_connect() + * asocket_accept() + * asocket_read() + * asocket_write() + * These calls are similar to the regular syscalls, but can be aborted with: + * asocket_abort() + * + * Calling close() on a regular POSIX socket does not abort blocked syscalls on + * that socket in other threads. + * + * After calling asocket_abort() the socket cannot be reused. + * + * Call asocket_destory() *after* all threads have finished with the socket to + * finish closing the socket and free the asocket structure. + * + * The helper is implemented by setting the socket non-blocking to initiate + * syscalls connect(), accept(), read(), write(), then using a blocking poll() + * on both the primary socket and a local pipe. This makes the poll() abortable + * by writing a byte to the local pipe in asocket_abort(). + * + * asocket_create() sets the fd to non-blocking mode. It must not be changed to + * blocking mode. + * + * Using asocket will triple the number of file descriptors required per + * socket, due to the local pipe. It may be possible to use a global pipe per + * process rather than per socket, but we have not been able to come up with a + * race-free implementation yet. + * + * All functions except asocket_init() and asocket_destroy() are thread safe. + */ + +#include <stdlib.h> +#include <sys/socket.h> + +#ifndef __CUTILS_ABORT_SOCKET_H__ +#define __CUTILS_ABORT_SOCKET_H__ +#ifdef __cplusplus +extern "C" { +#endif + +struct asocket { + int fd; /* primary socket fd */ + int abort_fd[2]; /* pipe used to abort */ +}; + +/* Create an asocket from fd. + * Sets the socket to non-blocking mode. + * Returns NULL on error with errno set. + */ +struct asocket *asocket_init(int fd); + +/* Blocking socket I/O with timeout. + * Calling asocket_abort() from another thread will cause each of these + * functions to immediately return with value -1 and errno ECANCELED. + * timeout is in ms, use -1 to indicate no timeout. On timeout -1 is returned + * with errno ETIMEDOUT. + * EINTR is handled in-call. + * Other semantics are identical to the regular syscalls. + */ +int asocket_connect(struct asocket *s, const struct sockaddr *addr, + socklen_t addrlen, int timeout); + +int asocket_accept(struct asocket *s, struct sockaddr *addr, + socklen_t *addrlen, int timeout); + +int asocket_read(struct asocket *s, void *buf, size_t count, int timeout); + +int asocket_write(struct asocket *s, const void *buf, size_t count, + int timeout); + +/* Abort above calls and shutdown socket. + * Further I/O operations on this socket will immediately fail after this call. + * asocket_destroy() should be used to release resources once all threads + * have returned from blocking calls on the socket. + */ +void asocket_abort(struct asocket *s); + +/* Close socket and free asocket structure. + * Must not be called until all calls on this structure have completed. + */ +void asocket_destroy(struct asocket *s); + +#ifdef __cplusplus +} +#endif +#endif //__CUTILS_ABORT_SOCKET__H__ diff --git a/include/cutils/array.h b/include/cutils/array.h new file mode 100644 index 0000000..c97ff34 --- /dev/null +++ b/include/cutils/array.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A pointer array which intelligently expands its capacity ad needed. + */ + +#ifndef __ARRAY_H +#define __ARRAY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> + +/** An array. */ +typedef struct Array Array; + +/** Constructs a new array. Returns NULL if we ran out of memory. */ +Array* arrayCreate(); + +/** Frees an array. Does not free elements themselves. */ +void arrayFree(Array* array); + +/** Adds a pointer. Returns 0 is successful, < 0 otherwise. */ +int arrayAdd(Array* array, void* pointer); + +/** Gets the pointer at the specified index. */ +void* arrayGet(Array* array, int index); + +/** Removes the pointer at the given index and returns it. */ +void* arrayRemove(Array* array, int index); + +/** Sets pointer at the given index. Returns old pointer. */ +void* arraySet(Array* array, int index, void* pointer); + +/** Sets the array size. Sets new pointers to NULL. Returns 0 if successful, < 0 otherwise . */ +int arraySetSize(Array* array, int size); + +/** Returns the size of the given array. */ +int arraySize(Array* array); + +/** + * Returns a pointer to a C-style array which will be valid until this array + * changes. + */ +const void** arrayUnwrap(Array* array); + +#ifdef __cplusplus +} +#endif + +#endif /* __ARRAY_H */ diff --git a/include/cutils/ashmem.h b/include/cutils/ashmem.h new file mode 100644 index 0000000..25b233e --- /dev/null +++ b/include/cutils/ashmem.h @@ -0,0 +1,45 @@ +/* cutils/ashmem.h + ** + ** Copyright 2008 The Android Open Source Project + ** + ** This file is dual licensed. It may be redistributed and/or modified + ** under the terms of the Apache 2.0 License OR version 2 of the GNU + ** General Public License. + */ + +#ifndef _CUTILS_ASHMEM_H +#define _CUTILS_ASHMEM_H + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +int ashmem_create_region(const char *name, size_t size); +int ashmem_set_prot_region(int fd, int prot); +int ashmem_pin_region(int fd, size_t offset, size_t len); +int ashmem_unpin_region(int fd, size_t offset, size_t len); +int ashmem_get_size_region(int fd); + +#ifdef __cplusplus +} +#endif + +#ifndef __ASHMEMIOC /* in case someone included <linux/ashmem.h> too */ + +#define ASHMEM_NAME_LEN 256 + +#define ASHMEM_NAME_DEF "dev/ashmem" + +/* Return values from ASHMEM_PIN: Was the mapping purged while unpinned? */ +#define ASHMEM_NOT_PURGED 0 +#define ASHMEM_WAS_PURGED 1 + +/* Return values from ASHMEM_UNPIN: Is the mapping now pinned or unpinned? */ +#define ASHMEM_IS_UNPINNED 0 +#define ASHMEM_IS_PINNED 1 + +#endif /* ! __ASHMEMIOC */ + +#endif /* _CUTILS_ASHMEM_H */ diff --git a/include/cutils/atomic-arm.h b/include/cutils/atomic-arm.h new file mode 100644 index 0000000..5595021 --- /dev/null +++ b/include/cutils/atomic-arm.h @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_CUTILS_ATOMIC_ARM_H +#define ANDROID_CUTILS_ATOMIC_ARM_H + +#include <stdint.h> +#include <machine/cpu-features.h> + +extern inline void android_compiler_barrier(void) +{ + __asm__ __volatile__ ("" : : : "memory"); +} + +#if ANDROID_SMP == 0 +extern inline void android_memory_barrier(void) +{ + android_compiler_barrier(); +} +#elif defined(__ARM_HAVE_DMB) +extern inline void android_memory_barrier(void) +{ + __asm__ __volatile__ ("dmb" : : : "memory"); +} +#elif defined(__ARM_HAVE_LDREX_STREX) +extern inline void android_memory_barrier(void) +{ + __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" + : : "r" (0) : "memory"); +} +#else +extern inline void android_memory_barrier(void) +{ + typedef void (kuser_memory_barrier)(void); + (*(kuser_memory_barrier *)0xffff0fa0)(); +} +#endif + +extern inline int32_t android_atomic_acquire_load(volatile const int32_t *ptr) +{ + int32_t value = *ptr; + android_memory_barrier(); + return value; +} + +extern inline int32_t android_atomic_release_load(volatile const int32_t *ptr) +{ + android_memory_barrier(); + return *ptr; +} + +extern inline void android_atomic_acquire_store(int32_t value, + volatile int32_t *ptr) +{ + *ptr = value; + android_memory_barrier(); +} + +extern inline void android_atomic_release_store(int32_t value, + volatile int32_t *ptr) +{ + android_memory_barrier(); + *ptr = value; +} + +#if defined(__thumb__) +extern int android_atomic_cas(int32_t old_value, int32_t new_value, + volatile int32_t *ptr); +#elif defined(__ARM_HAVE_LDREX_STREX) +extern inline int android_atomic_cas(int32_t old_value, int32_t new_value, + volatile int32_t *ptr) +{ + int32_t prev, status; + do { + __asm__ __volatile__ ("ldrex %0, [%3]\n" + "mov %1, #0\n" + "teq %0, %4\n" + "strexeq %1, %5, [%3]" + : "=&r" (prev), "=&r" (status), "+m"(*ptr) + : "r" (ptr), "Ir" (old_value), "r" (new_value) + : "cc"); + } while (__builtin_expect(status != 0, 0)); + return prev != old_value; +} +#else +extern inline int android_atomic_cas(int32_t old_value, int32_t new_value, + volatile int32_t *ptr) +{ + typedef int (kuser_cmpxchg)(int32_t, int32_t, volatile int32_t *); + int32_t prev, status; + prev = *ptr; + do { + status = (*(kuser_cmpxchg *)0xffff0fc0)(old_value, new_value, ptr); + if (__builtin_expect(status == 0, 1)) + return 0; + prev = *ptr; + } while (prev == old_value); + return 1; +} +#endif + +extern inline int android_atomic_acquire_cas(int32_t old_value, + int32_t new_value, + volatile int32_t *ptr) +{ + int status = android_atomic_cas(old_value, new_value, ptr); + android_memory_barrier(); + return status; +} + +extern inline int android_atomic_release_cas(int32_t old_value, + int32_t new_value, + volatile int32_t *ptr) +{ + android_memory_barrier(); + return android_atomic_cas(old_value, new_value, ptr); +} + +/* Yeah, deprecated but old props use it */ +extern inline int android_atomic_cmpxchg(int32_t old_value, + int32_t new_value, + volatile int32_t *ptr) +{ + android_memory_barrier(); + return android_atomic_cas(old_value, new_value, ptr); +} + + +#if defined(__thumb__) +extern int32_t android_atomic_swap(int32_t new_value, + volatile int32_t *ptr); +#elif defined(__ARM_HAVE_LDREX_STREX) +extern inline int32_t android_atomic_swap(int32_t new_value, + volatile int32_t *ptr) +{ + int32_t prev, status; + do { + __asm__ __volatile__ ("ldrex %0, [%3]\n" + "strex %1, %4, [%3]" + : "=&r" (prev), "=&r" (status), "+m" (*ptr) + : "r" (ptr), "r" (new_value) + : "cc"); + } while (__builtin_expect(status != 0, 0)); + android_memory_barrier(); + return prev; +} +#else +extern inline int32_t android_atomic_swap(int32_t new_value, + volatile int32_t *ptr) +{ + int32_t prev; + __asm__ __volatile__ ("swp %0, %2, [%3]" + : "=&r" (prev), "+m" (*ptr) + : "r" (new_value), "r" (ptr) + : "cc"); + android_memory_barrier(); + return prev; +} +#endif + +#if defined(__thumb__) +extern int32_t android_atomic_add(int32_t increment, + volatile int32_t *ptr); +#elif defined(__ARM_HAVE_LDREX_STREX) +extern inline int32_t android_atomic_add(int32_t increment, + volatile int32_t *ptr) +{ + int32_t prev, tmp, status; + android_memory_barrier(); + do { + __asm__ __volatile__ ("ldrex %0, [%4]\n" + "add %1, %0, %5\n" + "strex %2, %1, [%4]" + : "=&r" (prev), "=&r" (tmp), + "=&r" (status), "+m" (*ptr) + : "r" (ptr), "Ir" (increment) + : "cc"); + } while (__builtin_expect(status != 0, 0)); + return prev; +} +#else +extern inline int32_t android_atomic_add(int32_t increment, + volatile int32_t *ptr) +{ + int32_t prev, status; + android_memory_barrier(); + do { + prev = *ptr; + status = android_atomic_cas(prev, prev + increment, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} +#endif + +extern inline int32_t android_atomic_inc(volatile int32_t *addr) +{ + return android_atomic_add(1, addr); +} + +extern inline int32_t android_atomic_dec(volatile int32_t *addr) +{ + return android_atomic_add(-1, addr); +} + +#if defined(__thumb__) +extern int32_t android_atomic_and(int32_t value, volatile int32_t *ptr); +#elif defined(__ARM_HAVE_LDREX_STREX) +extern inline int32_t android_atomic_and(int32_t value, volatile int32_t *ptr) +{ + int32_t prev, tmp, status; + android_memory_barrier(); + do { + __asm__ __volatile__ ("ldrex %0, [%4]\n" + "and %1, %0, %5\n" + "strex %2, %1, [%4]" + : "=&r" (prev), "=&r" (tmp), + "=&r" (status), "+m" (*ptr) + : "r" (ptr), "Ir" (value) + : "cc"); + } while (__builtin_expect(status != 0, 0)); + return prev; +} +#else +extern inline int32_t android_atomic_and(int32_t value, volatile int32_t *ptr) +{ + int32_t prev, status; + android_memory_barrier(); + do { + prev = *ptr; + status = android_atomic_cas(prev, prev & value, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} +#endif + +#if defined(__thumb__) +extern int32_t android_atomic_or(int32_t value, volatile int32_t *ptr); +#elif defined(__ARM_HAVE_LDREX_STREX) +extern inline int32_t android_atomic_or(int32_t value, volatile int32_t *ptr) +{ + int32_t prev, tmp, status; + android_memory_barrier(); + do { + __asm__ __volatile__ ("ldrex %0, [%4]\n" + "orr %1, %0, %5\n" + "strex %2, %1, [%4]" + : "=&r" (prev), "=&r" (tmp), + "=&r" (status), "+m" (*ptr) + : "r" (ptr), "Ir" (value) + : "cc"); + } while (__builtin_expect(status != 0, 0)); + return prev; +} +#else +extern inline int32_t android_atomic_or(int32_t value, volatile int32_t *ptr) +{ + int32_t prev, status; + android_memory_barrier(); + do { + prev = *ptr; + status = android_atomic_cas(prev, prev | value, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} +#endif + +#endif /* ANDROID_CUTILS_ATOMIC_ARM_H */ diff --git a/include/cutils/atomic-inline.h b/include/cutils/atomic-inline.h new file mode 100644 index 0000000..715e0aa --- /dev/null +++ b/include/cutils/atomic-inline.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_CUTILS_ATOMIC_INLINE_H +#define ANDROID_CUTILS_ATOMIC_INLINE_H + +/* + * Inline declarations and macros for some special-purpose atomic + * operations. These are intended for rare circumstances where a + * memory barrier needs to be issued inline rather than as a function + * call. + * + * Most code should not use these. + * + * Anything that does include this file must set ANDROID_SMP to either + * 0 or 1, indicating compilation for UP or SMP, respectively. + * + * Macros defined in this header: + * + * void ANDROID_MEMBAR_FULL(void) + * Full memory barrier. Provides a compiler reordering barrier, and + * on SMP systems emits an appropriate instruction. + */ + +#if !defined(ANDROID_SMP) +# error "Must define ANDROID_SMP before including atomic-inline.h" +#endif + +#if defined(__arm__) +#include <cutils/atomic-arm.h> +#elif defined(__i386__) || defined(__x86_64__) +#include <cutils/atomic-x86.h> +#elif defined(__sh__) +/* implementation is in atomic-android-sh.c */ +#else +#error atomic operations are unsupported +#endif + +#if ANDROID_SMP == 0 +#define ANDROID_MEMBAR_FULL android_compiler_barrier +#else +#define ANDROID_MEMBAR_FULL android_memory_barrier +#endif + +#endif /* ANDROID_CUTILS_ATOMIC_INLINE_H */ diff --git a/include/cutils/atomic-x86.h b/include/cutils/atomic-x86.h new file mode 100644 index 0000000..12a1985 --- /dev/null +++ b/include/cutils/atomic-x86.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_CUTILS_ATOMIC_X86_H +#define ANDROID_CUTILS_ATOMIC_X86_H + +#include <stdint.h> + +extern inline void android_compiler_barrier(void) +{ + __asm__ __volatile__ ("" : : : "memory"); +} + +#if ANDROID_SMP == 0 +extern inline void android_memory_barrier(void) +{ + android_compiler_barrier(); +} +#else +extern inline void android_memory_barrier(void) +{ + __asm__ __volatile__ ("mfence" : : : "memory"); +} +#endif + +extern inline int32_t android_atomic_acquire_load(volatile const int32_t *ptr) +{ + int32_t value = *ptr; + android_compiler_barrier(); + return value; +} + +extern inline int32_t android_atomic_release_load(volatile const int32_t *ptr) +{ + android_memory_barrier(); + return *ptr; +} + +extern inline void android_atomic_acquire_store(int32_t value, + volatile int32_t *ptr) +{ + *ptr = value; + android_memory_barrier(); +} + +extern inline void android_atomic_release_store(int32_t value, + volatile int32_t *ptr) +{ + android_compiler_barrier(); + *ptr = value; +} + +extern inline int android_atomic_cas(int32_t old_value, int32_t new_value, + volatile int32_t *ptr) +{ + int32_t prev; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (prev) + : "q" (new_value), "m" (*ptr), "0" (old_value) + : "memory"); + return prev != old_value; +} + +extern inline int android_atomic_acquire_cas(int32_t old_value, + int32_t new_value, + volatile int32_t *ptr) +{ + /* Loads are not reordered with other loads. */ + return android_atomic_cas(old_value, new_value, ptr); +} + +extern inline int android_atomic_release_cas(int32_t old_value, + int32_t new_value, + volatile int32_t *ptr) +{ + /* Stores are not reordered with other stores. */ + return android_atomic_cas(old_value, new_value, ptr); +} + +extern inline int32_t android_atomic_swap(int32_t new_value, + volatile int32_t *ptr) +{ + __asm__ __volatile__ ("xchgl %1, %0" + : "=r" (new_value) + : "m" (*ptr), "0" (new_value) + : "memory"); + /* new_value now holds the old value of *ptr */ + return new_value; +} + +extern inline int32_t android_atomic_add(int32_t increment, + volatile int32_t *ptr) +{ + __asm__ __volatile__ ("lock; xaddl %0, %1" + : "+r" (increment), "+m" (*ptr) + : : "memory"); + /* increment now holds the old value of *ptr */ + return increment; +} + +extern inline int32_t android_atomic_inc(volatile int32_t *addr) +{ + return android_atomic_add(1, addr); +} + +extern inline int32_t android_atomic_dec(volatile int32_t *addr) +{ + return android_atomic_add(-1, addr); +} + +extern inline int32_t android_atomic_and(int32_t value, + volatile int32_t *ptr) +{ + int32_t prev, status; + do { + prev = *ptr; + status = android_atomic_cas(prev, prev & value, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} + +extern inline int32_t android_atomic_or(int32_t value, volatile int32_t *ptr) +{ + int32_t prev, status; + do { + prev = *ptr; + status = android_atomic_cas(prev, prev | value, ptr); + } while (__builtin_expect(status != 0, 0)); + return prev; +} + +#endif /* ANDROID_CUTILS_ATOMIC_X86_H */ diff --git a/include/cutils/atomic.h b/include/cutils/atomic.h new file mode 100644 index 0000000..48f87f7 --- /dev/null +++ b/include/cutils/atomic.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_CUTILS_ATOMIC_H +#define ANDROID_CUTILS_ATOMIC_H + +#include <stdint.h> +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * A handful of basic atomic operations. The appropriate pthread + * functions should be used instead of these whenever possible. + * + * The "acquire" and "release" terms can be defined intuitively in terms + * of the placement of memory barriers in a simple lock implementation: + * - wait until compare-and-swap(lock-is-free --> lock-is-held) succeeds + * - barrier + * - [do work] + * - barrier + * - store(lock-is-free) + * In very crude terms, the initial (acquire) barrier prevents any of the + * "work" from happening before the lock is held, and the later (release) + * barrier ensures that all of the work happens before the lock is released. + * (Think of cached writes, cache read-ahead, and instruction reordering + * around the CAS and store instructions.) + * + * The barriers must apply to both the compiler and the CPU. Note it is + * legal for instructions that occur before an "acquire" barrier to be + * moved down below it, and for instructions that occur after a "release" + * barrier to be moved up above it. + * + * The ARM-driven implementation we use here is short on subtlety, + * and actually requests a full barrier from the compiler and the CPU. + * The only difference between acquire and release is in whether they + * are issued before or after the atomic operation with which they + * are associated. To ease the transition to C/C++ atomic intrinsics, + * you should not rely on this, and instead assume that only the minimal + * acquire/release protection is provided. + * + * NOTE: all int32_t* values are expected to be aligned on 32-bit boundaries. + * If they are not, atomicity is not guaranteed. + */ + +/* + * Basic arithmetic and bitwise operations. These all provide a + * barrier with "release" ordering, and return the previous value. + * + * These have the same characteristics (e.g. what happens on overflow) + * as the equivalent non-atomic C operations. + */ +int32_t android_atomic_inc(volatile int32_t* addr); +int32_t android_atomic_dec(volatile int32_t* addr); +int32_t android_atomic_add(int32_t value, volatile int32_t* addr); +int32_t android_atomic_and(int32_t value, volatile int32_t* addr); +int32_t android_atomic_or(int32_t value, volatile int32_t* addr); + +/* + * Perform an atomic load with "acquire" or "release" ordering. + * + * This is only necessary if you need the memory barrier. A 32-bit read + * from a 32-bit aligned address is atomic on all supported platforms. + */ +int32_t android_atomic_acquire_load(volatile const int32_t* addr); +int32_t android_atomic_release_load(volatile const int32_t* addr); + +/* + * Perform an atomic store with "acquire" or "release" ordering. + * + * This is only necessary if you need the memory barrier. A 32-bit write + * to a 32-bit aligned address is atomic on all supported platforms. + */ +void android_atomic_acquire_store(int32_t value, volatile int32_t* addr); +void android_atomic_release_store(int32_t value, volatile int32_t* addr); + +/* + * Unconditional swap operation with release ordering. + * + * Stores the new value at *addr, and returns the previous value. + */ +int32_t android_atomic_swap(int32_t value, volatile int32_t* addr); + +/* + * Compare-and-set operation with "acquire" or "release" ordering. + * + * This returns zero if the new value was successfully stored, which will + * only happen when *addr == oldvalue. + * + * (The return value is inverted from implementations on other platforms, + * but matches the ARM ldrex/strex result.) + * + * Implementations that use the release CAS in a loop may be less efficient + * than possible, because we re-issue the memory barrier on each iteration. + */ +int android_atomic_acquire_cas(int32_t oldvalue, int32_t newvalue, + volatile int32_t* addr); +int android_atomic_release_cas(int32_t oldvalue, int32_t newvalue, + volatile int32_t* addr); +#if defined(__ARM_ARCH__) || defined (__ARM_EABI__) +int android_atomic_cmpxchg(int32_t oldvalue, int32_t newvalue, + volatile int32_t* addr); +#else +#define android_atomic_cmpxchg android_atomic_release_cas +#endif + +/* + * Aliases for code using an older version of this header. These are now + * deprecated and should not be used. The definitions will be removed + * in a future release. + */ +#define android_atomic_write android_atomic_release_store + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ANDROID_CUTILS_ATOMIC_H diff --git a/include/cutils/compiler.h b/include/cutils/compiler.h new file mode 100644 index 0000000..09112d5 --- /dev/null +++ b/include/cutils/compiler.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_CUTILS_COMPILER_H +#define ANDROID_CUTILS_COMPILER_H + +/* + * helps the compiler's optimizer predicting branches + */ + +#ifdef __cplusplus +# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), true )) +# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), false )) +#else +# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), 1 )) +# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 )) +#endif + +#endif // ANDROID_CUTILS_COMPILER_H diff --git a/include/cutils/config_utils.h b/include/cutils/config_utils.h new file mode 100644 index 0000000..f3fb370 --- /dev/null +++ b/include/cutils/config_utils.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_CONFIG_UTILS_H +#define __CUTILS_CONFIG_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct cnode cnode; + + +struct cnode +{ + cnode *next; + cnode *first_child; + cnode *last_child; + const char *name; + const char *value; +}; + +/* parse a text string into a config node tree */ +void config_load(cnode *root, char *data); + +/* parse a file into a config node tree */ +void config_load_file(cnode *root, const char *fn); + +/* create a single config node */ +cnode* config_node(const char *name, const char *value); + +/* locate a named child of a config node */ +cnode* config_find(cnode *root, const char *name); + +/* look up a child by name and return the boolean value */ +int config_bool(cnode *root, const char *name, int _default); + +/* look up a child by name and return the string value */ +const char* config_str(cnode *root, const char *name, const char *_default); + +/* add a named child to a config node (or modify it if it already exists) */ +void config_set(cnode *root, const char *name, const char *value); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/cutils/cpu_info.h b/include/cutils/cpu_info.h new file mode 100644 index 0000000..78c1884 --- /dev/null +++ b/include/cutils/cpu_info.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_CPU_INFO_H +#define __CUTILS_CPU_INFO_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* returns a string contiaining an ASCII representation of the CPU serial number, +** or NULL if cpu info not available. +** The string is a static variable, so don't call free() on it. +*/ +extern const char* get_cpu_serial_number(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_CPU_INFO_H */ diff --git a/include/cutils/dir_hash.h b/include/cutils/dir_hash.h new file mode 100644 index 0000000..fbb4d02 --- /dev/null +++ b/include/cutils/dir_hash.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +typedef enum { + SHA_1, +} HashAlgorithm; + +int get_file_hash(HashAlgorithm algorithm, const char *path, + char *output_string, size_t max_output_string); + +int get_recursive_hash_manifest(HashAlgorithm algorithm, + const char *directory_path, + char **output_string); diff --git a/include/cutils/event_tag_map.h b/include/cutils/event_tag_map.h new file mode 100644 index 0000000..1653c61 --- /dev/null +++ b/include/cutils/event_tag_map.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBS_CUTILS_EVENTTAGMAP_H +#define _LIBS_CUTILS_EVENTTAGMAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define EVENT_TAG_MAP_FILE "/system/etc/event-log-tags" + +struct EventTagMap; +typedef struct EventTagMap EventTagMap; + +/* + * Open the specified file as an event log tag map. + * + * Returns NULL on failure. + */ +EventTagMap* android_openEventTagMap(const char* fileName); + +/* + * Close the map. + */ +void android_closeEventTagMap(EventTagMap* map); + +/* + * Look up a tag by index. Returns the tag string, or NULL if not found. + */ +const char* android_lookupEventTag(const EventTagMap* map, int tag); + +#ifdef __cplusplus +} +#endif + +#endif /*_LIBS_CUTILS_EVENTTAGMAP_H*/ diff --git a/include/cutils/hashmap.h b/include/cutils/hashmap.h new file mode 100644 index 0000000..5cb344c --- /dev/null +++ b/include/cutils/hashmap.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Hash map. + */ + +#ifndef __HASHMAP_H +#define __HASHMAP_H + +#include <stdbool.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** A hash map. */ +typedef struct Hashmap Hashmap; + +/** + * Creates a new hash map. Returns NULL if memory allocation fails. + * + * @param initialCapacity number of expected entries + * @param hash function which hashes keys + * @param equals function which compares keys for equality + */ +Hashmap* hashmapCreate(size_t initialCapacity, + int (*hash)(void* key), bool (*equals)(void* keyA, void* keyB)); + +/** + * Frees the hash map. Does not free the keys or values themselves. + */ +void hashmapFree(Hashmap* map); + +/** + * Hashes the memory pointed to by key with the given size. Useful for + * implementing hash functions. + */ +int hashmapHash(void* key, size_t keySize); + +/** + * Puts value for the given key in the map. Returns pre-existing value if + * any. + * + * If memory allocation fails, this function returns NULL, the map's size + * does not increase, and errno is set to ENOMEM. + */ +void* hashmapPut(Hashmap* map, void* key, void* value); + +/** + * Gets a value from the map. Returns NULL if no entry for the given key is + * found or if the value itself is NULL. + */ +void* hashmapGet(Hashmap* map, void* key); + +/** + * Returns true if the map contains an entry for the given key. + */ +bool hashmapContainsKey(Hashmap* map, void* key); + +/** + * Gets the value for a key. If a value is not found, this function gets a + * value and creates an entry using the given callback. + * + * If memory allocation fails, the callback is not called, this function + * returns NULL, and errno is set to ENOMEM. + */ +void* hashmapMemoize(Hashmap* map, void* key, + void* (*initialValue)(void* key, void* context), void* context); + +/** + * Removes an entry from the map. Returns the removed value or NULL if no + * entry was present. + */ +void* hashmapRemove(Hashmap* map, void* key); + +/** + * Gets the number of entries in this map. + */ +size_t hashmapSize(Hashmap* map); + +/** + * Invokes the given callback on each entry in the map. Stops iterating if + * the callback returns false. + */ +void hashmapForEach(Hashmap* map, + bool (*callback)(void* key, void* value, void* context), + void* context); + +/** + * Concurrency support. + */ + +/** + * Locks the hash map so only the current thread can access it. + */ +void hashmapLock(Hashmap* map); + +/** + * Unlocks the hash map so other threads can access it. + */ +void hashmapUnlock(Hashmap* map); + +/** + * Key utilities. + */ + +/** + * Hashes int keys. 'key' is a pointer to int. + */ +int hashmapIntHash(void* key); + +/** + * Compares two int keys for equality. + */ +bool hashmapIntEquals(void* keyA, void* keyB); + +/** + * For debugging. + */ + +/** + * Gets current capacity. + */ +size_t hashmapCurrentCapacity(Hashmap* map); + +/** + * Counts the number of entry collisions. + */ +size_t hashmapCountCollisions(Hashmap* map); + +#ifdef __cplusplus +} +#endif + +#endif /* __HASHMAP_H */ diff --git a/include/cutils/iosched_policy.h b/include/cutils/iosched_policy.h new file mode 100644 index 0000000..07c5d1f --- /dev/null +++ b/include/cutils/iosched_policy.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_IOSCHED_POLICY_H +#define __CUTILS_IOSCHED_POLICY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + IoSchedClass_NONE, + IoSchedClass_RT, + IoSchedClass_BE, + IoSchedClass_IDLE, +} IoSchedClass; + +extern int android_set_ioprio(int pid, IoSchedClass clazz, int ioprio); +extern int android_get_ioprio(int pid, IoSchedClass *clazz, int *ioprio); + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_IOSCHED_POLICY_H */ diff --git a/include/cutils/jstring.h b/include/cutils/jstring.h new file mode 100644 index 0000000..ee0018f --- /dev/null +++ b/include/cutils/jstring.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_STRING16_H +#define __CUTILS_STRING16_H + +#include <stdint.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint16_t char16_t; + +extern char * strndup16to8 (const char16_t* s, size_t n); +extern size_t strnlen16to8 (const char16_t* s, size_t n); +extern char * strncpy16to8 (char *dest, const char16_t*s, size_t n); + +extern char16_t * strdup8to16 (const char* s, size_t *out_len); +extern size_t strlen8to16 (const char* utf8Str); +extern char16_t * strcpy8to16 (char16_t *dest, const char*s, size_t *out_len); +extern char16_t * strcpylen8to16 (char16_t *dest, const char*s, int length, + size_t *out_len); + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_STRING16_H */ diff --git a/include/cutils/log.h b/include/cutils/log.h new file mode 100644 index 0000000..dd47c35 --- /dev/null +++ b/include/cutils/log.h @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// C/C++ logging functions. See the logging documentation for API details. +// +// We'd like these to be available from C code (in case we import some from +// somewhere), so this has a C interface. +// +// The output will be correct when the log file is shared between multiple +// threads and/or multiple processes so long as the operating system +// supports O_APPEND. These calls have mutex-protected data structures +// and so are NOT reentrant. Do not use LOG in a signal handler. +// +#ifndef _LIBS_CUTILS_LOG_H +#define _LIBS_CUTILS_LOG_H + +#include <stdio.h> +#include <time.h> +#include <sys/types.h> +#include <unistd.h> +#ifdef HAVE_PTHREADS +#include <pthread.h> +#endif +#include <stdarg.h> + +#include <cutils/uio.h> +#include <cutils/logd.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// --------------------------------------------------------------------- + +/* + * Normally we strip LOGV (VERBOSE messages) from release builds. + * You can modify this (for example with "#define LOG_NDEBUG 0" + * at the top of your source file) to change that behavior. + */ +#ifndef LOG_NDEBUG +#ifdef NDEBUG +#define LOG_NDEBUG 1 +#else +#define LOG_NDEBUG 0 +#endif +#endif + +/* + * This is the local tag used for the following simplified + * logging macros. You can change this preprocessor definition + * before using the other macros to change the tag. + */ +#ifndef LOG_TAG +#define LOG_TAG NULL +#endif + +// --------------------------------------------------------------------- + +/* + * Simplified macro to send a verbose log message using the current LOG_TAG. + */ +#ifndef LOGV +#if LOG_NDEBUG +#define LOGV(...) ((void)0) +#else +#define LOGV(...) ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) + +#ifndef LOGV_IF +#if LOG_NDEBUG +#define LOGV_IF(cond, ...) ((void)0) +#else +#define LOGV_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif +#endif + +/* + * Simplified macro to send a debug log message using the current LOG_TAG. + */ +#ifndef LOGD +#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGD_IF +#define LOGD_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an info log message using the current LOG_TAG. + */ +#ifndef LOGI +#define LOGI(...) ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGI_IF +#define LOGI_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send a warning log message using the current LOG_TAG. + */ +#ifndef LOGW +#define LOGW(...) ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGW_IF +#define LOGW_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an error log message using the current LOG_TAG. + */ +#ifndef LOGE +#define LOGE(...) ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef LOGE_IF +#define LOGE_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +// --------------------------------------------------------------------- + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * verbose priority. + */ +#ifndef IF_LOGV +#if LOG_NDEBUG +#define IF_LOGV() if (false) +#else +#define IF_LOGV() IF_LOG(LOG_VERBOSE, LOG_TAG) +#endif +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * debug priority. + */ +#ifndef IF_LOGD +#define IF_LOGD() IF_LOG(LOG_DEBUG, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * info priority. + */ +#ifndef IF_LOGI +#define IF_LOGI() IF_LOG(LOG_INFO, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * warn priority. + */ +#ifndef IF_LOGW +#define IF_LOGW() IF_LOG(LOG_WARN, LOG_TAG) +#endif + +/* + * Conditional based on whether the current LOG_TAG is enabled at + * error priority. + */ +#ifndef IF_LOGE +#define IF_LOGE() IF_LOG(LOG_ERROR, LOG_TAG) +#endif + + +// --------------------------------------------------------------------- + +/* + * Simplified macro to send a verbose system log message using the current LOG_TAG. + */ +#ifndef SLOGV +#if LOG_NDEBUG +#define SLOGV(...) ((void)0) +#else +#define SLOGV(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#define CONDITION(cond) (__builtin_expect((cond)!=0, 0)) + +#ifndef SLOGV_IF +#if LOG_NDEBUG +#define SLOGV_IF(cond, ...) ((void)0) +#else +#define SLOGV_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif +#endif + +/* + * Simplified macro to send a debug system log message using the current LOG_TAG. + */ +#ifndef SLOGD +#define SLOGD(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef SLOGD_IF +#define SLOGD_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an info system log message using the current LOG_TAG. + */ +#ifndef SLOGI +#define SLOGI(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef SLOGI_IF +#define SLOGI_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send a warning system log message using the current LOG_TAG. + */ +#ifndef SLOGW +#define SLOGW(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef SLOGW_IF +#define SLOGW_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + +/* + * Simplified macro to send an error system log message using the current LOG_TAG. + */ +#ifndef SLOGE +#define SLOGE(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef SLOGE_IF +#define SLOGE_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) +#endif + + + +// --------------------------------------------------------------------- + +/* + * Log a fatal error. If the given condition fails, this stops program + * execution like a normal assertion, but also generating the given message. + * It is NOT stripped from release builds. Note that the condition test + * is -inverted- from the normal assert() semantics. + */ +#define LOG_ALWAYS_FATAL_IF(cond, ...) \ + ( (CONDITION(cond)) \ + ? ((void)android_printAssert(#cond, LOG_TAG, __VA_ARGS__)) \ + : (void)0 ) + +#define LOG_ALWAYS_FATAL(...) \ + ( ((void)android_printAssert(NULL, LOG_TAG, __VA_ARGS__)) ) + +/* + * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that + * are stripped out of release builds. + */ +#if LOG_NDEBUG + +#define LOG_FATAL_IF(cond, ...) ((void)0) +#define LOG_FATAL(...) ((void)0) + +#else + +#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__) +#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__) + +#endif + +/* + * Assertion that generates a log message when the assertion fails. + * Stripped out of release builds. Uses the current LOG_TAG. + */ +#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), __VA_ARGS__) +//#define LOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) + +// --------------------------------------------------------------------- + +/* + * Basic log message macro. + * + * Example: + * LOG(LOG_WARN, NULL, "Failed with error %d", errno); + * + * The second argument may be NULL or "" to indicate the "global" tag. + */ +#ifndef LOG +#define LOG(priority, tag, ...) \ + LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to specify a number for the priority. + */ +#ifndef LOG_PRI +#define LOG_PRI(priority, tag, ...) \ + android_printLog(priority, tag, __VA_ARGS__) +#endif + +/* + * Log macro that allows you to pass in a varargs ("args" is a va_list). + */ +#ifndef LOG_PRI_VA +#define LOG_PRI_VA(priority, tag, fmt, args) \ + android_vprintLog(priority, NULL, tag, fmt, args) +#endif + +/* + * Conditional given a desired logging priority and tag. + */ +#ifndef IF_LOG +#define IF_LOG(priority, tag) \ + if (android_testLog(ANDROID_##priority, tag)) +#endif + +// --------------------------------------------------------------------- + +/* + * Event logging. + */ + +/* + * Event log entry types. These must match up with the declarations in + * java/android/android/util/EventLog.java. + */ +typedef enum { + EVENT_TYPE_INT = 0, + EVENT_TYPE_LONG = 1, + EVENT_TYPE_STRING = 2, + EVENT_TYPE_LIST = 3, +} AndroidEventLogType; + + +#define LOG_EVENT_INT(_tag, _value) { \ + int intBuf = _value; \ + (void) android_btWriteLog(_tag, EVENT_TYPE_INT, &intBuf, \ + sizeof(intBuf)); \ + } +#define LOG_EVENT_LONG(_tag, _value) { \ + long long longBuf = _value; \ + (void) android_btWriteLog(_tag, EVENT_TYPE_LONG, &longBuf, \ + sizeof(longBuf)); \ + } +#define LOG_EVENT_STRING(_tag, _value) \ + ((void) 0) /* not implemented -- must combine len with string */ +/* TODO: something for LIST */ + +/* + * =========================================================================== + * + * The stuff in the rest of this file should not be used directly. + */ + +#define android_printLog(prio, tag, fmt...) \ + __android_log_print(prio, tag, fmt) + +#define android_vprintLog(prio, cond, tag, fmt...) \ + __android_log_vprint(prio, tag, fmt) + +#define android_printAssert(cond, tag, fmt...) \ + __android_log_assert(cond, tag, fmt) + +#define android_writeLog(prio, tag, text) \ + __android_log_write(prio, tag, text) + +#define android_bWriteLog(tag, payload, len) \ + __android_log_bwrite(tag, payload, len) +#define android_btWriteLog(tag, type, payload, len) \ + __android_log_btwrite(tag, type, payload, len) + +// TODO: remove these prototypes and their users +#define android_testLog(prio, tag) (1) +#define android_writevLog(vec,num) do{}while(0) +#define android_write1Log(str,len) do{}while (0) +#define android_setMinPriority(tag, prio) do{}while(0) +//#define android_logToCallback(func) do{}while(0) +#define android_logToFile(tag, file) (0) +#define android_logToFd(tag, fd) (0) + +typedef enum { + LOG_ID_MAIN = 0, + LOG_ID_RADIO = 1, + LOG_ID_EVENTS = 2, + LOG_ID_SYSTEM = 3, + + LOG_ID_MAX +} log_id_t; + +/* + * Send a simple string to the log. + */ +int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text); +int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...); + + +#ifdef __cplusplus +} +#endif + +#endif // _LIBS_CUTILS_LOG_H diff --git a/include/cutils/logd.h b/include/cutils/logd.h new file mode 100644 index 0000000..8737639 --- /dev/null +++ b/include/cutils/logd.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_CUTILS_LOGD_H +#define _ANDROID_CUTILS_LOGD_H + +/* the stable/frozen log-related definitions have been + * moved to this header, which is exposed by the NDK + */ +#include <android/log.h> + +/* the rest is only used internally by the system */ +#include <time.h> +#include <stdio.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/types.h> +#ifdef HAVE_PTHREADS +#include <pthread.h> +#endif +#include <cutils/uio.h> +#include <stdarg.h> + +#ifdef __cplusplus +extern "C" { +#endif + +int __android_log_bwrite(int32_t tag, const void *payload, size_t len); +int __android_log_btwrite(int32_t tag, char type, const void *payload, + size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* _LOGD_H */ diff --git a/include/cutils/logger.h b/include/cutils/logger.h new file mode 100644 index 0000000..b60f7ad --- /dev/null +++ b/include/cutils/logger.h @@ -0,0 +1,47 @@ +/* utils/logger.h +** +** Copyright 2007, The Android Open Source Project +** +** This file is dual licensed. It may be redistributed and/or modified +** under the terms of the Apache 2.0 License OR version 2 of the GNU +** General Public License. +*/ + +#ifndef _UTILS_LOGGER_H +#define _UTILS_LOGGER_H + +#include <stdint.h> + +struct logger_entry { + uint16_t len; /* length of the payload */ + uint16_t __pad; /* no matter what, we get 2 bytes of padding */ + int32_t pid; /* generating process's pid */ + int32_t tid; /* generating process's tid */ + int32_t sec; /* seconds since Epoch */ + int32_t nsec; /* nanoseconds */ + char msg[0]; /* the entry's payload */ +}; + +#define LOGGER_LOG_MAIN "log/main" +#define LOGGER_LOG_RADIO "log/radio" +#define LOGGER_LOG_EVENTS "log/events" +#define LOGGER_LOG_SYSTEM "log/system" + +#define LOGGER_ENTRY_MAX_LEN (4*1024) +#define LOGGER_ENTRY_MAX_PAYLOAD \ + (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry)) + +#ifdef HAVE_IOCTL + +#include <sys/ioctl.h> + +#define __LOGGERIO 0xAE + +#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */ +#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */ +#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */ +#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */ + +#endif // HAVE_IOCTL + +#endif /* _UTILS_LOGGER_H */ diff --git a/include/cutils/logprint.h b/include/cutils/logprint.h new file mode 100644 index 0000000..1102154 --- /dev/null +++ b/include/cutils/logprint.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LOGPRINT_H +#define _LOGPRINT_H + +#include <cutils/log.h> +#include <cutils/logger.h> +#include <cutils/event_tag_map.h> +#include <pthread.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + FORMAT_OFF = 0, + FORMAT_BRIEF, + FORMAT_PROCESS, + FORMAT_TAG, + FORMAT_THREAD, + FORMAT_RAW, + FORMAT_TIME, + FORMAT_THREADTIME, + FORMAT_LONG, +} AndroidLogPrintFormat; + +typedef enum { + OUTPUT_COLOR_ON = 0, + OUTPUT_COLOR_OFF, +} AndroidLogColoredOutput; + +typedef struct AndroidLogFormat_t AndroidLogFormat; + +typedef struct AndroidLogEntry_t { + time_t tv_sec; + long tv_nsec; + android_LogPriority priority; + pid_t pid; + pthread_t tid; + const char * tag; + size_t messageLen; + const char * message; +} AndroidLogEntry; + +AndroidLogFormat *android_log_format_new(); + +void android_log_format_free(AndroidLogFormat *p_format); + +void android_log_setPrintFormat(AndroidLogFormat *p_format, + AndroidLogPrintFormat format); + +void android_log_setColoredOutput(AndroidLogFormat *p_format); + +/** + * Returns FORMAT_OFF on invalid string + */ +AndroidLogPrintFormat android_log_formatFromString(const char *s); + +/** + * filterExpression: a single filter expression + * eg "AT:d" + * + * returns 0 on success and -1 on invalid expression + * + * Assumes single threaded execution + * + */ + +int android_log_addFilterRule(AndroidLogFormat *p_format, + const char *filterExpression); + + +/** + * filterString: a whitespace-separated set of filter expressions + * eg "AT:d *:i" + * + * returns 0 on success and -1 on invalid expression + * + * Assumes single threaded execution + * + */ + +int android_log_addFilterString(AndroidLogFormat *p_format, + const char *filterString); + + +/** + * returns 1 if this log line should be printed based on its priority + * and tag, and 0 if it should not + */ +int android_log_shouldPrintLine ( + AndroidLogFormat *p_format, const char *tag, android_LogPriority pri); + + +/** + * Splits a wire-format buffer into an AndroidLogEntry + * entry allocated by caller. Pointers will point directly into buf + * + * Returns 0 on success and -1 on invalid wire format (entry will be + * in unspecified state) + */ +int android_log_processLogBuffer(struct logger_entry *buf, + AndroidLogEntry *entry); + +/** + * Like android_log_processLogBuffer, but for binary logs. + * + * If "map" is non-NULL, it will be used to convert the log tag number + * into a string. + */ +int android_log_processBinaryLogBuffer(struct logger_entry *buf, + AndroidLogEntry *entry, const EventTagMap* map, char* messageBuf, + int messageBufLen); + + +/** + * Formats a log message into a buffer + * + * Uses defaultBuffer if it can, otherwise malloc()'s a new buffer + * If return value != defaultBuffer, caller must call free() + * Returns NULL on malloc error + */ + +char *android_log_formatLogLine ( + AndroidLogFormat *p_format, + char *defaultBuffer, + size_t defaultBufferSize, + const AndroidLogEntry *p_line, + size_t *p_outLength); + + +/** + * Either print or do not print log line, based on filter + * + * Assumes single threaded execution + * + */ +int android_log_printLogLine( + AndroidLogFormat *p_format, + int fd, + const AndroidLogEntry *entry); + + +#ifdef __cplusplus +} +#endif + + +#endif /*_LOGPRINT_H*/ diff --git a/include/cutils/memory.h b/include/cutils/memory.h new file mode 100644 index 0000000..e725cdd --- /dev/null +++ b/include/cutils/memory.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_CUTILS_MEMORY_H +#define ANDROID_CUTILS_MEMORY_H + +#include <stdint.h> +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* size is given in bytes and must be multiple of 2 */ +void android_memset16(uint16_t* dst, uint16_t value, size_t size); + +/* size is given in bytes and must be multiple of 4 */ +void android_memset32(uint32_t* dst, uint32_t value, size_t size); + +#if !HAVE_STRLCPY +/* Declaration of strlcpy() for platforms that don't already have it. */ +size_t strlcpy(char *dst, const char *src, size_t size); +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ANDROID_CUTILS_MEMORY_H diff --git a/include/cutils/misc.h b/include/cutils/misc.h new file mode 100644 index 0000000..2c48dfa --- /dev/null +++ b/include/cutils/misc.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_MISC_H +#define __CUTILS_MISC_H + +#ifdef __cplusplus +extern "C" { +#endif + + /* Load an entire file into a malloc'd chunk of memory + * that is length_of_file + 1 (null terminator). If + * sz is non-zero, return the size of the file via sz. + * Returns 0 on failure. + */ +extern void *load_file(const char *fn, unsigned *sz); + + /* Connects your process to the system debugger daemon + * so that on a crash it may be logged or interactively + * debugged (depending on system settings). + */ +extern void debuggerd_connect(void); + + + /* This is the range of UIDs (and GIDs) that are reserved + * for assigning to applications. + */ +#define FIRST_APPLICATION_UID 10000 +#define LAST_APPLICATION_UID 99999 + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_MISC_H */ diff --git a/include/cutils/mq.h b/include/cutils/mq.h new file mode 100644 index 0000000..b27456d --- /dev/null +++ b/include/cutils/mq.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * IPC messaging library. + */ + +#ifndef __MQ_H +#define __MQ_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** A message. */ +typedef struct MqMessage MqMessage; + +/** A destination to which messages can be sent. */ +typedef struct MqDestination MqDestination; + +/* Array of bytes. */ +typedef struct MqBytes MqBytes; + +/** + * Hears messages. + * + * @param destination to which the message was sent + * @param message the message to hear + */ +typedef void MqMessageListener(MqDestination* destination, MqMessage* message); + +/** + * Hears a destination close. + * + * @param destination that closed + */ +typedef void MqCloseListener(MqDestination* destination); + +/** Message functions. */ + +/** + * Creates a new Message. + * + * @param header as defined by user + * @param body as defined by user + * @param replyTo destination to which replies should be sent, NULL if none + */ +MqMessage* mqCreateMessage(MqBytes header, MqBytes body, + MqDestination* replyTo); + +/** Sends a message to a destination. */ +void mqSendMessage(MqMessage* message, MqDestination* destination); + +/** Destination functions. */ + +/** + * Creates a new destination. Acquires a reference implicitly. + * + * @param messageListener function to call when a message is recieved + * @param closeListener function to call when the destination closes + * @param userData user-specific data to associate with the destination. + * Retrieve using mqGetDestinationUserData(). + */ +MqDestination* mqCreateDestination(MqMessageListener* messageListener, + MqCloseListener* closeListener, void* userData); + +/** + * Gets user data which was associated with the given destination at + * construction time. + * + * It is only valid to call this function in the same process that the + * given destination was created in. + * This function returns a null pointer if you call it on a destination + * created in a remote process. + */ +void* mqGetUserData(MqDestination* destination); + +/** + * Returns 1 if the destination was created in this process, or 0 if + * the destination was created in a different process, in which case you have + * a remote stub. + */ +int mqIsDestinationLocal(MqDestination* destination); + +/** + * Increments the destination's reference count. + */ +void mqKeepDestination(MqDesintation* destination); + +/** + * Decrements the destination's reference count. + */ +void mqFreeDestination(MqDestination* desintation); + +/** Registry API. */ + +/** + * Gets the destination bound to a name. + */ +MqDestination* mqGetDestination(char* name); + +/** + * Binds a destination to a name. + */ +void mqPutDestination(char* name, MqDestination* desintation); + +#ifdef __cplusplus +} +#endif + +#endif /* __MQ_H */ diff --git a/include/cutils/mspace.h b/include/cutils/mspace.h new file mode 100644 index 0000000..93fe48e --- /dev/null +++ b/include/cutils/mspace.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* A wrapper file for dlmalloc.h that defines prototypes for the + * mspace_*() functions, which provide an interface for creating + * multiple heaps. + */ + +#ifndef MSPACE_H_ +#define MSPACE_H_ + +/* It's a pain getting the mallinfo stuff to work + * with Linux, OSX, and klibc, so just turn it off + * for now. + * TODO: make mallinfo work + */ +#define NO_MALLINFO 1 + +/* Allow setting the maximum heap footprint. + */ +#define USE_MAX_ALLOWED_FOOTPRINT 1 + +#define USE_CONTIGUOUS_MSPACES 1 +#if USE_CONTIGUOUS_MSPACES +#define HAVE_MMAP 0 +#define HAVE_MORECORE 1 +#define MORECORE_CONTIGUOUS 0 +#endif + +#define MSPACES 1 +#define ONLY_MSPACES 1 +#include "../../../../bionic/libc/bionic/dlmalloc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + mspace_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. mspace_usable_size can be more useful in + debugging and assertions, for example: + + p = mspace_malloc(msp, n); + assert(mspace_usable_size(msp, p) >= 256); +*/ +size_t mspace_usable_size(mspace, const void*); + +#if USE_CONTIGUOUS_MSPACES +/* + Similar to create_mspace(), but the underlying memory is + guaranteed to be contiguous. No more than max_capacity + bytes is ever allocated to the mspace. + */ +mspace create_contiguous_mspace(size_t starting_capacity, size_t max_capacity, + int locked); + +/* + Identical to create_contiguous_mspace, but labels the mapping 'mspace/name' + instead of 'mspace' +*/ +mspace create_contiguous_mspace_with_name(size_t starting_capacity, + size_t max_capacity, int locked, const char *name); + +/* + Identical to create_contiguous_mspace, but uses previously mapped memory. +*/ +mspace create_contiguous_mspace_with_base(size_t starting_capacity, + size_t max_capacity, int locked, void *base); + +size_t destroy_contiguous_mspace(mspace msp); + +/* + Returns the position of the "break" within the given mspace. +*/ +void *contiguous_mspace_sbrk0(mspace msp); +#endif + +/* + Call the handler for each block in the specified mspace. + chunkptr and chunklen refer to the heap-level chunk including + the chunk overhead, and userptr and userlen refer to the + user-usable part of the chunk. If the chunk is free, userptr + will be NULL and userlen will be 0. userlen is not guaranteed + to be the same value passed into malloc() for a given chunk; + it is >= the requested size. + */ +void mspace_walk_heap(mspace msp, + void(*handler)(const void *chunkptr, size_t chunklen, + const void *userptr, size_t userlen, void *arg), void *harg); + +/* + mspace_walk_free_pages(handler, harg) + + Calls the provided handler on each free region in the specified + mspace. The memory between start and end are guaranteed not to + contain any important data, so the handler is free to alter the + contents in any way. This can be used to advise the OS that large + free regions may be swapped out. + + The value in harg will be passed to each call of the handler. + */ +void mspace_walk_free_pages(mspace msp, + void(*handler)(void *start, void *end, void *arg), void *harg); + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif + +#endif /* MSPACE_H_ */ diff --git a/include/cutils/native_handle.h b/include/cutils/native_handle.h new file mode 100644 index 0000000..89d6b65 --- /dev/null +++ b/include/cutils/native_handle.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NATIVE_HANDLE_H_ +#define NATIVE_HANDLE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + int version; /* sizeof(native_handle_t) */ + int numFds; /* number of file-descriptors at &data[0] */ + int numInts; /* number of ints at &data[numFds] */ + int data[0]; /* numFds + numInts ints */ +} native_handle_t; + + +/* keep the old definition for backward source-compatibility */ +typedef native_handle_t native_handle; + +/* + * native_handle_close + * + * closes the file descriptors contained in this native_handle_t + * + * return 0 on success, or a negative error code on failure + * + */ +int native_handle_close(const native_handle_t* h); + + +/* + * native_handle_create + * + * creates a native_handle_t and initializes it. must be destroyed with + * native_handle_delete(). + * + */ +native_handle_t* native_handle_create(int numFds, int numInts); + +/* + * native_handle_delete + * + * frees a native_handle_t allocated with native_handle_create(). + * This ONLY frees the memory allocated for the native_handle_t, but doesn't + * close the file descriptors; which can be achieved with native_handle_close(). + * + * return 0 on success, or a negative error code on failure + * + */ +int native_handle_delete(native_handle_t* h); + + +#ifdef __cplusplus +} +#endif + +#endif /* NATIVE_HANDLE_H_ */ diff --git a/include/cutils/open_memstream.h b/include/cutils/open_memstream.h new file mode 100644 index 0000000..b7998be --- /dev/null +++ b/include/cutils/open_memstream.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_OPEN_MEMSTREAM_H__ +#define __CUTILS_OPEN_MEMSTREAM_H__ + +#include <stdio.h> + +#ifndef HAVE_OPEN_MEMSTREAM + +#ifdef __cplusplus +extern "C" { +#endif + +FILE* open_memstream(char** bufp, size_t* sizep); + +#ifdef __cplusplus +} +#endif + +#endif /*!HAVE_OPEN_MEMSTREAM*/ + +#endif /*__CUTILS_OPEN_MEMSTREAM_H__*/ diff --git a/include/cutils/process_name.h b/include/cutils/process_name.h new file mode 100644 index 0000000..1e72e5c --- /dev/null +++ b/include/cutils/process_name.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Gives the current process a name. + */ + +#ifndef __PROCESS_NAME_H +#define __PROCESS_NAME_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Sets the current process name. + * + * Warning: This leaks a string every time you call it. Use judiciously! + */ +void set_process_name(const char* process_name); + +/** Gets the current process name. */ +const char* get_process_name(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __PROCESS_NAME_H */ diff --git a/include/cutils/properties.h b/include/cutils/properties.h new file mode 100644 index 0000000..2869948 --- /dev/null +++ b/include/cutils/properties.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_PROPERTIES_H +#define __CUTILS_PROPERTIES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* System properties are *small* name value pairs managed by the +** property service. If your data doesn't fit in the provided +** space it is not appropriate for a system property. +** +** WARNING: system/bionic/include/sys/system_properties.h also defines +** these, but with different names. (TODO: fix that) +*/ +#define PROPERTY_KEY_MAX 32 +#define PROPERTY_VALUE_MAX 92 + +/* property_get: returns the length of the value which will never be +** greater than PROPERTY_VALUE_MAX - 1 and will always be zero terminated. +** (the length does not include the terminating zero). +** +** If the property read fails or returns an empty value, the default +** value is used (if nonnull). +*/ +int property_get(const char *key, char *value, const char *default_value); + +/* property_set: returns 0 on success, < 0 on failure +*/ +int property_set(const char *key, const char *value); + +/* property_set_sync: returns 0 on success, < 0 on failure +** +** synchronized version of property_set, ensure the property is set when +** it returns to caller. +*/ +int property_set_sync(const char *key, const char *value); + +int property_list(void (*propfn)(const char *key, const char *value, void *cookie), void *cookie); + + +#ifdef HAVE_SYSTEM_PROPERTY_SERVER +/* + * We have an external property server instead of built-in libc support. + * Used by the simulator. + */ +#define SYSTEM_PROPERTY_PIPE_NAME "/tmp/android-sysprop" + +enum { + kSystemPropertyUnknown = 0, + kSystemPropertyGet, + kSystemPropertySet, + kSystemPropertyList +}; +#endif /*HAVE_SYSTEM_PROPERTY_SERVER*/ + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/cutils/record_stream.h b/include/cutils/record_stream.h new file mode 100644 index 0000000..bfac87a --- /dev/null +++ b/include/cutils/record_stream.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * A simple utility for reading fixed records out of a stream fd + */ + +#ifndef _CUTILS_RECORD_STREAM_H +#define _CUTILS_RECORD_STREAM_H + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct RecordStream RecordStream; + +extern RecordStream *record_stream_new(int fd, size_t maxRecordLen); +extern void record_stream_free(RecordStream *p_rs); + +extern int record_stream_get_next (RecordStream *p_rs, void ** p_outRecord, + size_t *p_outRecordLen); + +#ifdef __cplusplus +} +#endif + + +#endif /*_CUTILS_RECORD_STREAM_H*/ + diff --git a/include/cutils/sched_policy.h b/include/cutils/sched_policy.h new file mode 100644 index 0000000..eaf3993 --- /dev/null +++ b/include/cutils/sched_policy.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_SCHED_POLICY_H +#define __CUTILS_SCHED_POLICY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SP_BACKGROUND = 0, + SP_FOREGROUND = 1, +} SchedPolicy; + +extern int set_sched_policy(int tid, SchedPolicy policy); +extern int get_sched_policy(int tid, SchedPolicy *policy); + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_SCHED_POLICY_H */ diff --git a/include/cutils/selector.h b/include/cutils/selector.h new file mode 100644 index 0000000..dfc2a9d --- /dev/null +++ b/include/cutils/selector.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Framework for multiplexing I/O. A selector manages a set of file + * descriptors and calls out to user-provided callback functions to read and + * write data and handle errors. + */ + +#ifndef __SELECTOR_H +#define __SELECTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> + +/** + * Manages SelectableFds and invokes their callbacks at appropriate times. + */ +typedef struct Selector Selector; + +/** + * A selectable descriptor. Contains callbacks which the selector can invoke + * before calling select(), when the descriptor is readable or writable, and + * when the descriptor contains out-of-band data. Simply set a callback to + * NULL if you're not interested in that particular event. + * + * A selectable descriptor can indicate that it needs to be removed from the + * selector by setting the 'remove' flag. The selector will remove the + * descriptor at a later time and invoke the onRemove() callback. + * + * SelectableFd fields should only be modified from the selector loop. + */ +typedef struct SelectableFd SelectableFd; +struct SelectableFd { + + /** The file descriptor itself. */ + int fd; + + /** Pointer to user-specific data. Can be NULL. */ + void* data; + + /** + * Set this flag when you no longer wish to be selected. The selector + * will invoke onRemove() when the descriptor is actually removed. + */ + bool remove; + + /** + * Invoked by the selector before calling select. You can set up other + * callbacks from here as necessary. + */ + void (*beforeSelect)(SelectableFd* self); + + /** + * Invoked by the selector when the descriptor has data available. Set to + * NULL to indicate that you're not interested in reading. + */ + void (*onReadable)(SelectableFd* self); + + /** + * Invoked by the selector when the descriptor can accept data. Set to + * NULL to indicate that you're not interested in writing. + */ + void (*onWritable)(SelectableFd* self); + + /** + * Invoked by the selector when out-of-band (OOB) data is available. Set to + * NULL to indicate that you're not interested in OOB data. + */ + void (*onExcept)(SelectableFd* self); + + /** + * Invoked by the selector after the descriptor is removed from the + * selector but before the selector frees the SelectableFd memory. + */ + void (*onRemove)(SelectableFd* self); + + /** + * The selector which selected this fd. Set by the selector itself. + */ + Selector* selector; +}; + +/** + * Creates a new selector. + */ +Selector* selectorCreate(void); + +/** + * Creates a new selectable fd, adds it to the given selector and returns a + * pointer. Outside of 'selector' and 'fd', all fields are set to 0 or NULL + * by default. + * + * The selectable fd should only be modified from the selector loop thread. + */ +SelectableFd* selectorAdd(Selector* selector, int fd); + +/** + * Wakes up the selector even though no I/O events occurred. Use this + * to indicate that you're ready to write to a descriptor. + */ +void selectorWakeUp(Selector* selector); + +/** + * Loops continuously selecting file descriptors and firing events. + * Does not return. + */ +void selectorLoop(Selector* selector); + +#ifdef __cplusplus +} +#endif + +#endif /* __SELECTOR_H */ diff --git a/include/cutils/sockets.h b/include/cutils/sockets.h new file mode 100644 index 0000000..aa8682e --- /dev/null +++ b/include/cutils/sockets.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_SOCKETS_H +#define __CUTILS_SOCKETS_H + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#ifdef HAVE_WINSOCK +#include <winsock2.h> +typedef int socklen_t; +#elif HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_" +#define ANDROID_SOCKET_DIR "/dev/socket" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * android_get_control_socket - simple helper function to get the file + * descriptor of our init-managed Unix domain socket. `name' is the name of the + * socket, as given in init.rc. Returns -1 on error. + * + * This is inline and not in libcutils proper because we want to use this in + * third-party daemons with minimal modification. + */ +static inline int android_get_control_socket(const char *name) +{ + char key[64] = ANDROID_SOCKET_ENV_PREFIX; + const char *val; + int fd; + + /* build our environment variable, counting cycles like a wolf ... */ +#if HAVE_STRLCPY + strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1, + name, + sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX)); +#else /* for the host, which may lack the almightly strncpy ... */ + strncpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1, + name, + sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX)); + key[sizeof(key)-1] = '\0'; +#endif + + val = getenv(key); + if (!val) + return -1; + + errno = 0; + fd = strtol(val, NULL, 10); + if (errno) + return -1; + + return fd; +} + +/* + * See also android.os.LocalSocketAddress.Namespace + */ +// Linux "abstract" (non-filesystem) namespace +#define ANDROID_SOCKET_NAMESPACE_ABSTRACT 0 +// Android "reserved" (/dev/socket) namespace +#define ANDROID_SOCKET_NAMESPACE_RESERVED 1 +// Normal filesystem namespace +#define ANDROID_SOCKET_NAMESPACE_FILESYSTEM 2 + +extern int socket_loopback_client(int port, int type); +extern int socket_network_client(const char *host, int port, int type); +extern int socket_loopback_server(int port, int type); +extern int socket_local_server(const char *name, int namespaceId, int type); +extern int socket_local_server_bind(int s, const char *name, int namespaceId); +extern int socket_local_client_connect(int fd, + const char *name, int namespaceId, int type); +extern int socket_local_client(const char *name, int namespaceId, int type); +extern int socket_inaddr_any_server(int port, int type); + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_SOCKETS_H */ diff --git a/include/cutils/threads.h b/include/cutils/threads.h new file mode 100644 index 0000000..acf8f48 --- /dev/null +++ b/include/cutils/threads.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LIBS_CUTILS_THREADS_H +#define _LIBS_CUTILS_THREADS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/***********************************************************************/ +/***********************************************************************/ +/***** *****/ +/***** local thread storage *****/ +/***** *****/ +/***********************************************************************/ +/***********************************************************************/ + +#ifdef HAVE_PTHREADS + +#include <pthread.h> + +typedef struct { + pthread_mutex_t lock; + int has_tls; + pthread_key_t tls; + +} thread_store_t; + +#define THREAD_STORE_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, 0, 0 } + +#elif defined HAVE_WIN32_THREADS + +#include <windows.h> + +typedef struct { + int lock_init; + int has_tls; + DWORD tls; + CRITICAL_SECTION lock; + +} thread_store_t; + +#define THREAD_STORE_INITIALIZER { 0, 0, 0, {0, 0, 0, 0, 0, 0} } + +#else +# error "no thread_store_t implementation for your platform !!" +#endif + +typedef void (*thread_store_destruct_t)(void* value); + +extern void* thread_store_get(thread_store_t* store); + +extern void thread_store_set(thread_store_t* store, + void* value, + thread_store_destruct_t destroy); + +/***********************************************************************/ +/***********************************************************************/ +/***** *****/ +/***** mutexes *****/ +/***** *****/ +/***********************************************************************/ +/***********************************************************************/ + +#ifdef HAVE_PTHREADS + +typedef pthread_mutex_t mutex_t; + +#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER + +static __inline__ void mutex_lock(mutex_t* lock) +{ + pthread_mutex_lock(lock); +} +static __inline__ void mutex_unlock(mutex_t* lock) +{ + pthread_mutex_unlock(lock); +} +static __inline__ int mutex_init(mutex_t* lock) +{ + return pthread_mutex_init(lock, NULL); +} +static __inline__ void mutex_destroy(mutex_t* lock) +{ + pthread_mutex_destroy(lock); +} +#endif + +#ifdef HAVE_WIN32_THREADS +typedef struct { + int init; + CRITICAL_SECTION lock[1]; +} mutex_t; + +#define MUTEX_INITIALIZER { 0, {{ NULL, 0, 0, NULL, NULL, 0 }} } + +static __inline__ void mutex_lock(mutex_t* lock) +{ + if (!lock->init) { + lock->init = 1; + InitializeCriticalSection( lock->lock ); + lock->init = 2; + } else while (lock->init != 2) + Sleep(10); + + EnterCriticalSection(lock->lock); +} + +static __inline__ void mutex_unlock(mutex_t* lock) +{ + LeaveCriticalSection(lock->lock); +} +static __inline__ int mutex_init(mutex_t* lock) +{ + InitializeCriticalSection(lock->lock); + lock->init = 2; + return 0; +} +static __inline__ void mutex_destroy(mutex_t* lock) +{ + if (lock->init) { + lock->init = 0; + DeleteCriticalSection(lock->lock); + } +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBS_CUTILS_THREADS_H */ diff --git a/include/cutils/tztime.h b/include/cutils/tztime.h new file mode 100644 index 0000000..cf103ca --- /dev/null +++ b/include/cutils/tztime.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _CUTILS_TZTIME_H +#define _CUTILS_TZTIME_H + +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +time_t mktime_tz(struct tm * const tmp, char const * tz); +void localtime_tz(const time_t * const timep, struct tm * tmp, const char* tz); + +#ifndef HAVE_ANDROID_OS +/* the following is defined in <time.h> in Bionic */ + +struct strftime_locale { + const char *mon[12]; /* short names */ + const char *month[12]; /* long names */ + const char *standalone_month[12]; /* long standalone names */ + const char *wday[7]; /* short names */ + const char *weekday[7]; /* long names */ + const char *X_fmt; + const char *x_fmt; + const char *c_fmt; + const char *am; + const char *pm; + const char *date_fmt; +}; + +size_t strftime_tz(char *s, size_t max, const char *format, const struct tm *tm, const struct strftime_locale *locale); + +#endif /* !HAVE_ANDROID_OS */ + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_TZTIME_H */ + diff --git a/include/cutils/uio.h b/include/cutils/uio.h new file mode 100644 index 0000000..01a74d2 --- /dev/null +++ b/include/cutils/uio.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// implementation of sys/uio.h for platforms that don't have it (Win32) +// +#ifndef _LIBS_CUTILS_UIO_H +#define _LIBS_CUTILS_UIO_H + +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#else + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> + +struct iovec { + const void* iov_base; + size_t iov_len; +}; + +extern int readv( int fd, struct iovec* vecs, int count ); +extern int writev( int fd, const struct iovec* vecs, int count ); + +#ifdef __cplusplus +} +#endif + +#endif /* !HAVE_SYS_UIO_H */ + +#endif /* _LIBS_UTILS_UIO_H */ + diff --git a/include/cutils/zygote.h b/include/cutils/zygote.h new file mode 100644 index 0000000..22721a6 --- /dev/null +++ b/include/cutils/zygote.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CUTILS_ZYGOTE_H +#define __CUTILS_ZYGOTE_H + +#ifdef __cplusplus +extern "C" { +#endif + +int zygote_run_oneshot(int sendStdio, int argc, const char **argv); +int zygote_run(int argc, const char **argv); +int zygote_run_wait(int argc, const char **argv, void (*post_run_func)(int)); + +#ifdef __cplusplus +} +#endif + +#endif /* __CUTILS_ZYGOTE_H */ diff --git a/include/zipfile/zipfile.h b/include/zipfile/zipfile.h new file mode 100644 index 0000000..0ae4ee4 --- /dev/null +++ b/include/zipfile/zipfile.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ZIPFILE_ZIPFILE_H +#define _ZIPFILE_ZIPFILE_H + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* zipfile_t; +typedef void* zipentry_t; + +// Provide a buffer. Returns NULL on failure. +zipfile_t init_zipfile(const void* data, size_t size); + +// Release the zipfile resources. +void release_zipfile(zipfile_t file); + +// Get a named entry object. Returns NULL if it doesn't exist +// or if we won't be able to decompress it. The zipentry_t is +// freed by release_zipfile() +zipentry_t lookup_zipentry(zipfile_t file, const char* entryName); + +// Return the size of the entry. +size_t get_zipentry_size(zipentry_t entry); + +// return the filename of this entry, you own the memory returned +char* get_zipentry_name(zipentry_t entry); + +// The buffer must be 1.001 times the buffer size returned +// by get_zipentry_size. Returns nonzero on failure. +int decompress_zipentry(zipentry_t entry, void* buf, int bufsize); + +// iterate through the entries in the zip file. pass a pointer to +// a void* initialized to NULL to start. Returns NULL when done +zipentry_t iterate_zipfile(zipfile_t file, void** cookie); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _ZIPFILE_ZIPFILE_H diff --git a/libcutils/Android.mk b/libcutils/Android.mk new file mode 100644 index 0000000..dec41a5 --- /dev/null +++ b/libcutils/Android.mk @@ -0,0 +1,149 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +LOCAL_PATH := $(my-dir) +include $(CLEAR_VARS) + +ifeq ($(TARGET_CPU_SMP),true) + targetSmpFlag := -DANDROID_SMP=1 +else + targetSmpFlag := -DANDROID_SMP=0 +endif +hostSmpFlag := -DANDROID_SMP=0 + +commonSources := \ + array.c \ + hashmap.c \ + atomic.c.arm \ + native_handle.c \ + buffer.c \ + socket_inaddr_any_server.c \ + socket_local_client.c \ + socket_local_server.c \ + socket_loopback_client.c \ + socket_loopback_server.c \ + socket_network_client.c \ + config_utils.c \ + cpu_info.c \ + load_file.c \ + open_memstream.c \ + strdup16to8.c \ + strdup8to16.c \ + record_stream.c \ + process_name.c \ + properties.c \ + threads.c \ + sched_policy.c \ + iosched_policy.c + +commonHostSources := \ + ashmem-host.c + +# some files must not be compiled when building against Mingw +# they correspond to features not used by our host development tools +# which are also hard or even impossible to port to native Win32 +WINDOWS_HOST_ONLY := +ifeq ($(HOST_OS),windows) + ifeq ($(strip $(USE_CYGWIN)),) + WINDOWS_HOST_ONLY := 1 + endif +endif +# USE_MINGW is defined when we build against Mingw on Linux +ifneq ($(strip $(USE_MINGW)),) + WINDOWS_HOST_ONLY := 1 +endif + +ifeq ($(WINDOWS_HOST_ONLY),1) + commonSources += \ + uio.c +else + commonSources += \ + abort_socket.c \ + mspace.c \ + selector.c \ + tztime.c \ + zygote.c + + commonHostSources += \ + tzstrftime.c +endif + + +# Static library for host +# ======================================================== +LOCAL_MODULE := libcutils +LOCAL_SRC_FILES := $(commonSources) $(commonHostSources) dlmalloc_stubs.c +LOCAL_LDLIBS := -lpthread +LOCAL_STATIC_LIBRARIES := liblog +LOCAL_CFLAGS += $(hostSmpFlag) +include $(BUILD_HOST_STATIC_LIBRARY) + + +ifeq ($(TARGET_SIMULATOR),true) + +# Shared library for simulator +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libcutils +LOCAL_SRC_FILES := $(commonSources) $(commonHostSources) memory.c dlmalloc_stubs.c +LOCAL_LDLIBS := -lpthread +LOCAL_SHARED_LIBRARIES := liblog +LOCAL_CFLAGS += $(targetSmpFlag) +include $(BUILD_SHARED_LIBRARY) + +else #!sim + +# Shared and static library for target +# ======================================================== + +targetSources := ashmem-dev.c mq.c +ifeq ($(TARGET_ARCH),arm) +targetSources += arch-arm/memset32.S +else # !arm +ifeq ($(TARGET_ARCH),sh) +targetSources += memory.c atomic-android-sh.c +else # !sh +ifeq ($(TARGET_ARCH_VARIANT),x86-atom) +LOCAL_CFLAGS += -DHAVE_MEMSET16 -DHAVE_MEMSET32 +LOCAL_SRC_FILES += arch-x86/android_memset16.S arch-x86/android_memset32.S memory.c +else # !x86-atom +LOCAL_SRC_FILES += memory.c +endif # !x86-atom +endif # !sh +endif # !arm + +include $(CLEAR_VARS) +LOCAL_MODULE := libcutils +LOCAL_SRC_FILES := $(commonSources) $(targetSources) +LOCAL_CFLAGS += $(targetCFLAGS) $(targetSmpFlag) + +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) +LOCAL_STATIC_LIBRARIES := liblog +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := libcutils +ifeq ($(BOARD_NEEDS_CUTILS_LOG),true) +LOCAL_WHOLE_STATIC_LIBRARIES := libcutils +else +LOCAL_SRC_FILES := $(commonSources) $(targetSources) +endif +LOCAL_CFLAGS += $(targetCFLAGS) $(targetSmpFlag) + +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) +LOCAL_SHARED_LIBRARIES := liblog +include $(BUILD_SHARED_LIBRARY) + +endif #!sim diff --git a/libcutils/MODULE_LICENSE_APACHE2 b/libcutils/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/libcutils/MODULE_LICENSE_APACHE2 diff --git a/libcutils/Makefile.am b/libcutils/Makefile.am new file mode 100644 index 0000000..e08d4dd --- /dev/null +++ b/libcutils/Makefile.am @@ -0,0 +1,49 @@ +AUTOMAKE_OPTIONS = 1.4 foreign +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I. \ +-I$(top_builddir) \ +-I$(top_srcdir) \ +-I$(top_srcdir)/include \ +-I$(top_builddir)/include + +lib_LTLIBRARIES = libcutils.la + +libcutils_la_SOURCES = \ +array.c \ +hashmap.c \ +native_handle.c \ +buffer.c \ +socket_inaddr_any_server.c \ +socket_local_client.c \ +socket_local_server.c \ +socket_loopback_client.c \ +socket_loopback_server.c \ +socket_network_client.c \ +config_utils.c \ +cpu_info.c \ +load_file.c \ +open_memstream.c \ +strdup16to8.c \ +strdup8to16.c \ +record_stream.c \ +process_name.c \ +properties.c \ +threads.c \ +sched_policy.c \ +iosched_policy.c \ +ashmem-host.c \ +abort_socket.c \ +mspace.c \ +selector.c \ +tztime.c \ +tzstrftime.c \ +dlmalloc_stubs.c + +libcutils_la_CFLAGS = -DHAVE_PTHREADS -DANDROID_SMP=0 +libcutils_la_LIBADD = +libcutils_la_LDFLAGS = + +docdir = $(datadir)/doc/cutils +doc_DATA = NOTICE diff --git a/libcutils/NOTICE b/libcutils/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/libcutils/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/libcutils/abort_socket.c b/libcutils/abort_socket.c new file mode 100644 index 0000000..6a5e5e4 --- /dev/null +++ b/libcutils/abort_socket.c @@ -0,0 +1,293 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/poll.h> + +#include "cutils/abort_socket.h" + +struct asocket *asocket_init(int fd) { + int abort_fd[2]; + int flags; + struct asocket *s; + + /* set primary socket to non-blocking */ + flags = fcntl(fd, F_GETFL); + if (flags == -1) + return NULL; + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) + return NULL; + + /* create pipe with non-blocking write, so that asocket_close() cannot + block */ + if (pipe(abort_fd)) + return NULL; + flags = fcntl(abort_fd[1], F_GETFL); + if (flags == -1) + return NULL; + if (fcntl(abort_fd[1], F_SETFL, flags | O_NONBLOCK)) + return NULL; + + s = malloc(sizeof(struct asocket)); + if (!s) + return NULL; + + s->fd = fd; + s->abort_fd[0] = abort_fd[0]; + s->abort_fd[1] = abort_fd[1]; + + return s; +} + +int asocket_connect(struct asocket *s, const struct sockaddr *addr, + socklen_t addrlen, int timeout) { + + int ret; + + do { + ret = connect(s->fd, addr, addrlen); + } while (ret && errno == EINTR); + + if (ret && errno == EINPROGRESS) { + /* ready to poll() */ + socklen_t retlen; + struct pollfd pfd[2]; + + pfd[0].fd = s->fd; + pfd[0].events = POLLOUT; + pfd[0].revents = 0; + pfd[1].fd = s->abort_fd[0]; + pfd[1].events = POLLIN; + pfd[1].revents = 0; + + do { + ret = poll(pfd, 2, timeout); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) + return -1; + else if (ret == 0) { + /* timeout */ + errno = ETIMEDOUT; + return -1; + } + + if (pfd[1].revents) { + /* abort due to asocket_abort() */ + errno = ECANCELED; + return -1; + } + + if (pfd[0].revents) { + if (pfd[0].revents & POLLOUT) { + /* connect call complete, read return code */ + retlen = sizeof(ret); + if (getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &ret, &retlen)) + return -1; + /* got connect() return code */ + if (ret) { + errno = ret; + } + } else { + /* some error event on this fd */ + errno = ECONNABORTED; + return -1; + } + } + } + + return ret; +} + +int asocket_accept(struct asocket *s, struct sockaddr *addr, + socklen_t *addrlen, int timeout) { + + int ret; + struct pollfd pfd[2]; + + pfd[0].fd = s->fd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + pfd[1].fd = s->abort_fd[0]; + pfd[1].events = POLLIN; + pfd[1].revents = 0; + + do { + ret = poll(pfd, 2, timeout); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) + return -1; + else if (ret == 0) { + /* timeout */ + errno = ETIMEDOUT; + return -1; + } + + if (pfd[1].revents) { + /* abort due to asocket_abort() */ + errno = ECANCELED; + return -1; + } + + if (pfd[0].revents) { + if (pfd[0].revents & POLLIN) { + /* ready to accept() without blocking */ + do { + ret = accept(s->fd, addr, addrlen); + } while (ret < 0 && errno == EINTR); + } else { + /* some error event on this fd */ + errno = ECONNABORTED; + return -1; + } + } + + return ret; +} + +int asocket_read(struct asocket *s, void *buf, size_t count, int timeout) { + int ret; + struct pollfd pfd[2]; + + pfd[0].fd = s->fd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + pfd[1].fd = s->abort_fd[0]; + pfd[1].events = POLLIN; + pfd[1].revents = 0; + + do { + ret = poll(pfd, 2, timeout); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) + return -1; + else if (ret == 0) { + /* timeout */ + errno = ETIMEDOUT; + return -1; + } + + if (pfd[1].revents) { + /* abort due to asocket_abort() */ + errno = ECANCELED; + return -1; + } + + if (pfd[0].revents) { + if (pfd[0].revents & POLLIN) { + /* ready to read() without blocking */ + do { + ret = read(s->fd, buf, count); + } while (ret < 0 && errno == EINTR); + } else { + /* some error event on this fd */ + errno = ECONNABORTED; + return -1; + } + } + + return ret; +} + +int asocket_write(struct asocket *s, const void *buf, size_t count, + int timeout) { + int ret; + struct pollfd pfd[2]; + + pfd[0].fd = s->fd; + pfd[0].events = POLLOUT; + pfd[0].revents = 0; + pfd[1].fd = s->abort_fd[0]; + pfd[1].events = POLLIN; + pfd[1].revents = 0; + + do { + ret = poll(pfd, 2, timeout); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) + return -1; + else if (ret == 0) { + /* timeout */ + errno = ETIMEDOUT; + return -1; + } + + if (pfd[1].revents) { + /* abort due to asocket_abort() */ + errno = ECANCELED; + return -1; + } + + if (pfd[0].revents) { + if (pfd[0].revents & POLLOUT) { + /* ready to write() without blocking */ + do { + ret = write(s->fd, buf, count); + } while (ret < 0 && errno == EINTR); + } else { + /* some error event on this fd */ + errno = ECONNABORTED; + return -1; + } + } + + return ret; +} + +void asocket_abort(struct asocket *s) { + int ret; + char buf = 0; + + /* Prevent further use of fd, without yet releasing the fd */ + shutdown(s->fd, SHUT_RDWR); + + /* wake up calls blocked at poll() */ + do { + ret = write(s->abort_fd[1], &buf, 1); + } while (ret < 0 && errno == EINTR); +} + +void asocket_destroy(struct asocket *s) { + struct asocket s_copy = *s; + + /* Clients should *not* be using these fd's after calling + asocket_destroy(), but in case they do, set to -1 so they cannot use a + stale fd */ + s->fd = -1; + s->abort_fd[0] = -1; + s->abort_fd[1] = -1; + + /* Call asocket_abort() in case there are still threads blocked on this + socket. Clients should not rely on this behavior - it is racy because we + are about to close() these sockets - clients should instead make sure + all threads are done with the socket before calling asocket_destory(). + */ + asocket_abort(&s_copy); + + /* enough safety checks, close and release memory */ + close(s_copy.abort_fd[1]); + close(s_copy.abort_fd[0]); + close(s_copy.fd); + + free(s); +} diff --git a/libcutils/arch-arm/memset32.S b/libcutils/arch-arm/memset32.S new file mode 100644 index 0000000..4697265 --- /dev/null +++ b/libcutils/arch-arm/memset32.S @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * memset32.S + * + */ + + .text + .align + + .global android_memset32 + .type android_memset32, %function + .global android_memset16 + .type android_memset16, %function + + /* + * Optimized memset32 and memset16 for ARM. + * + * void android_memset16(uint16_t* dst, uint16_t value, size_t size); + * void android_memset32(uint32_t* dst, uint32_t value, size_t size); + * + */ + +android_memset16: + .fnstart + cmp r2, #1 + bxle lr + + /* expand the data to 32 bits */ + mov r1, r1, lsl #16 + orr r1, r1, r1, lsr #16 + + /* align to 32 bits */ + tst r0, #2 + strneh r1, [r0], #2 + subne r2, r2, #2 + .fnend + +android_memset32: + .fnstart + .save {lr} + str lr, [sp, #-4]! + + /* align the destination to a cache-line */ + mov r12, r1 + mov lr, r1 + rsb r3, r0, #0 + ands r3, r3, #0x1C + beq .Laligned32 + cmp r3, r2 + andhi r3, r2, #0x1C + sub r2, r2, r3 + + /* conditionally writes 0 to 7 words (length in r3) */ + movs r3, r3, lsl #28 + stmcsia r0!, {r1, lr} + stmcsia r0!, {r1, lr} + stmmiia r0!, {r1, lr} + movs r3, r3, lsl #2 + strcs r1, [r0], #4 + +.Laligned32: + mov r3, r1 +1: subs r2, r2, #32 + stmhsia r0!, {r1,r3,r12,lr} + stmhsia r0!, {r1,r3,r12,lr} + bhs 1b + add r2, r2, #32 + + /* conditionally stores 0 to 30 bytes */ + movs r2, r2, lsl #28 + stmcsia r0!, {r1,r3,r12,lr} + stmmiia r0!, {r1,lr} + movs r2, r2, lsl #2 + strcs r1, [r0], #4 + strmih lr, [r0], #2 + + ldr lr, [sp], #4 + bx lr + .fnend diff --git a/libcutils/arch-x86/android_memset16.S b/libcutils/arch-x86/android_memset16.S new file mode 100644 index 0000000..b1f09cb --- /dev/null +++ b/libcutils/arch-x86/android_memset16.S @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Intel Corporation + */ + +#if defined(USE_SSE2) + +# include "cache_wrapper.S" +# undef __i686 +# define USE_AS_ANDROID +# define sse2_memset16_atom android_memset16 +# include "sse2-memset16-atom.S" + +#else + +# include "memset16.S" + +#endif diff --git a/libcutils/arch-x86/android_memset32.S b/libcutils/arch-x86/android_memset32.S new file mode 100644 index 0000000..1fb2ffe --- /dev/null +++ b/libcutils/arch-x86/android_memset32.S @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Intel Corporation + */ + +#if defined(USE_SSE2) + +# include "cache_wrapper.S" +# undef __i686 +# define USE_AS_ANDROID +# define sse2_memset32_atom android_memset32 +# include "sse2-memset32-atom.S" + +#else + +# include "memset32.S" + +#endif + diff --git a/libcutils/arch-x86/cache_wrapper.S b/libcutils/arch-x86/cache_wrapper.S new file mode 100644 index 0000000..508fdd3 --- /dev/null +++ b/libcutils/arch-x86/cache_wrapper.S @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Intel Corporation + */ + +/* Values are optimized for Atom */ +#define SHARED_CACHE_SIZE (512*1024) /* Atom L2 Cache */ +#define DATA_CACHE_SIZE (24*1024) /* Atom L1 Data Cache */ +#define SHARED_CACHE_SIZE_HALF (SHARED_CACHE_SIZE / 2) +#define DATA_CACHE_SIZE_HALF (DATA_CACHE_SIZE / 2) diff --git a/libcutils/arch-x86/sse2-memset16-atom.S b/libcutils/arch-x86/sse2-memset16-atom.S new file mode 100644 index 0000000..cafec82 --- /dev/null +++ b/libcutils/arch-x86/sse2-memset16-atom.S @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Intel Corporation + */ + +#ifndef L +# define L(label) .L##label +#endif + +#ifndef ALIGN +# define ALIGN(n) .p2align n +#endif + +#ifndef cfi_startproc +# define cfi_startproc .cfi_startproc +#endif + +#ifndef cfi_endproc +# define cfi_endproc .cfi_endproc +#endif + +#ifndef cfi_rel_offset +# define cfi_rel_offset(reg, off) .cfi_rel_offset reg, off +#endif + +#ifndef cfi_restore +# define cfi_restore(reg) .cfi_restore reg +#endif + +#ifndef cfi_adjust_cfa_offset +# define cfi_adjust_cfa_offset(off) .cfi_adjust_cfa_offset off +#endif + +#ifndef ENTRY +# define ENTRY(name) \ + .type name, @function; \ + .globl name; \ + .p2align 4; \ +name: \ + cfi_startproc +#endif + +#ifndef END +# define END(name) \ + cfi_endproc; \ + .size name, .-name +#endif + +#define CFI_PUSH(REG) \ + cfi_adjust_cfa_offset (4); \ + cfi_rel_offset (REG, 0) + +#define CFI_POP(REG) \ + cfi_adjust_cfa_offset (-4); \ + cfi_restore (REG) + +#define PUSH(REG) pushl REG; CFI_PUSH (REG) +#define POP(REG) popl REG; CFI_POP (REG) + +#ifdef USE_AS_BZERO16 +# define DEST PARMS +# define LEN DEST+4 +#else +# define DEST PARMS +# define CHR DEST+4 +# define LEN CHR+4 +#endif + +#if 1 +# define SETRTNVAL +#else +# define SETRTNVAL movl DEST(%esp), %eax +#endif + +#ifdef SHARED +# define ENTRANCE PUSH (%ebx); +# define RETURN_END POP (%ebx); ret +# define RETURN RETURN_END; CFI_PUSH (%ebx) +# define PARMS 8 /* Preserve EBX. */ +# define JMPTBL(I, B) I - B + +/* Load an entry in a jump table into EBX and branch to it. TABLE is a + jump table with relative offsets. */ +# define BRANCH_TO_JMPTBL_ENTRY(TABLE) \ + /* We first load PC into EBX. */ \ + call __i686.get_pc_thunk.bx; \ + /* Get the address of the jump table. */ \ + add $(TABLE - .), %ebx; \ + /* Get the entry and convert the relative offset to the \ + absolute address. */ \ + add (%ebx,%ecx,4), %ebx; \ + /* We loaded the jump table and adjuested EDX. Go. */ \ + jmp *%ebx + + .section .gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits + .globl __i686.get_pc_thunk.bx + .hidden __i686.get_pc_thunk.bx + ALIGN (4) + .type __i686.get_pc_thunk.bx,@function +__i686.get_pc_thunk.bx: + movl (%esp), %ebx + ret +#else +# define ENTRANCE +# define RETURN_END ret +# define RETURN RETURN_END +# define PARMS 4 +# define JMPTBL(I, B) I + +/* Branch to an entry in a jump table. TABLE is a jump table with + absolute offsets. */ +# define BRANCH_TO_JMPTBL_ENTRY(TABLE) \ + jmp *TABLE(,%ecx,4) +#endif + + .section .text.sse2,"ax",@progbits + ALIGN (4) +ENTRY (sse2_memset16_atom) + ENTRANCE + + movl LEN(%esp), %ecx +#ifdef USE_AS_ANDROID + shr $1, %ecx +#endif +#ifdef USE_AS_BZERO16 + xor %eax, %eax +#else + movzwl CHR(%esp), %eax + mov %eax, %edx + shl $16, %eax + or %edx, %eax +#endif + movl DEST(%esp), %edx + cmp $32, %ecx + jae L(32wordsormore) + +L(write_less32words): + lea (%edx, %ecx, 2), %edx + BRANCH_TO_JMPTBL_ENTRY (L(table_less32words)) + + + .pushsection .rodata.sse2,"a",@progbits + ALIGN (2) +L(table_less32words): + .int JMPTBL (L(write_0words), L(table_less32words)) + .int JMPTBL (L(write_1words), L(table_less32words)) + .int JMPTBL (L(write_2words), L(table_less32words)) + .int JMPTBL (L(write_3words), L(table_less32words)) + .int JMPTBL (L(write_4words), L(table_less32words)) + .int JMPTBL (L(write_5words), L(table_less32words)) + .int JMPTBL (L(write_6words), L(table_less32words)) + .int JMPTBL (L(write_7words), L(table_less32words)) + .int JMPTBL (L(write_8words), L(table_less32words)) + .int JMPTBL (L(write_9words), L(table_less32words)) + .int JMPTBL (L(write_10words), L(table_less32words)) + .int JMPTBL (L(write_11words), L(table_less32words)) + .int JMPTBL (L(write_12words), L(table_less32words)) + .int JMPTBL (L(write_13words), L(table_less32words)) + .int JMPTBL (L(write_14words), L(table_less32words)) + .int JMPTBL (L(write_15words), L(table_less32words)) + .int JMPTBL (L(write_16words), L(table_less32words)) + .int JMPTBL (L(write_17words), L(table_less32words)) + .int JMPTBL (L(write_18words), L(table_less32words)) + .int JMPTBL (L(write_19words), L(table_less32words)) + .int JMPTBL (L(write_20words), L(table_less32words)) + .int JMPTBL (L(write_21words), L(table_less32words)) + .int JMPTBL (L(write_22words), L(table_less32words)) + .int JMPTBL (L(write_23words), L(table_less32words)) + .int JMPTBL (L(write_24words), L(table_less32words)) + .int JMPTBL (L(write_25words), L(table_less32words)) + .int JMPTBL (L(write_26words), L(table_less32words)) + .int JMPTBL (L(write_27words), L(table_less32words)) + .int JMPTBL (L(write_28words), L(table_less32words)) + .int JMPTBL (L(write_29words), L(table_less32words)) + .int JMPTBL (L(write_30words), L(table_less32words)) + .int JMPTBL (L(write_31words), L(table_less32words)) + .popsection + + ALIGN (4) +L(write_28words): + movl %eax, -56(%edx) + movl %eax, -52(%edx) +L(write_24words): + movl %eax, -48(%edx) + movl %eax, -44(%edx) +L(write_20words): + movl %eax, -40(%edx) + movl %eax, -36(%edx) +L(write_16words): + movl %eax, -32(%edx) + movl %eax, -28(%edx) +L(write_12words): + movl %eax, -24(%edx) + movl %eax, -20(%edx) +L(write_8words): + movl %eax, -16(%edx) + movl %eax, -12(%edx) +L(write_4words): + movl %eax, -8(%edx) + movl %eax, -4(%edx) +L(write_0words): + SETRTNVAL + RETURN + + ALIGN (4) +L(write_29words): + movl %eax, -58(%edx) + movl %eax, -54(%edx) +L(write_25words): + movl %eax, -50(%edx) + movl %eax, -46(%edx) +L(write_21words): + movl %eax, -42(%edx) + movl %eax, -38(%edx) +L(write_17words): + movl %eax, -34(%edx) + movl %eax, -30(%edx) +L(write_13words): + movl %eax, -26(%edx) + movl %eax, -22(%edx) +L(write_9words): + movl %eax, -18(%edx) + movl %eax, -14(%edx) +L(write_5words): + movl %eax, -10(%edx) + movl %eax, -6(%edx) +L(write_1words): + mov %ax, -2(%edx) + SETRTNVAL + RETURN + + ALIGN (4) +L(write_30words): + movl %eax, -60(%edx) + movl %eax, -56(%edx) +L(write_26words): + movl %eax, -52(%edx) + movl %eax, -48(%edx) +L(write_22words): + movl %eax, -44(%edx) + movl %eax, -40(%edx) +L(write_18words): + movl %eax, -36(%edx) + movl %eax, -32(%edx) +L(write_14words): + movl %eax, -28(%edx) + movl %eax, -24(%edx) +L(write_10words): + movl %eax, -20(%edx) + movl %eax, -16(%edx) +L(write_6words): + movl %eax, -12(%edx) + movl %eax, -8(%edx) +L(write_2words): + movl %eax, -4(%edx) + SETRTNVAL + RETURN + + ALIGN (4) +L(write_31words): + movl %eax, -62(%edx) + movl %eax, -58(%edx) +L(write_27words): + movl %eax, -54(%edx) + movl %eax, -50(%edx) +L(write_23words): + movl %eax, -46(%edx) + movl %eax, -42(%edx) +L(write_19words): + movl %eax, -38(%edx) + movl %eax, -34(%edx) +L(write_15words): + movl %eax, -30(%edx) + movl %eax, -26(%edx) +L(write_11words): + movl %eax, -22(%edx) + movl %eax, -18(%edx) +L(write_7words): + movl %eax, -14(%edx) + movl %eax, -10(%edx) +L(write_3words): + movl %eax, -6(%edx) + movw %ax, -2(%edx) + SETRTNVAL + RETURN + + ALIGN (4) + +L(32wordsormore): + shl $1, %ecx + test $0x01, %edx + jz L(aligned2bytes) + mov %eax, (%edx) + mov %eax, -4(%edx, %ecx) + sub $2, %ecx + add $1, %edx + rol $8, %eax +L(aligned2bytes): +#ifdef USE_AS_BZERO16 + pxor %xmm0, %xmm0 +#else + movd %eax, %xmm0 + pshufd $0, %xmm0, %xmm0 +#endif + testl $0xf, %edx + jz L(aligned_16) +/* ECX > 32 and EDX is not 16 byte aligned. */ +L(not_aligned_16): + movdqu %xmm0, (%edx) + movl %edx, %eax + and $-16, %edx + add $16, %edx + sub %edx, %eax + add %eax, %ecx + movd %xmm0, %eax + + ALIGN (4) +L(aligned_16): + cmp $128, %ecx + jae L(128bytesormore) + +L(aligned_16_less128bytes): + add %ecx, %edx + shr $1, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + ALIGN (4) +L(128bytesormore): +#ifdef SHARED_CACHE_SIZE + PUSH (%ebx) + mov $SHARED_CACHE_SIZE, %ebx +#else +# ifdef SHARED + call __i686.get_pc_thunk.bx + add $_GLOBAL_OFFSET_TABLE_, %ebx + mov __x86_shared_cache_size@GOTOFF(%ebx), %ebx +# else + PUSH (%ebx) + mov __x86_shared_cache_size, %ebx +# endif +#endif + cmp %ebx, %ecx + jae L(128bytesormore_nt_start) + + +#ifdef DATA_CACHE_SIZE + POP (%ebx) +# define RESTORE_EBX_STATE CFI_PUSH (%ebx) + cmp $DATA_CACHE_SIZE, %ecx +#else +# ifdef SHARED +# define RESTORE_EBX_STATE + call __i686.get_pc_thunk.bx + add $_GLOBAL_OFFSET_TABLE_, %ebx + cmp __x86_data_cache_size@GOTOFF(%ebx), %ecx +# else + POP (%ebx) +# define RESTORE_EBX_STATE CFI_PUSH (%ebx) + cmp __x86_data_cache_size, %ecx +# endif +#endif + + jae L(128bytes_L2_normal) + subl $128, %ecx +L(128bytesormore_normal): + sub $128, %ecx + movdqa %xmm0, (%edx) + movdqa %xmm0, 0x10(%edx) + movdqa %xmm0, 0x20(%edx) + movdqa %xmm0, 0x30(%edx) + movdqa %xmm0, 0x40(%edx) + movdqa %xmm0, 0x50(%edx) + movdqa %xmm0, 0x60(%edx) + movdqa %xmm0, 0x70(%edx) + lea 128(%edx), %edx + jb L(128bytesless_normal) + + + sub $128, %ecx + movdqa %xmm0, (%edx) + movdqa %xmm0, 0x10(%edx) + movdqa %xmm0, 0x20(%edx) + movdqa %xmm0, 0x30(%edx) + movdqa %xmm0, 0x40(%edx) + movdqa %xmm0, 0x50(%edx) + movdqa %xmm0, 0x60(%edx) + movdqa %xmm0, 0x70(%edx) + lea 128(%edx), %edx + jae L(128bytesormore_normal) + +L(128bytesless_normal): + lea 128(%ecx), %ecx + add %ecx, %edx + shr $1, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + ALIGN (4) +L(128bytes_L2_normal): + prefetcht0 0x380(%edx) + prefetcht0 0x3c0(%edx) + sub $128, %ecx + movdqa %xmm0, (%edx) + movaps %xmm0, 0x10(%edx) + movaps %xmm0, 0x20(%edx) + movaps %xmm0, 0x30(%edx) + movaps %xmm0, 0x40(%edx) + movaps %xmm0, 0x50(%edx) + movaps %xmm0, 0x60(%edx) + movaps %xmm0, 0x70(%edx) + add $128, %edx + cmp $128, %ecx + jae L(128bytes_L2_normal) + +L(128bytesless_L2_normal): + add %ecx, %edx + shr $1, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + RESTORE_EBX_STATE +L(128bytesormore_nt_start): + sub %ebx, %ecx + mov %ebx, %eax + and $0x7f, %eax + add %eax, %ecx + movd %xmm0, %eax + ALIGN (4) +L(128bytesormore_shared_cache_loop): + prefetcht0 0x3c0(%edx) + prefetcht0 0x380(%edx) + sub $0x80, %ebx + movdqa %xmm0, (%edx) + movdqa %xmm0, 0x10(%edx) + movdqa %xmm0, 0x20(%edx) + movdqa %xmm0, 0x30(%edx) + movdqa %xmm0, 0x40(%edx) + movdqa %xmm0, 0x50(%edx) + movdqa %xmm0, 0x60(%edx) + movdqa %xmm0, 0x70(%edx) + add $0x80, %edx + cmp $0x80, %ebx + jae L(128bytesormore_shared_cache_loop) + cmp $0x80, %ecx + jb L(shared_cache_loop_end) + ALIGN (4) +L(128bytesormore_nt): + sub $0x80, %ecx + movntdq %xmm0, (%edx) + movntdq %xmm0, 0x10(%edx) + movntdq %xmm0, 0x20(%edx) + movntdq %xmm0, 0x30(%edx) + movntdq %xmm0, 0x40(%edx) + movntdq %xmm0, 0x50(%edx) + movntdq %xmm0, 0x60(%edx) + movntdq %xmm0, 0x70(%edx) + add $0x80, %edx + cmp $0x80, %ecx + jae L(128bytesormore_nt) + sfence +L(shared_cache_loop_end): +#if defined DATA_CACHE_SIZE || !defined SHARED + POP (%ebx) +#endif + add %ecx, %edx + shr $1, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + + .pushsection .rodata.sse2,"a",@progbits + ALIGN (2) +L(table_16_128bytes): + .int JMPTBL (L(aligned_16_0bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_2bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_4bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_6bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_8bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_10bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_12bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_14bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_16bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_18bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_20bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_22bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_24bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_26bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_28bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_30bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_32bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_34bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_36bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_38bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_40bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_42bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_44bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_46bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_48bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_50bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_52bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_54bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_56bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_58bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_60bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_62bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_64bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_66bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_68bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_70bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_72bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_74bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_76bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_78bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_80bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_82bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_84bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_86bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_88bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_90bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_92bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_94bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_96bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_98bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_100bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_102bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_104bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_106bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_108bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_110bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_112bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_114bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_116bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_118bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_120bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_122bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_124bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_126bytes), L(table_16_128bytes)) + .popsection + + + ALIGN (4) +L(aligned_16_112bytes): + movdqa %xmm0, -112(%edx) +L(aligned_16_96bytes): + movdqa %xmm0, -96(%edx) +L(aligned_16_80bytes): + movdqa %xmm0, -80(%edx) +L(aligned_16_64bytes): + movdqa %xmm0, -64(%edx) +L(aligned_16_48bytes): + movdqa %xmm0, -48(%edx) +L(aligned_16_32bytes): + movdqa %xmm0, -32(%edx) +L(aligned_16_16bytes): + movdqa %xmm0, -16(%edx) +L(aligned_16_0bytes): + SETRTNVAL + RETURN + + + ALIGN (4) +L(aligned_16_114bytes): + movdqa %xmm0, -114(%edx) +L(aligned_16_98bytes): + movdqa %xmm0, -98(%edx) +L(aligned_16_82bytes): + movdqa %xmm0, -82(%edx) +L(aligned_16_66bytes): + movdqa %xmm0, -66(%edx) +L(aligned_16_50bytes): + movdqa %xmm0, -50(%edx) +L(aligned_16_34bytes): + movdqa %xmm0, -34(%edx) +L(aligned_16_18bytes): + movdqa %xmm0, -18(%edx) +L(aligned_16_2bytes): + movw %ax, -2(%edx) + SETRTNVAL + RETURN + + ALIGN (4) +L(aligned_16_116bytes): + movdqa %xmm0, -116(%edx) +L(aligned_16_100bytes): + movdqa %xmm0, -100(%edx) +L(aligned_16_84bytes): + movdqa %xmm0, -84(%edx) +L(aligned_16_68bytes): + movdqa %xmm0, -68(%edx) +L(aligned_16_52bytes): + movdqa %xmm0, -52(%edx) +L(aligned_16_36bytes): + movdqa %xmm0, -36(%edx) +L(aligned_16_20bytes): + movdqa %xmm0, -20(%edx) +L(aligned_16_4bytes): + movl %eax, -4(%edx) + SETRTNVAL + RETURN + + + ALIGN (4) +L(aligned_16_118bytes): + movdqa %xmm0, -118(%edx) +L(aligned_16_102bytes): + movdqa %xmm0, -102(%edx) +L(aligned_16_86bytes): + movdqa %xmm0, -86(%edx) +L(aligned_16_70bytes): + movdqa %xmm0, -70(%edx) +L(aligned_16_54bytes): + movdqa %xmm0, -54(%edx) +L(aligned_16_38bytes): + movdqa %xmm0, -38(%edx) +L(aligned_16_22bytes): + movdqa %xmm0, -22(%edx) +L(aligned_16_6bytes): + movl %eax, -6(%edx) + movw %ax, -2(%edx) + SETRTNVAL + RETURN + + + ALIGN (4) +L(aligned_16_120bytes): + movdqa %xmm0, -120(%edx) +L(aligned_16_104bytes): + movdqa %xmm0, -104(%edx) +L(aligned_16_88bytes): + movdqa %xmm0, -88(%edx) +L(aligned_16_72bytes): + movdqa %xmm0, -72(%edx) +L(aligned_16_56bytes): + movdqa %xmm0, -56(%edx) +L(aligned_16_40bytes): + movdqa %xmm0, -40(%edx) +L(aligned_16_24bytes): + movdqa %xmm0, -24(%edx) +L(aligned_16_8bytes): + movq %xmm0, -8(%edx) + SETRTNVAL + RETURN + + + ALIGN (4) +L(aligned_16_122bytes): + movdqa %xmm0, -122(%edx) +L(aligned_16_106bytes): + movdqa %xmm0, -106(%edx) +L(aligned_16_90bytes): + movdqa %xmm0, -90(%edx) +L(aligned_16_74bytes): + movdqa %xmm0, -74(%edx) +L(aligned_16_58bytes): + movdqa %xmm0, -58(%edx) +L(aligned_16_42bytes): + movdqa %xmm0, -42(%edx) +L(aligned_16_26bytes): + movdqa %xmm0, -26(%edx) +L(aligned_16_10bytes): + movq %xmm0, -10(%edx) + movw %ax, -2(%edx) + SETRTNVAL + RETURN + + + ALIGN (4) +L(aligned_16_124bytes): + movdqa %xmm0, -124(%edx) +L(aligned_16_108bytes): + movdqa %xmm0, -108(%edx) +L(aligned_16_92bytes): + movdqa %xmm0, -92(%edx) +L(aligned_16_76bytes): + movdqa %xmm0, -76(%edx) +L(aligned_16_60bytes): + movdqa %xmm0, -60(%edx) +L(aligned_16_44bytes): + movdqa %xmm0, -44(%edx) +L(aligned_16_28bytes): + movdqa %xmm0, -28(%edx) +L(aligned_16_12bytes): + movq %xmm0, -12(%edx) + movl %eax, -4(%edx) + SETRTNVAL + RETURN + + + ALIGN (4) +L(aligned_16_126bytes): + movdqa %xmm0, -126(%edx) +L(aligned_16_110bytes): + movdqa %xmm0, -110(%edx) +L(aligned_16_94bytes): + movdqa %xmm0, -94(%edx) +L(aligned_16_78bytes): + movdqa %xmm0, -78(%edx) +L(aligned_16_62bytes): + movdqa %xmm0, -62(%edx) +L(aligned_16_46bytes): + movdqa %xmm0, -46(%edx) +L(aligned_16_30bytes): + movdqa %xmm0, -30(%edx) +L(aligned_16_14bytes): + movq %xmm0, -14(%edx) + movl %eax, -6(%edx) + movw %ax, -2(%edx) + SETRTNVAL + RETURN + +END (sse2_memset16_atom) diff --git a/libcutils/arch-x86/sse2-memset32-atom.S b/libcutils/arch-x86/sse2-memset32-atom.S new file mode 100644 index 0000000..4a52484 --- /dev/null +++ b/libcutils/arch-x86/sse2-memset32-atom.S @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Contributed by: Intel Corporation + */ + +#ifndef L +# define L(label) .L##label +#endif + +#ifndef ALIGN +# define ALIGN(n) .p2align n +#endif + +#ifndef cfi_startproc +# define cfi_startproc .cfi_startproc +#endif + +#ifndef cfi_endproc +# define cfi_endproc .cfi_endproc +#endif + +#ifndef cfi_rel_offset +# define cfi_rel_offset(reg, off) .cfi_rel_offset reg, off +#endif + +#ifndef cfi_restore +# define cfi_restore(reg) .cfi_restore reg +#endif + +#ifndef cfi_adjust_cfa_offset +# define cfi_adjust_cfa_offset(off) .cfi_adjust_cfa_offset off +#endif + +#ifndef ENTRY +# define ENTRY(name) \ + .type name, @function; \ + .globl name; \ + .p2align 4; \ +name: \ + cfi_startproc +#endif + +#ifndef END +# define END(name) \ + cfi_endproc; \ + .size name, .-name +#endif + +#define CFI_PUSH(REG) \ + cfi_adjust_cfa_offset (4); \ + cfi_rel_offset (REG, 0) + +#define CFI_POP(REG) \ + cfi_adjust_cfa_offset (-4); \ + cfi_restore (REG) + +#define PUSH(REG) pushl REG; CFI_PUSH (REG) +#define POP(REG) popl REG; CFI_POP (REG) + +#ifdef USE_AS_BZERO32 +# define DEST PARMS +# define LEN DEST+4 +#else +# define DEST PARMS +# define DWDS DEST+4 +# define LEN DWDS+4 +#endif + +#ifdef USE_AS_WMEMSET32 +# define SETRTNVAL movl DEST(%esp), %eax +#else +# define SETRTNVAL +#endif + +#ifdef SHARED +# define ENTRANCE PUSH (%ebx); +# define RETURN_END POP (%ebx); ret +# define RETURN RETURN_END; CFI_PUSH (%ebx) +# define PARMS 8 /* Preserve EBX. */ +# define JMPTBL(I, B) I - B + +/* Load an entry in a jump table into EBX and branch to it. TABLE is a + jump table with relative offsets. */ +# define BRANCH_TO_JMPTBL_ENTRY(TABLE) \ + /* We first load PC into EBX. */ \ + call __i686.get_pc_thunk.bx; \ + /* Get the address of the jump table. */ \ + add $(TABLE - .), %ebx; \ + /* Get the entry and convert the relative offset to the \ + absolute address. */ \ + add (%ebx,%ecx,4), %ebx; \ + /* We loaded the jump table and adjuested EDX. Go. */ \ + jmp *%ebx + + .section .gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits + .globl __i686.get_pc_thunk.bx + .hidden __i686.get_pc_thunk.bx + ALIGN (4) + .type __i686.get_pc_thunk.bx,@function +__i686.get_pc_thunk.bx: + movl (%esp), %ebx + ret +#else +# define ENTRANCE +# define RETURN_END ret +# define RETURN RETURN_END +# define PARMS 4 +# define JMPTBL(I, B) I + +/* Branch to an entry in a jump table. TABLE is a jump table with + absolute offsets. */ +# define BRANCH_TO_JMPTBL_ENTRY(TABLE) \ + jmp *TABLE(,%ecx,4) +#endif + + .section .text.sse2,"ax",@progbits + ALIGN (4) +ENTRY (sse2_memset32_atom) + ENTRANCE + + movl LEN(%esp), %ecx +#ifdef USE_AS_ANDROID + shr $2, %ecx +#endif +#ifdef USE_AS_BZERO32 + xor %eax, %eax +#else + mov DWDS(%esp), %eax + mov %eax, %edx +#endif + movl DEST(%esp), %edx + cmp $16, %ecx + jae L(16dbwordsormore) + +L(write_less16dbwords): + lea (%edx, %ecx, 4), %edx + BRANCH_TO_JMPTBL_ENTRY (L(table_less16dbwords)) + + .pushsection .rodata.sse2,"a",@progbits + ALIGN (2) +L(table_less16dbwords): + .int JMPTBL (L(write_0dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_1dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_2dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_3dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_4dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_5dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_6dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_7dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_8dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_9dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_10dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_11dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_12dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_13dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_14dbwords), L(table_less16dbwords)) + .int JMPTBL (L(write_15dbwords), L(table_less16dbwords)) + .popsection + + ALIGN (4) +L(write_15dbwords): + movl %eax, -60(%edx) +L(write_14dbwords): + movl %eax, -56(%edx) +L(write_13dbwords): + movl %eax, -52(%edx) +L(write_12dbwords): + movl %eax, -48(%edx) +L(write_11dbwords): + movl %eax, -44(%edx) +L(write_10dbwords): + movl %eax, -40(%edx) +L(write_9dbwords): + movl %eax, -36(%edx) +L(write_8dbwords): + movl %eax, -32(%edx) +L(write_7dbwords): + movl %eax, -28(%edx) +L(write_6dbwords): + movl %eax, -24(%edx) +L(write_5dbwords): + movl %eax, -20(%edx) +L(write_4dbwords): + movl %eax, -16(%edx) +L(write_3dbwords): + movl %eax, -12(%edx) +L(write_2dbwords): + movl %eax, -8(%edx) +L(write_1dbwords): + movl %eax, -4(%edx) +L(write_0dbwords): + SETRTNVAL + RETURN + + ALIGN (4) +L(16dbwordsormore): + test $3, %edx + jz L(aligned4bytes) + mov %eax, (%edx) + mov %eax, -4(%edx, %ecx, 4) + sub $1, %ecx + rol $24, %eax + add $1, %edx + test $3, %edx + jz L(aligned4bytes) + ror $8, %eax + add $1, %edx + test $3, %edx + jz L(aligned4bytes) + ror $8, %eax + add $1, %edx +L(aligned4bytes): + shl $2, %ecx + +#ifdef USE_AS_BZERO32 + pxor %xmm0, %xmm0 +#else + movd %eax, %xmm0 + pshufd $0, %xmm0, %xmm0 +#endif + testl $0xf, %edx + jz L(aligned_16) +/* ECX > 32 and EDX is not 16 byte aligned. */ +L(not_aligned_16): + movdqu %xmm0, (%edx) + movl %edx, %eax + and $-16, %edx + add $16, %edx + sub %edx, %eax + add %eax, %ecx + movd %xmm0, %eax + ALIGN (4) +L(aligned_16): + cmp $128, %ecx + jae L(128bytesormore) + +L(aligned_16_less128bytes): + add %ecx, %edx + shr $2, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + ALIGN (4) +L(128bytesormore): +#ifdef SHARED_CACHE_SIZE + PUSH (%ebx) + mov $SHARED_CACHE_SIZE, %ebx +#else +# ifdef SHARED + call __i686.get_pc_thunk.bx + add $_GLOBAL_OFFSET_TABLE_, %ebx + mov __x86_shared_cache_size@GOTOFF(%ebx), %ebx +# else + PUSH (%ebx) + mov __x86_shared_cache_size, %ebx +# endif +#endif + cmp %ebx, %ecx + jae L(128bytesormore_nt_start) + +#ifdef DATA_CACHE_SIZE + POP (%ebx) +# define RESTORE_EBX_STATE CFI_PUSH (%ebx) + cmp $DATA_CACHE_SIZE, %ecx +#else +# ifdef SHARED +# define RESTORE_EBX_STATE + call __i686.get_pc_thunk.bx + add $_GLOBAL_OFFSET_TABLE_, %ebx + cmp __x86_data_cache_size@GOTOFF(%ebx), %ecx +# else + POP (%ebx) +# define RESTORE_EBX_STATE CFI_PUSH (%ebx) + cmp __x86_data_cache_size, %ecx +# endif +#endif + + jae L(128bytes_L2_normal) + subl $128, %ecx +L(128bytesormore_normal): + sub $128, %ecx + movdqa %xmm0, (%edx) + movdqa %xmm0, 0x10(%edx) + movdqa %xmm0, 0x20(%edx) + movdqa %xmm0, 0x30(%edx) + movdqa %xmm0, 0x40(%edx) + movdqa %xmm0, 0x50(%edx) + movdqa %xmm0, 0x60(%edx) + movdqa %xmm0, 0x70(%edx) + lea 128(%edx), %edx + jb L(128bytesless_normal) + + + sub $128, %ecx + movdqa %xmm0, (%edx) + movdqa %xmm0, 0x10(%edx) + movdqa %xmm0, 0x20(%edx) + movdqa %xmm0, 0x30(%edx) + movdqa %xmm0, 0x40(%edx) + movdqa %xmm0, 0x50(%edx) + movdqa %xmm0, 0x60(%edx) + movdqa %xmm0, 0x70(%edx) + lea 128(%edx), %edx + jae L(128bytesormore_normal) + +L(128bytesless_normal): + lea 128(%ecx), %ecx + add %ecx, %edx + shr $2, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + ALIGN (4) +L(128bytes_L2_normal): + prefetcht0 0x380(%edx) + prefetcht0 0x3c0(%edx) + sub $128, %ecx + movdqa %xmm0, (%edx) + movaps %xmm0, 0x10(%edx) + movaps %xmm0, 0x20(%edx) + movaps %xmm0, 0x30(%edx) + movaps %xmm0, 0x40(%edx) + movaps %xmm0, 0x50(%edx) + movaps %xmm0, 0x60(%edx) + movaps %xmm0, 0x70(%edx) + add $128, %edx + cmp $128, %ecx + jae L(128bytes_L2_normal) + +L(128bytesless_L2_normal): + add %ecx, %edx + shr $2, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + RESTORE_EBX_STATE +L(128bytesormore_nt_start): + sub %ebx, %ecx + mov %ebx, %eax + and $0x7f, %eax + add %eax, %ecx + movd %xmm0, %eax + ALIGN (4) +L(128bytesormore_shared_cache_loop): + prefetcht0 0x3c0(%edx) + prefetcht0 0x380(%edx) + sub $0x80, %ebx + movdqa %xmm0, (%edx) + movdqa %xmm0, 0x10(%edx) + movdqa %xmm0, 0x20(%edx) + movdqa %xmm0, 0x30(%edx) + movdqa %xmm0, 0x40(%edx) + movdqa %xmm0, 0x50(%edx) + movdqa %xmm0, 0x60(%edx) + movdqa %xmm0, 0x70(%edx) + add $0x80, %edx + cmp $0x80, %ebx + jae L(128bytesormore_shared_cache_loop) + cmp $0x80, %ecx + jb L(shared_cache_loop_end) + + ALIGN (4) +L(128bytesormore_nt): + sub $0x80, %ecx + movntdq %xmm0, (%edx) + movntdq %xmm0, 0x10(%edx) + movntdq %xmm0, 0x20(%edx) + movntdq %xmm0, 0x30(%edx) + movntdq %xmm0, 0x40(%edx) + movntdq %xmm0, 0x50(%edx) + movntdq %xmm0, 0x60(%edx) + movntdq %xmm0, 0x70(%edx) + add $0x80, %edx + cmp $0x80, %ecx + jae L(128bytesormore_nt) + sfence +L(shared_cache_loop_end): +#if defined DATA_CACHE_SIZE || !defined SHARED + POP (%ebx) +#endif + add %ecx, %edx + shr $2, %ecx + BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes)) + + .pushsection .rodata.sse2,"a",@progbits + ALIGN (2) +L(table_16_128bytes): + .int JMPTBL (L(aligned_16_0bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_4bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_8bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_12bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_16bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_20bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_24bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_28bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_32bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_36bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_40bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_44bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_48bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_52bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_56bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_60bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_64bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_68bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_72bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_76bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_80bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_84bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_88bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_92bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_96bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_100bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_104bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_108bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_112bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_116bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_120bytes), L(table_16_128bytes)) + .int JMPTBL (L(aligned_16_124bytes), L(table_16_128bytes)) + .popsection + + ALIGN (4) +L(aligned_16_112bytes): + movdqa %xmm0, -112(%edx) +L(aligned_16_96bytes): + movdqa %xmm0, -96(%edx) +L(aligned_16_80bytes): + movdqa %xmm0, -80(%edx) +L(aligned_16_64bytes): + movdqa %xmm0, -64(%edx) +L(aligned_16_48bytes): + movdqa %xmm0, -48(%edx) +L(aligned_16_32bytes): + movdqa %xmm0, -32(%edx) +L(aligned_16_16bytes): + movdqa %xmm0, -16(%edx) +L(aligned_16_0bytes): + SETRTNVAL + RETURN + + ALIGN (4) +L(aligned_16_116bytes): + movdqa %xmm0, -116(%edx) +L(aligned_16_100bytes): + movdqa %xmm0, -100(%edx) +L(aligned_16_84bytes): + movdqa %xmm0, -84(%edx) +L(aligned_16_68bytes): + movdqa %xmm0, -68(%edx) +L(aligned_16_52bytes): + movdqa %xmm0, -52(%edx) +L(aligned_16_36bytes): + movdqa %xmm0, -36(%edx) +L(aligned_16_20bytes): + movdqa %xmm0, -20(%edx) +L(aligned_16_4bytes): + movl %eax, -4(%edx) + SETRTNVAL + RETURN + + ALIGN (4) +L(aligned_16_120bytes): + movdqa %xmm0, -120(%edx) +L(aligned_16_104bytes): + movdqa %xmm0, -104(%edx) +L(aligned_16_88bytes): + movdqa %xmm0, -88(%edx) +L(aligned_16_72bytes): + movdqa %xmm0, -72(%edx) +L(aligned_16_56bytes): + movdqa %xmm0, -56(%edx) +L(aligned_16_40bytes): + movdqa %xmm0, -40(%edx) +L(aligned_16_24bytes): + movdqa %xmm0, -24(%edx) +L(aligned_16_8bytes): + movq %xmm0, -8(%edx) + SETRTNVAL + RETURN + + ALIGN (4) +L(aligned_16_124bytes): + movdqa %xmm0, -124(%edx) +L(aligned_16_108bytes): + movdqa %xmm0, -108(%edx) +L(aligned_16_92bytes): + movdqa %xmm0, -92(%edx) +L(aligned_16_76bytes): + movdqa %xmm0, -76(%edx) +L(aligned_16_60bytes): + movdqa %xmm0, -60(%edx) +L(aligned_16_44bytes): + movdqa %xmm0, -44(%edx) +L(aligned_16_28bytes): + movdqa %xmm0, -28(%edx) +L(aligned_16_12bytes): + movq %xmm0, -12(%edx) + movl %eax, -4(%edx) + SETRTNVAL + RETURN + +END (sse2_memset32_atom) diff --git a/libcutils/array.c b/libcutils/array.c new file mode 100644 index 0000000..55ec055 --- /dev/null +++ b/libcutils/array.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/array.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#define INITIAL_CAPACITY (4) +#define MAX_CAPACITY ((int)(UINT_MAX/sizeof(void*))) + +struct Array { + void** contents; + int size; + int capacity; +}; + +Array* arrayCreate() { + return calloc(1, sizeof(struct Array)); +} + +void arrayFree(Array* array) { + assert(array != NULL); + + // Free internal array. + free(array->contents); + + // Free the Array itself. + free(array); +} + +/** Returns 0 if successful, < 0 otherwise.. */ +static int ensureCapacity(Array* array, int capacity) { + int oldCapacity = array->capacity; + if (capacity > oldCapacity) { + int newCapacity = (oldCapacity == 0) ? INITIAL_CAPACITY : oldCapacity; + + // Ensure we're not doing something nasty + if (capacity > MAX_CAPACITY) + return -1; + + // Keep doubling capacity until we surpass necessary capacity. + while (newCapacity < capacity) { + int newCap = newCapacity*2; + // Handle integer overflows + if (newCap < newCapacity || newCap > MAX_CAPACITY) { + newCap = MAX_CAPACITY; + } + newCapacity = newCap; + } + + // Should not happen, but better be safe than sorry + if (newCapacity < 0 || newCapacity > MAX_CAPACITY) + return -1; + + void** newContents; + if (array->contents == NULL) { + // Allocate new array. + newContents = malloc(newCapacity * sizeof(void*)); + if (newContents == NULL) { + return -1; + } + } else { + // Expand existing array. + newContents = realloc(array->contents, sizeof(void*) * newCapacity); + if (newContents == NULL) { + return -1; + } + } + + array->capacity = newCapacity; + array->contents = newContents; + } + + return 0; +} + +int arrayAdd(Array* array, void* pointer) { + assert(array != NULL); + int size = array->size; + int result = ensureCapacity(array, size + 1); + if (result < 0) { + return result; + } + array->contents[size] = pointer; + array->size++; + return 0; +} + +static inline void checkBounds(Array* array, int index) { + assert(array != NULL); + assert(index < array->size); + assert(index >= 0); +} + +void* arrayGet(Array* array, int index) { + checkBounds(array, index); + return array->contents[index]; +} + +void* arrayRemove(Array* array, int index) { + checkBounds(array, index); + + void* pointer = array->contents[index]; + + int newSize = array->size - 1; + + // Shift entries left. + if (index != newSize) { + memmove(array->contents + index, array->contents + index + 1, + (sizeof(void*)) * (newSize - index)); + } + + array->size = newSize; + + return pointer; +} + +void* arraySet(Array* array, int index, void* pointer) { + checkBounds(array, index); + void* old = array->contents[index]; + array->contents[index] = pointer; + return old; +} + +int arraySetSize(Array* array, int newSize) { + assert(array != NULL); + assert(newSize >= 0); + + int oldSize = array->size; + + if (newSize > oldSize) { + // Expand. + int result = ensureCapacity(array, newSize); + if (result < 0) { + return result; + } + + // Zero out new entries. + memset(array->contents + sizeof(void*) * oldSize, 0, + sizeof(void*) * (newSize - oldSize)); + } + + array->size = newSize; + + return 0; +} + +int arraySize(Array* array) { + assert(array != NULL); + return array->size; +} + +const void** arrayUnwrap(Array* array) { + return (const void**)array->contents; +} diff --git a/libcutils/ashmem-dev.c b/libcutils/ashmem-dev.c new file mode 100644 index 0000000..8b71f87 --- /dev/null +++ b/libcutils/ashmem-dev.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Implementation of the user-space ashmem API for devices, which have our + * ashmem-enabled kernel. See ashmem-sim.c for the "fake" tmp-based version, + * used by the simulator. + */ + +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> + +#include <linux/ashmem.h> +#include <cutils/ashmem.h> + +#define ASHMEM_DEVICE "/dev/ashmem" + +/* + * ashmem_create_region - creates a new ashmem region and returns the file + * descriptor, or <0 on error + * + * `name' is an optional label to give the region (visible in /proc/pid/maps) + * `size' is the size of the region, in page-aligned bytes + */ +int ashmem_create_region(const char *name, size_t size) +{ + int fd, ret; + + fd = open(ASHMEM_DEVICE, O_RDWR); + if (fd < 0) + return fd; + + if (name) { + char buf[ASHMEM_NAME_LEN]; + + strlcpy(buf, name, sizeof(buf)); + ret = ioctl(fd, ASHMEM_SET_NAME, buf); + if (ret < 0) + goto error; + } + + ret = ioctl(fd, ASHMEM_SET_SIZE, size); + if (ret < 0) + goto error; + + return fd; + +error: + close(fd); + return ret; +} + +int ashmem_set_prot_region(int fd, int prot) +{ + return ioctl(fd, ASHMEM_SET_PROT_MASK, prot); +} + +int ashmem_pin_region(int fd, size_t offset, size_t len) +{ + struct ashmem_pin pin = { offset, len }; + return ioctl(fd, ASHMEM_PIN, &pin); +} + +int ashmem_unpin_region(int fd, size_t offset, size_t len) +{ + struct ashmem_pin pin = { offset, len }; + return ioctl(fd, ASHMEM_UNPIN, &pin); +} + +int ashmem_get_size_region(int fd) +{ + return ioctl(fd, ASHMEM_GET_SIZE, NULL); +} diff --git a/libcutils/ashmem-host.c b/libcutils/ashmem-host.c new file mode 100644 index 0000000..f03e130 --- /dev/null +++ b/libcutils/ashmem-host.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Implementation of the user-space ashmem API for the simulator, which lacks + * an ashmem-enabled kernel. See ashmem-dev.c for the real ashmem-based version. + */ + +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <errno.h> +#include <time.h> +#include <limits.h> + +#include <cutils/ashmem.h> + +int ashmem_create_region(const char *ignored, size_t size) +{ + static const char txt[] = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + char name[64]; + unsigned int retries = 0; + pid_t pid = getpid(); + int fd; + + srand(time(NULL) + pid); + +retry: + /* not beautiful, its just wolf-like loop unrolling */ + snprintf(name, sizeof(name), "/tmp/android-ashmem-%d-%c%c%c%c%c%c%c%c", + pid, + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))], + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))], + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))], + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))], + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))], + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))], + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))], + txt[(int) ((sizeof(txt) - 1) * (rand() / (RAND_MAX + 1.0)))]); + + /* open O_EXCL & O_CREAT: we are either the sole owner or we fail */ + fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd == -1) { + /* unlikely, but if we failed because `name' exists, retry */ + if (errno == EEXIST && ++retries < 6) + goto retry; + return -1; + } + + /* truncate the file to `len' bytes */ + if (ftruncate(fd, size) == -1) + goto error; + + if (unlink(name) == -1) + goto error; + + return fd; +error: + close(fd); + return -1; +} + +int ashmem_set_prot_region(int fd, int prot) +{ + return 0; +} + +int ashmem_pin_region(int fd, size_t offset, size_t len) +{ + return ASHMEM_NOT_PURGED; +} + +int ashmem_unpin_region(int fd, size_t offset, size_t len) +{ + return ASHMEM_IS_UNPINNED; +} + +int ashmem_get_size_region(int fd) +{ + struct stat buf; + int result; + + result = fstat(fd, &buf); + if (result == -1) { + return -1; + } + + // Check if this is an "ashmem" region. + // TODO: This is very hacky, and can easily break. We need some reliable indicator. + if (!(buf.st_nlink == 0 && S_ISREG(buf.st_mode))) { + errno = ENOTTY; + return -1; + } + + return (int)buf.st_size; // TODO: care about overflow (> 2GB file)? +} diff --git a/libcutils/atomic-android-sh.c b/libcutils/atomic-android-sh.c new file mode 100644 index 0000000..f8f1f57 --- /dev/null +++ b/libcutils/atomic-android-sh.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/atomic.h> +#ifdef HAVE_WIN32_THREADS +#include <windows.h> +#else +#include <sched.h> +#endif + +/* + * Note : + * + * (1) SuperH does not have CMPXCHG. It has only TAS for atomic + * operations. It does not seem a good idea to implement CMPXCHG, + * with TAS. So, we choose to implemnt these operations with + * posix mutexes. Please be sure that this might cause performance + * problem for Android-SH. Using LL/SC instructions supported in SH-X3, + * best performnace would be realized. + * + * (2) Mutex initialization problem happens, which is commented for + * ARM implementation, in this file above. + * We follow the fact that the initializer for mutex is a simple zero + * value. + * + * (3) These operations are NOT safe for SMP, as there is no currently + * no definition for a memory barrier operation. + */ + +#include <pthread.h> + +#define SWAP_LOCK_COUNT 32U +static pthread_mutex_t _swap_locks[SWAP_LOCK_COUNT]; + +#define SWAP_LOCK(addr) \ + &_swap_locks[((unsigned)(void*)(addr) >> 3U) % SWAP_LOCK_COUNT] + + +int32_t android_atomic_acquire_load(volatile const int32_t* addr) +{ + return *addr; +} + +int32_t android_atomic_release_load(volatile const int32_t* addr) +{ + return *addr; +} + +void android_atomic_acquire_store(int32_t value, volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_release_cas(oldValue, value, addr)); +} + +void android_atomic_release_store(int32_t value, volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_release_cas(oldValue, value, addr)); +} + +int32_t android_atomic_inc(volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_release_cas(oldValue, oldValue+1, addr)); + return oldValue; +} + +int32_t android_atomic_dec(volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_release_cas(oldValue, oldValue-1, addr)); + return oldValue; +} + +int32_t android_atomic_add(int32_t value, volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_release_cas(oldValue, oldValue+value, addr)); + return oldValue; +} + +int32_t android_atomic_and(int32_t value, volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_release_cas(oldValue, oldValue&value, addr)); + return oldValue; +} + +int32_t android_atomic_or(int32_t value, volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_release_cas(oldValue, oldValue|value, addr)); + return oldValue; +} + +int32_t android_atomic_acquire_swap(int32_t value, volatile int32_t* addr) { + return android_atomic_release_swap(value, addr); +} + +int32_t android_atomic_release_swap(int32_t value, volatile int32_t* addr) { + int32_t oldValue; + do { + oldValue = *addr; + } while (android_atomic_cmpxchg(oldValue, value, addr)); + return oldValue; +} + +int android_atomic_acquire_cmpxchg(int32_t oldvalue, int32_t newvalue, + volatile int32_t* addr) { + return android_atomic_release_cmpxchg(oldValue, newValue, addr); +} + +int android_atomic_release_cmpxchg(int32_t oldvalue, int32_t newvalue, + volatile int32_t* addr) { + int result; + pthread_mutex_t* lock = SWAP_LOCK(addr); + + pthread_mutex_lock(lock); + + if (*addr == oldvalue) { + *addr = newvalue; + result = 0; + } else { + result = 1; + } + pthread_mutex_unlock(lock); + return result; +} + diff --git a/libcutils/atomic.c b/libcutils/atomic.c new file mode 100644 index 0000000..f6cd8b0 --- /dev/null +++ b/libcutils/atomic.c @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define inline + +#include <cutils/atomic-inline.h> diff --git a/libcutils/buffer.c b/libcutils/buffer.c new file mode 100644 index 0000000..f34b8f8 --- /dev/null +++ b/libcutils/buffer.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "buffer" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include "buffer.h" +#include "loghack.h" + +Buffer* bufferCreate(size_t capacity) { + Buffer* buffer = malloc(sizeof(Buffer)); + if (buffer == NULL) { + return NULL; + } + buffer->capacity = capacity; + buffer->expected = 0; + buffer->data = malloc(capacity); + if (buffer->data == NULL) { + free(buffer); + return NULL; + } + return buffer; +} + +void bufferFree(Buffer* buffer) { + free(buffer->data); + free(buffer); +} + +Buffer* bufferWrap(char* data, size_t capacity, size_t size) { + Buffer* buffer = malloc(sizeof(Buffer)); + if (buffer == NULL) { + return NULL; + } + + buffer->data = data; + buffer->capacity = capacity; + buffer->size = size; + buffer->expected = 0; + return buffer; +} + +int bufferPrepareForRead(Buffer* buffer, size_t expected) { + if (expected > buffer->capacity) { + // Expand buffer. + char* expanded = realloc(buffer->data, expected); + if (expanded == NULL) { + errno = ENOMEM; + return -1; + } + buffer->capacity = expected; + buffer->data = expanded; + } + + buffer->size = 0; + buffer->expected = expected; + return 0; +} + +ssize_t bufferRead(Buffer* buffer, int fd) { + assert(buffer->size < buffer->expected); + + ssize_t bytesRead = read(fd, + buffer->data + buffer->size, + buffer->expected - buffer->size); + + if (bytesRead > 0) { + buffer->size += bytesRead; + return buffer->size; + } + + return bytesRead; +} + +void bufferPrepareForWrite(Buffer* buffer) { + buffer->remaining = buffer->size; +} + +ssize_t bufferWrite(Buffer* buffer, int fd) { + assert(buffer->remaining > 0); + assert(buffer->remaining <= buffer->size); + + ssize_t bytesWritten = write(fd, + buffer->data + buffer->size - buffer->remaining, + buffer->remaining); + + if (bytesWritten >= 0) { + buffer->remaining -= bytesWritten; + + LOGD("Buffer bytes written: %d", (int) bytesWritten); + LOGD("Buffer size: %d", (int) buffer->size); + LOGD("Buffer remaining: %d", (int) buffer->remaining); + + return buffer->remaining; + } + + return bytesWritten; +} + diff --git a/libcutils/buffer.h b/libcutils/buffer.h new file mode 100644 index 0000000..d8bc108 --- /dev/null +++ b/libcutils/buffer.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Byte buffer utilities. + */ + +#ifndef __BUFFER_H +#define __BUFFER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> + +/** + * Byte buffer of known size. Keeps track of how much data has been read + * into or written out of the buffer. + */ +typedef struct { + /** Buffered data. */ + char* data; + + union { + /** For reading. # of bytes we expect. */ + size_t expected; + + /** For writing. # of bytes to write. */ + size_t remaining; + }; + + /** Actual # of bytes in the buffer. */ + size_t size; + + /** Amount of memory allocated for this buffer. */ + size_t capacity; +} Buffer; + +/** + * Returns true if all data has been read into the buffer. + */ +#define bufferReadComplete(buffer) (buffer->expected == buffer->size) + +/** + * Returns true if the buffer has been completely written. + */ +#define bufferWriteComplete(buffer) (buffer->remaining == 0) + +/** + * Creates a new buffer with the given initial capacity. + */ +Buffer* bufferCreate(size_t initialCapacity); + +/** + * Wraps an existing byte array. + */ +Buffer* bufferWrap(char* data, size_t capacity, size_t size); + +/** + * Frees and its data. + */ +void bufferFree(Buffer* buffer); + +/** + * Prepares buffer to read 'expected' number of bytes. Expands capacity if + * necessary. Returns 0 if successful or -1 if an error occurs allocating + * memory. + */ +int bufferPrepareForRead(Buffer* buffer, size_t expected); + +/** + * Reads some data into a buffer. Returns -1 in case of an error and sets + * errno (see read()). Returns 0 for EOF. Updates buffer->size and returns + * the new size after a succesful read. + * + * Precondition: buffer->size < buffer->expected + */ +ssize_t bufferRead(Buffer* buffer, int fd); + +/** + * Prepares a buffer to be written out. + */ +void bufferPrepareForWrite(Buffer* buffer); + +/** + * Writes data from buffer to the given fd. Returns -1 and sets errno in case + * of an error. Updates buffer->remaining and returns the number of remaining + * bytes to be written after a successful write. + * + * Precondition: buffer->remaining > 0 + */ +ssize_t bufferWrite(Buffer* buffer, int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* __BUFFER_H */ diff --git a/libcutils/config_utils.c b/libcutils/config_utils.c new file mode 100644 index 0000000..75fa6c6 --- /dev/null +++ b/libcutils/config_utils.c @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> + +#include <cutils/config_utils.h> +#include <cutils/misc.h> + +cnode* config_node(const char *name, const char *value) +{ + cnode *node; + + node = calloc(sizeof(cnode), 1); + if(node) { + node->name = name ? name : ""; + node->value = value ? value : ""; + } + + return node; +} + +cnode* config_find(cnode *root, const char *name) +{ + cnode *node, *match = NULL; + + /* we walk the whole list, as we need to return the last (newest) entry */ + for(node = root->first_child; node; node = node->next) + if(!strcmp(node->name, name)) + match = node; + + return match; +} + +static cnode* _config_create(cnode *root, const char *name) +{ + cnode *node; + + node = config_node(name, NULL); + + if(root->last_child) + root->last_child->next = node; + else + root->first_child = node; + + root->last_child = node; + + return node; +} + +int config_bool(cnode *root, const char *name, int _default) +{ + cnode *node; + + node = config_find(root, name); + if(!node) + return _default; + + switch(node->value[0]) { + case 'y': + case 'Y': + case '1': + return 1; + default: + return 0; + } +} + +const char* config_str(cnode *root, const char *name, const char *_default) +{ + cnode *node; + + node = config_find(root, name); + if(!node) + return _default; + return node->value; +} + +void config_set(cnode *root, const char *name, const char *value) +{ + cnode *node; + + node = config_find(root, name); + if(node) + node->value = value; + else { + node = _config_create(root, name); + node->value = value; + } +} + +#define T_EOF 0 +#define T_TEXT 1 +#define T_DOT 2 +#define T_OBRACE 3 +#define T_CBRACE 4 + +typedef struct +{ + char *data; + char *text; + int len; + char next; +} cstate; + +static int _lex(cstate *cs, int value) +{ + char c; + char *s; + char *data; + + data = cs->data; + + if(cs->next != 0) { + c = cs->next; + cs->next = 0; + goto got_c; + } + +restart: + for(;;) { + c = *data++; + got_c: + if(isspace(c)) + continue; + + switch(c) { + case 0: + return T_EOF; + + case '#': + for(;;) { + switch(*data) { + case 0: + cs->data = data; + return T_EOF; + case '\n': + cs->data = data + 1; + goto restart; + default: + data++; + } + } + break; + + case '.': + cs->data = data; + return T_DOT; + + case '{': + cs->data = data; + return T_OBRACE; + + case '}': + cs->data = data; + return T_CBRACE; + + default: + s = data - 1; + + if(value) { + for(;;) { + if(*data == 0) { + cs->data = data; + break; + } + if(*data == '\n') { + cs->data = data + 1; + *data-- = 0; + break; + } + data++; + } + + /* strip trailing whitespace */ + while(data > s){ + if(!isspace(*data)) break; + *data-- = 0; + } + + goto got_text; + } else { + for(;;) { + if(isspace(*data)) { + *data = 0; + cs->data = data + 1; + goto got_text; + } + switch(*data) { + case 0: + cs->data = data; + goto got_text; + case '.': + case '{': + case '}': + cs->next = *data; + *data = 0; + cs->data = data + 1; + goto got_text; + default: + data++; + } + } + } + } + } + +got_text: + cs->text = s; + return T_TEXT; +} + +#if 0 +char *TOKENNAMES[] = { "EOF", "TEXT", "DOT", "OBRACE", "CBRACE" }; + +static int lex(cstate *cs, int value) +{ + int tok = _lex(cs, value); + printf("TOKEN(%d) %s %s\n", value, TOKENNAMES[tok], + tok == T_TEXT ? cs->text : ""); + return tok; +} +#else +#define lex(cs,v) _lex(cs,v) +#endif + +static int parse_expr(cstate *cs, cnode *node); + +static int parse_block(cstate *cs, cnode *node) +{ + for(;;){ + switch(lex(cs, 0)){ + case T_TEXT: + if(parse_expr(cs, node)) return -1; + continue; + + case T_CBRACE: + return 0; + + default: + return -1; + } + } +} + +static int parse_expr(cstate *cs, cnode *root) +{ + cnode *node; + + /* last token was T_TEXT */ + node = config_find(root, cs->text); + if(!node || *node->value) + node = _config_create(root, cs->text); + + for(;;) { + switch(lex(cs, 1)) { + case T_DOT: + if(lex(cs, 0) != T_TEXT) + return -1; + node = _config_create(node, cs->text); + continue; + + case T_TEXT: + node->value = cs->text; + return 0; + + case T_OBRACE: + return parse_block(cs, node); + + default: + return -1; + } + } +} + +void config_load(cnode *root, char *data) +{ + if(data != 0) { + cstate cs; + cs.data = data; + cs.next = 0; + + for(;;) { + switch(lex(&cs, 0)) { + case T_TEXT: + if(parse_expr(&cs, root)) + return; + break; + default: + return; + } + } + } +} + +void config_load_file(cnode *root, const char *fn) +{ + char *data; + data = load_file(fn, 0); + config_load(root, data); +} diff --git a/libcutils/cpu_info.c b/libcutils/cpu_info.c new file mode 100644 index 0000000..23dda8a --- /dev/null +++ b/libcutils/cpu_info.c @@ -0,0 +1,83 @@ +/* libs/cutils/cpu_info.c +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <cutils/cpu_info.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +// we cache the serial number here. +// this is also used as a fgets() line buffer when we are reading /proc/cpuinfo +static char serial_number[100] = { 0 }; + +extern const char* get_cpu_serial_number(void) +{ + if (serial_number[0] == 0) + { + FILE* file; + char* chp, *end; + char* whitespace; + int length; + + // read serial number from /proc/cpuinfo + file = fopen("proc/cpuinfo", "r"); + if (! file) + return NULL; + + while ((chp = fgets(serial_number, sizeof(serial_number), file)) != NULL) + { + // look for something like "Serial : 999206122a03591c" + + if (strncmp(chp, "Serial", 6) != 0) + continue; + + chp = strchr(chp, ':'); + if (!chp) + continue; + + // skip colon and whitespace + while ( *(++chp) == ' ') {} + + // truncate trailing whitespace + end = chp; + while (*end && *end != ' ' && *end != '\t' && *end != '\n' && *end != '\r') + ++end; + *end = 0; + + whitespace = strchr(chp, ' '); + if (whitespace) + *whitespace = 0; + whitespace = strchr(chp, '\t'); + if (whitespace) + *whitespace = 0; + whitespace = strchr(chp, '\r'); + if (whitespace) + *whitespace = 0; + whitespace = strchr(chp, '\n'); + if (whitespace) + *whitespace = 0; + + // shift serial number to beginning of the buffer + memmove(serial_number, chp, strlen(chp) + 1); + break; + } + + fclose(file); + } + + return (serial_number[0] ? serial_number : NULL); +} diff --git a/libcutils/dir_hash.c b/libcutils/dir_hash.c new file mode 100644 index 0000000..be14af6 --- /dev/null +++ b/libcutils/dir_hash.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <dirent.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sha1.h> +#include <unistd.h> +#include <limits.h> + +#include <sys/stat.h> + +#include <netinet/in.h> +#include <resolv.h> + +#include <cutils/dir_hash.h> + +/** + * Copies, if it fits within max_output_string bytes, into output_string + * a hash of the contents, size, permissions, uid, and gid of the file + * specified by path, using the specified algorithm. Returns the length + * of the output string, or a negative number if the buffer is too short. + */ +int get_file_hash(HashAlgorithm algorithm, const char *path, + char *output_string, size_t max_output_string) { + SHA1_CTX context; + struct stat sb; + unsigned char md[SHA1_DIGEST_LENGTH]; + int used; + size_t n; + + if (algorithm != SHA_1) { + errno = EINVAL; + return -1; + } + + if (stat(path, &sb) != 0) { + return -1; + } + + if (S_ISLNK(sb.st_mode)) { + char buf[PATH_MAX]; + int len; + + len = readlink(path, buf, sizeof(buf)); + if (len < 0) { + return -1; + } + + SHA1Init(&context); + SHA1Update(&context, (unsigned char *) buf, len); + SHA1Final(md, &context); + } else if (S_ISREG(sb.st_mode)) { + char buf[10000]; + FILE *f = fopen(path, "rb"); + int len; + + if (f == NULL) { + return -1; + } + + SHA1Init(&context); + + while ((len = fread(buf, 1, sizeof(buf), f)) > 0) { + SHA1Update(&context, (unsigned char *) buf, len); + } + + if (ferror(f)) { + fclose(f); + return -1; + } + + fclose(f); + SHA1Final(md, &context); + } + + if (S_ISLNK(sb.st_mode) || S_ISREG(sb.st_mode)) { + used = b64_ntop(md, SHA1_DIGEST_LENGTH, + output_string, max_output_string); + if (used < 0) { + errno = ENOSPC; + return -1; + } + + n = snprintf(output_string + used, max_output_string - used, + " %d 0%o %d %d", (int) sb.st_size, sb.st_mode, + (int) sb.st_uid, (int) sb.st_gid); + } else { + n = snprintf(output_string, max_output_string, + "- - 0%o %d %d", sb.st_mode, + (int) sb.st_uid, (int) sb.st_gid); + } + + if (n >= max_output_string - used) { + errno = ENOSPC; + return -(used + n); + } + + return used + n; +} + +struct list { + char *name; + struct list *next; +}; + +static int cmp(const void *a, const void *b) { + struct list *const *ra = a; + struct list *const *rb = b; + + return strcmp((*ra)->name, (*rb)->name); +} + +static int recurse(HashAlgorithm algorithm, const char *directory_path, + struct list **out) { + struct list *list = NULL; + struct list *f; + + struct dirent *de; + DIR *d = opendir(directory_path); + + if (d == NULL) { + return -1; + } + + while ((de = readdir(d)) != NULL) { + if (strcmp(de->d_name, ".") == 0) { + continue; + } + if (strcmp(de->d_name, "..") == 0) { + continue; + } + + char *name = malloc(strlen(de->d_name) + 1); + struct list *node = malloc(sizeof(struct list)); + + if (name == NULL || node == NULL) { + struct list *next; + for (f = list; f != NULL; f = next) { + next = f->next; + free(f->name); + free(f); + } + + free(name); + free(node); + return -1; + } + + strcpy(name, de->d_name); + + node->name = name; + node->next = list; + list = node; + } + + closedir(d); + + for (f = list; f != NULL; f = f->next) { + struct stat sb; + char *name; + char outstr[NAME_MAX + 100]; + char *keep; + struct list *res; + + name = malloc(strlen(f->name) + strlen(directory_path) + 2); + if (name == NULL) { + struct list *next; + for (f = list; f != NULL; f = f->next) { + next = f->next; + free(f->name); + free(f); + } + for (f = *out; f != NULL; f = f->next) { + next = f->next; + free(f->name); + free(f); + } + *out = NULL; + return -1; + } + + sprintf(name, "%s/%s", directory_path, f->name); + + int len = get_file_hash(algorithm, name, + outstr, sizeof(outstr)); + if (len < 0) { + // should not happen + return -1; + } + + keep = malloc(len + strlen(name) + 3); + res = malloc(sizeof(struct list)); + + if (keep == NULL || res == NULL) { + struct list *next; + for (f = list; f != NULL; f = f->next) { + next = f->next; + free(f->name); + free(f); + } + for (f = *out; f != NULL; f = f->next) { + next = f->next; + free(f->name); + free(f); + } + *out = NULL; + + free(keep); + free(res); + return -1; + } + + sprintf(keep, "%s %s\n", name, outstr); + + res->name = keep; + res->next = *out; + *out = res; + + if ((stat(name, &sb) == 0) && S_ISDIR(sb.st_mode)) { + if (recurse(algorithm, name, out) < 0) { + struct list *next; + for (f = list; f != NULL; f = next) { + next = f->next; + free(f->name); + free(f); + } + + return -1; + } + } + } + + struct list *next; + for (f = list; f != NULL; f = next) { + next = f->next; + + free(f->name); + free(f); + } +} + +/** + * Allocates a string containing the names and hashes of all files recursively + * reached under the specified directory_path, using the specified algorithm. + * The string is returned as *output_string; the return value is the length + * of the string, or a negative number if there was a failure. + */ +int get_recursive_hash_manifest(HashAlgorithm algorithm, + const char *directory_path, + char **output_string) { + struct list *out = NULL; + struct list *r; + struct list **list; + int count = 0; + int len = 0; + int retlen = 0; + int i; + char *buf; + + if (recurse(algorithm, directory_path, &out) < 0) { + return -1; + } + + for (r = out; r != NULL; r = r->next) { + count++; + len += strlen(r->name); + } + + list = malloc(count * sizeof(struct list *)); + if (list == NULL) { + struct list *next; + for (r = out; r != NULL; r = next) { + next = r->next; + free(r->name); + free(r); + } + return -1; + } + + count = 0; + for (r = out; r != NULL; r = r->next) { + list[count++] = r; + } + + qsort(list, count, sizeof(struct list *), cmp); + + buf = malloc(len + 1); + if (buf == NULL) { + struct list *next; + for (r = out; r != NULL; r = next) { + next = r->next; + free(r->name); + free(r); + } + free(list); + return -1; + } + + for (i = 0; i < count; i++) { + int n = strlen(list[i]->name); + + strcpy(buf + retlen, list[i]->name); + retlen += n; + } + + free(list); + + struct list *next; + for (r = out; r != NULL; r = next) { + next = r->next; + + free(r->name); + free(r); + } + + *output_string = buf; + return retlen; +} diff --git a/libcutils/dlmalloc_stubs.c b/libcutils/dlmalloc_stubs.c new file mode 100644 index 0000000..1ced147 --- /dev/null +++ b/libcutils/dlmalloc_stubs.c @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* No-op stubs for functions defined in system/bionic/bionic/dlmalloc.c. + */ +void dlmalloc_walk_free_pages() +{ +} + +void dlmalloc_walk_heap() +{ +} + +void dlmalloc_trim() +{ +} diff --git a/libcutils/hashmap.c b/libcutils/hashmap.c new file mode 100644 index 0000000..e29bc24 --- /dev/null +++ b/libcutils/hashmap.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/hashmap.h> +#include <assert.h> +#include <errno.h> +#include <cutils/threads.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <sys/types.h> + +typedef struct Entry Entry; +struct Entry { + void* key; + int hash; + void* value; + Entry* next; +}; + +struct Hashmap { + Entry** buckets; + size_t bucketCount; + int (*hash)(void* key); + bool (*equals)(void* keyA, void* keyB); + mutex_t lock; + size_t size; +}; + +Hashmap* hashmapCreate(size_t initialCapacity, + int (*hash)(void* key), bool (*equals)(void* keyA, void* keyB)) { + assert(hash != NULL); + assert(equals != NULL); + + Hashmap* map = malloc(sizeof(Hashmap)); + if (map == NULL) { + return NULL; + } + + // 0.75 load factor. + size_t minimumBucketCount = initialCapacity * 4 / 3; + map->bucketCount = 1; + while (map->bucketCount <= minimumBucketCount) { + // Bucket count must be power of 2. + map->bucketCount <<= 1; + } + + map->buckets = calloc(map->bucketCount, sizeof(Entry*)); + if (map->buckets == NULL) { + free(map); + return NULL; + } + + map->size = 0; + + map->hash = hash; + map->equals = equals; + + mutex_init(&map->lock); + + return map; +} + +/** + * Hashes the given key. + */ +static inline int hashKey(Hashmap* map, void* key) { + int h = map->hash(key); + + // We apply this secondary hashing discovered by Doug Lea to defend + // against bad hashes. + h += ~(h << 9); + h ^= (((unsigned int) h) >> 14); + h += (h << 4); + h ^= (((unsigned int) h) >> 10); + + return h; +} + +size_t hashmapSize(Hashmap* map) { + return map->size; +} + +static inline size_t calculateIndex(size_t bucketCount, int hash) { + return ((size_t) hash) & (bucketCount - 1); +} + +static void expandIfNecessary(Hashmap* map) { + // If the load factor exceeds 0.75... + if (map->size > (map->bucketCount * 3 / 4)) { + // Start off with a 0.33 load factor. + size_t newBucketCount = map->bucketCount << 1; + Entry** newBuckets = calloc(newBucketCount, sizeof(Entry*)); + if (newBuckets == NULL) { + // Abort expansion. + return; + } + + // Move over existing entries. + size_t i; + for (i = 0; i < map->bucketCount; i++) { + Entry* entry = map->buckets[i]; + while (entry != NULL) { + Entry* next = entry->next; + size_t index = calculateIndex(newBucketCount, entry->hash); + entry->next = newBuckets[index]; + newBuckets[index] = entry; + entry = next; + } + } + + // Copy over internals. + free(map->buckets); + map->buckets = newBuckets; + map->bucketCount = newBucketCount; + } +} + +void hashmapLock(Hashmap* map) { + mutex_lock(&map->lock); +} + +void hashmapUnlock(Hashmap* map) { + mutex_unlock(&map->lock); +} + +void hashmapFree(Hashmap* map) { + size_t i; + for (i = 0; i < map->bucketCount; i++) { + Entry* entry = map->buckets[i]; + while (entry != NULL) { + Entry* next = entry->next; + free(entry); + entry = next; + } + } + free(map->buckets); + mutex_destroy(&map->lock); + free(map); +} + +int hashmapHash(void* key, size_t keySize) { + int h = keySize; + char* data = (char*) key; + size_t i; + for (i = 0; i < keySize; i++) { + h = h * 31 + *data; + data++; + } + return h; +} + +static Entry* createEntry(void* key, int hash, void* value) { + Entry* entry = malloc(sizeof(Entry)); + if (entry == NULL) { + return NULL; + } + entry->key = key; + entry->hash = hash; + entry->value = value; + entry->next = NULL; + return entry; +} + +static inline bool equalKeys(void* keyA, int hashA, void* keyB, int hashB, + bool (*equals)(void*, void*)) { + if (keyA == keyB) { + return true; + } + if (hashA != hashB) { + return false; + } + return equals(keyA, keyB); +} + +void* hashmapPut(Hashmap* map, void* key, void* value) { + int hash = hashKey(map, key); + size_t index = calculateIndex(map->bucketCount, hash); + + Entry** p = &(map->buckets[index]); + while (true) { + Entry* current = *p; + + // Add a new entry. + if (current == NULL) { + *p = createEntry(key, hash, value); + if (*p == NULL) { + errno = ENOMEM; + return NULL; + } + map->size++; + expandIfNecessary(map); + return NULL; + } + + // Replace existing entry. + if (equalKeys(current->key, current->hash, key, hash, map->equals)) { + void* oldValue = current->value; + current->value = value; + return oldValue; + } + + // Move to next entry. + p = ¤t->next; + } +} + +void* hashmapGet(Hashmap* map, void* key) { + int hash = hashKey(map, key); + size_t index = calculateIndex(map->bucketCount, hash); + + Entry* entry = map->buckets[index]; + while (entry != NULL) { + if (equalKeys(entry->key, entry->hash, key, hash, map->equals)) { + return entry->value; + } + entry = entry->next; + } + + return NULL; +} + +bool hashmapContainsKey(Hashmap* map, void* key) { + int hash = hashKey(map, key); + size_t index = calculateIndex(map->bucketCount, hash); + + Entry* entry = map->buckets[index]; + while (entry != NULL) { + if (equalKeys(entry->key, entry->hash, key, hash, map->equals)) { + return true; + } + entry = entry->next; + } + + return false; +} + +void* hashmapMemoize(Hashmap* map, void* key, + void* (*initialValue)(void* key, void* context), void* context) { + int hash = hashKey(map, key); + size_t index = calculateIndex(map->bucketCount, hash); + + Entry** p = &(map->buckets[index]); + while (true) { + Entry* current = *p; + + // Add a new entry. + if (current == NULL) { + *p = createEntry(key, hash, NULL); + if (*p == NULL) { + errno = ENOMEM; + return NULL; + } + void* value = initialValue(key, context); + (*p)->value = value; + map->size++; + expandIfNecessary(map); + return value; + } + + // Return existing value. + if (equalKeys(current->key, current->hash, key, hash, map->equals)) { + return current->value; + } + + // Move to next entry. + p = ¤t->next; + } +} + +void* hashmapRemove(Hashmap* map, void* key) { + int hash = hashKey(map, key); + size_t index = calculateIndex(map->bucketCount, hash); + + // Pointer to the current entry. + Entry** p = &(map->buckets[index]); + Entry* current; + while ((current = *p) != NULL) { + if (equalKeys(current->key, current->hash, key, hash, map->equals)) { + void* value = current->value; + *p = current->next; + free(current); + map->size--; + return value; + } + + p = ¤t->next; + } + + return NULL; +} + +void hashmapForEach(Hashmap* map, + bool (*callback)(void* key, void* value, void* context), + void* context) { + size_t i; + for (i = 0; i < map->bucketCount; i++) { + Entry* entry = map->buckets[i]; + while (entry != NULL) { + if (!callback(entry->key, entry->value, context)) { + return; + } + entry = entry->next; + } + } +} + +size_t hashmapCurrentCapacity(Hashmap* map) { + size_t bucketCount = map->bucketCount; + return bucketCount * 3 / 4; +} + +size_t hashmapCountCollisions(Hashmap* map) { + size_t collisions = 0; + size_t i; + for (i = 0; i < map->bucketCount; i++) { + Entry* entry = map->buckets[i]; + while (entry != NULL) { + if (entry->next != NULL) { + collisions++; + } + entry = entry->next; + } + } + return collisions; +} + +int hashmapIntHash(void* key) { + // Return the key value itself. + return *((int*) key); +} + +bool hashmapIntEquals(void* keyA, void* keyB) { + int a = *((int*) keyA); + int b = *((int*) keyB); + return a == b; +} diff --git a/libcutils/iosched_policy.c b/libcutils/iosched_policy.c new file mode 100644 index 0000000..f350f58 --- /dev/null +++ b/libcutils/iosched_policy.c @@ -0,0 +1,67 @@ + +/* libs/cutils/iosched_policy.c +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#ifdef HAVE_SCHED_H + +#include <cutils/iosched_policy.h> + +extern int ioprio_set(int which, int who, int ioprio); + +enum { + WHO_PROCESS = 1, + WHO_PGRP, + WHO_USER, +}; + +#define CLASS_SHIFT 13 +#define IOPRIO_NORM 4 + +int android_set_ioprio(int pid, IoSchedClass clazz, int ioprio) { +#ifdef HAVE_ANDROID_OS + if (ioprio_set(WHO_PROCESS, pid, ioprio | (clazz << CLASS_SHIFT))) { + return -1; + } +#endif + return 0; +} + +int android_get_ioprio(int pid, IoSchedClass *clazz, int *ioprio) { +#ifdef HAVE_ANDROID_OS + int rc; + + if ((rc = ioprio_get(WHO_PROCESS, pid)) < 0) { + return -1; + } + + *clazz = (rc >> CLASS_SHIFT); + *ioprio = (rc & 0xff); +#else + *clazz = IoSchedClass_NONE; + *ioprio = 0; +#endif + return 0; +} + +#endif /* HAVE_SCHED_H */ diff --git a/libcutils/load_file.c b/libcutils/load_file.c new file mode 100644 index 0000000..99f2965 --- /dev/null +++ b/libcutils/load_file.c @@ -0,0 +1,51 @@ +/* libs/cutils/load_file.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +void *load_file(const char *fn, unsigned *_sz) +{ + char *data; + int sz; + int fd; + + data = 0; + fd = open(fn, O_RDONLY); + if(fd < 0) return 0; + + sz = lseek(fd, 0, SEEK_END); + if(sz < 0) goto oops; + + if(lseek(fd, 0, SEEK_SET) != 0) goto oops; + + data = (char*) malloc(sz + 1); + if(data == 0) goto oops; + + if(read(fd, data, sz) != sz) goto oops; + close(fd); + data[sz] = 0; + + if(_sz) *_sz = sz; + return data; + +oops: + close(fd); + if(data != 0) free(data); + return 0; +} diff --git a/libcutils/loghack.h b/libcutils/loghack.h new file mode 100644 index 0000000..2bfffe4 --- /dev/null +++ b/libcutils/loghack.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is a temporary hack to enable logging from cutils. + */ + +#ifndef _CUTILS_LOGHACK_H +#define _CUTILS_LOGHACK_H + +#ifdef HAVE_ANDROID_OS +#include <cutils/log.h> +#else +#include <stdio.h> +#define LOG(level, ...) \ + ((void)printf("cutils:" level "/" LOG_TAG ": " __VA_ARGS__)) +#define LOGV(...) LOG("V", __VA_ARGS__) +#define LOGD(...) LOG("D", __VA_ARGS__) +#define LOGI(...) LOG("I", __VA_ARGS__) +#define LOGW(...) LOG("W", __VA_ARGS__) +#define LOGE(...) LOG("E", __VA_ARGS__) +#define LOG_ALWAYS_FATAL(...) do { LOGE(__VA_ARGS__); exit(1); } while (0) +#endif + +#endif // _CUTILS_LOGHACK_H diff --git a/libcutils/memory.c b/libcutils/memory.c new file mode 100644 index 0000000..6486b45 --- /dev/null +++ b/libcutils/memory.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/memory.h> + +#if !HAVE_MEMSET16 +void android_memset16(uint16_t* dst, uint16_t value, size_t size) +{ + size >>= 1; + while (size--) { + *dst++ = value; + } +} +#endif + +#if !HAVE_MEMSET32 +void android_memset32(uint32_t* dst, uint32_t value, size_t size) +{ + size >>= 2; + while (size--) { + *dst++ = value; + } +} +#endif + +#if !HAVE_STRLCPY +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <string.h> + +/* Implementation of strlcpy() for platforms that don't already have it. */ + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} +#endif diff --git a/libcutils/mq.c b/libcutils/mq.c new file mode 100644 index 0000000..3b65f1f --- /dev/null +++ b/libcutils/mq.c @@ -0,0 +1,1357 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "mq" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/un.h> +#include <sys/uio.h> + +#include <cutils/array.h> +#include <cutils/hashmap.h> +#include <cutils/selector.h> + +#include "loghack.h" +#include "buffer.h" + +/** Number of dead peers to remember. */ +#define PEER_HISTORY (16) + +typedef struct sockaddr SocketAddress; +typedef struct sockaddr_un UnixAddress; + +/** + * Process/user/group ID. We don't use ucred directly because it's only + * available on Linux. + */ +typedef struct { + pid_t pid; + uid_t uid; + gid_t gid; +} Credentials; + +/** Listens for bytes coming from remote peers. */ +typedef void BytesListener(Credentials credentials, char* bytes, size_t size); + +/** Listens for the deaths of remote peers. */ +typedef void DeathListener(pid_t pid); + +/** Types of packets. */ +typedef enum { + /** Request for a connection to another peer. */ + CONNECTION_REQUEST, + + /** A connection to another peer. */ + CONNECTION, + + /** Reports a failed connection attempt. */ + CONNECTION_ERROR, + + /** A generic packet of bytes. */ + BYTES, +} PacketType; + +typedef enum { + /** Reading a packet header. */ + READING_HEADER, + + /** Waiting for a connection from the master. */ + ACCEPTING_CONNECTION, + + /** Reading bytes. */ + READING_BYTES, +} InputState; + +/** A packet header. */ +// TODO: Use custom headers for master->peer, peer->master, peer->peer. +typedef struct { + PacketType type; + union { + /** Packet size. Used for BYTES. */ + size_t size; + + /** Credentials. Used for CONNECTION and CONNECTION_REQUEST. */ + Credentials credentials; + }; +} Header; + +/** A packet which will be sent to a peer. */ +typedef struct OutgoingPacket OutgoingPacket; +struct OutgoingPacket { + /** Packet header. */ + Header header; + + union { + /** Connection to peer. Used with CONNECTION. */ + int socket; + + /** Buffer of bytes. Used with BYTES. */ + Buffer* bytes; + }; + + /** Frees all resources associated with this packet. */ + void (*free)(OutgoingPacket* packet); + + /** Optional context. */ + void* context; + + /** Next packet in the queue. */ + OutgoingPacket* nextPacket; +}; + +/** Represents a remote peer. */ +typedef struct PeerProxy PeerProxy; + +/** Local peer state. You typically have one peer per process. */ +typedef struct { + /** This peer's PID. */ + pid_t pid; + + /** + * Map from pid to peer proxy. The peer has a peer proxy for each remote + * peer it's connected to. + * + * Acquire mutex before use. + */ + Hashmap* peerProxies; + + /** Manages I/O. */ + Selector* selector; + + /** Used to synchronize operations with the selector thread. */ + pthread_mutex_t mutex; + + /** Is this peer the master? */ + bool master; + + /** Peer proxy for the master. */ + PeerProxy* masterProxy; + + /** Listens for packets from remote peers. */ + BytesListener* onBytes; + + /** Listens for deaths of remote peers. */ + DeathListener* onDeath; + + /** Keeps track of recently dead peers. Requires mutex. */ + pid_t deadPeers[PEER_HISTORY]; + size_t deadPeerCursor; +} Peer; + +struct PeerProxy { + /** Credentials of the remote process. */ + Credentials credentials; + + /** Keeps track of data coming in from the remote peer. */ + InputState inputState; + Buffer* inputBuffer; + PeerProxy* connecting; + + /** File descriptor for this peer. */ + SelectableFd* fd; + + /** + * Queue of packets to be written out to the remote peer. + * + * Requires mutex. + */ + // TODO: Limit queue length. + OutgoingPacket* currentPacket; + OutgoingPacket* lastPacket; + + /** Used to write outgoing header. */ + Buffer outgoingHeader; + + /** True if this is the master's proxy. */ + bool master; + + /** Reference back to the local peer. */ + Peer* peer; + + /** + * Used in master only. Maps this peer proxy to other peer proxies to + * which the peer has been connected to. Maps pid to PeerProxy. Helps + * keep track of which connections we've sent to whom. + */ + Hashmap* connections; +}; + +/** Server socket path. */ +static const char* MASTER_PATH = "/master.peer"; + +/** Credentials of the master peer. */ +static const Credentials MASTER_CREDENTIALS = {0, 0, 0}; + +/** Creates a peer proxy and adds it to the peer proxy map. */ +static PeerProxy* peerProxyCreate(Peer* peer, Credentials credentials); + +/** Sets the non-blocking flag on a descriptor. */ +static void setNonBlocking(int fd) { + int flags; + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) { + LOG_ALWAYS_FATAL("fcntl() error: %s", strerror(errno)); + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + LOG_ALWAYS_FATAL("fcntl() error: %s", strerror(errno)); + } +} + +/** Closes a fd and logs a warning if the close fails. */ +static void closeWithWarning(int fd) { + int result = close(fd); + if (result == -1) { + LOGW("close() error: %s", strerror(errno)); + } +} + +/** Hashes pid_t keys. */ +static int pidHash(void* key) { + pid_t* pid = (pid_t*) key; + return (int) (*pid); +} + +/** Compares pid_t keys. */ +static bool pidEquals(void* keyA, void* keyB) { + pid_t* a = (pid_t*) keyA; + pid_t* b = (pid_t*) keyB; + return *a == *b; +} + +/** Gets the master address. Not thread safe. */ +static UnixAddress* getMasterAddress() { + static UnixAddress masterAddress; + static bool initialized = false; + if (initialized == false) { + masterAddress.sun_family = AF_LOCAL; + strcpy(masterAddress.sun_path, MASTER_PATH); + initialized = true; + } + return &masterAddress; +} + +/** Gets exclusive access to the peer for this thread. */ +static void peerLock(Peer* peer) { + pthread_mutex_lock(&peer->mutex); +} + +/** Releases exclusive access to the peer. */ +static void peerUnlock(Peer* peer) { + pthread_mutex_unlock(&peer->mutex); +} + +/** Frees a simple, i.e. header-only, outgoing packet. */ +static void outgoingPacketFree(OutgoingPacket* packet) { + LOGD("Freeing outgoing packet."); + free(packet); +} + +/** + * Prepare to read a new packet from the peer. + */ +static void peerProxyExpectHeader(PeerProxy* peerProxy) { + peerProxy->inputState = READING_HEADER; + bufferPrepareForRead(peerProxy->inputBuffer, sizeof(Header)); +} + +/** Sets up the buffer for the outgoing header. */ +static void peerProxyPrepareOutgoingHeader(PeerProxy* peerProxy) { + peerProxy->outgoingHeader.data + = (char*) &(peerProxy->currentPacket->header); + peerProxy->outgoingHeader.size = sizeof(Header); + bufferPrepareForWrite(&peerProxy->outgoingHeader); +} + +/** Adds a packet to the end of the queue. Callers must have the mutex. */ +static void peerProxyEnqueueOutgoingPacket(PeerProxy* peerProxy, + OutgoingPacket* newPacket) { + newPacket->nextPacket = NULL; // Just in case. + if (peerProxy->currentPacket == NULL) { + // The queue is empty. + peerProxy->currentPacket = newPacket; + peerProxy->lastPacket = newPacket; + + peerProxyPrepareOutgoingHeader(peerProxy); + } else { + peerProxy->lastPacket->nextPacket = newPacket; + } +} + +/** Takes the peer lock and enqueues the given packet. */ +static void peerProxyLockAndEnqueueOutgoingPacket(PeerProxy* peerProxy, + OutgoingPacket* newPacket) { + Peer* peer = peerProxy->peer; + peerLock(peer); + peerProxyEnqueueOutgoingPacket(peerProxy, newPacket); + peerUnlock(peer); +} + +/** + * Frees current packet and moves to the next one. Returns true if there is + * a next packet or false if the queue is empty. + */ +static bool peerProxyNextPacket(PeerProxy* peerProxy) { + Peer* peer = peerProxy->peer; + peerLock(peer); + + OutgoingPacket* current = peerProxy->currentPacket; + + if (current == NULL) { + // The queue is already empty. + peerUnlock(peer); + return false; + } + + OutgoingPacket* next = current->nextPacket; + peerProxy->currentPacket = next; + current->nextPacket = NULL; + current->free(current); + if (next == NULL) { + // The queue is empty. + peerProxy->lastPacket = NULL; + peerUnlock(peer); + return false; + } else { + peerUnlock(peer); + peerProxyPrepareOutgoingHeader(peerProxy); + + // TODO: Start writing next packet? It would reduce the number of + // system calls, but we could also starve other peers. + return true; + } +} + +/** + * Checks whether a peer died recently. + */ +static bool peerIsDead(Peer* peer, pid_t pid) { + size_t i; + for (i = 0; i < PEER_HISTORY; i++) { + pid_t deadPeer = peer->deadPeers[i]; + if (deadPeer == 0) { + return false; + } + if (deadPeer == pid) { + return true; + } + } + return false; +} + +/** + * Cleans up connection information. + */ +static bool peerProxyRemoveConnection(void* key, void* value, void* context) { + PeerProxy* deadPeer = (PeerProxy*) context; + PeerProxy* otherPeer = (PeerProxy*) value; + hashmapRemove(otherPeer->connections, &(deadPeer->credentials.pid)); + return true; +} + +/** + * Called when the peer dies. + */ +static void peerProxyKill(PeerProxy* peerProxy, bool errnoIsSet) { + if (errnoIsSet) { + LOGI("Peer %d died. errno: %s", peerProxy->credentials.pid, + strerror(errno)); + } else { + LOGI("Peer %d died.", peerProxy->credentials.pid); + } + + // If we lost the master, we're up a creek. We can't let this happen. + if (peerProxy->master) { + LOG_ALWAYS_FATAL("Lost connection to master."); + } + + Peer* localPeer = peerProxy->peer; + pid_t pid = peerProxy->credentials.pid; + + peerLock(localPeer); + + // Remember for awhile that the peer died. + localPeer->deadPeers[localPeer->deadPeerCursor] + = peerProxy->credentials.pid; + localPeer->deadPeerCursor++; + if (localPeer->deadPeerCursor == PEER_HISTORY) { + localPeer->deadPeerCursor = 0; + } + + // Remove from peer map. + hashmapRemove(localPeer->peerProxies, &pid); + + // External threads can no longer get to this peer proxy, so we don't + // need the lock anymore. + peerUnlock(localPeer); + + // Remove the fd from the selector. + if (peerProxy->fd != NULL) { + peerProxy->fd->remove = true; + } + + // Clear outgoing packet queue. + while (peerProxyNextPacket(peerProxy)) {} + + bufferFree(peerProxy->inputBuffer); + + // This only applies to the master. + if (peerProxy->connections != NULL) { + // We can't leave these other maps pointing to freed memory. + hashmapForEach(peerProxy->connections, &peerProxyRemoveConnection, + peerProxy); + hashmapFree(peerProxy->connections); + } + + // Invoke death listener. + localPeer->onDeath(pid); + + // Free the peer proxy itself. + free(peerProxy); +} + +static void peerProxyHandleError(PeerProxy* peerProxy, char* functionName) { + if (errno == EINTR) { + // Log interruptions but otherwise ignore them. + LOGW("%s() interrupted.", functionName); + } else if (errno == EAGAIN) { + LOGD("EWOULDBLOCK"); + // Ignore. + } else { + LOGW("Error returned by %s().", functionName); + peerProxyKill(peerProxy, true); + } +} + +/** + * Buffers output sent to a peer. May be called multiple times until the entire + * buffer is filled. Returns true when the buffer is empty. + */ +static bool peerProxyWriteFromBuffer(PeerProxy* peerProxy, Buffer* outgoing) { + ssize_t size = bufferWrite(outgoing, peerProxy->fd->fd); + if (size < 0) { + peerProxyHandleError(peerProxy, "write"); + return false; + } else { + return bufferWriteComplete(outgoing); + } +} + +/** Writes packet bytes to peer. */ +static void peerProxyWriteBytes(PeerProxy* peerProxy) { + Buffer* buffer = peerProxy->currentPacket->bytes; + if (peerProxyWriteFromBuffer(peerProxy, buffer)) { + LOGD("Bytes written."); + peerProxyNextPacket(peerProxy); + } +} + +/** Sends a socket to the peer. */ +static void peerProxyWriteConnection(PeerProxy* peerProxy) { + int socket = peerProxy->currentPacket->socket; + + // Why does sending and receiving fds have to be such a PITA? + struct msghdr msg; + struct iovec iov[1]; + + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(int))]; + } control_un; + + struct cmsghdr *cmptr; + + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); + cmptr = CMSG_FIRSTHDR(&msg); + cmptr->cmsg_len = CMSG_LEN(sizeof(int)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + + // Store the socket in the message. + *((int *) CMSG_DATA(cmptr)) = peerProxy->currentPacket->socket; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + iov[0].iov_base = ""; + iov[0].iov_len = 1; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + ssize_t result = sendmsg(peerProxy->fd->fd, &msg, 0); + + if (result < 0) { + peerProxyHandleError(peerProxy, "sendmsg"); + } else { + // Success. Queue up the next packet. + peerProxyNextPacket(peerProxy); + + } +} + +/** + * Writes some outgoing data. + */ +static void peerProxyWrite(SelectableFd* fd) { + // TODO: Try to write header and body with one system call. + + PeerProxy* peerProxy = (PeerProxy*) fd->data; + OutgoingPacket* current = peerProxy->currentPacket; + + if (current == NULL) { + // We have nothing left to write. + return; + } + + // Write the header. + Buffer* outgoingHeader = &peerProxy->outgoingHeader; + bool headerWritten = bufferWriteComplete(outgoingHeader); + if (!headerWritten) { + LOGD("Writing header..."); + headerWritten = peerProxyWriteFromBuffer(peerProxy, outgoingHeader); + if (headerWritten) { + LOGD("Header written."); + } + } + + // Write body. + if (headerWritten) { + PacketType type = current->header.type; + switch (type) { + case CONNECTION: + peerProxyWriteConnection(peerProxy); + break; + case BYTES: + peerProxyWriteBytes(peerProxy); + break; + case CONNECTION_REQUEST: + case CONNECTION_ERROR: + // These packets consist solely of a header. + peerProxyNextPacket(peerProxy); + break; + default: + LOG_ALWAYS_FATAL("Unknown packet type: %d", type); + } + } +} + +/** + * Sets up a peer proxy's fd before we try to select() it. + */ +static void peerProxyBeforeSelect(SelectableFd* fd) { + LOGD("Before select..."); + + PeerProxy* peerProxy = (PeerProxy*) fd->data; + + peerLock(peerProxy->peer); + bool hasPackets = peerProxy->currentPacket != NULL; + peerUnlock(peerProxy->peer); + + if (hasPackets) { + LOGD("Packets found. Setting onWritable()."); + + fd->onWritable = &peerProxyWrite; + } else { + // We have nothing to write. + fd->onWritable = NULL; + } +} + +/** Prepare to read bytes from the peer. */ +static void peerProxyExpectBytes(PeerProxy* peerProxy, Header* header) { + LOGD("Expecting %d bytes.", header->size); + + peerProxy->inputState = READING_BYTES; + if (bufferPrepareForRead(peerProxy->inputBuffer, header->size) == -1) { + LOGW("Couldn't allocate memory for incoming data. Size: %u", + (unsigned int) header->size); + + // TODO: Ignore the packet and log a warning? + peerProxyKill(peerProxy, false); + } +} + +/** + * Gets a peer proxy for the given ID. Creates a peer proxy if necessary. + * Sends a connection request to the master if desired. + * + * Returns NULL if an error occurs. Sets errno to EHOSTDOWN if the peer died + * or ENOMEM if memory couldn't be allocated. + */ +static PeerProxy* peerProxyGetOrCreate(Peer* peer, pid_t pid, + bool requestConnection) { + if (pid == peer->pid) { + errno = EINVAL; + return NULL; + } + + if (peerIsDead(peer, pid)) { + errno = EHOSTDOWN; + return NULL; + } + + PeerProxy* peerProxy = hashmapGet(peer->peerProxies, &pid); + if (peerProxy != NULL) { + return peerProxy; + } + + // If this is the master peer, we already know about all peers. + if (peer->master) { + errno = EHOSTDOWN; + return NULL; + } + + // Try to create a peer proxy. + Credentials credentials; + credentials.pid = pid; + + // Fake gid and uid until we have the real thing. The real creds are + // filled in by masterProxyExpectConnection(). These fake creds will + // never be exposed to the user. + credentials.uid = 0; + credentials.gid = 0; + + // Make sure we can allocate the connection request packet. + OutgoingPacket* packet = NULL; + if (requestConnection) { + packet = calloc(1, sizeof(OutgoingPacket)); + if (packet == NULL) { + errno = ENOMEM; + return NULL; + } + + packet->header.type = CONNECTION_REQUEST; + packet->header.credentials = credentials; + packet->free = &outgoingPacketFree; + } + + peerProxy = peerProxyCreate(peer, credentials); + if (peerProxy == NULL) { + free(packet); + errno = ENOMEM; + return NULL; + } else { + // Send a connection request to the master. + if (requestConnection) { + PeerProxy* masterProxy = peer->masterProxy; + peerProxyEnqueueOutgoingPacket(masterProxy, packet); + } + + return peerProxy; + } +} + +/** + * Switches the master peer proxy into a state where it's waiting for a + * connection from the master. + */ +static void masterProxyExpectConnection(PeerProxy* masterProxy, + Header* header) { + // TODO: Restructure things so we don't need this check. + // Verify that this really is the master. + if (!masterProxy->master) { + LOGW("Non-master process %d tried to send us a connection.", + masterProxy->credentials.pid); + // Kill off the evil peer. + peerProxyKill(masterProxy, false); + return; + } + + masterProxy->inputState = ACCEPTING_CONNECTION; + Peer* localPeer = masterProxy->peer; + + // Create a peer proxy so we have somewhere to stash the creds. + // See if we already have a proxy set up. + pid_t pid = header->credentials.pid; + peerLock(localPeer); + PeerProxy* peerProxy = peerProxyGetOrCreate(localPeer, pid, false); + if (peerProxy == NULL) { + LOGW("Peer proxy creation failed: %s", strerror(errno)); + } else { + // Fill in full credentials. + peerProxy->credentials = header->credentials; + } + peerUnlock(localPeer); + + // Keep track of which peer proxy we're accepting a connection for. + masterProxy->connecting = peerProxy; +} + +/** + * Reads input from a peer process. + */ +static void peerProxyRead(SelectableFd* fd); + +/** Sets up fd callbacks. */ +static void peerProxySetFd(PeerProxy* peerProxy, SelectableFd* fd) { + peerProxy->fd = fd; + fd->data = peerProxy; + fd->onReadable = &peerProxyRead; + fd->beforeSelect = &peerProxyBeforeSelect; + + // Make the socket non-blocking. + setNonBlocking(fd->fd); +} + +/** + * Accepts a connection sent by the master proxy. + */ +static void masterProxyAcceptConnection(PeerProxy* masterProxy) { + struct msghdr msg; + struct iovec iov[1]; + ssize_t size; + char ignored; + int incomingFd; + + // TODO: Reuse code which writes the connection. Who the heck designed + // this API anyway? + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(int))]; + } control_un; + struct cmsghdr *cmptr; + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + + // We sent 1 byte of data so we can detect EOF. + iov[0].iov_base = &ignored; + iov[0].iov_len = 1; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + size = recvmsg(masterProxy->fd->fd, &msg, 0); + if (size < 0) { + if (errno == EINTR) { + // Log interruptions but otherwise ignore them. + LOGW("recvmsg() interrupted."); + return; + } else if (errno == EAGAIN) { + // Keep waiting for the connection. + return; + } else { + LOG_ALWAYS_FATAL("Error reading connection from master: %s", + strerror(errno)); + } + } else if (size == 0) { + // EOF. + LOG_ALWAYS_FATAL("Received EOF from master."); + } + + // Extract fd from message. + if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL + && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) { + if (cmptr->cmsg_level != SOL_SOCKET) { + LOG_ALWAYS_FATAL("Expected SOL_SOCKET."); + } + if (cmptr->cmsg_type != SCM_RIGHTS) { + LOG_ALWAYS_FATAL("Expected SCM_RIGHTS."); + } + incomingFd = *((int*) CMSG_DATA(cmptr)); + } else { + LOG_ALWAYS_FATAL("Expected fd."); + } + + // The peer proxy this connection is for. + PeerProxy* peerProxy = masterProxy->connecting; + if (peerProxy == NULL) { + LOGW("Received connection for unknown peer."); + closeWithWarning(incomingFd); + } else { + Peer* peer = masterProxy->peer; + + SelectableFd* selectableFd = selectorAdd(peer->selector, incomingFd); + if (selectableFd == NULL) { + LOGW("Error adding fd to selector for %d.", + peerProxy->credentials.pid); + closeWithWarning(incomingFd); + peerProxyKill(peerProxy, false); + } + + peerProxySetFd(peerProxy, selectableFd); + } + + peerProxyExpectHeader(masterProxy); +} + +/** + * Frees an outgoing packet containing a connection. + */ +static void outgoingPacketFreeSocket(OutgoingPacket* packet) { + closeWithWarning(packet->socket); + outgoingPacketFree(packet); +} + +/** + * Connects two known peers. + */ +static void masterConnectPeers(PeerProxy* peerA, PeerProxy* peerB) { + int sockets[2]; + int result = socketpair(AF_LOCAL, SOCK_STREAM, 0, sockets); + if (result == -1) { + LOGW("socketpair() error: %s", strerror(errno)); + // TODO: Send CONNECTION_FAILED packets to peers. + return; + } + + OutgoingPacket* packetA = calloc(1, sizeof(OutgoingPacket)); + OutgoingPacket* packetB = calloc(1, sizeof(OutgoingPacket)); + if (packetA == NULL || packetB == NULL) { + free(packetA); + free(packetB); + LOGW("malloc() error. Failed to tell process %d that process %d is" + " dead.", peerA->credentials.pid, peerB->credentials.pid); + return; + } + + packetA->header.type = CONNECTION; + packetB->header.type = CONNECTION; + + packetA->header.credentials = peerB->credentials; + packetB->header.credentials = peerA->credentials; + + packetA->socket = sockets[0]; + packetB->socket = sockets[1]; + + packetA->free = &outgoingPacketFreeSocket; + packetB->free = &outgoingPacketFreeSocket; + + peerLock(peerA->peer); + peerProxyEnqueueOutgoingPacket(peerA, packetA); + peerProxyEnqueueOutgoingPacket(peerB, packetB); + peerUnlock(peerA->peer); +} + +/** + * Informs a peer that the peer they're trying to connect to couldn't be + * found. + */ +static void masterReportConnectionError(PeerProxy* peerProxy, + Credentials credentials) { + OutgoingPacket* packet = calloc(1, sizeof(OutgoingPacket)); + if (packet == NULL) { + LOGW("malloc() error. Failed to tell process %d that process %d is" + " dead.", peerProxy->credentials.pid, credentials.pid); + return; + } + + packet->header.type = CONNECTION_ERROR; + packet->header.credentials = credentials; + packet->free = &outgoingPacketFree; + + peerProxyLockAndEnqueueOutgoingPacket(peerProxy, packet); +} + +/** + * Handles a request to be connected to another peer. + */ +static void masterHandleConnectionRequest(PeerProxy* peerProxy, + Header* header) { + Peer* master = peerProxy->peer; + pid_t targetPid = header->credentials.pid; + if (!hashmapContainsKey(peerProxy->connections, &targetPid)) { + // We haven't connected these peers yet. + PeerProxy* targetPeer + = (PeerProxy*) hashmapGet(master->peerProxies, &targetPid); + if (targetPeer == NULL) { + // Unknown process. + masterReportConnectionError(peerProxy, header->credentials); + } else { + masterConnectPeers(peerProxy, targetPeer); + } + } + + // This packet is complete. Get ready for the next one. + peerProxyExpectHeader(peerProxy); +} + +/** + * The master told us this peer is dead. + */ +static void masterProxyHandleConnectionError(PeerProxy* masterProxy, + Header* header) { + Peer* peer = masterProxy->peer; + + // Look up the peer proxy. + pid_t pid = header->credentials.pid; + PeerProxy* peerProxy = NULL; + peerLock(peer); + peerProxy = hashmapGet(peer->peerProxies, &pid); + peerUnlock(peer); + + if (peerProxy != NULL) { + LOGI("Couldn't connect to %d.", pid); + peerProxyKill(peerProxy, false); + } else { + LOGW("Peer proxy for %d not found. This shouldn't happen.", pid); + } + + peerProxyExpectHeader(masterProxy); +} + +/** + * Handles a packet header. + */ +static void peerProxyHandleHeader(PeerProxy* peerProxy, Header* header) { + switch (header->type) { + case CONNECTION_REQUEST: + masterHandleConnectionRequest(peerProxy, header); + break; + case CONNECTION: + masterProxyExpectConnection(peerProxy, header); + break; + case CONNECTION_ERROR: + masterProxyHandleConnectionError(peerProxy, header); + break; + case BYTES: + peerProxyExpectBytes(peerProxy, header); + break; + default: + LOGW("Invalid packet type from %d: %d", peerProxy->credentials.pid, + header->type); + peerProxyKill(peerProxy, false); + } +} + +/** + * Buffers input sent by peer. May be called multiple times until the entire + * buffer is filled. Returns true when the buffer is full. + */ +static bool peerProxyBufferInput(PeerProxy* peerProxy) { + Buffer* in = peerProxy->inputBuffer; + ssize_t size = bufferRead(in, peerProxy->fd->fd); + if (size < 0) { + peerProxyHandleError(peerProxy, "read"); + return false; + } else if (size == 0) { + // EOF. + LOGI("EOF"); + peerProxyKill(peerProxy, false); + return false; + } else if (bufferReadComplete(in)) { + // We're done! + return true; + } else { + // Continue reading. + return false; + } +} + +/** + * Reads input from a peer process. + */ +static void peerProxyRead(SelectableFd* fd) { + LOGD("Reading..."); + PeerProxy* peerProxy = (PeerProxy*) fd->data; + int state = peerProxy->inputState; + Buffer* in = peerProxy->inputBuffer; + switch (state) { + case READING_HEADER: + if (peerProxyBufferInput(peerProxy)) { + LOGD("Header read."); + // We've read the complete header. + Header* header = (Header*) in->data; + peerProxyHandleHeader(peerProxy, header); + } + break; + case READING_BYTES: + LOGD("Reading bytes..."); + if (peerProxyBufferInput(peerProxy)) { + LOGD("Bytes read."); + // We have the complete packet. Notify bytes listener. + peerProxy->peer->onBytes(peerProxy->credentials, + in->data, in->size); + + // Get ready for the next packet. + peerProxyExpectHeader(peerProxy); + } + break; + case ACCEPTING_CONNECTION: + masterProxyAcceptConnection(peerProxy); + break; + default: + LOG_ALWAYS_FATAL("Unknown state: %d", state); + } +} + +static PeerProxy* peerProxyCreate(Peer* peer, Credentials credentials) { + PeerProxy* peerProxy = calloc(1, sizeof(PeerProxy)); + if (peerProxy == NULL) { + return NULL; + } + + peerProxy->inputBuffer = bufferCreate(sizeof(Header)); + if (peerProxy->inputBuffer == NULL) { + free(peerProxy); + return NULL; + } + + peerProxy->peer = peer; + peerProxy->credentials = credentials; + + // Initial state == expecting a header. + peerProxyExpectHeader(peerProxy); + + // Add this proxy to the map. Make sure the key points to the stable memory + // inside of the peer proxy itself. + pid_t* pid = &(peerProxy->credentials.pid); + hashmapPut(peer->peerProxies, pid, peerProxy); + return peerProxy; +} + +/** Accepts a connection to the master peer. */ +static void masterAcceptConnection(SelectableFd* listenerFd) { + // Accept connection. + int socket = accept(listenerFd->fd, NULL, NULL); + if (socket == -1) { + LOGW("accept() error: %s", strerror(errno)); + return; + } + + LOGD("Accepted connection as fd %d.", socket); + + // Get credentials. + Credentials credentials; + struct ucred ucredentials; + socklen_t credentialsSize = sizeof(struct ucred); + int result = getsockopt(socket, SOL_SOCKET, SO_PEERCRED, + &ucredentials, &credentialsSize); + // We might want to verify credentialsSize. + if (result == -1) { + LOGW("getsockopt() error: %s", strerror(errno)); + closeWithWarning(socket); + return; + } + + // Copy values into our own structure so we know we have the types right. + credentials.pid = ucredentials.pid; + credentials.uid = ucredentials.uid; + credentials.gid = ucredentials.gid; + + LOGI("Accepted connection from process %d.", credentials.pid); + + Peer* masterPeer = (Peer*) listenerFd->data; + + peerLock(masterPeer); + + // Make sure we don't already have a connection from that process. + PeerProxy* peerProxy + = hashmapGet(masterPeer->peerProxies, &credentials.pid); + if (peerProxy != NULL) { + peerUnlock(masterPeer); + LOGW("Alread connected to process %d.", credentials.pid); + closeWithWarning(socket); + return; + } + + // Add connection to the selector. + SelectableFd* socketFd = selectorAdd(masterPeer->selector, socket); + if (socketFd == NULL) { + peerUnlock(masterPeer); + LOGW("malloc() failed."); + closeWithWarning(socket); + return; + } + + // Create a peer proxy. + peerProxy = peerProxyCreate(masterPeer, credentials); + peerUnlock(masterPeer); + if (peerProxy == NULL) { + LOGW("malloc() failed."); + socketFd->remove = true; + closeWithWarning(socket); + } + peerProxy->connections = hashmapCreate(10, &pidHash, &pidEquals); + peerProxySetFd(peerProxy, socketFd); +} + +/** + * Creates the local peer. + */ +static Peer* peerCreate() { + Peer* peer = calloc(1, sizeof(Peer)); + if (peer == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + peer->peerProxies = hashmapCreate(10, &pidHash, &pidEquals); + peer->selector = selectorCreate(); + + pthread_mutexattr_t attributes; + if (pthread_mutexattr_init(&attributes) != 0) { + LOG_ALWAYS_FATAL("pthread_mutexattr_init() error."); + } + if (pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE) != 0) { + LOG_ALWAYS_FATAL("pthread_mutexattr_settype() error."); + } + if (pthread_mutex_init(&peer->mutex, &attributes) != 0) { + LOG_ALWAYS_FATAL("pthread_mutex_init() error."); + } + + peer->pid = getpid(); + return peer; +} + +/** The local peer. */ +static Peer* localPeer; + +/** Frees a packet of bytes. */ +static void outgoingPacketFreeBytes(OutgoingPacket* packet) { + LOGD("Freeing outgoing packet."); + bufferFree(packet->bytes); + free(packet); +} + +/** + * Sends a packet of bytes to a remote peer. Returns 0 on success. + * + * Returns -1 if an error occurs. Sets errno to ENOMEM if memory couldn't be + * allocated. Sets errno to EHOSTDOWN if the peer died recently. Sets errno + * to EINVAL if pid is the same as the local pid. + */ +int peerSendBytes(pid_t pid, const char* bytes, size_t size) { + Peer* peer = localPeer; + assert(peer != NULL); + + OutgoingPacket* packet = calloc(1, sizeof(OutgoingPacket)); + if (packet == NULL) { + errno = ENOMEM; + return -1; + } + + Buffer* copy = bufferCreate(size); + if (copy == NULL) { + free(packet); + errno = ENOMEM; + return -1; + } + + // Copy data. + memcpy(copy->data, bytes, size); + copy->size = size; + + packet->bytes = copy; + packet->header.type = BYTES; + packet->header.size = size; + packet->free = outgoingPacketFreeBytes; + bufferPrepareForWrite(packet->bytes); + + peerLock(peer); + + PeerProxy* peerProxy = peerProxyGetOrCreate(peer, pid, true); + if (peerProxy == NULL) { + // The peer is already dead or we couldn't alloc memory. Either way, + // errno is set. + peerUnlock(peer); + packet->free(packet); + return -1; + } else { + peerProxyEnqueueOutgoingPacket(peerProxy, packet); + peerUnlock(peer); + selectorWakeUp(peer->selector); + return 0; + } +} + +/** Keeps track of how to free shared bytes. */ +typedef struct { + void (*free)(void* context); + void* context; +} SharedBytesFreer; + +/** Frees shared bytes. */ +static void outgoingPacketFreeSharedBytes(OutgoingPacket* packet) { + SharedBytesFreer* sharedBytesFreer + = (SharedBytesFreer*) packet->context; + sharedBytesFreer->free(sharedBytesFreer->context); + free(sharedBytesFreer); + free(packet); +} + +/** + * Sends a packet of bytes to a remote peer without copying the bytes. Calls + * free() with context after the bytes have been sent. + * + * Returns -1 if an error occurs. Sets errno to ENOMEM if memory couldn't be + * allocated. Sets errno to EHOSTDOWN if the peer died recently. Sets errno + * to EINVAL if pid is the same as the local pid. + */ +int peerSendSharedBytes(pid_t pid, char* bytes, size_t size, + void (*free)(void* context), void* context) { + Peer* peer = localPeer; + assert(peer != NULL); + + OutgoingPacket* packet = calloc(1, sizeof(OutgoingPacket)); + if (packet == NULL) { + errno = ENOMEM; + return -1; + } + + Buffer* wrapper = bufferWrap(bytes, size, size); + if (wrapper == NULL) { + free(packet); + errno = ENOMEM; + return -1; + } + + SharedBytesFreer* sharedBytesFreer = malloc(sizeof(SharedBytesFreer)); + if (sharedBytesFreer == NULL) { + free(packet); + free(wrapper); + errno = ENOMEM; + return -1; + } + sharedBytesFreer->free = free; + sharedBytesFreer->context = context; + + packet->bytes = wrapper; + packet->context = sharedBytesFreer; + packet->header.type = BYTES; + packet->header.size = size; + packet->free = &outgoingPacketFreeSharedBytes; + bufferPrepareForWrite(packet->bytes); + + peerLock(peer); + + PeerProxy* peerProxy = peerProxyGetOrCreate(peer, pid, true); + if (peerProxy == NULL) { + // The peer is already dead or we couldn't alloc memory. Either way, + // errno is set. + peerUnlock(peer); + packet->free(packet); + return -1; + } else { + peerProxyEnqueueOutgoingPacket(peerProxy, packet); + peerUnlock(peer); + selectorWakeUp(peer->selector); + return 0; + } +} + +/** + * Starts the master peer. The master peer differs from other peers in that + * it is responsible for connecting the other peers. You can only have one + * master peer. + * + * Goes into an I/O loop and does not return. + */ +void masterPeerInitialize(BytesListener* bytesListener, + DeathListener* deathListener) { + // Create and bind socket. + int listenerSocket = socket(AF_LOCAL, SOCK_STREAM, 0); + if (listenerSocket == -1) { + LOG_ALWAYS_FATAL("socket() error: %s", strerror(errno)); + } + unlink(MASTER_PATH); + int result = bind(listenerSocket, (SocketAddress*) getMasterAddress(), + sizeof(UnixAddress)); + if (result == -1) { + LOG_ALWAYS_FATAL("bind() error: %s", strerror(errno)); + } + + LOGD("Listener socket: %d", listenerSocket); + + // Queue up to 16 connections. + result = listen(listenerSocket, 16); + if (result != 0) { + LOG_ALWAYS_FATAL("listen() error: %s", strerror(errno)); + } + + // Make socket non-blocking. + setNonBlocking(listenerSocket); + + // Create the peer for this process. Fail if we already have one. + if (localPeer != NULL) { + LOG_ALWAYS_FATAL("Peer is already initialized."); + } + localPeer = peerCreate(); + if (localPeer == NULL) { + LOG_ALWAYS_FATAL("malloc() failed."); + } + localPeer->master = true; + localPeer->onBytes = bytesListener; + localPeer->onDeath = deathListener; + + // Make listener socket selectable. + SelectableFd* listenerFd = selectorAdd(localPeer->selector, listenerSocket); + if (listenerFd == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + listenerFd->data = localPeer; + listenerFd->onReadable = &masterAcceptConnection; +} + +/** + * Starts a local peer. + * + * Goes into an I/O loop and does not return. + */ +void peerInitialize(BytesListener* bytesListener, + DeathListener* deathListener) { + // Connect to master peer. + int masterSocket = socket(AF_LOCAL, SOCK_STREAM, 0); + if (masterSocket == -1) { + LOG_ALWAYS_FATAL("socket() error: %s", strerror(errno)); + } + int result = connect(masterSocket, (SocketAddress*) getMasterAddress(), + sizeof(UnixAddress)); + if (result != 0) { + LOG_ALWAYS_FATAL("connect() error: %s", strerror(errno)); + } + + // Create the peer for this process. Fail if we already have one. + if (localPeer != NULL) { + LOG_ALWAYS_FATAL("Peer is already initialized."); + } + localPeer = peerCreate(); + if (localPeer == NULL) { + LOG_ALWAYS_FATAL("malloc() failed."); + } + localPeer->onBytes = bytesListener; + localPeer->onDeath = deathListener; + + // Make connection selectable. + SelectableFd* masterFd = selectorAdd(localPeer->selector, masterSocket); + if (masterFd == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + + // Create a peer proxy for the master peer. + PeerProxy* masterProxy = peerProxyCreate(localPeer, MASTER_CREDENTIALS); + if (masterProxy == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + peerProxySetFd(masterProxy, masterFd); + masterProxy->master = true; + localPeer->masterProxy = masterProxy; +} + +/** Starts the master peer I/O loop. Doesn't return. */ +void peerLoop() { + assert(localPeer != NULL); + + // Start selector. + selectorLoop(localPeer->selector); +} + diff --git a/libcutils/mspace.c b/libcutils/mspace.c new file mode 100644 index 0000000..295e47b --- /dev/null +++ b/libcutils/mspace.c @@ -0,0 +1,286 @@ +/* Copyright 2006 The Android Open Source Project */ + +/* A wrapper file for dlmalloc.c that compiles in the + * mspace_*() functions, which provide an interface for + * creating multiple heaps. + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/ioctl.h> + +#include <cutils/ashmem.h> + +/* It's a pain getting the mallinfo stuff to work + * with Linux, OSX, and klibc, so just turn it off + * for now. + * TODO: make mallinfo work + */ +#define NO_MALLINFO 1 + +/* Allow setting the maximum heap footprint. + */ +#define USE_MAX_ALLOWED_FOOTPRINT 1 + +/* Don't try to trim memory. + * TODO: support this. + */ +#define MORECORE_CANNOT_TRIM 1 + +/* Use mmap()d anonymous memory to guarantee + * that an mspace is contiguous. + * + * create_mspace() won't work right if this is + * defined, so hide the definition of it and + * break any users at build time. + */ +#define USE_CONTIGUOUS_MSPACES 1 +#if USE_CONTIGUOUS_MSPACES +/* This combination of settings forces sys_alloc() + * to always use MORECORE(). It won't expect the + * results to be contiguous, but we'll guarantee + * that they are. + */ +#define HAVE_MMAP 0 +#define HAVE_MORECORE 1 +#define MORECORE_CONTIGUOUS 0 +/* m is always the appropriate local when MORECORE() is called. */ +#define MORECORE(S) contiguous_mspace_morecore(m, S) +#define create_mspace HIDDEN_create_mspace_HIDDEN +#define destroy_mspace HIDDEN_destroy_mspace_HIDDEN +typedef struct malloc_state *mstate0; +static void *contiguous_mspace_morecore(mstate0 m, ssize_t nb); +#endif + +#define MSPACES 1 +#define ONLY_MSPACES 1 +#include "../bionic/dlmalloc.c" + +#ifndef PAGESIZE +#define PAGESIZE mparams.page_size +#endif + +#define ALIGN_UP(p, alignment) \ + (((uintptr_t)(p) + (alignment)-1) & ~((alignment)-1)) + +/* A direct copy of dlmalloc_usable_size(), + * which isn't compiled in when ONLY_MSPACES is set. + * The mspace parameter isn't actually necessary, + * but we include it to be consistent with the + * rest of the mspace_*() functions. + */ +size_t mspace_usable_size(mspace _unused, const void* mem) { + if (mem != 0) { + const mchunkptr p = mem2chunk(mem); + if (cinuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +#if USE_CONTIGUOUS_MSPACES +#include <sys/mman.h> +#include <limits.h> + +#define CONTIG_STATE_MAGIC 0xf00dd00d +struct mspace_contig_state { + unsigned int magic; + char *brk; + char *top; + mspace m; +}; + +static void *contiguous_mspace_morecore(mstate m, ssize_t nb) { + struct mspace_contig_state *cs; + char *oldbrk; + const unsigned int pagesize = PAGESIZE; + + cs = (struct mspace_contig_state *)((uintptr_t)m & ~(pagesize-1)); + assert(cs->magic == CONTIG_STATE_MAGIC); + assert(cs->m == m); +assert(nb >= 0); //xxx deal with the trim case + + oldbrk = cs->brk; + if (nb > 0) { + /* Break to the first page boundary that satisfies the request. + */ + char *newbrk = (char *)ALIGN_UP(oldbrk + nb, pagesize); + if (newbrk > cs->top) + return CMFAIL; + + /* Update the protection on the underlying memory. + * Pages we've given to dlmalloc are read/write, and + * pages we haven't are not accessable (read or write + * will cause a seg fault). + */ + if (mprotect(cs, newbrk - (char *)cs, PROT_READ | PROT_WRITE) < 0) + return CMFAIL; + if (newbrk != cs->top) { + if (mprotect(newbrk, cs->top - newbrk, PROT_NONE) < 0) + return CMFAIL; + } + + cs->brk = newbrk; + + /* Make sure that dlmalloc will merge this block with the + * initial block that was passed to create_mspace_with_base(). + * We don't care about extern vs. non-extern, so just clear it. + */ + m->seg.sflags &= ~EXTERN_BIT; + } + + return oldbrk; +} + +mspace create_contiguous_mspace_with_base(size_t starting_capacity, + size_t max_capacity, int locked, void *base) { + struct mspace_contig_state *cs; + unsigned int pagesize; + mstate m; + + init_mparams(); + pagesize = PAGESIZE; + assert(starting_capacity <= max_capacity); + assert(((uintptr_t)base & (pagesize-1)) == 0); + assert(((uintptr_t)max_capacity & (pagesize-1)) == 0); + starting_capacity = (size_t)ALIGN_UP(starting_capacity, pagesize); + + /* Make the first page read/write. dlmalloc needs to use that page. + */ + if (mprotect(base, starting_capacity, PROT_READ | PROT_WRITE) < 0) { + goto error; + } + + /* Create the mspace, pointing to the memory given. + */ + m = create_mspace_with_base((char *)base + sizeof(*cs), starting_capacity, + locked); + if (m == (mspace)0) { + goto error; + } + /* Make sure that m is in the same page as base. + */ + assert(((uintptr_t)m & (uintptr_t)~(pagesize-1)) == (uintptr_t)base); + /* Use some space for the information that our MORECORE needs. + */ + cs = (struct mspace_contig_state *)base; + + /* Find out exactly how much of the memory the mspace + * is using. + */ + cs->brk = m->seg.base + m->seg.size; + cs->top = (char *)base + max_capacity; + + assert((char *)base <= cs->brk); + assert(cs->brk <= cs->top); + /* Prevent access to the memory we haven't handed out yet. + */ + if (cs->brk != cs->top) { + /* mprotect() requires page-aligned arguments, but it's possible + * for cs->brk not to be page-aligned at this point. + */ + char *prot_brk = (char *)ALIGN_UP(cs->brk, pagesize); + if ((mprotect(base, prot_brk - (char *)base, PROT_READ | PROT_WRITE) < 0) || + (mprotect(prot_brk, cs->top - prot_brk, PROT_NONE) < 0)) { + goto error; + } + } + + cs->m = m; + cs->magic = CONTIG_STATE_MAGIC; + + return (mspace)m; + +error: + return (mspace)0; +} + + +mspace create_contiguous_mspace_with_name(size_t starting_capacity, + size_t max_capacity, int locked, char const *name) { + int fd, ret; + char buf[ASHMEM_NAME_LEN] = "mspace"; + void *base; + unsigned int pagesize; + mstate m; + + if (starting_capacity > max_capacity) + return (mspace)0; + + init_mparams(); + pagesize = PAGESIZE; + + /* Create the anonymous memory that will back the mspace. + * This reserves all of the virtual address space we could + * ever need. Physical pages will be mapped as the memory + * is touched. + * + * Align max_capacity to a whole page. + */ + max_capacity = (size_t)ALIGN_UP(max_capacity, pagesize); + + if (name) + snprintf(buf, sizeof(buf), "mspace/%s", name); + fd = ashmem_create_region(buf, max_capacity); + if (fd < 0) + return (mspace)0; + + base = mmap(NULL, max_capacity, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + close(fd); + if (base == MAP_FAILED) + return (mspace)0; + + /* Make sure that base is at the beginning of a page. + */ + assert(((uintptr_t)base & (pagesize-1)) == 0); + + m = create_contiguous_mspace_with_base(starting_capacity, max_capacity, + locked, base); + if (m == 0) { + munmap(base, max_capacity); + } + return m; +} + +mspace create_contiguous_mspace(size_t starting_capacity, + size_t max_capacity, int locked) { + return create_contiguous_mspace_with_name(starting_capacity, + max_capacity, locked, NULL); +} + +size_t destroy_contiguous_mspace(mspace msp) { + mstate ms = (mstate)msp; + + if (ok_magic(ms)) { + struct mspace_contig_state *cs; + size_t length; + const unsigned int pagesize = PAGESIZE; + + cs = (struct mspace_contig_state *)((uintptr_t)ms & ~(pagesize-1)); + assert(cs->magic == CONTIG_STATE_MAGIC); + assert(cs->m == ms); + + length = cs->top - (char *)cs; + if (munmap((char *)cs, length) != 0) + return length; + } + else { + USAGE_ERROR_ACTION(ms, ms); + } + return 0; +} + +void *contiguous_mspace_sbrk0(mspace msp) { + struct mspace_contig_state *cs; + mstate ms; + const unsigned int pagesize = PAGESIZE; + + ms = (mstate)msp; + cs = (struct mspace_contig_state *)((uintptr_t)ms & ~(pagesize-1)); + assert(cs->magic == CONTIG_STATE_MAGIC); + assert(cs->m == ms); + return cs->brk; +} +#endif diff --git a/libcutils/native_handle.c b/libcutils/native_handle.c new file mode 100644 index 0000000..4089968 --- /dev/null +++ b/libcutils/native_handle.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "NativeHandle" + +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include <cutils/log.h> +#include <cutils/native_handle.h> + +native_handle_t* native_handle_create(int numFds, int numInts) +{ + native_handle_t* h = malloc( + sizeof(native_handle_t) + sizeof(int)*(numFds+numInts)); + + h->version = sizeof(native_handle_t); + h->numFds = numFds; + h->numInts = numInts; + return h; +} + +int native_handle_delete(native_handle_t* h) +{ + if (h) { + if (h->version != sizeof(native_handle_t)) + return -EINVAL; + free(h); + } + return 0; +} + +int native_handle_close(const native_handle_t* h) +{ + if (h->version != sizeof(native_handle_t)) + return -EINVAL; + + const int numFds = h->numFds; + int i; + for (i=0 ; i<numFds ; i++) { + close(h->data[i]); + } + return 0; +} diff --git a/libcutils/open_memstream.c b/libcutils/open_memstream.c new file mode 100644 index 0000000..5b4388a --- /dev/null +++ b/libcutils/open_memstream.c @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HAVE_OPEN_MEMSTREAM + +/* + * Implementation of the POSIX open_memstream() function, which Linux has + * but BSD lacks. + * + * Summary: + * - Works like a file-backed FILE* opened with fopen(name, "w"), but the + * backing is a chunk of memory rather than a file. + * - The buffer expands as you write more data. Seeking past the end + * of the file and then writing to it zero-fills the gap. + * - The values at "*bufp" and "*sizep" should be considered read-only, + * and are only valid immediately after an fflush() or fclose(). + * - A '\0' is maintained just past the end of the file. This is not included + * in "*sizep". (The behavior w.r.t. fseek() is not clearly defined. + * The spec says the null byte is written when a write() advances EOF, + * but it looks like glibc ensures the null byte is always found at EOF, + * even if you just seeked backwards. The example on the opengroup.org + * page suggests that this is the expected behavior. The null must be + * present after a no-op fflush(), which we can't see, so we have to save + * and restore it. Annoying, but allows file truncation.) + * - After fclose(), the caller must eventually free(*bufp). + * + * This is built out of funopen(), which BSD has but Linux lacks. There is + * no flush() operator, so we need to keep the user pointers up to date + * after each operation. + * + * I don't think Windows has any of the above, but we don't need to use + * them there, so we just supply a stub. + */ +#include <cutils/open_memstream.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#if 0 +# define DBUG(x) printf x +#else +# define DBUG(x) ((void)0) +#endif + +#ifdef HAVE_FUNOPEN + +/* + * Definition of a seekable, write-only memory stream. + */ +typedef struct { + char** bufp; /* pointer to buffer pointer */ + size_t* sizep; /* pointer to eof */ + + size_t allocSize; /* size of buffer */ + size_t eof; /* furthest point we've written to */ + size_t offset; /* current write offset */ + char saved; /* required by NUL handling */ +} MemStream; + +#define kInitialSize 1024 + +/* + * Ensure that we have enough storage to write "size" bytes at the + * current offset. We also have to take into account the extra '\0' + * that we maintain just past EOF. + * + * Returns 0 on success. + */ +static int ensureCapacity(MemStream* stream, int writeSize) +{ + DBUG(("+++ ensureCap off=%d size=%d\n", stream->offset, writeSize)); + + size_t neededSize = stream->offset + writeSize + 1; + if (neededSize <= stream->allocSize) + return 0; + + size_t newSize; + + if (stream->allocSize == 0) { + newSize = kInitialSize; + } else { + newSize = stream->allocSize; + newSize += newSize / 2; /* expand by 3/2 */ + } + + if (newSize < neededSize) + newSize = neededSize; + DBUG(("+++ realloc %p->%p to size=%d\n", + stream->bufp, *stream->bufp, newSize)); + char* newBuf = (char*) realloc(*stream->bufp, newSize); + if (newBuf == NULL) + return -1; + + *stream->bufp = newBuf; + stream->allocSize = newSize; + return 0; +} + +/* + * Write data to a memstream, expanding the buffer if necessary. + * + * If we previously seeked beyond EOF, zero-fill the gap. + * + * Returns the number of bytes written. + */ +static int write_memstream(void* cookie, const char* buf, int size) +{ + MemStream* stream = (MemStream*) cookie; + + if (ensureCapacity(stream, size) < 0) + return -1; + + /* seeked past EOF earlier? */ + if (stream->eof < stream->offset) { + DBUG(("+++ zero-fill gap from %d to %d\n", + stream->eof, stream->offset-1)); + memset(*stream->bufp + stream->eof, '\0', + stream->offset - stream->eof); + } + + /* copy data, advance write pointer */ + memcpy(*stream->bufp + stream->offset, buf, size); + stream->offset += size; + + if (stream->offset > stream->eof) { + /* EOF has advanced, update it and append null byte */ + DBUG(("+++ EOF advanced to %d, appending nul\n", stream->offset)); + assert(stream->offset < stream->allocSize); + stream->eof = stream->offset; + } else { + /* within previously-written area; save char we're about to stomp */ + DBUG(("+++ within written area, saving '%c' at %d\n", + *(*stream->bufp + stream->offset), stream->offset)); + stream->saved = *(*stream->bufp + stream->offset); + } + *(*stream->bufp + stream->offset) = '\0'; + *stream->sizep = stream->offset; + + return size; +} + +/* + * Seek within a memstream. + * + * Returns the new offset, or -1 on failure. + */ +static fpos_t seek_memstream(void* cookie, fpos_t offset, int whence) +{ + MemStream* stream = (MemStream*) cookie; + off_t newPosn = (off_t) offset; + + if (whence == SEEK_CUR) { + newPosn += stream->offset; + } else if (whence == SEEK_END) { + newPosn += stream->eof; + } + + if (newPosn < 0 || ((fpos_t)((size_t) newPosn)) != newPosn) { + /* bad offset - negative or huge */ + DBUG(("+++ bogus seek offset %ld\n", (long) newPosn)); + errno = EINVAL; + return (fpos_t) -1; + } + + if (stream->offset < stream->eof) { + /* + * We were pointing to an area we'd already written to, which means + * we stomped on a character and must now restore it. + */ + DBUG(("+++ restoring char '%c' at %d\n", + stream->saved, stream->offset)); + *(*stream->bufp + stream->offset) = stream->saved; + } + + stream->offset = (size_t) newPosn; + + if (stream->offset < stream->eof) { + /* + * We're seeked backward into the stream. Preserve the character + * at EOF and stomp it with a NUL. + */ + stream->saved = *(*stream->bufp + stream->offset); + *(*stream->bufp + stream->offset) = '\0'; + *stream->sizep = stream->offset; + } else { + /* + * We're positioned at, or possibly beyond, the EOF. We want to + * publish the current EOF, not the current position. + */ + *stream->sizep = stream->eof; + } + + return newPosn; +} + +/* + * Close the memstream. We free everything but the data buffer. + */ +static int close_memstream(void* cookie) +{ + free(cookie); + return 0; +} + +/* + * Prepare a memstream. + */ +FILE* open_memstream(char** bufp, size_t* sizep) +{ + FILE* fp; + MemStream* stream; + + if (bufp == NULL || sizep == NULL) { + errno = EINVAL; + return NULL; + } + + stream = (MemStream*) calloc(1, sizeof(MemStream)); + if (stream == NULL) + return NULL; + + fp = funopen(stream, + NULL, write_memstream, seek_memstream, close_memstream); + if (fp == NULL) { + free(stream); + return NULL; + } + + *sizep = 0; + *bufp = NULL; + stream->bufp = bufp; + stream->sizep = sizep; + + return fp; +} + +#else /*not HAVE_FUNOPEN*/ +FILE* open_memstream(char** bufp, size_t* sizep) +{ + abort(); +} +#endif /*HAVE_FUNOPEN*/ + + + +#if 0 +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * Simple regression test. + * + * To test on desktop Linux with valgrind, it's possible to make a simple + * change to open_memstream() to use fopencookie instead: + * + * cookie_io_functions_t iofuncs = + * { NULL, write_memstream, seek_memstream, close_memstream }; + * fp = fopencookie(stream, "w", iofuncs); + * + * (Some tweaks to seek_memstream are also required, as that takes a + * pointer to an offset rather than an offset, and returns 0 or -1.) + */ +int testMemStream(void) +{ + FILE *stream; + char *buf; + size_t len; + off_t eob; + + printf("Test1\n"); + + /* std example */ + stream = open_memstream(&buf, &len); + fprintf(stream, "hello my world"); + fflush(stream); + printf("buf=%s, len=%zu\n", buf, len); + eob = ftello(stream); + fseeko(stream, 0, SEEK_SET); + fprintf(stream, "good-bye"); + fseeko(stream, eob, SEEK_SET); + fclose(stream); + printf("buf=%s, len=%zu\n", buf, len); + free(buf); + + printf("Test2\n"); + + /* std example without final seek-to-end */ + stream = open_memstream(&buf, &len); + fprintf(stream, "hello my world"); + fflush(stream); + printf("buf=%s, len=%zu\n", buf, len); + eob = ftello(stream); + fseeko(stream, 0, SEEK_SET); + fprintf(stream, "good-bye"); + //fseeko(stream, eob, SEEK_SET); + fclose(stream); + printf("buf=%s, len=%zu\n", buf, len); + free(buf); + + printf("Test3\n"); + + /* fancy example; should expand buffer with writes */ + static const int kCmpLen = 1024 + 128; + char* cmp = malloc(kCmpLen); + memset(cmp, 0, 1024); + memset(cmp+1024, 0xff, kCmpLen-1024); + sprintf(cmp, "This-is-a-tes1234"); + sprintf(cmp + 1022, "abcdef"); + + stream = open_memstream (&buf, &len); + setvbuf(stream, NULL, _IONBF, 0); /* note: crashes in glibc with this */ + fprintf(stream, "This-is-a-test"); + fseek(stream, -1, SEEK_CUR); /* broken in glibc; can use {13,SEEK_SET} */ + fprintf(stream, "1234"); + fseek(stream, 1022, SEEK_SET); + fputc('a', stream); + fputc('b', stream); + fputc('c', stream); + fputc('d', stream); + fputc('e', stream); + fputc('f', stream); + fflush(stream); + + if (memcmp(buf, cmp, len+1) != 0) { + printf("mismatch\n"); + } else { + printf("match\n"); + } + + printf("Test4\n"); + stream = open_memstream (&buf, &len); + fseek(stream, 5000, SEEK_SET); + fseek(stream, 4096, SEEK_SET); + fseek(stream, -1, SEEK_SET); /* should have no effect */ + fputc('x', stream); + if (ftell(stream) == 4097) + printf("good\n"); + else + printf("BAD: offset is %ld\n", ftell(stream)); + + printf("DONE\n"); + + return 0; +} + +/* expected output: +Test1 +buf=hello my world, len=14 +buf=good-bye world, len=14 +Test2 +buf=hello my world, len=14 +buf=good-bye, len=8 +Test3 +match +Test4 +good +DONE +*/ + +#endif + +#endif /*!HAVE_OPEN_MEMSTREAM*/ diff --git a/libcutils/private.h b/libcutils/private.h new file mode 100644 index 0000000..2837b70 --- /dev/null +++ b/libcutils/private.h @@ -0,0 +1,368 @@ +#ifndef PRIVATE_H + +#define PRIVATE_H + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + +/* +** ID +*/ + +#ifndef lint +#ifndef NOID +static char privatehid[] = "@(#)private.h 8.2"; +#endif /* !defined NOID */ +#endif /* !defined lint */ + +#define GRANDPARENTED "Local time zone must be set--see zic manual page" + +/* +** Defaults for preprocessor symbols. +** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'. +*/ + +#ifndef HAVE_ADJTIME +#define HAVE_ADJTIME 1 +#endif /* !defined HAVE_ADJTIME */ + +#ifndef HAVE_GETTEXT +#define HAVE_GETTEXT 0 +#endif /* !defined HAVE_GETTEXT */ + +#ifndef HAVE_INCOMPATIBLE_CTIME_R +#define HAVE_INCOMPATIBLE_CTIME_R 0 +#endif /* !defined INCOMPATIBLE_CTIME_R */ + +#ifndef HAVE_SETTIMEOFDAY +#define HAVE_SETTIMEOFDAY 3 +#endif /* !defined HAVE_SETTIMEOFDAY */ + +#ifndef HAVE_STRERROR +#define HAVE_STRERROR 1 +#endif /* !defined HAVE_STRERROR */ + +#ifndef HAVE_SYMLINK +#define HAVE_SYMLINK 1 +#endif /* !defined HAVE_SYMLINK */ + +#ifndef HAVE_SYS_STAT_H +#define HAVE_SYS_STAT_H 1 +#endif /* !defined HAVE_SYS_STAT_H */ + +#ifndef HAVE_SYS_WAIT_H +#define HAVE_SYS_WAIT_H 1 +#endif /* !defined HAVE_SYS_WAIT_H */ + +#ifndef HAVE_UNISTD_H +#define HAVE_UNISTD_H 1 +#endif /* !defined HAVE_UNISTD_H */ + +#ifndef HAVE_UTMPX_H +#define HAVE_UTMPX_H 0 +#endif /* !defined HAVE_UTMPX_H */ + +#ifndef LOCALE_HOME +#define LOCALE_HOME "/usr/lib/locale" +#endif /* !defined LOCALE_HOME */ + +#if HAVE_INCOMPATIBLE_CTIME_R +#define asctime_r _incompatible_asctime_r +#define ctime_r _incompatible_ctime_r +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +/* +** Nested includes +*/ + +#include "sys/types.h" /* for time_t */ +#include "stdio.h" +#include "errno.h" +#include "string.h" +#include "limits.h" /* for CHAR_BIT et al. */ +#include "time.h" +#include "stdlib.h" + +#if HAVE_GETTEXT +#include "libintl.h" +#endif /* HAVE_GETTEXT */ + +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */ +#endif /* HAVE_SYS_WAIT_H */ + +#ifndef WIFEXITED +#define WIFEXITED(status) (((status) & 0xff) == 0) +#endif /* !defined WIFEXITED */ +#ifndef WEXITSTATUS +#define WEXITSTATUS(status) (((status) >> 8) & 0xff) +#endif /* !defined WEXITSTATUS */ + +#if HAVE_UNISTD_H +#include "unistd.h" /* for F_OK and R_OK */ +#endif /* HAVE_UNISTD_H */ + +#if !HAVE_UNISTD_H +#ifndef F_OK +#define F_OK 0 +#endif /* !defined F_OK */ +#ifndef R_OK +#define R_OK 4 +#endif /* !defined R_OK */ +#endif /* !HAVE_UNISTD_H */ + +/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */ +#define is_digit(c) ((unsigned)(c) - '0' <= 9) + +/* +** Define HAVE_STDINT_H's default value here, rather than at the +** start, since __GLIBC__'s value depends on previously-included +** files. +** (glibc 2.1 and later have stdint.h, even with pre-C99 compilers.) +*/ +#ifndef HAVE_STDINT_H +#define HAVE_STDINT_H \ + (199901 <= __STDC_VERSION__ || \ + 2 < (__GLIBC__ + (0 < __GLIBC_MINOR__))) +#endif /* !defined HAVE_STDINT_H */ + +#if HAVE_STDINT_H +#include "stdint.h" +#endif /* !HAVE_STDINT_H */ + +#ifndef INT_FAST64_MAX +/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ +#if defined LLONG_MAX || defined __LONG_LONG_MAX__ +typedef long long int_fast64_t; +#else /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */ +#if (LONG_MAX >> 31) < 0xffffffff +Please use a compiler that supports a 64-bit integer type (or wider); +you may need to compile with "-DHAVE_STDINT_H". +#endif /* (LONG_MAX >> 31) < 0xffffffff */ +typedef long int_fast64_t; +#endif /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */ +#endif /* !defined INT_FAST64_MAX */ + +#ifndef INT32_MAX +#define INT32_MAX 0x7fffffff +#endif /* !defined INT32_MAX */ +#ifndef INT32_MIN +#define INT32_MIN (-1 - INT32_MAX) +#endif /* !defined INT32_MIN */ + +/* +** Workarounds for compilers/systems. +*/ + +/* +** If your compiler lacks prototypes, "#define P(x) ()". +*/ + +#ifndef P +#define P(x) x +#endif /* !defined P */ + +/* +** SunOS 4.1.1 headers lack EXIT_SUCCESS. +*/ + +#ifndef EXIT_SUCCESS +#define EXIT_SUCCESS 0 +#endif /* !defined EXIT_SUCCESS */ + +/* +** SunOS 4.1.1 headers lack EXIT_FAILURE. +*/ + +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif /* !defined EXIT_FAILURE */ + +/* +** SunOS 4.1.1 headers lack FILENAME_MAX. +*/ + +#ifndef FILENAME_MAX + +#ifndef MAXPATHLEN +#ifdef unix +#include "sys/param.h" +#endif /* defined unix */ +#endif /* !defined MAXPATHLEN */ + +#ifdef MAXPATHLEN +#define FILENAME_MAX MAXPATHLEN +#endif /* defined MAXPATHLEN */ +#ifndef MAXPATHLEN +#define FILENAME_MAX 1024 /* Pure guesswork */ +#endif /* !defined MAXPATHLEN */ + +#endif /* !defined FILENAME_MAX */ + +/* +** SunOS 4.1.1 libraries lack remove. +*/ + +#ifndef remove +extern int unlink P((const char * filename)); +#define remove unlink +#endif /* !defined remove */ + +/* +** Some ancient errno.h implementations don't declare errno. +** But some newer errno.h implementations define it as a macro. +** Fix the former without affecting the latter. +*/ + +#ifndef errno +extern int errno; +#endif /* !defined errno */ + +/* +** Some time.h implementations don't declare asctime_r. +** Others might define it as a macro. +** Fix the former without affecting the latter. +*/ + +#ifndef asctime_r +extern char * asctime_r(); +#endif + +/* +** Private function declarations. +*/ + +char * icalloc P((int nelem, int elsize)); +char * icatalloc P((char * old, const char * new)); +char * icpyalloc P((const char * string)); +char * imalloc P((int n)); +void * irealloc P((void * pointer, int size)); +void icfree P((char * pointer)); +void ifree P((char * pointer)); +const char * scheck P((const char * string, const char * format)); + +/* +** Finally, some convenience items. +*/ + +#ifndef TRUE +#define TRUE 1 +#endif /* !defined TRUE */ + +#ifndef FALSE +#define FALSE 0 +#endif /* !defined FALSE */ + +#ifndef TYPE_BIT +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#endif /* !defined TYPE_BIT */ + +#ifndef TYPE_SIGNED +#define TYPE_SIGNED(type) (((type) -1) < 0) +#endif /* !defined TYPE_SIGNED */ + +/* +** Since the definition of TYPE_INTEGRAL contains floating point numbers, +** it cannot be used in preprocessor directives. +*/ + +#ifndef TYPE_INTEGRAL +#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5) +#endif /* !defined TYPE_INTEGRAL */ + +#ifndef INT_STRLEN_MAXIMUM +/* +** 302 / 1000 is log10(2.0) rounded up. +** Subtract one for the sign bit if the type is signed; +** add one for integer division truncation; +** add one more for a minus sign if the type is signed. +*/ +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) +#endif /* !defined INT_STRLEN_MAXIMUM */ + +/* +** INITIALIZE(x) +*/ + +#ifndef GNUC_or_lint +#ifdef lint +#define GNUC_or_lint +#endif /* defined lint */ +#ifndef lint +#ifdef __GNUC__ +#define GNUC_or_lint +#endif /* defined __GNUC__ */ +#endif /* !defined lint */ +#endif /* !defined GNUC_or_lint */ + +#ifndef INITIALIZE +#ifdef GNUC_or_lint +#define INITIALIZE(x) ((x) = 0) +#endif /* defined GNUC_or_lint */ +#ifndef GNUC_or_lint +#define INITIALIZE(x) +#endif /* !defined GNUC_or_lint */ +#endif /* !defined INITIALIZE */ + +/* +** For the benefit of GNU folk... +** `_(MSGID)' uses the current locale's message library string for MSGID. +** The default is to use gettext if available, and use MSGID otherwise. +*/ + +#ifndef _ +#if HAVE_GETTEXT +#define _(msgid) gettext(msgid) +#else /* !HAVE_GETTEXT */ +#define _(msgid) msgid +#endif /* !HAVE_GETTEXT */ +#endif /* !defined _ */ + +#ifndef TZ_DOMAIN +#define TZ_DOMAIN "tz" +#endif /* !defined TZ_DOMAIN */ + +#if HAVE_INCOMPATIBLE_CTIME_R +#undef asctime_r +#undef ctime_r +char *asctime_r P((struct tm const *, char *)); +char *ctime_r P((time_t const *, char *)); +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +#ifndef YEARSPERREPEAT +#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ +#endif /* !defined YEARSPERREPEAT */ + +/* +** The Gregorian year averages 365.2425 days, which is 31556952 seconds. +*/ + +#ifndef AVGSECSPERYEAR +#define AVGSECSPERYEAR 31556952L +#endif /* !defined AVGSECSPERYEAR */ + +#ifndef SECSPERREPEAT +#define SECSPERREPEAT ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR) +#endif /* !defined SECSPERREPEAT */ + +#ifndef SECSPERREPEAT_BITS +#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ +#endif /* !defined SECSPERREPEAT_BITS */ + +/* +** UNIX was a registered trademark of The Open Group in 2003. +*/ + +#endif /* !defined PRIVATE_H */ diff --git a/libcutils/process_name.c b/libcutils/process_name.c new file mode 100644 index 0000000..b235429 --- /dev/null +++ b/libcutils/process_name.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> +#include <cutils/process_name.h> +#include <cutils/properties.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#if defined(HAVE_PRCTL) +#include <sys/prctl.h> +#endif + +#define PROCESS_NAME_DEVICE "/sys/qemu_trace/process_name" + +static const char* process_name = "unknown"; +static int running_in_emulator = -1; + +void set_process_name(const char* new_name) { + char propBuf[PROPERTY_VALUE_MAX]; + + if (new_name == NULL) { + return; + } + + // We never free the old name. Someone else could be using it. + int len = strlen(new_name); + char* copy = (char*) malloc(len + 1); + strcpy(copy, new_name); + process_name = (const char*) copy; + +#if defined(HAVE_PRCTL) + if (len < 16) { + prctl(PR_SET_NAME, (unsigned long) new_name, 0, 0, 0); + } else { + prctl(PR_SET_NAME, (unsigned long) new_name + len - 15, 0, 0, 0); + } +#endif + + // If we know we are not running in the emulator, then return. + if (running_in_emulator == 0) { + return; + } + + // If the "running_in_emulator" variable has not been initialized, + // then do it now. + if (running_in_emulator == -1) { + property_get("ro.kernel.qemu", propBuf, ""); + if (propBuf[0] == '1') { + running_in_emulator = 1; + } else { + running_in_emulator = 0; + return; + } + } + + // If the emulator was started with the "-trace file" command line option + // then we want to record the process name in the trace even if we are + // not currently tracing instructions (so that we will know the process + // name when we do start tracing instructions). We do not need to execute + // this code if we are just running in the emulator without the "-trace" + // command line option, but we don't know that here and this function + // isn't called frequently enough to bother optimizing that case. + int fd = open(PROCESS_NAME_DEVICE, O_RDWR); + if (fd < 0) + return; + write(fd, process_name, strlen(process_name) + 1); + close(fd); +} + +const char* get_process_name(void) { + return process_name; +} diff --git a/libcutils/properties.c b/libcutils/properties.c new file mode 100644 index 0000000..f90147d --- /dev/null +++ b/libcutils/properties.c @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "properties" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <cutils/sockets.h> +#include <errno.h> +#include <assert.h> + +#include <cutils/properties.h> +#include "loghack.h" + +#ifdef HAVE_LIBC_SYSTEM_PROPERTIES + +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include <sys/_system_properties.h> + +static int send_prop_msg(prop_msg *msg, int sync) +{ + int s; + int r; + int dummy; + + s = socket_local_client(PROP_SERVICE_NAME, + ANDROID_SOCKET_NAMESPACE_RESERVED, + SOCK_STREAM); + if(s < 0) return -1; + + while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) { + if((errno == EINTR) || (errno == EAGAIN)) continue; + break; + } + + if(r == sizeof(prop_msg)) { + r = 0; + } else { + r = -1; + } + + if(sync == 1) + { + // wait for server side to close + if(recv(s, &dummy, sizeof(dummy), 0) == 0) { + } else { + // LOGW("unexpected return code %d:%s", errno, strerror(errno)); + } + } + + close(s); + return r; +} + +static int property_set_internal(const char *key, const char *value, int sync) +{ + prop_msg msg; + unsigned resp; + + if(key == 0) return -1; + if(value == 0) value = ""; + + if(strlen(key) >= PROP_NAME_MAX) return -1; + if(strlen(value) >= PROP_VALUE_MAX) return -1; + + if(sync) { + msg.cmd = PROP_MSG_SETPROP_SYNC; + } else { + msg.cmd = PROP_MSG_SETPROP; + } + strcpy((char*) msg.name, key); + strcpy((char*) msg.value, value); + + return send_prop_msg(&msg, sync); +} + +int property_set(const char *key, const char *value) +{ + return property_set_internal(key, value, 0); +} + +int property_set_sync(const char *key, const char *value) +{ + return property_set_internal(key, value, 1); +} + +int property_get(const char *key, char *value, const char *default_value) +{ + int len; + + len = __system_property_get(key, value); + if(len > 0) { + return len; + } + + if(default_value) { + len = strlen(default_value); + memcpy(value, default_value, len + 1); + } + return len; +} + +int property_list(void (*propfn)(const char *key, const char *value, void *cookie), + void *cookie) +{ + char name[PROP_NAME_MAX]; + char value[PROP_VALUE_MAX]; + const prop_info *pi; + unsigned n; + + for(n = 0; (pi = __system_property_find_nth(n)); n++) { + __system_property_read(pi, name, value); + propfn(name, value, cookie); + } + return 0; +} + +#elif defined(HAVE_SYSTEM_PROPERTY_SERVER) + +/* + * The Linux simulator provides a "system property server" that uses IPC + * to set/get/list properties. The file descriptor is shared by all + * threads in the process, so we use a mutex to ensure that requests + * from multiple threads don't get interleaved. + */ +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <pthread.h> + +static pthread_once_t gInitOnce = PTHREAD_ONCE_INIT; +static pthread_mutex_t gPropertyFdLock = PTHREAD_MUTEX_INITIALIZER; +static int gPropFd = -1; + +/* + * Connect to the properties server. + * + * Returns the socket descriptor on success. + */ +static int connectToServer(const char* fileName) +{ + int sock = -1; + int cc; + + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + LOGW("UNIX domain socket create failed (errno=%d)\n", errno); + return -1; + } + + /* connect to socket; fails if file doesn't exist */ + strcpy(addr.sun_path, fileName); // max 108 bytes + addr.sun_family = AF_UNIX; + cc = connect(sock, (struct sockaddr*) &addr, SUN_LEN(&addr)); + if (cc < 0) { + // ENOENT means socket file doesn't exist + // ECONNREFUSED means socket exists but nobody is listening + //LOGW("AF_UNIX connect failed for '%s': %s\n", + // fileName, strerror(errno)); + close(sock); + return -1; + } + + return sock; +} + +/* + * Perform one-time initialization. + */ +static void init(void) +{ + assert(gPropFd == -1); + + gPropFd = connectToServer(SYSTEM_PROPERTY_PIPE_NAME); + if (gPropFd < 0) { + //LOGW("not connected to system property server\n"); + } else { + //LOGV("Connected to system property server\n"); + } +} + +int property_get(const char *key, char *value, const char *default_value) +{ + char sendBuf[1+PROPERTY_KEY_MAX]; + char recvBuf[1+PROPERTY_VALUE_MAX]; + int len = -1; + + //LOGV("PROPERTY GET [%s]\n", key); + + pthread_once(&gInitOnce, init); + if (gPropFd < 0) { + /* this mimics the behavior of the device implementation */ + if (default_value != NULL) { + strcpy(value, default_value); + len = strlen(value); + } + return len; + } + + if (strlen(key) >= PROPERTY_KEY_MAX) return -1; + + memset(sendBuf, 0xdd, sizeof(sendBuf)); // placate valgrind + + sendBuf[0] = (char) kSystemPropertyGet; + strcpy(sendBuf+1, key); + + pthread_mutex_lock(&gPropertyFdLock); + if (write(gPropFd, sendBuf, sizeof(sendBuf)) != sizeof(sendBuf)) { + pthread_mutex_unlock(&gPropertyFdLock); + return -1; + } + if (read(gPropFd, recvBuf, sizeof(recvBuf)) != sizeof(recvBuf)) { + pthread_mutex_unlock(&gPropertyFdLock); + return -1; + } + pthread_mutex_unlock(&gPropertyFdLock); + + /* first byte is 0 if value not defined, 1 if found */ + if (recvBuf[0] == 0) { + if (default_value != NULL) { + strcpy(value, default_value); + len = strlen(value); + } else { + /* + * If the value isn't defined, hand back an empty string and + * a zero length, rather than a failure. This seems wrong, + * since you can't tell the difference between "undefined" and + * "defined but empty", but it's what the device does. + */ + value[0] = '\0'; + len = 0; + } + } else if (recvBuf[0] == 1) { + strcpy(value, recvBuf+1); + len = strlen(value); + } else { + LOGE("Got strange response to property_get request (%d)\n", + recvBuf[0]); + assert(0); + return -1; + } + //LOGV("PROP [found=%d def='%s'] (%d) [%s]: [%s]\n", + // recvBuf[0], default_value, len, key, value); + + return len; +} + + +int property_set(const char *key, const char *value) +{ + char sendBuf[1+PROPERTY_KEY_MAX+PROPERTY_VALUE_MAX]; + char recvBuf[1]; + int result = -1; + + //LOGV("PROPERTY SET [%s]: [%s]\n", key, value); + + pthread_once(&gInitOnce, init); + if (gPropFd < 0) + return -1; + + if (strlen(key) >= PROPERTY_KEY_MAX) return -1; + if (strlen(value) >= PROPERTY_VALUE_MAX) return -1; + + memset(sendBuf, 0xdd, sizeof(sendBuf)); // placate valgrind + + sendBuf[0] = (char) kSystemPropertySet; + strcpy(sendBuf+1, key); + strcpy(sendBuf+1+PROPERTY_KEY_MAX, value); + + pthread_mutex_lock(&gPropertyFdLock); + if (write(gPropFd, sendBuf, sizeof(sendBuf)) != sizeof(sendBuf)) { + pthread_mutex_unlock(&gPropertyFdLock); + return -1; + } + if (read(gPropFd, recvBuf, sizeof(recvBuf)) != sizeof(recvBuf)) { + pthread_mutex_unlock(&gPropertyFdLock); + return -1; + } + pthread_mutex_unlock(&gPropertyFdLock); + + if (recvBuf[0] != 1) + return -1; + return 0; +} + +int property_list(void (*propfn)(const char *key, const char *value, void *cookie), + void *cookie) +{ + //LOGV("PROPERTY LIST\n"); + pthread_once(&gInitOnce, init); + if (gPropFd < 0) + return -1; + + return 0; +} + +#else + +/* SUPER-cheesy place-holder implementation for Win32 */ + +#include <cutils/threads.h> + +static mutex_t env_lock = MUTEX_INITIALIZER; + +int property_get(const char *key, char *value, const char *default_value) +{ + char ename[PROPERTY_KEY_MAX + 6]; + char *p; + int len; + + len = strlen(key); + if(len >= PROPERTY_KEY_MAX) return -1; + memcpy(ename, "PROP_", 5); + memcpy(ename + 5, key, len + 1); + + mutex_lock(&env_lock); + + p = getenv(ename); + if(p == 0) p = ""; + len = strlen(p); + if(len >= PROPERTY_VALUE_MAX) { + len = PROPERTY_VALUE_MAX - 1; + } + + if((len == 0) && default_value) { + len = strlen(default_value); + memcpy(value, default_value, len + 1); + } else { + memcpy(value, p, len); + value[len] = 0; + } + + mutex_unlock(&env_lock); + + return len; +} + + +int property_set(const char *key, const char *value) +{ + char ename[PROPERTY_KEY_MAX + 6]; + char *p; + int len; + int r; + + if(strlen(value) >= PROPERTY_VALUE_MAX) return -1; + + len = strlen(key); + if(len >= PROPERTY_KEY_MAX) return -1; + memcpy(ename, "PROP_", 5); + memcpy(ename + 5, key, len + 1); + + mutex_lock(&env_lock); +#ifdef HAVE_MS_C_RUNTIME + { + char temp[256]; + snprintf( temp, sizeof(temp), "%s=%s", ename, value); + putenv(temp); + r = 0; + } +#else + r = setenv(ename, value, 1); +#endif + mutex_unlock(&env_lock); + + return r; +} + +int property_list(void (*propfn)(const char *key, const char *value, void *cookie), + void *cookie) +{ + return 0; +} + +#endif diff --git a/libcutils/record_stream.c b/libcutils/record_stream.c new file mode 100644 index 0000000..274423b --- /dev/null +++ b/libcutils/record_stream.c @@ -0,0 +1,186 @@ +/* libs/cutils/record_stream.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> +#include <cutils/record_stream.h> +#include <string.h> +#include <stdint.h> +#ifdef HAVE_WINSOCK +#include <winsock2.h> /* for ntohl */ +#else +#include <netinet/in.h> +#endif + +#define HEADER_SIZE 4 + +struct RecordStream { + int fd; + size_t maxRecordLen; + + unsigned char *buffer; + + unsigned char *unconsumed; + unsigned char *read_end; + unsigned char *buffer_end; +}; + + +extern RecordStream *record_stream_new(int fd, size_t maxRecordLen) +{ + RecordStream *ret; + + assert (maxRecordLen <= 0xffff); + + ret = (RecordStream *)calloc(1, sizeof(RecordStream)); + + ret->fd = fd; + ret->maxRecordLen = maxRecordLen; + ret->buffer = (unsigned char *)malloc (maxRecordLen + HEADER_SIZE); + + ret->unconsumed = ret->buffer; + ret->read_end = ret->buffer; + ret->buffer_end = ret->buffer + maxRecordLen + HEADER_SIZE; + + return ret; +} + + +extern void record_stream_free(RecordStream *rs) +{ + free(rs->buffer); + free(rs); +} + + +/* returns NULL; if there isn't a full record in the buffer */ +static unsigned char * getEndOfRecord (unsigned char *p_begin, + unsigned char *p_end) +{ + size_t len; + unsigned char * p_ret; + + if (p_end < p_begin + HEADER_SIZE) { + return NULL; + } + + //First four bytes are length + len = ntohl(*((uint32_t *)p_begin)); + + p_ret = p_begin + HEADER_SIZE + len; + + if (p_end < p_ret) { + return NULL; + } + + return p_ret; +} + +static void *getNextRecord (RecordStream *p_rs, size_t *p_outRecordLen) +{ + unsigned char *record_start, *record_end; + + record_end = getEndOfRecord (p_rs->unconsumed, p_rs->read_end); + + if (record_end != NULL) { + /* one full line in the buffer */ + record_start = p_rs->unconsumed + HEADER_SIZE; + p_rs->unconsumed = record_end; + + *p_outRecordLen = record_end - record_start; + + return record_start; + } + + return NULL; +} + +/** + * Reads the next record from stream fd + * Records are prefixed by a 16-bit big endian length value + * Records may not be larger than maxRecordLen + * + * Doesn't guard against EINTR + * + * p_outRecord and p_outRecordLen may not be NULL + * + * Return 0 on success, -1 on fail + * Returns 0 with *p_outRecord set to NULL on end of stream + * Returns -1 / errno = EAGAIN if it needs to read again + */ +int record_stream_get_next (RecordStream *p_rs, void ** p_outRecord, + size_t *p_outRecordLen) +{ + void *ret; + + ssize_t countRead; + + /* is there one record already in the buffer? */ + ret = getNextRecord (p_rs, p_outRecordLen); + + if (ret != NULL) { + *p_outRecord = ret; + return 0; + } + + // if the buffer is full and we don't have a full record + if (p_rs->unconsumed == p_rs->buffer + && p_rs->read_end == p_rs->buffer_end + ) { + // this should never happen + //LOGE("max record length exceeded\n"); + assert (0); + errno = EFBIG; + return -1; + } + + if (p_rs->unconsumed != p_rs->buffer) { + // move remainder to the beginning of the buffer + size_t toMove; + + toMove = p_rs->read_end - p_rs->unconsumed; + if (toMove) { + memmove(p_rs->buffer, p_rs->unconsumed, toMove); + } + + p_rs->read_end = p_rs->buffer + toMove; + p_rs->unconsumed = p_rs->buffer; + } + + countRead = read (p_rs->fd, p_rs->read_end, p_rs->buffer_end - p_rs->read_end); + + if (countRead <= 0) { + /* note: end-of-stream drops through here too */ + *p_outRecord = NULL; + return countRead; + } + + p_rs->read_end += countRead; + + ret = getNextRecord (p_rs, p_outRecordLen); + + if (ret == NULL) { + /* not enough of a buffer to for a whole command */ + errno = EAGAIN; + return -1; + } + + *p_outRecord = ret; + return 0; +} diff --git a/libcutils/sched_policy.c b/libcutils/sched_policy.c new file mode 100644 index 0000000..f9c111e --- /dev/null +++ b/libcutils/sched_policy.c @@ -0,0 +1,277 @@ + +/* libs/cutils/sched_policy.c +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#define LOG_TAG "SchedPolicy" +#include "cutils/log.h" + +#ifdef HAVE_SCHED_H +#ifdef HAVE_PTHREADS + +#include <sched.h> +#include <pthread.h> + +#include <cutils/sched_policy.h> + +#ifndef SCHED_NORMAL + #define SCHED_NORMAL 0 +#endif + +#ifndef SCHED_BATCH + #define SCHED_BATCH 3 +#endif + +#define POLICY_DEBUG 0 + +static pthread_once_t the_once = PTHREAD_ONCE_INIT; + +static int __sys_supports_schedgroups = -1; + +// File descriptors open to /dev/cpuctl/../tasks, setup by initialize, or -1 on error. +static int normal_cgroup_fd = -1; +static int bg_cgroup_fd = -1; + +/* Add tid to the scheduling group defined by the policy */ +static int add_tid_to_cgroup(int tid, SchedPolicy policy) +{ + int fd; + + if (policy == SP_BACKGROUND) { + fd = bg_cgroup_fd; + } else { + fd = normal_cgroup_fd; + } + + if (fd < 0) { + SLOGE("add_tid_to_cgroup failed; background=%d\n", + policy == SP_BACKGROUND ? 1 : 0); + return -1; + } + + // specialized itoa -- works for tid > 0 + char text[22]; + char *end = text + sizeof(text) - 1; + char *ptr = end; + *ptr = '\0'; + while (tid > 0) { + *--ptr = '0' + (tid % 10); + tid = tid / 10; + } + + if (write(fd, ptr, end - ptr) < 0) { + /* + * If the thread is in the process of exiting, + * don't flag an error + */ + if (errno == ESRCH) + return 0; + SLOGW("add_tid_to_cgroup failed to write '%s' (%s); background=%d\n", + ptr, strerror(errno), policy == SP_BACKGROUND ? 1 : 0); + return -1; + } + + return 0; +} + +static void __initialize(void) { + char* filename; + if (!access("/dev/cpuctl/tasks", F_OK)) { + __sys_supports_schedgroups = 1; + + filename = "/dev/cpuctl/tasks"; + normal_cgroup_fd = open(filename, O_WRONLY); + if (normal_cgroup_fd < 0) { + SLOGE("open of %s failed: %s\n", filename, strerror(errno)); + } + + filename = "/dev/cpuctl/bg_non_interactive/tasks"; + bg_cgroup_fd = open(filename, O_WRONLY); + if (bg_cgroup_fd < 0) { + SLOGE("open of %s failed: %s\n", filename, strerror(errno)); + } + } else { + __sys_supports_schedgroups = 0; + } +} + +/* + * Try to get the scheduler group. + * + * The data from /proc/<pid>/cgroup looks (something) like: + * 2:cpu:/bg_non_interactive + * 1:cpuacct:/ + * + * We return the part after the "/", which will be an empty string for + * the default cgroup. If the string is longer than "bufLen", the string + * will be truncated. + */ +static int getSchedulerGroup(int tid, char* buf, size_t bufLen) +{ +#ifdef HAVE_ANDROID_OS + char pathBuf[32]; + char lineBuf[256]; + FILE *fp; + + snprintf(pathBuf, sizeof(pathBuf), "/proc/%d/cgroup", tid); + if (!(fp = fopen(pathBuf, "r"))) { + return -1; + } + + while(fgets(lineBuf, sizeof(lineBuf) -1, fp)) { + char *next = lineBuf; + char *subsys; + char *grp; + size_t len; + + /* Junk the first field */ + if (!strsep(&next, ":")) { + goto out_bad_data; + } + + if (!(subsys = strsep(&next, ":"))) { + goto out_bad_data; + } + + if (strcmp(subsys, "cpu")) { + /* Not the subsys we're looking for */ + continue; + } + + if (!(grp = strsep(&next, ":"))) { + goto out_bad_data; + } + grp++; /* Drop the leading '/' */ + len = strlen(grp); + grp[len-1] = '\0'; /* Drop the trailing '\n' */ + + if (bufLen <= len) { + len = bufLen - 1; + } + strncpy(buf, grp, len); + buf[len] = '\0'; + fclose(fp); + return 0; + } + + SLOGE("Failed to find cpu subsys"); + fclose(fp); + return -1; + out_bad_data: + SLOGE("Bad cgroup data {%s}", lineBuf); + fclose(fp); + return -1; +#else + errno = ENOSYS; + return -1; +#endif +} + +int get_sched_policy(int tid, SchedPolicy *policy) +{ + pthread_once(&the_once, __initialize); + + if (__sys_supports_schedgroups) { + char grpBuf[32]; + if (getSchedulerGroup(tid, grpBuf, sizeof(grpBuf)) < 0) + return -1; + if (grpBuf[0] == '\0') { + *policy = SP_FOREGROUND; + } else if (!strcmp(grpBuf, "bg_non_interactive")) { + *policy = SP_BACKGROUND; + } else { + errno = ERANGE; + return -1; + } + } else { + int rc = sched_getscheduler(tid); + if (rc < 0) + return -1; + else if (rc == SCHED_NORMAL) + *policy = SP_FOREGROUND; + else if (rc == SCHED_BATCH) + *policy = SP_BACKGROUND; + else { + errno = ERANGE; + return -1; + } + } + return 0; +} + +int set_sched_policy(int tid, SchedPolicy policy) +{ + pthread_once(&the_once, __initialize); + +#if POLICY_DEBUG + char statfile[64]; + char statline[1024]; + char thread_name[255]; + int fd; + + sprintf(statfile, "/proc/%d/stat", tid); + memset(thread_name, 0, sizeof(thread_name)); + + fd = open(statfile, O_RDONLY); + if (fd >= 0) { + int rc = read(fd, statline, 1023); + close(fd); + statline[rc] = 0; + char *p = statline; + char *q; + + for (p = statline; *p != '('; p++); + p++; + for (q = p; *q != ')'; q++); + + strncpy(thread_name, p, (q-p)); + } + if (policy == SP_BACKGROUND) { + SLOGD("vvv tid %d (%s)", tid, thread_name); + } else if (policy == SP_FOREGROUND) { + SLOGD("^^^ tid %d (%s)", tid, thread_name); + } else { + SLOGD("??? tid %d (%s)", tid, thread_name); + } +#endif + + if (__sys_supports_schedgroups) { + if (add_tid_to_cgroup(tid, policy)) { + if (errno != ESRCH && errno != ENOENT) + return -errno; + } + } else { + struct sched_param param; + + param.sched_priority = 0; + sched_setscheduler(tid, + (policy == SP_BACKGROUND) ? + SCHED_BATCH : SCHED_NORMAL, + ¶m); + } + + return 0; +} + +#endif /* HAVE_PTHREADS */ +#endif /* HAVE_SCHED_H */ diff --git a/libcutils/selector.c b/libcutils/selector.c new file mode 100644 index 0000000..9436393 --- /dev/null +++ b/libcutils/selector.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "selector" + +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> + +#include <cutils/array.h> +#include <cutils/selector.h> + +#include "loghack.h" + +struct Selector { + Array* selectableFds; + bool looping; + fd_set readFds; + fd_set writeFds; + fd_set exceptFds; + int maxFd; + int wakeupPipe[2]; + SelectableFd* wakeupFd; + + bool inSelect; + pthread_mutex_t inSelectLock; +}; + +/** Reads and ignores wake up data. */ +static void eatWakeupData(SelectableFd* wakeupFd) { + static char garbage[64]; + if (read(wakeupFd->fd, garbage, sizeof(garbage)) < 0) { + if (errno == EINTR) { + LOGI("read() interrupted."); + } else { + LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno)); + } + } +} + +static void setInSelect(Selector* selector, bool inSelect) { + pthread_mutex_lock(&selector->inSelectLock); + selector->inSelect = inSelect; + pthread_mutex_unlock(&selector->inSelectLock); +} + +static bool isInSelect(Selector* selector) { + pthread_mutex_lock(&selector->inSelectLock); + bool inSelect = selector->inSelect; + pthread_mutex_unlock(&selector->inSelectLock); + return inSelect; +} + +void selectorWakeUp(Selector* selector) { + if (!isInSelect(selector)) { + // We only need to write wake-up data if we're blocked in select(). + return; + } + + static char garbage[1]; + if (write(selector->wakeupPipe[1], garbage, sizeof(garbage)) < 0) { + if (errno == EINTR) { + LOGI("read() interrupted."); + } else { + LOG_ALWAYS_FATAL("This should never happen: %s", strerror(errno)); + } + } +} + +Selector* selectorCreate(void) { + Selector* selector = calloc(1, sizeof(Selector)); + if (selector == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + selector->selectableFds = arrayCreate(); + + // Set up wake-up pipe. + if (pipe(selector->wakeupPipe) < 0) { + LOG_ALWAYS_FATAL("pipe() error: %s", strerror(errno)); + } + + LOGD("Wakeup fd: %d", selector->wakeupPipe[0]); + + SelectableFd* wakeupFd = selectorAdd(selector, selector->wakeupPipe[0]); + if (wakeupFd == NULL) { + LOG_ALWAYS_FATAL("malloc() error."); + } + wakeupFd->onReadable = &eatWakeupData; + + pthread_mutex_init(&selector->inSelectLock, NULL); + + return selector; +} + +SelectableFd* selectorAdd(Selector* selector, int fd) { + assert(selector != NULL); + + SelectableFd* selectableFd = calloc(1, sizeof(SelectableFd)); + if (selectableFd != NULL) { + selectableFd->selector = selector; + selectableFd->fd = fd; + + arrayAdd(selector->selectableFds, selectableFd); + } + + return selectableFd; +} + +/** + * Adds an fd to the given set if the callback is non-null. Returns true + * if the fd was added. + */ +static inline bool maybeAdd(SelectableFd* selectableFd, + void (*callback)(SelectableFd*), fd_set* fdSet) { + if (callback != NULL) { + FD_SET(selectableFd->fd, fdSet); + return true; + } + return false; +} + +/** + * Removes stale file descriptors and initializes file descriptor sets. + */ +static void prepareForSelect(Selector* selector) { + fd_set* exceptFds = &selector->exceptFds; + fd_set* readFds = &selector->readFds; + fd_set* writeFds = &selector->writeFds; + + FD_ZERO(exceptFds); + FD_ZERO(readFds); + FD_ZERO(writeFds); + + Array* selectableFds = selector->selectableFds; + int i = 0; + selector->maxFd = 0; + int size = arraySize(selectableFds); + while (i < size) { + SelectableFd* selectableFd = arrayGet(selectableFds, i); + if (selectableFd->remove) { + // This descriptor should be removed. + arrayRemove(selectableFds, i); + size--; + if (selectableFd->onRemove != NULL) { + selectableFd->onRemove(selectableFd); + } + free(selectableFd); + } else { + if (selectableFd->beforeSelect != NULL) { + selectableFd->beforeSelect(selectableFd); + } + + bool inSet = false; + if (maybeAdd(selectableFd, selectableFd->onExcept, exceptFds)) { + LOGD("Selecting fd %d for writing...", selectableFd->fd); + inSet = true; + } + if (maybeAdd(selectableFd, selectableFd->onReadable, readFds)) { + LOGD("Selecting fd %d for reading...", selectableFd->fd); + inSet = true; + } + if (maybeAdd(selectableFd, selectableFd->onWritable, writeFds)) { + inSet = true; + } + + if (inSet) { + // If the fd is in a set, check it against max. + int fd = selectableFd->fd; + if (fd > selector->maxFd) { + selector->maxFd = fd; + } + } + + // Move to next descriptor. + i++; + } + } +} + +/** + * Invokes a callback if the callback is non-null and the fd is in the given + * set. + */ +static inline void maybeInvoke(SelectableFd* selectableFd, + void (*callback)(SelectableFd*), fd_set* fdSet) { + if (callback != NULL && !selectableFd->remove && + FD_ISSET(selectableFd->fd, fdSet)) { + LOGD("Selected fd %d.", selectableFd->fd); + callback(selectableFd); + } +} + +/** + * Notifies user if file descriptors are readable or writable, or if + * out-of-band data is present. + */ +static void fireEvents(Selector* selector) { + Array* selectableFds = selector->selectableFds; + int size = arraySize(selectableFds); + int i; + for (i = 0; i < size; i++) { + SelectableFd* selectableFd = arrayGet(selectableFds, i); + maybeInvoke(selectableFd, selectableFd->onExcept, + &selector->exceptFds); + maybeInvoke(selectableFd, selectableFd->onReadable, + &selector->readFds); + maybeInvoke(selectableFd, selectableFd->onWritable, + &selector->writeFds); + } +} + +void selectorLoop(Selector* selector) { + // Make sure we're not already looping. + if (selector->looping) { + LOG_ALWAYS_FATAL("Already looping."); + } + selector->looping = true; + + while (true) { + setInSelect(selector, true); + + prepareForSelect(selector); + + LOGD("Entering select()."); + + // Select file descriptors. + int result = select(selector->maxFd + 1, &selector->readFds, + &selector->writeFds, &selector->exceptFds, NULL); + + LOGD("Exiting select()."); + + setInSelect(selector, false); + + if (result == -1) { + // Abort on everything except EINTR. + if (errno == EINTR) { + LOGI("select() interrupted."); + } else { + LOG_ALWAYS_FATAL("select() error: %s", + strerror(errno)); + } + } else if (result > 0) { + fireEvents(selector); + } + } +} diff --git a/libcutils/socket_inaddr_any_server.c b/libcutils/socket_inaddr_any_server.c new file mode 100644 index 0000000..7d5dab4 --- /dev/null +++ b/libcutils/socket_inaddr_any_server.c @@ -0,0 +1,70 @@ +/* libs/cutils/socket_inaddr_any_server.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <cutils/sockets.h> + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> + +#ifndef HAVE_WINSOCK +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/types.h> +#include <netinet/in.h> +#endif + +#define LISTEN_BACKLOG 4 + +/* open listen() port on any interface */ +int socket_inaddr_any_server(int port, int type) +{ + struct sockaddr_in addr; + size_t alen; + int s, n; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + s = socket(AF_INET, type, 0); + if(s < 0) return -1; + + n = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); + + if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + if (type == SOCK_STREAM) { + int ret; + + ret = listen(s, LISTEN_BACKLOG); + + if (ret < 0) { + close(s); + return -1; + } + } + + return s; +} diff --git a/libcutils/socket_local.h b/libcutils/socket_local.h new file mode 100644 index 0000000..45b9856 --- /dev/null +++ b/libcutils/socket_local.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SOCKET_LOCAL_H +#define __SOCKET_LOCAL_H + +#define FILESYSTEM_SOCKET_PREFIX "/tmp/" +#define ANDROID_RESERVED_SOCKET_PREFIX "/dev/socket/" + +/* + * Set up a given sockaddr_un, to have it refer to the given + * name in the given namespace. The namespace must be one + * of <code>ANDROID_SOCKET_NAMESPACE_ABSTRACT</code>, + * <code>ANDROID_SOCKET_NAMESPACE_RESERVED</code>, or + * <code>ANDROID_SOCKET_NAMESPACE_FILESYSTEM</code>. Upon success, + * the pointed at sockaddr_un is filled in and the pointed at + * socklen_t is set to indicate the final length. This function + * will fail if the namespace is invalid (not one of the indicated + * constants) or if the name is too long. + * + * @return 0 on success or -1 on failure + */ +int socket_make_sockaddr_un(const char *name, int namespaceId, + struct sockaddr_un *p_addr, socklen_t *alen); + +#endif diff --git a/libcutils/socket_local_client.c b/libcutils/socket_local_client.c new file mode 100644 index 0000000..036ce2e --- /dev/null +++ b/libcutils/socket_local_client.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/sockets.h> + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> + +#ifdef HAVE_WINSOCK + +int socket_local_client(const char *name, int namespaceId, int type) +{ + errno = ENOSYS; + return -1; +} + +#else /* !HAVE_WINSOCK */ + +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/select.h> +#include <sys/types.h> + +#include "socket_local.h" + +#define LISTEN_BACKLOG 4 + +/* Documented in header file. */ +int socket_make_sockaddr_un(const char *name, int namespaceId, + struct sockaddr_un *p_addr, socklen_t *alen) +{ + memset (p_addr, 0, sizeof (*p_addr)); + size_t namelen; + + switch (namespaceId) { + case ANDROID_SOCKET_NAMESPACE_ABSTRACT: +#ifdef HAVE_LINUX_LOCAL_SOCKET_NAMESPACE + namelen = strlen(name); + + // Test with length +1 for the *initial* '\0'. + if ((namelen + 1) > sizeof(p_addr->sun_path)) { + goto error; + } + + /* + * Note: The path in this case is *not* supposed to be + * '\0'-terminated. ("man 7 unix" for the gory details.) + */ + + p_addr->sun_path[0] = 0; + memcpy(p_addr->sun_path + 1, name, namelen); +#else /*HAVE_LINUX_LOCAL_SOCKET_NAMESPACE*/ + /* this OS doesn't have the Linux abstract namespace */ + + namelen = strlen(name) + strlen(FILESYSTEM_SOCKET_PREFIX); + /* unix_path_max appears to be missing on linux */ + if (namelen > sizeof(*p_addr) + - offsetof(struct sockaddr_un, sun_path) - 1) { + goto error; + } + + strcpy(p_addr->sun_path, FILESYSTEM_SOCKET_PREFIX); + strcat(p_addr->sun_path, name); +#endif /*HAVE_LINUX_LOCAL_SOCKET_NAMESPACE*/ + break; + + case ANDROID_SOCKET_NAMESPACE_RESERVED: + namelen = strlen(name) + strlen(ANDROID_RESERVED_SOCKET_PREFIX); + /* unix_path_max appears to be missing on linux */ + if (namelen > sizeof(*p_addr) + - offsetof(struct sockaddr_un, sun_path) - 1) { + goto error; + } + + strcpy(p_addr->sun_path, ANDROID_RESERVED_SOCKET_PREFIX); + strcat(p_addr->sun_path, name); + break; + + case ANDROID_SOCKET_NAMESPACE_FILESYSTEM: + namelen = strlen(name); + /* unix_path_max appears to be missing on linux */ + if (namelen > sizeof(*p_addr) + - offsetof(struct sockaddr_un, sun_path) - 1) { + goto error; + } + + strcpy(p_addr->sun_path, name); + break; + default: + // invalid namespace id + return -1; + } + + p_addr->sun_family = AF_LOCAL; + *alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1; + return 0; +error: + return -1; +} + +/** + * connect to peer named "name" on fd + * returns same fd or -1 on error. + * fd is not closed on error. that's your job. + * + * Used by AndroidSocketImpl + */ +int socket_local_client_connect(int fd, const char *name, int namespaceId, + int type) +{ + struct sockaddr_un addr; + socklen_t alen; + size_t namelen; + int err; + + err = socket_make_sockaddr_un(name, namespaceId, &addr, &alen); + + if (err < 0) { + goto error; + } + + if(connect(fd, (struct sockaddr *) &addr, alen) < 0) { + goto error; + } + + return fd; + +error: + return -1; +} + +/** + * connect to peer named "name" + * returns fd or -1 on error + */ +int socket_local_client(const char *name, int namespaceId, int type) +{ + int s; + + s = socket(AF_LOCAL, type, 0); + if(s < 0) return -1; + + if ( 0 > socket_local_client_connect(s, name, namespaceId, type)) { + close(s); + return -1; + } + + return s; +} + +#endif /* !HAVE_WINSOCK */ diff --git a/libcutils/socket_local_server.c b/libcutils/socket_local_server.c new file mode 100644 index 0000000..4971b1b --- /dev/null +++ b/libcutils/socket_local_server.c @@ -0,0 +1,124 @@ +/* libs/cutils/socket_local_server.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <cutils/sockets.h> + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> + +#ifdef HAVE_WINSOCK + +int socket_local_server(const char *name, int namespaceId, int type) +{ + errno = ENOSYS; + return -1; +} + +#else /* !HAVE_WINSOCK */ + +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/select.h> +#include <sys/types.h> +#include <netinet/in.h> + +#include "socket_local.h" + +#define LISTEN_BACKLOG 4 + + +/** + * Binds a pre-created socket(AF_LOCAL) 's' to 'name' + * returns 's' on success, -1 on fail + * + * Does not call listen() + */ +int socket_local_server_bind(int s, const char *name, int namespaceId) +{ + struct sockaddr_un addr; + socklen_t alen; + int n; + int err; + + err = socket_make_sockaddr_un(name, namespaceId, &addr, &alen); + + if (err < 0) { + return -1; + } + + /* basically: if this is a filesystem path, unlink first */ +#ifndef HAVE_LINUX_LOCAL_SOCKET_NAMESPACE + if (1) { +#else + if (namespaceId == ANDROID_SOCKET_NAMESPACE_RESERVED + || namespaceId == ANDROID_SOCKET_NAMESPACE_FILESYSTEM) { +#endif + /*ignore ENOENT*/ + unlink(addr.sun_path); + } + + n = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); + + if(bind(s, (struct sockaddr *) &addr, alen) < 0) { + return -1; + } + + return s; + +} + + +/** Open a server-side UNIX domain datagram socket in the Linux non-filesystem + * namespace + * + * Returns fd on success, -1 on fail + */ + +int socket_local_server(const char *name, int namespace, int type) +{ + int err; + int s; + + s = socket(AF_LOCAL, type, 0); + if (s < 0) return -1; + + err = socket_local_server_bind(s, name, namespace); + + if (err < 0) { + close(s); + return -1; + } + + if (type == SOCK_STREAM) { + int ret; + + ret = listen(s, LISTEN_BACKLOG); + + if (ret < 0) { + close(s); + return -1; + } + } + + return s; +} + +#endif /* !HAVE_WINSOCK */ diff --git a/libcutils/socket_loopback_client.c b/libcutils/socket_loopback_client.c new file mode 100644 index 0000000..cb82c5e --- /dev/null +++ b/libcutils/socket_loopback_client.c @@ -0,0 +1,59 @@ +/* libs/cutils/socket_loopback_client.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <cutils/sockets.h> + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> + +#ifndef HAVE_WINSOCK +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/types.h> +#include <netinet/in.h> +#endif + +/* Connect to port on the loopback IP interface. type is + * SOCK_STREAM or SOCK_DGRAM. + * return is a file descriptor or -1 on error + */ +int socket_loopback_client(int port, int type) +{ + struct sockaddr_in addr; + socklen_t alen; + int s; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket(AF_INET, type, 0); + if(s < 0) return -1; + + if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + return s; + +} + diff --git a/libcutils/socket_loopback_server.c b/libcutils/socket_loopback_server.c new file mode 100644 index 0000000..3208488 --- /dev/null +++ b/libcutils/socket_loopback_server.c @@ -0,0 +1,71 @@ +/* libs/cutils/socket_loopback_server.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <cutils/sockets.h> + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> + +#define LISTEN_BACKLOG 4 + +#ifndef HAVE_WINSOCK +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/types.h> +#include <netinet/in.h> +#endif + +/* open listen() port on loopback interface */ +int socket_loopback_server(int port, int type) +{ + struct sockaddr_in addr; + size_t alen; + int s, n; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + s = socket(AF_INET, type, 0); + if(s < 0) return -1; + + n = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); + + if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + if (type == SOCK_STREAM) { + int ret; + + ret = listen(s, LISTEN_BACKLOG); + + if (ret < 0) { + close(s); + return -1; + } + } + + return s; +} + diff --git a/libcutils/socket_network_client.c b/libcutils/socket_network_client.c new file mode 100644 index 0000000..a64006c --- /dev/null +++ b/libcutils/socket_network_client.c @@ -0,0 +1,65 @@ +/* libs/cutils/socket_network_client.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <cutils/sockets.h> + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> + +#ifndef HAVE_WINSOCK +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <netdb.h> +#endif + + +/* Connect to port on the IP interface. type is + * SOCK_STREAM or SOCK_DGRAM. + * return is a file descriptor or -1 on error + */ +int socket_network_client(const char *host, int port, int type) +{ + struct hostent *hp; + struct sockaddr_in addr; + socklen_t alen; + int s; + + hp = gethostbyname(host); + if(hp == 0) return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = hp->h_addrtype; + addr.sin_port = htons(port); + memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); + + s = socket(hp->h_addrtype, type, 0); + if(s < 0) return -1; + + if(connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + return s; + +} + diff --git a/libcutils/strdup16to8.c b/libcutils/strdup16to8.c new file mode 100644 index 0000000..1a8ba86 --- /dev/null +++ b/libcutils/strdup16to8.c @@ -0,0 +1,168 @@ +/* libs/cutils/strdup16to8.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <limits.h> /* for SIZE_MAX */ + +#include <cutils/jstring.h> +#include <assert.h> +#include <stdlib.h> + + +/** + * Given a UTF-16 string, compute the length of the corresponding UTF-8 + * string in bytes. + */ +extern size_t strnlen16to8(const char16_t* utf16Str, size_t len) +{ + size_t utf8Len = 0; + + /* A small note on integer overflow. The result can + * potentially be as big as 3*len, which will overflow + * for len > SIZE_MAX/3. + * + * Moreover, the result of a strnlen16to8 is typically used + * to allocate a destination buffer to strncpy16to8 which + * requires one more byte to terminate the UTF-8 copy, and + * this is generally done by careless users by incrementing + * the result without checking for integer overflows, e.g.: + * + * dst = malloc(strnlen16to8(utf16,len)+1) + * + * Due to this, the following code will try to detect + * overflows, and never return more than (SIZE_MAX-1) + * when it detects one. A careless user will try to malloc + * SIZE_MAX bytes, which will return NULL which can at least + * be detected appropriately. + * + * As far as I know, this function is only used by strndup16(), + * but better be safe than sorry. + */ + + /* Fast path for the usual case where 3*len is < SIZE_MAX-1. + */ + if (len < (SIZE_MAX-1)/3) { + while (len--) { + unsigned int uic = *utf16Str++; + + if (uic > 0x07ff) + utf8Len += 3; + else if (uic > 0x7f || uic == 0) + utf8Len += 2; + else + utf8Len++; + } + return utf8Len; + } + + /* The slower but paranoid version */ + while (len--) { + unsigned int uic = *utf16Str++; + size_t utf8Cur = utf8Len; + + if (uic > 0x07ff) + utf8Len += 3; + else if (uic > 0x7f || uic == 0) + utf8Len += 2; + else + utf8Len++; + + if (utf8Len < utf8Cur) /* overflow detected */ + return SIZE_MAX-1; + } + + /* don't return SIZE_MAX to avoid common user bug */ + if (utf8Len == SIZE_MAX) + utf8Len = SIZE_MAX-1; + + return utf8Len; +} + + +/** + * Convert a Java-Style UTF-16 string + length to a JNI-Style UTF-8 string. + * + * This basically means: embedded \0's in the UTF-16 string are encoded + * as "0xc0 0x80" + * + * Make sure you allocate "utf8Str" with the result of strlen16to8() + 1, + * not just "len". + * + * Please note, a terminated \0 is always added, so your result will always + * be "strlen16to8() + 1" bytes long. + */ +extern char* strncpy16to8(char* utf8Str, const char16_t* utf16Str, size_t len) +{ + char* utf8cur = utf8Str; + + /* Note on overflows: We assume the user did check the result of + * strnlen16to8() properly or at a minimum checked the result of + * its malloc(SIZE_MAX) in case of overflow. + */ + while (len--) { + unsigned int uic = *utf16Str++; + + if (uic > 0x07ff) { + *utf8cur++ = (uic >> 12) | 0xe0; + *utf8cur++ = ((uic >> 6) & 0x3f) | 0x80; + *utf8cur++ = (uic & 0x3f) | 0x80; + } else if (uic > 0x7f || uic == 0) { + *utf8cur++ = (uic >> 6) | 0xc0; + *utf8cur++ = (uic & 0x3f) | 0x80; + } else { + *utf8cur++ = uic; + + if (uic == 0) { + break; + } + } + } + + *utf8cur = '\0'; + + return utf8Str; +} + +/** + * Convert a UTF-16 string to UTF-8. + * + */ +char * strndup16to8 (const char16_t* s, size_t n) +{ + char* ret; + size_t len; + + if (s == NULL) { + return NULL; + } + + len = strnlen16to8(s, n); + + /* We are paranoid, and we check for SIZE_MAX-1 + * too since it is an overflow value for our + * strnlen16to8 implementation. + */ + if (len >= SIZE_MAX-1) + return NULL; + + ret = malloc(len + 1); + if (ret == NULL) + return NULL; + + strncpy16to8 (ret, s, n); + + return ret; +} diff --git a/libcutils/strdup8to16.c b/libcutils/strdup8to16.c new file mode 100644 index 0000000..63e5ca4 --- /dev/null +++ b/libcutils/strdup8to16.c @@ -0,0 +1,214 @@ +/* libs/cutils/strdup8to16.c +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include <cutils/jstring.h> +#include <assert.h> +#include <stdlib.h> +#include <limits.h> + +/* See http://www.unicode.org/reports/tr22/ for discussion + * on invalid sequences + */ + +#define UTF16_REPLACEMENT_CHAR 0xfffd + +/* Clever trick from Dianne that returns 1-4 depending on leading bit sequence*/ +#define UTF8_SEQ_LENGTH(ch) (((0xe5000000 >> ((ch >> 3) & 0x1e)) & 3) + 1) + +/* note: macro expands to multiple lines */ +#define UTF8_SHIFT_AND_MASK(unicode, byte) \ + (unicode)<<=6; (unicode) |= (0x3f & (byte)); + +#define UNICODE_UPPER_LIMIT 0x10fffd + +/** + * out_len is an out parameter (which may not be null) containing the + * length of the UTF-16 string (which may contain embedded \0's) + */ + +extern char16_t * strdup8to16 (const char* s, size_t *out_len) +{ + char16_t *ret; + size_t len; + + if (s == NULL) return NULL; + + len = strlen8to16(s); + + // fail on overflow + if (len && SIZE_MAX/len < sizeof(char16_t)) + return NULL; + + // no plus-one here. UTF-16 strings are not null terminated + ret = (char16_t *) malloc (sizeof(char16_t) * len); + + return strcpy8to16 (ret, s, out_len); +} + +/** + * Like "strlen", but for strings encoded with Java's modified UTF-8. + * + * The value returned is the number of UTF-16 characters required + * to represent this string. + */ +extern size_t strlen8to16 (const char* utf8Str) +{ + size_t len = 0; + int ic; + int expected = 0; + + while ((ic = *utf8Str++) != '\0') { + /* bytes that start 0? or 11 are lead bytes and count as characters.*/ + /* bytes that start 10 are extention bytes and are not counted */ + + if ((ic & 0xc0) == 0x80) { + /* count the 0x80 extention bytes. if we have more than + * expected, then start counting them because strcpy8to16 + * will insert UTF16_REPLACEMENT_CHAR's + */ + expected--; + if (expected < 0) { + len++; + } + } else { + len++; + expected = UTF8_SEQ_LENGTH(ic) - 1; + + /* this will result in a surrogate pair */ + if (expected == 3) { + len++; + } + } + } + + return len; +} + + + +/* + * Retrieve the next UTF-32 character from a UTF-8 string. + * + * Stops at inner \0's + * + * Returns UTF16_REPLACEMENT_CHAR if an invalid sequence is encountered + * + * Advances "*pUtf8Ptr" to the start of the next character. + */ +static inline uint32_t getUtf32FromUtf8(const char** pUtf8Ptr) +{ + uint32_t ret; + int seq_len; + int i; + + /* Mask for leader byte for lengths 1, 2, 3, and 4 respectively*/ + static const char leaderMask[4] = {0xff, 0x1f, 0x0f, 0x07}; + + /* Bytes that start with bits "10" are not leading characters. */ + if (((**pUtf8Ptr) & 0xc0) == 0x80) { + (*pUtf8Ptr)++; + return UTF16_REPLACEMENT_CHAR; + } + + /* note we tolerate invalid leader 11111xxx here */ + seq_len = UTF8_SEQ_LENGTH(**pUtf8Ptr); + + ret = (**pUtf8Ptr) & leaderMask [seq_len - 1]; + + if (**pUtf8Ptr == '\0') return ret; + + (*pUtf8Ptr)++; + for (i = 1; i < seq_len ; i++, (*pUtf8Ptr)++) { + if ((**pUtf8Ptr) == '\0') return UTF16_REPLACEMENT_CHAR; + if (((**pUtf8Ptr) & 0xc0) != 0x80) return UTF16_REPLACEMENT_CHAR; + + UTF8_SHIFT_AND_MASK(ret, **pUtf8Ptr); + } + + return ret; +} + + +/** + * out_len is an out parameter (which may not be null) containing the + * length of the UTF-16 string (which may contain embedded \0's) + */ + +extern char16_t * strcpy8to16 (char16_t *utf16Str, const char*utf8Str, + size_t *out_len) +{ + char16_t *dest = utf16Str; + + while (*utf8Str != '\0') { + uint32_t ret; + + ret = getUtf32FromUtf8(&utf8Str); + + if (ret <= 0xffff) { + *dest++ = (char16_t) ret; + } else if (ret <= UNICODE_UPPER_LIMIT) { + /* Create surrogate pairs */ + /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */ + + *dest++ = 0xd800 | ((ret - 0x10000) >> 10); + *dest++ = 0xdc00 | ((ret - 0x10000) & 0x3ff); + } else { + *dest++ = UTF16_REPLACEMENT_CHAR; + } + } + + *out_len = dest - utf16Str; + + return utf16Str; +} + +/** + * length is the number of characters in the UTF-8 string. + * out_len is an out parameter (which may not be null) containing the + * length of the UTF-16 string (which may contain embedded \0's) + */ + +extern char16_t * strcpylen8to16 (char16_t *utf16Str, const char*utf8Str, + int length, size_t *out_len) +{ + /* TODO: Share more of this code with the method above. Only 2 lines changed. */ + + char16_t *dest = utf16Str; + + const char *end = utf8Str + length; /* This line */ + while (utf8Str < end) { /* and this line changed. */ + uint32_t ret; + + ret = getUtf32FromUtf8(&utf8Str); + + if (ret <= 0xffff) { + *dest++ = (char16_t) ret; + } else if (ret <= UNICODE_UPPER_LIMIT) { + /* Create surrogate pairs */ + /* See http://en.wikipedia.org/wiki/UTF-16/UCS-2#Method_for_code_points_in_Plane_1.2C_Plane_2 */ + + *dest++ = 0xd800 | ((ret - 0x10000) >> 10); + *dest++ = 0xdc00 | ((ret - 0x10000) & 0x3ff); + } else { + *dest++ = UTF16_REPLACEMENT_CHAR; + } + } + + *out_len = dest - utf16Str; + + return utf16Str; +} diff --git a/libcutils/threads.c b/libcutils/threads.c new file mode 100644 index 0000000..42cc928 --- /dev/null +++ b/libcutils/threads.c @@ -0,0 +1,84 @@ +/* libs/cutils/threads.c +** +** Copyright (C) 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +#include <cutils/threads.h> + +#ifdef HAVE_PTHREADS +void* thread_store_get( thread_store_t* store ) +{ + const pthread_key_t k = store->tls; + + if (!store->has_tls) + return NULL; + + return pthread_getspecific( store->tls ); +} + +extern void thread_store_set( thread_store_t* store, + void* value, + thread_store_destruct_t destroy) +{ + pthread_mutex_lock( &store->lock ); + if (!store->has_tls) { + if (pthread_key_create( &store->tls, destroy) != 0) { + pthread_mutex_unlock(&store->lock); + return; + } + store->has_tls = 1; + } + pthread_mutex_unlock( &store->lock ); + + pthread_setspecific( store->tls, value ); +} + +#endif + +#ifdef HAVE_WIN32_THREADS +void* thread_store_get( thread_store_t* store ) +{ + if (!store->has_tls) + return NULL; + + return (void*) TlsGetValue( store->tls ); +} + +void thread_store_set( thread_store_t* store, + void* value, + thread_store_destruct_t destroy ) +{ + /* XXX: can't use destructor on thread exit */ + if (!store->lock_init) { + store->lock_init = -1; + InitializeCriticalSection( &store->lock ); + store->lock_init = -2; + } else while (store->lock_init != -2) { + Sleep(10); /* 10ms */ + } + + EnterCriticalSection( &store->lock ); + if (!store->has_tls) { + store->tls = TlsAlloc(); + if (store->tls == TLS_OUT_OF_INDEXES) { + LeaveCriticalSection( &store->lock ); + return; + } + store->has_tls = 1; + } + LeaveCriticalSection( &store->lock ); + + TlsSetValue( store->tls, value ); +} +#endif diff --git a/libcutils/tzfile.h b/libcutils/tzfile.h new file mode 100644 index 0000000..8c70375 --- /dev/null +++ b/libcutils/tzfile.h @@ -0,0 +1,180 @@ +#ifndef TZFILE_H + +#define TZFILE_H + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + +/* +** ID +*/ + +#ifndef lint +#ifndef NOID +static char tzfilehid[] = "@(#)tzfile.h 8.1"; +#endif /* !defined NOID */ +#endif /* !defined lint */ + +/* +** Information about time zone files. +*/ + +#ifndef TZDIR +#define TZDIR "/usr/share/zoneinfo" /* "/android/usr/share/zoneinfo" */ /* Time zone object file directory */ +#endif /* !defined TZDIR */ + +#ifndef TZDEFAULT +#define TZDEFAULT "localtime" +#endif /* !defined TZDEFAULT */ + +#ifndef TZDEFRULES +#define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ + +/* +** Each file begins with. . . +*/ + +#define TZ_MAGIC "TZif" + +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2' as of 2005 */ + char tzh_reserved[15]; /* reserved--must be zero */ + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +/* +** . . .followed by. . . +** +** tzh_timecnt (char [4])s coded transition times a la time(2) +** tzh_timecnt (unsigned char)s types of local time starting at above +** tzh_typecnt repetitions of +** one (char [4]) coded UTC offset in seconds +** one (unsigned char) used to set tm_isdst +** one (unsigned char) that's an abbreviation list index +** tzh_charcnt (char)s '\0'-terminated zone abbreviations +** tzh_leapcnt repetitions of +** one (char [4]) coded leap second transition times +** one (char [4]) total correction after above +** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition +** time is standard time, if FALSE, +** transition time is wall clock time +** if absent, transition times are +** assumed to be wall clock time +** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition +** time is UTC, if FALSE, +** transition time is local time +** if absent, transition times are +** assumed to be local time +*/ + +/* +** If tzh_version is '2' or greater, the above is followed by a second instance +** of tzhead and a second instance of the data in which each coded transition +** time uses 8 rather than 4 chars, +** then a POSIX-TZ-environment-variable-style string for use in handling +** instants after the last transition time stored in the file +** (with nothing between the newlines if there is no POSIX representation for +** such instants). +*/ + +/* +** In the current implementation, "tzset()" refuses to deal with files that +** exceed any of the limits below. +*/ + +#ifndef TZ_MAX_TIMES +#define TZ_MAX_TIMES 1200 +#endif /* !defined TZ_MAX_TIMES */ + +#ifndef TZ_MAX_TYPES +#ifndef NOSOLAR +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined NOSOLAR */ +#ifdef NOSOLAR +/* +** Must be at least 14 for Europe/Riga as of Jan 12 1995, +** as noted by Earl Chew. +*/ +#define TZ_MAX_TYPES 20 /* Maximum number of local time types */ +#endif /* !defined NOSOLAR */ +#endif /* !defined TZ_MAX_TYPES */ + +#ifndef TZ_MAX_CHARS +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ + +#ifndef TZ_MAX_LEAPS +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_JANUARY 0 +#define TM_FEBRUARY 1 +#define TM_MARCH 2 +#define TM_APRIL 3 +#define TM_MAY 4 +#define TM_JUNE 5 +#define TM_JULY 6 +#define TM_AUGUST 7 +#define TM_SEPTEMBER 8 +#define TM_OCTOBER 9 +#define TM_NOVEMBER 10 +#define TM_DECEMBER 11 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not C99). +** We use this to avoid addition overflow problems. +*/ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + +#endif /* !defined TZFILE_H */ diff --git a/libcutils/tzstrftime.c b/libcutils/tzstrftime.c new file mode 100644 index 0000000..e37d79a --- /dev/null +++ b/libcutils/tzstrftime.c @@ -0,0 +1,841 @@ +#ifndef lint +#ifndef NOID +static char elsieid[] = "@(#)strftime.c 8.1"; +/* +** Based on the UCB version with the ID appearing below. +** This is ANSIish only when "multibyte character == plain character". +*/ +#endif /* !defined NOID */ +#endif /* !defined lint */ + +#include <time.h> +#include <tzfile.h> +#include <limits.h> +#include <cutils/tztime.h> + +/* +** Copyright (c) 1989 The Regents of the University of California. +** All rights reserved. +** +** Redistribution and use in source and binary forms are permitted +** provided that the above copyright notice and this paragraph are +** duplicated in all such forms and that any documentation, +** advertising materials, and other materials related to such +** distribution and use acknowledge that the software was developed +** by the University of California, Berkeley. The name of the +** University may not be used to endorse or promote products derived +** from this software without specific prior written permission. +** THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +*/ + +#ifndef LIBC_SCCS +#ifndef lint +static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; +#endif /* !defined lint */ +#endif /* !defined LIBC_SCCS */ + +#include <ctype.h> + +#define P(x) x + +static char * _add P((const char *, char *, const char *, int)); +static char * _conv P((int, const char *, char *, const char *)); +static char * _fmt P((const char *, const struct tm *, char *, const char *, + int *, const struct strftime_locale *Locale)); +static char * _yconv P((int, int, int, int, char *, const char *, int)); +static char * getformat P((int, char *, char *, char *, char *)); + +extern char * tzname[]; + + + + + +/* from private.h */ + +#ifndef TYPE_BIT +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#endif /* !defined TYPE_BIT */ + +#ifndef TYPE_SIGNED +#define TYPE_SIGNED(type) (((type) -1) < 0) +#endif /* !defined TYPE_SIGNED */ + +#ifndef INT_STRLEN_MAXIMUM +/* + * ** 302 / 1000 is log10(2.0) rounded up. + * ** Subtract one for the sign bit if the type is signed; + * ** add one for integer division truncation; + * ** add one more for a minus sign if the type is signed. + * */ +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) +#endif /* !defined INT_STRLEN_MAXIMUM */ + +/* end of part from private.h */ + + + + +#ifndef YEAR_2000_NAME +#define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" +#endif /* !defined YEAR_2000_NAME */ + +#define IN_NONE 0 +#define IN_SOME 1 +#define IN_THIS 2 +#define IN_ALL 3 + +#define FORCE_LOWER_CASE 0x100 + +size_t +strftime_tz(s, maxsize, format, t, Locale) +char * const s; +const size_t maxsize; +const char * const format; +const struct tm * const t; +const struct strftime_locale *Locale; +{ + char * p; + int warn; + + warn = IN_NONE; + p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn, Locale); +#if 0 + if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) { + (void) fprintf(stderr, "\n"); + if (format == NULL) + (void) fprintf(stderr, "NULL strftime format "); + else (void) fprintf(stderr, "strftime format \"%s\" ", + format); + (void) fprintf(stderr, "yields only two digits of years in "); + if (warn == IN_SOME) + (void) fprintf(stderr, "some locales"); + else if (warn == IN_THIS) + (void) fprintf(stderr, "the current locale"); + else (void) fprintf(stderr, "all locales"); + (void) fprintf(stderr, "\n"); + } +#endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */ + if (p == s + maxsize) + return 0; + *p = '\0'; + return p - s; +} + +static char *getformat(int modifier, char *normal, char *underscore, + char *dash, char *zero) { + switch (modifier) { + case '_': + return underscore; + + case '-': + return dash; + + case '0': + return zero; + } + + return normal; +} + +static char * +_fmt(format, t, pt, ptlim, warnp, Locale) +const char * format; +const struct tm * const t; +char * pt; +const char * const ptlim; +int * warnp; +const struct strftime_locale *Locale; +{ + for ( ; *format; ++format) { + if (*format == '%') { + int modifier = 0; +label: + switch (*++format) { + case '\0': + --format; + break; + case 'A': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->weekday[t->tm_wday], + pt, ptlim, modifier); + continue; + case 'a': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->wday[t->tm_wday], + pt, ptlim, modifier); + continue; + case 'B': + if (modifier == '-') { + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->standalone_month[t->tm_mon], + pt, ptlim, modifier); + } else { + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->month[t->tm_mon], + pt, ptlim, modifier); + } + continue; + case 'b': + case 'h': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->mon[t->tm_mon], + pt, ptlim, modifier); + continue; + case 'C': + /* + ** %C used to do a... + ** _fmt("%a %b %e %X %Y", t); + ** ...whereas now POSIX 1003.2 calls for + ** something completely different. + ** (ado, 1993-05-24) + */ + pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0, + pt, ptlim, modifier); + continue; + case 'c': + { + int warn2 = IN_SOME; + + pt = _fmt(Locale->c_fmt, t, pt, ptlim, warnp, Locale); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'D': + pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, Locale); + continue; + case 'd': + pt = _conv(t->tm_mday, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'E': + case 'O': + /* + ** C99 locale modifiers. + ** The sequences + ** %Ec %EC %Ex %EX %Ey %EY + ** %Od %oe %OH %OI %Om %OM + ** %OS %Ou %OU %OV %Ow %OW %Oy + ** are supposed to provide alternate + ** representations. + */ + goto label; + case '_': + case '-': + case '0': + case '^': + case '#': + modifier = *format; + goto label; + case 'e': + pt = _conv(t->tm_mday, + getformat(modifier, "%2d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'F': + pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, Locale); + continue; + case 'H': + pt = _conv(t->tm_hour, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'I': + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'j': + pt = _conv(t->tm_yday + 1, + getformat(modifier, "%03d", "%3d", "%d", "%03d"), + pt, ptlim); + continue; + case 'k': + /* + ** This used to be... + ** _conv(t->tm_hour % 12 ? + ** t->tm_hour % 12 : 12, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbins' + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv(t->tm_hour, + getformat(modifier, "%2d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; +#ifdef KITCHEN_SINK + case 'K': + /* + ** After all this time, still unclaimed! + */ + pt = _add("kitchen sink", pt, ptlim, modifier); + continue; +#endif /* defined KITCHEN_SINK */ + case 'l': + /* + ** This used to be... + ** _conv(t->tm_hour, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbin's + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + getformat(modifier, "%2d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'M': + pt = _conv(t->tm_min, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'm': + pt = _conv(t->tm_mon + 1, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'n': + pt = _add("\n", pt, ptlim, modifier); + continue; + case 'p': + pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? + Locale->pm : + Locale->am, + pt, ptlim, modifier); + continue; + case 'P': + pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? + Locale->pm : + Locale->am, + pt, ptlim, FORCE_LOWER_CASE); + continue; + case 'R': + pt = _fmt("%H:%M", t, pt, ptlim, warnp, Locale); + continue; + case 'r': + pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp, Locale); + continue; + case 'S': + pt = _conv(t->tm_sec, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 's': + { + struct tm tm; + char buf[INT_STRLEN_MAXIMUM( + time_t) + 1]; + time_t mkt; + + tm = *t; + mkt = mktime(&tm); + if (TYPE_SIGNED(time_t)) + (void) sprintf(buf, "%ld", + (long) mkt); + else (void) sprintf(buf, "%lu", + (unsigned long) mkt); + pt = _add(buf, pt, ptlim, modifier); + } + continue; + case 'T': + pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp, Locale); + continue; + case 't': + pt = _add("\t", pt, ptlim, modifier); + continue; + case 'U': + pt = _conv((t->tm_yday + DAYSPERWEEK - + t->tm_wday) / DAYSPERWEEK, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'u': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "ISO 8601: Weekday as a decimal number + ** [1 (Monday) - 7]" + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_wday == 0) ? + DAYSPERWEEK : t->tm_wday, "%d", pt, ptlim); + continue; + case 'V': /* ISO 8601 week number */ + case 'G': /* ISO 8601 year (four digits) */ + case 'g': /* ISO 8601 year (two digits) */ +/* +** From Arnold Robbins' strftime version 3.0: "the week number of the +** year (the first Monday as the first day of week 1) as a decimal number +** (01-53)." +** (ado, 1993-05-24) +** +** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: +** "Week 01 of a year is per definition the first week which has the +** Thursday in this year, which is equivalent to the week which contains +** the fourth day of January. In other words, the first week of a new year +** is the week which has the majority of its days in the new year. Week 01 +** might also contain days from the previous year and the week before week +** 01 of a year is the last week (52 or 53) of the previous year even if +** it contains days from the new year. A week starts with Monday (day 1) +** and ends with Sunday (day 7). For example, the first week of the year +** 1997 lasts from 1996-12-30 to 1997-01-05..." +** (ado, 1996-01-02) +*/ + { + int year; + int base; + int yday; + int wday; + int w; + + year = t->tm_year; + base = TM_YEAR_BASE; + yday = t->tm_yday; + wday = t->tm_wday; + for ( ; ; ) { + int len; + int bot; + int top; + + len = isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + /* + ** What yday (-3 ... 3) does + ** the ISO year begin on? + */ + bot = ((yday + 11 - wday) % + DAYSPERWEEK) - 3; + /* + ** What yday does the NEXT + ** ISO year begin on? + */ + top = bot - + (len % DAYSPERWEEK); + if (top < -3) + top += DAYSPERWEEK; + top += len; + if (yday >= top) { + ++base; + w = 1; + break; + } + if (yday >= bot) { + w = 1 + ((yday - bot) / + DAYSPERWEEK); + break; + } + --base; + yday += isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + } +#ifdef XPG4_1994_04_09 + if ((w == 52 && + t->tm_mon == TM_JANUARY) || + (w == 1 && + t->tm_mon == TM_DECEMBER)) + w = 53; +#endif /* defined XPG4_1994_04_09 */ + if (*format == 'V') + pt = _conv(w, + getformat(modifier, + "%02d", + "%2d", + "%d", + "%02d"), + pt, ptlim); + else if (*format == 'g') { + *warnp = IN_ALL; + pt = _yconv(year, base, 0, 1, + pt, ptlim, modifier); + } else pt = _yconv(year, base, 1, 1, + pt, ptlim, modifier); + } + continue; + case 'v': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "date as dd-bbb-YYYY" + ** (ado, 1993-05-24) + */ + pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, Locale); + continue; + case 'W': + pt = _conv((t->tm_yday + DAYSPERWEEK - + (t->tm_wday ? + (t->tm_wday - 1) : + (DAYSPERWEEK - 1))) / DAYSPERWEEK, + getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + continue; + case 'w': + pt = _conv(t->tm_wday, "%d", pt, ptlim); + continue; + case 'X': + pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp, Locale); + continue; + case 'x': + { + int warn2 = IN_SOME; + + pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2, Locale); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'y': + *warnp = IN_ALL; + pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1, + pt, ptlim, modifier); + continue; + case 'Y': + pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1, + pt, ptlim, modifier); + continue; + case 'Z': +#ifdef TM_ZONE + if (t->TM_ZONE != NULL) + pt = _add(t->TM_ZONE, pt, ptlim, + modifier); + else +#endif /* defined TM_ZONE */ + if (t->tm_isdst >= 0) + pt = _add(tzname[t->tm_isdst != 0], + pt, ptlim, modifier); + /* + ** C99 says that %Z must be replaced by the + ** empty string if the time zone is not + ** determinable. + */ + continue; + case 'z': + { + int diff; + char const * sign; + + if (t->tm_isdst < 0) + continue; +#ifdef TM_GMTOFF + diff = t->TM_GMTOFF; +#else /* !defined TM_GMTOFF */ + /* + ** C99 says that the UTC offset must + ** be computed by looking only at + ** tm_isdst. This requirement is + ** incorrect, since it means the code + ** must rely on magic (in this case + ** altzone and timezone), and the + ** magic might not have the correct + ** offset. Doing things correctly is + ** tricky and requires disobeying C99; + ** see GNU C strftime for details. + ** For now, punt and conform to the + ** standard, even though it's incorrect. + ** + ** C99 says that %z must be replaced by the + ** empty string if the time zone is not + ** determinable, so output nothing if the + ** appropriate variables are not available. + */ + if (t->tm_isdst == 0) +#ifdef USG_COMPAT + diff = -timezone; +#else /* !defined USG_COMPAT */ + continue; +#endif /* !defined USG_COMPAT */ + else +#ifdef ALTZONE + diff = -altzone; +#else /* !defined ALTZONE */ + continue; +#endif /* !defined ALTZONE */ +#endif /* !defined TM_GMTOFF */ + if (diff < 0) { + sign = "-"; + diff = -diff; + } else sign = "+"; + pt = _add(sign, pt, ptlim, modifier); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + + (diff % MINSPERHOUR); + pt = _conv(diff, + getformat(modifier, "%04d", + "%4d", "%d", "%04d"), + pt, ptlim); + } + continue; + case '+': + pt = _fmt(Locale->date_fmt, t, pt, ptlim, + warnp, Locale); + continue; + case '%': + /* + ** X311J/88-090 (4.12.3.5): if conversion char is + ** undefined, behavior is undefined. Print out the + ** character itself as printf(3) also does. + */ + default: + break; + } + } + if (pt == ptlim) + break; + *pt++ = *format; + } + return pt; +} + +static char * +_conv(n, format, pt, ptlim) +const int n; +const char * const format; +char * const pt; +const char * const ptlim; +{ + char buf[INT_STRLEN_MAXIMUM(int) + 1]; + + (void) sprintf(buf, format, n); + return _add(buf, pt, ptlim, 0); +} + +static char * +_add(str, pt, ptlim, modifier) +const char * str; +char * pt; +const char * const ptlim; +int modifier; +{ + int c; + + switch (modifier) { + case FORCE_LOWER_CASE: + while (pt < ptlim && (*pt = tolower(*str++)) != '\0') { + ++pt; + } + break; + + case '^': + while (pt < ptlim && (*pt = toupper(*str++)) != '\0') { + ++pt; + } + break; + + case '#': + while (pt < ptlim && (c = *str++) != '\0') { + if (isupper(c)) { + c = tolower(c); + } else if (islower(c)) { + c = toupper(c); + } + *pt = c; + ++pt; + } + + break; + + default: + while (pt < ptlim && (*pt = *str++) != '\0') { + ++pt; + } + } + + return pt; +} + +/* +** POSIX and the C Standard are unclear or inconsistent about +** what %C and %y do if the year is negative or exceeds 9999. +** Use the convention that %C concatenated with %y yields the +** same output as %Y, and that %Y contains at least 4 bytes, +** with more only if necessary. +*/ + +static char * +_yconv(a, b, convert_top, convert_yy, pt, ptlim, modifier) +const int a; +const int b; +const int convert_top; +const int convert_yy; +char * pt; +const char * const ptlim; +int modifier; +{ + register int lead; + register int trail; + +#define DIVISOR 100 + trail = a % DIVISOR + b % DIVISOR; + lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; + trail %= DIVISOR; + if (trail < 0 && lead > 0) { + trail += DIVISOR; + --lead; + } else if (lead < 0 && trail > 0) { + trail -= DIVISOR; + ++lead; + } + if (convert_top) { + if (lead == 0 && trail < 0) + pt = _add("-0", pt, ptlim, modifier); + else pt = _conv(lead, getformat(modifier, "%02d", + "%2d", "%d", "%02d"), + pt, ptlim); + } + if (convert_yy) + pt = _conv(((trail < 0) ? -trail : trail), + getformat(modifier, "%02d", "%2d", "%d", "%02d"), + pt, ptlim); + return pt; +} + +#ifdef LOCALE_HOME +static struct lc_time_T * +_loc P((void)) +{ + static const char locale_home[] = LOCALE_HOME; + static const char lc_time[] = "LC_TIME"; + static char * locale_buf; + + int fd; + int oldsun; /* "...ain't got nothin' to do..." */ + char * lbuf; + char * name; + char * p; + const char ** ap; + const char * plim; + char filename[FILENAME_MAX]; + struct stat st; + size_t namesize; + size_t bufsize; + + /* + ** Use localebuf.mon[0] to signal whether locale is already set up. + */ + if (localebuf.mon[0]) + return &localebuf; + name = setlocale(LC_TIME, (char *) NULL); + if (name == NULL || *name == '\0') + goto no_locale; + /* + ** If the locale name is the same as our cache, use the cache. + */ + lbuf = locale_buf; + if (lbuf != NULL && strcmp(name, lbuf) == 0) { + p = lbuf; + for (ap = (const char **) &localebuf; + ap < (const char **) (&localebuf + 1); + ++ap) + *ap = p += strlen(p) + 1; + return &localebuf; + } + /* + ** Slurp the locale file into the cache. + */ + namesize = strlen(name) + 1; + if (sizeof filename < + ((sizeof locale_home) + namesize + (sizeof lc_time))) + goto no_locale; + oldsun = 0; + (void) sprintf(filename, "%s/%s/%s", locale_home, name, lc_time); + fd = open(filename, O_RDONLY); + if (fd < 0) { + /* + ** Old Sun systems have a different naming and data convention. + */ + oldsun = 1; + (void) sprintf(filename, "%s/%s/%s", locale_home, + lc_time, name); + fd = open(filename, O_RDONLY); + if (fd < 0) + goto no_locale; + } + if (fstat(fd, &st) != 0) + goto bad_locale; + if (st.st_size <= 0) + goto bad_locale; + bufsize = namesize + st.st_size; + locale_buf = NULL; + lbuf = (lbuf == NULL) ? malloc(bufsize) : realloc(lbuf, bufsize); + if (lbuf == NULL) + goto bad_locale; + (void) strcpy(lbuf, name); + p = lbuf + namesize; + plim = p + st.st_size; + if (read(fd, p, (size_t) st.st_size) != st.st_size) + goto bad_lbuf; + if (close(fd) != 0) + goto bad_lbuf; + /* + ** Parse the locale file into localebuf. + */ + if (plim[-1] != '\n') + goto bad_lbuf; + for (ap = (const char **) &localebuf; + ap < (const char **) (&localebuf + 1); + ++ap) { + if (p == plim) + goto bad_lbuf; + *ap = p; + while (*p != '\n') + ++p; + *p++ = '\0'; + } + if (oldsun) { + /* + ** SunOS 4 used an obsolescent format; see localdtconv(3). + ** c_fmt had the ``short format for dates and times together'' + ** (SunOS 4 date, "%a %b %e %T %Z %Y" in the C locale); + ** date_fmt had the ``long format for dates'' + ** (SunOS 4 strftime %C, "%A, %B %e, %Y" in the C locale). + ** Discard the latter in favor of the former. + */ + localebuf.date_fmt = localebuf.c_fmt; + } + /* + ** Record the successful parse in the cache. + */ + locale_buf = lbuf; + + return &localebuf; + +bad_lbuf: + free(lbuf); +bad_locale: + (void) close(fd); +no_locale: + localebuf = C_time_locale; + locale_buf = NULL; + return &localebuf; +} +#endif /* defined LOCALE_HOME */ diff --git a/libcutils/tztime.c b/libcutils/tztime.c new file mode 100644 index 0000000..d6448a1 --- /dev/null +++ b/libcutils/tztime.c @@ -0,0 +1,1950 @@ +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +#include <stdio.h> + +#ifndef lint +#ifndef NOID +static char elsieid[] = "@(#)localtime.c 8.3"; +#endif /* !defined NOID */ +#endif /* !defined lint */ + +/* +** Leap second handling from Bradley White. +** POSIX-style TZ environment variable handling from Guy Harris. +*/ + +/*LINTLIBRARY*/ + +#include "private.h" +#include "tzfile.h" +#include "fcntl.h" +#include "float.h" /* for FLT_MAX and DBL_MAX */ + +#ifndef TZ_ABBR_MAX_LEN +#define TZ_ABBR_MAX_LEN 16 +#endif /* !defined TZ_ABBR_MAX_LEN */ + +#ifndef TZ_ABBR_CHAR_SET +#define TZ_ABBR_CHAR_SET \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" +#endif /* !defined TZ_ABBR_CHAR_SET */ + +#ifndef TZ_ABBR_ERR_CHAR +#define TZ_ABBR_ERR_CHAR '_' +#endif /* !defined TZ_ABBR_ERR_CHAR */ + +#define INDEXFILE "/system/usr/share/zoneinfo/zoneinfo.idx" +#define DATAFILE "/system/usr/share/zoneinfo/zoneinfo.dat" +#define NAMELEN 40 +#define INTLEN 4 +#define READLEN (NAMELEN + 3 * INTLEN) + +/* +** SunOS 4.1.1 headers lack O_BINARY. +*/ + +#ifdef O_BINARY +#define OPEN_MODE (O_RDONLY | O_BINARY) +#endif /* defined O_BINARY */ +#ifndef O_BINARY +#define OPEN_MODE O_RDONLY +#endif /* !defined O_BINARY */ + +/* Complex computations to determine the min/max of time_t depending + * on TYPE_BIT / TYPE_SIGNED / TYPE_INTEGRAL. + * These macros cannot be used in pre-processor directives, so we + * let the C compiler do the work, which makes things a bit funky. + */ +static const time_t TIME_T_MAX = + TYPE_INTEGRAL(time_t) ? + ( TYPE_SIGNED(time_t) ? + ~((time_t)1 << (TYPE_BIT(time_t)-1)) + : + ~(time_t)0 + ) + : /* if time_t is a floating point number */ + ( sizeof(time_t) > sizeof(float) ? (time_t)DBL_MAX : (time_t)FLT_MAX ); + +static const time_t TIME_T_MIN = + TYPE_INTEGRAL(time_t) ? + ( TYPE_SIGNED(time_t) ? + ((time_t)1 << (TYPE_BIT(time_t)-1)) + : + 0 + ) + : + ( sizeof(time_t) > sizeof(float) ? (time_t)DBL_MIN : (time_t)FLT_MIN ); + +#ifndef WILDABBR +/* +** Someone might make incorrect use of a time zone abbreviation: +** 1. They might reference tzname[0] before calling tzset (explicitly +** or implicitly). +** 2. They might reference tzname[1] before calling tzset (explicitly +** or implicitly). +** 3. They might reference tzname[1] after setting to a time zone +** in which Daylight Saving Time is never observed. +** 4. They might reference tzname[0] after setting to a time zone +** in which Standard Time is never observed. +** 5. They might reference tm.TM_ZONE after calling offtime. +** What's best to do in the above cases is open to debate; +** for now, we just set things up so that in any of the five cases +** WILDABBR is used. Another possibility: initialize tzname[0] to the +** string "tzname[0] used before set", and similarly for the other cases. +** And another: initialize tzname[0] to "ERA", with an explanation in the +** manual page of what this "time zone abbreviation" means (doing this so +** that tzname[0] has the "normal" length of three characters). +*/ +#define WILDABBR " " +#endif /* !defined WILDABBR */ + +static char wildabbr[] = WILDABBR; + +static const char gmt[] = "GMT"; + +/* +** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** We default to US rules as of 1999-08-17. +** POSIX 1003.1 section 8.1.1 says that the default DST rules are +** implementation dependent; for historical reasons, US rules are a +** common default. +*/ +#ifndef TZDEFRULESTRING +#define TZDEFRULESTRING ",M4.1.0,M10.5.0" +#endif /* !defined TZDEFDST */ + +struct ttinfo { /* time type information */ + long tt_gmtoff; /* UTC offset in seconds */ + int tt_isdst; /* used to set tm_isdst */ + int tt_abbrind; /* abbreviation list index */ + int tt_ttisstd; /* TRUE if transition is std time */ + int tt_ttisgmt; /* TRUE if transition is UTC */ +}; + +struct lsinfo { /* leap second information */ + time_t ls_trans; /* transition time */ + long ls_corr; /* correction to apply */ +}; + +#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) + +#ifdef TZNAME_MAX +#define MY_TZNAME_MAX TZNAME_MAX +#endif /* defined TZNAME_MAX */ +#ifndef TZNAME_MAX +#define MY_TZNAME_MAX 255 +#endif /* !defined TZNAME_MAX */ + +struct state { + int leapcnt; + int timecnt; + int typecnt; + int charcnt; + int goback; + int goahead; + time_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; + struct ttinfo ttis[TZ_MAX_TYPES]; + char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt), + (2 * (MY_TZNAME_MAX + 1)))]; + struct lsinfo lsis[TZ_MAX_LEAPS]; +}; + +struct rule { + int r_type; /* type of rule--see below */ + int r_day; /* day number of rule */ + int r_week; /* week number of rule */ + int r_mon; /* month number of rule */ + long r_time; /* transition time of rule */ +}; + +#define JULIAN_DAY 0 /* Jn - Julian day */ +#define DAY_OF_YEAR 1 /* n - day of year */ +#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */ + +/* +** Prototypes for static functions. +*/ + +static long detzcode P((const char * codep)); +static time_t detzcode64 P((const char * codep)); +static int differ_by_repeat P((time_t t1, time_t t0)); +static const char * getzname P((const char * strp)); +static const char * getqzname P((const char * strp, const int delim)); +static const char * getnum P((const char * strp, int * nump, int min, + int max)); +static const char * getsecs P((const char * strp, long * secsp)); +static const char * getoffset P((const char * strp, long * offsetp)); +static const char * getrule P((const char * strp, struct rule * rulep)); +static void gmtload P((struct state * sp)); +static struct tm * gmtsub P((const time_t * timep, long offset, + struct tm * tmp)); +static struct tm * localsub P((const time_t * timep, long offset, + struct tm * tmp, const struct state *sp)); +static int increment_overflow P((int * number, int delta)); +static int leaps_thru_end_of P((int y)); +static int long_increment_overflow P((long * number, int delta)); +static int long_normalize_overflow P((long * tensptr, + int * unitsptr, int base)); +static int normalize_overflow P((int * tensptr, int * unitsptr, + int base)); +static void settzname P((void)); +static time_t time1 P((struct tm * tmp, + struct tm * (*funcp) P((const time_t *, + long, struct tm *, const struct state* sp)), + long offset, const struct state * sp)); +static time_t time2 P((struct tm *tmp, + struct tm * (*funcp) P((const time_t *, + long, struct tm*, const struct state* sp)), + long offset, int * okayp, const struct state * sp)); +static time_t time2sub P((struct tm *tmp, + struct tm * (*funcp) P((const time_t*, long, struct tm*,const struct state *sp)), + long offset, int * okayp, int do_norm_secs, + const struct state *sp)); +static struct tm * timesub P((const time_t * timep, long offset, + const struct state * sp, struct tm * tmp)); +static int tmcomp P((const struct tm * atmp, + const struct tm * btmp)); +static time_t transtime P((time_t janfirst, int year, + const struct rule * rulep, long offset)); +static int tzload P((const char * name, struct state * sp, + int doextend)); +static int tzload_uncached P((const char * name, struct state * sp, + int doextend)); +static int tzparse P((const char * name, struct state * sp, + int lastditch)); + +#ifdef ALL_STATE +static struct state * gmtptr; +#endif /* defined ALL_STATE */ + +#ifndef ALL_STATE +static struct state gmtmem; +#define gmtptr (&gmtmem) +#endif /* State Farm */ + +#define CACHE_COUNT 4 +static char * g_cacheNames[CACHE_COUNT] = {0,0}; +static struct state g_cacheStates[CACHE_COUNT]; +static int g_lastCache = 0; +static struct state g_utc; +unsigned char g_utcSet = 0; + + +#ifndef TZ_STRLEN_MAX +#define TZ_STRLEN_MAX 255 +#endif /* !defined TZ_STRLEN_MAX */ + +static char lcl_TZname[TZ_STRLEN_MAX + 1]; +static int lcl_is_set; +static int gmt_is_set; + +char * tzname[2] = { + wildabbr, + wildabbr +}; + +/* +** Section 4.12.3 of X3.159-1989 requires that +** Except for the strftime function, these functions [asctime, +** ctime, gmtime, localtime] return values in one of two static +** objects: a broken-down time structure and an array of char. +** Thanks to Paul Eggert for noting this. +*/ + +static struct tm tm; + +#ifdef USG_COMPAT +time_t timezone = 0; +int daylight = 0; +#endif /* defined USG_COMPAT */ + +#ifdef ALTZONE +time_t altzone = 0; +#endif /* defined ALTZONE */ + +static long +detzcode(codep) +const char * const codep; +{ + register long result; + register int i; + + result = (codep[0] & 0x80) ? ~0L : 0; + for (i = 0; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); + return result; +} + +static time_t +detzcode64(codep) +const char * const codep; +{ + register time_t result; + register int i; + + result = (codep[0] & 0x80) ? (~(int_fast64_t) 0) : 0; + for (i = 0; i < 8; ++i) + result = result * 256 + (codep[i] & 0xff); + return result; +} + +static int +differ_by_repeat(t1, t0) +const time_t t1; +const time_t t0; +{ + if (TYPE_INTEGRAL(time_t) && + TYPE_BIT(time_t) - TYPE_SIGNED(time_t) < SECSPERREPEAT_BITS) + return 0; + return t1 - t0 == SECSPERREPEAT; +} + +static int toint(unsigned char *s) { + return (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3]; +} + +static int +tzload(const char *name, struct state * const sp, const int doextend) +{ + if (name) { + int i, err; + if (0 == strcmp(name, "UTC")) { + if (!g_utcSet) { + tzload_uncached(name, &g_utc, 1); + g_utcSet = 1; + } + //printf("tzload: utc\n"); + *sp = g_utc; + return 0; + } + for (i=0; i<CACHE_COUNT; i++) { + if (g_cacheNames[i] && 0 == strcmp(name, g_cacheNames[i])) { + *sp = g_cacheStates[i]; + //printf("tzload: hit: %s\n", name); + return 0; + } + } + //printf("tzload: miss: %s\n", name); + g_lastCache++; + if (g_lastCache >= CACHE_COUNT) { + g_lastCache = 0; + } + i = g_lastCache; + if (g_cacheNames[i]) { + free(g_cacheNames[i]); + } + err = tzload_uncached(name, &(g_cacheStates[i]), 1); + if (err == 0) { + g_cacheNames[i] = strdup(name); + *sp = g_cacheStates[i]; + return 0; + } else { + g_cacheNames[i] = NULL; + return err; + } + } + return tzload_uncached(name, sp, doextend); +} + +static int +tzload_uncached(name, sp, doextend) +register const char * name; +register struct state * const sp; +register const int doextend; +{ + register const char * p; + register int i; + register int fid; + register int stored; + register int nread; + union { + struct tzhead tzhead; + char buf[2 * sizeof(struct tzhead) + + 2 * sizeof *sp + + 4 * TZ_MAX_TIMES]; + } u; + int toread = sizeof u.buf; + + if (name == NULL && (name = TZDEFAULT) == NULL) + return -1; + { + register int doaccess; + /* + ** Section 4.9.1 of the C standard says that + ** "FILENAME_MAX expands to an integral constant expression + ** that is the size needed for an array of char large enough + ** to hold the longest file name string that the implementation + ** guarantees can be opened." + */ + char fullname[FILENAME_MAX + 1]; + const char *origname = name; + + if (name[0] == ':') + ++name; + doaccess = name[0] == '/'; + if (!doaccess) { + if ((p = TZDIR) == NULL) + return -1; + if ((strlen(p) + strlen(name) + 1) >= sizeof fullname) + return -1; + (void) strcpy(fullname, p); + (void) strcat(fullname, "/"); + (void) strcat(fullname, name); + /* + ** Set doaccess if '.' (as in "../") shows up in name. + */ + if (strchr(name, '.') != NULL) + doaccess = TRUE; + name = fullname; + } + if (doaccess && access(name, R_OK) != 0) + return -1; + if ((fid = open(name, OPEN_MODE)) == -1) { + char buf[READLEN]; + char name[NAMELEN + 1]; + int fidix = open(INDEXFILE, OPEN_MODE); + int off = -1; + + if (fidix < 0) { + return -1; + } + + while (read(fidix, buf, sizeof(buf)) == sizeof(buf)) { + memcpy(name, buf, NAMELEN); + name[NAMELEN] = '\0'; + + if (strcmp(name, origname) == 0) { + off = toint((unsigned char *) buf + NAMELEN); + toread = toint((unsigned char *) buf + NAMELEN + INTLEN); + break; + } + } + + close(fidix); + + if (off < 0) + return -1; + + fid = open(DATAFILE, OPEN_MODE); + + if (fid < 0) { + return -1; + } + + if (lseek(fid, off, SEEK_SET) < 0) { + return -1; + } + } + } + nread = read(fid, u.buf, toread); + if (close(fid) < 0 || nread <= 0) + return -1; + for (stored = 4; stored <= 8; stored *= 2) { + int ttisstdcnt; + int ttisgmtcnt; + + ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt); + ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt); + sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt); + sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt); + sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt); + sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt); + p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt; + if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS || + sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES || + sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES || + sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS || + (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) || + (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) + return -1; + if (nread - (p - u.buf) < + sp->timecnt * stored + /* ats */ + sp->timecnt + /* types */ + sp->typecnt * 6 + /* ttinfos */ + sp->charcnt + /* chars */ + sp->leapcnt * (stored + 4) + /* lsinfos */ + ttisstdcnt + /* ttisstds */ + ttisgmtcnt) /* ttisgmts */ + return -1; + for (i = 0; i < sp->timecnt; ++i) { + sp->ats[i] = (stored == 4) ? + detzcode(p) : detzcode64(p); + p += stored; + } + for (i = 0; i < sp->timecnt; ++i) { + sp->types[i] = (unsigned char) *p++; + if (sp->types[i] >= sp->typecnt) + return -1; + } + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + ttisp->tt_gmtoff = detzcode(p); + p += 4; + ttisp->tt_isdst = (unsigned char) *p++; + if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) + return -1; + ttisp->tt_abbrind = (unsigned char) *p++; + if (ttisp->tt_abbrind < 0 || + ttisp->tt_abbrind > sp->charcnt) + return -1; + } + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = *p++; + sp->chars[i] = '\0'; /* ensure '\0' at end */ + for (i = 0; i < sp->leapcnt; ++i) { + register struct lsinfo * lsisp; + + lsisp = &sp->lsis[i]; + lsisp->ls_trans = (stored == 4) ? + detzcode(p) : detzcode64(p); + p += stored; + lsisp->ls_corr = detzcode(p); + p += 4; + } + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisstdcnt == 0) + ttisp->tt_ttisstd = FALSE; + else { + ttisp->tt_ttisstd = *p++; + if (ttisp->tt_ttisstd != TRUE && + ttisp->tt_ttisstd != FALSE) + return -1; + } + } + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisgmtcnt == 0) + ttisp->tt_ttisgmt = FALSE; + else { + ttisp->tt_ttisgmt = *p++; + if (ttisp->tt_ttisgmt != TRUE && + ttisp->tt_ttisgmt != FALSE) + return -1; + } + } + /* + ** Out-of-sort ats should mean we're running on a + ** signed time_t system but using a data file with + ** unsigned values (or vice versa). + */ + for (i = 0; i < sp->timecnt - 2; ++i) + if (sp->ats[i] > sp->ats[i + 1]) { + ++i; + if (TYPE_SIGNED(time_t)) { + /* + ** Ignore the end (easy). + */ + sp->timecnt = i; + } else { + /* + ** Ignore the beginning (harder). + */ + register int j; + + for (j = 0; j + i < sp->timecnt; ++j) { + sp->ats[j] = sp->ats[j + i]; + sp->types[j] = sp->types[j + i]; + } + sp->timecnt = j; + } + break; + } + /* + ** If this is an old file, we're done. + */ + if (u.tzhead.tzh_version[0] == '\0') + break; + nread -= p - u.buf; + for (i = 0; i < nread; ++i) + u.buf[i] = p[i]; + /* + ** If this is a narrow integer time_t system, we're done. + */ + if (stored >= (int) sizeof(time_t) && TYPE_INTEGRAL(time_t)) + break; + } + if (doextend && nread > 2 && + u.buf[0] == '\n' && u.buf[nread - 1] == '\n' && + sp->typecnt + 2 <= TZ_MAX_TYPES) { + struct state ts; + register int result; + + u.buf[nread - 1] = '\0'; + result = tzparse(&u.buf[1], &ts, FALSE); + if (result == 0 && ts.typecnt == 2 && + sp->charcnt + ts.charcnt <= TZ_MAX_CHARS) { + for (i = 0; i < 2; ++i) + ts.ttis[i].tt_abbrind += + sp->charcnt; + for (i = 0; i < ts.charcnt; ++i) + sp->chars[sp->charcnt++] = + ts.chars[i]; + i = 0; + while (i < ts.timecnt && + ts.ats[i] <= + sp->ats[sp->timecnt - 1]) + ++i; + while (i < ts.timecnt && + sp->timecnt < TZ_MAX_TIMES) { + sp->ats[sp->timecnt] = + ts.ats[i]; + sp->types[sp->timecnt] = + sp->typecnt + + ts.types[i]; + ++sp->timecnt; + ++i; + } + sp->ttis[sp->typecnt++] = ts.ttis[0]; + sp->ttis[sp->typecnt++] = ts.ttis[1]; + } + } + i = 2 * YEARSPERREPEAT; + sp->goback = sp->goahead = sp->timecnt > i; + sp->goback &= sp->types[i] == sp->types[0] && + differ_by_repeat(sp->ats[i], sp->ats[0]); + sp->goahead &= + sp->types[sp->timecnt - 1] == sp->types[sp->timecnt - 1 - i] && + differ_by_repeat(sp->ats[sp->timecnt - 1], + sp->ats[sp->timecnt - 1 - i]); + return 0; +} + +static const int mon_lengths[2][MONSPERYEAR] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const int year_lengths[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +}; + +/* +** Given a pointer into a time zone string, scan until a character that is not +** a valid character in a zone name is found. Return a pointer to that +** character. +*/ + +static const char * +getzname(strp) +register const char * strp; +{ + register char c; + + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && + c != '+') + ++strp; + return strp; +} + +/* +** Given a pointer into an extended time zone string, scan until the ending +** delimiter of the zone name is located. Return a pointer to the delimiter. +** +** As with getzname above, the legal character set is actually quite +** restricted, with other characters producing undefined results. +** We don't do any checking here; checking is done later in common-case code. +*/ + +static const char * +getqzname(register const char *strp, const int delim) +{ + register int c; + + while ((c = *strp) != '\0' && c != delim) + ++strp; + return strp; +} + +/* +** Given a pointer into a time zone string, extract a number from that string. +** Check that the number is within a specified range; if it is not, return +** NULL. +** Otherwise, return a pointer to the first character not part of the number. +*/ + +static const char * +getnum(strp, nump, min, max) +register const char * strp; +int * const nump; +const int min; +const int max; +{ + register char c; + register int num; + + if (strp == NULL || !is_digit(c = *strp)) + return NULL; + num = 0; + do { + num = num * 10 + (c - '0'); + if (num > max) + return NULL; /* illegal value */ + c = *++strp; + } while (is_digit(c)); + if (num < min) + return NULL; /* illegal value */ + *nump = num; + return strp; +} + +/* +** Given a pointer into a time zone string, extract a number of seconds, +** in hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the number +** of seconds. +*/ + +static const char * +getsecs(strp, secsp) +register const char * strp; +long * const secsp; +{ + int num; + + /* + ** `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like + ** "M10.4.6/26", which does not conform to Posix, + ** but which specifies the equivalent of + ** ``02:00 on the first Sunday on or after 23 Oct''. + */ + strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); + if (strp == NULL) + return NULL; + *secsp = num * (long) SECSPERHOUR; + if (*strp == ':') { + ++strp; + strp = getnum(strp, &num, 0, MINSPERHOUR - 1); + if (strp == NULL) + return NULL; + *secsp += num * SECSPERMIN; + if (*strp == ':') { + ++strp; + /* `SECSPERMIN' allows for leap seconds. */ + strp = getnum(strp, &num, 0, SECSPERMIN); + if (strp == NULL) + return NULL; + *secsp += num; + } + } + return strp; +} + +/* +** Given a pointer into a time zone string, extract an offset, in +** [+-]hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the time. +*/ + +static const char * +getoffset(strp, offsetp) +register const char * strp; +long * const offsetp; +{ + register int neg = 0; + + if (*strp == '-') { + neg = 1; + ++strp; + } else if (*strp == '+') + ++strp; + strp = getsecs(strp, offsetp); + if (strp == NULL) + return NULL; /* illegal time */ + if (neg) + *offsetp = -*offsetp; + return strp; +} + +/* +** Given a pointer into a time zone string, extract a rule in the form +** date[/time]. See POSIX section 8 for the format of "date" and "time". +** If a valid rule is not found, return NULL. +** Otherwise, return a pointer to the first character not part of the rule. +*/ + +static const char * +getrule(strp, rulep) +const char * strp; +register struct rule * const rulep; +{ + if (*strp == 'J') { + /* + ** Julian day. + */ + rulep->r_type = JULIAN_DAY; + ++strp; + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); + } else if (*strp == 'M') { + /* + ** Month, week, day. + */ + rulep->r_type = MONTH_NTH_DAY_OF_WEEK; + ++strp; + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_week, 1, 5); + if (strp == NULL) + return NULL; + if (*strp++ != '.') + return NULL; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); + } else if (is_digit(*strp)) { + /* + ** Day of year. + */ + rulep->r_type = DAY_OF_YEAR; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); + } else return NULL; /* invalid format */ + if (strp == NULL) + return NULL; + if (*strp == '/') { + /* + ** Time specified. + */ + ++strp; + strp = getsecs(strp, &rulep->r_time); + } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ + return strp; +} + +/* +** Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the +** year, a rule, and the offset from UTC at the time that rule takes effect, +** calculate the Epoch-relative time that rule takes effect. +*/ + +static time_t +transtime(janfirst, year, rulep, offset) +const time_t janfirst; +const int year; +register const struct rule * const rulep; +const long offset; +{ + register int leapyear; + register time_t value; + register int i; + int d, m1, yy0, yy1, yy2, dow; + + INITIALIZE(value); + leapyear = isleap(year); + switch (rulep->r_type) { + + case JULIAN_DAY: + /* + ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap + ** years. + ** In non-leap years, or if the day number is 59 or less, just + ** add SECSPERDAY times the day number-1 to the time of + ** January 1, midnight, to get the day. + */ + value = janfirst + (rulep->r_day - 1) * SECSPERDAY; + if (leapyear && rulep->r_day >= 60) + value += SECSPERDAY; + break; + + case DAY_OF_YEAR: + /* + ** n - day of year. + ** Just add SECSPERDAY times the day number to the time of + ** January 1, midnight, to get the day. + */ + value = janfirst + rulep->r_day * SECSPERDAY; + break; + + case MONTH_NTH_DAY_OF_WEEK: + /* + ** Mm.n.d - nth "dth day" of month m. + */ + value = janfirst; + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[leapyear][i] * SECSPERDAY; + + /* + ** Use Zeller's Congruence to get day-of-week of first day of + ** month. + */ + m1 = (rulep->r_mon + 9) % 12 + 1; + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; + yy1 = yy0 / 100; + yy2 = yy0 % 100; + dow = ((26 * m1 - 2) / 10 + + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + if (dow < 0) + dow += DAYSPERWEEK; + + /* + ** "dow" is the day-of-week of the first day of the month. Get + ** the day-of-month (zero-origin) of the first "dow" day of the + ** month. + */ + d = rulep->r_day - dow; + if (d < 0) + d += DAYSPERWEEK; + for (i = 1; i < rulep->r_week; ++i) { + if (d + DAYSPERWEEK >= + mon_lengths[leapyear][rulep->r_mon - 1]) + break; + d += DAYSPERWEEK; + } + + /* + ** "d" is the day-of-month (zero-origin) of the day we want. + */ + value += d * SECSPERDAY; + break; + } + + /* + ** "value" is the Epoch-relative time of 00:00:00 UTC on the day in + ** question. To get the Epoch-relative time of the specified local + ** time on that day, add the transition time and the current offset + ** from UTC. + */ + return value + rulep->r_time + offset; +} + +/* +** Given a POSIX section 8-style TZ string, fill in the rule tables as +** appropriate. +*/ + +static int +tzparse(name, sp, lastditch) +const char * name; +register struct state * const sp; +const int lastditch; +{ + const char * stdname; + const char * dstname; + size_t stdlen; + size_t dstlen; + long stdoffset; + long dstoffset; + register time_t * atp; + register unsigned char * typep; + register char * cp; + register int load_result; + + INITIALIZE(dstname); + stdname = name; + if (lastditch) { + stdlen = strlen(name); /* length of standard zone name */ + name += stdlen; + if (stdlen >= sizeof sp->chars) + stdlen = (sizeof sp->chars) - 1; + stdoffset = 0; + } else { + if (*name == '<') { + name++; + stdname = name; + name = getqzname(name, '>'); + if (*name != '>') + return (-1); + stdlen = name - stdname; + name++; + } else { + name = getzname(name); + stdlen = name - stdname; + } + if (*name == '\0') + return -1; + name = getoffset(name, &stdoffset); + if (name == NULL) + return -1; + } + load_result = tzload(TZDEFRULES, sp, FALSE); + if (load_result != 0) + sp->leapcnt = 0; /* so, we're off a little */ + sp->timecnt = 0; + if (*name != '\0') { + if (*name == '<') { + dstname = ++name; + name = getqzname(name, '>'); + if (*name != '>') + return -1; + dstlen = name - dstname; + name++; + } else { + dstname = name; + name = getzname(name); + dstlen = name - dstname; /* length of DST zone name */ + } + if (*name != '\0' && *name != ',' && *name != ';') { + name = getoffset(name, &dstoffset); + if (name == NULL) + return -1; + } else dstoffset = stdoffset - SECSPERHOUR; + if (*name == '\0' && load_result != 0) + name = TZDEFRULESTRING; + if (*name == ',' || *name == ';') { + struct rule start; + struct rule end; + register int year; + register time_t janfirst; + time_t starttime; + time_t endtime; + + ++name; + if ((name = getrule(name, &start)) == NULL) + return -1; + if (*name++ != ',') + return -1; + if ((name = getrule(name, &end)) == NULL) + return -1; + if (*name != '\0') + return -1; + sp->typecnt = 2; /* standard time and DST */ + /* + ** Two transitions per year, from EPOCH_YEAR forward. + */ + sp->ttis[0].tt_gmtoff = -dstoffset; + sp->ttis[0].tt_isdst = 1; + sp->ttis[0].tt_abbrind = stdlen + 1; + sp->ttis[1].tt_gmtoff = -stdoffset; + sp->ttis[1].tt_isdst = 0; + sp->ttis[1].tt_abbrind = 0; + atp = sp->ats; + typep = sp->types; + janfirst = 0; + for (year = EPOCH_YEAR; + sp->timecnt + 2 <= TZ_MAX_TIMES; + ++year) { + time_t newfirst; + + starttime = transtime(janfirst, year, &start, + stdoffset); + endtime = transtime(janfirst, year, &end, + dstoffset); + if (starttime > endtime) { + *atp++ = endtime; + *typep++ = 1; /* DST ends */ + *atp++ = starttime; + *typep++ = 0; /* DST begins */ + } else { + *atp++ = starttime; + *typep++ = 0; /* DST begins */ + *atp++ = endtime; + *typep++ = 1; /* DST ends */ + } + sp->timecnt += 2; + newfirst = janfirst; + newfirst += year_lengths[isleap(year)] * + SECSPERDAY; + if (newfirst <= janfirst) + break; + janfirst = newfirst; + } + } else { + register long theirstdoffset; + register long theirdstoffset; + register long theiroffset; + register int isdst; + register int i; + register int j; + + if (*name != '\0') + return -1; + /* + ** Initial values of theirstdoffset and theirdstoffset. + */ + theirstdoffset = 0; + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + if (!sp->ttis[j].tt_isdst) { + theirstdoffset = + -sp->ttis[j].tt_gmtoff; + break; + } + } + theirdstoffset = 0; + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + if (sp->ttis[j].tt_isdst) { + theirdstoffset = + -sp->ttis[j].tt_gmtoff; + break; + } + } + /* + ** Initially we're assumed to be in standard time. + */ + isdst = FALSE; + theiroffset = theirstdoffset; + /* + ** Now juggle transition times and types + ** tracking offsets as you do. + */ + for (i = 0; i < sp->timecnt; ++i) { + j = sp->types[i]; + sp->types[i] = sp->ttis[j].tt_isdst; + if (sp->ttis[j].tt_ttisgmt) { + /* No adjustment to transition time */ + } else { + /* + ** If summer time is in effect, and the + ** transition time was not specified as + ** standard time, add the summer time + ** offset to the transition time; + ** otherwise, add the standard time + ** offset to the transition time. + */ + /* + ** Transitions from DST to DDST + ** will effectively disappear since + ** POSIX provides for only one DST + ** offset. + */ + if (isdst && !sp->ttis[j].tt_ttisstd) { + sp->ats[i] += dstoffset - + theirdstoffset; + } else { + sp->ats[i] += stdoffset - + theirstdoffset; + } + } + theiroffset = -sp->ttis[j].tt_gmtoff; + if (sp->ttis[j].tt_isdst) + theirdstoffset = theiroffset; + else theirstdoffset = theiroffset; + } + /* + ** Finally, fill in ttis. + ** ttisstd and ttisgmt need not be handled. + */ + sp->ttis[0].tt_gmtoff = -stdoffset; + sp->ttis[0].tt_isdst = FALSE; + sp->ttis[0].tt_abbrind = 0; + sp->ttis[1].tt_gmtoff = -dstoffset; + sp->ttis[1].tt_isdst = TRUE; + sp->ttis[1].tt_abbrind = stdlen + 1; + sp->typecnt = 2; + } + } else { + dstlen = 0; + sp->typecnt = 1; /* only standard time */ + sp->timecnt = 0; + sp->ttis[0].tt_gmtoff = -stdoffset; + sp->ttis[0].tt_isdst = 0; + sp->ttis[0].tt_abbrind = 0; + } + sp->charcnt = stdlen + 1; + if (dstlen != 0) + sp->charcnt += dstlen + 1; + if ((size_t) sp->charcnt > sizeof sp->chars) + return -1; + cp = sp->chars; + (void) strncpy(cp, stdname, stdlen); + cp += stdlen; + *cp++ = '\0'; + if (dstlen != 0) { + (void) strncpy(cp, dstname, dstlen); + *(cp + dstlen) = '\0'; + } + return 0; +} + +static void +gmtload(sp) +struct state * const sp; +{ + if (tzload(gmt, sp, TRUE) != 0) + (void) tzparse(gmt, sp, TRUE); +} + +/* +** The easy way to behave "as if no library function calls" localtime +** is to not call it--so we drop its guts into "localsub", which can be +** freely called. (And no, the PANS doesn't require the above behavior-- +** but it *is* desirable.) +** +** The unused offset argument is for the benefit of mktime variants. +*/ + +/*ARGSUSED*/ +static struct tm * +localsub(timep, offset, tmp, sp) +const time_t * const timep; +const long offset; +struct tm * const tmp; +const struct state * sp; +{ + register const struct ttinfo * ttisp; + register int i; + register struct tm * result; + const time_t t = *timep; + +#ifdef ALL_STATE + if (sp == NULL) + return gmtsub(timep, offset, tmp); +#endif /* defined ALL_STATE */ + if ((sp->goback && t < sp->ats[0]) || + (sp->goahead && t > sp->ats[sp->timecnt - 1])) { + time_t newt = t; + register time_t seconds; + register time_t tcycles; + register int_fast64_t icycles; + + if (t < sp->ats[0]) + seconds = sp->ats[0] - t; + else seconds = t - sp->ats[sp->timecnt - 1]; + --seconds; + tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR; + ++tcycles; + icycles = tcycles; + if (tcycles - icycles >= 1 || icycles - tcycles >= 1) + return NULL; + seconds = icycles; + seconds *= YEARSPERREPEAT; + seconds *= AVGSECSPERYEAR; + if (t < sp->ats[0]) + newt += seconds; + else newt -= seconds; + if (newt < sp->ats[0] || + newt > sp->ats[sp->timecnt - 1]) + return NULL; /* "cannot happen" */ + result = localsub(&newt, offset, tmp, sp); + if (result == tmp) { + register time_t newy; + + newy = tmp->tm_year; + if (t < sp->ats[0]) + newy -= icycles * YEARSPERREPEAT; + else newy += icycles * YEARSPERREPEAT; + tmp->tm_year = newy; + if (tmp->tm_year != newy) + return NULL; + } + return result; + } + if (sp->timecnt == 0 || t < sp->ats[0]) { + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) { + i = 0; + break; + } + } else { + register int lo = 1; + register int hi = sp->timecnt; + + while (lo < hi) { + register int mid = (lo + hi) >> 1; + + if (t < sp->ats[mid]) + hi = mid; + else lo = mid + 1; + } + i = (int) sp->types[lo - 1]; + } + ttisp = &sp->ttis[i]; + /* + ** To get (wrong) behavior that's compatible with System V Release 2.0 + ** you'd replace the statement below with + ** t += ttisp->tt_gmtoff; + ** timesub(&t, 0L, sp, tmp); + */ + result = timesub(&t, ttisp->tt_gmtoff, sp, tmp); + tmp->tm_isdst = ttisp->tt_isdst; +#ifdef HAVE_TM_GMTOFF + tmp->tm_gmtoff = ttisp->tt_gmtoff; +#endif + tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind]; +#ifdef TM_ZONE + tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind]; +#endif /* defined TM_ZONE */ + return result; +} + + +// ============================================================================ +#if 0 +struct tm * +localtime(timep) +const time_t * const timep; +{ + tzset(); + return localsub(timep, 0L, &tm); +} +#endif + +/* +** Re-entrant version of localtime. +*/ + +// ============================================================================ +void +localtime_tz(const time_t * const timep, struct tm * tmp, const char* tz) +{ + struct state st; + if (tzload(tz, &st, TRUE) != 0) { + // not sure what's best here, but for now, we fall back to gmt + gmtload(&st); + } + + localsub(timep, 0L, tmp, &st); +} + +/* +** gmtsub is to gmtime as localsub is to localtime. +*/ + +static struct tm * +gmtsub(timep, offset, tmp) +const time_t * const timep; +const long offset; +struct tm * const tmp; +{ + register struct tm * result; + + if (!gmt_is_set) { + gmt_is_set = TRUE; +#ifdef ALL_STATE + gmtptr = (struct state *) malloc(sizeof *gmtptr); + if (gmtptr != NULL) +#endif /* defined ALL_STATE */ + gmtload(gmtptr); + } + result = timesub(timep, offset, gmtptr, tmp); +#ifdef TM_ZONE + /* + ** Could get fancy here and deliver something such as + ** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero, + ** but this is no time for a treasure hunt. + */ + if (offset != 0) + tmp->TM_ZONE = wildabbr; + else { +#ifdef ALL_STATE + if (gmtptr == NULL) + tmp->TM_ZONE = gmt; + else tmp->TM_ZONE = gmtptr->chars; +#endif /* defined ALL_STATE */ +#ifndef ALL_STATE + tmp->TM_ZONE = gmtptr->chars; +#endif /* State Farm */ + } +#endif /* defined TM_ZONE */ + return result; +} + +// ============================================================================ +#if 0 +struct tm * +gmtime(timep) +const time_t * const timep; +{ + return gmtsub(timep, 0L, &tm); +} +#endif + +/* +* Re-entrant version of gmtime. +*/ + +// ============================================================================ +#if 0 +struct tm * +gmtime_r(timep, tmp) +const time_t * const timep; +struct tm * tmp; +{ + return gmtsub(timep, 0L, tmp); +} +#endif + +#ifdef STD_INSPIRED + +// ============================================================================ +#if 0 +struct tm * +offtime(timep, offset) +const time_t * const timep; +const long offset; +{ + return gmtsub(timep, offset, &tm); +} +#endif + +#endif /* defined STD_INSPIRED */ + +/* +** Return the number of leap years through the end of the given year +** where, to make the math easy, the answer for year zero is defined as zero. +*/ + +static int +leaps_thru_end_of(y) +register const int y; +{ + return (y >= 0) ? (y / 4 - y / 100 + y / 400) : + -(leaps_thru_end_of(-(y + 1)) + 1); +} + +static struct tm * +timesub(timep, offset, sp, tmp) +const time_t * const timep; +const long offset; +register const struct state * const sp; +register struct tm * const tmp; +{ + register const struct lsinfo * lp; + register time_t tdays; + register int idays; /* unsigned would be so 2003 */ + register long rem; + int y; + register const int * ip; + register long corr; + register int hit; + register int i; + + corr = 0; + hit = 0; +#ifdef ALL_STATE + i = (sp == NULL) ? 0 : sp->leapcnt; +#endif /* defined ALL_STATE */ +#ifndef ALL_STATE + i = sp->leapcnt; +#endif /* State Farm */ + while (--i >= 0) { + lp = &sp->lsis[i]; + if (*timep >= lp->ls_trans) { + if (*timep == lp->ls_trans) { + hit = ((i == 0 && lp->ls_corr > 0) || + lp->ls_corr > sp->lsis[i - 1].ls_corr); + if (hit) + while (i > 0 && + sp->lsis[i].ls_trans == + sp->lsis[i - 1].ls_trans + 1 && + sp->lsis[i].ls_corr == + sp->lsis[i - 1].ls_corr + 1) { + ++hit; + --i; + } + } + corr = lp->ls_corr; + break; + } + } + y = EPOCH_YEAR; + tdays = *timep / SECSPERDAY; + rem = *timep - tdays * SECSPERDAY; + while (tdays < 0 || tdays >= year_lengths[isleap(y)]) { + int newy; + register time_t tdelta; + register int idelta; + register int leapdays; + + tdelta = tdays / DAYSPERLYEAR; + idelta = tdelta; + if (tdelta - idelta >= 1 || idelta - tdelta >= 1) + return NULL; + if (idelta == 0) + idelta = (tdays < 0) ? -1 : 1; + newy = y; + if (increment_overflow(&newy, idelta)) + return NULL; + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + tdays -= ((time_t) newy - y) * DAYSPERNYEAR; + tdays -= leapdays; + y = newy; + } + { + register long seconds; + + seconds = tdays * SECSPERDAY + 0.5; + tdays = seconds / SECSPERDAY; + rem += seconds - tdays * SECSPERDAY; + } + /* + ** Given the range, we can now fearlessly cast... + */ + idays = tdays; + rem += offset - corr; + while (rem < 0) { + rem += SECSPERDAY; + --idays; + } + while (rem >= SECSPERDAY) { + rem -= SECSPERDAY; + ++idays; + } + while (idays < 0) { + if (increment_overflow(&y, -1)) + return NULL; + idays += year_lengths[isleap(y)]; + } + while (idays >= year_lengths[isleap(y)]) { + idays -= year_lengths[isleap(y)]; + if (increment_overflow(&y, 1)) + return NULL; + } + tmp->tm_year = y; + if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) + return NULL; + tmp->tm_yday = idays; + /* + ** The "extra" mods below avoid overflow problems. + */ + tmp->tm_wday = EPOCH_WDAY + + ((y - EPOCH_YEAR) % DAYSPERWEEK) * + (DAYSPERNYEAR % DAYSPERWEEK) + + leaps_thru_end_of(y - 1) - + leaps_thru_end_of(EPOCH_YEAR - 1) + + idays; + tmp->tm_wday %= DAYSPERWEEK; + if (tmp->tm_wday < 0) + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = (int) (rem / SECSPERHOUR); + rem %= SECSPERHOUR; + tmp->tm_min = (int) (rem / SECSPERMIN); + /* + ** A positive leap second requires a special + ** representation. This uses "... ??:59:60" et seq. + */ + tmp->tm_sec = (int) (rem % SECSPERMIN) + hit; + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = (int) (idays + 1); + tmp->tm_isdst = 0; +#ifdef TM_GMTOFF + tmp->TM_GMTOFF = offset; +#endif /* defined TM_GMTOFF */ + return tmp; +} + +// ============================================================================ +#if 0 +char * +ctime(timep) +const time_t * const timep; +{ +/* +** Section 4.12.3.2 of X3.159-1989 requires that +** The ctime function converts the calendar time pointed to by timer +** to local time in the form of a string. It is equivalent to +** asctime(localtime(timer)) +*/ + return asctime(localtime(timep)); +} +#endif + +// ============================================================================ +#if 0 +char * +ctime_r(timep, buf) +const time_t * const timep; +char * buf; +{ + struct tm mytm; + + return asctime_r(localtime_r(timep, &mytm), buf); +} +#endif + +/* +** Adapted from code provided by Robert Elz, who writes: +** The "best" way to do mktime I think is based on an idea of Bob +** Kridle's (so its said...) from a long time ago. +** It does a binary search of the time_t space. Since time_t's are +** just 32 bits, its a max of 32 iterations (even at 64 bits it +** would still be very reasonable). +*/ + +#ifndef WRONG +#define WRONG (-1) +#endif /* !defined WRONG */ + +/* +** Simplified normalize logic courtesy Paul Eggert. +*/ + +static int +increment_overflow(number, delta) +int * number; +int delta; +{ + unsigned number0 = (unsigned)*number; + unsigned number1 = (unsigned)(number0 + delta); + + *number = (int)number1; + + if (delta >= 0) { + return ((int)number1 < (int)number0); + } else { + return ((int)number1 > (int)number0); + } +} + +static int +long_increment_overflow(number, delta) +long * number; +int delta; +{ + unsigned long number0 = (unsigned long)*number; + unsigned long number1 = (unsigned long)(number0 + delta); + + *number = (long)number1; + + if (delta >= 0) { + return ((long)number1 < (long)number0); + } else { + return ((long)number1 > (long)number0); + } +} + +static int +normalize_overflow(tensptr, unitsptr, base) +int * const tensptr; +int * const unitsptr; +const int base; +{ + register int tensdelta; + + tensdelta = (*unitsptr >= 0) ? + (*unitsptr / base) : + (-1 - (-1 - *unitsptr) / base); + *unitsptr -= tensdelta * base; + return increment_overflow(tensptr, tensdelta); +} + +static int +long_normalize_overflow(tensptr, unitsptr, base) +long * const tensptr; +int * const unitsptr; +const int base; +{ + register int tensdelta; + + tensdelta = (*unitsptr >= 0) ? + (*unitsptr / base) : + (-1 - (-1 - *unitsptr) / base); + *unitsptr -= tensdelta * base; + return long_increment_overflow(tensptr, tensdelta); +} + +static int +tmcomp(atmp, btmp) +register const struct tm * const atmp; +register const struct tm * const btmp; +{ + register int result; + + if ((result = (atmp->tm_year - btmp->tm_year)) == 0 && + (result = (atmp->tm_mon - btmp->tm_mon)) == 0 && + (result = (atmp->tm_mday - btmp->tm_mday)) == 0 && + (result = (atmp->tm_hour - btmp->tm_hour)) == 0 && + (result = (atmp->tm_min - btmp->tm_min)) == 0) + result = atmp->tm_sec - btmp->tm_sec; + return result; +} + +static time_t +time2sub(tmp, funcp, offset, okayp, do_norm_secs, sp) +struct tm * const tmp; +struct tm * (* const funcp) P((const time_t*, long, struct tm*,const struct state *sp)); +const long offset; +int * const okayp; +const int do_norm_secs; +const struct state * sp; +{ + register int dir; + register int i, j; + register int saved_seconds; + register long li; + register time_t lo; + register time_t hi; + long y; + time_t newt; + time_t t; + struct tm yourtm, mytm; + + *okayp = FALSE; + yourtm = *tmp; + if (do_norm_secs) { + if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec, + SECSPERMIN)) + return WRONG; + } + if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR)) + return WRONG; + if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) + return WRONG; + y = yourtm.tm_year; + if (long_normalize_overflow(&y, &yourtm.tm_mon, MONSPERYEAR)) + return WRONG; + /* + ** Turn y into an actual year number for now. + ** It is converted back to an offset from TM_YEAR_BASE later. + */ + if (long_increment_overflow(&y, TM_YEAR_BASE)) + return WRONG; + while (yourtm.tm_mday <= 0) { + if (long_increment_overflow(&y, -1)) + return WRONG; + li = y + (1 < yourtm.tm_mon); + yourtm.tm_mday += year_lengths[isleap(li)]; + } + while (yourtm.tm_mday > DAYSPERLYEAR) { + li = y + (1 < yourtm.tm_mon); + yourtm.tm_mday -= year_lengths[isleap(li)]; + if (long_increment_overflow(&y, 1)) + return WRONG; + } + for ( ; ; ) { + i = mon_lengths[isleap(y)][yourtm.tm_mon]; + if (yourtm.tm_mday <= i) + break; + yourtm.tm_mday -= i; + if (++yourtm.tm_mon >= MONSPERYEAR) { + yourtm.tm_mon = 0; + if (long_increment_overflow(&y, 1)) + return WRONG; + } + } + if (long_increment_overflow(&y, -TM_YEAR_BASE)) + return WRONG; + yourtm.tm_year = y; + if (yourtm.tm_year != y) + return WRONG; + if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) + saved_seconds = 0; + else if (y + TM_YEAR_BASE < EPOCH_YEAR) { + /* + ** We can't set tm_sec to 0, because that might push the + ** time below the minimum representable time. + ** Set tm_sec to 59 instead. + ** This assumes that the minimum representable time is + ** not in the same minute that a leap second was deleted from, + ** which is a safer assumption than using 58 would be. + */ + if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN)) + return WRONG; + saved_seconds = yourtm.tm_sec; + yourtm.tm_sec = SECSPERMIN - 1; + } else { + saved_seconds = yourtm.tm_sec; + yourtm.tm_sec = 0; + } + /* + ** Do a binary search (this works whatever time_t's type is). + */ + if (!TYPE_SIGNED(time_t)) { + lo = 0; + hi = lo - 1; + } else if (!TYPE_INTEGRAL(time_t)) { + if (sizeof(time_t) > sizeof(float)) + hi = (time_t) DBL_MAX; + else hi = (time_t) FLT_MAX; + lo = -hi; + } else { + lo = 1; + for (i = 0; i < (int) TYPE_BIT(time_t) - 1; ++i) + lo *= 2; + hi = -(lo + 1); + } + for ( ; ; ) { + t = lo / 2 + hi / 2; + if (t < lo) + t = lo; + else if (t > hi) + t = hi; + if ((*funcp)(&t, offset, &mytm, sp) == NULL) { + /* + ** Assume that t is too extreme to be represented in + ** a struct tm; arrange things so that it is less + ** extreme on the next pass. + */ + dir = (t > 0) ? 1 : -1; + } else dir = tmcomp(&mytm, &yourtm); + if (dir != 0) { + if (t == lo) { + if (t == TIME_T_MAX) + return WRONG; + ++t; + ++lo; + } else if (t == hi) { + if (t == TIME_T_MIN) + return WRONG; + --t; + --hi; + } + if (lo > hi) + return WRONG; + if (dir > 0) + hi = t; + else lo = t; + continue; + } + if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) + break; + /* + ** Right time, wrong type. + ** Hunt for right time, right type. + ** It's okay to guess wrong since the guess + ** gets checked. + */ + /* + ** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's. + */ +#ifdef ALL_STATE + if (sp == NULL) + return WRONG; +#endif /* defined ALL_STATE */ + for (i = sp->typecnt - 1; i >= 0; --i) { + if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) + continue; + for (j = sp->typecnt - 1; j >= 0; --j) { + if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) + continue; + newt = t + sp->ttis[j].tt_gmtoff - + sp->ttis[i].tt_gmtoff; + if ((*funcp)(&newt, offset, &mytm, sp) == NULL) + continue; + if (tmcomp(&mytm, &yourtm) != 0) + continue; + if (mytm.tm_isdst != yourtm.tm_isdst) + continue; + /* + ** We have a match. + */ + t = newt; + goto label; + } + } + return WRONG; + } +label: + newt = t + saved_seconds; + if ((newt < t) != (saved_seconds < 0)) + return WRONG; + t = newt; + if ((*funcp)(&t, offset, tmp, sp)) + *okayp = TRUE; + return t; +} + +static time_t +time2(tmp, funcp, offset, okayp, sp) +struct tm * const tmp; +struct tm * (* const funcp) P((const time_t*, long, struct tm*, + const struct state* sp)); +const long offset; +int * const okayp; +const struct state * sp; +{ + time_t t; + + /* + ** First try without normalization of seconds + ** (in case tm_sec contains a value associated with a leap second). + ** If that fails, try with normalization of seconds. + */ + t = time2sub(tmp, funcp, offset, okayp, FALSE, sp); + return *okayp ? t : time2sub(tmp, funcp, offset, okayp, TRUE, sp); +} + +static time_t +time1(tmp, funcp, offset, sp) +struct tm * const tmp; +struct tm * (* const funcp) P((const time_t *, long, struct tm *, const struct state* sp)); +const long offset; +const struct state * sp; +{ + register time_t t; + register int samei, otheri; + register int sameind, otherind; + register int i; + register int nseen; + int seen[TZ_MAX_TYPES]; + int types[TZ_MAX_TYPES]; + int okay; + + if (tmp->tm_isdst > 1) + tmp->tm_isdst = 1; + t = time2(tmp, funcp, offset, &okay, sp); +#define PCTS 1 +#ifdef PCTS + /* + ** PCTS code courtesy Grant Sullivan. + */ + if (okay) + return t; + if (tmp->tm_isdst < 0) + tmp->tm_isdst = 0; /* reset to std and try again */ +#endif /* defined PCTS */ +#ifndef PCTS + if (okay || tmp->tm_isdst < 0) + return t; +#endif /* !defined PCTS */ + /* + ** We're supposed to assume that somebody took a time of one type + ** and did some math on it that yielded a "struct tm" that's bad. + ** We try to divine the type they started from and adjust to the + ** type they need. + */ + /* + ** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's. + */ +#ifdef ALL_STATE + if (sp == NULL) + return WRONG; +#endif /* defined ALL_STATE */ + for (i = 0; i < sp->typecnt; ++i) + seen[i] = FALSE; + nseen = 0; + for (i = sp->timecnt - 1; i >= 0; --i) + if (!seen[sp->types[i]]) { + seen[sp->types[i]] = TRUE; + types[nseen++] = sp->types[i]; + } + for (sameind = 0; sameind < nseen; ++sameind) { + samei = types[sameind]; + if (sp->ttis[samei].tt_isdst != tmp->tm_isdst) + continue; + for (otherind = 0; otherind < nseen; ++otherind) { + otheri = types[otherind]; + if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) + continue; + tmp->tm_sec += sp->ttis[otheri].tt_gmtoff - + sp->ttis[samei].tt_gmtoff; + tmp->tm_isdst = !tmp->tm_isdst; + t = time2(tmp, funcp, offset, &okay, sp); + if (okay) + return t; + tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff - + sp->ttis[samei].tt_gmtoff; + tmp->tm_isdst = !tmp->tm_isdst; + } + } + return WRONG; +} + +// ============================================================================ +time_t +mktime_tz(struct tm * const tmp, char const * tz) +{ + struct state st; + if (tzload(tz, &st, TRUE) != 0) { + // not sure what's best here, but for now, we fall back to gmt + gmtload(&st); + } + return time1(tmp, localsub, 0L, &st); +} diff --git a/libcutils/uio.c b/libcutils/uio.c new file mode 100644 index 0000000..baa8051 --- /dev/null +++ b/libcutils/uio.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HAVE_SYS_UIO_H + +#include <cutils/uio.h> +#include <unistd.h> + +int readv( int fd, struct iovec* vecs, int count ) +{ + int total = 0; + + for ( ; count > 0; count--, vecs++ ) { + const char* buf = vecs->iov_base; + int len = vecs->iov_len; + + while (len > 0) { + int ret = read( fd, buf, len ); + if (ret < 0) { + if (total == 0) + total = -1; + goto Exit; + } + if (ret == 0) + goto Exit; + + total += ret; + buf += ret; + len -= ret; + } + } +Exit: + return total; +} + +int writev( int fd, const struct iovec* vecs, int count ) +{ + int total = 0; + + for ( ; count > 0; count--, vecs++ ) { + const char* buf = (const char*)vecs->iov_base; + int len = (int)vecs->iov_len; + + while (len > 0) { + int ret = write( fd, buf, len ); + if (ret < 0) { + if (total == 0) + total = -1; + goto Exit; + } + if (ret == 0) + goto Exit; + + total += ret; + buf += ret; + len -= ret; + } + } +Exit: + return total; +} + +#endif /* !HAVE_SYS_UIO_H */ diff --git a/libcutils/zygote.c b/libcutils/zygote.c new file mode 100644 index 0000000..aa060c0 --- /dev/null +++ b/libcutils/zygote.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Zygote" + +#include <cutils/sockets.h> +#include <cutils/zygote.h> +#include <cutils/log.h> + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> + +#define ZYGOTE_SOCKET "zygote" + +#define ZYGOTE_RETRY_COUNT 1000 +#define ZYGOTE_RETRY_MILLIS 500 + +static void replace_nl(char *str); + +/* + * If sendStdio is non-zero, the current process's stdio file descriptors + * will be sent and inherited by the spawned process. + */ +static int send_request(int fd, int sendStdio, int argc, const char **argv) +{ +#ifndef HAVE_ANDROID_OS + // not supported on simulator targets + //LOGE("zygote_* not supported on simulator targets"); + return -1; +#else /* HAVE_ANDROID_OS */ + uint32_t pid; + int i; + struct iovec ivs[2]; + struct msghdr msg; + char argc_buffer[12]; + const char *newline_string = "\n"; + struct cmsghdr *cmsg; + char msgbuf[CMSG_SPACE(sizeof(int) * 3)]; + int *cmsg_payload; + ssize_t ret; + + memset(&msg, 0, sizeof(msg)); + memset(&ivs, 0, sizeof(ivs)); + + // First line is arg count + snprintf(argc_buffer, sizeof(argc_buffer), "%d\n", argc); + + ivs[0].iov_base = argc_buffer; + ivs[0].iov_len = strlen(argc_buffer); + + msg.msg_iov = ivs; + msg.msg_iovlen = 1; + + if (sendStdio != 0) { + // Pass the file descriptors with the first write + msg.msg_control = msgbuf; + msg.msg_controllen = sizeof msgbuf; + + cmsg = CMSG_FIRSTHDR(&msg); + + cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + cmsg_payload = (int *)CMSG_DATA(cmsg); + cmsg_payload[0] = STDIN_FILENO; + cmsg_payload[1] = STDOUT_FILENO; + cmsg_payload[2] = STDERR_FILENO; + } + + do { + ret = sendmsg(fd, &msg, MSG_NOSIGNAL); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + return -1; + } + + // Only send the fd's once + msg.msg_control = NULL; + msg.msg_controllen = 0; + + // replace any newlines with spaces and send the args + for (i = 0; i < argc; i++) { + char *tofree = NULL; + const char *toprint; + + toprint = argv[i]; + + if (strchr(toprint, '\n') != NULL) { + tofree = strdup(toprint); + toprint = tofree; + replace_nl(tofree); + } + + ivs[0].iov_base = (char *)toprint; + ivs[0].iov_len = strlen(toprint); + ivs[1].iov_base = (char *)newline_string; + ivs[1].iov_len = 1; + + msg.msg_iovlen = 2; + + do { + ret = sendmsg(fd, &msg, MSG_NOSIGNAL); + } while (ret < 0 && errno == EINTR); + + if (tofree != NULL) { + free(tofree); + } + + if (ret < 0) { + return -1; + } + } + + // Read the pid, as a 4-byte network-order integer + + ivs[0].iov_base = &pid; + ivs[0].iov_len = sizeof(pid); + msg.msg_iovlen = 1; + + do { + do { + ret = recvmsg(fd, &msg, MSG_NOSIGNAL | MSG_WAITALL); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + return -1; + } + + ivs[0].iov_len -= ret; + ivs[0].iov_base += ret; + } while (ivs[0].iov_len > 0); + + pid = ntohl(pid); + + return pid; +#endif /* HAVE_ANDROID_OS */ +} + +int zygote_run_wait(int argc, const char **argv, void (*post_run_func)(int)) +{ + int fd; + int pid; + int err; + const char *newargv[argc + 1]; + + fd = socket_local_client(ZYGOTE_SOCKET, + ANDROID_SOCKET_NAMESPACE_RESERVED, AF_LOCAL); + + if (fd < 0) { + return -1; + } + + // The command socket is passed to the peer as close-on-exec + // and will close when the peer dies + newargv[0] = "--peer-wait"; + memcpy(newargv + 1, argv, argc * sizeof(*argv)); + + pid = send_request(fd, 1, argc + 1, newargv); + + if (pid > 0 && post_run_func != NULL) { + post_run_func(pid); + } + + // Wait for socket to close + do { + int dummy; + err = read(fd, &dummy, sizeof(dummy)); + } while ((err < 0 && errno == EINTR) || err != 0); + + do { + err = close(fd); + } while (err < 0 && errno == EINTR); + + return 0; +} + +/** + * Spawns a new dalvik instance via the Zygote process. The non-zygote + * arguments are passed to com.android.internal.os.RuntimeInit(). The + * first non-option argument should be a class name in the system class path. + * + * The arg list may start with zygote params such as --set-uid. + * + * If sendStdio is non-zero, the current process's stdio file descriptors + * will be sent and inherited by the spawned process. + * + * The pid of the child process is returned, or -1 if an error was + * encountered. + * + * zygote_run_oneshot waits up to ZYGOTE_RETRY_COUNT * + * ZYGOTE_RETRY_MILLIS for the zygote socket to be available. + */ +int zygote_run_oneshot(int sendStdio, int argc, const char **argv) +{ + int fd = -1; + int err; + int i; + int retries; + int pid; + const char **newargv = argv; + const int newargc = argc; + + for (retries = 0; (fd < 0) && (retries < ZYGOTE_RETRY_COUNT); retries++) { + if (retries > 0) { + struct timespec ts; + + memset(&ts, 0, sizeof(ts)); + ts.tv_nsec = ZYGOTE_RETRY_MILLIS * 1000 * 1000; + + do { + err = nanosleep (&ts, &ts); + } while (err < 0 && errno == EINTR); + } + fd = socket_local_client(ZYGOTE_SOCKET, AF_LOCAL, + ANDROID_SOCKET_NAMESPACE_RESERVED); + } + + if (fd < 0) { + return -1; + } + + pid = send_request(fd, 0, newargc, newargv); + + do { + err = close(fd); + } while (err < 0 && errno == EINTR); + + return pid; +} + +/** + * Replaces all occurrances of newline with space. + */ +static void replace_nl(char *str) +{ + for(; *str; str++) { + if (*str == '\n') { + *str = ' '; + } + } +} + + + diff --git a/libzipfile/Android.mk b/libzipfile/Android.mk new file mode 100644 index 0000000..d2d758c --- /dev/null +++ b/libzipfile/Android.mk @@ -0,0 +1,48 @@ +LOCAL_PATH:= $(call my-dir) + +# build host static library +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + centraldir.c \ + zipfile.c + +LOCAL_STATIC_LIBRARIES := \ + libunz + +LOCAL_MODULE:= libzipfile + +LOCAL_C_INCLUDES += external/zlib + +include $(BUILD_HOST_STATIC_LIBRARY) + +# build device static library +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + centraldir.c \ + zipfile.c + +LOCAL_STATIC_LIBRARIES := \ + libunz + +LOCAL_MODULE:= libzipfile + +LOCAL_C_INCLUDES += external/zlib + +include $(BUILD_STATIC_LIBRARY) + + +# build test_zipfile +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + test_zipfile.c + +LOCAL_STATIC_LIBRARIES := libzipfile libunz + +LOCAL_MODULE := test_zipfile + +LOCAL_C_INCLUDES += external/zlib + +include $(BUILD_HOST_EXECUTABLE) diff --git a/libzipfile/MODULE_LICENSE_APACHE2 b/libzipfile/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/libzipfile/MODULE_LICENSE_APACHE2 diff --git a/libzipfile/Makefile.am b/libzipfile/Makefile.am new file mode 100644 index 0000000..6479658 --- /dev/null +++ b/libzipfile/Makefile.am @@ -0,0 +1,25 @@ +AUTOMAKE_OPTIONS = 1.4 foreign +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I. \ +-I$(top_builddir) \ +-I$(top_srcdir) \ +-I$(top_srcdir)/src/lib \ +-I$(top_builddir)/src/lib \ +-I$(top_srcdir)/include \ +-I$(top_builddir)/include + +lib_LTLIBRARIES = libzipfile.la + +libzipfile_la_SOURCES = \ +private.h \ +centraldir.c \ +zipfile.c + +libzipfile_la_CFLAGS = +libzipfile_la_LIBADD = +libzipfile_la_LDFLAGS = + +docdir = $(datadir)/doc/zipfile +doc_DATA = NOTICE
\ No newline at end of file diff --git a/libzipfile/NOTICE b/libzipfile/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/libzipfile/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/libzipfile/centraldir.c b/libzipfile/centraldir.c new file mode 100644 index 0000000..0e264a3 --- /dev/null +++ b/libzipfile/centraldir.c @@ -0,0 +1,253 @@ +#include "private.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+enum {
+ // finding the directory
+ CD_SIGNATURE = 0x06054b50,
+ EOCD_LEN = 22, // EndOfCentralDir len, excl. comment
+ MAX_COMMENT_LEN = 65535,
+ MAX_EOCD_SEARCH = MAX_COMMENT_LEN + EOCD_LEN,
+
+ // central directory entries
+ ENTRY_SIGNATURE = 0x02014b50,
+ ENTRY_LEN = 46, // CentralDirEnt len, excl. var fields
+
+ // local file header
+ LFH_SIZE = 30,
+};
+
+unsigned int
+read_le_int(const unsigned char* buf)
+{
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+}
+
+unsigned int
+read_le_short(const unsigned char* buf)
+{
+ return buf[0] | (buf[1] << 8);
+}
+
+static int
+read_central_dir_values(Zipfile* file, const unsigned char* buf, int len)
+{
+ if (len < EOCD_LEN) {
+ // looks like ZIP file got truncated
+ fprintf(stderr, " Zip EOCD: expected >= %d bytes, found %d\n",
+ EOCD_LEN, len);
+ return -1;
+ }
+
+ file->disknum = read_le_short(&buf[0x04]);
+ file->diskWithCentralDir = read_le_short(&buf[0x06]);
+ file->entryCount = read_le_short(&buf[0x08]);
+ file->totalEntryCount = read_le_short(&buf[0x0a]);
+ file->centralDirSize = read_le_int(&buf[0x0c]);
+ file->centralDirOffest = read_le_int(&buf[0x10]);
+ file->commentLen = read_le_short(&buf[0x14]);
+
+ if (file->commentLen > 0) {
+ if (EOCD_LEN + file->commentLen > len) {
+ fprintf(stderr, "EOCD(%d) + comment(%d) exceeds len (%d)\n",
+ EOCD_LEN, file->commentLen, len);
+ return -1;
+ }
+ file->comment = buf + EOCD_LEN;
+ }
+
+ return 0;
+}
+
+static int
+read_central_directory_entry(Zipfile* file, Zipentry* entry,
+ const unsigned char** buf, ssize_t* len)
+{
+ const unsigned char* p;
+
+ unsigned short versionMadeBy;
+ unsigned short versionToExtract;
+ unsigned short gpBitFlag;
+ unsigned short compressionMethod;
+ unsigned short lastModFileTime;
+ unsigned short lastModFileDate;
+ unsigned long crc32;
+ unsigned short extraFieldLength;
+ unsigned short fileCommentLength;
+ unsigned short diskNumberStart;
+ unsigned short internalAttrs;
+ unsigned long externalAttrs;
+ unsigned long localHeaderRelOffset;
+ const unsigned char* extraField;
+ const unsigned char* fileComment;
+ unsigned int dataOffset;
+ unsigned short lfhExtraFieldSize;
+
+
+ p = *buf;
+
+ if (*len < ENTRY_LEN) {
+ fprintf(stderr, "cde entry not large enough\n");
+ return -1;
+ }
+
+ if (read_le_int(&p[0x00]) != ENTRY_SIGNATURE) {
+ fprintf(stderr, "Whoops: didn't find expected signature\n");
+ return -1;
+ }
+
+ versionMadeBy = read_le_short(&p[0x04]);
+ versionToExtract = read_le_short(&p[0x06]);
+ gpBitFlag = read_le_short(&p[0x08]);
+ entry->compressionMethod = read_le_short(&p[0x0a]);
+ lastModFileTime = read_le_short(&p[0x0c]);
+ lastModFileDate = read_le_short(&p[0x0e]);
+ crc32 = read_le_int(&p[0x10]);
+ entry->compressedSize = read_le_int(&p[0x14]);
+ entry->uncompressedSize = read_le_int(&p[0x18]);
+ entry->fileNameLength = read_le_short(&p[0x1c]);
+ extraFieldLength = read_le_short(&p[0x1e]);
+ fileCommentLength = read_le_short(&p[0x20]);
+ diskNumberStart = read_le_short(&p[0x22]);
+ internalAttrs = read_le_short(&p[0x24]);
+ externalAttrs = read_le_int(&p[0x26]);
+ localHeaderRelOffset = read_le_int(&p[0x2a]);
+
+ p += ENTRY_LEN;
+
+ // filename
+ if (entry->fileNameLength != 0) {
+ entry->fileName = p;
+ } else {
+ entry->fileName = NULL;
+ }
+ p += entry->fileNameLength;
+
+ // extra field
+ if (extraFieldLength != 0) {
+ extraField = p;
+ } else {
+ extraField = NULL;
+ }
+ p += extraFieldLength;
+
+ // comment, if any
+ if (fileCommentLength != 0) {
+ fileComment = p;
+ } else {
+ fileComment = NULL;
+ }
+ p += fileCommentLength;
+
+ *buf = p;
+
+ // the size of the extraField in the central dir is how much data there is,
+ // but the one in the local file header also contains some padding.
+ p = file->buf + localHeaderRelOffset;
+ extraFieldLength = read_le_short(&p[0x1c]);
+
+ dataOffset = localHeaderRelOffset + LFH_SIZE
+ + entry->fileNameLength + extraFieldLength;
+ entry->data = file->buf + dataOffset;
+#if 0
+ printf("file->buf=%p entry->data=%p dataOffset=%x localHeaderRelOffset=%d "
+ "entry->fileNameLength=%d extraFieldLength=%d\n",
+ file->buf, entry->data, dataOffset, localHeaderRelOffset,
+ entry->fileNameLength, extraFieldLength);
+#endif
+ return 0;
+}
+
+/*
+ * Find the central directory and read the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end. In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards. The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly. If the wrong value ends up in the EOCD
+ * area, we're hosed. This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+int
+read_central_dir(Zipfile *file)
+{
+ int err;
+
+ const unsigned char* buf = file->buf;
+ ssize_t bufsize = file->bufsize;
+ const unsigned char* eocd;
+ const unsigned char* p;
+ const unsigned char* start;
+ ssize_t len;
+ int i;
+
+ // too small to be a ZIP archive?
+ if (bufsize < EOCD_LEN) {
+ fprintf(stderr, "Length is %d -- too small\n", bufsize);
+ goto bail;
+ }
+
+ // find the end-of-central-dir magic
+ if (bufsize > MAX_EOCD_SEARCH) {
+ start = buf + bufsize - MAX_EOCD_SEARCH;
+ } else {
+ start = buf;
+ }
+ p = buf + bufsize - 4;
+ while (p >= start) {
+ if (*p == 0x50 && read_le_int(p) == CD_SIGNATURE) {
+ eocd = p;
+ break;
+ }
+ p--;
+ }
+ if (p < start) {
+ fprintf(stderr, "EOCD not found, not Zip\n");
+ goto bail;
+ }
+
+ // extract eocd values
+ err = read_central_dir_values(file, eocd, (buf+bufsize)-eocd);
+ if (err != 0) {
+ goto bail;
+ }
+
+ if (file->disknum != 0
+ || file->diskWithCentralDir != 0
+ || file->entryCount != file->totalEntryCount) {
+ fprintf(stderr, "Archive spanning not supported\n");
+ goto bail;
+ }
+
+ // Loop through and read the central dir entries.
+ p = buf + file->centralDirOffest;
+ len = (buf+bufsize)-p;
+ for (i=0; i < file->totalEntryCount; i++) {
+ Zipentry* entry = malloc(sizeof(Zipentry));
+ memset(entry, 0, sizeof(Zipentry));
+
+ err = read_central_directory_entry(file, entry, &p, &len);
+ if (err != 0) {
+ fprintf(stderr, "read_central_directory_entry failed\n");
+ free(entry);
+ goto bail;
+ }
+
+ // add it to our list
+ entry->next = file->entries;
+ file->entries = entry;
+ }
+
+ return 0;
+bail:
+ return -1;
+}
diff --git a/libzipfile/private.h b/libzipfile/private.h new file mode 100644 index 0000000..06f788d --- /dev/null +++ b/libzipfile/private.h @@ -0,0 +1,45 @@ +#ifndef PRIVATE_H
+#define PRIVATE_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct Zipentry {
+ unsigned long fileNameLength;
+ const unsigned char* fileName;
+ unsigned short compressionMethod;
+ unsigned int uncompressedSize;
+ unsigned int compressedSize;
+ const unsigned char* data;
+
+ struct Zipentry* next;
+} Zipentry;
+
+typedef struct Zipfile
+{
+ const unsigned char *buf;
+ ssize_t bufsize;
+
+ // Central directory
+ unsigned short disknum; //mDiskNumber;
+ unsigned short diskWithCentralDir; //mDiskWithCentralDir;
+ unsigned short entryCount; //mNumEntries;
+ unsigned short totalEntryCount; //mTotalNumEntries;
+ unsigned int centralDirSize; //mCentralDirSize;
+ unsigned int centralDirOffest; // offset from first disk //mCentralDirOffset;
+ unsigned short commentLen; //mCommentLen;
+ const unsigned char* comment; //mComment;
+
+ Zipentry* entries;
+} Zipfile;
+
+int read_central_dir(Zipfile* file);
+
+unsigned int read_le_int(const unsigned char* buf);
+unsigned int read_le_short(const unsigned char* buf);
+
+#endif // PRIVATE_H
+
diff --git a/libzipfile/test_zipfile.c b/libzipfile/test_zipfile.c new file mode 100644 index 0000000..40840ec --- /dev/null +++ b/libzipfile/test_zipfile.c @@ -0,0 +1,92 @@ +#include <zipfile/zipfile.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void dump_zipfile(FILE* to, zipfile_t file);
+
+int
+main(int argc, char** argv)
+{
+ FILE* f;
+ size_t size, unsize;
+ void* buf;
+ void* scratch;
+ zipfile_t zip;
+ zipentry_t entry;
+ int err;
+ enum { HUH, LIST, UNZIP } what = HUH;
+
+ if (strcmp(argv[2], "-l") == 0 && argc == 3) {
+ what = LIST;
+ }
+ else if (strcmp(argv[2], "-u") == 0 && argc == 5) {
+ what = UNZIP;
+ }
+ else {
+ fprintf(stderr, "usage: test_zipfile ZIPFILE -l\n"
+ " lists the files in the zipfile\n"
+ " test_zipfile ZIPFILE -u FILENAME SAVETO\n"
+ " saves FILENAME from the zip file into SAVETO\n");
+ return 1;
+ }
+
+ f = fopen(argv[1], "r");
+ if (f == NULL) {
+ fprintf(stderr, "couldn't open %s\n", argv[1]);
+ return 1;
+ }
+
+ fseek(f, 0, SEEK_END);
+ size = ftell(f);
+ rewind(f);
+
+ buf = malloc(size);
+ fread(buf, 1, size, f);
+
+ zip = init_zipfile(buf, size);
+ if (zip == NULL) {
+ fprintf(stderr, "inti_zipfile failed\n");
+ return 1;
+ }
+
+ fclose(f);
+
+
+ switch (what)
+ {
+ case LIST:
+ dump_zipfile(stdout, zip);
+ break;
+ case UNZIP:
+ entry = lookup_zipentry(zip, argv[3]);
+ if (entry == NULL) {
+ fprintf(stderr, "zip file '%s' does not contain file '%s'\n",
+ argv[1], argv[1]);
+ return 1;
+ }
+ f = fopen(argv[4], "w");
+ if (f == NULL) {
+ fprintf(stderr, "can't open file for writing '%s'\n", argv[4]);
+ return 1;
+ }
+ unsize = get_zipentry_size(entry);
+ size = unsize * 1.001;
+ scratch = malloc(size);
+ printf("scratch=%p\n", scratch);
+ err = decompress_zipentry(entry, scratch, size);
+ if (err != 0) {
+ fprintf(stderr, "error decompressing file\n");
+ return 1;
+ }
+ fwrite(scratch, unsize, 1, f);
+ free(scratch);
+ fclose(f);
+ break;
+ }
+
+ free(buf);
+
+ return 0;
+}
+
diff --git a/libzipfile/zipfile.c b/libzipfile/zipfile.c new file mode 100644 index 0000000..a401a9b --- /dev/null +++ b/libzipfile/zipfile.c @@ -0,0 +1,160 @@ +#include <zipfile/zipfile.h> + +#include "private.h" +#include <stdlib.h> +#include <string.h> +#include <zlib.h> +#define DEF_MEM_LEVEL 8 // normally in zutil.h? + +zipfile_t +init_zipfile(const void* data, size_t size) +{ + int err; + + Zipfile *file = malloc(sizeof(Zipfile)); + if (file == NULL) return NULL; + memset(file, 0, sizeof(Zipfile)); + file->buf = data; + file->bufsize = size; + + err = read_central_dir(file); + if (err != 0) goto fail; + + return file; +fail: + free(file); + return NULL; +} + +void +release_zipfile(zipfile_t f) +{ + Zipfile* file = (Zipfile*)f; + Zipentry* entry = file->entries; + while (entry) { + Zipentry* next = entry->next; + free(entry); + entry = next; + } + free(file); +} + +zipentry_t +lookup_zipentry(zipfile_t f, const char* entryName) +{ + Zipfile* file = (Zipfile*)f; + Zipentry* entry = file->entries; + while (entry) { + if (0 == memcmp(entryName, entry->fileName, entry->fileNameLength)) { + return entry; + } + entry = entry->next; + } + return NULL; +} + +size_t +get_zipentry_size(zipentry_t entry) +{ + return ((Zipentry*)entry)->uncompressedSize; +} + +char* +get_zipentry_name(zipentry_t entry) +{ + Zipentry* e = (Zipentry*)entry; + int l = e->fileNameLength; + char* s = malloc(l+1); + memcpy(s, e->fileName, l); + s[l] = '\0'; + return s; +} + +enum { + STORED = 0, + DEFLATED = 8 +}; + +static int +uninflate(unsigned char* out, int unlen, const unsigned char* in, int clen) +{ + z_stream zstream; + unsigned long crc; + int err = 0; + int zerr; + + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = (void*)in; + zstream.avail_in = clen; + zstream.next_out = (Bytef*) out; + zstream.avail_out = unlen; + zstream.data_type = Z_UNKNOWN; + + // Use the undocumented "negative window bits" feature to tell zlib + // that there's no zlib header waiting for it. + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + return -1; + } + + // uncompress the data + zerr = inflate(&zstream, Z_FINISH); + if (zerr != Z_STREAM_END) { + fprintf(stderr, "zerr=%d Z_STREAM_END=%d total_out=%lu\n", zerr, Z_STREAM_END, + zstream.total_out); + err = -1; + } + + inflateEnd(&zstream); + return err; +} + +int +decompress_zipentry(zipentry_t e, void* buf, int bufsize) +{ + Zipentry* entry = (Zipentry*)e; + switch (entry->compressionMethod) + { + case STORED: + memcpy(buf, entry->data, entry->uncompressedSize); + return 0; + case DEFLATED: + return uninflate(buf, bufsize, entry->data, entry->compressedSize); + default: + return -1; + } +} + +void +dump_zipfile(FILE* to, zipfile_t file) +{ + Zipfile* zip = (Zipfile*)file; + Zipentry* entry = zip->entries; + int i; + + fprintf(to, "entryCount=%d\n", zip->entryCount); + for (i=0; i<zip->entryCount; i++) { + fprintf(to, " file \""); + fwrite(entry->fileName, entry->fileNameLength, 1, to); + fprintf(to, "\"\n"); + entry = entry->next; + } +} + +zipentry_t +iterate_zipfile(zipfile_t file, void** cookie) +{ + Zipentry* entry = (Zipentry*)*cookie; + if (entry == NULL) { + Zipfile* zip = (Zipfile*)file; + *cookie = zip->entries; + return *cookie; + } else { + entry = entry->next; + *cookie = entry; + return entry; + } +} |