//
// 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;
}