frameworks_system_utils/server.c

555 lines
14 KiB
C

/*
* Copyright (C) 2020 Xiaomi Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <netpacket/rpmsg.h>
#include "kvdb.h"
#include "unqlite.h"
#define KVDB_MEM 0
#define KVDB_PERSIST 1
#define KVDB_COUNT 2
#define KVFD_LOCAL 0
#define KVFD_REMOTE 1
#define KVFD_COUNT 2
/****************************************************************************
* Database Types
****************************************************************************/
typedef int (*kvdb_consume)(const char* key, size_t key_len,
const char* value, size_t val_len,
void* cookie);
typedef struct kvdb_consume_data {
kvdb_consume consume;
void* cookie;
unqlite_kv_cursor* cur;
const char* key;
size_t key_len;
} kvdb_consume_data;
/****************************************************************************
* Database Functions
****************************************************************************/
static bool kvdb_is_comment(const char* line)
{
size_t i = strspn(line, " \t\r\n");
return line[i] == '\0' || line[i] == '#';
}
static bool kvdb_is_readonly(const char* key)
{
return strncmp(key, "ro.", 3) == 0;
}
static int kvdb_get_index(const char* key)
{
if (strncmp(key, "persist.", 8) == 0)
return KVDB_PERSIST;
else
return KVDB_MEM;
}
static int kvdb_set(unqlite* db[], const char* key, size_t key_len,
const char* value, size_t val_len, bool force)
{
if (--key_len >= PROP_KEY_MAX)
return -E2BIG;
if (key[key_len])
return -EINVAL;
if (--val_len >= PROP_VALUE_MAX)
return -E2BIG;
if (value[val_len])
return -EINVAL;
if(kvdb_is_readonly(key) && !force)
return -EPERM;
/* in environment variable? */
if (getenv(key)) {
int ret = setenv(key, value, 1);
if (ret < 0)
ret = -errno;
return ret;
}
/* no, then try database */
int i = kvdb_get_index(key);
if (i < 0)
return i;
return unqlite_kv_store(db[i], key, ++key_len, value, ++val_len);
}
static int kvdb_get(unqlite* db[], const char* key, size_t key_len, char* value)
{
if (--key_len >= PROP_KEY_MAX)
return -E2BIG;
if (key[key_len])
return -EINVAL;
/* in environment variable? */
const char* env = getenv(key);
if (env) {
size_t len = strlen(env) + 1;
if (len > PROP_VALUE_MAX)
return -E2BIG;
memcpy(value, env, len);
return len;
}
/* no, then try database */
int i = kvdb_get_index(key);
if (i < 0)
return i;
unqlite_int64 val_len = value ? PROP_VALUE_MAX : 0;
int ret = unqlite_kv_fetch(db[i], key, ++key_len, value, &val_len);
if (ret < 0)
return ret;
if (val_len <= 0 || value && value[val_len - 1])
return -EINVAL;
return val_len;
}
static int kvdb_delete(unqlite* db[], const char* key, size_t key_len)
{
if (--key_len >= PROP_KEY_MAX)
return -E2BIG;
if (key[key_len])
return -EINVAL;
if(kvdb_is_readonly(key))
return -EPERM;
/* in environment variable? */
if (getenv(key)) {
int ret = unsetenv(key);
if (ret < 0)
ret = -errno;
return ret;
}
/* no, then try database */
int i = kvdb_get_index(key);
if (i < 0)
return i;
return unqlite_kv_delete(db[i], key, ++key_len);
}
static int kvdb_list_value(const void* value, unsigned int len, void* arg)
{
kvdb_consume_data* data = arg;
return data->consume(data->key, data->key_len, value, len, data->cookie);
}
static int kvdb_list_key(const void* value, unsigned int len, void* arg)
{
kvdb_consume_data* data = arg;
data->key = value;
data->key_len = len;
return unqlite_kv_cursor_data_callback(data->cur, kvdb_list_value, data);
}
static int kvdb_list(unqlite* db[], kvdb_consume consume, void* cookie)
{
for (int i = 0; i < KVDB_COUNT; i++) {
unqlite_kv_cursor* cur = NULL;
unqlite_kv_cursor_init(db[i], &cur);
kvdb_consume_data data = {
.consume = consume,
.cookie = cookie,
.cur = cur,
};
unqlite_kv_cursor_first_entry(cur);
while (unqlite_kv_cursor_valid_entry(cur)) {
int ret = unqlite_kv_cursor_key_callback(cur, kvdb_list_key, &data);
if (ret < 0) { /* exit loop demanded by consume */
unqlite_kv_cursor_release(db[i], cur);
return ret;
}
unqlite_kv_cursor_next_entry(cur);
}
unqlite_kv_cursor_release(db[i], cur);
}
return 0;
}
static int kvdb_commit(unqlite* db[])
{
int ret = 0;
for (int i = 0; i < KVDB_COUNT; i++) {
int r = unqlite_commit(db[i]);
if (r < 0 && ret == 0)
ret = r;
}
return ret;
}
static void kvdb_uninit(unqlite* db[])
{
for (int i = 0; i < KVDB_COUNT; i++) {
if (db[i]) {
unqlite_close(db[i]);
db[i] = NULL;
}
}
}
static int kvdb_init(unqlite* db[])
{
static const char* path[KVDB_COUNT] = {
[KVDB_MEM] = "",
[KVDB_PERSIST] = CONFIG_KVDB_PERSIST_PATH,
};
int ret = 0;
/* open database */
memset(db, 0, sizeof(db[0]) * KVDB_COUNT);
for (int i = 0; i < KVDB_COUNT; i++) {
if (path[i][0])
ret = unqlite_open(&db[i], path[i], UNQLITE_OPEN_CREATE | UNQLITE_OPEN_OMIT_JOURNALING);
else
ret = unqlite_open(&db[i], NULL, UNQLITE_OPEN_IN_MEMORY);
if (ret < 0)
goto out;
}
/* load initial value from text file */
FILE* f = fopen(CONFIG_KVDB_SOURCE_PATH, "r");
if (!f)
return 0; /* optional */
char buf[PROP_MSG_MAX];
while (fgets(buf, PROP_MSG_MAX, f)) {
if (kvdb_is_comment(buf))
continue;
char* tmp;
char* key = strtok_r(buf, "=", &tmp);
char* value = strtok_r(NULL, "\n", &tmp);
if (!value)
continue;
int i = kvdb_get_index(key);
if (i < 0)
continue;
size_t key_len = strlen(key) + 1;
if (kvdb_get(db, key, key_len, NULL) >= 0)
continue;
kvdb_set(db, key, key_len, value, strlen(value) + 1, true);
}
fclose(f);
kvdb_commit(db);
return 0;
out:
kvdb_uninit(db);
return ret;
}
/****************************************************************************
* Network Types
****************************************************************************/
typedef struct kvdb_list_data {
int fd;
unqlite** db;
} kvdb_list_data;
/****************************************************************************
* Network Functions
****************************************************************************/
static int kvdb_bind(int fd[])
{
const int family[] = {
[KVFD_LOCAL] = AF_UNIX,
[KVFD_REMOTE] = AF_RPMSG,
};
const struct sockaddr_un addr0 = {
.sun_family = AF_UNIX,
.sun_path = PROP_SERVER_PATH,
};
const struct sockaddr_rpmsg addr1 = {
.rp_family = AF_RPMSG,
.rp_cpu = "",
.rp_name = PROP_SERVER_PATH,
};
const struct sockaddr* addr[] = {
[KVFD_LOCAL] = (const struct sockaddr*)&addr0,
[KVFD_REMOTE] = (const struct sockaddr*)&addr1,
};
const socklen_t addrlen[] = {
[KVFD_LOCAL] = sizeof(struct sockaddr_un),
[KVFD_REMOTE] = sizeof(struct sockaddr_rpmsg),
};
memset(fd, 0, sizeof(int) * KVFD_COUNT);
for (int i = 0; i < KVFD_COUNT; i++) {
fd[i] = socket(family[i], SOCK_STREAM, 0);
if (fd[i] < 0)
continue;
int ret = bind(fd[i], addr[i], addrlen[i]);
if (ret < 0)
return ret;
ret = listen(fd[i], SOMAXCONN);
if (ret < 0)
return ret;
}
return 0;
}
static void kvdb_unbind(int fd[])
{
for (int i = 0; i < KVFD_COUNT; i++)
if (fd[i] > 0)
close(fd[i]);
}
static int kvdb_list_consume(const char* key, size_t key_len,
const char* value, size_t val_len,
void* cookie)
{
char cmd[2] = {
key_len, val_len
};
struct iovec iov[3] = {
{.iov_base = cmd , .iov_len = 2 },
{.iov_base = (char*)key , .iov_len = key_len},
{.iov_base = (char*)value, .iov_len = val_len},
};
struct msghdr msg = {0};
msg.msg_iov = iov;
msg.msg_iovlen = 3;
int fd = (int)cookie;
int ret = sendmsg(fd, &msg, 0);
return ret > 0 ? 0 : ret;
}
static void* kvdb_list_thread(void* arg)
{
kvdb_list_data* data = arg;
void* cookie = (void*)data->fd;
kvdb_list(data->db, kvdb_list_consume, cookie);
send(data->fd, "", 1, 0); /* terminator */
close(data->fd);
free(data);
return NULL;
}
static bool kvdb_client(int fd, unqlite* db[])
{
bool dirty = false;
#if CONFIG_KVDB_TIMEOUT_INTERVAL
struct timeval timeout = {
.tv_sec = CONFIG_KVDB_TIMEOUT_INTERVAL,
.tv_usec = 0,
};
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
#endif
char msg[PROP_MSG_MAX];
msg[0] = msg[1] = msg[2] = 0; /* zero the first key bytes */
recv(fd, msg, PROP_MSG_MAX, 0);
switch (msg[0]) {
case 'D': {
size_t key_len = msg[1];
const char* key = msg + 2;
int32_t err = kvdb_delete(db, key, key_len);
if (err >= 0)
dirty = true;
send(fd, &err, 4, 0);
break;
}
case 'G': {
size_t key_len = msg[1];
const char* key = msg + 2;
char value[PROP_VALUE_MAX];
int len = kvdb_get(db, key, key_len, value);
if (len > 0)
send(fd, value, len, 0);
break;
}
case 'S': {
size_t key_len = msg[1];
size_t val_len = msg[2];
const char* key = msg + 3;
const char* value = key + key_len;
int32_t err = kvdb_set(db, key, key_len, value, val_len, false);
if (err >= 0)
dirty = true;
send(fd, &err, 4, 0);
break;
}
case 'L': {
kvdb_list_data* data = malloc(sizeof(*data));
if (!data)
break;
data->fd = fd;
data->db = db;
/* dispatch to new thread to allow the recursion */
pthread_t t;
if (pthread_create(&t, NULL, kvdb_list_thread, data) > 0) {
free(data);
break;
}
pthread_detach(t);
goto out; /* skip close fd, done in the thread */
}
case 'C': {
kvdb_commit(db);
break;
}
}
close(fd); /* done, close client socket */
out:
return dirty;
}
static void kvdb_server(int fd[], unqlite* db[])
{
struct pollfd pfd[KVFD_COUNT];
int pfd_count = 0;
for (int i = 0; i < KVFD_COUNT; i++) {
if (fd[i] > 0) {
pfd[pfd_count].fd = fd[i];
pfd[pfd_count].events = POLLIN;
pfd_count++;
}
}
time_t next = 0;
while (1) {
int timeout = -1;
/* commit the change after timeout */
if (next) {
timeout = next - time(NULL);
if (timeout <= 0) {
kvdb_commit(db);
timeout = -1;
next = 0;
} else
timeout *= 1000;
}
int nfds = poll(pfd, pfd_count, timeout);
for (int i = 0; nfds > 0; i++) {
if ((pfd[i].revents & POLLIN) == 0)
continue;
nfds--;
int newfd = accept(pfd[i].fd, NULL, NULL);
if (newfd < 0)
continue;
/* is database changed? */
if (kvdb_client(newfd, db) && next == 0) {
next = time(NULL) + CONFIG_KVDB_COMMIT_INTERVAL;
if (next == 0)
next++; /* ensure no zero */
}
}
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: main
*
* Description:
* Main entry point. Listens for UNIX domain socket connection and perform
* corresponding database operations.
*
****************************************************************************/
int main(int argc, char* argv[])
{
int fd[KVFD_COUNT];
int ret = kvdb_bind(fd);
if (ret < 0)
goto out;
unqlite* db[KVDB_COUNT];
ret = kvdb_init(db);
if (ret < 0)
goto out;
kvdb_server(fd, db);
kvdb_uninit(db);
out:
kvdb_unbind(fd);
return -ret;
}