diff --git a/third_party/ashmem/ashmem-dev.c b/third_party/ashmem/ashmem-dev.c index 52b3f47eeae0..25a33cdcd0c8 100644 --- a/third_party/ashmem/ashmem-dev.c +++ b/third_party/ashmem/ashmem-dev.c @@ -14,23 +14,115 @@ * 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 "ashmem.h" +#include +#include #include +#include #include +#include #include #include #include +#include /* for fdstat() */ #include #include -#include "ashmem.h" +#include -#define ASHMEM_DEVICE "/dev/ashmem" +#define ASHMEM_DEVICE "/dev/ashmem" + +/* Technical note regarding reading system properties. + * + * Try to use the new __system_property_read_callback API that appeared in + * Android O / API level 26 when available. Otherwise use the deprecated + * __system_property_get function. + * + * For more technical details from an NDK maintainer, see: + * https://bugs.chromium.org/p/chromium/issues/detail?id=392191#c17 + */ + +/* Weak symbol import */ +void __system_property_read_callback( + const prop_info* info, + void (*callback)( + void* cookie, const char* name, const char* value, uint32_t serial), + void* cookie) __attribute__((weak)); + +/* Callback used with __system_property_read_callback. */ +static void prop_read_int(void* cookie, + const char* name, + const char* value, + uint32_t serial) { + *(int *)cookie = atoi(value); + (void)name; + (void)serial; +} + +static int system_property_get_int(const char* name) { + int result = 0; + if (__system_property_read_callback) { + const prop_info* info = __system_property_find(name); + if (info) + __system_property_read_callback(info, &prop_read_int, &result); + } else { + char value[PROP_VALUE_MAX] = {}; + if (__system_property_get(name, value) >= 1) + result = atoi(value); + } + return result; +} + +static int device_api_level() { + static int s_api_level = -1; + if (s_api_level < 0) + s_api_level = system_property_get_int("ro.build.version.sdk"); + return s_api_level; +} + +typedef enum { + ASHMEM_STATUS_INIT, + ASHMEM_STATUS_NOT_SUPPORTED, + ASHMEM_STATUS_SUPPORTED, +} AshmemStatus; + +static AshmemStatus s_ashmem_status = ASHMEM_STATUS_INIT; +static dev_t s_ashmem_dev; + +/* Return the dev_t of a given file path, or 0 if not available, */ +static dev_t ashmem_find_dev(const char* path) { + struct stat st; + dev_t result = 0; + if (stat(path, &st) == 0 && S_ISCHR(st.st_mode)) + result = st.st_dev; + return result; +} + +static AshmemStatus ashmem_get_status(void) { + /* NOTE: No need to make this thread-safe, assuming that + * all threads will find the same value. */ + if (s_ashmem_status != ASHMEM_STATUS_INIT) + return s_ashmem_status; + + s_ashmem_dev = ashmem_find_dev(ASHMEM_DEVICE); + s_ashmem_status = (s_ashmem_dev == 0) ? ASHMEM_STATUS_NOT_SUPPORTED + : ASHMEM_STATUS_SUPPORTED; + return s_ashmem_status; +} + +/* Returns true iff the ashmem device ioctl should be used for a given fd. + * NOTE: Try not to use fstat() when possible to avoid performance issues. */ +static int ashmem_dev_fd_check(int fd) { + if (device_api_level() <= __ANDROID_API_O_MR1__) + return 1; + if (ashmem_get_status() == ASHMEM_STATUS_SUPPORTED) { + struct stat st; + return (fstat(fd, &st) == 0 && S_ISCHR(st.st_mode) && + st.st_dev != 0 && st.st_dev == s_ashmem_dev); + } + return 0; +} /* * ashmem_create_region - creates a new ashmem region and returns the file @@ -39,67 +131,133 @@ * `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; +static int ashmem_dev_create_region(const char *name, size_t size) { + int fd = open(ASHMEM_DEVICE, O_RDWR); + if (fd < 0) + return fd; - fd = open(ASHMEM_DEVICE, O_RDWR); - if (fd < 0) - return fd; + int ret; + 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; - if (name) { - char buf[ASHMEM_NAME_LEN]; + return fd; - strlcpy(buf, name, sizeof(buf)); - ret = ioctl(fd, ASHMEM_SET_NAME, buf); - if (ret < 0) - goto error; - } +error: + close(fd); + return ret; +} - ret = ioctl(fd, ASHMEM_SET_SIZE, size); - if (ret < 0) - goto error; +static int ashmem_dev_set_prot_region(int fd, int prot) { + return ioctl(fd, ASHMEM_SET_PROT_MASK, prot); +} - return fd; +static int ashmem_dev_get_prot_region(int fd) { + return ioctl(fd, ASHMEM_GET_PROT_MASK); +} -error: - close(fd); - return ret; +static int ashmem_dev_pin_region(int fd, size_t offset, size_t len) { + struct ashmem_pin pin = { offset, len }; + return ioctl(fd, ASHMEM_PIN, &pin); } -int ashmem_set_prot_region(int fd, int prot) -{ - return ioctl(fd, ASHMEM_SET_PROT_MASK, prot); +static int ashmem_dev_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_prot_region(int fd) -{ - return ioctl(fd, ASHMEM_GET_PROT_MASK); +static size_t ashmem_dev_get_size_region(int fd) { + return ioctl(fd, ASHMEM_GET_SIZE, NULL); } -int ashmem_pin_region(int fd, size_t offset, size_t len) -{ - struct ashmem_pin pin = { offset, len }; - return ioctl(fd, ASHMEM_PIN, &pin); +// Starting with API level 26, the following functions from +// libandroid.so should be used to create shared memory regions. +typedef int(*ASharedMemory_createFunc)(const char*, size_t); +typedef size_t(*ASharedMemory_getSizeFunc)(int fd); +typedef int(*ASharedMemory_setProtFunc)(int fd, int prot); + +// Function pointers to shared memory functions. +typedef struct { + ASharedMemory_createFunc create; + ASharedMemory_getSizeFunc getSize; + ASharedMemory_setProtFunc setProt; +} ASharedMemoryFuncs; + +const ASharedMemoryFuncs* ashmem_get_funcs() { + static ASharedMemoryFuncs s_ashmem_funcs = {}; + ASharedMemoryFuncs* funcs = &s_ashmem_funcs; + if (funcs->create == NULL) { + if (device_api_level() >= __ANDROID_API_O__) { + /* Leaked intentionally! */ + void* lib = dlopen("libandroid.so", RTLD_NOW); + funcs->create = (ASharedMemory_createFunc) + dlsym(lib, "ASharedMemory_create"); + funcs->getSize = (ASharedMemory_getSizeFunc) + dlsym(lib, "ASharedMemory_getSize"); + funcs->setProt = (ASharedMemory_setProtFunc) + dlsym(lib, "ASharedMemory_setProt"); + } else { + funcs->create = &ashmem_dev_create_region; + funcs->getSize = &ashmem_dev_get_size_region; + funcs->setProt = &ashmem_dev_set_prot_region; + } + } + return funcs; } -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_create_region(const char* name, size_t size) { + return ashmem_get_funcs()->create(name, size); } -int ashmem_get_size_region(int fd) -{ - return ioctl(fd, ASHMEM_GET_SIZE, NULL); +int ashmem_set_prot_region(int fd, int prot) { + return ashmem_get_funcs()->setProt(fd, prot); } -int ashmem_purge_all(void) -{ - const int fd = open(ASHMEM_DEVICE, O_RDWR); - if (fd < 0) - return fd; - const int ret = ioctl(fd, ASHMEM_PURGE_ALL_CACHES, 0); - close(fd); - return ret; +int ashmem_get_prot_region(int fd) { + if (ashmem_dev_fd_check(fd)) + return ashmem_dev_get_prot_region(fd); + /* There are only two practical values to return here: either + * PROT_READ|PROT_WRITE or just PROT_READ, so try to determine + * the flags by trying to mmap() the region read-write first. + */ + int result = PROT_READ; + const size_t page_size = (size_t)sysconf(_SC_PAGESIZE); + void* m = mmap(NULL, page_size, PROT_READ|PROT_WRITE, + MAP_PRIVATE, fd, 0); + if (m != MAP_FAILED) { + munmap(m, page_size); + result = PROT_READ|PROT_WRITE; + } + return result; +} + +int ashmem_pin_region(int fd, size_t offset, size_t len) { + if (ashmem_dev_fd_check(fd)) + return ashmem_dev_pin_region(fd, offset, len); + return ASHMEM_NOT_PURGED; +} + +int ashmem_unpin_region(int fd, size_t offset, size_t len) { + if (ashmem_dev_fd_check(fd)) + return ashmem_dev_unpin_region(fd, offset, len); + /* NOTE: It is not possible to use madvise() here because it requires a + * memory address. This could be done in the caller though, instead of + * this function. */ + return 0; +} + +int ashmem_get_size_region(int fd) { + /* NOTE: Original API returns an int. Avoid breaking it. */ + return (int)ashmem_get_funcs()->getSize(fd); +} + +int ashmem_device_is_supported(void) { + return ashmem_get_status() == ASHMEM_STATUS_SUPPORTED; } diff --git a/third_party/ashmem/ashmem.h b/third_party/ashmem/ashmem.h index d8afccbd2a6e..f3675c98b19a 100644 --- a/third_party/ashmem/ashmem.h +++ b/third_party/ashmem/ashmem.h @@ -16,13 +16,20 @@ extern "C" { #endif +/* Returns true if the ashmem device is supported on this device. + * Not that even if the device is not supported, + * ashmem_{create,set_prot,get_prot,get_size}_region() will still work + * because they will use the ASharedMemory functions from libandroid.so + * instead. But ashmem_{pin,unpin}_region() will be no-ops. + */ +int ashmem_device_is_supported(void); + int ashmem_create_region(const char *name, size_t size); int ashmem_set_prot_region(int fd, int prot); int ashmem_get_prot_region(int fd); 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); -int ashmem_purge_all(void); #ifdef __cplusplus }