//
// Copyright 2007 The Android Open Source Project
//
// Property sever.  Mimics behavior provided on the device by init(8) and
// some code built into libc.
//
    
// For compilers that support precompilation, include "wx/wx.h".
#include "wx/wxprec.h"
    
// Otherwise, include all standard headers
#ifndef WX_PRECOMP
# include "wx/wx.h"
#endif
#include "wx/image.h"
    
#include "PropertyServer.h"
#include "MyApp.h"
#include "Preferences.h"
#include "MainFrame.h"
#include "utils.h"

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>


using namespace android;

const char* PropertyServer::kPropCheckJni = "ro.kernel.android.checkjni";

/*
 * Destructor.
 */
PropertyServer::~PropertyServer(void)
{
    if (IsRunning()) {
        // TODO: cause thread to stop, then Wait for it
    }
    printf("Sim: in ~PropertyServer()\n");
}

/*
 * Create and run the thread.
 */
bool PropertyServer::StartThread(void)
{
    if (Create() != wxTHREAD_NO_ERROR) {
        fprintf(stderr, "Sim: ERROR: can't create PropertyServer thread\n");
        return false;
    }

    Run();
    return true;
}


/*
 * Clear out the list.
 */
void PropertyServer::ClearProperties(void)
{
    typedef List<Property>::iterator PropIter;

    for (PropIter pi = mPropList.begin(); pi != mPropList.end(); ++pi) {
        pi = mPropList.erase(pi);
    }
}

/*
 * Set default values for several properties.
 */
void PropertyServer::SetDefaultProperties(void)
{
    static const struct {
        const char* key;
        const char* value;
    } propList[] = {
        { "net.bt.name", "Android" },
        { "ro.kernel.mem", "60M" },
        { "ro.kernel.board_sardine.version", "4" },
        { "ro.kernel.console", "null" },
        { "ro.build.id", "engineering" },
        { "ro.build.date", "Wed Nov 28 07:44:14 PST 2007" },
        { "ro.build.date.utc", "1196264654" },
        { "ro.build.type", "eng" },
        { "ro.product.device", "simulator" /*"sooner"*/ },
        { "ro.product.brand", "generic" },
        { "ro.build.user", "fadden" },
        { "ro.build.host", "marathon" },
        { "ro.config.nocheckin", "yes" },
        { "ro.product.manufacturer", "" },
        { "ro.radio.use-ppp", "no" },
        { "ro.FOREGROUND_APP_ADJ", "0" },
        { "ro.VISIBLE_APP_ADJ", "1" },
        { "ro.SECONDARY_SERVER_ADJ", "2" },
        { "ro.HIDDEN_APP_MIN_ADJ", "7" },
        { "ro.CONTENT_PROVIDER_ADJ", "14" },
        { "ro.EMPTY_APP_ADJ", "15" },
        { "ro.FOREGROUND_APP_MEM", "1536" },
        { "ro.VISIBLE_APP_MEM", "2048" },
        { "ro.SECONDARY_SERVER_MEM", "4096" },
        { "ro.HIDDEN_APP_MEM", "8192" },
        { "ro.EMPTY_APP_MEM", "16384" },
        { "ro.HOME_APP_ADJ", "4" },
        { "ro.HOME_APP_MEM", "4096" },
        { "ro.BACKUP_APP_ADJ", "2" },
        { "ro.BACKUP_APP_MEM", "4096" },
        //{ "init.svc.adbd", "running" },       // causes ADB-JDWP
        { "init.svc.usbd", "running" },
        { "init.svc.debuggerd", "running" },
        { "init.svc.ril-daemon", "running" },
        { "init.svc.zygote", "running" },
        { "init.svc.runtime", "running" },
        { "init.svc.dbus", "running" },
        { "init.svc.pppd_gprs", "running" },
        { "adb.connected", "0" },
        /*
        { "status.battery.state", "Slow" },
        { "status.battery.level", "5" },
        { "status.battery.level_raw", "50" },
        { "status.battery.level_scale", "9" },
        */

        /* disable the annoying setup wizard */
        { "app.setupwizard.disable", "1" },

        /* Dalvik options, set by AndroidRuntime */
        { "dalvik.vm.stack-trace-file", "/data/anr/traces.txt" },
        //{ "dalvik.vm.execution-mode", "int:portable" },
        { "dalvik.vm.enableassertions", "all" },    // -ea
        { "dalvik.vm.dexopt-flags", "" },           // e.g. "v=a,o=v,m=n"
        { "dalvik.vm.deadlock-predict", "off" },    // -Xdeadlockpredict
        //{ "dalvik.vm.jniopts", "forcecopy" },       // -Xjniopts
        { "log.redirect-stdio", "false" },          // -Xlog-stdio

        /* SurfaceFlinger options */
        { "debug.sf.nobootanimation", "1" },
        { "debug.sf.showupdates", "0" },
        { "debug.sf.showcpu", "0" },
        { "debug.sf.showbackground", "0" },
        { "debug.sf.showfps", "0" },
        { "default", "default" },

        /* Stagefright options */
        { "media.stagefright.enable-player", "true" },
        { "media.stagefright.enable-meta", "true" },
        { "media.stagefright.enable-scan", "true" },
        { "media.stagefright.enable-http", "true" },
    };

    for (int i = 0; i < NELEM(propList); i++)
        SetProperty(propList[i].key, propList[i].value);

    Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
    bool doCheckJni = false;

    pPrefs->GetBool("check-jni", &doCheckJni);
    if (doCheckJni)
        SetProperty(kPropCheckJni, "1");
    else
        SetProperty(kPropCheckJni, "0");
}

/*
 * Get the value of a property.
 *
 * "valueBuf" must hold at least PROPERTY_VALUE_MAX bytes.
 *
 * Returns "true" if the property was found.
 */
bool PropertyServer::GetProperty(const char* key, char* valueBuf)
{
    typedef List<Property>::iterator PropIter;

    assert(key != NULL);
    assert(valueBuf != NULL);

    for (PropIter pi = mPropList.begin(); pi != mPropList.end(); ++pi) {
        Property& prop = *pi;
        if (strcmp(prop.key, key) == 0) {
            if (strlen(prop.value) >= PROPERTY_VALUE_MAX) {
                fprintf(stderr,
                    "GLITCH: properties table holds '%s' '%s' (len=%d)\n",
                    prop.key, prop.value, (int) strlen(prop.value));
                abort();
            }
            assert(strlen(prop.value) < PROPERTY_VALUE_MAX);
            strcpy(valueBuf, prop.value);
            return true;
        }
    }

    //printf("Prop: get [%s] not found\n", key);
    return false;
}

/*
 * Set the value of a property, replacing it if it already exists.
 *
 * If "value" is NULL, the property is removed.
 *
 * If the property is immutable, this returns "false" without doing
 * anything.  (Not implemented.)
 */
bool PropertyServer::SetProperty(const char* key, const char* value)
{
    typedef List<Property>::iterator PropIter;

    assert(key != NULL);
    assert(value != NULL);

    for (PropIter pi = mPropList.begin(); pi != mPropList.end(); ++pi) {
        Property& prop = *pi;
        if (strcmp(prop.key, key) == 0) {
            if (value != NULL) {
                //printf("Prop: replacing [%s]: [%s] with [%s]\n",
                //    prop.key, prop.value, value);
                strcpy(prop.value, value);
            } else {
                //printf("Prop: removing [%s]\n", prop.key);
                mPropList.erase(pi);
            }
            return true;
        }
    }

    //printf("Prop: adding [%s]: [%s]\n", key, value);
    Property tmp;
    strcpy(tmp.key, key);
    strcpy(tmp.value, value);
    mPropList.push_back(tmp);
    return true;
}

/*
 * Create a UNIX domain socket, carefully removing it if it already
 * exists.
 */
bool PropertyServer::CreateSocket(const char* fileName)
{
    struct stat sb;
    bool result = false;
    int sock = -1;
    int cc;

    cc = stat(fileName, &sb);
    if (cc < 0) {
        if (errno != ENOENT) {
            LOG(LOG_ERROR, "sim-prop",
                "Unable to stat '%s' (errno=%d)\n", fileName, errno);
            goto bail;
        }
    } else {
        /* don't touch it if it's not a socket */
        if (!(S_ISSOCK(sb.st_mode))) {
            LOG(LOG_ERROR, "sim-prop",
                "File '%s' exists and is not a socket\n", fileName);
            goto bail;
        }

        /* remove the cruft */
        if (unlink(fileName) < 0) {
            LOG(LOG_ERROR, "sim-prop",
                "Unable to remove '%s' (errno=%d)\n", fileName, errno);
            goto bail;
        }
    }

    struct sockaddr_un addr;

    sock = ::socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
        LOG(LOG_ERROR, "sim-prop",
            "UNIX domain socket create failed (errno=%d)\n", errno);
        goto bail;
    }

    /* bind the socket; this creates the file on disk */
    strcpy(addr.sun_path, fileName);    // max 108 bytes
    addr.sun_family = AF_UNIX;
    cc = ::bind(sock, (struct sockaddr*) &addr, SUN_LEN(&addr));
    if (cc < 0) {
        LOG(LOG_ERROR, "sim",
            "AF_UNIX bind failed for '%s' (errno=%d)\n", fileName, errno);
        goto bail;
    }

    cc = ::listen(sock, 5);
    if (cc < 0) {
        LOG(LOG_ERROR, "sim", "AF_UNIX listen failed (errno=%d)\n", errno);
        goto bail;
    }

    mListenSock = sock;
    sock = -1;
    result = true;

bail:
    if (sock >= 0)
        close(sock);
    return result;
}

/*
 * Handle a client request.
 *
 * Returns true on success, false if the fd should be closed.
 */
bool PropertyServer::HandleRequest(int fd)
{
    char reqBuf[PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX];
    char valueBuf[1 + PROPERTY_VALUE_MAX];
    ssize_t actual;

    memset(valueBuf, 'x', sizeof(valueBuf));        // placate valgrind

    /* read the command byte; this determines the message length */
    actual = read(fd, reqBuf, 1);
    if (actual <= 0)
        return false;

    if (reqBuf[0] == kSystemPropertyGet) {
        actual = read(fd, reqBuf, PROPERTY_KEY_MAX);

        if (actual != PROPERTY_KEY_MAX) {
            fprintf(stderr, "Bad read on get: %d of %d\n",
                (int) actual, PROPERTY_KEY_MAX);
            return false;
        }
        if (GetProperty(reqBuf, valueBuf+1))
            valueBuf[0] = 1;
        else
            valueBuf[0] = 0;
        //printf("GET property [%s]: (found=%d) [%s]\n",
        //    reqBuf, valueBuf[0], valueBuf+1);
        if (write(fd, valueBuf, sizeof(valueBuf)) != sizeof(valueBuf)) {
            fprintf(stderr, "Bad write on get\n");
            return false;
        }
    } else if (reqBuf[0] == kSystemPropertySet) {
        actual = read(fd, reqBuf, PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX);
        if (actual != PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX) {
            fprintf(stderr, "Bad read on set: %d of %d\n",
                (int) actual, PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX);
            return false;
        }
        //printf("SET property '%s'\n", reqBuf);
        if (SetProperty(reqBuf, reqBuf + PROPERTY_KEY_MAX))
            valueBuf[0] = 1;
        else
            valueBuf[0] = 0;
        if (write(fd, valueBuf, 1) != 1) {
            fprintf(stderr, "Bad write on set\n");
            return false;
        }
    } else if (reqBuf[0] == kSystemPropertyList) {
        /* TODO */
        assert(false);
    } else {
        fprintf(stderr, "Unexpected request %d from prop client\n", reqBuf[0]);
        return false;
    }

    return true;
}

/*
 * Serve up properties.
 */
void PropertyServer::ServeProperties(void)
{
    typedef List<int>::iterator IntIter;
    fd_set readfds;
    int maxfd;

    while (true) {
        int cc;

        FD_ZERO(&readfds);
        FD_SET(mListenSock, &readfds);
        maxfd = mListenSock;

        for (IntIter ii = mClientList.begin(); ii != mClientList.end(); ++ii) {
            int fd = (*ii);

            FD_SET(fd, &readfds);
            if (maxfd < fd)
                maxfd = fd;
        }

        cc = select(maxfd+1, &readfds, NULL, NULL, NULL);
        if (cc < 0) {
            if (errno == EINTR) {
                printf("hiccup!\n");
                continue;
            }
            return;
        }
        if (FD_ISSET(mListenSock, &readfds)) {
            struct sockaddr_un from;
            socklen_t fromlen;
            int newSock;

            fromlen = sizeof(from);
            newSock = ::accept(mListenSock, (struct sockaddr*) &from, &fromlen);
            if (newSock < 0) {
                LOG(LOG_WARN, "sim",
                    "AF_UNIX accept failed (errno=%d)\n", errno);
            } else {
                //printf("new props connection on %d --> %d\n",
                //    mListenSock, newSock);

                mClientList.push_back(newSock);
            }
        }

        for (IntIter ii = mClientList.begin(); ii != mClientList.end(); ) {
            int fd = (*ii);
            bool ok = true;

            if (FD_ISSET(fd, &readfds)) {
                //printf("--- activity on %d\n", fd);

                ok = HandleRequest(fd);
            }

            if (ok) {
                ++ii;
            } else {
                //printf("--- closing %d\n", fd);
                close(fd);
                ii = mClientList.erase(ii);
            }
        }
    }
}

/*
 * Thread entry point.
 *
 * This just sits and waits for a new connection.  It hands it off to the
 * main thread and then goes back to waiting.
 *
 * There is currently no "polite" way to shut this down.
 */
void* PropertyServer::Entry(void)
{
    if (CreateSocket(SYSTEM_PROPERTY_PIPE_NAME)) {
        assert(mListenSock >= 0);
        SetDefaultProperties();

        /* loop until it's time to exit or we fail */
        ServeProperties();

        ClearProperties();

        /*
         * Close listen socket and all clients.
         */
        LOG(LOG_INFO, "sim", "Cleaning up socket list\n");
        typedef List<int>::iterator IntIter;
        for (IntIter ii = mClientList.begin(); ii != mClientList.end(); ++ii)
            close((*ii));
        close(mListenSock);
    }

    LOG(LOG_INFO, "sim", "PropertyServer thread exiting\n");
    return NULL;
}