shithub: libini

Download patch

ref: 6c63e71880f8bcdf597548e8bbaba165c4fe7d38
parent: 61a400cc2f9b50b4a39537eb9d0981df77347929
author: sirjofri <sirjofri@sirjofri.de>
date: Sun Feb 26 17:34:16 EST 2023

adds freeini command

--- a/README
+++ b/README
@@ -11,6 +11,7 @@
           int forcelower)
 
           int    iniconfig(char *file, int forcelower)
+          int    freeini(int config)
 
           char*  getinivalue(int config, char *section, char *key)
           char** getiniarray(int config, char *section, char *key, int
@@ -32,7 +33,9 @@
           Iniconfig parses the given file into a convenient config
           database.  It returns the id of the parsed config file
           that's needed for future lookups.  It is possible to parse
-          multiple INI files within one program this way.
+          multiple INI files within one program this way.  Freeini
+          frees the whole loaded INI configuration specified by the
+          config parameter.
 
           The functions getinivalue and getiniarray can be used to
           fetch the parsed config values.  Both functions expect the
@@ -57,4 +60,6 @@
 
      BUGS
           Sure.
+
+          Freeini potentially leaks memory.
 
--- a/ini.c
+++ b/ini.c
@@ -22,6 +22,7 @@
 void** Aarr(Array *a);
 int Anum(Array *a);
 void Ashrink(Array *a);
+int Aset(Array *a, int index, void *item);
 
 typedef struct Property Property;
 struct Property {
@@ -65,8 +66,10 @@
 		return nil;
 	
 	IniConfig *c = (IniConfig*)Aget(&configs, config);
-	if (!c)
-		sysfatal("invalid config: %r");
+	if (!c) {
+		werrstr("invalid config: %r");
+		return nil;
+	}
 	
 	for (int i = 0; i < Anum(&c->properties); i++) {
 		Property *p = (Property*)Aget(&c->properties, i);
@@ -292,6 +295,8 @@
 		Ainit(&configs);
 	
 	IniConfig *ini = malloc(sizeof(IniConfig));
+	if (!ini)
+		sysfatal("error: %r");
 	Ainit(&ini->sections);
 	Ainit(&ini->properties);
 	Ainit(&ini->arrayproperties);
@@ -306,6 +311,44 @@
 }
 
 int
+freeini(int config)
+{
+	IniConfig *c = (IniConfig*)Aget(&configs, config);
+	if (!c)
+		sysfatal("invalid config: %r");
+	
+	for (int i = 0; i < Anum(&c->properties); i++) {
+		Property *p = (Property*)Aget(&c->properties, i);
+		free(p->key);
+		p->key = nil;
+		free(p->value);
+		p->value = nil;
+		free(p);
+		Aset(&c->properties, i, nil);
+	}
+	for (int i = 0; i < Anum(&c->arrayproperties); i++) {
+		ArrayProperty *p = (ArrayProperty*)Aget(&c->arrayproperties, i);
+		for (int j = 0; j < Anum(&p->values); j++) {
+			free(Aget(&p->values, j));
+			Aset(&p->values, j, nil);
+		}
+		free(p->key);
+		p->key = nil;
+		free(p);
+		Aset(&c->arrayproperties, i, nil);
+	}
+	for (int i = 0; i < Anum(&c->sections); i++) {
+		free(Aget(&c->sections, i));
+		Aset(&c->sections, i, nil);
+	}
+	free(c);
+	if (Aset(&configs, config, nil))
+		return 1;
+	werrstr("cannot free ini config: %r");
+	return 0;
+}
+
+int
 Afindstrid(Array *a, char *item)
 {
 	for (int i = 0; i < a->num; i++) {
@@ -397,6 +440,17 @@
 	if (!a->items)
 		sysfatal("error: %r");
 	a->maxnum = a->num;
+}
+
+int
+Aset(Array *a, int index, void *item)
+{
+	if (index >= a->num) {
+		werrstr("bad index");
+		return 0;
+	}
+	a->items[index] = item;
+	return 1;
 }
 
 void
--- a/ini.h
+++ b/ini.h
@@ -7,3 +7,4 @@
 
 char* getinivalue(int config, char *section, char *key);
 char** getiniarray(int config, char *section, char *key, int *num);
+int freeini(int config);
--- a/ini.man
+++ b/ini.man
@@ -11,6 +11,9 @@
 .B
 .ta \w'char** 'u
 int	iniconfig(char *file, int forcelower)
+.br
+.B
+int	freeini(int config)
 .PP
 .B
 .ta \w'char** 'u
@@ -38,6 +41,10 @@
 .I id
 of the parsed config file that's needed for future lookups.
 It is possible to parse multiple INI files within one program this way.
+.I Freeini
+frees the whole loaded INI configuration specified by the
+.I config
+parameter.
 .PP
 The functions
 .I getinivalue
@@ -69,3 +76,6 @@
 .IR errstr .
 .SH BUGS
 Sure.
+.PP
+.I Freeini
+potentially leaks memory.
--- a/mkfile
+++ b/mkfile
@@ -12,8 +12,8 @@
 test.$O: test.c ini.h
 	$CC $CFLAGS test.c
 
-test:QV: $O.out
-	cat <<EOF >test.ini
+test.ini:Q:
+	cat <<EOF >$target
 	nocat=novalue
 	+nocatarr=valA
 	+nocatarr=valB
@@ -25,8 +25,7 @@
 	; Comment
 	
 	[Category B]
-	keyC=valueC
-	keyD=valueD
+	keyB=valueB
 	
 	[Category C]
 	keyx=nope
@@ -33,7 +32,12 @@
 	+keyF=abc
 	+keyF=def
 	EOF
-	$O.out test.ini
+
+test:QV: $O.out test.ini
+	$O.out test.ini >/dev/null &
+	p=$apid
+	sleep 0.5 && leak -s $p | acid $p
+	wait $p
 	rm test.ini
 
 README: ini.man
--- a/test.c
+++ b/test.c
@@ -2,10 +2,13 @@
 #include <libc.h>
 #include "ini.h"
 
+int verbose = 0;
+
 void
 parseline(char *section, char *key, char *value, int)
 {
-	print("section: '%s', key: '%s', value: '%s'\n", section, key, value);
+	if (verbose)
+		print("section: '%s', key: '%s', value: '%s'\n", section, key, value);
 }
 
 void
@@ -15,43 +18,58 @@
 	char *status = nil;
 	char *err = "error";
 	
-	print("Testing forcelower=0\n\n");
-	
 	if (!parseini(file, parseline, 0, 0)) {
 		fprint(2, "error: %r\n");
 		status = err;
 	}
 	
-	print("\nTesting forcelower=1\n\n");
-	
 	if (!parseini(file, parseline, 1, 0)) {
 		fprint(2, "error: %r\n");
 		status = err;
 	}
 	
-	print("\nTesting iniconfig\n\n");
 	int conf = iniconfig(file, 0);
+	if (conf < 0)
+		sysfatal("error: %r");
 	
 	char* v = getinivalue(conf, "Category A", "keyB");
-	print("Category A, keyB: %s\n", v);
+	if (strcmp(v, "valueB") != 0)
+		fprint(2, "fail: expected 'valueB', got '%s'\n", v);
 	
 	int num;
 	char** values = getiniarray(conf, "Category C", "keyF", &num);
+	if (num != 2)
+		fprint(2, "fail: expected 2 entries, got %d\n", num);
 	if (values) {
 		for (int i = 0; i < num; i++) {
-			print("array: %s\n", values[i]);
+			if (i == 0 && strcmp(values[i], "abc") != 0)
+				fprint(2, "fail: expected 'abc', got '%s'\n", values[i]);
+			if (i == 1 && strcmp(values[i], "def") != 0)
+				fprint(2, "fail: expected 'def', got '%s'\n", values[i]);
 		}
 	}
 	
 	v = getinivalue(conf, nil, "nocat");
-	print("no category, nocat: %s\n", v);
+	if (strcmp(v, "novalue") != 0)
+		fprint(2, "fail: expected 'novalue', got '%s'\n", v);
 	
 	values = getiniarray(conf, nil, "nocatarr", &num);
+	if (num != 2)
+		fprint(2, "fail: expected 2 entries, got %d\n", num);
 	if (values) {
 		for (int i = 0; i < num; i++) {
-			print("nocatarr: %s\n", values[i]);
+			if (i == 0 && strcmp(values[i], "valA") != 0)
+				fprint(2, "fail: expected 'valA', got '%s'\n", values[i]);
+			if (i == 1 && strcmp(values[i], "valB") != 0)
+				fprint(2, "fail: expected 'valB', got '%s'\n", values[i]);
 		}
 	}
+	
+	int r = freeini(conf);
+	if (!r)
+		fprint(2, "error freeing: %r\n");
+	
+	sleep(1000);
 	
 	exits(status);
 }