[PATCH] ngx_conf_file: "include ./" acts relative to currently parsed file

Guillaume Outters guillaume-nginx at outters.eu
Thu Aug 29 06:04:41 UTC 2019


as an Nginx user, I regularly discover new features that prove useful if not game changers.

However I stay puzzled (nearly since I started using it) on why the config include system does not allow relative paths, that is, relative to the currently parsed file. This would allow for simple modular config designs, where a "main" server config file can embed the snippets that are deployed next to it.
The only tips I see on the forums and so is "hey, just use a templating system to absolutize every include at deployment time", which takes us away from Nginx' KISS philosophy.

In an ideal world, my production nginx.conf would only include /var/www/*/app.conf, and I could drop my "blorp" web app (that I developed on /home/gui/www/blorp) in /var/www and have it running at the next nginx reload, with it correctly loading every location /xxx { include inc/phpfpm.conf; } of its app.conf.
For now, I either have to centralize the snippets in /etc/nginx/inc/phpfpm.conf (thus when a new rule has to be added my developer has to tell my system operator to apply the change to the centralized file), or inline the snippets in the (then monolithic) app.conf (hey, duplication!), or hardcode the snippet's path as /var/www/blorp/inc/phpfpm.conf (and symlink it on my dev machine so that prod and dev config files are shared?), or better make the app.conf a template and fill absolute paths at deployment, so that if I want to run my shiny new version of blorp as blorp-ng along blorp it does not include the old version's phpfpm.conf erronously.

The following patch adds a simple heuristic to include: if the includee starts with "./", it is considered relative to the current file. If not, the current heuristic applies (paths stay relative to the prefix).

I would be interested in learning the flaws or drawbacks in this (bad?) idea. I first thought "security", (disallowing relative includes keeps included files under control in config's root), but anyhow, either you keep total control on the config (and are on your responsibility to not include anything out of the conf tree) or give the web app's developer a hook to load its app-required snippets, and then nothing prevents him to include whatever he wants.


# HG changeset patch
# User Guillaume Outters <guillaume-nginx at outters.eu>
# Date 1567058353 -7200
# Thu Aug 29 07:59:13 2019 +0200
# Node ID 704100d8a8772a4bc2faaef9abcc0308316e580c
# Parent  9f1f9d6e056a4f85907957ef263f78a426ae4f9c
ngx_conf_file: "include ./" acts relative to currently parsed file

Allow configuration files to include relatively to them instead of to prefix.
Eases modular configurations, where includees do not need to know their
absolute path to reach helper config files next to them.

diff -r 9f1f9d6e056a -r 704100d8a877 src/core/ngx_conf_file.c
--- a/src/core/ngx_conf_file.c	Mon Aug 19 15:16:06 2019 +0300
+++ b/src/core/ngx_conf_file.c	Thu Aug 29 12:59:13 2019 +0200
@@ -15,6 +15,7 @@
 static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf);
 static void ngx_conf_flush_files(ngx_cycle_t *cycle);
+ngx_int_t ngx_conf_full_name_rel(ngx_cycle_t *cycle, ngx_str_t *name, ngx_uint_t conf_prefix, ngx_str_t *current);
 static ngx_command_t  ngx_conf_commands[] = {
@@ -830,7 +831,7 @@
     ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
-    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+    if (ngx_conf_full_name_rel(cf->cycle, &file, 1, &cf->conf_file->file.name) != NGX_OK) {
         return NGX_CONF_ERROR;
@@ -884,16 +885,39 @@
-ngx_conf_full_name(ngx_cycle_t *cycle, ngx_str_t *name, ngx_uint_t conf_prefix)
+ngx_conf_full_name_rel(ngx_cycle_t *cycle, ngx_str_t *name, ngx_uint_t conf_prefix, ngx_str_t *current)
     ngx_str_t  *prefix;
+    ngx_str_t  local_prefix;
-    prefix = conf_prefix ? &cycle->conf_prefix : &cycle->prefix;
+    /*
+     * Path starting with literal ./ is interpreted relative to current's
+     * directory instead of prefix.
+     */
+    if (name->len >= 2 && name->data[0] == '.' && name->data[1] == '/' && current && current->len && current->data[0] == '/') {
+        name->len -= 2;
+        name->data += 2;
+        local_prefix.data = current->data;
+        for (local_prefix.len = current->len; local_prefix.data[--local_prefix.len] != '/'; /* void */ ) /* void */ ;
+        ++local_prefix.len;
+        prefix = &local_prefix;
+    } else {
+        prefix = conf_prefix ? &cycle->conf_prefix : &cycle->prefix;
+    }
     return ngx_get_full_name(cycle->pool, prefix, name);
+ngx_conf_full_name(ngx_cycle_t *cycle, ngx_str_t *name, ngx_uint_t conf_prefix)
+    return ngx_conf_full_name_rel(cycle, name, conf_prefix, NULL);
 ngx_open_file_t *
 ngx_conf_open_file(ngx_cycle_t *cycle, ngx_str_t *name)

More information about the nginx-devel mailing list