diff mbox

[FFmpeg-devel,2/8] ffserver: Implement lua config file reader

Message ID 20180520185404.29836-3-klaxa1337@googlemail.com
State Superseded
Headers show

Commit Message

Stephan Holljes May 20, 2018, 6:53 p.m. UTC
Signed-off-by: Stephan Holljes <klaxa1337@googlemail.com>
---
 configreader.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 configreader.h |  46 +++++++++++++
 2 files changed, 257 insertions(+)
 create mode 100644 configreader.c
 create mode 100644 configreader.h

Comments

Michael Niedermayer May 20, 2018, 8:07 p.m. UTC | #1
On Sun, May 20, 2018 at 08:53:58PM +0200, Stephan Holljes wrote:
> Signed-off-by: Stephan Holljes <klaxa1337@googlemail.com>
> ---
>  configreader.c | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  configreader.h |  46 +++++++++++++
>  2 files changed, 257 insertions(+)
>  create mode 100644 configreader.c
>  create mode 100644 configreader.h
> 
> diff --git a/configreader.c b/configreader.c
> new file mode 100644
> index 0000000..84b27fa
> --- /dev/null
> +++ b/configreader.c
> @@ -0,0 +1,211 @@
[...]
> +int configs_read(struct HTTPDConfig **configs, const char *filename)
> +{
> +    int ret = 0;
> +    int nb_configs = 0;
> +    int nb_streams = 0;
> +    int nb_formats = 0;
> +    int i;
> +    int index = 0;
> +    const char *key, *error;
> +    struct HTTPDConfig *parsed_configs = NULL;
> +    struct HTTPDConfig *config;
> +    struct StreamConfig *stream;
> +    lua_State *L = luaL_newstate();
> +    ret = luaL_loadfile(L, filename);
> +    if (ret != 0) {
> +        fprintf(stderr, "Unable to open config file: %s\n", lua_tostring(L, -1));
> +        lua_close(L);
> +        return -1;
> +    }
> +    
> +    ret = lua_pcall(L, 0, 0, 0);
> +    
> +    if (ret != 0) {
> +        fprintf(stderr, "Unable to read config file: %s\n", lua_tostring(L, -1));
> +        lua_close(L);
> +        return -1;
> +    }
> +    lua_getglobal(L, "settings");
> +    if (lua_type(L, -1) != LUA_TTABLE) {
> +        lua_pushstring(L, "Error \"settings\" is not a table");
> +        goto fail;
> +    }
> +    lua_pushnil(L);
> +    
> +    // iterate servers
> +    while (lua_next(L, -2) != 0) {
> +        nb_configs++;

> +        parsed_configs = av_realloc(parsed_configs, nb_configs * sizeof(struct HTTPDConfig));
> +        config = &parsed_configs[nb_configs - 1];

Missing realloc failure handling


> +        config->server_name = NULL;
> +        config->bind_address = NULL;
> +        config->port = 0;
> +        config->accept_timeout = 1000;
> +        config->streams = NULL;
> +        config->nb_streams = 0;
> +        if (lua_type(L, -2) != LUA_TSTRING) {
> +            lua_pushstring(L, "Error server name is not a string.");
> +            goto fail;
> +        }
> +        if (lua_type(L, -1) != LUA_TTABLE) {
> +            lua_pushstring(L, "Error server settings is not a table.");
> +            goto fail;
> +        }

> +        config->server_name = av_strdup(lua_tostring(L, -2));

Missing alloc failure check, same with other similar allocation

[...]
> +fail:
> +    error = lua_tostring(L, -1);
> +    fprintf(stderr, "%s\n", error);
> +    lua_close(L);
> +    for (i = 0; i < nb_configs; i++)
> +        config_free(&parsed_configs[i]);
> +    av_free(parsed_configs);

> +    return -1;
> +}

We probably should use error codes (AVERROR*) from the begin.
Otherwise it would need a "2nd" pass one day to cleanup


> +
> diff --git a/configreader.h b/configreader.h
> new file mode 100644
> index 0000000..788ff60
> --- /dev/null
> +++ b/configreader.h
> @@ -0,0 +1,46 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#ifndef CONFIGREADER_H
> +#define CONFIGREADER_H
> +
> +#include "httpd.h"
> +
> +/**
> + * Read configurations from a file using the json format. The configurations
> + * are allocated as an array at *configs. This has to be freed by the user.
> + *
> + * @param configs pointer to a pointer where configurations will be allocated.
> + * @param filename filename of the configuration to use.
> + * @return number of configurations read, -1 on error.
> + */
> +int configs_read(struct HTTPDConfig **configs, const char *filename);
> +
> +/**
> + * Dump a configuration to stdout.
> + * @param config pointer to a configuration
> + */
> +void config_dump(struct HTTPDConfig *config);

it may be more flexible to add a argument that can point to stdout/stderr/file

[...]
James Darnley May 21, 2018, 1:04 p.m. UTC | #2
On 2018-05-20 20:53, Stephan Holljes wrote:
> +#include <lua5.3/lua.h>
> +#include <lua5.3/lauxlib.h>
> +#include <lua5.3/lualib.h>

That's not portable.  Lua headers are not in a subdirectory.

> +int configs_read(struct HTTPDConfig **configs, const char *filename)
> +{
> +    int ret = 0;
> +    int nb_configs = 0;
> +    int nb_streams = 0;
> +    int nb_formats = 0;
> +    int i;
> +    int index = 0;
> +    const char *key, *error;
> +    struct HTTPDConfig *parsed_configs = NULL;
> +    struct HTTPDConfig *config;
> +    struct StreamConfig *stream;
> +    lua_State *L = luaL_newstate();
> +    ret = luaL_loadfile(L, filename);
> +    if (ret != 0) {
> +        fprintf(stderr, "Unable to open config file: %s\n", lua_tostring(L, -1));
> +        lua_close(L);
> +        return -1;
> +    }
> +    
> +    ret = lua_pcall(L, 0, 0, 0);
> +    
> +    if (ret != 0) {
> +        fprintf(stderr, "Unable to read config file: %s\n", lua_tostring(L, -1));
> +        lua_close(L);
> +        return -1;
> +    }
> +    lua_getglobal(L, "settings");
> +    if (lua_type(L, -1) != LUA_TTABLE) {
> +        lua_pushstring(L, "Error \"settings\" is not a table");
> +        goto fail;
> +    }
> +    lua_pushnil(L);
> +    
> +    // iterate servers
> +    while (lua_next(L, -2) != 0) {
> +        nb_configs++;
> +        parsed_configs = av_realloc(parsed_configs, nb_configs * sizeof(struct HTTPDConfig));
> +        config = &parsed_configs[nb_configs - 1];
> +        config->server_name = NULL;
> +        config->bind_address = NULL;
> +        config->port = 0;
> +        config->accept_timeout = 1000;
> +        config->streams = NULL;
> +        config->nb_streams = 0;
> +        if (lua_type(L, -2) != LUA_TSTRING) {
> +            lua_pushstring(L, "Error server name is not a string.");
> +            goto fail;
> +        }
> +        if (lua_type(L, -1) != LUA_TTABLE) {
> +            lua_pushstring(L, "Error server settings is not a table.");
> +            goto fail;
> +        }
> +        config->server_name = av_strdup(lua_tostring(L, -2));
> +        lua_pushnil(L);
> +        // iterate server properties
> +        nb_streams = 0;
> +        while(lua_next(L, -2) != 0) {
> +            if (lua_type(L, -2) != LUA_TSTRING) {
> +                lua_pushstring(L, "Error server property is not a string.");
> +                goto fail;
> +            }
> +            key = lua_tostring(L, -2);
> +            if (!strncmp("bind_address", key, 12)) {
> +                config->bind_address = av_strdup(lua_tostring(L, -1));
> +            } else if (!strncmp("port", key, 4)) {
> +                config->port = (int) lua_tonumber(L, -1);
> +            } else {
> +                // keys that are not "bind_address" or "port" are streams
> +                if (lua_type(L, -1) != LUA_TTABLE) {
> +                    lua_pushstring(L, "Error Stream configuration is not a table.");
> +                    goto fail;
> +                }
> +                
> +                nb_streams++;
> +                config->streams = av_realloc(config->streams, nb_streams * sizeof(struct StreamConfig));
> +                stream = &config->streams[nb_streams - 1];
> +                stream->input_uri = NULL;
> +                stream->formats = NULL;
> +                stream->stream_name = av_strdup(lua_tostring(L, -2));
> +                lua_pushnil(L);
> +                while(lua_next(L, -2) != 0) {
> +                    if (lua_type(L, -2) != LUA_TSTRING) {
> +                        lua_pushstring(L, "Error stream property is not a string.");
> +                        goto fail;
> +                    }
> +                    key = lua_tostring(L, -2);
> +                    if (!strncmp("input", key, 5)) {
> +                        stream->input_uri = av_strdup(lua_tostring(L, -1));
> +                    } else if (!strncmp("formats", key, 7)) {
> +                        index = 1;
> +                        nb_formats = 0;
> +                        lua_pushnumber(L, index);
> +                        while(1) {
> +                            lua_gettable(L, -2);
> +                            if (lua_isnil(L, -1))
> +                                break;
> +                            if (lua_type(L, -1) != LUA_TSTRING) {
> +                                lua_pushstring(L, "Error format name is not a string.");
> +                                goto fail;
> +                            }
> +                            stream->formats = av_realloc(stream->formats,
> +                                                         (nb_formats + 1) * sizeof(enum StreamFormat));
> +                            key = lua_tostring(L, -1);
> +                            if (!strncmp("mkv", key, 3)) {
> +                                stream->formats[nb_formats++] = FMT_MATROSKA;
> +                            } else {
> +                                fprintf(stderr, "Warning unknown format (%s) in stream format configuration.\n",
> +                                                                                                           key);
> +                                av_realloc(stream->formats, nb_formats * sizeof(enum StreamFormat));
> +                            }
> +                            stream->nb_formats = nb_formats;
> +                            lua_pop(L, 1);
> +                            lua_pushnumber(L, ++index);
> +                        }
> +                        lua_pop(L, 1);
> +                            
> +                    } else {
> +                        fprintf(stderr, "Warning unknown key (%s) in stream configuration.\n", key);
> +                    }
> +                    lua_pop(L, 1);
> +                }
> +            }
> +            lua_pop(L, 1);
> +        }
> +        config->nb_streams = nb_streams;
> +        lua_pop(L, 1);
> +     }
> +    
> +    lua_close(L);
> +    *configs = parsed_configs;
> +    return nb_configs;
> +    
> +fail:
> +    error = lua_tostring(L, -1);
> +    fprintf(stderr, "%s\n", error);
> +    lua_close(L);
> +    for (i = 0; i < nb_configs; i++)
> +        config_free(&parsed_configs[i]);
> +    av_free(parsed_configs);
> +    return -1;
> +}

Do you know what Lua does if any of these function calls raises an
error?  I see you catching the error when reading the config file but
all other use of Lua is not in a protected environment.

Lua 5.3 manual, section 4.6:
> If an error happens outside any protected environment, Lua calls a panic function (see lua_atpanic) and then calls abort, thus exiting the host application.

Even worse: if someone sets the global variable "settings" to something
that isn't a table then lua_next will immediately segfault and you will
get no diagnostics.  I was surprised by this because I thought it would
raise an error.

Other than these significant issues it looks like a reasonable use of
the Lua API.

My suggestions:
1 - Move all the Lua use into a lua_Cfunction and pcall it.
2 - Use that to raise a Lua error when checking types.
Stephan Holljes May 21, 2018, 9:10 p.m. UTC | #3
On Mon, May 21, 2018 at 3:04 PM, James Darnley <james.darnley@gmail.com> wrote:
> On 2018-05-20 20:53, Stephan Holljes wrote:
>> +#include <lua5.3/lua.h>
>> +#include <lua5.3/lauxlib.h>
>> +#include <lua5.3/lualib.h>
>
> That's not portable.  Lua headers are not in a subdirectory.

Yes, artifact from early testing, changed and tested, pkg-config adds
the appropriate include directories.

>
>> +int configs_read(struct HTTPDConfig **configs, const char *filename)
>> +{
>> +    int ret = 0;
>> +    int nb_configs = 0;
>> +    int nb_streams = 0;
>> +    int nb_formats = 0;
>> +    int i;
>> +    int index = 0;
>> +    const char *key, *error;
>> +    struct HTTPDConfig *parsed_configs = NULL;
>> +    struct HTTPDConfig *config;
>> +    struct StreamConfig *stream;
>> +    lua_State *L = luaL_newstate();
>> +    ret = luaL_loadfile(L, filename);
>> +    if (ret != 0) {
>> +        fprintf(stderr, "Unable to open config file: %s\n", lua_tostring(L, -1));
>> +        lua_close(L);
>> +        return -1;
>> +    }
>> +
>> +    ret = lua_pcall(L, 0, 0, 0);
>> +
>> +    if (ret != 0) {
>> +        fprintf(stderr, "Unable to read config file: %s\n", lua_tostring(L, -1));
>> +        lua_close(L);
>> +        return -1;
>> +    }
>> +    lua_getglobal(L, "settings");
>> +    if (lua_type(L, -1) != LUA_TTABLE) {
>> +        lua_pushstring(L, "Error \"settings\" is not a table");
>> +        goto fail;
>> +    }
>> +    lua_pushnil(L);
>> +
>> +    // iterate servers
>> +    while (lua_next(L, -2) != 0) {
>> +        nb_configs++;
>> +        parsed_configs = av_realloc(parsed_configs, nb_configs * sizeof(struct HTTPDConfig));
>> +        config = &parsed_configs[nb_configs - 1];
>> +        config->server_name = NULL;
>> +        config->bind_address = NULL;
>> +        config->port = 0;
>> +        config->accept_timeout = 1000;
>> +        config->streams = NULL;
>> +        config->nb_streams = 0;
>> +        if (lua_type(L, -2) != LUA_TSTRING) {
>> +            lua_pushstring(L, "Error server name is not a string.");
>> +            goto fail;
>> +        }
>> +        if (lua_type(L, -1) != LUA_TTABLE) {
>> +            lua_pushstring(L, "Error server settings is not a table.");
>> +            goto fail;
>> +        }
>> +        config->server_name = av_strdup(lua_tostring(L, -2));
>> +        lua_pushnil(L);
>> +        // iterate server properties
>> +        nb_streams = 0;
>> +        while(lua_next(L, -2) != 0) {
>> +            if (lua_type(L, -2) != LUA_TSTRING) {
>> +                lua_pushstring(L, "Error server property is not a string.");
>> +                goto fail;
>> +            }
>> +            key = lua_tostring(L, -2);
>> +            if (!strncmp("bind_address", key, 12)) {
>> +                config->bind_address = av_strdup(lua_tostring(L, -1));
>> +            } else if (!strncmp("port", key, 4)) {
>> +                config->port = (int) lua_tonumber(L, -1);
>> +            } else {
>> +                // keys that are not "bind_address" or "port" are streams
>> +                if (lua_type(L, -1) != LUA_TTABLE) {
>> +                    lua_pushstring(L, "Error Stream configuration is not a table.");
>> +                    goto fail;
>> +                }
>> +
>> +                nb_streams++;
>> +                config->streams = av_realloc(config->streams, nb_streams * sizeof(struct StreamConfig));
>> +                stream = &config->streams[nb_streams - 1];
>> +                stream->input_uri = NULL;
>> +                stream->formats = NULL;
>> +                stream->stream_name = av_strdup(lua_tostring(L, -2));
>> +                lua_pushnil(L);
>> +                while(lua_next(L, -2) != 0) {
>> +                    if (lua_type(L, -2) != LUA_TSTRING) {
>> +                        lua_pushstring(L, "Error stream property is not a string.");
>> +                        goto fail;
>> +                    }
>> +                    key = lua_tostring(L, -2);
>> +                    if (!strncmp("input", key, 5)) {
>> +                        stream->input_uri = av_strdup(lua_tostring(L, -1));
>> +                    } else if (!strncmp("formats", key, 7)) {
>> +                        index = 1;
>> +                        nb_formats = 0;
>> +                        lua_pushnumber(L, index);
>> +                        while(1) {
>> +                            lua_gettable(L, -2);
>> +                            if (lua_isnil(L, -1))
>> +                                break;
>> +                            if (lua_type(L, -1) != LUA_TSTRING) {
>> +                                lua_pushstring(L, "Error format name is not a string.");
>> +                                goto fail;
>> +                            }
>> +                            stream->formats = av_realloc(stream->formats,
>> +                                                         (nb_formats + 1) * sizeof(enum StreamFormat));
>> +                            key = lua_tostring(L, -1);
>> +                            if (!strncmp("mkv", key, 3)) {
>> +                                stream->formats[nb_formats++] = FMT_MATROSKA;
>> +                            } else {
>> +                                fprintf(stderr, "Warning unknown format (%s) in stream format configuration.\n",
>> +                                                                                                           key);
>> +                                av_realloc(stream->formats, nb_formats * sizeof(enum StreamFormat));
>> +                            }
>> +                            stream->nb_formats = nb_formats;
>> +                            lua_pop(L, 1);
>> +                            lua_pushnumber(L, ++index);
>> +                        }
>> +                        lua_pop(L, 1);
>> +
>> +                    } else {
>> +                        fprintf(stderr, "Warning unknown key (%s) in stream configuration.\n", key);
>> +                    }
>> +                    lua_pop(L, 1);
>> +                }
>> +            }
>> +            lua_pop(L, 1);
>> +        }
>> +        config->nb_streams = nb_streams;
>> +        lua_pop(L, 1);
>> +     }
>> +
>> +    lua_close(L);
>> +    *configs = parsed_configs;
>> +    return nb_configs;
>> +
>> +fail:
>> +    error = lua_tostring(L, -1);
>> +    fprintf(stderr, "%s\n", error);
>> +    lua_close(L);
>> +    for (i = 0; i < nb_configs; i++)
>> +        config_free(&parsed_configs[i]);
>> +    av_free(parsed_configs);
>> +    return -1;
>> +}
>
> Do you know what Lua does if any of these function calls raises an
> error?  I see you catching the error when reading the config file but
> all other use of Lua is not in a protected environment.

I've been wondering about this, but didn't look into it too deeply.

>
> Lua 5.3 manual, section 4.6:
>> If an error happens outside any protected environment, Lua calls a panic function (see lua_atpanic) and then calls abort, thus exiting the host application.
>
> Even worse: if someone sets the global variable "settings" to something
> that isn't a table then lua_next will immediately segfault and you will
> get no diagnostics.  I was surprised by this because I thought it would
> raise an error.

This should actually not happen since it is tested against LUA_TTABLE
before starting to read it. (All other variables that are extracted
from the stack are checked for the corresponding type as well.)

>
> Other than these significant issues it looks like a reasonable use of
> the Lua API.
>
> My suggestions:
> 1 - Move all the Lua use into a lua_Cfunction and pcall it.
> 2 - Use that to raise a Lua error when checking types.

Thanks, this sounds very reasonable and I have already started reading
the documentation on how to make use of that.

I have also locally fixed the other things addressed (AVERROR usage
and alloc and thread creation checks), but I have yet to test it works
properly.

Thanks again!
Stephan
diff mbox

Patch

diff --git a/configreader.c b/configreader.c
new file mode 100644
index 0000000..84b27fa
--- /dev/null
+++ b/configreader.c
@@ -0,0 +1,211 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "configreader.h"
+#include "httpd.h"
+#include <stdio.h>
+#include <string.h>
+#include <lua5.3/lua.h>
+#include <lua5.3/lauxlib.h>
+#include <lua5.3/lualib.h>
+
+#include <libavutil/mem.h>
+
+const char *stream_format_names[] = { "mkv" };
+
+void stream_free(struct StreamConfig *stream)
+{
+    if (stream->stream_name)
+        av_free(stream->stream_name);
+    if (stream->input_uri)
+        av_free(stream->input_uri);
+    if (stream->formats)
+        av_free(stream->formats);
+}
+
+void config_free(struct HTTPDConfig *config)
+{
+    int i;
+    if (config->server_name)
+        av_free(config->server_name);
+    if (config->bind_address)
+        av_free(config->bind_address);
+    if (config->streams) {
+        for (i = 0; i < config->nb_streams; i++)
+            stream_free(&config->streams[i]);
+        av_free(config->streams);
+    }
+}
+
+void config_dump(struct HTTPDConfig *config) {
+    int i, j;
+    printf("======\nserver name: %s\nbind_address: %s\nport: %d\nnb_streams: %d\n",
+            config->server_name, config->bind_address, config->port, config->nb_streams);
+    for (i = 0; i < config->nb_streams; i++) {
+        printf("------\nstream_name: %s\ninput: %s\nformats: ",
+            config->streams[i].stream_name, config->streams[i].input_uri);
+        for (j = 0; j < config->streams[i].nb_formats; j++) {
+            printf("%s ", stream_format_names[config->streams[i].formats[j]]);
+        }
+        printf("\n");
+    }
+}
+
+int configs_read(struct HTTPDConfig **configs, const char *filename)
+{
+    int ret = 0;
+    int nb_configs = 0;
+    int nb_streams = 0;
+    int nb_formats = 0;
+    int i;
+    int index = 0;
+    const char *key, *error;
+    struct HTTPDConfig *parsed_configs = NULL;
+    struct HTTPDConfig *config;
+    struct StreamConfig *stream;
+    lua_State *L = luaL_newstate();
+    ret = luaL_loadfile(L, filename);
+    if (ret != 0) {
+        fprintf(stderr, "Unable to open config file: %s\n", lua_tostring(L, -1));
+        lua_close(L);
+        return -1;
+    }
+    
+    ret = lua_pcall(L, 0, 0, 0);
+    
+    if (ret != 0) {
+        fprintf(stderr, "Unable to read config file: %s\n", lua_tostring(L, -1));
+        lua_close(L);
+        return -1;
+    }
+    lua_getglobal(L, "settings");
+    if (lua_type(L, -1) != LUA_TTABLE) {
+        lua_pushstring(L, "Error \"settings\" is not a table");
+        goto fail;
+    }
+    lua_pushnil(L);
+    
+    // iterate servers
+    while (lua_next(L, -2) != 0) {
+        nb_configs++;
+        parsed_configs = av_realloc(parsed_configs, nb_configs * sizeof(struct HTTPDConfig));
+        config = &parsed_configs[nb_configs - 1];
+        config->server_name = NULL;
+        config->bind_address = NULL;
+        config->port = 0;
+        config->accept_timeout = 1000;
+        config->streams = NULL;
+        config->nb_streams = 0;
+        if (lua_type(L, -2) != LUA_TSTRING) {
+            lua_pushstring(L, "Error server name is not a string.");
+            goto fail;
+        }
+        if (lua_type(L, -1) != LUA_TTABLE) {
+            lua_pushstring(L, "Error server settings is not a table.");
+            goto fail;
+        }
+        config->server_name = av_strdup(lua_tostring(L, -2));
+        lua_pushnil(L);
+        // iterate server properties
+        nb_streams = 0;
+        while(lua_next(L, -2) != 0) {
+            if (lua_type(L, -2) != LUA_TSTRING) {
+                lua_pushstring(L, "Error server property is not a string.");
+                goto fail;
+            }
+            key = lua_tostring(L, -2);
+            if (!strncmp("bind_address", key, 12)) {
+                config->bind_address = av_strdup(lua_tostring(L, -1));
+            } else if (!strncmp("port", key, 4)) {
+                config->port = (int) lua_tonumber(L, -1);
+            } else {
+                // keys that are not "bind_address" or "port" are streams
+                if (lua_type(L, -1) != LUA_TTABLE) {
+                    lua_pushstring(L, "Error Stream configuration is not a table.");
+                    goto fail;
+                }
+                
+                nb_streams++;
+                config->streams = av_realloc(config->streams, nb_streams * sizeof(struct StreamConfig));
+                stream = &config->streams[nb_streams - 1];
+                stream->input_uri = NULL;
+                stream->formats = NULL;
+                stream->stream_name = av_strdup(lua_tostring(L, -2));
+                lua_pushnil(L);
+                while(lua_next(L, -2) != 0) {
+                    if (lua_type(L, -2) != LUA_TSTRING) {
+                        lua_pushstring(L, "Error stream property is not a string.");
+                        goto fail;
+                    }
+                    key = lua_tostring(L, -2);
+                    if (!strncmp("input", key, 5)) {
+                        stream->input_uri = av_strdup(lua_tostring(L, -1));
+                    } else if (!strncmp("formats", key, 7)) {
+                        index = 1;
+                        nb_formats = 0;
+                        lua_pushnumber(L, index);
+                        while(1) {
+                            lua_gettable(L, -2);
+                            if (lua_isnil(L, -1))
+                                break;
+                            if (lua_type(L, -1) != LUA_TSTRING) {
+                                lua_pushstring(L, "Error format name is not a string.");
+                                goto fail;
+                            }
+                            stream->formats = av_realloc(stream->formats,
+                                                         (nb_formats + 1) * sizeof(enum StreamFormat));
+                            key = lua_tostring(L, -1);
+                            if (!strncmp("mkv", key, 3)) {
+                                stream->formats[nb_formats++] = FMT_MATROSKA;
+                            } else {
+                                fprintf(stderr, "Warning unknown format (%s) in stream format configuration.\n",
+                                                                                                           key);
+                                av_realloc(stream->formats, nb_formats * sizeof(enum StreamFormat));
+                            }
+                            stream->nb_formats = nb_formats;
+                            lua_pop(L, 1);
+                            lua_pushnumber(L, ++index);
+                        }
+                        lua_pop(L, 1);
+                            
+                    } else {
+                        fprintf(stderr, "Warning unknown key (%s) in stream configuration.\n", key);
+                    }
+                    lua_pop(L, 1);
+                }
+            }
+            lua_pop(L, 1);
+        }
+        config->nb_streams = nb_streams;
+        lua_pop(L, 1);
+     }
+    
+    lua_close(L);
+    *configs = parsed_configs;
+    return nb_configs;
+    
+fail:
+    error = lua_tostring(L, -1);
+    fprintf(stderr, "%s\n", error);
+    lua_close(L);
+    for (i = 0; i < nb_configs; i++)
+        config_free(&parsed_configs[i]);
+    av_free(parsed_configs);
+    return -1;
+}
+
diff --git a/configreader.h b/configreader.h
new file mode 100644
index 0000000..788ff60
--- /dev/null
+++ b/configreader.h
@@ -0,0 +1,46 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef CONFIGREADER_H
+#define CONFIGREADER_H
+
+#include "httpd.h"
+
+/**
+ * Read configurations from a file using the json format. The configurations
+ * are allocated as an array at *configs. This has to be freed by the user.
+ *
+ * @param configs pointer to a pointer where configurations will be allocated.
+ * @param filename filename of the configuration to use.
+ * @return number of configurations read, -1 on error.
+ */
+int configs_read(struct HTTPDConfig **configs, const char *filename);
+
+/**
+ * Dump a configuration to stdout.
+ * @param config pointer to a configuration
+ */
+void config_dump(struct HTTPDConfig *config);
+
+/**
+ * Free a configuration.
+ * @param config pointer to a configuration
+ */
+void config_free(struct HTTPDConfig *config);
+
+#endif