vzlogger/src/vzlogger.c

448 lines
10 KiB
C

/**
* Main source file
*
* @package vzlogger
* @copyright Copyright (c) 2011, The volkszaehler.org project
* @license http://www.gnu.org/licenses/gpl.txt GNU Public License
* @author Steffen Vogel <info@steffenvogel.de>
*/
/*
* This file is part of volkzaehler.org
*
* volkzaehler.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* volkzaehler.org is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with volkszaehler.org. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#include <curl/curl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include "list.h"
#include "meter.h"
#include "obis.h"
#include "vzlogger.h"
#include "channel.h"
#include "threads.h"
#ifdef LOCAL_SUPPORT
#include "local.h"
#endif /* LOCAL_SUPPORT */
list_t mappings; /* mapping between meters and channels */
config_options_t options; /* global application options */
/**
* Command line options
*/
const struct option long_options[] = {
{"config", required_argument, 0, 'c'},
{"log", required_argument, 0, 'o'},
{"daemon", required_argument, 0, 'd'},
{"foreground", required_argument, 0, 'f'},
#ifdef LOCAL_SUPPORT
{"httpd", no_argument, 0, 'l'},
{"httpd-port", required_argument, 0, 'p'},
#endif /* LOCAL_SUPPORT */
{"verbose", required_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
{} /* stop condition for iterator */
};
/**
* Descriptions vor command line options
*/
const char *long_options_descs[] = {
"configuration file",
"log file",
"run as periodically",
"do not run in background",
#ifdef LOCAL_SUPPORT
"activate local interface (tiny HTTPd which serves live readings)",
"TCP port for HTTPd",
#endif /* LOCAL_SUPPORT */
"enable verbose output",
"show this help",
"show version of vzlogger",
NULL /* stop condition for iterator */
};
/**
* Print error/debug/info messages to stdout and/or logfile
*
* @param id could be NULL for general messages
* @todo integrate into syslog
*/
void print(int level, const char *format, void *id, ... ) {
va_list args;
if (level > options.verbosity) {
return; /* skip message if its under the verbosity level */
}
struct timeval now;
struct tm * timeinfo;
char buffer[1024];
char *pos = buffer;
gettimeofday(&now, NULL);
timeinfo = localtime(&now.tv_sec);
/* print timestamp to buffer */
pos += sprintf(pos, "[");
pos += strftime(pos, 16, "%b %d %H:%M:%S", timeinfo);
/* print logging 'section' */
pos += (id != NULL) ? sprintf(pos, "][%s]", (char *) id) : sprintf(pos, "]");
/* fill with whitespaces */
while(pos - buffer < 24) {
pos += sprintf(pos, " ");
}
/* print formatstring */
va_start(args, id);
pos += vsprintf(pos, format, args);
va_end(args);
/* print to stdout/stderr */
if (getppid() != 1) {
fprintf((level > 0) ? stdout : stderr, "%s\n", buffer);
}
/* append to logfile */
if (options.logfd) {
fprintf(options.logfd, "%s\n", buffer);
fflush(options.logfd);
}
}
/**
* Print available options, protocols and OBIS aliases
*/
void show_usage(char *argv[]) {
const char **desc = long_options_descs;
const struct option *op = long_options;
printf("Usage: %s [options]\n\n", argv[0]);
/* command line options */
printf(" following options are available:\n");
while (op->name && desc) {
printf("\t-%c, --%-12s\t%s\n", op->val, op->name, *desc);
op++; desc++;
}
/* protocols */
printf("\n following protocol types are supported:\n");
for (const meter_details_t *it = meter_get_protocols(); it->name != NULL; it++) {
printf("\t%-12s\t%s\n", it->name, it->desc);
}
/* obis aliases */
printf("\n following OBIS aliases are available:\n");
char obis_str[OBIS_STR_LEN];
for (const obis_alias_t *it = obis_get_aliases(); it->name != NULL; it++) {
obis_unparse(it->id, obis_str, OBIS_STR_LEN);
printf("\t%-17s%-31s%-22s\n", it->name, it->desc, obis_str);
}
/* footer */
printf("\n%s - volkszaehler.org logging utility\n", PACKAGE_STRING);
printf("by Steffen Vogel <stv0g@0l.de>\n");
printf("send bugreports to %s\n", PACKAGE_BUGREPORT);
}
/**
* Fork process to background
*
* @link http://www.enderunix.org/docs/eng/daemon.php
*/
void daemonize() {
if (getppid() == 1) {
return; /* already a daemon */
}
int i = fork();
if (i < 0) {
exit(EXIT_FAILURE); /* fork error */
}
else if (i > 0) {
exit(EXIT_SUCCESS); /* parent exits */
}
/* child (daemon) continues */
setsid(); /* obtain a new process group */
for (i = getdtablesize(); i >= 0; --i) {
close(i); /* close all descriptors */
}
/* handle standart I/O */
i = open("/dev/null", O_RDWR);
dup(i);
dup(i);
chdir("/"); /* change working directory */
umask(0022);
/* ignore signals from parent tty */
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = SIG_IGN;
sigaction(SIGCHLD, &action, NULL); /* ignore child */
sigaction(SIGTSTP, &action, NULL); /* ignore tty signals */
sigaction(SIGTTOU, &action, NULL);
sigaction(SIGTTIN, &action, NULL);
}
/**
* Cancel threads
*
* Threads gets joined in main()
*/
void quit(int sig) {
print(log_info, "Closing connections to terminate", NULL);
foreach(mappings, mapping, map_t) {
pthread_cancel(mapping->thread);
foreach(mapping->channels, ch, channel_t) {
pthread_cancel(ch->thread);
}
}
}
/**
* Parse options from command line
*
* @param options pointer to structure for options
* @return int 0 on succes, <0 on error
*/
int config_parse_cli(int argc, char * argv[], config_options_t * options) {
while (1) {
int c = getopt_long(argc, argv, "c:o:p:lhVdfv:", long_options, NULL);
/* detect the end of the options. */
if (c == -1) break;
switch (c) {
case 'v':
options->verbosity = atoi(optarg);
break;
#ifdef LOCAL_SUPPORT
case 'l':
options->local = 1;
break;
case 'p': /* port for local interface */
options->port = atoi(optarg);
break;
#endif /* LOCAL_SUPPORT */
case 'd':
options->daemon = 1;
break;
case 'f':
options->foreground = 1;
break;
case 'c': /* config file */
options->config = strdup(optarg);
break;
case 'o': /* log file */
options->log = strdup(optarg);
break;
case 'V':
printf("%s\n", VERSION);
exit(EXIT_SUCCESS);
break;
case '?':
case 'h':
default:
show_usage(argv);
if (c == '?') {
exit(EXIT_FAILURE);
}
else {
exit(EXIT_SUCCESS);
}
}
}
return SUCCESS;
}
/**
* The application entrypoint
*/
int main(int argc, char *argv[]) {
/* default options */
options.config = "/etc/vzlogger.conf";
options.log = "/var/log/vzlogger.log";
options.logfd = NULL;
options.port = 8080;
options.verbosity = 0;
options.comet_timeout = 30;
options.buffer_length = 600;
options.retry_pause = 15;
options.daemon = FALSE;
options.local = FALSE;
options.logging = TRUE;
/* bind signal handler */
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = quit;
sigaction(SIGINT, &action, NULL); /* catch ctrl-c from terminal */
sigaction(SIGHUP, &action, NULL); /* catch hangup signal */
sigaction(SIGTERM, &action, NULL); /* catch kill signal */
/* initialize ADTs and APIs */
curl_global_init(CURL_GLOBAL_ALL);
list_init(&mappings);
/* parse command line and file options */
// TODO command line should have a higher priority as file
if (config_parse_cli(argc, argv, &options) != SUCCESS) {
return EXIT_FAILURE;
}
if (config_parse(options.config, &mappings, &options) != SUCCESS) {
return EXIT_FAILURE;
}
options.logging = (!options.local || options.daemon);
if (!options.foreground && (options.daemon || options.local)) {
print(log_info, "Daemonize process...", NULL);
daemonize();
}
/* open logfile */
if (options.log) {
FILE *logfd = fopen(options.log, "a");
if (logfd == NULL) {
print(log_error, "Cannot open logfile %s: %s", NULL, options.log, strerror(errno));
return EXIT_FAILURE;
}
options.logfd = logfd;
print(log_debug, "Opened logfile %s", NULL, options.log);
}
if (mappings.size <= 0) {
print(log_error, "No meters found!", NULL);
return EXIT_FAILURE;
}
/* open connection meters & start threads */
foreach(mappings, mapping, map_t) {
meter_t *mtr = &mapping->meter;
if (meter_open(mtr) != SUCCESS) {
print(log_error, "Failed to open meter. Aborting.", mtr);
return EXIT_FAILURE;
}
else {
print(log_info, "Meter connection established", mtr);
}
pthread_create(&mapping->thread, NULL, &reading_thread, (void *) mapping);
print(log_debug, "Meter thread started", mtr);
foreach(mapping->channels, ch, channel_t) {
/* set buffer length for perriodic meters */
if (meter_get_details(mtr->protocol)->periodic && options.local) {
ch->buffer.keep = ceil(options.buffer_length / (double) mapping->meter.interval);
}
if (ch->status != status_running && options.logging) {
pthread_create(&ch->thread, NULL, &logging_thread, (void *) ch);
print(log_debug, "Logging thread started", ch);
}
}
}
#ifdef LOCAL_SUPPORT
/* start webserver for local interface */
struct MHD_Daemon *httpd_handle = NULL;
if (options.local) {
print(log_info, "Starting local interface HTTPd on port %i", "http", options.port);
httpd_handle = MHD_start_daemon(
MHD_USE_THREAD_PER_CONNECTION,
options.port,
NULL, NULL,
&handle_request, &mappings,
MHD_OPTION_END
);
}
#endif /* LOCAL_SUPPORT */
/* wait for all threads to terminate */
foreach(mappings, mapping, map_t) {
meter_t *mtr = &mapping->meter;
pthread_join(mapping->thread, NULL);
foreach(mapping->channels, ch, channel_t) {
pthread_join(ch->thread, NULL);
channel_free(ch);
}
list_free(&mapping->channels);
meter_close(mtr); /* closing connection */
}
#ifdef LOCAL_SUPPORT
/* stop webserver */
if (httpd_handle) {
MHD_stop_daemon(httpd_handle);
}
#endif /* LOCAL_SUPPORT */
/* householding */
list_free(&mappings);
curl_global_cleanup();
/* close logfile */
if (options.logfd) {
fclose(options.logfd);
}
return EXIT_SUCCESS;
}