/*
* Copyright (C) NGINX, Inc.
* Copyright (C) Zhidao HONG
*/
#include <nxt_main.h>
#include <nxt_conf.h>
#include <nxt_script.h>
#include <dirent.h>
struct nxt_script_s {
nxt_str_t text;
};
typedef struct {
nxt_str_t name;
nxt_conf_value_t *value;
nxt_mp_t *mp;
} nxt_script_info_t;
typedef struct {
nxt_str_t name;
nxt_fd_t fd;
} nxt_script_item_t;
static nxt_script_t *nxt_script_get(nxt_task_t *task, nxt_str_t *name,
nxt_fd_t fd);
static nxt_conf_value_t *nxt_script_details(nxt_mp_t *mp, nxt_script_t *cert);
static void nxt_script_buf_completion(nxt_task_t *task, void *obj, void *data);
static nxt_lvlhsh_t nxt_script_info;
static njs_vm_ops_t nxt_js_ops = {
NULL,
NULL,
nxt_js_module_loader,
NULL,
};
nxt_script_t *
nxt_script_new(nxt_task_t *task, nxt_str_t *name, u_char *data, size_t size,
u_char *error)
{
u_char *start;
njs_vm_t *vm;
njs_str_t mod_name;
njs_mod_t *mod;
njs_vm_opt_t opts;
nxt_script_t *script;
njs_vm_opt_init(&opts);
opts.backtrace = 1;
opts.file.start = (u_char *) "default";
opts.file.length = 7;
opts.ops = &nxt_js_ops;
vm = njs_vm_create(&opts);
if (nxt_slow_path(vm == NULL)) {
return NULL;
}
mod_name.length = name->length;
mod_name.start = name->start;
start = data;
mod = njs_vm_compile_module(vm, &mod_name, &start, start + size);
if (nxt_slow_path(mod == NULL)) {
(void) nxt_js_error(vm, error);
nxt_alert(task, "JS compile module(%V) failed: %s", name, error);
goto fail;
}
script = nxt_zalloc(sizeof(nxt_script_t) + size);
if (nxt_slow_path(script == NULL)) {
goto fail;
}
script->text.length = size;
script->text.start = (u_char *) script + sizeof(nxt_script_t);
nxt_memcpy(script->text.start, data, size);
njs_vm_destroy(vm);
return script;
fail:
njs_vm_destroy(vm);
return NULL;
}
static nxt_script_t *
nxt_script_get(nxt_task_t *task, nxt_str_t *name, nxt_fd_t fd)
{
nxt_int_t ret;
nxt_str_t text;
nxt_script_t *script;
u_char error[NXT_MAX_ERROR_STR];
ret = nxt_script_file_read(fd, &text);
if (nxt_slow_path(ret != NXT_OK)) {
return NULL;
}
script = nxt_script_new(task, name, text.start, text.length, error);
nxt_free(text.start);
return script;
}
void
nxt_script_destroy(nxt_script_t *script)
{
nxt_free(script);
}
static nxt_int_t
nxt_script_info_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
{
nxt_script_info_t *info;
info = data;
if (nxt_strcasestr_eq(&lhq->key, &info->name)) {
return NXT_OK;
}
return NXT_DECLINED;
}
static const nxt_lvlhsh_proto_t nxt_script_info_hash_proto
nxt_aligned(64) =
{
NXT_LVLHSH_DEFAULT,
nxt_script_info_hash_test,
nxt_lvlhsh_alloc,
nxt_lvlhsh_free,
};
void
nxt_script_info_init(nxt_task_t *task, nxt_array_t *scripts)
{
uint32_t i;
nxt_script_t *script;
nxt_script_item_t *item;
item = scripts->elts;
for (i = 0; i < scripts->nelts; i++) {
script = nxt_script_get(task, &item->name, item->fd);
if (nxt_slow_path(script == NULL)) {
continue;
}
(void) nxt_script_info_save(&item->name, script);
nxt_script_destroy(script);
item++;
}
}
nxt_int_t
nxt_script_info_save(nxt_str_t *name, nxt_script_t *script)
{
nxt_mp_t *mp;
nxt_int_t ret;
nxt_conf_value_t *value;
nxt_script_info_t *info;
nxt_lvlhsh_query_t lhq;
mp = nxt_mp_create(1024, 128, 256, 32);
if (nxt_slow_path(mp == NULL)) {
return NXT_ERROR;
}
info = nxt_mp_get(mp, sizeof(nxt_script_info_t));
if (nxt_slow_path(info == NULL)) {
goto fail;
}
name = nxt_str_dup(mp, &info->name, name);
if (nxt_slow_path(name == NULL)) {
goto fail;
}
value = nxt_script_details(mp, script);
if (nxt_slow_path(value == NULL)) {
goto fail;
}
info->mp = mp;
info->value = value;
lhq.key_hash = nxt_djb_hash(name->start, name->length);
lhq.replace = 1;
lhq.key = *name;
lhq.value = info;
lhq.proto = &nxt_script_info_hash_proto;
ret = nxt_lvlhsh_insert(&nxt_script_info, &lhq);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
if (lhq.value != info) {
info = lhq.value;
nxt_mp_destroy(info->mp);
}
return NXT_OK;
fail:
nxt_mp_destroy(mp);
return NXT_ERROR;
}
nxt_conf_value_t *
nxt_script_info_get(nxt_str_t *name)
{
nxt_int_t ret;
nxt_script_info_t *info;
nxt_lvlhsh_query_t lhq;
lhq.key_hash = nxt_djb_hash(name->start, name->length);
lhq.key = *name;
lhq.proto = &nxt_script_info_hash_proto;
ret = nxt_lvlhsh_find(&nxt_script_info, &lhq);
if (ret != NXT_OK) {
return NULL;
}
info = lhq.value;
return info->value;
}
nxt_conf_value_t *
nxt_script_info_get_all(nxt_mp_t *mp)
{
uint32_t i;
nxt_conf_value_t *all;
nxt_script_info_t *info;
nxt_lvlhsh_each_t lhe;
nxt_lvlhsh_each_init(&lhe, &nxt_script_info_hash_proto);
for (i = 0; /* void */; i++) {
info = nxt_lvlhsh_each(&nxt_script_info, &lhe);
if (info == NULL) {
break;
}
}
all = nxt_conf_create_object(mp, i);
if (nxt_slow_path(all == NULL)) {
return NULL;
}
nxt_lvlhsh_each_init(&lhe, &nxt_script_info_hash_proto);
for (i = 0; /* void */; i++) {
info = nxt_lvlhsh_each(&nxt_script_info, &lhe);
if (info == NULL) {
break;
}
nxt_conf_set_member(all, &info->name, info->value, i);
}
return all;
}
static nxt_conf_value_t *
nxt_script_details(nxt_mp_t *mp, nxt_script_t *script)
{
nxt_conf_value_t *value;
value = nxt_conf_create_object(mp, 0);
if (nxt_slow_path(value == NULL)) {
return NULL;
}
nxt_conf_set_string_dup(value, mp, &script->text);
return value;
}
nxt_int_t
nxt_script_info_delete(nxt_str_t *name)
{
nxt_int_t ret;
nxt_script_info_t *info;
nxt_lvlhsh_query_t lhq;
lhq.key_hash = nxt_djb_hash(name->start, name->length);
lhq.key = *name;
lhq.proto = &nxt_script_info_hash_proto;
ret = nxt_lvlhsh_delete(&nxt_script_info, &lhq);
if (ret == NXT_OK) {
info = lhq.value;
nxt_mp_destroy(info->mp);
}
return ret;
}
nxt_array_t *
nxt_script_store_load(nxt_task_t *task, nxt_mp_t *mp)
{
DIR *dir;
size_t size, alloc;
u_char *buf, *p;
nxt_str_t name;
nxt_int_t ret;
nxt_file_t file;
nxt_array_t *scripts;
nxt_runtime_t *rt;
struct dirent *de;
nxt_script_item_t *item;
rt = task->thread->runtime;
if (nxt_slow_path(rt->scripts.start == NULL)) {
nxt_alert(task, "no scripts storage directory");
return NULL;
}
scripts = nxt_array_create(mp, 16, sizeof(nxt_script_item_t));
if (nxt_slow_path(scripts == NULL)) {
return NULL;
}
buf = NULL;
alloc = 0;
dir = opendir((char *) rt->scripts.start);
if (nxt_slow_path(dir == NULL)) {
nxt_alert(task, "opendir(\"%s\") failed %E",
rt->scripts.start, nxt_errno);
goto fail;
}
for ( ;; ) {
de = readdir(dir);
if (de == NULL) {
break;
}
nxt_debug(task, "readdir(\"%s\"): \"%s\"",
rt->scripts.start, de->d_name);
name.length = nxt_strlen(de->d_name);
name.start = (u_char *) de->d_name;
if (nxt_str_eq(&name, ".", 1) || nxt_str_eq(&name, "..", 2)) {
continue;
}
item = nxt_array_add(scripts);
if (nxt_slow_path(item == NULL)) {
goto fail;
}
item->fd = -1;
size = rt->scripts.length + name.length + 1;
if (size > alloc) {
size += 32;
p = nxt_realloc(buf, size);
if (p == NULL) {
goto fail;
}
alloc = size;
buf = p;
}
p = nxt_cpymem(buf, rt->scripts.start, rt->scripts.length);
p = nxt_cpymem(p, name.start, name.length + 1);
nxt_memzero(&file, sizeof(nxt_file_t));
file.name = buf;
ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN,
NXT_FILE_OWNER_ACCESS);
if (nxt_slow_path(ret != NXT_OK)) {
nxt_array_remove_last(scripts);
continue;
}
item->fd = file.fd;
if (nxt_slow_path(nxt_str_dup(mp, &item->name, &name) == NULL)) {
goto fail;
}
}
if (buf != NULL) {
nxt_free(buf);
}
(void) closedir(dir);
return scripts;
fail:
if (buf != NULL) {
nxt_free(buf);
}
if (dir != NULL) {
(void) closedir(dir);
}
nxt_script_store_release(scripts);
return NULL;
}
void
nxt_script_store_release(nxt_array_t *scripts)
{
uint32_t i;
nxt_script_item_t *item;
item = scripts->elts;
for (i = 0; i < scripts->nelts; i++) {
nxt_fd_close(item[i].fd);
}
nxt_array_destroy(scripts);
}
void
nxt_script_store_get(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp,
nxt_port_rpc_handler_t handler, void *ctx)
{
uint32_t stream;
nxt_int_t ret;
nxt_buf_t *b;
nxt_port_t *main_port, *recv_port;
nxt_runtime_t *rt;
b = nxt_buf_mem_alloc(mp, name->length + 1, 0);
if (nxt_slow_path(b == NULL)) {
goto fail;
}
nxt_mp_retain(mp);
b->completion_handler = nxt_script_buf_completion;
nxt_buf_cpystr(b, name);
*b->mem.free++ = '\0';
rt = task->thread->runtime;
main_port = rt->port_by_type[NXT_PROCESS_MAIN];
recv_port = rt->port_by_type[rt->type];
stream = nxt_port_rpc_register_handler(task, recv_port, handler, handler,
-1, ctx);
if (nxt_slow_path(stream == 0)) {
goto fail;
}
ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_SCRIPT_GET, -1,
stream, recv_port->id, b);
if (nxt_slow_path(ret != NXT_OK)) {
nxt_port_rpc_cancel(task, recv_port, stream);
goto fail;
}
return;
fail:
handler(task, NULL, ctx);
}
static void
nxt_script_buf_completion(nxt_task_t *task, void *obj, void *data)
{
nxt_mp_t *mp;
nxt_buf_t *b;
b = obj;
mp = b->data;
nxt_assert(b->next == NULL);
nxt_mp_free(mp, b);
nxt_mp_release(mp);
}
void
nxt_script_store_get_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
{
u_char *p;
nxt_int_t ret;
nxt_str_t name;
nxt_file_t file;
nxt_port_t *port;
nxt_runtime_t *rt;
nxt_port_msg_type_t type;
port = nxt_runtime_port_find(task->thread->runtime, msg->port_msg.pid,
msg->port_msg.reply_port);
if (nxt_slow_path(port == NULL)) {
nxt_alert(task, "process port not found (pid %PI, reply_port %d)",
msg->port_msg.pid, msg->port_msg.reply_port);
return;
}
if (nxt_slow_path(port->type != NXT_PROCESS_CONTROLLER
&& port->type != NXT_PROCESS_ROUTER))
{
nxt_alert(task, "process %PI cannot store scripts",
msg->port_msg.pid);
return;
}
nxt_memzero(&file, sizeof(nxt_file_t));
file.fd = -1;
type = NXT_PORT_MSG_RPC_ERROR;
rt = task->thread->runtime;
if (nxt_slow_path(rt->certs.start == NULL)) {
nxt_alert(task, "no scripts storage directory");
goto error;
}
name.start = msg->buf->mem.pos;
name.length = nxt_strlen(name.start);
file.name = nxt_malloc(rt->scripts.length + name.length + 1);
if (nxt_slow_path(file.name == NULL)) {
goto error;
}
p = nxt_cpymem(file.name, rt->scripts.start, rt->scripts.length);
p = nxt_cpymem(p, name.start, name.length + 1);
ret = nxt_file_open(task, &file, NXT_FILE_RDWR, NXT_FILE_CREATE_OR_OPEN,
NXT_FILE_OWNER_ACCESS);
nxt_free(file.name);
if (nxt_fast_path(ret == NXT_OK)) {
type = NXT_PORT_MSG_RPC_READY_LAST | NXT_PORT_MSG_CLOSE_FD;
}
error:
(void) nxt_port_socket_write(task, port, type, file.fd,
msg->port_msg.stream, 0, NULL);
}
void
nxt_script_store_delete(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp)
{
nxt_buf_t *b;
nxt_port_t *main_port;
nxt_runtime_t *rt;
b = nxt_buf_mem_alloc(mp, name->length + 1, 0);
if (nxt_fast_path(b != NULL)) {
nxt_buf_cpystr(b, name);
*b->mem.free++ = '\0';
rt = task->thread->runtime;
main_port = rt->port_by_type[NXT_PROCESS_MAIN];
(void) nxt_port_socket_write(task, main_port,
NXT_PORT_MSG_SCRIPT_DELETE, -1, 0, 0, b);
}
}
void
nxt_script_store_delete_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
{
u_char *p;
nxt_str_t name;
nxt_port_t *ctl_port;
nxt_runtime_t *rt;
nxt_file_name_t *path;
rt = task->thread->runtime;
ctl_port = rt->port_by_type[NXT_PROCESS_CONTROLLER];
if (nxt_slow_path(ctl_port == NULL)) {
nxt_alert(task, "controller port not found");
return;
}
if (nxt_slow_path(nxt_recv_msg_cmsg_pid(msg) != ctl_port->pid)) {
nxt_alert(task, "process %PI cannot delete scripts",
nxt_recv_msg_cmsg_pid(msg));
return;
}
if (nxt_slow_path(rt->scripts.start == NULL)) {
nxt_alert(task, "no scripts storage directory");
return;
}
name.start = msg->buf->mem.pos;
name.length = nxt_strlen(name.start);
path = nxt_malloc(rt->scripts.length + name.length + 1);
if (nxt_fast_path(path != NULL)) {
p = nxt_cpymem(path, rt->scripts.start, rt->scripts.length);
p = nxt_cpymem(p, name.start, name.length + 1);
(void) nxt_file_delete(path);
nxt_free(path);
}
}
nxt_int_t
nxt_script_file_read(nxt_fd_t fd, nxt_str_t *str)
{
ssize_t n;
nxt_int_t ret;
nxt_file_t file;
nxt_file_info_t fi;
nxt_memzero(&file, sizeof(nxt_file_t));
file.fd = fd;
ret = nxt_file_info(&file, &fi);
if (nxt_slow_path(ret != NXT_OK)) {
return NXT_ERROR;
}
if (nxt_slow_path(!nxt_is_file(&fi))) {
nxt_str_null(str);
return NXT_DECLINED;
}
str->length = nxt_file_size(&fi);
str->start = nxt_malloc(str->length);
if (nxt_slow_path(str->start == NULL)) {
return NXT_ERROR;
}
n = nxt_file_read(&file, str->start, str->length, 0);
if (nxt_slow_path(n != (ssize_t) str->length)) {
nxt_free(str->start);
return NXT_ERROR;
}
return NXT_OK;
}