321 lines
8.8 KiB
C
321 lines
8.8 KiB
C
/*
|
|
* 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;
|
|
}
|