Index: uspace/lib/conf/include/conf/ini.h
===================================================================
--- uspace/lib/conf/include/conf/ini.h	(revision 0f5546a8c19b83125c71db41fd626cb711f1112a)
+++ uspace/lib/conf/include/conf/ini.h	(revision a58727cded39abc2fd1b1d8f3fdccdca5a99f6c6)
@@ -109,4 +109,6 @@
 extern int ini_parse_file(const char *, ini_configuration_t *, text_parse_t *);
 
+extern int ini_parse_string(const char *, ini_configuration_t *, text_parse_t *);
+
 extern ini_section_t *ini_get_section(ini_configuration_t *, const char *);
 
Index: uspace/lib/conf/meson.build
===================================================================
--- uspace/lib/conf/meson.build	(revision 0f5546a8c19b83125c71db41fd626cb711f1112a)
+++ uspace/lib/conf/meson.build	(revision a58727cded39abc2fd1b1d8f3fdccdca5a99f6c6)
@@ -32,2 +32,8 @@
 	'src/text_parse.c',
 )
+
+
+test_src = files(
+	'test/ini.c',
+	'test/main.c',
+)
Index: uspace/lib/conf/src/ini.c
===================================================================
--- uspace/lib/conf/src/ini.c	(revision 0f5546a8c19b83125c71db41fd626cb711f1112a)
+++ uspace/lib/conf/src/ini.c	(revision a58727cded39abc2fd1b1d8f3fdccdca5a99f6c6)
@@ -53,8 +53,12 @@
 } ini_item_t;
 
+/** Line reader for generic parsing */
+typedef char *(*line_reader_t)(char *, int, void *);
 
 /* Necessary forward declarations */
 static void ini_section_destroy(ini_section_t **);
 static void ini_item_destroy(ini_item_t **);
+static ini_section_t *ini_section_create(void);
+static ini_item_t *ini_item_create(void);
 
 /* Hash table functions */
@@ -73,4 +77,8 @@
 static size_t ini_section_ht_key_hash(void *key)
 {
+	/* Nameless default section */
+	if (key == NULL) {
+		return 0;
+	}
 	return hash_string((const char *)key);
 }
@@ -85,6 +93,13 @@
 static bool ini_section_ht_key_equal(void *key, const ht_link_t *item)
 {
-	return str_cmp((const char *)key,
-	    hash_table_get_inst(item, ini_section_t, ht_link)->name) == 0;
+	const char *name = key;
+	ini_section_t *section =
+	    hash_table_get_inst(item, ini_section_t, ht_link);
+
+	if (key == NULL || section->name == NULL) {
+		return section->name == key;
+	}
+
+	return str_cmp(name, section->name) == 0;
 }
 
@@ -145,97 +160,45 @@
 };
 
-/* Actual INI functions */
-
-void ini_configuration_init(ini_configuration_t *conf)
-{
-	hash_table_create(&conf->sections, 0, 0, &configuration_ht_ops);
-}
-
-/** INI configuration destructor
- *
- * Release all resources of INI structure but the structure itself.
- */
-void ini_configuration_deinit(ini_configuration_t *conf)
-{
-	hash_table_destroy(&conf->sections);
-}
-
-static void ini_section_init(ini_section_t *section)
-{
-	hash_table_create(&section->items, 0, 0, &section_ht_ops);
-	section->name = NULL;
-}
-
-static ini_section_t* ini_section_create(void)
-{
-	ini_section_t *section = malloc(sizeof(ini_section_t));
-	if (section != NULL) {
-		ini_section_init(section);
-	}
-	return section;
-}
-
-static void ini_section_destroy(ini_section_t **section_ptr)
-{
-	ini_section_t *section = *section_ptr;
-	if (section == NULL) {
-		return;
-	}
-	hash_table_destroy(&section->items);
-	free(section->name);
-	free(section);
-	*section_ptr = NULL;
-}
-
-static void ini_item_init(ini_item_t *item)
-{
-	item->key = NULL;
-	item->value = NULL;
-}
-
-static ini_item_t *ini_item_create(void)
-{
-	ini_item_t *item = malloc(sizeof(ini_item_t));
-	if (item != NULL) {
-		ini_item_init(item);
-	}
-	return item;
-}
-
-static void ini_item_destroy(ini_item_t **item_ptr)
-{
-	ini_item_t *item = *item_ptr;
-	if (item == NULL) {
-		return;
-	}
-	free(item->key);
-	free(item->value);
-	free(item);
-	*item_ptr = NULL;
-}
-
-/** Parse file contents to INI structure
- *
- * @param[in]    filename
- * @param[out]   conf      initialized structure for configuration
- * @param[out]   parse     initialized structure to keep parsing errors
- *
- * @return EOK on success
- * @return EIO when file cannot be opened
- * @return ENOMEM
- * @return EINVAL on parse error (details in parse structure)
- */
-int ini_parse_file(const char *filename, ini_configuration_t *conf,
-    text_parse_t *parse)
+/*
+ * Static functions
+ */
+static char *read_file(char *buffer, int size, void *data)
+{
+	return fgets(buffer, size, (FILE *)data);
+}
+
+static char *read_string(char *buffer, int size, void *data)
+{
+	char **string_ptr = (char **)data;
+	char *string = *string_ptr;
+
+	int i = 0;
+	while (i < size - 1) {
+		char c = string[i];
+		if (c == '\0') {
+			break;
+		}
+
+		buffer[i++] = c;
+
+		if (c == '\n') {
+			break;
+		}
+	}
+
+	if (i == 0) {
+		return NULL;
+	}
+
+	buffer[i] = '\0';
+	*string_ptr = string + i;
+	return buffer;
+}
+
+static int ini_parse_generic(line_reader_t line_reader, void *reader_data,
+    ini_configuration_t *conf, text_parse_t *parse)
 {
 	int rc = EOK;
-	FILE *f = NULL;
 	char *line_buffer = NULL;
-
-	f = fopen(filename, "r");
-	if (f == NULL) {
-		rc = EIO;
-		goto finish;
-	}
 
 	line_buffer = malloc(LINE_BUFFER);
@@ -249,5 +212,5 @@
 	size_t lineno = 0;
 
-	while ((line = fgets(line_buffer, LINE_BUFFER - 1, f))) {
+	while ((line = line_reader(line_buffer, LINE_BUFFER, reader_data))) {
 		++lineno;
 		size_t line_len = str_size(line);
@@ -361,10 +324,125 @@
 
 finish:
-	if (f) {
-		fclose(f);
-	}
 	free(line_buffer);
 
 	return rc;
+}
+
+
+
+/*
+ * Actual INI functions
+ */
+
+void ini_configuration_init(ini_configuration_t *conf)
+{
+	hash_table_create(&conf->sections, 0, 0, &configuration_ht_ops);
+}
+
+/** INI configuration destructor
+ *
+ * Release all resources of INI structure but the structure itself.
+ */
+void ini_configuration_deinit(ini_configuration_t *conf)
+{
+	hash_table_destroy(&conf->sections);
+}
+
+static void ini_section_init(ini_section_t *section)
+{
+	hash_table_create(&section->items, 0, 0, &section_ht_ops);
+	section->name = NULL;
+}
+
+static ini_section_t* ini_section_create(void)
+{
+	ini_section_t *section = malloc(sizeof(ini_section_t));
+	if (section != NULL) {
+		ini_section_init(section);
+	}
+	return section;
+}
+
+static void ini_section_destroy(ini_section_t **section_ptr)
+{
+	ini_section_t *section = *section_ptr;
+	if (section == NULL) {
+		return;
+	}
+	hash_table_destroy(&section->items);
+	free(section->name);
+	free(section);
+	*section_ptr = NULL;
+}
+
+static void ini_item_init(ini_item_t *item)
+{
+	item->key = NULL;
+	item->value = NULL;
+}
+
+static ini_item_t *ini_item_create(void)
+{
+	ini_item_t *item = malloc(sizeof(ini_item_t));
+	if (item != NULL) {
+		ini_item_init(item);
+	}
+	return item;
+}
+
+static void ini_item_destroy(ini_item_t **item_ptr)
+{
+	ini_item_t *item = *item_ptr;
+	if (item == NULL) {
+		return;
+	}
+	free(item->key);
+	free(item->value);
+	free(item);
+	*item_ptr = NULL;
+}
+
+
+/** Parse file contents to INI structure
+ *
+ * @param[in]    filename
+ * @param[out]   conf      initialized structure for configuration
+ * @param[out]   parse     initialized structure to keep parsing errors
+ *
+ * @return EOK on success
+ * @return EIO when file cannot be opened
+ * @return ENOMEM
+ * @return EINVAL on parse error (details in parse structure)
+ */
+int ini_parse_file(const char *filename, ini_configuration_t *conf,
+    text_parse_t *parse)
+{
+	FILE *f = NULL;
+	f = fopen(filename, "r");
+	if (f == NULL) {
+		return EIO;
+	}
+
+	int rc = ini_parse_generic(&read_file, f, conf, parse);
+	fclose(f);
+	return rc;
+}
+
+/** Parse string to INI structure
+ *
+ * @param[in]    string
+ * @param[out]   conf      initialized structure for configuration
+ * @param[out]   parse     initialized structure to keep parsing errors
+ *
+ * @return EOK on success
+ * @return ENOMEM
+ * @return EINVAL on parse error (details in parse structure)
+ */
+int ini_parse_string(const char *string, ini_configuration_t *conf,
+    text_parse_t *parse)
+{
+	const char *string_ptr = string;
+
+	return ini_parse_generic(&read_string, &string_ptr, conf, parse);
 }
 
Index: uspace/lib/conf/test/ini.c
===================================================================
--- uspace/lib/conf/test/ini.c	(revision a58727cded39abc2fd1b1d8f3fdccdca5a99f6c6)
+++ uspace/lib/conf/test/ini.c	(revision a58727cded39abc2fd1b1d8f3fdccdca5a99f6c6)
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2015 Michal Koutny
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <conf/ini.h>
+#include <pcut/pcut.h>
+#include <str.h>
+
+PCUT_INIT
+
+PCUT_TEST_SUITE(ini);
+
+static ini_configuration_t ini_conf;
+static text_parse_t parse;
+
+PCUT_TEST_BEFORE {
+	ini_configuration_init(&ini_conf);
+	text_parse_init(&parse);
+}
+
+PCUT_TEST_AFTER {
+	text_parse_deinit(&parse);
+	ini_configuration_deinit(&ini_conf);
+}
+
+PCUT_TEST(simple_parsing) {
+	const char *data =
+	    "[Section]\n"
+	    "key = value\n"
+	    "key2 = value2\n";
+
+	int rc = ini_parse_string(data, &ini_conf, &parse);
+
+	PCUT_ASSERT_INT_EQUALS(rc, EOK);
+
+	ini_section_t *section = ini_get_section(&ini_conf, "Section");
+	PCUT_ASSERT_TRUE(section != NULL);
+
+	ini_item_iterator_t it = ini_section_get_iterator(section, "key");
+	PCUT_ASSERT_TRUE(ini_item_iterator_valid(&it));
+
+	PCUT_ASSERT_STR_EQUALS(ini_item_iterator_value(&it), "value");
+}
+
+PCUT_TEST(default_section) {
+	const char *data =
+	    "key = value\n"
+	    "key2 = value2\n";
+
+	int rc = ini_parse_string(data, &ini_conf, &parse);
+
+	PCUT_ASSERT_INT_EQUALS(rc, EOK);
+
+	ini_section_t *section = ini_get_section(&ini_conf, NULL);
+	PCUT_ASSERT_TRUE(section != NULL);
+
+	ini_item_iterator_t it = ini_section_get_iterator(section, "key");
+	PCUT_ASSERT_TRUE(ini_item_iterator_valid(&it));
+
+	PCUT_ASSERT_STR_EQUALS(ini_item_iterator_value(&it), "value");
+}
+
+PCUT_TEST(multikey) {
+	const char *data =
+	    "key = value\n"
+	    "key = value2\n";
+
+	int rc = ini_parse_string(data, &ini_conf, &parse);
+
+	PCUT_ASSERT_INT_EQUALS(rc, EOK);
+
+	ini_section_t *section = ini_get_section(&ini_conf, NULL);
+	PCUT_ASSERT_TRUE(section != NULL);
+
+	ini_item_iterator_t it = ini_section_get_iterator(section, "key");
+	PCUT_ASSERT_TRUE(ini_item_iterator_valid(&it));
+	const char *first = ini_item_iterator_value(&it);
+
+	ini_item_iterator_inc(&it);
+	PCUT_ASSERT_TRUE(ini_item_iterator_valid(&it));
+	const char *second = ini_item_iterator_value(&it);
+
+	PCUT_ASSERT_TRUE(
+	    (str_cmp(first, "value") == 0 && str_cmp(second, "value2") == 0) ||
+	    (str_cmp(first, "value2") == 0 && str_cmp(second, "value") == 0));
+}
+
+PCUT_TEST(dup_section) {
+	const char *data =
+	    "[Section]\n"
+	    "key = value\n"
+	    "key = value2\n"
+	    "[Section]\n"
+	    "key = val\n";
+
+	int rc = ini_parse_string(data, &ini_conf, &parse);
+
+	PCUT_ASSERT_INT_EQUALS(rc, EINVAL);
+	PCUT_ASSERT_TRUE(parse.has_error);
+
+	link_t *li = list_first(&parse.errors);
+	text_parse_error_t *error = list_get_instance(li, text_parse_error_t, link);
+
+	PCUT_ASSERT_INT_EQUALS(error->parse_errno, INI_EDUP_SECTION);
+}
+
+PCUT_TEST(empty_section) {
+	const char *data =
+	    "[Section1]\n"
+	    "[Section2]\n"
+	    "key = value\n"
+	    "key2 = value2\n";
+
+	int rc = ini_parse_string(data, &ini_conf, &parse);
+
+	PCUT_ASSERT_INT_EQUALS(rc, EOK);
+
+	ini_section_t *section = ini_get_section(&ini_conf, "Section1");
+	PCUT_ASSERT_TRUE(section != NULL);
+
+	ini_item_iterator_t it = ini_section_get_iterator(section, "key");
+	PCUT_ASSERT_TRUE(!ini_item_iterator_valid(&it));
+}
+
+PCUT_EXPORT(ini);
Index: uspace/lib/conf/test/main.c
===================================================================
--- uspace/lib/conf/test/main.c	(revision a58727cded39abc2fd1b1d8f3fdccdca5a99f6c6)
+++ uspace/lib/conf/test/main.c	(revision a58727cded39abc2fd1b1d8f3fdccdca5a99f6c6)
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015 Michal Koutny
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT
+
+PCUT_IMPORT(ini);
+
+PCUT_MAIN()
