/* * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com */ #include "ngx_dynamic_conf.h" #include "ngx_toolkit_misc.h" #include "ngx_event_timer_module.h" static ngx_int_t ngx_dynamic_conf_process_init(ngx_cycle_t *cycle); static void *ngx_dynamic_conf_module_create_conf(ngx_cycle_t *cycle); static char *ngx_dynamic_conf_module_init_conf(ngx_cycle_t *cycle, void *conf); static char *ngx_dynamic_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_dynamic_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #define NGX_CONF_BUFFER 4096 ngx_uint_t ngx_core_max_module; static ngx_uint_t arg_number[] = { NGX_CONF_NOARGS, NGX_CONF_TAKE1, NGX_CONF_TAKE2, NGX_CONF_TAKE3, NGX_CONF_TAKE4, NGX_CONF_TAKE5, NGX_CONF_TAKE6, NGX_CONF_TAKE7 }; typedef struct { void **module_conf; /* index is module index */ ngx_pool_t *pool; /* dynamic conf alloc pool */ } ngx_dynamic_conf_ctx_t; typedef struct { ngx_dynamic_conf_ctx_t *conf[2]; unsigned used; /* 0 and 1 for index of conf */ ngx_str_t file; ngx_msec_t refresh; ngx_log_t *log; u_char md5key[NGX_MD5KEY_LEN + 1]; } ngx_dynamic_conf_conf_t; static ngx_command_t ngx_dynamic_conf_commands[] = { { ngx_string("dynamic_conf"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE2, ngx_dynamic_conf, 0, 0, NULL }, { ngx_string("dynamic_log"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE, ngx_dynamic_log, 0, 0, NULL }, ngx_null_command }; ngx_core_module_t ngx_dynamic_conf_module_ctx = { ngx_string("dynamic_conf"), ngx_dynamic_conf_module_create_conf, ngx_dynamic_conf_module_init_conf }; ngx_module_t ngx_dynamic_conf_module = { NGX_MODULE_V1, &ngx_dynamic_conf_module_ctx, /* module context */ ngx_dynamic_conf_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ ngx_dynamic_conf_process_init, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void * ngx_dynamic_conf_module_create_conf(ngx_cycle_t *cycle) { ngx_dynamic_conf_conf_t *conf; conf = ngx_pcalloc(cycle->pool, sizeof(ngx_dynamic_conf_conf_t)); if (conf == NULL) { return NULL; } conf->refresh = NGX_CONF_UNSET_MSEC; return conf; } static char * ngx_dynamic_conf_module_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_dynamic_conf_conf_t *dccf; dccf = conf; ngx_conf_init_msec_value(dccf->refresh, 60000); if (dccf->log == NULL) { dccf->log = &cycle->new_log; } return NGX_CONF_OK; } static char * ngx_dynamic_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_dynamic_conf_conf_t *dccf; ngx_str_t *value; ngx_fd_t fd; dccf = conf; if (dccf->file.len) { return "is duplicate"; } value = cf->args->elts; dccf->file = value[1]; if (ngx_conf_full_name(cf->cycle, &dccf->file, 0)) { return NGX_CONF_ERROR; } /* test file, ngx_conf_full_name will fill '\0' at end of file name */ fd = ngx_open_file(dccf->file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, "dynamic conf " ngx_open_file_n " \"%s\" failed", dccf->file.data); return NGX_CONF_ERROR; } ngx_close_file(fd); dccf->refresh = ngx_parse_time(&value[2], 0); if (dccf->refresh == (ngx_msec_t) NGX_ERROR) { return "invalid refresh"; } dccf->used = 1; return NGX_CONF_OK; } static char * ngx_dynamic_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_dynamic_conf_conf_t *dccf = conf; return ngx_log_set_log(cf, &dccf->log); } static ngx_int_t ngx_dynamic_conf_handler(ngx_conf_t *cf, ngx_int_t last) { char *rv; ngx_uint_t i, found; ngx_str_t *name; ngx_command_t *cmd; void *conf; name = cf->args->elts; found = 0; for (i = 0; cf->cycle->modules[i]; ++i) { cmd = (ngx_command_t *) cf->cycle->modules[i]->spare_hook1; if (cmd == NULL) { continue; } for (/* void */; cmd->name.len; ++cmd) { if (name->len != cmd->name.len) { continue; } if (ngx_strcmp(name->data, cmd->name.data) != 0) { continue; } found = 1; if (cf->cycle->modules[i]->type != NGX_CONF_MODULE && cf->cycle->modules[i]->type != cf->module_type) { continue; } /* is the directive's location right ? */ if (!(cmd->type & cf->cmd_type)) { continue; } if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "directive \"%s\" is not terminated by \";\"", name->data); return NGX_ERROR; } if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "directive \"%s\" has no opening \"{\"", name->data); return NGX_ERROR; } /* is the directive's argument count right ? */ if (!(cmd->type & NGX_CONF_ANY)) { if (cmd->type & NGX_CONF_FLAG) { if (cf->args->nelts != 2) { goto invalid; } } else if (cmd->type & NGX_CONF_1MORE) { if (cf->args->nelts < 2) { goto invalid; } } else if (cmd->type & NGX_CONF_2MORE) { if (cf->args->nelts < 3) { goto invalid; } } else if (cf->args->nelts > NGX_CONF_MAX_ARGS) { goto invalid; } else if (!(cmd->type & arg_number[cf->args->nelts - 1])) { goto invalid; } } /* set up the directive's configuration context */ conf = ((void **) cf->ctx)[cf->cycle->modules[i]->ctx_index]; rv = cmd->set(cf, cmd, conf); if (rv == NGX_CONF_OK) { return NGX_OK; } if (rv == NGX_CONF_ERROR) { return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"%s\" directive %s", name->data, rv); return NGX_ERROR; } } if (found) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"%s\" directive is not allowed here", name->data); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown directive \"%s\"", name->data); return NGX_ERROR; invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of arguments in \"%s\" directive", name->data); return NGX_ERROR; } /* same as ngx_conf_read_token in ngx_conf_file.c */ static ngx_int_t ngx_dynamic_conf_read_token(ngx_conf_t *cf) { u_char *start, ch, *src, *dst; off_t file_size; size_t len; ssize_t n, size; ngx_uint_t found, need_space, last_space, sharp_comment, variable; ngx_uint_t quoted, s_quoted, d_quoted, start_line; ngx_str_t *word; ngx_buf_t *b; found = 0; need_space = 0; last_space = 1; sharp_comment = 0; variable = 0; quoted = 0; s_quoted = 0; d_quoted = 0; cf->args->nelts = 0; b = cf->conf_file->buffer; start = b->pos; start_line = cf->conf_file->line; file_size = ngx_file_size(&cf->conf_file->file.info); for ( ;; ) { if (b->pos >= b->last) { if (cf->conf_file->file.offset >= file_size) { if (cf->args->nelts > 0 || !last_space) { if (cf->conf_file->file.fd == NGX_INVALID_FILE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of parameter, " "expecting \";\""); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of file, " "expecting \";\" or \"}\""); return NGX_ERROR; } return NGX_CONF_FILE_DONE; } len = b->pos - start; if (len == NGX_CONF_BUFFER) { cf->conf_file->line = start_line; if (d_quoted) { ch = '"'; } else if (s_quoted) { ch = '\''; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "too long parameter \"%*s...\" started", 10, start); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "too long parameter, probably " "missing terminating \"%c\" character", ch); return NGX_ERROR; } if (len) { ngx_memmove(b->start, start, len); } size = (ssize_t) (file_size - cf->conf_file->file.offset); if (size > b->end - (b->start + len)) { size = b->end - (b->start + len); } n = ngx_read_file(&cf->conf_file->file, b->start + len, size, cf->conf_file->file.offset); if (n == NGX_ERROR) { return NGX_ERROR; } if (n != size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ngx_read_file_n " returned " "only %z bytes instead of %z", n, size); return NGX_ERROR; } b->pos = b->start + len; b->last = b->pos + n; start = b->start; } ch = *b->pos++; if (ch == LF) { cf->conf_file->line++; if (sharp_comment) { sharp_comment = 0; } } if (sharp_comment) { continue; } if (quoted) { quoted = 0; continue; } if (need_space) { if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { last_space = 1; need_space = 0; continue; } if (ch == ';') { return NGX_OK; } if (ch == '{') { return NGX_CONF_BLOCK_START; } if (ch == ')') { last_space = 1; need_space = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"%c\"", ch); return NGX_ERROR; } } if (last_space) { if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { continue; } start = b->pos - 1; start_line = cf->conf_file->line; switch (ch) { case ';': case '{': if (cf->args->nelts == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"%c\"", ch); return NGX_ERROR; } if (ch == '{') { return NGX_CONF_BLOCK_START; } return NGX_OK; case '}': if (cf->args->nelts != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\""); return NGX_ERROR; } return NGX_CONF_BLOCK_DONE; case '#': sharp_comment = 1; continue; case '\\': quoted = 1; last_space = 0; continue; case '"': start++; d_quoted = 1; last_space = 0; continue; case '\'': start++; s_quoted = 1; last_space = 0; continue; default: last_space = 0; } } else { if (ch == '{' && variable) { continue; } variable = 0; if (ch == '\\') { quoted = 1; continue; } if (ch == '$') { variable = 1; continue; } if (d_quoted) { if (ch == '"') { d_quoted = 0; need_space = 1; found = 1; } } else if (s_quoted) { if (ch == '\'') { s_quoted = 0; need_space = 1; found = 1; } } else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF || ch == ';' || ch == '{') { last_space = 1; found = 1; } if (found) { word = ngx_array_push(cf->args); if (word == NULL) { return NGX_ERROR; } word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1); if (word->data == NULL) { return NGX_ERROR; } for (dst = word->data, src = start, len = 0; src < b->pos - 1; len++) { if (*src == '\\') { switch (src[1]) { case '"': case '\'': case '\\': src++; break; case 't': *dst++ = '\t'; src += 2; continue; case 'r': *dst++ = '\r'; src += 2; continue; case 'n': *dst++ = '\n'; src += 2; continue; } } *dst++ = *src++; } *dst = '\0'; word->len = len; if (ch == ';') { return NGX_OK; } if (ch == '{') { return NGX_CONF_BLOCK_START; } found = 0; } } } } ngx_int_t ngx_dynamic_conf_parse(ngx_conf_t *cf, unsigned init) { ngx_int_t rc; enum { parse_init = 0, parse_block } type; type = init ? parse_init : parse_block; for (;;) { rc = ngx_dynamic_conf_read_token(cf); /* * ngx_conf_read_token() may return * * NGX_ERROR there is error * NGX_OK the token terminated by ";" was found * NGX_CONF_BLOCK_START the token terminated by "{" was found * NGX_CONF_BLOCK_DONE the "}" was found * NGX_CONF_FILE_DONE the configuration file is done */ if (rc == NGX_ERROR) { return NGX_ERROR; } if (rc == NGX_CONF_BLOCK_DONE) { if (type != parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\""); return NGX_ERROR; } return NGX_OK; } if (rc == NGX_CONF_FILE_DONE) { if (type == parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of file, expecting \"}\""); return NGX_ERROR; } return NGX_OK; } rc = ngx_dynamic_conf_handler(cf, rc); if (rc == NGX_ERROR) { return NGX_ERROR; } } } static ngx_int_t ngx_dynamic_conf_load_conf(ngx_fd_t fd, ngx_dynamic_conf_conf_t *dccf) { ngx_dynamic_conf_ctx_t *conf; ngx_dynamic_core_module_t *module; ngx_pool_t *pool; ngx_conf_t cf; ngx_buf_t buf; ngx_conf_file_t conf_file; u_char buffer[NGX_CONF_BUFFER]; ngx_uint_t i; void *rv; ngx_memzero(&cf, sizeof(ngx_conf_t)); pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, dccf->log); if (pool == NULL) { return NGX_ERROR; } pool->log = dccf->log; conf = ngx_pcalloc(pool, sizeof(ngx_dynamic_conf_ctx_t)); if (conf == NULL) { goto failed; } conf->pool = pool; ngx_core_max_module = ngx_count_modules((ngx_cycle_t *) ngx_cycle, NGX_CORE_MODULE); conf->module_conf = ngx_pcalloc(pool, sizeof(void *) * ngx_core_max_module); if (conf->module_conf == NULL) { goto failed; } cf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t)); if (cf.args == NULL) { goto failed; } cf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, dccf->log); if (cf.temp_pool == NULL) { goto failed; } cf.ctx = conf->module_conf; cf.cycle = (ngx_cycle_t *) ngx_cycle; cf.pool = pool; cf.log = dccf->log; cf.module_type = NGX_CORE_MODULE; cf.cmd_type = NGX_MAIN_CONF; cf.conf_file = &conf_file; if (ngx_fd_info(fd, &cf.conf_file->file.info) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_EMERG, cf.log, ngx_errno, ngx_fd_info_n " failed"); } cf.conf_file->buffer = &buf; buf.pos = buf.last = buf.start = buffer; buf.end = buf.start + NGX_CONF_BUFFER; buf.temporary = 1; cf.conf_file->file.fd = fd; cf.conf_file->file.name.len = dccf->file.len; cf.conf_file->file.name.data = dccf->file.data; cf.conf_file->file.offset = 0; cf.conf_file->file.log = cf.log; cf.conf_file->line = 1; for (i = 0; ngx_cycle->modules[i]; ++i) { if (ngx_cycle->modules[i]->type != NGX_CORE_MODULE) { continue; } module = (ngx_dynamic_core_module_t *) ngx_cycle->modules[i]->spare_hook0; if (module == NULL) { /* dctx not configured */ continue; } if (module->create_conf) { rv = module->create_conf(&cf); if (rv == NULL) { goto failed; } conf->module_conf[ngx_cycle->modules[i]->ctx_index] = rv; } } if (ngx_dynamic_conf_parse(&cf, 1) != NGX_OK) { goto failed; } for (i = 0; ngx_cycle->modules[i]; ++i) { if (ngx_cycle->modules[i]->type != NGX_CORE_MODULE) { continue; } module = (ngx_dynamic_core_module_t *) ngx_cycle->modules[i]-> spare_hook0; if (module == NULL) { continue; } if (module->init_conf) { rv = conf->module_conf[ngx_cycle->modules[i]->ctx_index]; if (module->init_conf(&cf, rv) == NGX_CONF_ERROR) { goto failed; } } } /* load conf ok, switch conf to new conf */ if (dccf->conf[dccf->used]) { pool = dccf->conf[dccf->used]->pool; dccf->conf[dccf->used] = NULL; ngx_destroy_pool(pool); } dccf->used = dccf->used ? 0 : 1; dccf->conf[dccf->used] = conf; return NGX_OK; failed: if (cf.temp_pool) { ngx_destroy_pool(cf.temp_pool); } ngx_destroy_pool(pool); return NGX_ERROR; } static void ngx_dynamic_conf_check_conf(void *data) { ngx_dynamic_conf_conf_t *dccf; ngx_fd_t fd; u_char md5key[NGX_MD5KEY_LEN]; NGX_START_TIMING dccf = data; if (ngx_exiting) { /* avoid nginx reload worker hungup */ return; } fd = ngx_open_file(dccf->file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { ngx_log_error(NGX_LOG_ERR, dccf->log, ngx_errno, ngx_open_file_n "\"%V\" failed", &dccf->file); goto done; } /* check md5key for dynamic conf file */ if (ngx_md5_file(fd, md5key) == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "md5 file \"%V\" failed", &dccf->file); goto done; } if (ngx_memcmp(dccf->md5key, md5key, NGX_MD5KEY_LEN) == 0) { ngx_log_debug1(NGX_LOG_DEBUG_CORE, dccf->log, 0, "\"%V\" md5key not change", &dccf->file); goto done; } /* parse dynamic conf */ if (ngx_dynamic_conf_load_conf(fd, dccf) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "load file \"%V\" failed", &dccf->file); goto done; } ngx_memcpy(dccf->md5key, md5key, NGX_MD5KEY_LEN); ngx_log_error(NGX_LOG_INFO, dccf->log, 0, "dynamic conf load successd, md5key: \"%s\"", dccf->md5key); done: ngx_close_file(fd); ngx_event_timer_add_timer(dccf->refresh, ngx_dynamic_conf_check_conf, dccf); NGX_STOP_TIMING(dccf->log, "ngx_dynamic_conf_check_conf") } static ngx_int_t ngx_dynamic_conf_process_init(ngx_cycle_t *cycle) { ngx_dynamic_conf_conf_t *dccf; ngx_fd_t fd; u_char md5key[NGX_MD5KEY_LEN]; NGX_START_TIMING dccf = (ngx_dynamic_conf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_dynamic_conf_module); if (dccf->file.len == 0) { /* dynamic conf not configured */ return NGX_OK; } fd = ngx_open_file(dccf->file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { ngx_log_error(NGX_LOG_ERR, dccf->log, ngx_errno, ngx_open_file_n "\"%V\" failed", &dccf->file); return NGX_ERROR; } /* parse dynamic conf */ if (ngx_dynamic_conf_load_conf(fd, dccf) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "load file \"%V\" failed", &dccf->file); return NGX_ERROR; } /* calc md5key for dynamic conf file */ if (ngx_md5_file(fd, md5key) == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, dccf->log, 0, "md5 file \"%V\" failed", &dccf->file); return NGX_ERROR; } ngx_close_file(fd); ngx_memcpy(dccf->md5key, md5key, NGX_MD5KEY_LEN); ngx_log_error(NGX_LOG_INFO, dccf->log, 0, "dynamic conf load successd, md5key: \"%s\"", dccf->md5key); /* add dynamic conf parse timer */ ngx_event_timer_add_timer(dccf->refresh, ngx_dynamic_conf_check_conf, dccf); NGX_STOP_TIMING(dccf->log, "ngx_dynamic_conf_process_init") return NGX_OK; } #if (NGX_PCRE) ngx_dynamic_regex_t * ngx_dynamic_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc) { ngx_dynamic_regex_t *re; rc->pool = cf->pool; if (ngx_regex_compile(rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err); return NULL; } re = ngx_pcalloc(cf->pool, sizeof(ngx_regex_elt_t)); if (re == NULL) { return NULL; } re->regex = rc->regex; re->name = rc->pattern; return re; } #endif int ngx_dynamic_cmp_dns_wildcards(const void *one, const void *two) { ngx_hash_key_t *first, *second; first = (ngx_hash_key_t *) one; second = (ngx_hash_key_t *) two; return ngx_dns_strcmp(first->key.data, second->key.data); } void * ngx_get_dconf(ngx_module_t *m) { ngx_dynamic_conf_conf_t *dccf; dccf = (ngx_dynamic_conf_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, ngx_dynamic_conf_module); if (dccf->conf[dccf->used] == 0) { /* dynamic conf not configured */ return NULL; } return dccf->conf[dccf->used]->module_conf[m->ctx_index]; }