diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 110 | ||||
-rwxr-xr-x | src/configure | 167 | ||||
-rw-r--r-- | src/daemon.h | 168 | ||||
-rw-r--r-- | src/datatypes.h | 41 | ||||
-rw-r--r-- | src/file_list.c | 222 | ||||
-rw-r--r-- | src/file_list.h | 67 | ||||
-rw-r--r-- | src/log.c | 265 | ||||
-rw-r--r-- | src/log.h | 87 | ||||
-rw-r--r-- | src/log_targets.h | 357 | ||||
-rw-r--r-- | src/options.c | 337 | ||||
-rw-r--r-- | src/options.h | 67 | ||||
-rw-r--r-- | src/sig_handler.c | 113 | ||||
-rw-r--r-- | src/sig_handler.h | 39 | ||||
-rw-r--r-- | src/slist.c | 132 | ||||
-rw-r--r-- | src/slist.h | 53 | ||||
-rw-r--r-- | src/string_list.c | 71 | ||||
-rw-r--r-- | src/string_list.h | 43 | ||||
-rw-r--r-- | src/sydra.c | 255 | ||||
-rw-r--r-- | src/sysexec.c | 226 | ||||
-rw-r--r-- | src/sysexec.h | 51 | ||||
-rw-r--r-- | src/writer.c | 243 | ||||
-rw-r--r-- | src/writer.h | 64 |
22 files changed, 3178 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..456b20a --- /dev/null +++ b/src/Makefile @@ -0,0 +1,110 @@ +## +## sydra +## +## sydra is a toolbox which allows you to set up multiple bidirectional +## Video/Audio streams from external locations. +## sydra has been written to be used for the Elevate Festival in Graz +## Austria in order to involve external locations to present themselves +## at the festival. +## Sydra is based on GStreamer and is written in C. +## +## +## Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> +## +## This file is part of sydra. +## +## sydra 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. +## +## sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. +## + +ifneq ($(MAKECMDGOALS),distclean) +include include.mk +endif + +EXECUTABLE := sydra + +C_OBJS := log.o \ + sig_handler.o \ + options.o \ + slist.o \ + string_list.o \ + sysexec.o \ + file_list.o \ + writer.o \ + sydra.o + +C_SRCS := $(C_OBJS:%.o=%.c) + +.PHONY: clean cleanall distclean manpage install install-bin uninstall remove + +all: $(EXECUTABLE) + +%.d: %.c + @set -e; rm -f $@; \ + $(CC) -MM $(CFLAGS) $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ + rm -f $@.$$$$; echo '(re)building $@' + +ifneq ($(MAKECMDGOALS),distclean) +-include $(C_SRCS:%.c=%.d) +endif + +$(EXECUTABLE): $(C_OBJS) + $(CC) $(C_OBJS) -o $@ $(LDFLAGS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< + +strip: $(EXECUTABLE) + $(STRIP) -s $(EXECUTABLE) + + +distclean: clean + find . -name *.o -exec rm -f {} \; + find . -name "*.\~*" -exec rm -rf {} \; + rm -f include.mk + rm -f config.h + +clean: + rm -f *.o + rm -f *.d + rm -f *.d.* + rm -f $(EXECUTABLE) + +INSTALL_TARGETS := install-bin +REMOVE_TARGETS := remove-bin + +ifdef MANDIR +INSTALL_TARGETS += install-man +REMOVE_TARGETS += remove-man +endif + +install: all $(INSTALL_TARGETS) + +install-bin: $(EXECUTABLE) + $(INSTALL) -d $(DESTDIR)$(BINDIR) + $(INSTALL) -m 755 $(EXECUTABLE) $(DESTDIR)$(BINDIR) + +install-man: manpage + $(INSTALL) -d $(DESTDIR)$(MANDIR)/man8/ + $(INSTALL) -m 644 ../doc/$(EXECUTABLE).8 $(DESTDIR)$(MANDIR)/man8/$(EXECUTABLE).8 + +uninstall: remove + +remove: $(REMOVE_TARGETS) + +remove-bin: + rm -f $(DESTDIR)$(BINDIR)/$(EXECUTABLE) + +remove-man: + rm -f $(DESTDIR)$(MANDIR)/man8/$(EXECUTABLE).8 diff --git a/src/configure b/src/configure new file mode 100755 index 0000000..f7ddc99 --- /dev/null +++ b/src/configure @@ -0,0 +1,167 @@ +#!/bin/sh +# +# sydra +# +# sydra is a toolbox which allows you to set up multiple bidirectional +# Video/Audio streams from external locations. +# sydra has been written to be used for the Elevate Festival in Graz +# Austria in order to involve external locations to present themselves +# at the festival. +# Sydra is based on GStreamer and is written in C. +# +# +# Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> +# +# This file is part of sydra. +# +# sydra 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. +# +# sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. +# + +set -e + +TARGET=`uname -s` +EBUILD_COMPAT=0 +USE_CLANG=0 + +PREFIX='/usr/local' +BINDIR='' + +print_usage() { + echo "configure --help print this" + echo " --target=<TARGET> build target i.e. Linux (default: autodetect)" + echo " --prefix=<PREFIX> the installation prefix (default: /usr/local)" + echo " --bindir=<DIR> the path to the bin directory (default: $PREFIX/bin)" + echo " --use-clang use clang/llvm as compiler/linker" +} + +for arg +do + case $arg in + --target=*) + TARGET=${arg#--target=} + ;; + --prefix=*) + PREFIX=${arg#--prefix=} + ;; + --bindir=*) + BINDIR=${arg#--bindir=} + ;; + --use-clang) + USE_CLANG=1 + ;; + --ebuild-compat) + EBUILD_COMPAT=1 + ;; + --help) + print_usage + exit 0 + ;; + *) + ERRORS="$ERRORS $arg" + ;; + esac +done + +if [ -n "$ERRORS" ] && [ $EBUILD_COMPAT -ne 1 ]; then + for error in $ERRORS; do + echo "Unknown argument: $error" + done + + print_usage + exit 1 +fi + +if [ $USE_CLANG -eq 0 ]; then + CFLAGS='-g -Wall -O2' + LDFLAGS='-g -Wall -O2' + COMPILER='gcc' +else + CFLAGS='-g -O2' + LDFLAGS='-g -O2' + COMPILER='clang' +fi + +CFLAGS="$CFLAGS $(pkg-config --cflags gstreamer-0.10) -DGST_DISABLE_DEPRECATED" +LDFLAGS="$LDFLAGS $(pkg-config --libs gstreamer-0.10)" + +rm -f include.mk +rm -f config.h +case $TARGET in + Linux) + ;; + OpenBSD|FreeBSD|NetBSD|GNU/kFreeBSD) + CFLAGS=$CFLAGS' -I/usr/local/include' + LDFLAGS=$LDFLAGS' -L/usr/local/lib' + ;; + *) + echo "platform not supported" + exit 1; + ;; +esac + +if [ -z "$BINDIR" ]; then + BINDIR=$PREFIX/bin +fi + +cat > include.mk <<EOF +# this file was created automatically +# do not edit this file directly +# use ./configure instead + +TARGET := '$TARGET' +CC := $COMPILER +CFLAGS := $CFLAGS +LDFLAGS := $LDFLAGS +STRIP := strip +INSTALL := install +RAGEL := ragel + +PREFIX := '$PREFIX' +BINDIR := '$BINDIR' +EOF + +VERSION=`cat ../version` +if which git >/dev/null; then + GIT_HASH=`git rev-parse HEAD 2> /dev/null` + if [ -n "$GIT_HASH" ]; then + VERSION="$VERSION (git $GIT_HASH)" + fi +fi + +HOSTNAME=`hostname` +DATE=`date +"%d.%m.%Y %H:%M:%S %Z"` + +cat > config.h <<EOF +/* + * sydra config header + * + * this file was created automatically + * do not edit this file directly + * use ./configure instead + */ + +#ifndef SYDRA_config_h_INCLUDED +#define SYDRA_config_h_INCLUDED + +#define VERSION_STRING_0 "sydra version $VERSION" +#define VERSION_STRING_1 "built on $HOSTNAME, $DATE" + +#define TARGET "$TARGET" +#define PREFIX "$PREFIX" +#define BINDIR "$BINDIR" + +#endif +EOF + +exit 0 diff --git a/src/daemon.h b/src/daemon.h new file mode 100644 index 0000000..b7c2880 --- /dev/null +++ b/src/daemon.h @@ -0,0 +1,168 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_daemon_h_INCLUDED +#define SYDRA_daemon_h_INCLUDED + +#include <poll.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <unistd.h> + +struct priv_info_struct { + struct passwd* pw_; + struct group* gr_; +}; +typedef struct priv_info_struct priv_info_t; + +int priv_init(priv_info_t* priv, const char* username, const char* groupname) +{ + if(!priv) + return -1; + + priv->pw_ = NULL; + priv->gr_ = NULL; + + priv->pw_ = getpwnam(username); + if(!priv->pw_) { + log_printf(ERROR, "unknown user %s", username); + return -1; + } + + if(groupname) + priv->gr_ = getgrnam(groupname); + else + priv->gr_ = getgrgid(priv->pw_->pw_gid); + + if(!priv->gr_) { + log_printf(ERROR, "unknown group %s", groupname); + return -1; + } + + return 0; +} + +int priv_drop(priv_info_t* priv) +{ + if(!priv || !priv->pw_ || !priv->gr_) { + log_printf(ERROR, "privileges not initialized properly"); + return -1; + } + + if(setgid(priv->gr_->gr_gid)) { + log_printf(ERROR, "setgid('%s') failed: %s", priv->gr_->gr_name, strerror(errno)); + return -1; + } + + gid_t gr_list[1]; + gr_list[0] = priv->gr_->gr_gid; + if(setgroups (1, gr_list)) { + log_printf(ERROR, "setgroups(['%s']) failed: %s", priv->gr_->gr_name, strerror(errno)); + return -1; + } + + if(setuid(priv->pw_->pw_uid)) { + log_printf(ERROR, "setuid('%s') failed: %s", priv->pw_->pw_name, strerror(errno)); + return -1; + } + + log_printf(NOTICE, "dropped privileges to %s:%s", priv->pw_->pw_name, priv->gr_->gr_name); + return 0; +} + + +int do_chroot(const char* chrootdir) +{ + if(getuid() != 0) { + log_printf(ERROR, "this program has to be run as root in order to run in a chroot"); + return -1; + } + + if(chroot(chrootdir)) { + log_printf(ERROR, "can't chroot to %s: %s", chrootdir, strerror(errno)); + return -1; + } + log_printf(NOTICE, "we are in chroot jail (%s) now", chrootdir); + if(chdir("/")) { + log_printf(ERROR, "can't change to /: %s", strerror(errno)); + return -1; + } + + return 0; +} + +void daemonize() +{ + pid_t pid; + + pid = fork(); + if(pid < 0) { + log_printf(ERROR, "daemonizing failed at fork(): %s, exitting", strerror(errno)); + exit(-1); + } + if(pid) exit(0); + + umask(0); + + if(setsid() < 0) { + log_printf(ERROR, "daemonizing failed at setsid(): %s, exitting", strerror(errno)); + exit(-1); + } + + pid = fork(); + if(pid < 0) { + log_printf(ERROR, "daemonizing failed at fork(): %s, exitting", strerror(errno)); + exit(-1); + } + if(pid) exit(0); + + if ((chdir("/")) < 0) { + log_printf(ERROR, "daemonizing failed at chdir(): %s, exitting", strerror(errno)); + exit(-1); + } + + int fd; + for (fd=0;fd<=2;fd++) // close all file descriptors + close(fd); + fd = open("/dev/null",O_RDWR); // stdin + if(fd == -1) + log_printf(WARNING, "can't open stdin (chroot and no link to /dev/null?)"); + else { + if(dup(fd) == -1) // stdout + log_printf(WARNING, "can't open stdout"); + if(dup(fd) == -1) // stderr + log_printf(WARNING, "can't open stderr"); + } + umask(027); +} + +#endif diff --git a/src/datatypes.h b/src/datatypes.h new file mode 100644 index 0000000..365ed99 --- /dev/null +++ b/src/datatypes.h @@ -0,0 +1,41 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_datatypes_h_INCLUDED +#define SYDRA_datatypes_h_INCLUDED + +#include <stdint.h> + +struct buffer_struct { + uint32_t length_; + uint8_t* buf_; +}; +typedef struct buffer_struct buffer_t; + +#endif diff --git a/src/file_list.c b/src/file_list.c new file mode 100644 index 0000000..92296f0 --- /dev/null +++ b/src/file_list.c @@ -0,0 +1,222 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#define _GNU_SOURCE +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +#include <glib.h> + +#include "datatypes.h" +#include "file_list.h" +#include "slist.h" +#include "log.h" + +static void delete_file(void* element) +{ + file_t* deletee = (file_t*)element; + log_printf(INFO, "removing/closing file '%s' -> %d", deletee->path_, deletee->fd_); + if(deletee->path_) free(deletee->path_); + if(deletee->fd_ >= 0) close(deletee->fd_); + if(deletee->pp_child_) free_child(deletee->pp_child_); +} + +int file_list_init(file_list_t* list) +{ + g_mutex_init(&(list->mutex_)); + return slist_init(&(list->list_), &delete_file); +} + +void file_list_clear(file_list_t* list) +{ + g_mutex_lock(&(list->mutex_)); + slist_clear(&(list->list_)); + g_mutex_unlock(&(list->mutex_)); +} + +file_t* file_list_add(file_list_t* list, struct tm* time, const char* type, const char* format, const char* dir, mode_t mode, int nocache) +{ + if(!list || !(&(list->mutex_))) + return NULL; + + file_t* tmp = malloc(sizeof(file_t)); + if(!tmp) + return NULL; + + log_printf(INFO, "%s time: %02d:%02d:%02d on %d.%d.%d%s", type, time->tm_hour, time->tm_min, time->tm_sec, time->tm_mday, time->tm_mon+1, time->tm_year+1900, time->tm_isdst > 0 ? " (DST)": ""); + + char name[256]; + strftime(name, sizeof(name), format, time); + int len = asprintf(&(tmp->path_), "%s/%s", dir, name); + if(len == -1) { + free(tmp); + return NULL; + } + + log_printf(INFO, "%s filename is: %s(.?)", type, tmp->path_); + tmp->fd_ = FILE_CLOSED; + tmp->mode_ = mode; + tmp->nocache_ = nocache; + tmp->pp_child_ = NULL; + + g_mutex_lock(&(list->mutex_)); + if(slist_add(&(list->list_), tmp) == NULL) { + g_mutex_unlock(&(list->mutex_)); + free(tmp->path_); + free(tmp); + return NULL; + } + g_mutex_unlock(&(list->mutex_)); + + return tmp; +} + +int file_list_remove(file_list_t* list, int fd) +{ + if(!list || !(&(list->mutex_))) + return -1; + + g_mutex_lock(&(list->mutex_)); + slist_element_t* tmp = list->list_.first_; + while(tmp) { + if(((file_t*)tmp->data_)->fd_ == fd) { + slist_remove(&(list->list_), tmp->data_); + break; + } + tmp = tmp->next_; + } + g_mutex_unlock(&(list->mutex_)); + + return 0; +} + +int file_list_call_post_process(file_list_t* list, int fd, char* script) +{ + if(!list || !(&(list->mutex_))) + return -1; + + g_mutex_lock(&(list->mutex_)); + slist_element_t* tmp = list->list_.first_; + while(tmp) { + if(((file_t*)tmp->data_)->fd_ == fd) { + log_printf(INFO, "calling post processing for '%s'", ((file_t*)tmp->data_)->path_); + close(((file_t*)tmp->data_)->fd_); + ((file_t*)tmp->data_)->fd_ = FILE_POST_PROCESS; + + char* const argv[] = { script, ((file_t*)tmp->data_)->path_, NULL }; + char* const evp[] = { NULL }; + ((file_t*)tmp->data_)->pp_child_ = sydra_exec(script, argv, evp); + if(!((file_t*)tmp->data_)->pp_child_) + slist_remove(&(list->list_), tmp->data_); + + break; + } + tmp = tmp->next_; + } + g_mutex_unlock(&(list->mutex_)); + + return 0; +} + +int file_list_waitpid(file_list_t* list) +{ + if(!list || !(&(list->mutex_))) + return -1; + + g_mutex_lock(&(list->mutex_)); + slist_element_t* tmp = list->list_.first_; + while(tmp) { + if(((file_t*)tmp->data_)->fd_ == FILE_POST_PROCESS) { + int ret = sydra_waitpid(((file_t*)tmp->data_)->pp_child_, NULL); + file_t* deletee = tmp->data_; + tmp = tmp->next_; + if(ret) + slist_remove(&(list->list_), deletee); + } + else + tmp = tmp->next_; + } + g_mutex_unlock(&(list->mutex_)); + + return 0; +} + +int open_file(file_t* file) +{ + if(!file || file->fd_ != FILE_CLOSED) // file already open! + return -1; + + char* orig_path = file->path_; + int cnt = 0; + do { + int flags = O_WRONLY | O_CREAT | O_EXCL; + if(file->nocache_) + flags |= O_DIRECT; + file->fd_ = open(file->path_, flags, file->mode_); + if(file->fd_ < 0) { + if(errno != EEXIST) { + // TODO: thread safe strerror + log_printf(ERROR, "can't open file '%s': %s", file->path_, strerror(errno)); + if(orig_path != file->path_) + free(orig_path); + file->fd_ = FILE_CLOSED; + return -1; + } + cnt++; + char* tmp; + int len = asprintf(&tmp, "%s.%d", orig_path, cnt); + if(len == -1) { + if(orig_path != file->path_) + free(orig_path); + return -2; + } + + if(file->path_ != orig_path) + free(file->path_); + file->path_ = tmp; + } + fchmod(file->fd_, file->mode_); + } + while(file->fd_ < 0); + + if(orig_path != file->path_) + free(orig_path); + + log_printf(INFO, "opened file '%s' -> %d", file->path_, file->fd_); + + return 0; +} diff --git a/src/file_list.h b/src/file_list.h new file mode 100644 index 0000000..6d8344c --- /dev/null +++ b/src/file_list.h @@ -0,0 +1,67 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_file_list_h_INCLUDED +#define SYDRA_file_list_h_INCLUDED + +#include <sys/types.h> +#include <time.h> + +#include <glib.h> + +#include "slist.h" +#include "sysexec.h" + +#define FILE_CLOSED -1 +#define FILE_POST_PROCESS -2 + +struct file_struct { + int fd_; + char* path_; + mode_t mode_; + int nocache_; + child_t* pp_child_; +}; +typedef struct file_struct file_t; + +struct file_list_struct { + slist_t list_; + GMutex mutex_; +}; +typedef struct file_list_struct file_list_t; + +int file_list_init(file_list_t* list); +void file_list_clear(file_list_t* list); +file_t* file_list_add(file_list_t* list, struct tm* time, const char* type, const char* format, const char* dir, mode_t mode, int nocache); +int file_list_remove(file_list_t* list, int fd); +int file_list_call_post_process(file_list_t* list, int fd, char* script); +int file_list_waitpid(file_list_t* list); +int open_file(file_t* file); + +#endif diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..7b66553 --- /dev/null +++ b/src/log.c @@ -0,0 +1,265 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <ctype.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#include <glib.h> + +#define SYSLOG_NAMES +#include <syslog.h> + +#include "log.h" + +log_t stdlog; + +#include "log_targets.h" + +const char* log_prio_to_string(log_prio_t prio) +{ + switch(prio) { + case ERROR: return "ERROR"; + case WARNING: return "WARNING"; + case NOTICE: return "NOTICE"; + case INFO: return "INFO"; + case DEBUG: return "DEBUG"; + } + return "UNKNOWN"; +} + +log_target_type_t log_target_parse_type(const char* conf) +{ + if(!conf) + return TARGET_UNKNOWN; + + if(!strncmp(conf, "syslog", 6)) return TARGET_SYSLOG; + if(!strncmp(conf, "file", 4)) return TARGET_FILE; + if(!strncmp(conf, "stdout", 6)) return TARGET_STDOUT; + if(!strncmp(conf, "stderr", 6)) return TARGET_STDERR; + + return TARGET_UNKNOWN; +} + +int log_targets_target_exists(log_targets_t* targets, log_target_type_t type) +{ + if(!targets && !targets->first_) + return 0; + + log_target_t* tmp = targets->first_; + while(tmp) { + if(tmp->type_ == type) + return 1; + tmp = tmp->next_; + } + return 0; +} + +int log_targets_add(log_targets_t* targets, const char* conf) +{ + if(!targets) + return -1; + + log_target_t* new_target = NULL; + int duplicates_allowed = 0; + switch(log_target_parse_type(conf)) { + case TARGET_SYSLOG: new_target = log_target_syslog_new(); break; + case TARGET_FILE: new_target = log_target_file_new(); duplicates_allowed = 1; break; + case TARGET_STDOUT: new_target = log_target_stdout_new(); break; + case TARGET_STDERR: new_target = log_target_stderr_new(); break; + default: return -3; + } + if(!new_target) + return -2; + + if(!duplicates_allowed && log_targets_target_exists(targets, new_target->type_)) { + free(new_target); + return -4; + } + + const char* prioptr = strchr(conf, ':'); + if(!prioptr || prioptr[1] == 0) { + free(new_target); + return -1; + } + prioptr++; + if(!isdigit(prioptr[0]) || (prioptr[1] != 0 && prioptr[1] != ',')) { + free(new_target); + return -1; + } + new_target->max_prio_ = prioptr[0] - '0'; + if(new_target->max_prio_ > 0) + new_target->enabled_ = 1; + + if(new_target->init != NULL) { + const char* confptr = NULL; + if(prioptr[1] != 0) + confptr = prioptr+2; + + int ret = (*new_target->init)(new_target, confptr); + if(ret) { + free(new_target); + return ret; + } + } + + if(new_target->open != NULL) + (*new_target->open)(new_target); + + + if(!targets->first_) { + targets->first_ = new_target; + } + else { + log_target_t* tmp = targets->first_; + while(tmp->next_) + tmp = tmp->next_; + + tmp->next_ = new_target; + } + return 0; +} + +void log_targets_log(log_targets_t* targets, log_prio_t prio, const char* msg) +{ + if(!targets) + return; + + log_target_t* tmp = targets->first_; + while(tmp) { + if(tmp->log != NULL && tmp->enabled_ && tmp->max_prio_ >= prio) + (*tmp->log)(tmp, prio, msg); + + tmp = tmp->next_; + } +} + +void log_targets_clear(log_targets_t* targets) +{ + if(!targets) + return; + + while(targets->first_) { + log_target_t* tmp = targets->first_; + targets->first_ = tmp->next_; + if(tmp->close != NULL) + (*tmp->close)(tmp); + if(tmp->clear != NULL) + (*tmp->clear)(tmp); + free(tmp); + } +} + + +void log_init() +{ + stdlog.max_prio_ = 0; + stdlog.targets_.first_ = NULL; + g_mutex_init(&(stdlog.log_mutex_)); +} + +void log_close() +{ + g_mutex_lock(&(stdlog.log_mutex_)); + log_targets_clear(&stdlog.targets_); + g_mutex_unlock(&(stdlog.log_mutex_)); +} + +void update_max_prio() +{ + log_target_t* tmp = stdlog.targets_.first_; + while(tmp) { + if(tmp->enabled_ && tmp->max_prio_ > stdlog.max_prio_) + stdlog.max_prio_ = tmp->max_prio_; + + tmp = tmp->next_; + } +} + +int log_add_target(const char* conf) +{ + if(!conf) + return -1; + + g_mutex_lock(&(stdlog.log_mutex_)); + int ret = log_targets_add(&stdlog.targets_, conf); + if(!ret) update_max_prio(); + g_mutex_unlock(&(stdlog.log_mutex_)); + return ret; +} + +void log_printf(log_prio_t prio, const char* fmt, ...) +{ + if(stdlog.max_prio_ < prio) + return; + + static char msg[MSG_LENGTH_MAX]; + va_list args; + + va_start(args, fmt); + vsnprintf(msg, MSG_LENGTH_MAX, fmt, args); + va_end(args); + + g_mutex_lock(&(stdlog.log_mutex_)); + log_targets_log(&stdlog.targets_, prio, msg); + g_mutex_unlock(&(stdlog.log_mutex_)); +} + +void log_print_hex_dump(log_prio_t prio, const u_int8_t* buf, u_int32_t len) +{ + if(stdlog.max_prio_ < prio) + return; + + static char msg[MSG_LENGTH_MAX]; + + if(!buf) { + snprintf(msg, MSG_LENGTH_MAX, "(NULL)"); + } + else { + u_int32_t i; + int offset = snprintf(msg, MSG_LENGTH_MAX, "dump(%d): ", len); + if(offset < 0) + return; + char* ptr = &msg[offset]; + + for(i=0; i < len; i++) { + if(((i+1)*3) >= (MSG_LENGTH_MAX - offset)) + break; + snprintf(ptr, 3, "%02X ", buf[i]); + ptr+=3; + } + } + g_mutex_lock(&(stdlog.log_mutex_)); + log_targets_log(&stdlog.targets_, prio, msg); + g_mutex_unlock(&(stdlog.log_mutex_)); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..3a9c3e2 --- /dev/null +++ b/src/log.h @@ -0,0 +1,87 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_log_h_INCLUDED +#define SYDRA_log_h_INCLUDED + +#include <glib.h> + +#define MSG_LENGTH_MAX 1024 + +enum log_prio_enum { ERROR = 1, WARNING = 2, NOTICE = 3, + INFO = 4, DEBUG = 5 }; +typedef enum log_prio_enum log_prio_t; + +const char* log_prio_to_string(log_prio_t prio); + +enum log_target_type_enum { TARGET_SYSLOG , TARGET_STDOUT, TARGET_STDERR, TARGET_FILE , TARGET_UNKNOWN }; +typedef enum log_target_type_enum log_target_type_t; + +struct log_target_struct { + log_target_type_t type_; + int (*init)(struct log_target_struct* self, const char* conf); + void (*open)(struct log_target_struct* self); + void (*log)(struct log_target_struct* self, log_prio_t prio, const char* msg); + void (*close)(struct log_target_struct* self); + void (*clear)(struct log_target_struct* self); + int opened_; + int enabled_; + log_prio_t max_prio_; + void* param_; + struct log_target_struct* next_; +}; +typedef struct log_target_struct log_target_t; + + +struct log_targets_struct { + log_target_t* first_; +}; +typedef struct log_targets_struct log_targets_t; + +int log_targets_target_exists(log_targets_t* targets, log_target_type_t type); +int log_targets_add(log_targets_t* targets, const char* conf); +void log_targets_log(log_targets_t* targets, log_prio_t prio, const char* msg); +void log_targets_clear(log_targets_t* targets); + + +struct log_struct { + log_prio_t max_prio_; + log_targets_t targets_; + GMutex log_mutex_; +}; +typedef struct log_struct log_t; + +void log_init(); +void log_close(); +void update_max_prio(); +int log_add_target(const char* conf); +void log_printf(log_prio_t prio, const char* fmt, ...); +void log_print_hex_dump(log_prio_t prio, const u_int8_t* buf, u_int32_t len); + +#endif diff --git a/src/log_targets.h b/src/log_targets.h new file mode 100644 index 0000000..de17f39 --- /dev/null +++ b/src/log_targets.h @@ -0,0 +1,357 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_log_targets_h_INCLUDED +#define SYDRA_log_targets_h_INCLUDED + +#include <time.h> + +static char* get_time_formatted() +{ + char* time_string; + time_t t = time(NULL); + if(t < 0) + time_string = "<time read error>"; + else { + time_string = ctime(&t); + if(!time_string) + time_string = "<time format error>"; + else { + char* newline = strchr(time_string, '\n'); + if(newline) + newline[0] = 0; + } + } + return time_string; +} + +enum syslog_facility_enum { USER = LOG_USER, MAIL = LOG_MAIL, + DAEMON = LOG_DAEMON, AUTH = LOG_AUTH, + SYSLOG = LOG_SYSLOG, LPR = LOG_LPR, + NEWS = LOG_NEWS, UUCP = LOG_UUCP, + CRON = LOG_CRON, AUTHPRIV = LOG_AUTHPRIV, + FTP = LOG_FTP, LOCAL0 = LOG_LOCAL0, + LOCAL1 = LOG_LOCAL1, LOCAL2 = LOG_LOCAL2, + LOCAL3 = LOG_LOCAL3, LOCAL4 = LOG_LOCAL4, + LOCAL5 = LOG_LOCAL5, LOCAL6 = LOG_LOCAL6, + LOCAL7 = LOG_LOCAL7 }; +typedef enum syslog_facility_enum syslog_facility_t; + +struct log_target_syslog_param_struct { + char* logname_; + syslog_facility_t facility_; +}; +typedef struct log_target_syslog_param_struct log_target_syslog_param_t; + +int log_target_syslog_init(log_target_t* self, const char* conf) +{ + if(!self || (conf && conf[0] == 0)) + return -1; + + self->param_ = malloc(sizeof(log_target_syslog_param_t)); + if(!self->param_) + return -2; + + char* logname; + const char* end = NULL; + if(!conf) + logname = strdup("sydra"); + else { + end = strchr(conf, ','); + if(end) { + size_t len = (size_t)(end - conf); + if(!len) { + free(self->param_); + return -1; + } + logname = malloc(len+1); + if(logname) { + strncpy(logname, conf, len); + logname[len] = 0; + } + } + else + logname = strdup(conf); + } + + if(!logname) { + free(self->param_); + return -2; + } + ((log_target_syslog_param_t*)(self->param_))->logname_ = logname; + + if(!end) { + ((log_target_syslog_param_t*)(self->param_))->facility_ = DAEMON; + return 0; + } + + if(end[1] == 0 || end[1] == ',') { + free(logname); + free(self->param_); + return -1; + } + + const char* start = end + 1; + end = strchr(start, ','); + int i; + for(i=0;;++i) { + if(facilitynames[i].c_name == NULL) { + free(logname); + free(self->param_); + return -1; + } + + if(( end && !strncmp(start, facilitynames[i].c_name, (size_t)(end - start)) && facilitynames[i].c_name[(size_t)(end-start)] == 0) || + (!end && !strcmp(start, facilitynames[i].c_name))) { + ((log_target_syslog_param_t*)(self->param_))->facility_ = facilitynames[i].c_val; + break; + } + } + + return 0; +} + +void log_target_syslog_open(log_target_t* self) +{ + if(!self || !self->param_) + return; + + openlog(((log_target_syslog_param_t*)(self->param_))->logname_, LOG_PID, ((log_target_syslog_param_t*)(self->param_))->facility_); + self->opened_ = 1; +} + +void log_target_syslog_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + if(!self || !self->param_ || !self->opened_) + return; + + syslog((prio + 2) | ((log_target_syslog_param_t*)(self->param_))->facility_, "%s", msg); +} + +void log_target_syslog_close(log_target_t* self) +{ + closelog(); + self->opened_ = 0; +} + +void log_target_syslog_clear(log_target_t* self) +{ + if(!self || !self->param_) + return; + + if(((log_target_syslog_param_t*)(self->param_))->logname_) + free(((log_target_syslog_param_t*)(self->param_))->logname_); + + free(self->param_); +} + +log_target_t* log_target_syslog_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_SYSLOG; + tmp->init = &log_target_syslog_init; + tmp->open = &log_target_syslog_open; + tmp->log = &log_target_syslog_log; + tmp->close = &log_target_syslog_close; + tmp->clear = &log_target_syslog_clear; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + + +struct log_target_file_param_struct { + char* logfilename_; + FILE* file_; +}; +typedef struct log_target_file_param_struct log_target_file_param_t; + +int log_target_file_init(log_target_t* self, const char* conf) +{ + if(!self || (conf && conf[0] == 0)) + return -1; + + self->param_ = malloc(sizeof(log_target_file_param_t)); + if(!self->param_) + return -2; + + char* logfilename; + if(!conf) + logfilename = strdup("sydra.log"); + else { + const char* end = strchr(conf, ','); + if(end) { + size_t len = (size_t)(end - conf); + if(!len) { + free(self->param_); + return -1; + } + logfilename = malloc(len+1); + if(logfilename) { + strncpy(logfilename, conf, len); + logfilename[len] = 0; + } + } + else + logfilename = strdup(conf); + } + + if(!logfilename) { + free(self->param_); + return -2; + } + ((log_target_file_param_t*)(self->param_))->logfilename_ = logfilename; + ((log_target_file_param_t*)(self->param_))->file_ = NULL; + + return 0; +} + +void log_target_file_open(log_target_t* self) +{ + if(!self || !self->param_) + return; + + ((log_target_file_param_t*)(self->param_))->file_ = fopen(((log_target_file_param_t*)(self->param_))->logfilename_, "w"); + if(((log_target_file_param_t*)(self->param_))->file_) + self->opened_ = 1; +} + +void log_target_file_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + if(!self || !self->param_ || !self->opened_) + return; + + fprintf(((log_target_file_param_t*)(self->param_))->file_, "%s %s: %s\n", get_time_formatted(), log_prio_to_string(prio), msg); + fflush(((log_target_file_param_t*)(self->param_))->file_); +} + +void log_target_file_close(log_target_t* self) +{ + if(!self || !self->param_) + return; + + fclose(((log_target_file_param_t*)(self->param_))->file_); + self->opened_ = 0; +} + +void log_target_file_clear(log_target_t* self) +{ + if(!self || !self->param_) + return; + + if(((log_target_file_param_t*)(self->param_))->logfilename_) + free(((log_target_file_param_t*)(self->param_))->logfilename_); + + free(self->param_); +} + + +log_target_t* log_target_file_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_FILE; + tmp->init = &log_target_file_init; + tmp->open = &log_target_file_open; + tmp->log = &log_target_file_log; + tmp->close = &log_target_file_close; + tmp->clear = &log_target_file_clear; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + + +void log_target_stdout_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + printf("%s %s: %s\n", get_time_formatted(), log_prio_to_string(prio), msg); +} + +log_target_t* log_target_stdout_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_STDOUT; + tmp->init = NULL; + tmp->open = NULL; + tmp->log = &log_target_stdout_log; + tmp->close = NULL; + tmp->clear = NULL; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + + +void log_target_stderr_log(log_target_t* self, log_prio_t prio, const char* msg) +{ + fprintf(stderr, "%s %s: %s\n", get_time_formatted(), log_prio_to_string(prio), msg); +} + +log_target_t* log_target_stderr_new() +{ + log_target_t* tmp = malloc(sizeof(log_target_t)); + if(!tmp) + return NULL; + + tmp->type_ = TARGET_STDERR; + tmp->init = NULL; + tmp->open = NULL; + tmp->log = &log_target_stderr_log; + tmp->close = NULL; + tmp->clear = NULL; + tmp->opened_ = 0; + tmp->enabled_ = 0; + tmp->max_prio_ = NOTICE; + tmp->param_ = NULL; + tmp->next_ = NULL; + + return tmp; +} + +#endif diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..1273542 --- /dev/null +++ b/src/options.c @@ -0,0 +1,337 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" +#include "config.h" + +#include "options.h" +#include "log.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#define PARSE_BOOL_PARAM(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + VALUE = 1; + +#define PARSE_INVERSE_BOOL_PARAM(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + VALUE = 0; + +#define PARSE_INT_PARAM(SHORT, LONG, VALUE, BASE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1) \ + return i; \ + VALUE = strtol(argv[i+1], (char **)NULL, BASE); \ + argc--; \ + i++; \ + } + +#define PARSE_STRING_PARAM(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + if(VALUE) free(VALUE); \ + VALUE = strdup(argv[i+1]); \ + if(!VALUE) \ + return -2; \ + argc--; \ + i++; \ + } + +#define PARSE_STRING_PARAM_SEC(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + if(VALUE) free(VALUE); \ + VALUE = strdup(argv[i+1]); \ + if(!VALUE) \ + return -2; \ + size_t j; \ + for(j=0; j < strlen(argv[i+1]); ++j) \ + argv[i+1][j] = '#'; \ + argc--; \ + i++; \ + } + +#define PARSE_HEXSTRING_PARAM_SEC(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + int ret; \ + ret = options_parse_hex_string(argv[i+1], &VALUE); \ + if(ret > 0) \ + return i+1; \ + else if(ret < 0) \ + return ret; \ + size_t j; \ + for(j=0; j < strlen(argv[i+1]); ++j) \ + argv[i+1][j] = '#'; \ + argc--; \ + i++; \ + } + +#define PARSE_STRING_LIST(SHORT, LONG, LIST) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + int ret = string_list_add(&LIST, argv[i+1]); \ + if(ret == -2) \ + return ret; \ + else if(ret) \ + return i+1; \ + argc--; \ + i++; \ + } + +#define PARSE_RESOLV_TYPE(SHORT, LONG, VALUE) \ + else if(!strcmp(str,SHORT) || !strcmp(str,LONG)) \ + { \ + if(argc < 1 || argv[i+1][0] == '-') \ + return i; \ + if(!strcmp(argv[i+1], "4") || \ + !strcmp(argv[i+1], "ipv4")) \ + VALUE = IPV4_ONLY; \ + else if(!strcmp(argv[i+1], "6") || \ + !strcmp(argv[i+1], "ipv6")) \ + VALUE = IPV6_ONLY; \ + else \ + return i+1; \ + argc--; \ + i++; \ + } + +int options_parse_hex_string(const char* hex, buffer_t* buffer) +{ + if(!hex || !buffer) + return -1; + + u_int32_t hex_len = strlen(hex); + if(hex_len%2) + return 1; + + if(buffer->buf_) + free(buffer->buf_); + + buffer->length_ = hex_len/2; + buffer->buf_ = malloc(buffer->length_); + if(!buffer->buf_) { + buffer->length_ = 0; + return -2; + } + + const char* ptr = hex; + int i; + for(i=0;i<buffer->length_;++i) { + u_int32_t tmp; + sscanf(ptr, "%2X", &tmp); + buffer->buf_[i] = (u_int8_t)tmp; + ptr += 2; + } + + return 0; +} + + +int options_parse(options_t* opt, int argc, char* argv[]) +{ + if(!opt) + return -1; + + options_default(opt); + + if(opt->progname_) + free(opt->progname_); + opt->progname_ = strdup(argv[0]); + if(!opt->progname_) + return -2; + + argc--; + + int i; + for(i=1; argc > 0; ++i) + { + char* str = argv[i]; + argc--; + + if(!strcmp(str,"-h") || !strcmp(str,"--help")) + return -1; + else if(!strcmp(str,"-v") || !strcmp(str,"--version")) + return -3; + PARSE_INVERSE_BOOL_PARAM("-D","--nodaemonize", opt->daemonize_) + PARSE_STRING_PARAM("-u","--username", opt->username_) + PARSE_STRING_PARAM("-g","--groupname", opt->groupname_) + PARSE_STRING_PARAM("-C","--chroot", opt->chroot_dir_) + PARSE_STRING_PARAM("-P","--write-pid", opt->pid_file_) + PARSE_STRING_LIST("-L","--log", opt->log_targets_) + PARSE_BOOL_PARAM("-U", "--debug", opt->debug_) + PARSE_STRING_PARAM("-s","--source", opt->src_bin_desc_) + PARSE_STRING_PARAM("-d","--output-dir", opt->output_dir_) + PARSE_STRING_PARAM("-f","--name-format", opt->name_format_) + PARSE_INT_PARAM("-m","--mode", opt->mode_, 8) + PARSE_BOOL_PARAM("-n", "--nocache", opt->nocache_) + PARSE_INT_PARAM("-i","--interval", opt->interval_, 10) + PARSE_INT_PARAM("-o","--offset", opt->offset_, 10) + PARSE_STRING_PARAM("-x","--post-process", opt->post_process_) + else + return i; + } + + if(opt->interval_ <= 0) + return -4; + + if(opt->debug_) { + string_list_add(&opt->log_targets_, "stdout:5"); + opt->daemonize_ = 0; + } + + if(!opt->log_targets_.first_) { + string_list_add(&opt->log_targets_, "syslog:3,sydra,daemon"); + } + + return 0; +} + +void options_parse_post(options_t* opt) +{ + if(!opt) + return; + +// nothing yet +} + +void options_default(options_t* opt) +{ + if(!opt) + return; + + opt->progname_ = strdup("sydra"); + opt->daemonize_ = 1; + opt->username_ = NULL; + opt->groupname_ = NULL; + opt->chroot_dir_ = NULL; + opt->pid_file_ = NULL; + string_list_init(&opt->log_targets_); + opt->debug_ = 0; + opt->src_bin_desc_ = NULL; + opt->output_dir_ = strdup("/srv/archiv"); + opt->name_format_ = strdup("%Y-%m-%d-%H00.ogg"); + opt->mode_ = 0644; + opt->nocache_ = 0; + opt->interval_ = 50; + opt->offset_ = 0; + opt->post_process_ = NULL; +} + +void options_clear(options_t* opt) +{ + if(!opt) + return; + + if(opt->progname_) + free(opt->progname_); + if(opt->username_) + free(opt->username_); + if(opt->groupname_) + free(opt->groupname_); + if(opt->chroot_dir_) + free(opt->chroot_dir_); + if(opt->pid_file_) + free(opt->pid_file_); + string_list_clear(&opt->log_targets_); + if(opt->src_bin_desc_) + free(opt->src_bin_desc_); + if(opt->output_dir_) + free(opt->output_dir_); + if(opt->name_format_) + free(opt->name_format_); + if(opt->post_process_) + free(opt->post_process_); +} + +void options_print_usage() +{ + printf("USAGE:\n"); + printf("sydra [-h|--help] prints this...\n"); + printf(" [-v|--version] print version info and exit\n"); + printf(" [-D|--nodaemonize] don't run in background\n"); + printf(" [-u|--username] <username> change to this user\n"); + printf(" [-g|--groupname] <groupname> change to this group\n"); + printf(" [-C|--chroot] <path> chroot to this directory\n"); + printf(" [-P|--write-pid] <path> write pid to this file\n"); + printf(" [-L|--log] <target>:<level>[,<param1>[,<param2>..]]\n"); + printf(" add a log target, can be invoked several times\n"); + printf(" [-U|--debug] don't daemonize and log to stdout with maximum log level\n"); + printf(" [-s|--source] <description> a gstreamer pipeline-style description which will be used\n"); + printf(" as data source, see gst-launch man-page for syntax\n"); + printf(" [-d|--output-dir] <path> path to the output directory\n"); + printf(" [-f|--name-format] <format> the file name format, see manpage of strftime for the syntax\n"); + printf(" [-m|--mode] <value> octal representation of the file permission mode\n"); + printf(" [-n|--nocache] Use O_DIRECT for recorded files\n"); + printf(" [-i|--interval] <value> interval for time checks in ms\n"); + printf(" [-o|--offset] <value> time offset for recordings in ms\n"); + printf(" [-x|--post-process] <script> call script when file is finished\n"); +} + +void options_print_version() +{ + printf("%s\n", VERSION_STRING_0); + printf("%s\n", VERSION_STRING_1); +} + +void options_print(options_t* opt) +{ + if(!opt) + return; + + printf("progname: '%s'\n", opt->progname_); + printf("daemonize: %d\n", opt->daemonize_); + printf("username: '%s'\n", opt->username_); + printf("groupname: '%s'\n", opt->groupname_); + printf("chroot_dir: '%s'\n", opt->chroot_dir_); + printf("pid_file: '%s'\n", opt->pid_file_); + printf("log_targets: \n"); + string_list_print(&opt->log_targets_, " '", "'\n"); + printf("debug: %s\n", !opt->debug_ ? "false" : "true"); + printf("src_bin_desc: >>%s<<\n", opt->src_bin_desc_); + printf("output_dir: '%s'\n", opt->output_dir_); + printf("name_format: '%s'\n", opt->name_format_); + printf("mode: %04o\n", (int)opt->mode_); + printf("nocache: %s\n", !opt->nocache_ ? "false" : "true"); + printf("interval: %d\n", opt->interval_); + printf("offset: %d\n", opt->offset_); + printf("post_process: '%s'\n", opt->post_process_); +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..465291c --- /dev/null +++ b/src/options.h @@ -0,0 +1,67 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_options_h_INCLUDED +#define SYDRA_options_h_INCLUDED + +#include <sys/types.h> +#include "string_list.h" +#include "datatypes.h" + +struct options_struct { + char* progname_; + int daemonize_; + char* username_; + char* groupname_; + char* chroot_dir_; + char* pid_file_; + string_list_t log_targets_; + int debug_; + char* src_bin_desc_; + char* output_dir_; + char* name_format_; + mode_t mode_; + int nocache_; + int interval_; + int offset_; + char* post_process_; +}; +typedef struct options_struct options_t; + +int options_parse_hex_string(const char* hex, buffer_t* buffer); + +int options_parse(options_t* opt, int argc, char* argv[]); +void options_parse_post(options_t* opt); +void options_default(options_t* opt); +void options_clear(options_t* opt); +void options_print_usage(); +void options_print_version(); +void options_print(options_t* opt); + +#endif diff --git a/src/sig_handler.c b/src/sig_handler.c new file mode 100644 index 0000000..9a77d39 --- /dev/null +++ b/src/sig_handler.c @@ -0,0 +1,113 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include "log.h" + +#include "sig_handler.h" + +#include <glib.h> +#include <errno.h> + +static GThread *signal_thread; + +void signal_init() +{ + sigset_t signal_set; + + sigemptyset(&signal_set); + sigaddset(&signal_set, SIGINT); + sigaddset(&signal_set, SIGQUIT); + sigaddset(&signal_set, SIGHUP); + sigaddset(&signal_set, SIGTERM); + sigaddset(&signal_set, SIGUSR1); + sigaddset(&signal_set, SIGUSR2); + + pthread_sigmask(SIG_BLOCK, &signal_set, NULL); +} + +static gpointer signal_thread_func(gpointer data) +{ + GMainLoop *loop = (GMainLoop *)data; + + struct timespec timeout; + sigset_t signal_set; + int sig_num; + for(;;) { + sigemptyset(&signal_set); + sigaddset(&signal_set, SIGINT); + sigaddset(&signal_set, SIGQUIT); + sigaddset(&signal_set, SIGHUP); + sigaddset(&signal_set, SIGTERM); + sigaddset(&signal_set, SIGUSR1); + sigaddset(&signal_set, SIGUSR2); + timeout.tv_sec = 1; + timeout.tv_nsec = 0; + sig_num = sigtimedwait(&signal_set, NULL, &timeout); + if(sig_num == -1) { + if(errno != EINTR && errno != EAGAIN) { + log_printf(ERROR, "sigwait failed with error: %d, signal handling will be disabled", errno); + break; + } + } else { + switch(sig_num) { + case SIGTERM: + case SIGINT: + case SIGQUIT: { + log_printf(NOTICE, "signal %d received, exiting", sig_num); + g_main_loop_quit(loop); + break; + } + default: { + log_printf(NOTICE, "signal %d received, ignoring", sig_num); + break; + } + } + } + } + + return NULL; +} + +int signal_start(GMainLoop *loop) +{ + g_assert(!signal_thread); + + signal_thread = g_thread_new("sig_handler", signal_thread_func, loop); + if(!signal_thread) + return -1; + + return 0; +} + +void signal_stop() +{ + // nothing yet.. +} diff --git a/src/sig_handler.h b/src/sig_handler.h new file mode 100644 index 0000000..739cdc2 --- /dev/null +++ b/src/sig_handler.h @@ -0,0 +1,39 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_sig_handler_h_INCLUDED +#define SYDRA_sig_handler_h_INCLUDED + +#include <glib.h> + +void signal_init(); +int signal_start(GMainLoop *loop); +void signal_stop(); + +#endif diff --git a/src/slist.c b/src/slist.c new file mode 100644 index 0000000..884339f --- /dev/null +++ b/src/slist.c @@ -0,0 +1,132 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <unistd.h> +#include <stdlib.h> + +#include "datatypes.h" + +#include "slist.h" + +slist_element_t* slist_get_last(slist_element_t* first) +{ + if(!first) + return NULL; + + while(first->next_) + first = first->next_; + + return first; +} + +int slist_init(slist_t* lst, void (*delete_element)(void*)) +{ + if(!lst || !delete_element) + return -1; + + lst->delete_element = delete_element; + lst->first_ = NULL; + + return 0; +} + +slist_element_t* slist_add(slist_t* lst, void* data) +{ + if(!lst || !data) + return NULL; + + slist_element_t* new_element = malloc(sizeof(slist_element_t)); + if(!new_element) + return NULL; + + new_element->data_ = data; + new_element->next_ = NULL; + + if(!lst->first_) + lst->first_ = new_element; + else + slist_get_last(lst->first_)->next_ = new_element; + + return new_element; +} + +void slist_remove(slist_t* lst, void* data) +{ + if(!lst || !lst->first_ || !data) + return; + + slist_element_t* tmp = lst->first_->next_; + slist_element_t* prev = lst->first_; + if(lst->first_->data_ == data) { + lst->first_ = tmp; + lst->delete_element(prev->data_); + free(prev); + } + else { + while(tmp) { + if(tmp->data_ == data) { + prev->next_ = tmp->next_; + lst->delete_element(tmp->data_); + free(tmp); + return; + } + prev = tmp; + tmp = tmp->next_; + } + } +} + +void slist_clear(slist_t* lst) +{ + if(!lst || !lst->first_) + return; + + do { + slist_element_t* deletee = lst->first_; + lst->first_ = lst->first_->next_; + lst->delete_element(deletee->data_); + free(deletee); + } + while(lst->first_); + + lst->first_ = NULL; +} + +int slist_length(slist_t* lst) +{ + if(!lst || !lst->first_) + return 0; + + int len = 0; + slist_element_t* tmp; + for(tmp = lst->first_; tmp; tmp = tmp->next_) + len++; + + return len; +} diff --git a/src/slist.h b/src/slist.h new file mode 100644 index 0000000..bc7f6ad --- /dev/null +++ b/src/slist.h @@ -0,0 +1,53 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_slist_h_INCLUDED +#define SYDRA_slist_h_INCLUDED + +struct slist_element_struct { + void* data_; + struct slist_element_struct* next_; +}; +typedef struct slist_element_struct slist_element_t; + +slist_element_t* slist_get_last(slist_element_t* first); + +struct slist_struct { + void (*delete_element)(void* element); + slist_element_t* first_; +}; +typedef struct slist_struct slist_t; + +int slist_init(slist_t* lst, void (*delete_element)(void*)); +slist_element_t* slist_add(slist_t* lst, void* data); +void slist_remove(slist_t* lst, void* data); +void slist_clear(slist_t* lst); +int slist_length(slist_t* lst); + +#endif diff --git a/src/string_list.c b/src/string_list.c new file mode 100644 index 0000000..1309745 --- /dev/null +++ b/src/string_list.c @@ -0,0 +1,71 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "string_list.h" +#include "slist.h" + +int string_list_init(string_list_t* list) +{ + return slist_init(list, &free); +} + +void string_list_clear(string_list_t* list) +{ + slist_clear(list); +} + +int string_list_add(string_list_t* list, const char* string) +{ + if(!list) + return -1; + + char* tmp = strdup(string); + if(slist_add(list, tmp) == NULL) { + free(tmp); + return -2; + } + + return 0; +} + +void string_list_print(string_list_t* list, const char* head, const char* tail) +{ + if(!list) + return; + + slist_element_t* tmp = list->first_; + while(tmp) { + printf("%s%s%s", head, (char*)(tmp->data_), tail); + tmp = tmp->next_; + } +} diff --git a/src/string_list.h b/src/string_list.h new file mode 100644 index 0000000..aaa90fb --- /dev/null +++ b/src/string_list.h @@ -0,0 +1,43 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_string_list_h_INCLUDED +#define SYDRA_string_list_h_INCLUDED + +#include "slist.h" + +typedef slist_t string_list_t; + +int string_list_init(string_list_t* list); +void string_list_clear(string_list_t* list); +int string_list_add(string_list_t* list, const char* string); + +void string_list_print(string_list_t* list, const char* head, const char* tail); + +#endif diff --git a/src/sydra.c b/src/sydra.c new file mode 100644 index 0000000..bded404 --- /dev/null +++ b/src/sydra.c @@ -0,0 +1,255 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> + +#include <gst/gst.h> + +#include "datatypes.h" +#include "options.h" +#include "string_list.h" +#include "log.h" +#include "sig_handler.h" +#include "daemon.h" +#include "writer.h" + +static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data) +{ + GMainLoop *loop = (GMainLoop *)data; + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: { + log_printf(NOTICE, "End of stream"); + g_main_loop_quit(loop); + break; + } + case GST_MESSAGE_INFO: { + GError *info; + gst_message_parse_info(msg, &info, NULL); + log_printf(INFO, "%s", info->message); + g_error_free(info); + break; + } + case GST_MESSAGE_WARNING: { + GError *warning; + gst_message_parse_warning(msg, &warning, NULL); + log_printf(WARNING, "%s", warning->message); + g_error_free(warning); + break; + } + case GST_MESSAGE_ERROR: { + GError *error; + gst_message_parse_error(msg, &error, NULL); + log_printf(ERROR, "%s", error->message); + g_error_free(error); + g_main_loop_quit(loop); + break; + } + default: + break; + } + return TRUE; +} + +int main_loop(options_t* opt) +{ + log_printf(INFO, "entering main loop"); + + GMainLoop *loop; + GstElement *pipeline, *source; + GstBus *bus; + writer_t writer; + + loop = g_main_loop_new(NULL, FALSE); + pipeline = gst_pipeline_new("sydra"); + if(!pipeline || !loop) { + log_printf(ERROR, "the pipeline/loop object could not be created. Exiting."); + return -1; + } + + int ret = writer_init(&writer, loop, opt->name_format_, opt->mode_, opt->nocache_, opt->output_dir_, opt->interval_, opt->offset_, opt->post_process_); + if(ret) { + gst_object_unref(GST_OBJECT(pipeline)); + gst_object_unref(GST_OBJECT(loop)); + return ret; + } + + GError *error = NULL; + source = gst_parse_bin_from_description(opt->src_bin_desc_, TRUE, &error); + if(!source || error) { + log_printf(ERROR, "Source Bin Description Parser Error: %s", error ? error->message : "unknown"); + g_error_free(error); + gst_object_unref(GST_OBJECT(writer.sink_)); + gst_object_unref(GST_OBJECT(pipeline)); + gst_object_unref(GST_OBJECT(loop)); + return -1; + } + + gst_bin_add_many(GST_BIN(pipeline), source, writer.sink_, NULL); + gst_element_link_many(source, writer.sink_, NULL); + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + gst_bus_add_watch(bus, bus_call, loop); + gst_object_unref(bus); + + log_printf(INFO, "Set State: Paused"); + gst_element_set_state(pipeline, GST_STATE_PAUSED); + log_printf(INFO, "Set State: Playing"); + gst_element_set_state(pipeline, GST_STATE_PLAYING); + + signal_start(loop); + ret = writer_start(&writer); + if(!ret) { + g_main_loop_run(loop); + signal_stop(); + } + + log_printf(NOTICE, "Stopping pipeline"); + gst_element_set_state (pipeline, GST_STATE_NULL); + writer_stop(&writer); + gst_object_unref(GST_OBJECT(pipeline)); + + return ret; +} + +int main(int argc, char* argv[]) +{ + log_init(); + + options_t opt; + int ret = options_parse(&opt, argc, argv); + if(ret) { + if(ret > 0) + fprintf(stderr, "syntax error near: %s\n\n", argv[ret]); + if(ret == -2) + fprintf(stderr, "memory error on options_parse, exitting\n"); + if(ret == -3) + options_print_version(); + if(ret == -4) + fprintf(stderr, "the interval must be bigger than 0\n"); + + if(ret != -2 && ret != -3 && ret != -4) + options_print_usage(); + + if(ret == -1 || ret == -3) + ret = 0; + + options_clear(&opt); + log_close(); + exit(ret); + } + slist_element_t* tmp = opt.log_targets_.first_; + while(tmp) { + ret = log_add_target(tmp->data_); + if(ret) { + switch(ret) { + case -2: fprintf(stderr, "memory error on log_add_target, exitting\n"); break; + case -3: fprintf(stderr, "unknown log target: '%s', exitting\n", (char*)(tmp->data_)); break; + case -4: fprintf(stderr, "this log target is only allowed once: '%s', exitting\n", (char*)(tmp->data_)); break; + default: fprintf(stderr, "syntax error near: '%s', exitting\n", (char*)(tmp->data_)); break; + } + + options_clear(&opt); + log_close(); + exit(ret); + } + tmp = tmp->next_; + } + + log_printf(NOTICE, "just started..."); + options_parse_post(&opt); + + priv_info_t priv; + if(opt.username_) + if(priv_init(&priv, opt.username_, opt.groupname_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + FILE* pid_file = NULL; + if(opt.pid_file_) { + pid_file = fopen(opt.pid_file_, "w"); + if(!pid_file) { + log_printf(WARNING, "unable to open pid file: %s", strerror(errno)); + } + } + + if(opt.chroot_dir_) + if(do_chroot(opt.chroot_dir_)) { + options_clear(&opt); + log_close(); + exit(-1); + } + if(opt.username_) + if(priv_drop(&priv)) { + options_clear(&opt); + log_close(); + exit(-1); + } + + if(opt.daemonize_) { + pid_t oldpid = getpid(); + daemonize(); + log_printf(INFO, "running in background now (old pid: %d)", oldpid); + } + + if(pid_file) { + pid_t pid = getpid(); + fprintf(pid_file, "%d", pid); + fclose(pid_file); + } + + signal_init(); + gst_init(NULL, NULL); + const gchar *nano_str; + guint major, minor, micro, nano; + gst_version(&major, &minor, µ, &nano); + if (nano == 1) + nano_str = " (CVS)"; + else if (nano == 2) + nano_str = " (Prerelease)"; + else + nano_str = ""; + log_printf(NOTICE, "sydra linked against GStreamer %d.%d.%d%s", major, minor, micro, nano_str); + + ret = main_loop(&opt); + + options_clear(&opt); + + log_printf(NOTICE, "sydra shutdown"); + + gst_deinit(); + log_close(); + + return ret; +} diff --git a/src/sysexec.c b/src/sysexec.c new file mode 100644 index 0000000..2d740ce --- /dev/null +++ b/src/sysexec.c @@ -0,0 +1,226 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "datatypes.h" + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/select.h> +#include <stdlib.h> +#include <string.h> + +#include "sysexec.h" +#include "log.h" + +char** dup_ptrptr(char* const ptrptr[]) +{ + if(!ptrptr) + return NULL; + + int n = 0; + while(ptrptr[n]) + n++; + + char** my_ptrptr; + my_ptrptr = malloc((n+1)*sizeof(char*)); + if(!my_ptrptr) + return NULL; + + int i; + for(i = 0; i < n; ++i) { + my_ptrptr[i] = strdup(ptrptr[i]); + if(!my_ptrptr[i]) { + i--; + for(; i >= 0; --i) + free(my_ptrptr[i]); + + free(my_ptrptr); + return NULL; + } + } + + my_ptrptr[n] = NULL; + + return my_ptrptr; +} + +void free_ptrptr(char** ptrptr) +{ + if(!ptrptr) + return; + + int i; + for(i = 0; ptrptr[i]; ++i) + free(ptrptr[i]); + + free(ptrptr); +} + +child_t* new_child(const char* script, char* const argv[], char* const evp[]) +{ + child_t* new_child; + + new_child = malloc(sizeof(child_t)); + if(!new_child) + return NULL; + + new_child->pid_ = -1; + new_child->err_fd_ = -1; + new_child->script_ = strdup(script); + if(!new_child->script_) { + free(new_child); + return NULL; + } + + new_child->argv_ = dup_ptrptr(argv); + if(!new_child->argv_) { + free(new_child->script_); + free(new_child); + return NULL; + + } + + new_child->evp_ = dup_ptrptr(evp); + if(!new_child->evp_) { + free_ptrptr(new_child->argv_); + free(new_child->script_); + free(new_child); + return NULL; + } + return new_child; +} + +void free_child(child_t* child) +{ + if(!child) + return; + + free_ptrptr(child->argv_); + free_ptrptr(child->evp_); + if(child->script_) + free(child->script_); + if(child->err_fd_ >= 0) close(child->err_fd_); + free(child); +} + +child_t* sydra_exec(const char* script, char* const argv[], char* const evp[]) +{ + if(!script) + return NULL; + + child_t* child = new_child(script, argv, evp); + if(!child) + return NULL; + + int pipefd[2]; + if(pipe(pipefd) == -1) { + log_printf(ERROR, "executing script '%s' failed: pipe() error: %s", child->script_, strerror(errno)); // TODO: thread safe strerror + free_child(child); + return NULL; + } + + pid_t pid; + pid = fork(); + if(pid == -1) { + log_printf(ERROR, "executing script '%s' failed: fork() error: %s", child->script_, strerror(errno)); // TODO: thread safe strerror + close(pipefd[0]); + close(pipefd[1]); + free_child(child); + return NULL; + } + + if(!pid) { + int fd; + for (fd=getdtablesize();fd>=0;--fd) // close all file descriptors + if(fd != pipefd[1]) close(fd); + + fd = open("/dev/null",O_RDWR); // stdin + if(fd == -1) + log_printf(WARNING, "can't open stdin"); + else { + if(dup(fd) == -1) // stdout + log_printf(WARNING, "can't open stdout"); + if(dup(fd) == -1) // stderr + log_printf(WARNING, "can't open stderr"); + } + execve(child->script_, child->argv_, child->evp_); + // if execve returns, an error occurred, but logging doesn't work + // because we closed all file descriptors, so just write errno to + // pipe and call exit + int len = write(pipefd[1], (void*)(&errno), sizeof(errno)); + if(len) exit(-1); + exit(-1); + } + close(pipefd[1]); + + child->pid_ = pid; + child->err_fd_ = pipefd[0]; + + log_printf(INFO, "called script '%s' with pid %d", child->script_, child->pid_); + + return child; +} + +int sydra_waitpid(child_t* child, int* status_return) +{ + int status = 0; + pid_t pid = waitpid(child->pid_, &status, WNOHANG); + if(!pid || (pid < 0 && errno == ECHILD)) + return 0; + if(pid < 0) { + log_printf(ERROR, "waitpid returned with error: %s", strerror(errno)); // TODO: thread safe strerror + return pid; + } + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(child->err_fd_, &rfds); + struct timeval tv = { 0 , 0 }; + if(select(child->err_fd_+1, &rfds, NULL, NULL, &tv) == 1) { + int err = 0; + if(read(child->err_fd_, (void*)(&err), sizeof(err)) >= sizeof(err)) { + log_printf(INFO, "script '%s' exec() error: %s", child->script_, strerror(err)); // TODO: thread safe strerror + return -1; + } + } + if(WIFEXITED(status)) + log_printf(INFO, "script '%s' (pid %d) returned %d", child->script_, child->pid_, WEXITSTATUS(status)); + else if(WIFSIGNALED(status)) + log_printf(INFO, "script '%s' (pid %d) terminated after signal %d", child->script_, child->pid_, WTERMSIG(status)); + else + log_printf(INFO, "executing script '%s' (pid %d): unkown error", child->script_, child->pid_); + + if(status_return) *status_return = status; + + return 1; +} diff --git a/src/sysexec.h b/src/sysexec.h new file mode 100644 index 0000000..dc00bb6 --- /dev/null +++ b/src/sysexec.h @@ -0,0 +1,51 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_sysexec_h_INCLUDED +#define SYDRA_sysexec_h_INCLUDED + +#include <sys/types.h> +#include "options.h" + +struct child_struct { + pid_t pid_; + char* script_; + int err_fd_; + char** argv_; + char** evp_; +}; +typedef struct child_struct child_t; + +child_t* new_child(const char* script, char* const argv[], char* const evp[]); +void free_child(child_t* child); + +child_t* sydra_exec(const char* script, char* const argv[], char* const evp[]); +int sydra_waitpid(child_t* child, int* status); + +#endif diff --git a/src/writer.c b/src/writer.c new file mode 100644 index 0000000..f282f8d --- /dev/null +++ b/src/writer.c @@ -0,0 +1,243 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <gst/gst.h> + +#include <sys/types.h> + +#include <errno.h> +#include <string.h> +#include <time.h> + +#include "writer.h" + +#include "datatypes.h" +#include "log.h" +#include "file_list.h" + +static int init_time_boundaries(writer_t* writer) +{ + if(!writer) + return -1; + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + + struct tm bd_time; + localtime_r(&(now.tv_sec), &bd_time); + + writer->current_ = file_list_add(&(writer->files_), &bd_time, "current", writer->name_format_, writer->output_dir_, writer->mode_, writer->nocache_); + if(writer->current_ == NULL) return -2; + + bd_time.tm_sec = 0; + bd_time.tm_min = 0; + time_t T = mktime(&bd_time); + T+=3600; + localtime_r(&T, &bd_time); + + struct timespec b = { T, 0 }; + writer->next_boundary_ = b; + + writer->next_ = file_list_add(&(writer->files_), &bd_time, "next", writer->name_format_, writer->output_dir_, writer->mode_, writer->nocache_); + if(writer->next_ == NULL) return -2; + + return 0; +} + +static void added_cb(GstElement* sink, gint fd, gpointer data) +{ + gint num_fds; + g_object_get(G_OBJECT(sink), "num_fds", &num_fds, NULL); + log_printf(INFO, "fdsink: successfully added client %d (sink has now %d fds)", fd, num_fds); +} + +static void removed_cb(GstElement* sink, gint fd, gpointer data) +{ + gint num_fds; + g_object_get(G_OBJECT(sink), "num_fds", &num_fds, NULL); + log_printf(INFO, "fdsink: successfully removed client %d (sink has now %d fds)", fd, num_fds); +} + +static void fdremoved_cb(GstElement* sink, gint fd, gpointer data) +{ + gint num_fds; + g_object_get(G_OBJECT(sink), "num_fds", &num_fds, NULL); + log_printf(INFO, "fdsink: successfully removed fd %d (sink has now %d fds)", fd, num_fds); + + writer_t *writer = (writer_t*)data; + if(writer->post_process_) + file_list_call_post_process(&(writer->files_), fd, writer->post_process_); + else + file_list_remove(&(writer->files_), fd); +} + +int writer_init(writer_t* writer, GMainLoop *loop, const char* name_format, mode_t mode, int nocache, const char* output_dir, int interval, int offset, char* post_process) +{ + if(!writer) + return -1; + + writer->loop_ = loop; + writer->sink_ = gst_element_factory_make("multifdsink", "writer"); + if(!writer->sink_) { + log_printf(ERROR, "the writer object could not be created. Exiting."); + return -1; + } + // TODO: how the heck should we get the right value? 3 means keyframe... + g_object_set(G_OBJECT(writer->sink_), "recover-policy", 3, NULL); + g_signal_connect(G_OBJECT(writer->sink_), "client-added", G_CALLBACK(added_cb), writer); + g_signal_connect(G_OBJECT(writer->sink_), "client-removed", G_CALLBACK(removed_cb), writer); + g_signal_connect(G_OBJECT(writer->sink_), "client-fd-removed", G_CALLBACK(fdremoved_cb), writer); + + writer->clock_ = gst_system_clock_obtain(); + if(!writer->clock_) { + log_printf(ERROR, "unable to obtain the system clock"); + return -1; + } + writer->name_format_ = name_format; + writer->mode_ = mode; + writer->nocache_ = nocache; + writer->output_dir_ = output_dir; + writer->interval_ = interval * GST_MSECOND; + writer->offset_ = offset * GST_MSECOND; + writer->post_process_ = post_process; + writer->clock_id_ = NULL; + writer->thread_ = NULL; + int ret = file_list_init(&(writer->files_)); + if(ret) return ret; + + return init_time_boundaries(writer); +} + +static void add_fd(writer_t* writer, int fd) +{ + log_printf(INFO, "adding fd %d to fdsink", fd); + g_signal_emit_by_name(G_OBJECT(writer->sink_), "add", fd, NULL); +} + +static void remove_fd(writer_t* writer, int fd) +{ + log_printf(INFO, "removing fd %d from fdsink", fd); + g_signal_emit_by_name(G_OBJECT(writer->sink_), "remove-flush", fd, NULL); +} + +static int check_boundaries(writer_t* writer) +{ + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + + GstClockTime tmp = GST_TIMESPEC_TO_TIME(now); + GstClockTime boundary = GST_TIMESPEC_TO_TIME(writer->next_boundary_); + tmp -= writer->offset_; + if(tmp >= boundary) { + struct tm now_bd; + localtime_r(&(now.tv_sec), &now_bd); + log_printf(INFO, "boundary reached! it's now: %02d:%02d:%02d.%06d on %d.%d.%d%s (%d ms offset)", now_bd.tm_hour, now_bd.tm_min, now_bd.tm_sec, now.tv_nsec/1000, now_bd.tm_mday, now_bd.tm_mon+1, now_bd.tm_year+1900, now_bd.tm_isdst > 0 ? " (DST)": "", GST_TIME_AS_MSECONDS(writer->offset_)); + + int ret = open_file(writer->next_); + if(ret) return ret; // TODO: stop writer on open_file error ??? + + add_fd(writer, writer->next_->fd_); + remove_fd(writer, writer->current_->fd_); + writer->current_ = writer->next_; + + writer->next_boundary_.tv_sec += 3600; + struct tm bd_time; + localtime_r(&(writer->next_boundary_.tv_sec), &bd_time); + + writer->next_ = file_list_add(&(writer->files_), &bd_time, "next", writer->name_format_, writer->output_dir_, writer->mode_, writer->nocache_); + if(writer->next_ == NULL) return -2; + } + + return 0; +} + +static gpointer writer_thread_func(gpointer data) +{ + writer_t *writer = (writer_t*)data; + log_printf(NOTICE, "writer thread started"); + + for(;;) { + GstClockReturn wret = gst_clock_id_wait(writer->clock_id_, NULL); + if(GST_CLOCK_UNSCHEDULED == wret) + break; + if(GST_CLOCK_EARLY == wret) + continue; + + int ret = check_boundaries(writer); + if(ret) break; + + ret = file_list_waitpid(&(writer->files_)); + if(ret) break; + } + + log_printf(NOTICE, "writer thread stopped"); + g_main_loop_quit(writer->loop_); + return NULL; +} + +int writer_start(writer_t* writer) +{ + if(!writer) + return -1; + + int ret = open_file(writer->current_); + if(ret) + return ret; + + add_fd(writer, writer->current_->fd_); + + writer->clock_id_ = gst_clock_new_periodic_id(writer->clock_, 0, writer->interval_); + if(!writer->clock_id_) { + log_printf(ERROR, "clock id could not be created"); + return -1; + } + writer->thread_ = g_thread_new("writer", writer_thread_func, writer); + if(!writer->thread_) { + log_printf(ERROR, "writer thread could not be started"); + return -1; + } + + return 0; +} + +void writer_stop(writer_t* writer) +{ + if(!writer) + return; + + file_list_clear(&(writer->files_)); + if(writer->clock_id_) { + gst_clock_id_unschedule(writer->clock_id_); + } + if(writer->thread_) { + log_printf(NOTICE, "waiting for writer thread to stop"); + g_thread_join(writer->thread_); + } + gst_object_unref(GST_OBJECT(writer->clock_)); +} diff --git a/src/writer.h b/src/writer.h new file mode 100644 index 0000000..99993db --- /dev/null +++ b/src/writer.h @@ -0,0 +1,64 @@ +/* + * sydra + * + * sydra is a toolbox which allows you to set up multiple bidirectional + * Video/Audio streams from external locations. + * sydra has been written to be used for the Elevate Festival in Graz + * Austria in order to involve external locations to present themselves + * at the festival. + * Sydra is based on GStreamer and is written in C. + * + * + * Copyright (C) 2014 Christian Pointner <equinox@helsinki.at> + * + * This file is part of sydra. + * + * sydra 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. + * + * sydra 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 sydra. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYDRA_writer_h_INCLUDED +#define SYDRA_writer_h_INCLUDED + +#include <gst/gst.h> +#include <glib.h> +#include <time.h> +#include <sys/types.h> + +#include "file_list.h" + +struct writer_struct { + GMainLoop *loop_; + GstElement* sink_; + GstClock* clock_; + GstClockID clock_id_; + GThread* thread_; + const char* name_format_; + const char* output_dir_; + mode_t mode_; + int nocache_; + GstClockTime interval_; + GstClockTime offset_; + char* post_process_; + file_list_t files_; + file_t* current_; + file_t* next_; + struct timespec next_boundary_; +}; +typedef struct writer_struct writer_t; + +int writer_init(writer_t* writer, GMainLoop *loop, const char* name_format, mode_t mode, int nocache, const char* output_dir, int interval, int offset, char* post_process); +int writer_start(writer_t* writer); +void writer_stop(writer_t* writer); + +#endif |