Browse Source

Initial import.

Georgi Chorbadzhiyski 7 years ago
commit
61560683cf
38 changed files with 5318 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 6 0
      .gitmodules
  3. 56 0
      Makefile
  4. 8 0
      TODO
  5. 625 0
      config.c
  6. 84 0
      config.h
  7. 389 0
      data.c
  8. 230 0
      data.h
  9. 428 0
      inidict.c
  10. 174 0
      inidict.h
  11. 712 0
      iniparser.c
  12. 309 0
      iniparser.h
  13. 420 0
      input.c
  14. 6 0
      input.h
  15. 1 0
      libfuncs
  16. 1 0
      libtsfuncs
  17. 167 0
      mptsd.c
  18. 12 0
      mptsd.conf
  19. 63 0
      mptsd_channels.conf
  20. 70 0
      mptsd_epg.conf
  21. 50 0
      mptsd_nit.conf
  22. 11 0
      mptsd_valgrind
  23. 294 0
      network.c
  24. 10 0
      network.h
  25. 13 0
      output.h
  26. 215 0
      output_mix.c
  27. 249 0
      output_psi.c
  28. 211 0
      output_write.c
  29. 85 0
      pidref.c
  30. 29 0
      pidref.h
  31. 149 0
      rc.mptsd
  32. 25 0
      rc.mptsd.conf
  33. 43 0
      sleep.c
  34. 6 0
      sleep.h
  35. 27 0
      web_pages.c
  36. 7 0
      web_pages.h
  37. 122 0
      web_server.c
  38. 9 0
      web_server.h

+ 2 - 0
.gitignore View File

@@ -0,0 +1,2 @@
1
+*.o
2
+mptsd

+ 6 - 0
.gitmodules View File

@@ -0,0 +1,6 @@
1
+[submodule "libfuncs"]
2
+	path = libfuncs
3
+	url = git://github.com/gfto/libfuncs.git
4
+[submodule "libtsfuncs"]
5
+	path = libtsfuncs
6
+	url = git://github.com/gfto/libtsfuncs.git

+ 56 - 0
Makefile View File

@@ -0,0 +1,56 @@
1
+CC = $(CROSS)$(TARGET)gcc
2
+STRIP = $(CROSS)$(TARGET)strip
3
+BUILD_ID = $(shell date +%F_%R)
4
+GIT_VER = $(shell git describe --tags --dirty --always)
5
+CFLAGS = -ggdb -Wall -Wextra -Wshadow -Wformat-security -Wno-strict-aliasing -O2 -D_GNU_SOURCE -DBUILD_ID=\"$(BUILD_ID)\" -DGIT_VER=\"$(GIT_VER)\"
6
+RM = /bin/rm -f
7
+Q = @
8
+
9
+LIBS = -lpthread -lm -lrt
10
+
11
+FUNCS_DIR = libfuncs
12
+FUNCS_LIB = $(FUNCS_DIR)/libfuncs.a
13
+
14
+TSFUNCS_DIR = libtsfuncs
15
+TSFUNCS_LIB = $(TSFUNCS_DIR)/libtsfuncs.a
16
+
17
+mptsd_OBJS =  $(FUNCS_LIB) $(TSFUNCS_LIB) \
18
+	iniparser.o inidict.o pidref.o data.o config.o \
19
+	sleep.o network.o \
20
+	input.o \
21
+	output_psi.o output_mix.o output_write.o \
22
+	web_pages.o web_server.o \
23
+	mptsd.o
24
+
25
+PROGS = mptsd
26
+CLEAN_OBJS = $(PROGS) $(mptsd_OBJS) *~
27
+
28
+all: $(PROGS)
29
+
30
+$(FUNCS_LIB):
31
+	$(Q)echo "  MAKE	$(FUNCS_LIB)"
32
+	$(Q)$(MAKE) -s -C $(FUNCS_DIR)
33
+
34
+$(TSFUNCS_LIB):
35
+	$(Q)echo "  MAKE	$(TSFUNCS_LIB)"
36
+	$(Q)$(MAKE) -s -C $(TSFUNCS_DIR)
37
+
38
+mptsd: $(mptsd_OBJS)
39
+	$(Q)echo "  LINK	mptsd"
40
+	$(Q)$(CC) $(CFLAGS) $(mptsd_OBJS) $(LIBS) -o mptsd
41
+
42
+%.o: %.c data.h
43
+	$(Q)echo "  CC	mptsd		$<"
44
+	$(Q)$(CC) $(CFLAGS)  -c $<
45
+
46
+strip:
47
+	$(Q)echo "  STRIP	$(PROGS)"
48
+	$(Q)$(STRIP) $(PROGS)
49
+
50
+clean:
51
+	$(Q)echo "  RM	$(CLEAN_OBJS)"
52
+	$(Q)$(RM) $(CLEAN_OBJS)
53
+
54
+distclean: clean
55
+	$(Q)$(MAKE) -s -C $(TSFUNCS_DIR) clean
56
+	$(Q)$(MAKE) -s -C $(FUNCS_DIR) clean

+ 8 - 0
TODO View File

@@ -0,0 +1,8 @@
1
+mptsd todo
2
+- Add web access to stats
3
+- Support rewritting of incomming EIT tables
4
+- Add live reconfiguration
5
+  - Adding removing channels
6
+  - Changing of output address and bitrate
7
+  - Reloading of nit/eit
8
+- Add support for schedule eit

+ 625 - 0
config.c View File

@@ -0,0 +1,625 @@
1
+#include <stdio.h>
2
+#include <stdlib.h>
3
+#include <unistd.h>
4
+#include <string.h>
5
+#include <errno.h>
6
+#include <arpa/inet.h>
7
+#include <netinet/in.h>
8
+#include <sys/types.h>
9
+#include <sys/socket.h>
10
+#include <sys/types.h>
11
+#include <sys/stat.h>
12
+#include <sys/time.h>
13
+#include <sys/stat.h>
14
+#include <signal.h>
15
+#include <fcntl.h>
16
+#include <regex.h>
17
+#include <math.h>
18
+#include <netdb.h> // for uint32_t
19
+
20
+#include "libfuncs/log.h"
21
+#include "libfuncs/list.h"
22
+#include "libfuncs/server.h"
23
+
24
+#include "libtsfuncs/tsfuncs.h"
25
+
26
+#include "data.h"
27
+#include "sleep.h"
28
+#include "config.h"
29
+#include "inidict.h"
30
+#include "iniparser.h"
31
+
32
+extern char *server_sig;
33
+extern char *server_ver;
34
+extern char *copyright;
35
+
36
+int is_valid_url(char *url) {
37
+	regex_t re;
38
+	regmatch_t res[5];
39
+	int ret;
40
+	regcomp(&re, "^([a-z]+)://([^:/?]+):?([0-9]*)/?(.*)", REG_EXTENDED);
41
+	ret = regexec(&re,url,5,res,0);
42
+	regfree(&re);
43
+	return ret == 0;
44
+}
45
+
46
+CONFIG *config_alloc() {
47
+	CONFIG *conf = calloc(1, sizeof(CONFIG));
48
+	conf->inputs = list_new("input");
49
+	conf->nit    = list_new("nit");
50
+	conf->output = output_new();
51
+	return conf;
52
+}
53
+
54
+void config_free(CONFIG **pconf) {
55
+	CONFIG *conf = *pconf;
56
+	if (conf) {
57
+		conf->output->dienow = 1;
58
+
59
+		list_free(&conf->nit, NULL, nit_free);
60
+		list_free(&conf->inputs, NULL, input_free);
61
+		list_free(&conf->channels, NULL, channel_free);
62
+		output_free(&conf->output);
63
+
64
+		FREE(conf->ident);
65
+		FREE(conf->pidfile);
66
+		FREE(conf->server_addr);
67
+		FREE(conf->loghost);
68
+		FREE(conf->logident);
69
+		FREE(conf->global_conf);
70
+		FREE(conf->channels_conf);
71
+		FREE(conf->nit_conf);
72
+		FREE(conf->epg_conf);
73
+		FREE(conf->network_name);
74
+		FREE(conf->provider_name);
75
+		FREE(*pconf);
76
+	}
77
+}
78
+
79
+
80
+int config_load_channels(CONFIG *conf) {
81
+	int num_channels = 0, i, j;
82
+
83
+	LOGf("CONFIG: Loading channels config %s\n", conf->channels_conf);
84
+	dictionary *ini = iniparser_load(conf->channels_conf);
85
+	if (!ini) {
86
+		LOGf("CONFIG: Error loading channels config (%s)\n", conf->channels_conf);
87
+		return 0;
88
+	}
89
+
90
+	//iniparser_dump(ini, stdout);
91
+	LIST *channels = list_new("channels");
92
+
93
+	// Parse channels file
94
+	conf->provider_name = ini_get_string_copy(ini, NULL, "Global:provider_name");
95
+	conf->transport_stream_id = ini_get_int(ini, 0, "Global:transport_stream_id");
96
+	for (i=1;i<32;i++) {
97
+		CHANNEL *channel = NULL;
98
+		int service_id = ini_get_int(ini, 0, "Channel%d:service_id", i);
99
+		if (!service_id)
100
+			continue;
101
+
102
+		int is_radio = ini_get_bool(ini, 0, "Channel%d:radio", i);
103
+
104
+		char *id = ini_get_string(ini, NULL, "Channel%d:id", i);
105
+		if (!id) {
106
+			LOGf("CONFIG: Channel%d have no defined id\n", i);
107
+			continue;
108
+		}
109
+
110
+		char *name = ini_get_string(ini, NULL, "Channel%d:name", i);
111
+		if (!name) {
112
+			LOGf("CONFIG: Channel%d have no defined name\n", i);
113
+			continue;
114
+		}
115
+
116
+		for (j=1;j<8;j++) {
117
+			char *source = ini_get_string(ini, NULL, "Channel%d:source%d", i, j);
118
+			if (j == 1 && !source) {
119
+				source = ini_get_string(ini, NULL, "Channel%d:source", i);
120
+			}
121
+			if (source) {
122
+				if (!is_valid_url(source)) {
123
+					LOGf("CONFIG: Invalid url: %s\n", source);
124
+					continue;
125
+				}
126
+				// Init channel
127
+				if (channel == NULL) {
128
+					channel = channel_new(service_id, is_radio, id, name, source);
129
+				} else {
130
+					chansrc_add(channel, source);
131
+				}
132
+			}
133
+		}
134
+
135
+		char *worktime = ini_get_string(ini, NULL, "Channel%d:worktime", i);
136
+		int sh=0,sm=0,eh=0,em=0;
137
+		if (worktime && sscanf(worktime, "%02d:%02d-%02d:%02d", &sh, &sm, &eh, &em) == 4) {
138
+			channel->worktime_start = sh * 3600 + sm * 60;
139
+			channel->worktime_end   = eh * 3600 + em * 60;
140
+		}
141
+
142
+		// Channel is initialized, add it to channels list
143
+		if (channel) {
144
+			num_channels++;
145
+			list_add(channels, channel);
146
+		}
147
+	}
148
+//	iniparser_dump(ini, stderr);
149
+	iniparser_freedict(&ini);
150
+
151
+	// Check for channel changes
152
+	struct timeval tv;
153
+	gettimeofday(&tv, NULL);
154
+	unsigned int randstate = tv.tv_usec;
155
+	int cookie = rand_r(&randstate);
156
+
157
+	/* Save current channels config */
158
+	LIST *old_channels = conf->channels;
159
+
160
+	/* Switch new channels config */
161
+	conf->channels = channels;
162
+
163
+	/* Rewrite restreamer channels */
164
+	LNODE *lc, *lr, *lctmp, *lrtmp;
165
+	CHANNEL *chan;
166
+	list_lock(conf->inputs);	// Unlocked after second list_for_each(restreamer)
167
+	list_lock(conf->channels);
168
+	list_for_each(conf->channels, lc, lctmp) {
169
+		chan = lc->data;
170
+		list_for_each(conf->inputs, lr, lrtmp) {
171
+			if (strcmp(chan->name, ((INPUT *)lr->data)->name)==0) {
172
+				INPUT *restr = lr->data;
173
+				/* Mark the restreamer as valid */
174
+				restr->cookie = cookie;
175
+				/* Check if current source exists in new channel configuration */
176
+				int src_found = -1;
177
+				char *old_source = restr->channel->source;
178
+				for (i=0; i<chan->num_src; i++) {
179
+					if (strcmp(old_source, chan->sources[i]) == 0) {
180
+						src_found = i;
181
+					}
182
+				}
183
+				if (src_found > -1) {
184
+					/* New configuration contains existing source, just update the reference */
185
+					chansrc_set(chan, src_found);
186
+					restr->channel = chan;
187
+				} else {
188
+					/* New configuration *DO NOT* contain existing source. Force reconnect */
189
+					LOGf("INPUT: Source changed | Channel: %s srv_fd: %d Old:%s New:%s\n", chan->name, restr->sock, restr->channel->source, chan->source);
190
+					/* The order is important! */
191
+					chansrc_set(chan, chan->num_src-1); /* Set source to last one. On reconnect next source will be used. */
192
+					restr->channel = chan;
193
+					restr->reconnect = 1;
194
+				}
195
+				break;
196
+			}
197
+		}
198
+	}
199
+	list_unlock(conf->channels);
200
+
201
+	/* Kill restreamers that serve channels that no longer exist */
202
+	list_for_each(conf->inputs, lr, lrtmp) {
203
+		INPUT *r = lr->data;
204
+		/* This restreamer should no longer serve clients */
205
+		if (r->cookie != cookie) {
206
+			proxy_log(r, "Remove");
207
+			/* Replace channel reference with real object and instruct free_restreamer to free it */
208
+			r->channel = channel_new(r->channel->service_id, r->channel->radio, r->channel->id, r->channel->name, r->channel->source);
209
+			r->freechannel = 1;
210
+			r->dienow = 1;
211
+		}
212
+	}
213
+	list_unlock(conf->inputs);
214
+
215
+	/* Free old_channels */
216
+	list_free(&old_channels, NULL, channel_free);
217
+
218
+	LOGf("CONFIG: %d channels loaded\n", num_channels);
219
+	return num_channels;
220
+}
221
+
222
+int config_load_global(CONFIG *conf) {
223
+	LOGf("CONFIG: Loading global config %s\n", conf->global_conf);
224
+	dictionary *ini = iniparser_load(conf->global_conf);
225
+	if (!ini) {
226
+		LOGf("CONFIG: Error loading global config (%s)\n", conf->global_conf);
227
+		return 0;
228
+	}
229
+	conf->network_id		= ini_get_int(ini, 0,    "Global:network_id");
230
+	conf->timeouts.pat		= ini_get_int(ini, 100,  "Timeouts:pat");
231
+	conf->timeouts.pmt		= ini_get_int(ini, 200,  "Timeouts:pmt");
232
+	conf->timeouts.sdt		= ini_get_int(ini, 500,  "Timeouts:sdt");
233
+	conf->timeouts.nit		= ini_get_int(ini, 2000, "Timeouts:nit");
234
+	conf->timeouts.eit		= ini_get_int(ini, 1000, "Timeouts:eit");
235
+	conf->timeouts.tdt		= ini_get_int(ini, 7500, "Timeouts:tdt");
236
+	conf->timeouts.tot		= ini_get_int(ini, 1500, "Timeouts:tot");
237
+	conf->timeouts.stats	= ini_get_int(ini, 0,    "Timeouts:stats");
238
+	//iniparser_dump(ini, stderr);
239
+	iniparser_freedict(&ini);
240
+	return 1;
241
+}
242
+
243
+int config_load_nit(CONFIG *conf) {
244
+	LOGf("CONFIG: Loading nit config %s\n", conf->nit_conf);
245
+	dictionary *ini = iniparser_load(conf->nit_conf);
246
+	if (!ini) {
247
+		LOGf("CONFIG: Error loading nit config (%s)\n", conf->nit_conf);
248
+		return 0;
249
+	}
250
+	conf->network_name = ini_get_string_copy(ini, NULL, "Global:network_name");
251
+	int i;
252
+	for (i=1;i<32;i++) {
253
+		uint16_t ts_id = ini_get_int   (ini, 0, "Transponder%d:transport_stream_id", i);
254
+		char *freq     = ini_get_string(ini, NULL, "Transponder%d:frequency", i);
255
+		char *mod      = ini_get_string(ini, NULL, "Transponder%d:modulation", i);
256
+		char *sr       = ini_get_string(ini, NULL, "Transponder%d:symbol_rate", i);
257
+		if (ts_id && freq && mod && sr) {
258
+			if (strlen(freq) == 9 && strlen(sr) == 8) {
259
+				list_add(conf->nit, nit_new(ts_id, freq, mod, sr));
260
+			}
261
+		}
262
+	}
263
+	//iniparser_dump(ini, stderr);
264
+	iniparser_freedict(&ini);
265
+	return 1;
266
+}
267
+
268
+static void config_reset_channels_epg(CONFIG *conf) {
269
+	LNODE *lc, *lctmp;
270
+	list_lock(conf->channels);
271
+	list_for_each(conf->channels, lc, lctmp) {
272
+		CHANNEL *c = lc->data;
273
+		channel_free_epg(c);
274
+	}
275
+	list_unlock(conf->channels);
276
+	conf->epg_conf_mtime = 0;
277
+}
278
+
279
+static EPG_ENTRY *config_load_epg_entry(dictionary *ini, char *entry, char *channel) {
280
+	EPG_ENTRY *e = NULL;
281
+	time_t start = ini_get_int(ini, 0, "%s-%s:start", channel, entry);
282
+	int duration = ini_get_int(ini, 0, "%s-%s:duration", channel, entry);
283
+	char *event  = ini_get_string(ini, NULL, "%s-%s:event", channel, entry);
284
+	char *sdesc  = ini_get_string(ini, NULL, "%s-%s:sdescr", channel, entry);
285
+	char *ldesc  = ini_get_string(ini, NULL, "%s-%s:descr", channel, entry);
286
+	char *enc    = ini_get_string(ini, NULL, "%s-%s:encoding", channel, entry);
287
+	if (start && duration && event) {
288
+		e = epg_new(start, duration, enc, event, sdesc, ldesc);
289
+	}
290
+	return e;
291
+}
292
+
293
+static void config_channel_init_epg(CONFIG *conf, CHANNEL *c, EPG_ENTRY *now, EPG_ENTRY *next) {
294
+	int updated = 0;
295
+
296
+	if (epg_changed(now, c->epg_now)) {
297
+		LOGf("EPG  : %s | Now data changed\n", c->id);
298
+		updated++;
299
+	}
300
+
301
+	if (epg_changed(next, c->epg_next)) {
302
+		LOGf("EPG  : %s | Next data changed\n", c->id);
303
+		updated++;
304
+	}
305
+
306
+	if (!updated)
307
+		return;
308
+
309
+	// LOGf("EPG  : %s | Version %d\n", c->epg_version);
310
+	c->epg_version++;
311
+
312
+	struct ts_eit *eit_now = ts_eit_alloc_init_pf(c->service_id, conf->transport_stream_id, conf->network_id, 0, 1);
313
+	eit_now->section_header->version_number = c->epg_version;
314
+	if (now) {
315
+		ts_eit_add_short_event_descriptor(eit_now, now->event_id, 1, now->start, now->duration, now->event, now->short_desc);
316
+		ts_eit_add_extended_event_descriptor(eit_now, now->event_id, 1, now->start, now->duration, now->long_desc);
317
+	}
318
+	ts_eit_regenerate_packets(eit_now);
319
+
320
+	struct ts_eit *eit_next = ts_eit_alloc_init_pf(c->service_id, conf->transport_stream_id, conf->network_id, 1, 1);
321
+	eit_next->section_header->version_number = c->epg_version;
322
+	if (next) {
323
+		ts_eit_add_short_event_descriptor(eit_next, next->event_id, 1, next->start, next->duration, next->event, next->short_desc);
324
+		ts_eit_add_extended_event_descriptor(eit_next, next->event_id, 1, next->start, next->duration, next->long_desc);
325
+	}
326
+	ts_eit_regenerate_packets(eit_next);
327
+
328
+	channel_free_epg(c);
329
+
330
+	c->epg_now  = now;
331
+	c->epg_next = next;
332
+
333
+	if (now || next) {
334
+		c->eit_now  = eit_now;
335
+		c->eit_next = eit_next;
336
+		//LOGf(" ***** NOW ******\n");
337
+		//ts_eit_dump(eit_now);
338
+		//LOGf(" ***** NEXT *****\n");
339
+		//ts_eit_dump(eit_next);
340
+	} else {
341
+		ts_eit_free(&eit_now);
342
+		ts_eit_free(&eit_next);
343
+	}
344
+}
345
+
346
+
347
+int config_load_epg(CONFIG *conf) {
348
+	struct stat st;
349
+	if (stat(conf->epg_conf, &st) == 0) {
350
+		if (st.st_mtime > conf->epg_conf_mtime) {
351
+			// Set config last change time
352
+			conf->epg_conf_mtime = st.st_mtime;
353
+		} else {
354
+			// LOGf("CONFIG: EPG config not changed since last check.\n");
355
+			return 0; // The config has not changed!
356
+		}
357
+	} else {
358
+		// Config file not found!
359
+		LOGf("CONFIG: EPG config file is not found (%s)\n", conf->epg_conf);
360
+		config_reset_channels_epg(conf);
361
+		return 0;
362
+	}
363
+
364
+	// LOGf("CONFIG: Loading EPG config %s\n", conf->epg_conf);
365
+	dictionary *ini = iniparser_load(conf->epg_conf);
366
+	if (!ini) {
367
+		LOGf("CONFIG: Error parsing EPG config (%s)\n", conf->epg_conf);
368
+		config_reset_channels_epg(conf);
369
+		return 0;
370
+	}
371
+
372
+	LNODE *lc, *lctmp;
373
+	list_lock(conf->channels);
374
+	list_for_each(conf->channels, lc, lctmp) {
375
+		CHANNEL *c = lc->data;
376
+		EPG_ENTRY *enow  = config_load_epg_entry(ini, "now", c->id);
377
+		EPG_ENTRY *enext = config_load_epg_entry(ini, "next", c->id);
378
+		config_channel_init_epg(conf, c, enow, enext);
379
+	}
380
+	list_unlock(conf->channels);
381
+	//iniparser_dump(ini, stderr);
382
+	iniparser_freedict(&ini);
383
+
384
+	return 1;
385
+}
386
+
387
+extern char *program_id;
388
+
389
+static void show_usage() {
390
+	printf("%s\n", program_id);
391
+	puts(copyright);
392
+	puts("");
393
+	puts("Identification:");
394
+	puts("\t-i ident\tServer ident.               (default: ux/mptsd)");
395
+	puts("");
396
+	puts("Server settings:");
397
+	puts("\t-b addr\t\tLocal IP address to bind.   (default: 0.0.0.0)");
398
+	puts("\t-p port\t\tPort to listen.             (default: 0)");
399
+	puts("\t-d pidfile\tDaemonize with pidfile");
400
+	puts("\t-l host\t\tSyslog host                 (default: disabled)");
401
+	puts("\t-L port\t\tSyslog port                 (default: 514)");
402
+	puts("");
403
+	puts("Configuration files:");
404
+	puts("\t-g file\t\tGlobal configuration file   (default: mptsd.conf)");
405
+	puts("\t-c file\t\tChannels configuration file (default: mptsd_channels.conf)");
406
+	puts("\t-e file\t\tEPG configuration file      (default: mptsd_epg.conf)");
407
+	puts("\t-n file\t\tNIT configuration file      (default: mptsd_nit.conf)");
408
+	puts("");
409
+	puts("Output settings:");
410
+	puts("\t-O ip\t\tOutput udp address");
411
+	puts("\t-P ip\t\tOutput udp port             (default: 5000)");
412
+	puts("\t-o ip\t\tOutput interface address    (default: 0.0.0.0)");
413
+	puts("\t-t ttl\t\tSet multicast ttl           (default: 1)");
414
+	puts("");
415
+	puts("\t-B Mbps\t\tOutput bitrate in Mbps      (default: 38.00)");
416
+	puts("\t-m mode\t\tPCR mode (modes list bellow)");
417
+	puts("\t\t\t- Mode 0: do not touch PCRs (default)");
418
+	puts("\t\t\t- Mode 1: move PCRs to their calculated place");
419
+	puts("\t\t\t- Mode 2: rewrite PCRs using output bitrate");
420
+	puts("\t\t\t- Mode 3: move PCRs and rewrite them");
421
+	puts("");
422
+	puts("Other settings:");
423
+	puts("\t-q\t\tQuiet");
424
+	puts("\t-D\t\tDebug");
425
+	puts("");
426
+	puts("\t-W\t\tWrite output file");
427
+	puts("\t-E\t\tWrite input file");
428
+	puts("");
429
+}
430
+
431
+void config_load(CONFIG *conf, int argc, char **argv) {
432
+	int j, ttl;
433
+
434
+	conf->multicast_ttl = 1;
435
+	conf->output->out_port = 5000;
436
+	conf->output_bitrate = 38;
437
+	conf->logport = 514;
438
+	conf->server_port = 0;
439
+	conf->server_socket = -1;
440
+
441
+	while ((j = getopt(argc, argv, "i:b:p:g:c:n:e:d:t:o:O:P:l:L:B:m:qDHhWE")) != -1) {
442
+		switch (j) {
443
+			case 'i':
444
+				conf->ident = strdup(optarg);
445
+				conf->logident = strdup(optarg);
446
+				char *c = conf->logident;
447
+				while (*c) {
448
+					if (*c=='/')
449
+						*c='-';
450
+					c++;
451
+				}
452
+				break;
453
+			case 'b':
454
+				conf->server_addr = strdup(optarg);
455
+				break;
456
+			case 'p':
457
+				conf->server_port = atoi(optarg);
458
+				break;
459
+			case 'd':
460
+				conf->pidfile = strdup(optarg);
461
+				break;
462
+			case 'g':
463
+				conf->global_conf = strdup(optarg);
464
+				break;
465
+			case 'c':
466
+				conf->channels_conf = strdup(optarg);
467
+				break;
468
+			case 'n':
469
+				conf->nit_conf = strdup(optarg);
470
+				break;
471
+			case 'e':
472
+				conf->epg_conf = strdup(optarg);
473
+				break;
474
+			case 'o':
475
+				if (inet_aton(optarg, &conf->output_intf) == 0) {
476
+					fprintf(stderr, "Invalid interface address: %s\n", optarg);
477
+					exit(1);
478
+				}
479
+				break;
480
+			case 'O':
481
+				if (inet_aton(optarg, &(conf->output->out_host)) == 0) {
482
+					fprintf(stderr, "Invalid output address: %s\n", optarg);
483
+					exit(1);
484
+				}
485
+				break;
486
+			case 'P':
487
+				conf->output->out_port = atoi(optarg);
488
+				if (!conf->output->out_port || conf->output->out_port < 1024) {
489
+					fprintf(stderr, "Invalid output port: %s\n", optarg);
490
+					exit(1);
491
+				}
492
+				break;
493
+			case 'm':
494
+				conf->pcr_mode = atoi(optarg);
495
+				if (conf->pcr_mode < 0 || conf->pcr_mode > 4)
496
+					conf->pcr_mode = 0;
497
+				break;
498
+			case 't':
499
+				ttl = atoi(optarg);
500
+				conf->multicast_ttl = (ttl && ttl < 127) ? ttl : 1;
501
+				break;
502
+			case 'l':
503
+				conf->loghost = strdup(optarg);
504
+				conf->syslog_active = 1;
505
+				break;
506
+			case 'L':
507
+				conf->logport = atoi(optarg);
508
+				break;
509
+			case 'B':
510
+				conf->output_bitrate = atof(optarg);
511
+				if (conf->output_bitrate < 2 || conf->output_bitrate > 75) {
512
+					fprintf(stderr, "Invalid output bitrate: %.2f (valid 2-75)\n", conf->output_bitrate);
513
+					exit(1);
514
+				}
515
+				break;
516
+			case 'W':
517
+				conf->write_output_file = 1;
518
+				output_open_file(conf->output);
519
+				break;
520
+			case 'E':
521
+				conf->write_input_file = 1;
522
+				break;
523
+			case 'D':
524
+				conf->debug = 1;
525
+				break;
526
+			case 'q':
527
+				conf->quiet = 1;
528
+				break;
529
+			case 'H':
530
+			case 'h':
531
+				show_usage(0);
532
+				exit(0);
533
+				break;
534
+		}
535
+	}
536
+	if (!conf->output->out_host.s_addr) {
537
+		fprintf(stderr, "ERROR: Output address is not set (use -O x.x.x.x)\n");
538
+		show_usage();
539
+		goto ERR;
540
+	}
541
+	// Set defaults
542
+	if (!conf->ident) {
543
+		conf->ident = strdup("ux/mptsd");
544
+		conf->logident = strdup("ux-mptsd");
545
+	}
546
+	if (!conf->global_conf) {
547
+		conf->global_conf = strdup("mptsd.conf");
548
+	}
549
+	if (!conf->channels_conf) {
550
+		conf->channels_conf = strdup("mptsd_channels.conf");
551
+	}
552
+	if (!conf->nit_conf) {
553
+		conf->nit_conf = strdup("mptsd_nit.conf");
554
+	}
555
+	if (!conf->epg_conf) {
556
+		conf->epg_conf = strdup("mptsd_epg.conf");
557
+	}
558
+
559
+	// Align bitrate to 1 packet (1316 bytes)
560
+	conf->output_bitrate        *= 1000000; // In bytes
561
+	conf->output_packets_per_sec = ceil(conf->output_bitrate / (1316 * 8));
562
+	conf->output_bitrate         = conf->output_packets_per_sec * (1316 * 8);
563
+	conf->output_tmout           = 1000000 / conf->output_packets_per_sec;
564
+
565
+	if (conf->server_port)
566
+		init_server_socket(conf->server_addr, conf->server_port, &conf->server, &conf->server_socket);
567
+
568
+	if (!conf->quiet) {
569
+		printf("Configuration:\n");
570
+		printf("\tServer ident      : %s\n", conf->ident);
571
+		printf("\tGlobal config     : %s\n", conf->global_conf);
572
+		printf("\tChannels config   : %s\n", conf->channels_conf);
573
+		printf("\tNIT config        : %s\n", conf->nit_conf);
574
+		printf("\tOutput addr       : udp://%s:%d\n", inet_ntoa(conf->output->out_host), conf->output->out_port);
575
+		if (conf->output_intf.s_addr)
576
+			printf("\tOutput iface addr : %s\n", inet_ntoa(conf->output_intf));
577
+		printf("\tMulticast ttl     : %d\n", conf->multicast_ttl);
578
+		if (conf->syslog_active) {
579
+			printf("\tSyslog host       : %s\n", conf->loghost);
580
+			printf("\tSyslog port       : %d\n", conf->logport);
581
+		} else {
582
+			printf("\tSyslog disabled.\n");
583
+		}
584
+		printf("\tOutput bitrate    : %.0f bps, %.2f Kbps, %.2f Mbps\n", conf->output_bitrate, conf->output_bitrate / 1000, conf->output_bitrate / 1000000);
585
+		printf("\tOutput pkt tmout  : %ld us\n", conf->output_tmout);
586
+		printf("\tPackets per second: %ld\n", conf->output_packets_per_sec);
587
+		printf("\tPCR mode          : %s\n",
588
+			conf->pcr_mode == 0 ? "Do not touch PCRs" :
589
+			conf->pcr_mode == 1 ? "Move PCRs to their calculated place" :
590
+			conf->pcr_mode == 2 ? "Rewrite PCRs using output bitrate" :
591
+			conf->pcr_mode == 3 ? "Move PCRs and rewrite them" : "???"
592
+		);
593
+		if (conf->write_output_file)
594
+			printf("\tWrite output file\n");
595
+		if (conf->write_input_file)
596
+			printf("\tWrite input file(s)\n");
597
+	}
598
+
599
+	pthread_t sleepthread;
600
+	if (pthread_create(&sleepthread, NULL, &calibrate_sleep, conf) == 0) {
601
+		pthread_join(sleepthread, NULL);
602
+	} else {
603
+		perror("calibrate_thread");
604
+		exit(1);
605
+	}
606
+	if (!conf->output_tmout)
607
+		exit(1);
608
+
609
+	output_buffer_alloc(conf->output, conf->output_bitrate);
610
+
611
+	if (!config_load_global(conf))
612
+		goto ERR;
613
+	if (!config_load_nit(conf))
614
+		goto ERR;
615
+	if (!config_load_channels(conf))
616
+		goto ERR;
617
+
618
+	config_load_epg(conf);
619
+
620
+	return;
621
+
622
+ERR:
623
+	config_free(&conf);
624
+	exit(1);
625
+}

+ 84 - 0
config.h View File

@@ -0,0 +1,84 @@
1
+#ifndef CONFIG_H
2
+#define CONFIG_H
3
+
4
+#include <pthread.h>
5
+#include <arpa/inet.h>
6
+#include <netinet/in.h>
7
+
8
+#include "libfuncs/list.h"
9
+
10
+#include "data.h"
11
+
12
+typedef struct {
13
+	char			*ident;
14
+	char			*pidfile;
15
+
16
+	int				syslog_active;
17
+	char			*logident;
18
+	char			*loghost;
19
+	int				logport;
20
+
21
+	struct sockaddr_in	server;
22
+	char				*server_addr;
23
+	int					server_port;
24
+	int					server_socket;
25
+	pthread_t			server_thread;
26
+
27
+	int				multicast_ttl;
28
+	struct in_addr	output_intf;
29
+
30
+	char			*global_conf;
31
+	char			*channels_conf;
32
+	char			*nit_conf;
33
+	char			*epg_conf;
34
+
35
+	int				debug;
36
+	int				quiet;
37
+
38
+	int				write_input_file;
39
+	int				write_output_file;
40
+	int				pcr_mode;			// 0 - do not touch PCRs
41
+										// 1 - move PCRs to their calculated place
42
+										// 2 - rewrite PCRs using output bitrate
43
+										// 3 - move PCRs and rewrite them
44
+
45
+	uint16_t		network_id;			// For NIT && SDT
46
+	uint16_t		transport_stream_id;// For NIT
47
+	char 			*network_name;		// For NIT
48
+	char 			*provider_name;		// For SDT
49
+
50
+	double			output_bitrate;		// Output bitrate (bps)
51
+	long			output_tmout;		// Smooth interval miliseconds
52
+	long			usleep_overhead;		// How much more usecs uslep(1) takes
53
+	long			output_packets_per_sec;	// How much packets should be sent in one second
54
+
55
+	time_t			epg_conf_mtime;		// Last change time of epg config
56
+
57
+	LIST *			channels;			// List of channels
58
+	LIST *			inputs;				// Input threads
59
+	LIST *			nit;
60
+	OUTPUT *		output;				// Output
61
+
62
+	struct {
63
+		unsigned int	pat;			// DVB section id 0x00 (program_association_section)
64
+		unsigned int	pmt;			// DVB section id 0x02 (program_map_section)
65
+		unsigned int	nit;			// DVB section id 0x40 (network_information_section - actual_network)
66
+		unsigned int	sdt;			// DVB section id 0x42 (service_description_section - actual_transport_stream)
67
+		unsigned int	eit;			// DVB section id 0x4e (event_information_section - actual_transport_stream, present/following)
68
+		unsigned int	tdt;			// DVB section id 0x70 (time_date_section)
69
+		unsigned int	tot;			// DVB section id 0x73 (time_offset_section)
70
+		unsigned int	stats;			// Local
71
+	} timeouts;
72
+} CONFIG;
73
+
74
+
75
+CONFIG *	config_alloc	();
76
+void		config_free		(CONFIG **conf);
77
+void		config_load		(CONFIG *conf, int argc, char **argv);
78
+
79
+int			config_load_global		(CONFIG *conf);
80
+int			config_load_channels	(CONFIG *conf);
81
+int			config_load_nit			(CONFIG *conf);
82
+int			config_load_epg			(CONFIG *conf);
83
+
84
+#endif

+ 389 - 0
data.c View File

@@ -0,0 +1,389 @@
1
+#include <stdlib.h>
2
+#include <math.h>
3
+#include <ctype.h>
4
+#include <unistd.h>
5
+#include <string.h>
6
+#include <sys/time.h>
7
+#include <regex.h>
8
+#include <sys/types.h>
9
+#include <sys/stat.h>
10
+#include <fcntl.h>
11
+
12
+#include "libfuncs/io.h"
13
+#include "libfuncs/log.h"
14
+#include "libfuncs/list.h"
15
+#include "libfuncs/asyncdns.h"
16
+
17
+#include "libtsfuncs/tsfuncs.h"
18
+
19
+#include "data.h"
20
+#include "config.h"
21
+#include "output.h"
22
+
23
+extern CONFIG *config;
24
+
25
+channel_source get_sproto(char *url) {
26
+	return strncmp(url, "http", 4)==0 ? tcp_sock : udp_sock;
27
+}
28
+
29
+CHANSRC *chansrc_init(char *url) {
30
+	if (!url)
31
+		return NULL;
32
+	regex_t re;
33
+	regmatch_t res[5];
34
+	regcomp(&re, "^([a-z]+)://([^:/?]+):?([0-9]*)/?(.*)", REG_EXTENDED);
35
+	if (regexec(&re,url,5,res,0)==0) {
36
+		char *data = strdup(url);
37
+		char *proto, *host, *port, *path;
38
+		int iport;
39
+		proto= data+res[1].rm_so; data[res[1].rm_eo]=0;
40
+		host = data+res[2].rm_so; data[res[2].rm_eo]=0;
41
+		port = data+res[3].rm_so; data[res[3].rm_eo]=0;
42
+		path = data+res[4].rm_so; data[res[4].rm_eo]=0;
43
+		iport = atoi(port);
44
+		/* Setup */
45
+		CHANSRC *src = calloc(1, sizeof(CHANSRC));
46
+		src->proto = strdup(proto);
47
+		src->sproto= get_sproto(url);
48
+		src->host  = strdup(host);
49
+		src->port  = iport ? iport : 80;
50
+		src->path  = strdup(path);
51
+		FREE(data);
52
+		regfree(&re);
53
+		return src;
54
+	}
55
+	regfree(&re);
56
+	return NULL;
57
+}
58
+
59
+void chansrc_free(CHANSRC **purl) {
60
+	CHANSRC *url = *purl;
61
+	if (url) {
62
+		FREE(url->proto);
63
+		FREE(url->host);
64
+		FREE(url->path);
65
+		FREE(*purl);
66
+	}
67
+};
68
+
69
+void chansrc_add(CHANNEL *c, char *src) {
70
+	if (c->num_src >= MAX_CHANNEL_SOURCES-1)
71
+		return;
72
+	c->sources[c->num_src] = strdup(src);
73
+	if (c->num_src == 0) /* Set default source to first one */
74
+		c->source = c->sources[c->num_src];
75
+	c->num_src++;
76
+}
77
+
78
+void chansrc_next(CHANNEL *c) {
79
+	if (c->num_src <= 1)
80
+		return;
81
+	// uint8_t old_src = c->curr_src;
82
+	c->curr_src++;
83
+	if (c->curr_src >= MAX_CHANNEL_SOURCES-1 || c->sources[c->curr_src] == NULL)
84
+		c->curr_src = 0;
85
+	c->source = c->sources[c->curr_src];
86
+	// LOGf("CHAN : Switch source | Channel: %s OldSrc: %d %s NewSrc: %d %s\n", c->name, old_src, c->sources[old_src], c->curr_src, c->source);
87
+}
88
+
89
+void chansrc_set(CHANNEL *c, uint8_t src_id) {
90
+	if (src_id >= MAX_CHANNEL_SOURCES-1 || c->sources[src_id] == NULL)
91
+		return;
92
+	// uint8_t old_src = c->curr_src;
93
+	c->curr_src = src_id;
94
+	c->source = c->sources[c->curr_src];
95
+	// LOGf("CHAN : Set source    | Channel: %s OldSrc: %d %s NewSrc: %d %s\n", c->name, old_src, c->sources[old_src], c->curr_src, c->source);
96
+}
97
+
98
+
99
+
100
+
101
+
102
+CHANNEL *channel_new(int service_id, int is_radio, char *id, char *name, char *source) {
103
+	CHANNEL *c = calloc(1, sizeof(CHANNEL));
104
+	c->service_id = service_id;
105
+	c->radio = is_radio;
106
+	c->base_pid = service_id * 32; // The first pid is saved for PMT
107
+	c->pmt_pid = c->base_pid; // The first pid is saved for PMT
108
+	c->id = strdup(id);
109
+	c->name = strdup(name);
110
+	chansrc_add(c, source);
111
+	return c;
112
+}
113
+
114
+void channel_free_epg(CHANNEL *c) {
115
+	epg_free(&c->epg_now);
116
+	epg_free(&c->epg_next);
117
+
118
+	ts_eit_free(&c->eit_now);
119
+	ts_eit_free(&c->eit_next);
120
+}
121
+
122
+void channel_free(CHANNEL **pc) {
123
+	CHANNEL *c = *pc;
124
+	if (c) {
125
+		channel_free_epg(c);
126
+		FREE(c->id);
127
+		FREE(c->name);
128
+		int i;
129
+		for (i=c->num_src-1; i>=0; i--) {
130
+			FREE(c->sources[i]);
131
+		}
132
+		c->source = NULL;
133
+		FREE(*pc);
134
+	}
135
+}
136
+
137
+
138
+EPG_ENTRY *epg_new(time_t start, int duration, char *encoding, char *event, char *short_desc, char *long_desc) {
139
+	EPG_ENTRY *e;
140
+	if (!event)
141
+		return NULL;
142
+	e             = calloc(1, sizeof(EPG_ENTRY));
143
+	e->event_id   = (start / 60) &~ 0xffff0000;
144
+	e->start      = start;
145
+	e->duration   = duration;
146
+	if (encoding && strcmp(encoding, "iso-8859-5")==0) {
147
+		e->event      = init_dvb_string_iso_8859_5(event);
148
+		e->short_desc = init_dvb_string_iso_8859_5(short_desc);
149
+		e->long_desc  = init_dvb_string_iso_8859_5(long_desc);
150
+	} else {	// Default is utf-8
151
+		e->event      = init_dvb_string_utf8(event);
152
+		e->short_desc = init_dvb_string_utf8(short_desc);
153
+		e->long_desc  = init_dvb_string_utf8(long_desc);
154
+	}
155
+	return e;
156
+}
157
+
158
+void epg_free(EPG_ENTRY **pe) {
159
+	EPG_ENTRY *e = *pe;
160
+	if (e) {
161
+		FREE(e->event);
162
+		FREE(e->short_desc);
163
+		FREE(e->long_desc);
164
+		FREE(*pe);
165
+	}
166
+}
167
+
168
+
169
+// Return 1 if they are different
170
+// Return 0 if they are the same
171
+int epg_changed(EPG_ENTRY *a, EPG_ENTRY *b) {
172
+	if (!a && b) return 1;
173
+	if (!b && a) return 1;
174
+	if (!a && !b) return 0;
175
+	if (a->event_id != b->event_id) return 1;
176
+	if (a->start != b->start) return 1;
177
+	if (a->duration != b->duration) return 1;
178
+	if (xstrcmp(a->event, b->event) != 0) return 1;
179
+	if (xstrcmp(a->short_desc, b->short_desc) != 0) return 1;
180
+	if (xstrcmp(a->long_desc, b->long_desc) != 0) return 1;
181
+	return 0;
182
+}
183
+
184
+void input_stream_alloc(INPUT *input) {
185
+	input->stream.pidref = pidref_init(64, input->channel->base_pid);
186
+	input->stream.pat = ts_pat_alloc();
187
+	input->stream.pmt = ts_pmt_alloc();
188
+	input->stream.last_pat = ts_pat_alloc();
189
+	input->stream.last_pmt = ts_pmt_alloc();
190
+}
191
+
192
+void input_stream_free(INPUT *input) {
193
+	ts_pmt_free(&input->stream.pmt);
194
+	ts_pmt_free(&input->stream.pmt_rewritten);
195
+	ts_pmt_free(&input->stream.last_pmt);
196
+	ts_pat_free(&input->stream.pat);
197
+	ts_pat_free(&input->stream.pat_rewritten);
198
+	ts_pat_free(&input->stream.last_pat);
199
+	pidref_free(&input->stream.pidref);
200
+	input->stream.nit_pid    = 0;
201
+	input->stream.pmt_pid    = 0;
202
+	input->stream.pcr_pid    = 0;
203
+	input->stream.input_pcr  = 0;
204
+}
205
+
206
+void input_stream_reset(INPUT *input) {
207
+	input_stream_free(input);
208
+	input_stream_alloc(input);
209
+}
210
+
211
+INPUT * input_new(const char *name, CHANNEL *channel) {
212
+	char *tmp;
213
+	INPUT *r = calloc(1, sizeof(INPUT));
214
+
215
+	r->name = strdup(name);
216
+	r->sock = -1;
217
+	r->channel = channel;
218
+
219
+	if (config->write_input_file) {
220
+		asprintf(&tmp, "mptsd-input-%s.ts", channel->id);
221
+		r->ifd = open(tmp, O_CREAT | O_WRONLY | O_TRUNC, 0644);
222
+		FREE(tmp);
223
+	}
224
+
225
+	r->buf = cbuf_init(1428 * 1316, channel->id); // ~ 10000 x 188
226
+
227
+	input_stream_alloc(r);
228
+
229
+	return r;
230
+}
231
+
232
+void input_free(INPUT **pinput) {
233
+	INPUT *r = *pinput;
234
+	if (!r)
235
+		return;
236
+	if (r->sock > -1)
237
+		shutdown_fd(&(r->sock));
238
+	if (r->freechannel)
239
+		channel_free(&r->channel);
240
+	if (r->ifd)
241
+		close(r->ifd);
242
+
243
+	input_stream_free(r);
244
+
245
+	cbuf_free(&r->buf);
246
+
247
+	FREE(r->name);
248
+	FREE(*pinput);
249
+}
250
+
251
+
252
+
253
+OUTPUT *output_new() {
254
+	OUTPUT *o = calloc(1, sizeof(OUTPUT));
255
+	o->obuf_ms = 100;
256
+
257
+	o->psibuf = cbuf_init(50 * 1316, "psi");
258
+	if (!o->psibuf) {
259
+		LOGf("ERROR: Can't allocate PSI input buffer\n");
260
+		exit(1);
261
+	}
262
+	cbuf_poison(o->psibuf, 'Y');
263
+
264
+	return o;
265
+}
266
+
267
+void output_open_file(OUTPUT *o) {
268
+	o->ofd = open("mptsd-output.ts", O_CREAT | O_WRONLY | O_TRUNC, 0644);
269
+}
270
+
271
+void obuf_reset(OBUF *ob) {
272
+	int i;
273
+	memset(ob->buf, 0xff, ob->size);
274
+	for (i=0; i<ob->size; i+=188) {
275
+		ob->buf[i+0] = 0x47;
276
+		ob->buf[i+1] = 0x1f;
277
+		ob->buf[i+2] = 0xff;
278
+		ob->buf[i+3] = 0x00;
279
+	}
280
+	ob->written = 0;
281
+	ob->status  = obuf_empty;
282
+}
283
+
284
+void output_buffer_alloc(OUTPUT *o, double output_bitrate) {
285
+	if (!output_bitrate) {
286
+		LOGf("No output bitrate, can't determine buffer!\n");
287
+		exit(1);
288
+	}
289
+
290
+	o->output_bitrate = output_bitrate;
291
+
292
+	long pps  = ceil((double)output_bitrate / (FRAME_PACKET_SIZE * 8));	// Packets per second
293
+	long ppms = ceil((double)pps / ((double)1000 / o->obuf_ms));		// Packets per o->buffer_ms miliseconds
294
+	long obuf_size = ppms * 1316;
295
+
296
+	o->obuf[0].size   = obuf_size;
297
+	o->obuf[0].status = obuf_empty;
298
+	o->obuf[0].buf    = malloc(o->obuf[0].size);
299
+	obuf_reset(&o->obuf[0]);
300
+
301
+	o->obuf[1].size   = obuf_size;
302
+	o->obuf[1].status = obuf_empty;
303
+	o->obuf[1].buf    = malloc(o->obuf[0].size);
304
+	obuf_reset(&o->obuf[1]);
305
+
306
+	LOGf("\tOutput buf size   : %ld * 2 = %ld\n", obuf_size, obuf_size * 2);
307
+	LOGf("\tOutput buf packets: %ld (188 bytes)\n", obuf_size / 188);
308
+	LOGf("\tOutput buf frames : %ld (1316 bytes)\n", obuf_size / 1316);
309
+	LOGf("\tOutput buf ms     : %u ms\n", o->obuf_ms);
310
+}
311
+
312
+void output_free(OUTPUT **po) {
313
+	OUTPUT *o = *po;
314
+	if (!o)
315
+		return;
316
+	if (o->out_sock > -1)
317
+		shutdown_fd(&(o->out_sock));
318
+	if (o->ofd)
319
+		close(o->ofd);
320
+	cbuf_free(&o->psibuf);
321
+	FREE(o->obuf[0].buf);
322
+	FREE(o->obuf[1].buf);
323
+	output_psi_free(o);
324
+	FREE(*po);
325
+}
326
+
327
+
328
+NIT *nit_new(uint16_t ts_id, char *freq, char *mod, char *symbol_rate) {
329
+	char tmp[9];
330
+	unsigned i, pos;
331
+
332
+	NIT *n = calloc(1, sizeof(NIT));
333
+	if (strlen(freq) != 9 || strlen(symbol_rate) != 8)
334
+		return NULL;
335
+	n->freq        = strdup(freq);
336
+	n->modulation  = strdup(mod);
337
+	n->symbol_rate = strdup(symbol_rate);
338
+	n->ts_id       = ts_id;
339
+
340
+	n->_modulation =
341
+		strcmp(mod, "16-QAM") == 0  ? 0x01 :
342
+		strcmp(mod, "32-QAM") == 0  ? 0x02 :
343
+		strcmp(mod, "64-QAM") == 0  ? 0x03 :
344
+		strcmp(mod, "128-QAM") == 0 ? 0x04 :
345
+		strcmp(mod, "256-QAM") == 0 ? 0x05 : 0x00;
346
+
347
+	memset(tmp, 0, sizeof(tmp));
348
+	pos = 0;
349
+	for (i=0;i<strlen(freq);i++) {
350
+		if (isdigit(freq[i])) {
351
+			tmp[pos] = freq[i];
352
+			pos++;
353
+		}
354
+	}
355
+	n->_freq = strtol(tmp, NULL, 16);
356
+
357
+	memset(tmp, 0, sizeof(tmp));
358
+	pos = 0;
359
+	for (i=0;i<strlen(symbol_rate);i++) {
360
+		if (isdigit(symbol_rate[i])) {
361
+			tmp[pos] = symbol_rate[i];
362
+			pos++;
363
+		}
364
+	}
365
+	n->_symbol_rate = strtol(tmp, NULL, 16);
366
+
367
+	return n;
368
+}
369
+
370
+void nit_free(NIT **pn) {
371
+	NIT *n = *pn;
372
+	if (n) {
373
+		FREE(n->freq);
374
+		FREE(n->modulation);
375
+		FREE(n->symbol_rate);
376
+		FREE(*pn);
377
+	}
378
+}
379
+
380
+void proxy_log(INPUT *r, char *msg) {
381
+	LOGf("INPUT : [%-12s] %s fd: %d src: %s\n", r->channel->id, msg, r->sock, r->channel->source);
382
+}
383
+
384
+void proxy_close(LIST *inputs, INPUT **input) {
385
+	proxy_log(*input, "Stop");
386
+	// If there are no clients left, no "Timeout" messages will be logged
387
+	list_del_entry(inputs, *input);
388
+	input_free(input);
389
+}

+ 230 - 0
data.h View File

@@ -0,0 +1,230 @@
1
+#ifndef DATA_H
2
+#define DATA_H
3
+
4
+/* How much to wait for connection to be established with channel source (miliseconds) */
5
+#define PROXY_CONNECT_TIMEOUT 1000
6
+
7
+/* Seconds to sleep between retries (miliseconds) */
8
+#define PROXY_RETRY_TIMEOUT 1000
9
+
10
+/* 7 * 188 */
11
+#define FRAME_PACKET_SIZE 1316
12
+
13
+#include "libfuncs/libfuncs.h"
14
+
15
+#include "libtsfuncs/tsdata.h"
16
+
17
+#include "pidref.h"
18
+
19
+typedef enum { udp_sock, tcp_sock } channel_source;
20
+
21
+typedef struct {
22
+	channel_source sproto;
23
+	char *proto;
24
+	char *host;
25
+	char *path;
26
+	unsigned int port;
27
+} CHANSRC;
28
+
29
+#define MAX_CHANNEL_SOURCES 8
30
+
31
+typedef struct {
32
+	uint16_t	event_id;
33
+	time_t		start;
34
+	int			duration;
35
+	char *		event;
36
+	char *		short_desc;
37
+	char *		long_desc;
38
+} EPG_ENTRY;
39
+
40
+typedef struct {
41
+	/* Config */
42
+	int			base_pid;
43
+	int			service_id;
44
+	int			pmt_pid;
45
+	int			radio;
46
+	char *		id;
47
+	char *		name;
48
+	/* Sources */
49
+	char *		source; /* Full source url */
50
+	char *		sources[MAX_CHANNEL_SOURCES];
51
+	uint8_t		num_src;
52
+	uint8_t		curr_src;
53
+	int			worktime_start;
54
+	int			worktime_end;
55
+
56
+	/* EPG */
57
+	uint8_t			epg_version:5;
58
+
59
+	EPG_ENTRY *		epg_now;
60
+	EPG_ENTRY *		epg_next;
61
+
62
+	struct ts_eit *	eit_now;
63
+	struct ts_eit *	eit_next;
64
+} CHANNEL;
65
+
66
+typedef struct {
67
+	uint64_t pcr;
68
+	uint64_t last_pcr;
69
+	int bytes;
70
+	int ts_packets_per_output_bitrate;
71
+} PCR;
72
+
73
+typedef struct {
74
+	PIDREF *pidref;			/* Rewritten pids list */
75
+
76
+	uint16_t nit_pid;		/* Pid of the NIT, default 0x10 */
77
+	uint16_t pmt_pid;		/* Pid of the original PMT, used to replace PMT */
78
+	uint16_t pcr_pid;		/* PCR pid */
79
+
80
+	struct ts_pat *pat;		/* The PAT */
81
+	struct ts_pmt *pmt;		/* The PMT */
82
+
83
+	uint64_t input_pcr;		// Latest PCR entered into input buffer
84
+
85
+	uint8_t pid_pat_cont:4;
86
+	uint8_t pid_pmt_cont:4;
87
+	struct ts_pat *pat_rewritten;	/* The rewritten PAT */
88
+	struct ts_pmt *pmt_rewritten;	/* The rewritten PMT */
89
+
90
+	struct ts_pat *last_pat;		/* The last incoming PAT */
91
+	struct ts_pmt *last_pmt;		/* The last incoming PMT */
92
+} INPUT_STREAM;
93
+
94
+typedef struct {
95
+	char  *name;
96
+	CHANNEL *channel;
97
+	int sock;				/* Server socket */
98
+	struct sockaddr_in src_sockname;
99
+	int reconnect:1,		/* Set to 1 to force proxy reconnect */
100
+	    connected:1,		/* It's set to 1 when proxy is connected and serving clients */
101
+	    insert_eit:1,		/* When set to 1 input adds EIT table into stream (if there is info) */
102
+	    working:1,			/* Set to 1 if the input is in worktime */
103
+	    dienow:1,			/* Stop serving clients and exit now */
104
+	    freechannel:1;		/* Free channel data on object free (this is used in chanconf) */
105
+	int cookie;				/* Used in chanconf to determine if the restreamer is alrady checked */
106
+	int ifd;
107
+
108
+	pthread_t thread;
109
+
110
+	uint16_t output_pcr_pid;
111
+	uint64_t output_last_pcr;			// The PCR before latest outputed
112
+	uint64_t output_pcr;				// Latest outputed PCR
113
+	int output_pcr_packets_needed;		// Based on selected output rate how much packets should be between output_pcr and output_last_pcr
114
+	int outputed_packets;				// How much packets have been sent. This is reset on every PCR and incremented on every sent packet (every input and output padding)
115
+
116
+	int disabled;			/* Input is disabled, no data is fed to output buffers */
117
+	int input_ready;		/* Set to 1 from INPUT thread when input is ready to be mixed in output */
118
+
119
+	CBUF *buf;				// Input buffer */
120
+
121
+	INPUT_STREAM stream;
122
+} INPUT;
123
+
124
+typedef enum {
125
+	obuf_empty    = 0,		// Buffer is empty and can be used by mix thread
126
+	obuf_filling  = 1,		// Buffer is being filled by mix thread
127
+	obuf_full     = 2,		// Buffer is filled and can be used by write thread
128
+	obuf_emptying = 3,		// Buffer is being emptyed by write thread
129
+} OBUF_STATUS;
130
+
131
+typedef struct {
132
+	uint8_t *	buf;
133
+	int			size;		// Output buffer size (must be size % 1316 == 0)
134
+	int			written;
135
+	OBUF_STATUS	status;
136
+} OBUF;
137
+
138
+typedef struct {
139
+	struct in_addr out_host;
140
+	int out_port;
141
+	int out_sock;			/* The udp socket */
142
+	int ofd;
143
+	int dienow;				/* Instruct output to die */
144
+
145
+	pthread_t psi_thread;
146
+	pthread_t mix_thread;
147
+	pthread_t write_thread;
148
+
149
+	CBUF *psibuf;			// Input buffer */
150
+
151
+	unsigned int obuf_ms;	// How much miliseconds of data output buffer holds
152
+	OBUF obuf[2];			// Output buffers
153
+	double output_bitrate;	// Output bitrate (bps)
154
+
155
+	uint64_t traffic;
156
+
157
+	uint64_t traffic_period;
158
+	uint64_t padding_period;
159
+
160
+	uint8_t pid_pat_cont:4;
161
+	uint8_t pid_nit_cont:4;
162
+	uint8_t pid_sdt_cont:4;
163
+	uint8_t pid_eit_cont:4;
164
+	uint8_t pid_tdt_cont:4;
165
+
166
+	struct ts_pat *pat;
167
+	struct timeval pat_ts;
168
+
169
+	struct ts_sdt *sdt;
170
+	struct timeval sdt_ts;
171
+
172
+	struct ts_nit *nit;
173
+	struct timeval nit_ts;
174
+
175
+	struct ts_tdt *tdt;
176
+	struct timeval tdt_ts;
177
+
178
+	struct ts_tdt *tot;
179
+	struct timeval tot_ts;
180
+
181
+	struct timeval eit_ts;
182
+
183
+	uint64_t last_org_pcr[8193];	// Last PCR value indexed by PID x
184
+	uint64_t last_pcr[8193];		// Last PCR value indexed by PID x
185
+	uint64_t last_traffic[8193];	// Last traffic when PCR with PID x was seen
186
+} OUTPUT;
187
+
188
+typedef struct {
189
+	char		*freq;
190
+	char		*modulation;
191
+	char		*symbol_rate;
192
+	uint16_t	ts_id;
193
+	uint32_t	_freq;
194
+	uint8_t		_modulation;
195
+	uint32_t	_symbol_rate;
196
+} NIT;
197
+
198
+EPG_ENTRY *	epg_new			(time_t start, int duration, char *encoding, char *event, char *short_desc, char *long_desc);
199
+void		epg_free		(EPG_ENTRY **e);
200
+int			epg_changed		(EPG_ENTRY *a, EPG_ENTRY *b);
201
+
202
+CHANNEL *	channel_new		(int service_id, int is_radio, char *id, char *name, char *source);
203
+void		channel_free	(CHANNEL **c);
204
+void		channel_free_epg(CHANNEL *c);
205
+
206
+channel_source get_sproto(char *url);
207
+CHANSRC *	chansrc_init	(char *url);
208
+void		chansrc_free	(CHANSRC **url);
209
+void		chansrc_add		(CHANNEL *c, char *src);
210
+void		chansrc_next	(CHANNEL *c);
211
+void		chansrc_set		(CHANNEL *c, uint8_t src_id);
212
+
213
+INPUT *		input_new		(const char *name, CHANNEL *channel);
214
+void		input_free		(INPUT **input);
215
+
216
+void		input_stream_reset	(INPUT *input);
217
+
218
+OUTPUT *	output_new			();
219
+void		output_free			(OUTPUT **output);
220
+void		output_open_file	(OUTPUT *o);
221
+void		output_buffer_alloc	(OUTPUT *o, double output_bitrate);
222
+void		obuf_reset			(OBUF *ob);
223
+
224
+NIT *		nit_new			(uint16_t ts_id, char *freq, char *modulation, char *symbol_rate);
225
+void		nit_free		(NIT **nit);
226
+
227
+void		proxy_log		(INPUT *r, char *msg);
228
+void		proxy_close		(LIST *inputs, INPUT **input);
229
+
230
+#endif

+ 428 - 0
inidict.c View File

@@ -0,0 +1,428 @@
1
+/*
2
+Copyright (c) 2000-2007 by Nicolas Devillard.
3
+MIT License
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a
6
+copy of this software and associated documentation files (the "Software"),
7
+to deal in the Software without restriction, including without limitation
8
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
+and/or sell copies of the Software, and to permit persons to whom the
10
+Software is furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in
13
+all copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+DEALINGS IN THE SOFTWARE.
22
+*/
23
+
24
+/*-------------------------------------------------------------------------*/
25
+/**
26
+   @file	dictionary.c
27
+   @author	N. Devillard
28
+   @date	Sep 2007
29
+   @version	Revision: 1.27
30
+   @brief	Implements a dictionary for string variables.
31
+
32
+   This module implements a simple dictionary object, i.e. a list
33
+   of string/string associations. This object is useful to store e.g.
34
+   informations retrieved from a configuration file (ini files).
35
+*/
36
+/*--------------------------------------------------------------------------*/
37
+
38
+/*
39
+	Id: inidict.c,v 1.2 2010/04/26 09:35:52 gf Exp
40
+	Revision: 1.2
41
+*/
42
+/*---------------------------------------------------------------------------
43
+   								Includes
44
+ ---------------------------------------------------------------------------*/
45
+#include "inidict.h"
46
+
47
+#include <stdio.h>
48
+#include <stdlib.h>
49
+#include <string.h>
50
+#include <unistd.h>
51
+
52
+/** Maximum value size for integers and doubles. */
53
+#define MAXVALSZ	1024
54
+
55
+/** Minimal allocated number of entries in a dictionary */
56
+#define DICTMINSZ	128
57
+
58
+/** Invalid key token */
59
+#define DICT_INVALID_KEY    ((char*)-1)
60
+
61
+/*---------------------------------------------------------------------------
62
+  							Private functions
63
+ ---------------------------------------------------------------------------*/
64
+
65
+/* Doubles the allocated size associated to a pointer */
66
+/* 'size' is the current allocated size. */
67
+static void * mem_double(void * ptr, int size)
68
+{
69
+    void * newptr ;
70
+ 
71
+    newptr = calloc(2*size, 1);
72
+    if (newptr==NULL) {
73
+        return NULL ;
74
+    }
75
+    memcpy(newptr, ptr, size);
76
+    free(ptr);
77
+    return newptr ;
78
+}
79
+
80
+/*-------------------------------------------------------------------------*/
81
+/**
82
+  @brief    Duplicate a string
83
+  @param    s String to duplicate
84
+  @return   Pointer to a newly allocated string, to be freed with free()
85
+
86
+  This is a replacement for strdup(). This implementation is provided
87
+  for systems that do not have it.
88
+ */
89
+/*--------------------------------------------------------------------------*/
90
+static char * xstrdup(char * s)
91
+{
92
+    char * t ;
93
+    if (!s)
94
+        return NULL ;
95
+    t = malloc(strlen(s)+1) ;
96
+    if (t) {
97
+        strcpy(t,s);
98
+    }
99
+    return t ;
100
+}
101
+
102
+/*---------------------------------------------------------------------------
103
+  							Function codes
104
+ ---------------------------------------------------------------------------*/
105
+/*-------------------------------------------------------------------------*/
106
+/**
107
+  @brief	Compute the hash key for a string.
108
+  @param	key		Character string to use for key.
109
+  @return	1 unsigned int on at least 32 bits.
110
+
111
+  This hash function has been taken from an Article in Dr Dobbs Journal.
112
+  This is normally a collision-free function, distributing keys evenly.
113
+  The key is stored anyway in the struct so that collision can be avoided
114
+  by comparing the key itself in last resort.
115
+ */
116
+/*--------------------------------------------------------------------------*/
117
+unsigned dictionary_hash(char * key)
118
+{
119
+	int			len ;
120
+	unsigned	hash ;
121
+	int			i ;
122
+
123
+	len = strlen(key);
124
+	for (hash=0, i=0 ; i<len ; i++) {
125
+		hash += (unsigned)key[i] ;
126
+		hash += (hash<<10);
127
+		hash ^= (hash>>6) ;
128
+	}
129
+	hash += (hash <<3);
130
+	hash ^= (hash >>11);
131
+	hash += (hash <<15);
132
+	return hash ;
133
+}
134
+
135
+/*-------------------------------------------------------------------------*/
136
+/**
137
+  @brief	Create a new dictionary object.
138
+  @param	size	Optional initial size of the dictionary.
139
+  @return	1 newly allocated dictionary objet.
140
+
141
+  This function allocates a new dictionary object of given size and returns
142
+  it. If you do not know in advance (roughly) the number of entries in the
143
+  dictionary, give size=0.
144
+ */
145
+/*--------------------------------------------------------------------------*/
146
+dictionary * dictionary_new(int size)
147
+{
148
+	dictionary	*	d ;
149
+
150
+	/* If no size was specified, allocate space for DICTMINSZ */
151
+	if (size<DICTMINSZ) size=DICTMINSZ ;
152
+
153
+	if (!(d = (dictionary *)calloc(1, sizeof(dictionary)))) {
154
+		return NULL;
155
+	}
156
+	d->size = size ;
157
+	d->val  = (char **)calloc(size, sizeof(char*));
158
+	d->key  = (char **)calloc(size, sizeof(char*));
159
+	d->hash = (unsigned int *)calloc(size, sizeof(unsigned));
160
+	return d ;
161
+}
162
+
163
+/*-------------------------------------------------------------------------*/
164
+/**
165
+  @brief	Delete a dictionary object
166
+  @param	d	dictionary object to deallocate.
167
+  @return	void
168
+
169
+  Deallocate a dictionary object and all memory associated to it.
170
+ */
171
+/*--------------------------------------------------------------------------*/
172
+void dictionary_del(dictionary * d)
173
+{
174
+	int		i ;
175
+
176
+	if (d==NULL) return ;
177
+	for (i=0 ; i<d->size ; i++) {
178
+		if (d->key[i]!=NULL)
179
+			free(d->key[i]);
180
+		if (d->val[i]!=NULL)
181
+			free(d->val[i]);
182
+	}
183
+	free(d->val);
184
+	free(d->key);
185
+	free(d->hash);
186
+	free(d);
187
+	return ;
188
+}
189
+
190
+/*-------------------------------------------------------------------------*/
191
+/**
192
+  @brief	Get a value from a dictionary.
193
+  @param	d		dictionary object to search.
194
+  @param	key		Key to look for in the dictionary.
195
+  @param    def     Default value to return if key not found.
196
+  @return	1 pointer to internally allocated character string.
197
+
198
+  This function locates a key in a dictionary and returns a pointer to its
199
+  value, or the passed 'def' pointer if no such key can be found in
200
+  dictionary. The returned character pointer points to data internal to the
201
+  dictionary object, you should not try to free it or modify it.
202
+ */
203
+/*--------------------------------------------------------------------------*/
204
+char * dictionary_get(dictionary * d, char * key, char * def)
205
+{
206
+	unsigned	hash ;
207
+	int			i ;
208
+
209
+	hash = dictionary_hash(key);
210
+	for (i=0 ; i<d->size ; i++) {
211
+        if (d->key[i]==NULL)
212
+            continue ;
213
+        /* Compare hash */
214
+		if (hash==d->hash[i]) {
215
+            /* Compare string, to avoid hash collisions */
216
+            if (!strcmp(key, d->key[i])) {
217
+				return d->val[i] ;
218
+			}
219
+		}
220
+	}
221
+	return def ;
222
+}
223
+
224
+/*-------------------------------------------------------------------------*/
225
+/**
226
+  @brief    Set a value in a dictionary.
227
+  @param    d       dictionary object to modify.
228
+  @param    key     Key to modify or add.
229
+  @param    val     Value to add.
230
+  @return   int     0 if Ok, anything else otherwise
231
+
232
+  If the given key is found in the dictionary, the associated value is
233
+  replaced by the provided one. If the key cannot be found in the
234
+  dictionary, it is added to it.
235
+
236
+  It is Ok to provide a NULL value for val, but NULL values for the dictionary
237
+  or the key are considered as errors: the function will return immediately
238
+  in such a case.
239
+
240
+  Notice that if you dictionary_set a variable to NULL, a call to
241
+  dictionary_get will return a NULL value: the variable will be found, and
242
+  its value (NULL) is returned. In other words, setting the variable
243
+  content to NULL is equivalent to deleting the variable from the
244
+  dictionary. It is not possible (in this implementation) to have a key in
245
+  the dictionary without value.
246
+
247
+  This function returns non-zero in case of failure.
248
+ */
249
+/*--------------------------------------------------------------------------*/
250
+int dictionary_set(dictionary * d, char * key, char * val)
251
+{
252
+	int			i ;
253
+	unsigned	hash ;
254
+
255
+	if (d==NULL || key==NULL) return -1 ;
256
+	
257
+	/* Compute hash for this key */
258
+	hash = dictionary_hash(key) ;
259
+	/* Find if value is already in dictionary */
260
+	if (d->n>0) {
261
+		for (i=0 ; i<d->size ; i++) {
262
+            if (d->key[i]==NULL)
263
+                continue ;
264
+			if (hash==d->hash[i]) { /* Same hash value */
265
+				if (!strcmp(key, d->key[i])) {	 /* Same key */
266
+					/* Found a value: modify and return */
267
+					if (d->val[i]!=NULL)
268
+						free(d->val[i]);
269
+                    d->val[i] = val ? xstrdup(val) : NULL ;
270
+                    /* Value has been modified: return */
271
+					return 0 ;
272
+				}
273
+			}
274
+		}
275
+	}
276
+	/* Add a new value */
277
+	/* See if dictionary needs to grow */
278
+	if (d->n==d->size) {
279
+
280
+		/* Reached maximum size: reallocate dictionary */
281
+		d->val  = (char **)mem_double(d->val,  d->size * sizeof(char*)) ;
282
+		d->key  = (char **)mem_double(d->key,  d->size * sizeof(char*)) ;
283
+		d->hash = (unsigned int *)mem_double(d->hash, d->size * sizeof(unsigned)) ;
284
+        if ((d->val==NULL) || (d->key==NULL) || (d->hash==NULL)) {
285
+            /* Cannot grow dictionary */
286
+            return -1 ;
287
+        }
288
+		/* Double size */
289
+		d->size *= 2 ;
290
+	}
291
+
292
+    /* Insert key in the first empty slot */
293
+    for (i=0 ; i<d->size ; i++) {
294
+        if (d->key[i]==NULL) {
295
+            /* Add key here */
296
+            break ;
297
+        }
298
+    }
299
+	/* Copy key */
300
+	d->key[i]  = xstrdup(key);
301
+    d->val[i]  = val ? xstrdup(val) : NULL ;
302
+	d->hash[i] = hash;
303
+	d->n ++ ;
304
+	return 0 ;
305
+}
306
+
307
+/*-------------------------------------------------------------------------*/
308
+/**
309
+  @brief	Delete a key in a dictionary
310
+  @param	d		dictionary object to modify.
311
+  @param	key		Key to remove.
312
+  @return   void
313
+
314
+  This function deletes a key in a dictionary. Nothing is done if the
315
+  key cannot be found.
316
+ */
317
+/*--------------------------------------------------------------------------*/
318
+void dictionary_unset(dictionary * d, char * key)
319
+{
320
+	unsigned	hash ;
321
+	int			i ;
322
+
323
+	if (key == NULL) {
324
+		return;
325
+	}
326
+
327
+	hash = dictionary_hash(key);
328
+	for (i=0 ; i<d->size ; i++) {
329
+        if (d->key[i]==NULL)
330
+            continue ;
331
+        /* Compare hash */
332
+		if (hash==d->hash[i]) {
333
+            /* Compare string, to avoid hash collisions */
334
+            if (!strcmp(key, d->key[i])) {
335
+                /* Found key */
336
+                break ;
337
+			}
338
+		}
339
+	}
340
+    if (i>=d->size)
341
+        /* Key not found */
342
+        return ;
343
+
344
+    free(d->key[i]);
345
+    d->key[i] = NULL ;
346
+    if (d->val[i]!=NULL) {
347
+        free(d->val[i]);
348
+        d->val[i] = NULL ;
349
+    }
350
+    d->hash[i] = 0 ;
351
+    d->n -- ;
352
+    return ;
353
+}
354
+
355
+/*-------------------------------------------------------------------------*/
356
+/**
357
+  @brief	Dump a dictionary to an opened file pointer.
358
+  @param	d	Dictionary to dump
359
+  @param	f	Opened file pointer.
360
+  @return	void
361
+
362
+  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
363
+  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
364
+  output file pointers.
365
+ */
366
+/*--------------------------------------------------------------------------*/
367
+void dictionary_dump(dictionary * d, FILE * out)
368
+{
369
+	int		i ;
370
+
371
+	if (d==NULL || out==NULL) return ;
372
+	if (d->n<1) {
373
+		fprintf(out, "empty dictionary\n");
374
+		return ;
375
+	}
376
+	for (i=0 ; i<d->size ; i++) {
377
+        if (d->key[i]) {
378
+            fprintf(out, "%20s\t[%s]\n",
379
+                    d->key[i],
380
+                    d->val[i] ? d->val[i] : "UNDEF");
381
+        }
382
+	}
383
+	return ;
384
+}
385
+
386
+
387
+/* Test code */
388
+#ifdef TESTDIC
389
+#define NVALS 20000
390
+int main(int argc, char *argv[])
391
+{
392
+	dictionary	*	d ;
393
+	char	*	val ;
394
+	int			i ;
395
+	char		cval[90] ;
396
+
397
+	/* Allocate dictionary */
398
+	printf("allocating...\n");
399
+	d = dictionary_new(0);
400
+	
401
+	/* Set values in dictionary */
402
+	printf("setting %d values...\n", NVALS);
403
+	for (i=0 ; i<NVALS ; i++) {
404
+		sprintf(cval, "%04d", i);
405
+		dictionary_set(d, cval, "salut");
406
+	}
407
+	printf("getting %d values...\n", NVALS);
408
+	for (i=0 ; i<NVALS ; i++) {
409
+		sprintf(cval, "%04d", i);
410
+		val = dictionary_get(d, cval, DICT_INVALID_KEY);
411
+		if (val==DICT_INVALID_KEY) {
412
+			printf("cannot get value for key [%s]\n", cval);
413
+		}
414
+	}
415
+    printf("unsetting %d values...\n", NVALS);
416
+	for (i=0 ; i<NVALS ; i++) {
417
+		sprintf(cval, "%04d", i);
418
+		dictionary_unset(d, cval);
419
+	}
420
+    if (d->n != 0) {
421
+        printf("error deleting values\n");
422
+    }
423
+	printf("deallocating...\n");
424
+	dictionary_del(d);
425
+	return 0 ;
426
+}
427
+#endif
428
+/* vim: set ts=4 et sw=4 tw=75 */

+ 174 - 0
inidict.h View File

@@ -0,0 +1,174 @@
1
+
2
+/*-------------------------------------------------------------------------*/
3
+/**
4
+   @file    dictionary.h
5
+   @author  N. Devillard
6
+   @date    Sep 2007
7
+   @version Revision: 1.12
8
+   @brief   Implements a dictionary for string variables.
9
+
10
+   This module implements a simple dictionary object, i.e. a list
11
+   of string/string associations. This object is useful to store e.g.
12
+   informations retrieved from a configuration file (ini files).
13
+*/
14
+/*--------------------------------------------------------------------------*/
15
+
16
+/*
17
+	Id: dictionary.h,v 1.12 2007-11-23 21:37:00 ndevilla Exp
18
+	Author: ndevilla
19
+	Date: 2007-11-23 21:37:00
20
+	Revision: 1.12
21
+*/
22
+
23
+#ifndef _DICTIONARY_H_
24
+#define _DICTIONARY_H_
25
+
26
+/*---------------------------------------------------------------------------
27
+   								Includes
28
+ ---------------------------------------------------------------------------*/
29
+
30
+#include <stdio.h>
31
+#include <stdlib.h>
32
+#include <string.h>
33
+#include <unistd.h>
34
+
35
+/*---------------------------------------------------------------------------
36
+   								New types
37
+ ---------------------------------------------------------------------------*/
38
+
39
+
40
+/*-------------------------------------------------------------------------*/
41
+/**
42
+  @brief	Dictionary object
43
+
44
+  This object contains a list of string/string associations. Each
45
+  association is identified by a unique string key. Looking up values
46
+  in the dictionary is speeded up by the use of a (hopefully collision-free)
47
+  hash function.
48
+ */
49
+/*-------------------------------------------------------------------------*/
50
+typedef struct _dictionary_ {
51
+	int				n ;		/** Number of entries in dictionary */
52
+	int				size ;	/** Storage size */
53
+	char 		**	val ;	/** List of string values */
54
+	char 		**  key ;	/** List of string keys */
55
+	unsigned	 *	hash ;	/** List of hash values for keys */
56
+} dictionary ;
57
+
58
+
59
+/*---------------------------------------------------------------------------
60
+  							Function prototypes
61
+ ---------------------------------------------------------------------------*/
62
+
63
+/*-------------------------------------------------------------------------*/
64
+/**
65
+  @brief    Compute the hash key for a string.
66
+  @param    key     Character string to use for key.
67
+  @return   1 unsigned int on at least 32 bits.
68
+
69
+  This hash function has been taken from an Article in Dr Dobbs Journal.
70
+  This is normally a collision-free function, distributing keys evenly.
71
+  The key is stored anyway in the struct so that collision can be avoided
72
+  by comparing the key itself in last resort.
73
+ */
74
+/*--------------------------------------------------------------------------*/
75
+unsigned dictionary_hash(char * key);
76
+
77
+/*-------------------------------------------------------------------------*/
78
+/**
79
+  @brief    Create a new dictionary object.
80
+  @param    size    Optional initial size of the dictionary.
81
+  @return   1 newly allocated dictionary objet.
82
+
83
+  This function allocates a new dictionary object of given size and returns
84
+  it. If you do not know in advance (roughly) the number of entries in the
85
+  dictionary, give size=0.
86
+ */
87
+/*--------------------------------------------------------------------------*/
88
+dictionary * dictionary_new(int size);
89
+
90
+/*-------------------------------------------------------------------------*/
91
+/**
92
+  @brief    Delete a dictionary object
93
+  @param    d   dictionary object to deallocate.
94
+  @return   void
95
+
96
+  Deallocate a dictionary object and all memory associated to it.
97
+ */
98
+/*--------------------------------------------------------------------------*/
99
+void dictionary_del(dictionary * vd);
100
+
101
+/*-------------------------------------------------------------------------*/
102
+/**
103
+  @brief    Get a value from a dictionary.
104
+  @param    d       dictionary object to search.
105
+  @param    key     Key to look for in the dictionary.
106
+  @param    def     Default value to return if key not found.
107
+  @return   1 pointer to internally allocated character string.
108
+
109
+  This function locates a key in a dictionary and returns a pointer to its
110
+  value, or the passed 'def' pointer if no such key can be found in
111
+  dictionary. The returned character pointer points to data internal to the
112
+  dictionary object, you should not try to free it or modify it.
113
+ */
114
+/*--------------------------------------------------------------------------*/
115
+char * dictionary_get(dictionary * d, char * key, char * def);
116
+
117
+
118
+/*-------------------------------------------------------------------------*/
119
+/**
120
+  @brief    Set a value in a dictionary.
121
+  @param    d       dictionary object to modify.
122
+  @param    key     Key to modify or add.
123
+  @param    val     Value to add.
124
+  @return   int     0 if Ok, anything else otherwise
125
+
126
+  If the given key is found in the dictionary, the associated value is
127
+  replaced by the provided one. If the key cannot be found in the
128
+  dictionary, it is added to it.
129
+
130
+  It is Ok to provide a NULL value for val, but NULL values for the dictionary
131
+  or the key are considered as errors: the function will return immediately
132
+  in such a case.
133
+
134
+  Notice that if you dictionary_set a variable to NULL, a call to
135
+  dictionary_get will return a NULL value: the variable will be found, and
136
+  its value (NULL) is returned. In other words, setting the variable
137
+  content to NULL is equivalent to deleting the variable from the
138
+  dictionary. It is not possible (in this implementation) to have a key in
139
+  the dictionary without value.
140
+
141
+  This function returns non-zero in case of failure.
142
+ */
143
+/*--------------------------------------------------------------------------*/
144
+int dictionary_set(dictionary * vd, char * key, char * val);
145
+
146
+/*-------------------------------------------------------------------------*/
147
+/**
148
+  @brief    Delete a key in a dictionary
149
+  @param    d       dictionary object to modify.
150
+  @param    key     Key to remove.
151
+  @return   void
152
+
153
+  This function deletes a key in a dictionary. Nothing is done if the
154
+  key cannot be found.
155
+ */
156
+/*--------------------------------------------------------------------------*/
157
+void dictionary_unset(dictionary * d, char * key);
158
+
159
+
160
+/*-------------------------------------------------------------------------*/
161
+/**
162
+  @brief    Dump a dictionary to an opened file pointer.
163
+  @param    d   Dictionary to dump
164
+  @param    f   Opened file pointer.
165
+  @return   void
166
+
167
+  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
168
+  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
169
+  output file pointers.
170
+ */
171
+/*--------------------------------------------------------------------------*/
172
+void dictionary_dump(dictionary * d, FILE * out);
173
+
174
+#endif

+ 712 - 0
iniparser.c View File

@@ -0,0 +1,712 @@
1
+/*
2
+Copyright (c) 2000-2007 by Nicolas Devillard.
3
+MIT License
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a
6
+copy of this software and associated documentation files (the "Software"),
7
+to deal in the Software without restriction, including without limitation
8
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
+and/or sell copies of the Software, and to permit persons to whom the
10
+Software is furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in
13
+all copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+DEALINGS IN THE SOFTWARE.
22
+*/
23
+
24
+/*-------------------------------------------------------------------------*/
25
+/**
26
+   @file    iniparser.c
27
+   @author  N. Devillard
28
+   @date    Sep 2007
29
+   @version 3.0
30
+   @brief   Parser for ini files.
31
+*/
32
+/*--------------------------------------------------------------------------*/
33
+/*
34
+    Id: iniparser.c,v 2.18 2008-01-03 18:35:39 ndevilla Exp
35
+    Revision: 2.18
36
+    Date: 2008-01-03 18:35:39
37
+*/
38
+/*---------------------------- Includes ------------------------------------*/
39
+#include <ctype.h>
40
+#include "iniparser.h"
41
+
42
+/*---------------------------- Defines -------------------------------------*/
43
+#define ASCIILINESZ         (1024)
44
+#define INI_INVALID_KEY     ((char*)-1)
45
+#define MSG_PREFIX          "confparser: "
46
+
47
+/*---------------------------------------------------------------------------
48
+                        Private to this module
49
+ ---------------------------------------------------------------------------*/
50
+/**
51
+ * This enum stores the status for each parsed line (internal use only).
52
+ */
53
+typedef enum _line_status_ {
54
+    LINE_UNPROCESSED,
55
+    LINE_ERROR,
56
+    LINE_EMPTY,
57
+    LINE_COMMENT,
58
+    LINE_SECTION,
59
+    LINE_VALUE
60
+} line_status ;
61
+
62
+/*-------------------------------------------------------------------------*/
63
+/**
64
+  @brief	Convert a string to lowercase.
65
+  @param	s	String to convert.
66
+  @return	ptr to statically allocated string.
67
+
68
+  This function returns a pointer to a statically allocated string
69
+  containing a lowercased version of the input string. Do not free
70
+  or modify the returned string! Since the returned string is statically
71
+  allocated, it will be modified at each function call (not re-entrant).
72
+ */
73
+/*--------------------------------------------------------------------------*/
74
+static char * strlwc(const char * s)
75
+{
76
+    static char l[ASCIILINESZ+1];
77
+    int i ;
78
+
79
+    if (s==NULL) return NULL ;
80
+    memset(l, 0, ASCIILINESZ+1);
81
+    i=0 ;
82
+    while (s[i] && i<ASCIILINESZ) {
83
+        l[i] = (char)tolower((int)s[i]);
84
+        i++ ;
85
+    }
86
+    l[ASCIILINESZ]=(char)0;
87
+    return l ;
88
+}
89
+
90
+/*-------------------------------------------------------------------------*/
91
+/**
92
+  @brief	Remove blanks at the beginning and the end of a string.
93
+  @param	s	String to parse.
94
+  @return	ptr to statically allocated string.
95
+
96
+  This function returns a pointer to a statically allocated string,
97
+  which is identical to the input string, except that all blank
98
+  characters at the end and the beg. of the string have been removed.
99
+  Do not free or modify the returned string! Since the returned string
100
+  is statically allocated, it will be modified at each function call
101
+  (not re-entrant).
102
+ */
103
+/*--------------------------------------------------------------------------*/
104
+static char * strstrip(char * s)
105
+{
106
+    static char l[ASCIILINESZ+1];
107
+	char * last ;
108
+	
109
+    if (s==NULL) return NULL ;
110
+    
111
+	while (isspace((int)*s) && *s) s++;
112
+	memset(l, 0, ASCIILINESZ+1);
113
+	strcpy(l, s);
114
+	last = l + strlen(l);
115
+	while (last > l) {
116
+		if (!isspace((int)*(last-1)))
117
+			break ;
118
+		last -- ;
119
+	}
120
+	*last = (char)0;
121
+	return (char*)l ;
122
+}
123
+
124
+/*-------------------------------------------------------------------------*/
125
+/**
126
+  @brief    Get number of sections in a dictionary
127
+  @param    d   Dictionary to examine
128
+  @return   int Number of sections found in dictionary
129
+
130
+  This function returns the number of sections found in a dictionary.
131
+  The test to recognize sections is done on the string stored in the
132
+  dictionary: a section name is given as "section" whereas a key is
133
+  stored as "section:key", thus the test looks for entries that do not
134
+  contain a colon.
135
+
136
+  This clearly fails in the case a section name contains a colon, but
137
+  this should simply be avoided.
138
+
139
+  This function returns -1 in case of error.
140
+ */
141
+/*--------------------------------------------------------------------------*/
142
+int iniparser_getnsec(dictionary * d)
143
+{
144
+    int i ;
145
+    int nsec ;
146
+
147
+    if (d==NULL) return -1 ;
148
+    nsec=0 ;
149
+    for (i=0 ; i<d->size ; i++) {
150
+        if (d->key[i]==NULL)
151
+            continue ;
152
+        if (strchr(d->key[i], ':')==NULL) {
153
+            nsec ++ ;
154
+        }
155
+    }
156
+    return nsec ;
157
+}
158
+
159
+/*-------------------------------------------------------------------------*/
160
+/**
161
+  @brief    Get name for section n in a dictionary.
162
+  @param    d   Dictionary to examine
163
+  @param    n   Section number (from 0 to nsec-1).
164
+  @return   Pointer to char string
165
+
166
+  This function locates the n-th section in a dictionary and returns
167
+  its name as a pointer to a string statically allocated inside the
168
+  dictionary. Do not free or modify the returned string!
169
+
170
+  This function returns NULL in case of error.
171
+ */
172
+/*--------------------------------------------------------------------------*/
173
+char * iniparser_getsecname(dictionary * d, int n)
174
+{
175
+    int i ;
176
+    int foundsec ;
177
+
178
+    if (d==NULL || n<0) return NULL ;
179
+    foundsec=0 ;
180
+    for (i=0 ; i<d->size ; i++) {
181
+        if (d->key[i]==NULL)
182
+            continue ;
183
+        if (strchr(d->key[i], ':')==NULL) {
184
+            foundsec++ ;
185
+            if (foundsec>n)
186
+                break ;
187
+        }
188
+    }
189
+    if (foundsec<=n) {
190
+        return NULL ;
191
+    }
192
+    return d->key[i] ;
193
+}
194
+
195
+/*-------------------------------------------------------------------------*/
196
+/**
197
+  @brief    Dump a dictionary to an opened file pointer.
198
+  @param    d   Dictionary to dump.
199
+  @param    f   Opened file pointer to dump to.
200
+  @return   void
201
+
202
+  This function prints out the contents of a dictionary, one element by
203
+  line, onto the provided file pointer. It is OK to specify @c stderr
204
+  or @c stdout as output files. This function is meant for debugging
205
+  purposes mostly.
206
+ */
207
+/*--------------------------------------------------------------------------*/
208
+void iniparser_dump(dictionary * d, FILE * f)
209
+{
210
+    int     i ;
211
+
212
+    if (d==NULL || f==NULL) return ;
213
+    for (i=0 ; i<d->size ; i++) {
214
+        if (d->key[i]==NULL)
215
+            continue ;
216
+        if (d->val[i]!=NULL) {
217
+            fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
218
+        } else {
219
+            fprintf(f, "[%s]=UNDEF\n", d->key[i]);
220
+        }
221
+    }
222
+    return ;
223
+}
224
+
225
+/*-------------------------------------------------------------------------*/
226
+/**
227
+  @brief    Save a dictionary to a loadable ini file
228
+  @param    d   Dictionary to dump
229
+  @param    f   Opened file pointer to dump to
230
+  @return   void
231
+
232
+  This function dumps a given dictionary into a loadable ini file.
233
+  It is Ok to specify @c stderr or @c stdout as output files.
234
+ */
235
+/*--------------------------------------------------------------------------*/
236
+void iniparser_dump_ini(dictionary * d, FILE * f)
237
+{
238
+    int     i, j ;
239
+    char    keym[ASCIILINESZ+1];
240
+    int     nsec ;
241
+    char *  secname ;
242
+    int     seclen ;
243
+
244
+    if (d==NULL || f==NULL) return ;
245
+
246
+    nsec = iniparser_getnsec(d);
247
+    if (nsec<1) {
248
+        /* No section in file: dump all keys as they are */
249
+        for (i=0 ; i<d->size ; i++) {
250
+            if (d->key[i]==NULL)
251
+                continue ;
252
+            fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
253
+        }
254
+        return ;
255
+    }
256
+    for (i=0 ; i<nsec ; i++) {
257
+        secname = iniparser_getsecname(d, i) ;
258
+        seclen  = (int)strlen(secname);
259
+        fprintf(f, "\n[%s]\n", secname);
260
+        sprintf(keym, "%s:", secname);
261
+        for (j=0 ; j<d->size ; j++) {
262
+            if (d->key[j]==NULL)
263
+                continue ;
264
+            if (!strncmp(d->key[j], keym, seclen+1)) {
265
+                fprintf(f,
266
+                        "%-30s = %s\n",
267
+                        d->key[j]+seclen+1,
268
+                        d->val[j] ? d->val[j] : "");
269
+            }
270
+        }
271
+    }
272
+    fprintf(f, "\n");
273
+    return ;
274
+}
275
+
276
+/*-------------------------------------------------------------------------*/
277
+/**
278
+  @brief    Get the string associated to a key
279
+  @param    d       Dictionary to search
280
+  @param    key     Key string to look for
281
+  @param    def     Default value to return if key not found.
282
+  @return   pointer to statically allocated character string
283
+
284
+  This function queries a dictionary for a key. A key as read from an
285
+  ini file is given as "section:key". If the key cannot be found,
286
+  the pointer passed as 'def' is returned.
287
+  The returned char pointer is pointing to a string allocated in
288
+  the dictionary, do not free or modify it.
289
+ */
290
+/*--------------------------------------------------------------------------*/
291
+char * iniparser_getstring(dictionary * d, const char * key, char * def)
292
+{
293
+    char * lc_key ;
294
+    char * sval ;
295
+
296
+    if (d==NULL || key==NULL)
297
+        return def ;
298
+
299
+    lc_key = strlwc(key);
300
+    sval = dictionary_get(d, lc_key, def);
301
+    return sval ;
302
+}
303
+
304
+/*-------------------------------------------------------------------------*/
305
+/**
306
+  @brief    Get the string associated to a key, convert to an int
307
+  @param    d Dictionary to search
308
+  @param    key Key string to look for
309
+  @param    notfound Value to return in case of error
310
+  @return   integer
311
+
312
+  This function queries a dictionary for a key. A key as read from an
313
+  ini file is given as "section:key". If the key cannot be found,
314
+  the notfound value is returned.
315
+
316
+  Supported values for integers include the usual C notation
317
+  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
318
+  are supported. Examples:
319
+
320
+  "42"      ->  42
321
+  "042"     ->  34 (octal -> decimal)
322
+  "0x42"    ->  66 (hexa  -> decimal)
323
+
324
+  Warning: the conversion may overflow in various ways. Conversion is
325
+  totally outsourced to strtol(), see the associated man page for overflow
326
+  handling.
327
+
328
+  Credits: Thanks to A. Becker for suggesting strtol()
329
+ */
330
+/*--------------------------------------------------------------------------*/
331
+int iniparser_getint(dictionary * d, const char * key, int notfound)
332
+{
333
+    char    *   str ;
334
+
335
+    str = iniparser_getstring(d, key, INI_INVALID_KEY);
336
+    if (str==INI_INVALID_KEY) return notfound ;
337
+    return (int)strtol(str, NULL, 0);
338
+}
339
+
340
+/*-------------------------------------------------------------------------*/
341
+/**
342
+  @brief    Get the string associated to a key, convert to a double
343
+  @param    d Dictionary to search
344
+  @param    key Key string to look for
345
+  @param    notfound Value to return in case of error
346
+  @return   double
347
+
348
+  This function queries a dictionary for a key. A key as read from an
349
+  ini file is given as "section:key". If the key cannot be found,
350
+  the notfound value is returned.
351
+ */
352
+/*--------------------------------------------------------------------------*/
353
+double iniparser_getdouble(dictionary * d, char * key, double notfound)
354
+{
355
+    char    *   str ;
356
+
357
+    str = iniparser_getstring(d, key, INI_INVALID_KEY);
358
+    if (str==INI_INVALID_KEY) return notfound ;
359
+    return atof(str);
360
+}
361
+
362
+/*-------------------------------------------------------------------------*/
363
+/**
364
+  @brief    Get the string associated to a key, convert to a boolean
365
+  @param    d Dictionary to search
366
+  @param    key Key string to look for
367
+  @param    notfound Value to return in case of error
368
+  @return   integer
369
+
370
+  This function queries a dictionary for a key. A key as read from an
371
+  ini file is given as "section:key". If the key cannot be found,
372
+  the notfound value is returned.
373
+
374
+  A true boolean is found if one of the following is matched:
375
+
376
+  - A string starting with 'y'
377
+  - A string starting with 'Y'
378
+  - A string starting with 't'
379
+  - A string starting with 'T'
380
+  - A string starting with '1'
381
+
382
+  A false boolean is found if one of the following is matched:
383
+
384
+  - A string starting with 'n'
385
+  - A string starting with 'N'
386
+  - A string starting with 'f'
387
+  - A string starting with 'F'
388
+  - A string starting with '0'
389
+
390
+  The notfound value returned if no boolean is identified, does not
391
+  necessarily have to be 0 or 1.
392
+ */
393
+/*--------------------------------------------------------------------------*/
394
+int iniparser_getboolean(dictionary * d, const char * key, int notfound)
395
+{
396
+    char    *   c ;
397
+    int         ret ;
398
+
399
+    c = iniparser_getstring(d, key, INI_INVALID_KEY);
400
+    if (c==INI_INVALID_KEY) return notfound ;
401
+    if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
402
+        ret = 1 ;
403
+    } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
404
+        ret = 0 ;
405
+    } else {
406
+        ret = notfound ;
407
+    }
408
+    return ret;
409
+}
410
+
411
+/*-------------------------------------------------------------------------*/
412
+/**
413
+  @brief    Finds out if a given entry exists in a dictionary
414
+  @param    ini     Dictionary to search
415
+  @param    entry   Name of the entry to look for
416
+  @return   integer 1 if entry exists, 0 otherwise
417
+
418
+  Finds out if a given entry exists in the dictionary. Since sections
419
+  are stored as keys with NULL associated values, this is the only way
420
+  of querying for the presence of sections in a dictionary.
421
+ */
422
+/*--------------------------------------------------------------------------*/
423
+int iniparser_find_entry(
424
+    dictionary  *   ini,
425
+    char        *   entry
426
+)
427
+{
428
+    int found=0 ;
429
+    if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
430
+        found = 1 ;
431
+    }
432
+    return found ;
433
+}
434
+
435
+/*-------------------------------------------------------------------------*/
436
+/**
437
+  @brief    Set an entry in a dictionary.
438
+  @param    ini     Dictionary to modify.
439
+  @param    entry   Entry to modify (entry name)
440
+  @param    val     New value to associate to the entry.
441
+  @return   int 0 if Ok, -1 otherwise.
442
+
443
+  If the given entry can be found in the dictionary, it is modified to
444
+  contain the provided value. If it cannot be found, -1 is returned.
445
+  It is Ok to set val to NULL.
446
+ */
447
+/*--------------------------------------------------------------------------*/
448
+int iniparser_set(dictionary * ini, char * entry, char * val)
449
+{
450
+    return dictionary_set(ini, strlwc(entry), val) ;
451
+}
452
+
453
+/*-------------------------------------------------------------------------*/
454
+/**
455
+  @brief    Delete an entry in a dictionary
456
+  @param    ini     Dictionary to modify
457
+  @param    entry   Entry to delete (entry name)
458
+  @return   void
459
+
460
+  If the given entry can be found, it is deleted from the dictionary.
461
+ */
462
+/*--------------------------------------------------------------------------*/
463
+void iniparser_unset(dictionary * ini, char * entry)
464
+{
465
+    dictionary_unset(ini, strlwc(entry));
466
+}
467
+
468
+/*-------------------------------------------------------------------------*/
469
+/**
470
+  @brief	Load a single line from an INI file
471
+  @param    input_line  Input line, may be concatenated multi-line input
472
+  @param    section     Output space to store section
473
+  @param    key         Output space to store key
474
+  @param    value       Output space to store value
475
+  @return   line_status value
476
+ */
477
+/*--------------------------------------------------------------------------*/
478
+static line_status iniparser_line(
479
+    char * input_line,
480
+    char * section,
481
+    char * key,
482
+    char * value)
483
+{   
484
+    line_status sta ;
485
+    char        line[ASCIILINESZ+1];
486
+    int         len ;
487
+
488
+    strcpy(line, strstrip(input_line));
489
+    len = (int)strlen(line);
490
+
491
+    sta = LINE_UNPROCESSED ;
492
+    if (len<1) {
493
+        /* Empty line */
494
+        sta = LINE_EMPTY ;
495
+    } else if (line[0]=='#') {
496
+        /* Comment line */
497
+        sta = LINE_COMMENT ; 
498
+    } else if (line[0]=='[' && line[len-1]==']') {
499
+        /* Section name */
500
+        sscanf(line, "[%[^]]", section);
501
+        strcpy(section, strstrip(section));
502
+        strcpy(section, strlwc(section));
503
+        sta = LINE_SECTION ;
504
+    } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
505
+           ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2
506
+           ||  sscanf (line, "%[^=] = %[^;#]",     key, value) == 2) {
507
+        /* Usual key=value, with or without comments */
508
+        strcpy(key, strstrip(key));
509
+        strcpy(key, strlwc(key));
510
+        strcpy(value, strstrip(value));
511
+        /*
512
+         * sscanf cannot handle '' or "" as empty values
513
+         * this is done here
514
+         */
515
+        if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
516
+            value[0]=0 ;
517
+        }
518
+        sta = LINE_VALUE ;
519
+    } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
520
+           ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
521
+        /*
522
+         * Special cases:
523
+         * key=
524
+         * key=;
525
+         * key=#
526
+         */
527
+        strcpy(key, strstrip(key));
528
+        strcpy(key, strlwc(key));
529
+        value[0]=0 ;
530
+        sta = LINE_VALUE ;
531
+    } else {
532
+        /* Generate syntax error */
533
+        sta = LINE_ERROR ;
534
+    }
535
+    return sta ;
536
+}
537
+
538
+/*-------------------------------------------------------------------------*/
539
+/**
540
+  @brief    Parse an ini file and return an allocated dictionary object
541
+  @param    ininame Name of the ini file to read.
542
+  @return   Pointer to newly allocated dictionary
543
+
544
+  This is the parser for ini files. This function is called, providing
545
+  the name of the file to be read. It returns a dictionary object that
546
+  should not be accessed directly, but through accessor functions
547
+  instead.
548
+
549
+  The returned dictionary must be freed using iniparser_freedict().
550
+ */
551
+/*--------------------------------------------------------------------------*/
552
+dictionary * iniparser_load(const char * ininame)
553
+{
554
+    FILE * in ;
555
+
556
+    char line    [ASCIILINESZ+1] ;
557
+    char section [ASCIILINESZ+1] ;
558
+    char key     [ASCIILINESZ+1] ;
559
+    char tmp     [ASCIILINESZ+1] ;
560
+    char val     [ASCIILINESZ+1] ;
561
+
562
+    int  last=0 ;
563
+    int  len ;
564
+    int  lineno=0 ;
565
+    int  errs=0;
566
+
567
+    dictionary * dict ;
568
+
569
+    if ((in=fopen(ininame, "r"))==NULL) {
570
+        fprintf(stderr, MSG_PREFIX "cannot open %s\n", ininame);
571
+        return NULL ;
572
+    }
573
+
574
+    dict = dictionary_new(0) ;
575
+    if (!dict) {
576
+        fclose(in);
577
+        return NULL ;
578
+    }
579
+
580
+    memset(line,    0, ASCIILINESZ);
581
+    memset(section, 0, ASCIILINESZ);
582
+    memset(key,     0, ASCIILINESZ);
583
+    memset(val,     0, ASCIILINESZ);
584
+    last=0 ;
585
+
586
+    while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
587
+        lineno++ ;
588
+        len = (int)strlen(line)-1;
589
+        /* Safety check against buffer overflows */
590
+        if (line[len]!='\n') {
591
+            fprintf(stderr,
592
+                    MSG_PREFIX "input line too long in %s (%d)\n",
593
+                    ininame,
594
+                    lineno);
595
+            dictionary_del(dict);
596
+            fclose(in);
597
+            return NULL ;
598
+        }
599
+        /* Get rid of \n and spaces at end of line */
600
+        while ((len>=0) &&
601
+                ((line[len]=='\n') || (isspace(line[len])))) {
602
+            line[len]=0 ;
603
+            len-- ;
604
+        }
605
+        /* Detect multi-line */
606
+        if (line[len]=='\\') {
607
+            /* Multi-line value */
608
+            last=len ;
609
+            continue ;
610
+        } else {
611
+            last=0 ;
612
+        }
613
+        switch (iniparser_line(line, section, key, val)) {
614
+            case LINE_EMPTY:
615
+            case LINE_COMMENT:
616
+            break ;
617
+
618
+            case LINE_SECTION:
619
+            errs = dictionary_set(dict, section, NULL);
620
+            break ;
621
+
622
+            case LINE_VALUE:
623
+            sprintf(tmp, "%s:%s", section, key);
624
+            errs = dictionary_set(dict, tmp, val) ;
625
+            break ;
626
+
627
+            case LINE_ERROR:
628
+            fprintf(stderr, MSG_PREFIX "syntax error in %s (%d):\n",
629
+                    ininame,
630
+                    lineno);
631
+            fprintf(stderr, "-> %s\n", line);
632
+            errs++ ;
633
+            break;
634
+
635
+            default:
636
+            break ;
637
+        }
638
+        memset(line, 0, ASCIILINESZ);
639
+        last=0;
640
+        if (errs<0) {
641
+            fprintf(stderr, MSG_PREFIX "memory allocation failure\n");
642
+            break ;
643
+        }
644
+    }
645
+    if (errs) {
646
+        dictionary_del(dict);
647
+        dict = NULL ;
648
+    }
649
+    fclose(in);
650
+    return dict ;
651
+}
652
+
653
+/*-------------------------------------------------------------------------*/
654
+/**
655
+  @brief    Free all memory associated to an ini dictionary
656
+  @param    d Dictionary to free
657
+  @return   void
658
+
659
+  Free all memory associated to an ini dictionary.
660
+  It is mandatory to call this function before the dictionary object
661
+  gets out of the current context.
662
+ */
663
+/*--------------------------------------------------------------------------*/
664
+void iniparser_freedict(dictionary **d)
665
+{
666
+	if (*d) {
667
+		dictionary_del(*d);
668
+		*d = NULL;
669
+	}
670
+}
671
+
672
+/* vim: set ts=4 et sw=4 tw=75 */
673
+#include <stdarg.h>
674
+
675
+#define __handle_format \
676
+	char key[128]; \
677
+	{ \
678
+		va_list args; \
679
+		va_start(args, keyfmt); \
680
+		vsnprintf(key, sizeof(key)-1, keyfmt, args); \
681
+		va_end(args); \
682
+	}
683