C++程序  |  818行  |  23.13 KB

/*
 * Copyright 2007 The Android Open Source Project
 *
 * Syscall and library intercepts.
 */

/* don't remap open() to open64() */
#undef _FILE_OFFSET_BITS

#define CREATE_FUNC_STORAGE
#include "Common.h"

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <utime.h>
#include <limits.h>
#include <ftw.h>
#include <assert.h>


#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64
#warning "big"
#endif

//#define CALLTRACE(format, ...)   wsLog(format, ##__VA_ARGS__)
#define CALLTRACE(format, ...)   ((void)0)

//#define CALLTRACEV(format, ...)  wsLog(format, ##__VA_ARGS__)
#define CALLTRACEV(format, ...)  ((void)0)

/*
When opening certain files, we need to simulate the contents.  For example,
we can pretend to open the frame buffer, and respond to ioctl()s by
returning fake data or telling the front-end to render new data.

We want to intercept specific files in /dev.  In some cases we want to
intercept and reject, e.g. to indicate that a standard Linux device does
not exist.

Some things we're not going to intercept:
  /etc/... (e.g. /etc/timezone) -- std. Linux version should be okay
  /proc/... (e.g. /proc/stat) -- we're showing real pid, so real proc will work

For the device drivers we need to intercept:

  close(), ioctl(), mmap(), open()/open64(), read(), readv(), write(),
  writev()

May also need stat().  We don't need all fd calls, e.g. fchdir() is
not likely to succeed on a device driver.  The expected uses of mmap()
shouldn't require intercepting related calls like madvise() -- we will
provide an actual mapping of the requested size.  In some cases we will
want to return a "real" fd so the app can poll() or select() on it.


We also need to consider:
  getuid/setuid + variations -- fake out multi-user-id stuff


We also want to translate filenames, effectively performing a "chroot"
without all the baggage that comes with it.  The mapping looks like:

  /system/... --> $ANDROID_PRODUCT_OUT/system/...
  /data/... --> $ANDROID_PRODUCT_OUT/data/...

Translating pathnames requires interception of additional system calls,
substituting a new path.  Calls include:

  access(), chdir(), chmod(), chown(), creat(), execve(), getcwd(),
  lchown(), link(), lstat()/lstat64(), mkdir(), open()/open64(),
  readlink(), rename(), rmdir(), stat()/stat64(), statfs/statfs64(),
  symlink(), unlink(), utimes(),

Possibly also mknod(), mount(), umount().

The "at" family, notably openat(), should just work since the root comes
from an open directory fd.

We also need these libc calls, because LD_LIBRARY_PATH substitutes at
the libc link level, not the syscall layer:

  execl(), execlp(), execle(), execv(), execvp(), fopen(), ftw(), getwd(),
  opendir(), dlopen()

It is possible for the cwd to leak out.  Some possible leaks:
  - /proc/[self]/exe
  - /proc/[self]/cwd
  - LD_LIBRARY_PATH (which may be awkward to work around)


To provide a replacement for the dirent functions -- only required if we
want to show "fake" directory contents -- we would need:

  closedir(), dirfd() readdir(), rewinddir(), scandir(), seekdir(),
  telldir()


*/


/*
 * ===========================================================================
 *      Filename remapping
 * ===========================================================================
 */

/*
 * If appropriate, rewrite the path to point to a different location.
 *
 * Returns either "pathBuf" or "origPath" depending on whether or not we
 * chose to rewrite the path.  "origPath" must be a buffer capable of
 * holding an extended pathname; for best results use PATH_MAX.
 */
static const char* rewritePath(const char* func, char* pathBuf,
    const char* origPath)
{
    /*
     * Rewrite paths that start with "/system/" or "/data/"
     */
    if (origPath[0] != '/')
        goto skip_rewrite;
    while (origPath[1] == '/') origPath++; // some apps like to use paths like '//data/data/....'
    if (memcmp(origPath+1, "system", 6) == 0 &&
        (origPath[7] == '/' || origPath[7] == '\0'))
            goto do_rewrite;
    if (memcmp(origPath+1, "data", 4) == 0 &&
        (origPath[5] == '/' || origPath[5] == '\0'))
            goto do_rewrite;

skip_rewrite:
    /* check to see if something is side-stepping the rewrite */
    if (memcmp(origPath, gWrapSim.remapBaseDir, gWrapSim.remapBaseDirLen) == 0)
    {
        wsLog("NOTE: full path used: %s(%s)\n", func, origPath);
    }

    CALLTRACE("rewrite %s('%s') --> (not rewritten)\n", func, origPath);
    return origPath;

do_rewrite:
    memcpy(pathBuf, gWrapSim.remapBaseDir, gWrapSim.remapBaseDirLen);
    strcpy(pathBuf + gWrapSim.remapBaseDirLen, origPath);
    CALLTRACE("rewrite %s('%s') --> '%s'\n", func, origPath, pathBuf);
    return pathBuf;
}

/*
 * This works if the pathname is the first argument to the function, and
 * the function returns "int".
 */
#define PASS_THROUGH_DECL(_fname, _rtype, ...)                              \
    _rtype _fname( __VA_ARGS__ )
#define PASS_THROUGH_BODY(_fname, _patharg, ...)                            \
    {                                                                       \
        CALLTRACEV("%s\n", __FUNCTION__);                                   \
        char pathBuf[PATH_MAX];                                             \
        return _ws_##_fname(rewritePath(#_fname, pathBuf, _patharg),        \
            ##__VA_ARGS__);                                                 \
    }


PASS_THROUGH_DECL(chdir, int, const char* path)
PASS_THROUGH_BODY(chdir, path)

PASS_THROUGH_DECL(chmod, int, const char* path, mode_t mode)
PASS_THROUGH_BODY(chmod, path, mode)

PASS_THROUGH_DECL(chown, int, const char* path, uid_t owner, gid_t group)
PASS_THROUGH_BODY(chown, path, owner, group)

PASS_THROUGH_DECL(creat, int, const char* path, mode_t mode)
PASS_THROUGH_BODY(creat, path, mode)

PASS_THROUGH_DECL(execve, int, const char* path, char* const argv[],
    char* const envp[])
PASS_THROUGH_BODY(execve, path, argv, envp)

PASS_THROUGH_DECL(lchown, int, const char* path, uid_t owner, gid_t group)
PASS_THROUGH_BODY(lchown, path, owner, group)

PASS_THROUGH_DECL(lstat, int, const char* path, struct stat* buf)
PASS_THROUGH_BODY(lstat, path, buf)

PASS_THROUGH_DECL(lstat64, int, const char* path, struct stat* buf)
PASS_THROUGH_BODY(lstat64, path, buf)

PASS_THROUGH_DECL(mkdir, int, const char* path, mode_t mode)
PASS_THROUGH_BODY(mkdir, path, mode)

PASS_THROUGH_DECL(readlink, ssize_t, const char* path, char* buf, size_t bufsiz)
PASS_THROUGH_BODY(readlink, path, buf, bufsiz)

PASS_THROUGH_DECL(rmdir, int, const char* path)
PASS_THROUGH_BODY(rmdir, path)

PASS_THROUGH_DECL(stat, int, const char* path, struct stat* buf)
PASS_THROUGH_BODY(stat, path, buf)

PASS_THROUGH_DECL(stat64, int, const char* path, struct stat* buf)
PASS_THROUGH_BODY(stat64, path, buf)

PASS_THROUGH_DECL(statfs, int, const char* path, struct statfs* buf)
PASS_THROUGH_BODY(statfs, path, buf)

PASS_THROUGH_DECL(statfs64, int, const char* path, struct statfs* buf)
PASS_THROUGH_BODY(statfs64, path, buf)

PASS_THROUGH_DECL(unlink, int, const char* path)
PASS_THROUGH_BODY(unlink, path)

PASS_THROUGH_DECL(utime, int, const char* path, const struct utimbuf* buf)
PASS_THROUGH_BODY(utime, path, buf)

PASS_THROUGH_DECL(utimes, int, const char* path, const struct timeval times[2])
PASS_THROUGH_BODY(utimes, path, times)


PASS_THROUGH_DECL(fopen, FILE*, const char* path, const char* mode)
PASS_THROUGH_BODY(fopen, path, mode)

PASS_THROUGH_DECL(fopen64, FILE*, const char* path, const char* mode)
PASS_THROUGH_BODY(fopen64, path, mode)

PASS_THROUGH_DECL(freopen, FILE*, const char* path, const char* mode,
    FILE* stream)
PASS_THROUGH_BODY(freopen, path, mode, stream)

PASS_THROUGH_DECL(ftw, int, const char* dirpath,
          int (*fn) (const char* fpath, const struct stat* sb, int typeflag),
          int nopenfd)
PASS_THROUGH_BODY(ftw, dirpath, fn, nopenfd)

PASS_THROUGH_DECL(opendir, DIR*, const char* path)
PASS_THROUGH_BODY(opendir, path)

PASS_THROUGH_DECL(dlopen, void*, const char* path, int flag)
PASS_THROUGH_BODY(dlopen, path, flag)

/*
 * Opposite of path translation -- remove prefix.
 *
 * It looks like BSD allows you to pass a NULL value for "buf" to inspire
 * getcwd to allocate storage with malloc() (as an extension to the POSIX
 * definition, which doesn't specify this).  getcwd() is a system call
 * under Linux, so this doesn't work, but that doesn't stop gdb from
 * trying to use it anyway.
 */
char* getcwd(char* buf, size_t size)
{
    CALLTRACEV("%s %p %d\n", __FUNCTION__, buf, size);

    char* result = _ws_getcwd(buf, size);
    if (buf != NULL && result != NULL) {
        if (memcmp(buf, gWrapSim.remapBaseDir,
                    gWrapSim.remapBaseDirLen) == 0)
        {
            memmove(buf, buf + gWrapSim.remapBaseDirLen,
                strlen(buf + gWrapSim.remapBaseDirLen)+1);
            CALLTRACE("rewrite getcwd() -> %s\n", result);
        } else {
            CALLTRACE("not rewriting getcwd(%s)\n", result);
        }
    }
    return result;
}

/*
 * Need to tweak both pathnames.
 */
int link(const char* oldPath, const char* newPath)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf1[PATH_MAX];
    char pathBuf2[PATH_MAX];
    return _ws_link(rewritePath("link-1", pathBuf1, oldPath),
                    rewritePath("link-2", pathBuf2, newPath));
}

/*
 * Need to tweak both pathnames.
 */
int rename(const char* oldPath, const char* newPath)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf1[PATH_MAX];
    char pathBuf2[PATH_MAX];
    return _ws_rename(rewritePath("rename-1", pathBuf1, oldPath),
                      rewritePath("rename-2", pathBuf2, newPath));
}

/*
 * Need to tweak both pathnames.
 */
int symlink(const char* oldPath, const char* newPath)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf1[PATH_MAX];
    char pathBuf2[PATH_MAX];
    return _ws_symlink(rewritePath("symlink-1", pathBuf1, oldPath),
                       rewritePath("symlink-2", pathBuf2, newPath));
}

/*
 * glibc stat turns into this (32-bit).
 */
int __xstat(int version, const char* path, struct stat* sbuf)
{
    CALLTRACEV("%s\n", __FUNCTION__);
    char pathBuf[PATH_MAX];
    return _ws___xstat(version, rewritePath("__xstat", pathBuf, path),
        sbuf);
}

/*
 * glibc stat turns into this (64-bit).
 */
int __xstat64(int version, const char* path, struct stat* sbuf)
{
    CALLTRACEV("%s\n", __FUNCTION__);
    char pathBuf[PATH_MAX];
    return _ws___xstat64(version, rewritePath("__xstat64", pathBuf, path),
        sbuf);
}

/*
 * glibc lstat turns into this (32-bit).
 */
int __lxstat(int version, const char* path, struct stat* sbuf)
{
    CALLTRACEV("%s\n", __FUNCTION__);
    char pathBuf[PATH_MAX];
    return _ws___lxstat(version, rewritePath("__lxstat", pathBuf, path),
        sbuf);
}

/*
 * glibc lstat turns into this (64-bit).
 */
int __lxstat64(int version, const char* path, struct stat* sbuf)
{
    CALLTRACEV("%s\n", __FUNCTION__);
    char pathBuf[PATH_MAX];
    return _ws___lxstat64(version, rewritePath("__lxstat64", pathBuf, path),
        sbuf);
}

/*
 * Copy the argument list out of varargs for execl/execlp/execle.  This
 * leaves the argc value in _argc, and a NULL-terminated array of character
 * pointers in _argv.  We stop at the first NULL argument, so we shouldn't
 * end up copying "envp" out.
 *
 * We could use gcc __builtin_apply_args to just pass stuff through,
 * but that may not get along with the path rewriting.  It's unclear
 * whether we want to rewrite the first argument (i.e. the string that
 * becomes argv[0]); it only makes sense if the exec'ed program is also
 * getting remapped.
 */
#define COPY_EXEC_ARGLIST(_first, _argc, _argv)                             \
    int _argc = 0;                                                          \
    {                                                                       \
        va_list vargs;                                                      \
        va_start(vargs, _first);                                            \
        while (1) {                                                         \
            _argc++;                                                        \
            const char* val = va_arg(vargs, const char*);                   \
            if (val == NULL)                                                \
                break;                                                      \
        }                                                                   \
        va_end(vargs);                                                      \
    }                                                                       \
    const char* _argv[_argc+1];                                             \
    _argv[0] = _first;                                                      \
    {                                                                       \
        va_list vargs;                                                      \
        int i;                                                              \
        va_start(vargs, _first);                                            \
        for (i = 1; i < _argc; i++) {                                       \
            _argv[i] = va_arg(vargs, const char*);                          \
        }                                                                   \
        va_end(vargs);                                                      \
    }                                                                       \
    _argv[_argc] = NULL;

/*
 * Debug dump.
 */
static void dumpExecArgs(const char* callName, const char* path,
    int argc, const char* argv[], char* const envp[])
{
    int i;

    CALLTRACE("Calling %s '%s' (envp=%p)\n", callName, path, envp);
    for (i = 0; i <= argc; i++)
        CALLTRACE("  %d: %s\n", i, argv[i]);
}

/*
 * Extract varargs, convert paths, hand off to execv.
 */
int execl(const char* path, const char* arg, ...)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf[PATH_MAX];

    COPY_EXEC_ARGLIST(arg, argc, argv);
    dumpExecArgs("execl", path, argc, argv, NULL);
    path = rewritePath("execl", pathBuf, path);
    return _ws_execv(path, (char* const*) argv);
}

/*
 * Extract varargs, convert paths, hand off to execve.
 *
 * The execle prototype in the man page isn't valid C -- it shows the
 * "envp" argument after the "...".  We have to pull it out with the rest
 * of the varargs.
 */
int execle(const char* path, const char* arg, ...)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf[PATH_MAX];

    COPY_EXEC_ARGLIST(arg, argc, argv);

    /* run through again and find envp */
    char* const* envp;

    va_list vargs;
    va_start(vargs, arg);
    while (1) {
        const char* val = va_arg(vargs, const char*);
        if (val == NULL) {
            envp = va_arg(vargs, char* const*);
            break;
        }
    }
    va_end(vargs);

    dumpExecArgs("execle", path, argc, argv, envp);
    path = rewritePath("execl", pathBuf, path);

    return _ws_execve(path, (char* const*) argv, envp);
}

/*
 * Extract varargs, convert paths, hand off to execvp.
 */
int execlp(const char* file, const char* arg, ...)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf[PATH_MAX];

    COPY_EXEC_ARGLIST(arg, argc, argv);
    dumpExecArgs("execlp", file, argc, argv, NULL);
    file = rewritePath("execlp", pathBuf, file);
    return _ws_execvp(file, (char* const*) argv);
}

/*
 * Update path, forward to execv.
 */
int execv(const char* path, char* const argv[])
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf[PATH_MAX];

    path = rewritePath("execv", pathBuf, path);
    return _ws_execv(path, argv);
}

/*
 * Shouldn't need to do anything unless they specified a full path to execvp.
 */
int execvp(const char* file, char* const argv[])
{
    CALLTRACEV("%s\n", __FUNCTION__);

    char pathBuf[PATH_MAX];

    file = rewritePath("execvp", pathBuf, file);
    return _ws_execvp(file, argv);
}


/*
 * ===========================================================================
 *      Device fakery
 * ===========================================================================
 */

/*
 * Need to do filesystem translation and show fake devices.
 */
int access(const char* pathName, int mode)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    int status = wsInterceptDeviceAccess(pathName, mode);
    if (status == 0)
        return 0;
    else if (status == -2)
        return -1;          // errno already set
    else {
        char pathBuf[PATH_MAX];
        return _ws_access(rewritePath("access", pathBuf, pathName), mode);
    }
}

/*
 * Common handler for open().
 */
int openCommon(const char* pathName, int flags, mode_t mode)
{
    char pathBuf[PATH_MAX];
    int fd;

    assert(gWrapSim.initialized);

    fd = wsInterceptDeviceOpen(pathName, flags);
    if (fd >= 0) {
        return fd;
    } else if (fd == -2) {
        /* errno should be set */
        return -1;
    }

    if ((flags & O_CREAT) != 0) {
        fd = _ws_open(rewritePath("open", pathBuf, pathName), flags, mode);
        CALLTRACE("open(%s, 0x%x, 0%o) = %d\n", pathName, flags, mode, fd);
    } else {
        fd = _ws_open(rewritePath("open", pathBuf, pathName), flags, 0);
        CALLTRACE("open(%s, 0x%x) = %d\n", pathName, flags, fd);
    }
    return fd;
}

/*
 * Replacement open() and variants.
 *
 * We have to use the vararg decl for the standard call so it matches
 * the definition in fcntl.h.
 */
int open(const char* pathName, int flags, ...)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    mode_t mode = 0;
    if ((flags & O_CREAT) != 0) {
        va_list args;

        va_start(args, flags);
        mode = va_arg(args, mode_t);
        va_end(args);
    }

    return openCommon(pathName, flags, mode);
}
int __open(const char* pathName, int flags, mode_t mode)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    return openCommon(pathName, flags, mode);
}

/*
 * Common handler for open64().
 */
int open64Common(const char* pathName, int flags, mode_t mode)
{
    char pathBuf[PATH_MAX];
    int fd;

    assert(gWrapSim.initialized);

    fd = wsInterceptDeviceOpen(pathName, flags);
    if (fd >= 0) {
        return fd;
    }

    if ((flags & O_CREAT) != 0) {
        fd = _ws_open64(rewritePath("open64", pathBuf, pathName), flags, mode);
        CALLTRACE("open64(%s, 0x%x, 0%o) = %d\n", pathName, flags, mode, fd);
    } else {
        fd = _ws_open64(rewritePath("open64", pathBuf, pathName), flags, 0);
        CALLTRACE("open64(%s, 0x%x) = %d\n", pathName, flags, fd);
    }
    return fd;
}

/*
 * Replacement open64() and variants.
 *
 * We have to use the vararg decl for the standard call so it matches
 * the definition in fcntl.h.
 */
int open64(const char* pathName, int flags, ...)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    mode_t mode = 0;
    if ((flags & O_CREAT) != 0) {
        va_list args;

        va_start(args, flags);
        mode = va_arg(args, mode_t);
        va_end(args);
    }
    return open64Common(pathName, flags, mode);
}
int __open64(const char* pathName, int flags, mode_t mode)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    return open64Common(pathName, flags, mode);
}


/*
 * Close a file descriptor.
 */
int close(int fd)
{
    CALLTRACEV("%s(%d)\n", __FUNCTION__, fd);

    FakeDev* dev = wsFakeDevFromFd(fd);
    if (dev != NULL) {
        int result = dev->close(dev, fd);
        wsFreeFakeDev(dev);
        return result;
    } else {
        CALLTRACE("close(%d)\n", fd);
        return _ws_close(fd);
    }
}

/*
 * Map a region.
 */
void* mmap(void* start, size_t length, int prot, int flags, int fd,
    __off_t offset)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    FakeDev* dev = wsFakeDevFromFd(fd);
    if (dev != NULL) {
        return dev->mmap(dev, start, length, prot, flags, fd, offset);
    } else {
        CALLTRACE("mmap(%p, %d, %d, %d, %d, %d)\n",
            start, (int) length, prot, flags, fd, (int) offset);
        return _ws_mmap(start, length, prot, flags, fd, offset);
    }
}

/*
 * Map a region.
 */
void* mmap64(void* start, size_t length, int prot, int flags, int fd,
    __off64_t offset)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    FakeDev* dev = wsFakeDevFromFd(fd);
    if (dev != NULL) {
        return dev->mmap(dev, start, length, prot, flags, fd, (__off_t) offset);
    } else {
        CALLTRACE("mmap64(%p, %d, %d, %d, %d, %d)\n",
            start, (int) length, prot, flags, fd, (int) offset);
        return _ws_mmap(start, length, prot, flags, fd, offset);
    }
}

/*
 * The Linux headers show this with a vararg header, but as far as I can
 * tell the kernel always expects 3 args.
 */
int ioctl(int fd, int request, ...)
{
    CALLTRACEV("%s(%d, %d, ...)\n", __FUNCTION__, fd, request);

    FakeDev* dev = wsFakeDevFromFd(fd);
    va_list args;
    void* argp;

    /* extract argp from varargs */
    va_start(args, request);
    argp = va_arg(args, void*);
    va_end(args);

    if (dev != NULL) {
        return dev->ioctl(dev, fd, request, argp);
    } else {
        CALLTRACE("ioctl(%d, 0x%x, %p)\n", fd, request, argp);
        return _ws_ioctl(fd, request, argp);
    }
}

/*
 * Read data.
 */
ssize_t read(int fd, void* buf, size_t count)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    FakeDev* dev = wsFakeDevFromFd(fd);
    if (dev != NULL) {
        return dev->read(dev, fd, buf, count);
    } else {
        CALLTRACE("read(%d, %p, %u)\n", fd, buf, count);
        return _ws_read(fd, buf, count);
    }
}

/*
 * Write data.
 */
ssize_t write(int fd, const void* buf, size_t count)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    FakeDev* dev = wsFakeDevFromFd(fd);
    if (dev != NULL) {
        return dev->write(dev, fd, buf, count);
    } else {
        CALLTRACE("write(%d, %p, %u)\n", fd, buf, count);
        return _ws_write(fd, buf, count);
    }
}

/*
 * Read a data vector.
 */
ssize_t readv(int fd, const struct iovec* vector, int count)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    FakeDev* dev = wsFakeDevFromFd(fd);
    if (dev != NULL) {
        return dev->readv(dev, fd, vector, count);
    } else {
        CALLTRACE("readv(%d, %p, %u)\n", fd, vector, count);
        return _ws_readv(fd, vector, count);
    }
}

/*
 * Write a data vector.
 */
ssize_t writev(int fd, const struct iovec* vector, int count)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    FakeDev* dev = wsFakeDevFromFd(fd);
    if (dev != NULL) {
        return dev->writev(dev, fd, vector, count);
    } else {
        CALLTRACE("writev(%d, %p, %u)\n", fd, vector, count);
        return _ws_writev(fd, vector, count);
    }
}

/*
 * Set the scheduling priority.  The sim doesn't run as root, so we have
 * to fake this out.
 *
 * For now, do some basic verification of the which and who parameters,
 * but otherwise return success.  In the future we may want to track
 * these so getpriority works.
 */
int setpriority(__priority_which_t which, id_t who, int what)
{
    CALLTRACEV("%s\n", __FUNCTION__);

    if (which != PRIO_PROCESS &&
        which != PRIO_PGRP &&
        which != PRIO_USER) {
        return EINVAL;
    }

    if ((int)who < 0) {
        return ESRCH;
    }

    return 0;
}

#if 0
/*
 * Create a pipe.  (Only needed for debugging an fd leak.)
 */
int pipe(int filedes[2])
{
    CALLTRACEV("%s\n", __FUNCTION__);

    int result = _ws_pipe(filedes);
    if (result == 0)
        CALLTRACE("pipe(%p) -> %d,%d\n", filedes, filedes[0], filedes[1]);
    if (filedes[0] == 83)
        abort();
    return result;
}
#endif