blob: 31ea7ee0dd9814f7e6b040a5ce7115fe6ba3ae23 [file] [log] [blame]
/*
* Copyright (C) Dmitry Volyntsev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
typedef struct {
int fd;
njs_str_t name;
njs_str_t file;
} njs_module_info_t;
typedef struct {
njs_str_t text;
njs_module_info_t info;
njs_lexer_t *prev;
njs_lexer_t lexer;
} njs_module_temp_t;
static njs_int_t njs_parser_module_lambda_after(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current);
static njs_int_t njs_parser_module_after(njs_parser_t *parser,
njs_lexer_token_t *token, njs_queue_link_t *current);
static njs_int_t njs_module_lookup(njs_vm_t *vm, const njs_str_t *cwd,
njs_module_info_t *info);
static njs_int_t njs_module_relative_path(njs_vm_t *vm,
const njs_str_t *dir, njs_module_info_t *info);
static njs_int_t njs_module_absolute_path(njs_vm_t *vm,
njs_module_info_t *info);
static njs_bool_t njs_module_realpath_equal(const njs_str_t *path1,
const njs_str_t *path2);
static njs_int_t njs_module_read(njs_vm_t *vm, int fd, njs_str_t *body);
static njs_module_t *njs_module_find(njs_vm_t *vm, njs_str_t *name,
njs_bool_t local);
static njs_module_t *njs_module_add(njs_vm_t *vm, njs_str_t *name);
static njs_int_t njs_module_insert(njs_vm_t *vm, njs_module_t *module);
njs_int_t
njs_module_load(njs_vm_t *vm)
{
njs_int_t ret;
njs_uint_t i;
njs_value_t *value;
njs_module_t **item, *module;
if (vm->modules == NULL) {
return NJS_OK;
}
item = vm->modules->start;
for (i = 0; i < vm->modules->items; i++) {
module = *item;
if (module->function.native) {
value = njs_vmcode_operand(vm, module->index);
njs_set_object(value, &module->object);
} else {
ret = njs_vm_invoke(vm, &module->function, NULL, 0, module->index);
if (ret == NJS_ERROR) {
return ret;
}
}
item++;
}
return NJS_OK;
}
void
njs_module_reset(njs_vm_t *vm)
{
njs_uint_t i;
njs_module_t **item, *module;
njs_lvlhsh_query_t lhq;
if (vm->modules == NULL) {
return;
}
item = vm->modules->start;
for (i = 0; i < vm->modules->items; i++) {
module = *item;
if (!module->function.native) {
lhq.key = module->name;
lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length);
lhq.proto = &njs_modules_hash_proto;
lhq.pool = vm->mem_pool;
(void) njs_lvlhsh_delete(&vm->modules_hash, &lhq);
}
item++;
}
njs_arr_reset(vm->modules);
}
njs_int_t
njs_parser_module(njs_parser_t *parser, njs_lexer_token_t *token,
njs_queue_link_t *current)
{
njs_int_t ret;
njs_str_t name, text;
njs_module_t *module;
njs_module_temp_t *temp;
njs_module_info_t info;
name = token->text;
parser->node = NULL;
module = njs_module_find(parser->vm, &name, 0);
if (module != NULL && module->function.native) {
njs_lexer_consume_token(parser->lexer, 1);
parser->target = (njs_parser_node_t *) module;
return njs_parser_module_after(parser, token, current);
}
njs_memzero(&text, sizeof(njs_str_t));
if (parser->vm->options.sandbox || name.length == 0) {
njs_parser_syntax_error(parser, "Cannot find module \"%V\"", &name);
goto fail;
}
/* Non-native module. */
njs_memzero(&info, sizeof(njs_module_info_t));
info.name = name;
ret = njs_module_lookup(parser->vm, &parser->scope->cwd, &info);
if (njs_slow_path(ret != NJS_OK)) {
njs_parser_syntax_error(parser, "Cannot find module \"%V\"", &name);
goto fail;
}
module = njs_module_find(parser->vm, &info.file, 0);
if (module != NULL) {
(void) close(info.fd);
njs_lexer_consume_token(parser->lexer, 1);
parser->target = (njs_parser_node_t *) module;
return njs_parser_module_after(parser, token, current);
}
ret = njs_module_read(parser->vm, info.fd, &text);
(void) close(info.fd);
if (njs_slow_path(ret != NJS_OK)) {
njs_internal_error(parser->vm, "while reading \"%V\" module",
&info.file);
goto fail;
}
if (njs_module_realpath_equal(&parser->lexer->file, &info.file)) {
njs_parser_syntax_error(parser, "Cannot import itself \"%V\"",
&info.file);
goto fail;
}
temp = njs_mp_alloc(parser->vm->mem_pool, sizeof(njs_module_temp_t));
if (njs_slow_path(temp == NULL)) {
return NJS_ERROR;
}
ret = njs_lexer_init(parser->vm, &temp->lexer, &info.file, text.start,
text.start + text.length);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
njs_lexer_consume_token(parser->lexer, 1);
temp->prev = parser->lexer;
temp->info = info;
temp->text = text;
parser->lexer = &temp->lexer;
njs_parser_next(parser, njs_parser_module_lambda);
return njs_parser_after(parser, current, temp, 0,
njs_parser_module_lambda_after);
fail:
if (text.start != NULL) {
njs_mp_free(parser->vm->mem_pool, text.start);
}
return NJS_ERROR;
}
njs_int_t
njs_parser_module_lambda_after(njs_parser_t *parser, njs_lexer_token_t *token,
njs_queue_link_t *current)
{
njs_module_t *module;
njs_module_temp_t *temp;
temp = (njs_module_temp_t *) parser->target;
if (parser->ret != NJS_OK) {
njs_mp_free(parser->vm->mem_pool, temp->text.start);
njs_mp_free(parser->vm->mem_pool, temp);
if (token->type == NJS_TOKEN_END) {
return njs_parser_stack_pop(parser);
}
return njs_parser_failed(parser);
}
module = njs_module_add(parser->vm, &temp->info.file);
if (njs_slow_path(module == NULL)) {
parser->lexer = temp->prev;
if (temp->text.start != NULL) {
njs_mp_free(parser->vm->mem_pool, temp->text.start);
}
}
module->function.args_offset = 1;
module->function.u.lambda = parser->node->u.value.data.u.lambda;
njs_mp_free(parser->vm->mem_pool, temp->text.start);
parser->lexer = temp->prev;
parser->target = (njs_parser_node_t *) module;
njs_mp_free(parser->vm->mem_pool, temp);
return njs_parser_module_after(parser, token, current);
}
njs_int_t
njs_parser_module_after(njs_parser_t *parser, njs_lexer_token_t *token,
njs_queue_link_t *current)
{
njs_int_t ret;
njs_module_t *module;
njs_parser_node_t *node;
node = njs_parser_node_new(parser, 0);
if (njs_slow_path(node == NULL)) {
return NJS_ERROR;
}
node->left = parser->node;
module = (njs_module_t *) parser->target;
if (module->index == 0) {
ret = njs_module_insert(parser->vm, module);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
}
node->index = (njs_index_t) module;
parser->node = node;
return njs_parser_stack_pop(parser);
}
static njs_int_t
njs_module_lookup(njs_vm_t *vm, const njs_str_t *cwd, njs_module_info_t *info)
{
njs_int_t ret;
njs_str_t *path;
njs_uint_t i;
if (info->name.start[0] == '/') {
return njs_module_absolute_path(vm, info);
}
ret = njs_module_relative_path(vm, cwd, info);
if (ret != NJS_DECLINED) {
return ret;
}
if (vm->paths == NULL) {
return NJS_DECLINED;
}
path = vm->paths->start;
for (i = 0; i < vm->paths->items; i++) {
ret = njs_module_relative_path(vm, path, info);
if (ret != NJS_DECLINED) {
return ret;
}
path++;
}
return NJS_DECLINED;
}
static njs_int_t
njs_module_absolute_path(njs_vm_t *vm, njs_module_info_t *info)
{
njs_str_t file;
file.length = info->name.length;
file.start = njs_mp_alloc(vm->mem_pool, file.length + 1);
if (njs_slow_path(file.start == NULL)) {
return NJS_ERROR;
}
memcpy(file.start, info->name.start, file.length);
file.start[file.length] = '\0';
info->fd = open((char *) file.start, O_RDONLY);
if (info->fd < 0) {
njs_mp_free(vm->mem_pool, file.start);
return NJS_DECLINED;
}
info->file = file;
return NJS_OK;
}
static njs_int_t
njs_module_relative_path(njs_vm_t *vm, const njs_str_t *dir,
njs_module_info_t *info)
{
u_char *p;
njs_str_t file;
njs_bool_t trail;
file.length = dir->length;
trail = (dir->start[dir->length - 1] != '/');
if (trail) {
file.length++;
}
file.length += info->name.length;
file.start = njs_mp_alloc(vm->mem_pool, file.length + 1);
if (njs_slow_path(file.start == NULL)) {
return NJS_ERROR;
}
p = njs_cpymem(file.start, dir->start, dir->length);
if (trail) {
*p++ = '/';
}
p = njs_cpymem(p, info->name.start, info->name.length);
*p = '\0';
info->fd = open((char *) file.start, O_RDONLY);
if (info->fd < 0) {
njs_mp_free(vm->mem_pool, file.start);
return NJS_DECLINED;
}
info->file = file;
return NJS_OK;
}
static njs_int_t
njs_module_read(njs_vm_t *vm, int fd, njs_str_t *text)
{
ssize_t n;
struct stat sb;
if (fstat(fd, &sb) == -1) {
goto fail;
}
if (!S_ISREG(sb.st_mode)) {
goto fail;
}
text->length = sb.st_size;
text->start = njs_mp_alloc(vm->mem_pool, text->length);
if (text->start == NULL) {
goto fail;
}
n = read(fd, text->start, sb.st_size);
if (n < 0 || n != sb.st_size) {
goto fail;
}
return NJS_OK;
fail:
if (text->start != NULL) {
njs_mp_free(vm->mem_pool, text->start);
}
return NJS_ERROR;
}
static njs_bool_t
njs_module_realpath_equal(const njs_str_t *path1, const njs_str_t *path2)
{
char rpath1[MAXPATHLEN], rpath2[MAXPATHLEN];
realpath((char *) path1->start, rpath1);
realpath((char *) path2->start, rpath2);
return (strcmp(rpath1, rpath2) == 0);
}
static njs_int_t
njs_module_hash_test(njs_lvlhsh_query_t *lhq, void *data)
{
njs_module_t *module;
module = data;
if (njs_strstr_eq(&lhq->key, &module->name)) {
return NJS_OK;
}
return NJS_DECLINED;
}
const njs_lvlhsh_proto_t njs_modules_hash_proto
njs_aligned(64) =
{
NJS_LVLHSH_DEFAULT,
njs_module_hash_test,
njs_lvlhsh_alloc,
njs_lvlhsh_free,
};
static njs_module_t *
njs_module_find(njs_vm_t *vm, njs_str_t *name, njs_bool_t local)
{
njs_int_t ret;
njs_module_t *module, *shared;
njs_object_t *object;
njs_lvlhsh_query_t lhq;
lhq.key = *name;
lhq.key_hash = njs_djb_hash(name->start, name->length);
lhq.proto = &njs_modules_hash_proto;
if (njs_lvlhsh_find(&vm->modules_hash, &lhq) == NJS_OK) {
return lhq.value;
}
if (njs_lvlhsh_find(&vm->shared->modules_hash, &lhq) == NJS_OK) {
shared = lhq.value;
if (!local) {
return shared;
}
module = njs_mp_alloc(vm->mem_pool, sizeof(njs_module_t));
if (njs_slow_path(module == NULL)) {
njs_memory_error(vm);
return NULL;
}
memcpy(module, shared, sizeof(njs_module_t));
object = &module->object;
object->__proto__ = &vm->prototypes[NJS_OBJ_TYPE_OBJECT].object;
object->slots = NULL;
object->shared = 0;
object->extensible = 1;
object->error_data = 0;
object->fast_array = 0;
lhq.replace = 0;
lhq.value = module;
lhq.pool = vm->mem_pool;
ret = njs_lvlhsh_insert(&vm->modules_hash, &lhq);
if (njs_fast_path(ret == NJS_OK)) {
return module;
}
}
return NULL;
}
static njs_module_t *
njs_module_add(njs_vm_t *vm, njs_str_t *name)
{
njs_int_t ret;
njs_module_t *module;
njs_lvlhsh_query_t lhq;
module = njs_mp_zalloc(vm->mem_pool, sizeof(njs_module_t));
if (njs_slow_path(module == NULL)) {
njs_memory_error(vm);
return NULL;
}
ret = njs_name_copy(vm, &module->name, name);
if (njs_slow_path(ret != NJS_OK)) {
njs_memory_error(vm);
return NULL;
}
lhq.replace = 0;
lhq.key = *name;
lhq.key_hash = njs_djb_hash(name->start, name->length);
lhq.value = module;
lhq.pool = vm->mem_pool;
lhq.proto = &njs_modules_hash_proto;
ret = njs_lvlhsh_insert(&vm->modules_hash, &lhq);
if (njs_fast_path(ret == NJS_OK)) {
return module;
}
njs_mp_free(vm->mem_pool, module->name.start);
njs_mp_free(vm->mem_pool, module);
njs_internal_error(vm, "lvlhsh insert failed");
return NULL;
}
static njs_int_t
njs_module_insert(njs_vm_t *vm, njs_module_t *module)
{
njs_module_t **value;
njs_parser_scope_t *scope;
scope = njs_parser_global_scope(vm);
module->index = njs_scope_next_index(vm, scope, NJS_SCOPE_INDEX_LOCAL,
&njs_value_undefined);
if (njs_slow_path(module->index == NJS_INDEX_ERROR)) {
return NJS_ERROR;
}
if (vm->modules == NULL) {
vm->modules = njs_arr_create(vm->mem_pool, 4, sizeof(njs_module_t *));
if (njs_slow_path(vm->modules == NULL)) {
return NJS_ERROR;
}
}
value = njs_arr_add(vm->modules);
if (njs_slow_path(value == NULL)) {
return NJS_ERROR;
}
*value = module;
return NJS_OK;
}
njs_int_t
njs_module_require(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
njs_int_t ret;
njs_str_t name;
njs_value_t *path;
njs_module_t *module;
if (nargs < 2) {
njs_type_error(vm, "missing path");
return NJS_ERROR;
}
path = njs_argument(args, 1);
ret = njs_value_to_string(vm, path, path);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
njs_string_get(path, &name);
module = njs_module_find(vm, &name, 1);
if (njs_slow_path(module == NULL)) {
njs_error(vm, "Cannot find module \"%V\"", &name);
return NJS_ERROR;
}
njs_set_object(&vm->retval, &module->object);
return NJS_OK;
}