First running version

This commit is contained in:
Christoph Haas 2018-04-06 17:04:17 +02:00
parent 4b6daded8e
commit ef6408c5a6
10 changed files with 961 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
gclogger

30
Makefile Normal file
View File

@ -0,0 +1,30 @@
EXE = gclogger
SRC_DIR = src
OBJ_DIR = obj
SRC = $(wildcard $(SRC_DIR)/*.c)
OBJ = $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
CPPFLAGS += -Iinclude
CFLAGS += -Wall -Wextra -Os
#LDFLAGS += -Llib
LDLIBS += -lcurl
.PHONY: all clean
all: $(EXE)
# Linking:
$(EXE): $(OBJ)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
# Compiling:
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# Cleaning:
clean:
$(RM) $(OBJ)
$(RM) $(EXE)

45
gclogger.example.ini Normal file
View File

@ -0,0 +1,45 @@
;
; Geiger Counter Logger - configuration file
;
; Serial device filename
; ------------------------
[device]
port=/dev/ttyUSB0
baud=115200
; GPS location of your sensor
; -----------------------------
location=Innsbruck, AT
latitude=47.2530
longitude=11.3969
; Polling interval in seconds (60..3600)
; ----------------------------------------
interval=60
;
; Log portal configurations
;
; Uncomment sections to enable them
;
; Custom REST logging portal
; ----------------------------------------------
[custlog]
;id=example_device_id
;url=https://api.mygclog.com/log
;param_id=id
;param_cpm=cpm
;param_temp=temp
;param_lng=lng
;param_lat=lat
;param_loc=loc
;param_version=version
;param_time=captured_at
; Log to CSV file
; ----------------------------------------------
[csv]
;path=/tmp/gclog.csv

54
include/gclogger.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef _GCLOGGER_H_
#define _GCLOGGER_H_
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdbool.h>
#include <curl/curl.h>
#include "ini.h"
#include "gmc.h"
#define GCLOGGER_VERSION "0.1"
typedef struct
{
bool debug;
char* dev_port;
int dev_baud;
char* dev_location;
float dev_latitude;
float dev_longitude;
int dev_interval;
char* radmon_user;
char* radmon_pass;
char* safecast_key;
char* safecast_device;
char* netc_id;
char* custlog_url;
char* custlog_id;
char* custlog_param_id;
char* custlog_param_cpm;
char* custlog_param_temp;
char* custlog_param_lng;
char* custlog_param_lat;
char* custlog_param_loc;
char* custlog_param_version;
char* custlog_param_time;
char* csv_path;
} configuration;
static int confighandler(void* config, const char* section, const char* name, const char* value);
bool str_isset(char *str);
void init_configuration(configuration* config);
void signal_handler(int sig);
bool send_custlog(const configuration config, int cpm, float temperature, const char *version, struct tm *tm);
bool send_tocsv(const configuration config, int cpm, float temperature, const char *version, const struct tm *tm);
void show_usage();
#endif

26
include/gmc.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef _GC_GMC_H_
#define _GC_GMC_H_
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdbool.h>
// GQ Geiger Counter Communication Protocol: http://www.gqelectronicsllc.com/download/GQ-RFC1201.txt
int gmc_open(const char *device, int baud);
void gmc_close(int device);
int gmc_get_cpm(int device);
float gmc_get_temperature(int device);
int gmc_get_version(int device, char *buf);
bool gmc_set_heartbeat_off(int device);
int gmc_write(int device, const char *cmd);
int gmc_read(int device, char *buf, int length);
bool gmc_flush(int device);
#endif

130
include/ini.h Normal file
View File

@ -0,0 +1,130 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* __INI_H__ */

4
obj/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

300
src/gclogger.c Normal file
View File

@ -0,0 +1,300 @@
#include "gclogger.h"
extern char *optarg;
extern int optind;
static volatile bool running = true;
static int confighandler(void* config, const char* section, const char* name, const char* value) {
configuration* pconfig = (configuration*)config;
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
if (MATCH("device", "port")) {
pconfig->dev_port = strdup(value);
} else if (MATCH("device", "baud")) {
pconfig->dev_baud = atoi(value);
} else if (MATCH("device", "location")) {
pconfig->dev_location = strdup(value);
} else if (MATCH("device", "latitude")) {
pconfig->dev_latitude = atof(value);
} else if (MATCH("device", "longitude")) {
pconfig->dev_longitude = atof(value);
} else if (MATCH("device", "interval")) {
pconfig->dev_interval = atoi(value);
} else if (MATCH("custlog", "url")) {
pconfig->custlog_url = strdup(value);
} else if (MATCH("custlog", "id")) {
pconfig->custlog_id = strdup(value);
} else if (MATCH("custlog", "param_id")) {
pconfig->custlog_param_id = strdup(value);
} else if (MATCH("custlog", "param_cpm")) {
pconfig->custlog_param_cpm = strdup(value);
} else if (MATCH("custlog", "param_temp")) {
pconfig->custlog_param_temp = strdup(value);
} else if (MATCH("custlog", "param_lng")) {
pconfig->custlog_param_lng = strdup(value);
} else if (MATCH("custlog", "param_lat")) {
pconfig->custlog_param_lat = strdup(value);
} else if (MATCH("custlog", "param_loc")) {
pconfig->custlog_param_loc = strdup(value);
} else if (MATCH("custlog", "param_version")) {
pconfig->custlog_param_version = strdup(value);
} else if (MATCH("custlog", "param_time")) {
pconfig->custlog_param_time = strdup(value);
} else if (MATCH("csv", "path")) {
pconfig->csv_path = strdup(value);
} else {
return 0; /* unknown section/name, error */
}
return 1;
}
bool str_isset(char *str) {
return str != NULL && str[0] != '\0';
}
void init_configuration(configuration* config) {
config->debug = false;
config->dev_port = "";
config->dev_baud = 115200;
config->dev_location = "";
config->dev_latitude = 0.0;
config->dev_longitude = 0.0;
config->dev_interval = 60;
config->custlog_url = "";
config->custlog_id = "";
config->custlog_param_id = "id";
config->custlog_param_cpm = "cpm";
config->custlog_param_temp = "temp";
config->custlog_param_lng = "lng";
config->custlog_param_lat = "lat";
config->custlog_param_loc = "loc";
config->custlog_param_version = "version";
config->custlog_param_time = "time";
config->csv_path = "";
}
void signal_handler(int sig) {
switch (sig) {
case SIGTERM:
case SIGINT:
case SIGQUIT:
case SIGHUP:
running = false;
default:
break;
}
}
bool send_custlog(const configuration config, int cpm, float temperature, const char *version, struct tm *tm) {
CURL *curl;
CURLcode res;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
char *url_buffer;
size_t url_size;
// first get size of final url
url_size = snprintf(NULL, 0, "%s?%s=%s&%s=%d&%s=%f&%s=%s&%s=%ld&%s=%f&%s=%f&%s=%s",
config.custlog_url,
config.custlog_param_id, config.custlog_id,
config.custlog_param_cpm, cpm,
config.custlog_param_temp, temperature,
config.custlog_param_version, version,
config.custlog_param_time, mktime(tm),
config.custlog_param_lng, config.dev_longitude,
config.custlog_param_lat, config.dev_latitude,
config.custlog_param_loc, config.dev_location);
// now allocate buffer and build url
url_buffer = (char *)malloc(url_size + 1);
snprintf(url_buffer, url_size+1,"%s?%s=%s&%s=%d&%s=%f&%s=%s&%s=%ld&%s=%f&%s=%f&%s=%s",
config.custlog_url,
config.custlog_param_id, config.custlog_id,
config.custlog_param_cpm, cpm,
config.custlog_param_temp, temperature,
config.custlog_param_version, version,
config.custlog_param_time, mktime(tm),
config.custlog_param_lng, config.dev_longitude,
config.custlog_param_lat, config.dev_latitude,
config.custlog_param_loc, config.dev_location);
printf("final url: %s\n", url_buffer);
curl_easy_setopt(curl, CURLOPT_URL, url_buffer);
#ifdef SKIP_PEER_VERIFICATION
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
#endif
#ifdef SKIP_HOSTNAME_VERIFICATION
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
res = curl_easy_perform(curl);
if(res != CURLE_OK)
printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
else
printf("curl_easy_perform() ok!");
free(url_buffer);
curl_easy_cleanup(curl);
return true;
}
return false;
}
bool send_tocsv(const configuration config, int cpm, float temperature, const char *version, const struct tm *tm) {
FILE *fp;
if ((fp = fopen(config.csv_path, "a")) == NULL) {
printf("Error while opening the csv file '%s'.\n", config.csv_path);
return false;
}
char *ts = asctime(tm);
ts[strlen(ts) - 1] = 0; // get rid of \n
fprintf(fp, "%s,%d,%07.3f,%s\n", ts, cpm, temperature, version);
fclose(fp);
return true;
}
void show_usage() {
printf("Geiger Counter Logger\n");
printf("Version %s\n", GCLOGGER_VERSION);
printf("Copyright (C) 2018 Christoph Haas, christoph.h@sprinternet.at\n\n");
printf("This program comes with ABSOLUTELY NO WARRANTY.\n");
printf("This is free software, and you are welcome to redistribute it.\n\n");
printf("Usage: gclogger -c <file> [-d]\n");
printf(" -c <file> configuration file path\n");
printf(" -d enable verbose mode\n\n");
}
int main(int argc, char *argv[]) {
int opt = 0;
int gc_fd = -1;
configuration config;
// setup signal handlers
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);
signal(SIGHUP, signal_handler);
// parse config file
init_configuration(&config);
while ((opt = getopt(argc, argv, "c:d")) != -1) {
switch (opt) {
case 'd':
config.debug = true;
break;
case 'c':
if (ini_parse(optarg, confighandler, &config) < 0) {
printf("Can't load config file '%s'\n", optarg);
return 1;
}
printf("Config loaded from '%s': device_port=%s, interval=%d\n",
optarg, config.dev_port, config.dev_interval);
break;
default: /* '?' */
show_usage();
exit(EXIT_FAILURE);
}
}
// additional check if extra/no arguments where given
if (optind < argc || argc == 1) {
show_usage();
exit(EXIT_FAILURE);
}
// check if device port is set
if(!str_isset(config.dev_port)) {
printf("Device port must be set!'\n");
exit(EXIT_FAILURE);
}
gc_fd = gmc_open(config.dev_port, config.dev_baud);
// check if device connection was successful
if(gc_fd == -1) {
printf("Connection to device (%s) failed!'\n", config.dev_port);
exit(EXIT_FAILURE);
}
// read version
char version[15] = { 0 };
if(gmc_get_version(gc_fd, version) == -1) {
printf("Unable to read Geiger counter version!'\n");
exit(EXIT_FAILURE);
}
if (config.debug) {
printf("GC VERSION: %s\n", version);
}
time_t last = time(NULL);
int cpm, sum = 0, count = 0, tcount = 0;
float temperature, tsum = 0;
// main loop
while (running) {
// read cpm
if ((cpm = gmc_get_cpm(gc_fd)) > 0) {
sum += cpm;
count++;
}
// read temperature
tsum += gmc_get_temperature(gc_fd);
tcount++;
if (difftime(time(NULL), last) >= config.dev_interval) {
if (count > 0) {
struct tm *tm = gmtime(&last);
cpm = sum / count;
temperature = tsum / tcount;
if (config.debug) {
printf("CPM: %d (= %d/%d), TEMP: %07.3f, Timestamp: %s\n", cpm, sum, count, temperature, asctime(tm));
}
// log to custom REST api
if (str_isset(config.custlog_url)) {
printf("Uploading to %s.\n", config.custlog_url);
if(!send_custlog(config, cpm, temperature, version, tm)) {
printf("Upload to %s failed.\n", config.custlog_url);
}
}
// log to csv
if (str_isset(config.csv_path)) {
if(!send_tocsv(config, cpm, temperature, version, tm)) {
printf("Logging to %s failed.\n", config.csv_path);
}
}
time(&last);
sum = tsum = count = tcount = 0;
} else {
printf("Reading ZERO value from Geiger tube.\n");
}
}
sleep(1); // sleep one second
}
gmc_close(gc_fd);
return EXIT_SUCCESS;
}

102
src/gmc.c Normal file
View File

@ -0,0 +1,102 @@
#include "gmc.h"
int gmc_open(const char *device, int baud) {
int gc_fd = -1;
struct termios tio;
memset(&tio, 0, sizeof(struct termios));
tio.c_cflag = CS8 | CREAD | CLOCAL; // 8n1
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 5;
int tio_baud = B115200;
switch(baud) {
case 9600: tio_baud = B9600; break;
case 19200: tio_baud = B19200; break;
case 38400: tio_baud = B38400; break;
case 57600: tio_baud = B57600; break;
case 115200: tio_baud = B115200; break;
}
if ((gc_fd = open(device, O_RDWR)) != -1) {
if (cfsetspeed(&tio, tio_baud) == 0) { // set baud speed
if (tcsetattr(gc_fd, TCSANOW, &tio) == 0) { // apply baud speed change
if (gmc_set_heartbeat_off(gc_fd)) { // disable heartbeat, we use polling
return gc_fd;
}
}
} else {
// something failed
close(gc_fd);
gc_fd = -1;
}
}
return gc_fd;
}
void gmc_close(int device) {
close(device);
}
int gmc_get_cpm(int device) {
const char cmd[] = "<GETCPM>>";
char buf[2] = { 0 };
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
gmc_read(device, buf, 2);
return buf[0] * 256 + buf[1];
}
float gmc_get_temperature(int device) {
const char cmd[] = "<GETTEMP>>";
char buf[4] = { 0 };
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
gmc_read(device, buf, 4);
int sign = buf[2] == 0 ? 1 : -1;
float temp = buf[0];
temp += (float) buf[1] / 10;
temp = temp * sign;
return temp;
}
int gmc_get_version(int device, char *version) {
const char cmd[] = "<GETVER>>";
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
return gmc_read(device, version, 14);
return -1;
}
bool gmc_set_heartbeat_off(int device) {
const char cmd[] = "<HEARTBEAT0>>";
if (gmc_write(device, cmd) == (ssize_t) strlen(cmd))
return gmc_flush(device);
return false;
}
int gmc_write(int device, const char *cmd) {
return write(device, cmd, strlen(cmd));
}
int gmc_read(int device, char *buf, int length) {
return read(device, buf, length);
}
bool gmc_flush(int device) {
char ch;
// flush input stream (max 100 bytes)
for (int i = 0; i < 100; i++) {
if (read(device, &ch, 1) == 0)
return true;
}
return false;
}

269
src/ini.c Normal file
View File

@ -0,0 +1,269 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
int max_line = INI_MAX_LINE;
#else
char* line;
int max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC
char* new_line;
int offset;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, max_line, stream) != NULL) {
#if INI_ALLOW_REALLOC
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = realloc(line, max_line);
if (!new_line) {
free(line);
return -2;
}
line = new_line;
if (reader(line + offset, max_line - offset, stream) == NULL)
break;
if (max_line >= INI_MAX_LINE)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = strlen(string);
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}