|  | /* | 
|  | * Copyright 2007 The Android Open Source Project | 
|  | * | 
|  | * Fake device support. | 
|  | */ | 
|  | /* | 
|  | Implementation notes: | 
|  |  | 
|  | There are a couple of basic scenarios, exemplified by the "fb" and | 
|  | "events" devices.  The framebuffer driver is pretty simple, handling a | 
|  | few ioctl()s and managing a stretch of memory.  We can just intercept a | 
|  | few calls.  The input event driver can be used in a select() or poll() | 
|  | call with other file descriptors, which either requires us to do some | 
|  | fancy tricks with select() and poll(), or requires that we return a real | 
|  | file descriptor (perhaps based on a socketpair). | 
|  |  | 
|  | We have three basic approaches to dealing with "fake" file descriptors: | 
|  |  | 
|  | (1) Always use real fds.  We can dup() an open /dev/null to get a number | 
|  | for the cases where we don't need a socketpair. | 
|  | (2) Always use fake fds with absurdly high numeric values.  Testing to see | 
|  | if the fd is one we handle is trivial (range check).  This doesn't | 
|  | work for select(), which uses fd bitmaps accessed through macros. | 
|  | (3) Use a mix of real and fake fds, in a high range (512-1023).  Because | 
|  | it's in the "real" range, we can pass real fds around for things that | 
|  | are handed to poll() and select(), but because of the high numeric | 
|  | value we *should* be able to get away with a trivial range check. | 
|  |  | 
|  | Approach (1) is the most portable and least likely to break, but the | 
|  | efficiencies gained in approach (2) make it more desirable.  There is | 
|  | a small risk of application fds wandering into our range, but we can | 
|  | minimize that by asserting on a "guard zone" and/or obstructing dup2(). | 
|  | (We can also dup2(/dev/null) to "reserve" our fds, but that wastes | 
|  | resources.) | 
|  | */ | 
|  |  | 
|  | #include "Common.h" | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/socket.h> | 
|  | #include <assert.h> | 
|  | #include <fnmatch.h> | 
|  |  | 
|  | /* | 
|  | * Devices we intercept. | 
|  | * | 
|  | * Needed: | 
|  | *  /dev/alarm | 
|  | *  radio | 
|  | */ | 
|  | typedef FakeDev* (*wsFileHook)(const char *path, int flags); | 
|  |  | 
|  | typedef struct FakedPath { | 
|  | const char *pathexpr; | 
|  | wsFileHook hook; | 
|  | } FakedPath; | 
|  |  | 
|  | FakedPath fakedpaths[] = | 
|  | { | 
|  | { "/dev/graphics/fb0",      wsOpenDevFb }, | 
|  | { "/dev/hw3d",              NULL }, | 
|  | { "/dev/eac",               wsOpenDevAudio }, | 
|  | { "/dev/tty0",              wsOpenDevConsoleTty }, | 
|  | { "/dev/input/event0",      wsOpenDevEvent }, | 
|  | { "/dev/input/*",           NULL }, | 
|  | { "/dev/log/*",             wsOpenDevLog }, | 
|  | { "/sys/class/power_supply/*", wsOpenDevPower }, | 
|  | { "/sys/power/state",       wsOpenSysPower }, | 
|  | { "/sys/power/wake_lock",   wsOpenSysPower }, | 
|  | { "/sys/power/wake_unlock", wsOpenSysPower }, | 
|  | { "/sys/devices/platform/android-vibrator/enable",  wsOpenDevVibrator }, | 
|  | { "/sys/qemu_trace/*",      NULL }, | 
|  | { NULL,                     NULL } | 
|  | }; | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Generic drop-in for an unimplemented call. | 
|  | * | 
|  | * Returns -1, which conveniently is the same as MAP_FAILED for mmap. | 
|  | */ | 
|  | static int notImplemented(FakeDev* dev, const char* callName) | 
|  | { | 
|  | wsLog("WARNING: unimplemented %s() on '%s' %p\n", | 
|  | callName, dev->debugName, dev->state); | 
|  | errno = kNoHandlerError; | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Default implementations.  We want to log as much information as we can | 
|  | * so that we can fill in the missing implementation. | 
|  | * | 
|  | * TODO: for some or all of these we will want to display the full arg list. | 
|  | */ | 
|  | static int noClose(FakeDev* dev, ...) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  | static FakeDev* noDup(FakeDev* dev, ...) | 
|  | { | 
|  | notImplemented(dev, "dup"); | 
|  | return NULL; | 
|  | } | 
|  | static int noRead(FakeDev* dev, ...) | 
|  | { | 
|  | return notImplemented(dev, "read"); | 
|  | } | 
|  | static int noReadv(FakeDev* dev, ...) | 
|  | { | 
|  | return notImplemented(dev, "readv"); | 
|  | } | 
|  | static int noWrite(FakeDev* dev, ...) | 
|  | { | 
|  | return notImplemented(dev, "write"); | 
|  | } | 
|  | static int noWritev(FakeDev* dev, ...) | 
|  | { | 
|  | return notImplemented(dev, "writev"); | 
|  | } | 
|  | static int noMmap(FakeDev* dev, ...) | 
|  | { | 
|  | return notImplemented(dev, "mmap"); | 
|  | } | 
|  | static int noIoctl(FakeDev* dev, ...) | 
|  | { | 
|  | return notImplemented(dev, "ioctl"); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Create a new FakeDev entry. | 
|  | * | 
|  | * We mark the fd slot as "used" in the bitmap, but don't add it to the | 
|  | * table yet since the entry is not fully prepared. | 
|  | */ | 
|  | FakeDev* wsCreateFakeDev(const char* debugName) | 
|  | { | 
|  | FakeDev* newDev; | 
|  | int cc; | 
|  |  | 
|  | assert(debugName != NULL); | 
|  |  | 
|  | newDev = (FakeDev*) calloc(1, sizeof(FakeDev)); | 
|  | if (newDev == NULL) | 
|  | return NULL; | 
|  |  | 
|  | newDev->debugName = strdup(debugName); | 
|  | newDev->state = NULL; | 
|  |  | 
|  | newDev->close = (Fake_close) noClose; | 
|  | newDev->dup = (Fake_dup) noDup; | 
|  | newDev->read = (Fake_read) noRead; | 
|  | newDev->readv = (Fake_readv) noReadv; | 
|  | newDev->write = (Fake_write) noWrite; | 
|  | newDev->writev = (Fake_writev) noWritev; | 
|  | newDev->mmap = (Fake_mmap) noMmap; | 
|  | newDev->ioctl = (Fake_ioctl) noIoctl; | 
|  |  | 
|  | /* | 
|  | * Allocate a new entry.  The bit vector map is really only used as a | 
|  | * performance boost in the current implementation. | 
|  | */ | 
|  | cc = pthread_mutex_lock(&gWrapSim.fakeFdLock); assert(cc == 0); | 
|  | int newfd = wsAllocBit(gWrapSim.fakeFdMap); | 
|  | cc = pthread_mutex_unlock(&gWrapSim.fakeFdLock); assert(cc == 0); | 
|  |  | 
|  | if (newfd < 0) { | 
|  | wsLog("WARNING: ran out of 'fake' file descriptors\n"); | 
|  | free(newDev); | 
|  | return NULL; | 
|  | } | 
|  | newDev->fd = newfd + kFakeFdBase; | 
|  | newDev->otherFd = -1; | 
|  | assert(gWrapSim.fakeFdList[newDev->fd - kFakeFdBase] == NULL); | 
|  |  | 
|  | return newDev; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Create a new FakeDev entry, and open a file descriptor that actually | 
|  | * works. | 
|  | */ | 
|  | FakeDev* wsCreateRealFakeDev(const char* debugName) | 
|  | { | 
|  | FakeDev* newDev = wsCreateFakeDev(debugName); | 
|  | if (newDev == NULL) | 
|  | return newDev; | 
|  |  | 
|  | int fds[2]; | 
|  |  | 
|  | if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { | 
|  | wsLog("socketpair() failed: %s\n", strerror(errno)); | 
|  | wsFreeFakeDev(newDev); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (dup2(fds[0], newDev->fd) < 0) { | 
|  | wsLog("dup2(%d,%d) failed: %s\n", | 
|  | fds[0], newDev->fd, strerror(errno)); | 
|  | wsFreeFakeDev(newDev); | 
|  | return NULL; | 
|  | } | 
|  | close(fds[0]); | 
|  |  | 
|  | /* okay to leave this one in the "normal" range; not visible to app */ | 
|  | newDev->otherFd = fds[1]; | 
|  |  | 
|  | return newDev; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Free fake device entry. | 
|  | */ | 
|  | void wsFreeFakeDev(FakeDev* dev) | 
|  | { | 
|  | if (dev == NULL) | 
|  | return; | 
|  |  | 
|  | wsLog("## closing/freeing '%s' (%d/%d)\n", | 
|  | dev->debugName, dev->fd, dev->otherFd); | 
|  |  | 
|  | /* | 
|  | * If we assigned a file descriptor slot, free it up. | 
|  | */ | 
|  | if (dev->fd >= 0) { | 
|  | int cc; | 
|  |  | 
|  | gWrapSim.fakeFdList[dev->fd - kFakeFdBase] = NULL; | 
|  |  | 
|  | cc = pthread_mutex_lock(&gWrapSim.fakeFdLock); assert(cc == 0); | 
|  | wsFreeBit(gWrapSim.fakeFdMap, dev->fd - kFakeFdBase); | 
|  | cc = pthread_mutex_unlock(&gWrapSim.fakeFdLock); assert(cc == 0); | 
|  | } | 
|  | if (dev->otherFd >= 0) | 
|  | close(dev->otherFd); | 
|  |  | 
|  | if (dev->debugName) free(dev->debugName); | 
|  | free(dev); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Map a file descriptor to a fake device. | 
|  | * | 
|  | * Returns NULL if there's no corresponding entry. | 
|  | */ | 
|  | FakeDev* wsFakeDevFromFd(int fd) | 
|  | { | 
|  | /* quick range test */ | 
|  | if (fd < kFakeFdBase || fd >= kFakeFdBase + kMaxFakeFdCount) | 
|  | return NULL; | 
|  |  | 
|  | return gWrapSim.fakeFdList[fd - kFakeFdBase]; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Check to see if we're opening a device that we want to fake out. | 
|  | * | 
|  | * We return a file descriptor >= 0 on success, -1 if we're not interested, | 
|  | * or -2 if we explicitly want to pretend that the device doesn't exist. | 
|  | */ | 
|  | int wsInterceptDeviceOpen(const char* pathName, int flags) | 
|  | { | 
|  | FakedPath* p = fakedpaths; | 
|  |  | 
|  | while (p->pathexpr) { | 
|  | if (fnmatch(p->pathexpr, pathName, 0) == 0) { | 
|  | if (p->hook != NULL) { | 
|  | FakeDev* dev = p->hook(pathName, flags); | 
|  | if (dev != NULL) { | 
|  | /* | 
|  | * Now that the device entry is ready, add it to the list. | 
|  | */ | 
|  | wsLog("## created fake dev %d: '%s' %p\n", | 
|  | dev->fd, dev->debugName, dev->state); | 
|  | gWrapSim.fakeFdList[dev->fd - kFakeFdBase] = dev; | 
|  | return dev->fd; | 
|  | } | 
|  | } else { | 
|  | wsLog("## rejecting attempt to open %s\n", pathName); | 
|  | errno = ENOENT; | 
|  | return -2; | 
|  | } | 
|  | break; | 
|  | } | 
|  | p++; | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check to see if we're accessing a device that we want to fake out. | 
|  | * Returns 0 if the device can be (fake) opened with the given mode, | 
|  | * -1 if it can't, -2 if it can't and we don't want to allow fallback | 
|  | * to the host-device either. | 
|  | * TODO: actually check the mode. | 
|  | */ | 
|  | int wsInterceptDeviceAccess(const char *pathName, int mode) | 
|  | { | 
|  | FakedPath *p = fakedpaths; | 
|  |  | 
|  | while (p->pathexpr) { | 
|  | if (fnmatch(p->pathexpr, pathName, 0) == 0) { | 
|  | if (p->hook) { | 
|  | return 0; | 
|  | } else { | 
|  | wsLog("## rejecting attempt to open %s\n", pathName); | 
|  | errno = ENOENT; | 
|  | return -2; | 
|  | } | 
|  | break; | 
|  | } | 
|  | p++; | 
|  | } | 
|  | errno = ENOENT; | 
|  | return -1; | 
|  | } |