Change-Id: I8102144ab1508fe815be84d727f6fa3234fd0994changes/03/9503/6
| @ -1 +1,2 @@ | |||
| daemon/rtpengine /usr/sbin/ | |||
| recording-daemon/rtpengine-recording /usr/sbin/ | |||
| @ -0,0 +1,5 @@ | |||
| .depend | |||
| *.o | |||
| core | |||
| core.* | |||
| .ycm_extra_conf.pyc | |||
| @ -0,0 +1,102 @@ | |||
| import os | |||
| import ycm_core | |||
| from clang_helpers import PrepareClangFlags | |||
| # Set this to the absolute path to the folder (NOT the file!) containing the | |||
| # compile_commands.json file to use that instead of 'flags'. See here for | |||
| # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html | |||
| # Most projects will NOT need to set this to anything; you can just change the | |||
| # 'flags' list of compilation flags. Notice that YCM itself uses that approach. | |||
| compilation_database_folder = '' | |||
| # These are the compilation flags that will be used in case there's no | |||
| # compilation database set. | |||
| flags = [ | |||
| '-g', | |||
| '-Wall', | |||
| '-pthread', | |||
| '-fno-strict-aliasing', | |||
| '-I/usr/include/glib-2.0', | |||
| '-I/usr/lib/x86_64-linux-gnu/glib-2.0/include', | |||
| '-pthread', | |||
| '-D_GNU_SOURCE', | |||
| '-D__DEBUG=1', | |||
| '-D__YCM=1', | |||
| '-O2', | |||
| '-fstack-protector', | |||
| '--param=ssp-buffer-size=4', | |||
| '-Wformat', | |||
| '-Werror=format-security', | |||
| '-D_FORTIFY_SOURCE=2', | |||
| # THIS IS IMPORTANT! Without a "-std=<something>" flag, clang won't know which | |||
| # language to use when compiling headers. So it will guess. Badly. So C++ | |||
| # headers will be compiled as C headers. You don't want that so ALWAYS specify | |||
| # a "-std=<something>". | |||
| # For a C project, you would set this to something like 'c99' instead of | |||
| # 'c++11'. | |||
| '-std=c99', | |||
| # ...and the same thing goes for the magic -x option which specifies the | |||
| # language that the files to be compiled are written in. This is mostly | |||
| # relevant for c++ headers. | |||
| # For a C project, you would set this to 'c' instead of 'c++'. | |||
| '-x', | |||
| 'c', | |||
| ] | |||
| if compilation_database_folder: | |||
| database = ycm_core.CompilationDatabase( compilation_database_folder ) | |||
| else: | |||
| database = None | |||
| def DirectoryOfThisScript(): | |||
| return os.path.dirname( os.path.abspath( __file__ ) ) | |||
| def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): | |||
| if not working_directory: | |||
| return flags | |||
| new_flags = [] | |||
| make_next_absolute = False | |||
| path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] | |||
| for flag in flags: | |||
| new_flag = flag | |||
| if make_next_absolute: | |||
| make_next_absolute = False | |||
| if not flag.startswith( '/' ): | |||
| new_flag = os.path.join( working_directory, flag ) | |||
| for path_flag in path_flags: | |||
| if flag == path_flag: | |||
| make_next_absolute = True | |||
| break | |||
| if flag.startswith( path_flag ): | |||
| path = flag[ len( path_flag ): ] | |||
| new_flag = path_flag + os.path.join( working_directory, path ) | |||
| break | |||
| if new_flag: | |||
| new_flags.append( new_flag ) | |||
| return new_flags | |||
| def FlagsForFile( filename ): | |||
| if database: | |||
| # Bear in mind that compilation_info.compiler_flags_ does NOT return a | |||
| # python list, but a "list-like" StringVec object | |||
| compilation_info = database.GetCompilationInfoForFile( filename ) | |||
| final_flags = PrepareClangFlags( | |||
| MakeRelativePathsInFlagsAbsolute( | |||
| compilation_info.compiler_flags_, | |||
| compilation_info.compiler_working_dir_ ), | |||
| filename ) | |||
| else: | |||
| relative_to = DirectoryOfThisScript() | |||
| final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) | |||
| return { | |||
| 'flags': final_flags, | |||
| 'do_cache': True | |||
| } | |||
| @ -0,0 +1,58 @@ | |||
| TARGET= rtpengine-recording | |||
| CC?=gcc | |||
| CFLAGS= -g -Wall -pthread | |||
| CFLAGS+= -std=c99 | |||
| CFLAGS+= -D_GNU_SOURCE -D_POSIX_SOURCE -D_POSIX_C_SOURCE | |||
| CFLAGS+= `pkg-config --cflags glib-2.0` | |||
| CFLAGS+= `pkg-config --cflags gthread-2.0` | |||
| #CFLAGS+= `pcre-config --cflags` | |||
| ifeq ($(DBG),yes) | |||
| CFLAGS+= -D__DEBUG=1 | |||
| else | |||
| CFLAGS+= -O3 | |||
| endif | |||
| LDFLAGS= -lm | |||
| LDFLAGS+= `pkg-config --libs glib-2.0` | |||
| LDFLAGS+= `pkg-config --libs gthread-2.0` | |||
| #LDFLAGS+= `pcre-config --libs` | |||
| ifneq ($(DBG),yes) | |||
| DPKG_BLDFLGS= $(shell which dpkg-buildflags 2>/dev/null) | |||
| ifneq ($(DPKG_BLDFLGS),) | |||
| # support http://wiki.debian.org/Hardening for >=wheezy | |||
| CFLAGS+= `dpkg-buildflags --get CFLAGS` | |||
| CPPFLAGS+= `dpkg-buildflags --get CPPFLAGS` | |||
| LDFLAGS+= `dpkg-buildflags --get LDFLAGS` | |||
| endif | |||
| endif | |||
| SRCS= epoll.c garbage.c inotify.c main.c metafile.c stream.c aux.c | |||
| OBJS= $(SRCS:.c=.o) | |||
| .PHONY: all dep clean tests debug | |||
| all: | |||
| $(MAKE) $(TARGET) | |||
| debug: | |||
| $(MAKE) DBG=yes all | |||
| dep: .depend | |||
| clean: | |||
| rm -f $(OBJS) $(TARGET) .depend core core.* | |||
| .depend: $(SRCS) Makefile | |||
| $(CC) $(CFLAGS) -M $(SRCS) | sed -e 's/:/ .depend:/' > .depend | |||
| $(TARGET): $(OBJS) .depend Makefile | |||
| $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) | |||
| $(OBJS): Makefile | |||
| include .depend | |||
| @ -0,0 +1,21 @@ | |||
| #include "aux.h" | |||
| #include <stdio.h> | |||
| #include <stdarg.h> | |||
| int __thread __sscanf_hack_var; | |||
| int __sscanf_match(const char *str, const char *fmt, ...) { | |||
| va_list ap; | |||
| __sscanf_hack_var = 0; // to make sure that sscanf consumes the entire string | |||
| va_start(ap, fmt); | |||
| int ret = vsscanf(str, fmt, ap); | |||
| va_end(ap); | |||
| if (__sscanf_hack_var == 0) | |||
| return 0; | |||
| return ret; | |||
| } | |||
| @ -0,0 +1,9 @@ | |||
| #ifndef _AUX_H_ | |||
| #define _AUX_H_ | |||
| extern int __thread __sscanf_hack_var; | |||
| #define sscanf_match(str, format, ...) __sscanf_match(str, format "%n", ##__VA_ARGS__, &__sscanf_hack_var) | |||
| int __sscanf_match(const char *str, const char *fmt, ...) __attribute__ ((__format__ (__scanf__, 2, 3))); | |||
| #endif | |||
| @ -0,0 +1,64 @@ | |||
| #include "epoll.h" | |||
| #include <sys/epoll.h> | |||
| #include <glib.h> | |||
| #include <pthread.h> | |||
| #include <unistd.h> | |||
| #include "log.h" | |||
| #include "main.h" | |||
| #include "garbage.h" | |||
| static int epoll_fd = -1; | |||
| void epoll_setup(void) { | |||
| epoll_fd = epoll_create1(0); | |||
| if (epoll_fd == -1) | |||
| die_errno("epoll_create1 failed"); | |||
| } | |||
| int epoll_add(int fd, uint32_t events, handler_t *handler) { | |||
| struct epoll_event epev = { .events = events | EPOLLET, .data = { .ptr = handler } }; | |||
| int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &epev); | |||
| return ret; | |||
| } | |||
| void epoll_del(int fd) { | |||
| epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); | |||
| } | |||
| void *poller_thread(void *ptr) { | |||
| struct epoll_event epev; | |||
| unsigned int me_num = GPOINTER_TO_UINT(ptr); | |||
| dbg("poller thread %u running", me_num); | |||
| while (!shutdown_flag) { | |||
| pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); | |||
| int ret = epoll_wait(epoll_fd, &epev, 1, 10000); | |||
| pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); | |||
| if (ret == -1) | |||
| die_errno("epoll_wait failed"); | |||
| if (ret > 0) { | |||
| dbg("thread %u handling event", me_num); | |||
| handler_t *handler = epev.data.ptr; | |||
| handler->func(handler); | |||
| } | |||
| garbage_collect(me_num); | |||
| } | |||
| return NULL; | |||
| } | |||
| void epoll_cleanup(void) { | |||
| close(epoll_fd); | |||
| } | |||
| @ -0,0 +1,20 @@ | |||
| #ifndef _EPOLL_H_ | |||
| #define _EPOLL_H_ | |||
| #include <sys/types.h> | |||
| #include <sys/epoll.h> | |||
| #include <stdint.h> | |||
| #include "types.h" | |||
| void epoll_setup(void); | |||
| void epoll_cleanup(void); | |||
| int epoll_add(int fd, uint32_t events, handler_t *handler); | |||
| void epoll_del(int fd); | |||
| void *poller_thread(void *ptr); | |||
| #endif | |||
| @ -0,0 +1,96 @@ | |||
| #include "garbage.h" | |||
| #include <glib.h> | |||
| #include <pthread.h> | |||
| #include "log.h" | |||
| typedef struct { | |||
| void *ptr; | |||
| void (*free_func)(void *); | |||
| int *wait_threads; | |||
| unsigned int array_len; | |||
| unsigned int threads_left; | |||
| } garbage_t; | |||
| static pthread_mutex_t garbage_lock = PTHREAD_MUTEX_INITIALIZER; | |||
| static GQueue garbage = G_QUEUE_INIT; | |||
| static volatile int garbage_thread_num; | |||
| unsigned int garbage_new_thread_num(void) { | |||
| return g_atomic_int_add(&garbage_thread_num, 1); | |||
| } | |||
| void garbage_add(void *ptr, free_func_t *free_func) { | |||
| // Each running poller thread has a unique number associated with it, starting | |||
| // with 0. A garbage entry uses an array of boolean flags, one for each running | |||
| // thread, to keep track of which threads have seen this entry. Once a garbage | |||
| // entry has been seen by all threads, the free function is finally called. | |||
| // This is to make sure that all poller threads have left epoll_wait() after | |||
| // an fd has been removed from the watch list. | |||
| garbage_t *garb = g_slice_alloc(sizeof(*garb)); | |||
| garb->ptr = ptr; | |||
| garb->free_func = free_func; | |||
| pthread_mutex_lock(&garbage_lock); | |||
| garb->array_len = g_atomic_int_get(&garbage_thread_num); | |||
| garb->threads_left = garb->array_len; | |||
| garb->wait_threads = malloc(sizeof(int) * garb->array_len); | |||
| memset(garb->wait_threads, 0, sizeof(int) * garb->array_len); | |||
| g_queue_push_tail(&garbage, garb); | |||
| pthread_mutex_unlock(&garbage_lock); | |||
| } | |||
| static void garbage_collect1(garbage_t *garb) { | |||
| garb->free_func(garb->ptr); | |||
| free(garb->wait_threads); | |||
| g_slice_free1(sizeof(*garb), garb); | |||
| } | |||
| void garbage_collect(unsigned int num) { | |||
| dbg("running garbage collection thread %u", num); | |||
| restart: | |||
| pthread_mutex_lock(&garbage_lock); | |||
| for (GList *l = garbage.head; l; l = l->next) { | |||
| garbage_t *garb = l->data; | |||
| // has this been created before we were running? | |||
| if (garb->array_len <= num) | |||
| continue; | |||
| // have we processed this already? | |||
| if (garb->wait_threads[num]) | |||
| continue; | |||
| dbg("marking garbage entry %p as seen by %u with %u threads left", garb, num, | |||
| garb->threads_left); | |||
| garb->wait_threads[num] = 1; | |||
| garb->threads_left--; | |||
| // anything left? | |||
| if (!garb->threads_left) { | |||
| // remove from list and process | |||
| g_queue_delete_link(&garbage, l); | |||
| pthread_mutex_unlock(&garbage_lock); | |||
| garbage_collect1(garb); | |||
| goto restart; | |||
| } | |||
| } | |||
| pthread_mutex_unlock(&garbage_lock); | |||
| } | |||
| void garbage_collect_all(void) { | |||
| garbage_t *garb; | |||
| while ((garb = g_queue_pop_head(&garbage))) | |||
| garbage_collect1(garb); | |||
| } | |||
| @ -0,0 +1,11 @@ | |||
| #ifndef _GARBAGE_H_ | |||
| #define _GARBAGE_H_ | |||
| typedef void free_func_t(void *); | |||
| unsigned int garbage_new_thread_num(void); | |||
| void garbage_add(void *ptr, free_func_t *free_func); | |||
| void garbage_collect(unsigned int num); | |||
| void garbage_collect_all(void); | |||
| #endif | |||
| @ -0,0 +1,79 @@ | |||
| #include "inotify.h" | |||
| #include <sys/inotify.h> | |||
| #include <limits.h> | |||
| #include <stdlib.h> | |||
| #include <stdio.h> | |||
| #include <unistd.h> | |||
| #include "log.h" | |||
| #include "main.h" | |||
| #include "epoll.h" | |||
| #include "metafile.h" | |||
| static int inotify_fd = -1; | |||
| static handler_func inotify_handler_func; | |||
| static handler_t inotify_handler = { | |||
| .func = inotify_handler_func, | |||
| }; | |||
| static void inotify_close_write(struct inotify_event *inev) { | |||
| dbg("inotify close_write(%s)", inev->name); | |||
| metafile_change(inev->name); | |||
| } | |||
| static void inotify_delete(struct inotify_event *inev) { | |||
| dbg("inotify delete(%s)", inev->name); | |||
| metafile_delete(inev->name); | |||
| } | |||
| static void inotify_handler_func(handler_t *handler) { | |||
| char buf[4 * (sizeof(struct inotify_event) + NAME_MAX + 1)]; | |||
| while (1) { | |||
| int ret = read(inotify_fd, buf, sizeof(buf)); | |||
| if (ret == -1) { | |||
| if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) | |||
| break; | |||
| die_errno("read on inotify fd failed"); | |||
| } | |||
| if (ret == 0) | |||
| die("EOF on inotify fd"); | |||
| char *bufend = buf + ret; | |||
| char *bufhead = buf; | |||
| while (bufhead < bufend) { | |||
| struct inotify_event *inev = (void *) bufhead; | |||
| if ((inev->mask & IN_DELETE)) | |||
| inotify_delete(inev); | |||
| if ((inev->mask & IN_CLOSE_WRITE)) | |||
| inotify_close_write(inev); | |||
| bufhead += sizeof(*inev) + inev->len; | |||
| } | |||
| } | |||
| } | |||
| void inotify_setup(void) { | |||
| inotify_fd = inotify_init1(IN_NONBLOCK); | |||
| if (inotify_fd == -1) | |||
| die_errno("inotify_init1 failed"); | |||
| int ret = inotify_add_watch(inotify_fd, SPOOL_DIR, IN_CLOSE_WRITE | IN_DELETE); | |||
| if (ret == -1) | |||
| die_errno("inotify_add_watch failed"); | |||
| if (epoll_add(inotify_fd, EPOLLIN, &inotify_handler)) | |||
| die_errno("failed to add inotify_fd to epoll"); | |||
| } | |||
| void inotify_cleanup(void) { | |||
| close(inotify_fd); | |||
| } | |||
| @ -0,0 +1,7 @@ | |||
| #ifndef _INOTIFY_H_ | |||
| #define _INOTIFY_H_ | |||
| void inotify_setup(void); | |||
| void inotify_cleanup(void); | |||
| #endif | |||
| @ -0,0 +1,15 @@ | |||
| #ifndef _LOG_H_ | |||
| #define _LOG_H_ | |||
| #include <stdio.h> | |||
| #include <syslog.h> | |||
| #include <errno.h> | |||
| #include <string.h> | |||
| #include <stdlib.h> | |||
| #define die(fmt, ...) do { ilog(LOG_CRIT, "Fatal error: " fmt, ##__VA_ARGS__); exit(-1); } while (0) | |||
| #define die_errno(msg) die("%s: %s", msg, strerror(errno)) | |||
| #define ilog(fclt, fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) | |||
| #define dbg(fmt, ...) ilog(LOG_DEBUG, fmt, ##__VA_ARGS__) | |||
| #endif | |||
| @ -0,0 +1,105 @@ | |||
| #include "main.h" | |||
| #include <stdio.h> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include <pthread.h> | |||
| #include <glib.h> | |||
| #include <unistd.h> | |||
| #include <signal.h> | |||
| #include "log.h" | |||
| #include "epoll.h" | |||
| #include "inotify.h" | |||
| #include "metafile.h" | |||
| #include "garbage.h" | |||
| static GQueue threads = G_QUEUE_INIT; // only accessed from main thread | |||
| volatile int shutdown_flag; | |||
| static void signals(void) { | |||
| sigset_t ss; | |||
| sigfillset(&ss); | |||
| sigdelset(&ss, SIGABRT); | |||
| sigdelset(&ss, SIGSEGV); | |||
| sigdelset(&ss, SIGQUIT); | |||
| sigprocmask(SIG_SETMASK, &ss, NULL); | |||
| pthread_sigmask(SIG_SETMASK, &ss, NULL); | |||
| } | |||
| static void setup(void) { | |||
| signals(); | |||
| metafile_setup(); | |||
| epoll_setup(); | |||
| inotify_setup(); | |||
| } | |||
| static void start_poller_thread(void) { | |||
| pthread_t *thr = g_slice_alloc(sizeof(*thr)); | |||
| int ret = pthread_create(thr, NULL, poller_thread, | |||
| GUINT_TO_POINTER(garbage_new_thread_num())); | |||
| if (ret) | |||
| die_errno("pthread_create failed"); | |||
| g_queue_push_tail(&threads, thr); | |||
| } | |||
| static void wait_threads_finish(void) { | |||
| pthread_t *thr; | |||
| while ((thr = g_queue_pop_head(&threads))) { | |||
| pthread_cancel(*thr); | |||
| pthread_join(*thr, NULL); | |||
| g_slice_free1(sizeof(*thr), thr); | |||
| } | |||
| } | |||
| static void wait_for_signal(void) { | |||
| sigset_t ss; | |||
| int ret, sig; | |||
| sigemptyset(&ss); | |||
| sigaddset(&ss, SIGINT); | |||
| sigaddset(&ss, SIGTERM); | |||
| while (1) { | |||
| ret = sigwait(&ss, &sig); | |||
| if (ret == -1) { | |||
| if (errno == EAGAIN || errno == EINTR) | |||
| continue; | |||
| abort(); | |||
| } | |||
| shutdown_flag = 1; | |||
| break; | |||
| } | |||
| } | |||
| static void cleanup(void) { | |||
| garbage_collect_all(); | |||
| metafile_cleanup(); | |||
| inotify_cleanup(); | |||
| epoll_cleanup(); | |||
| } | |||
| int main() { | |||
| setup(); | |||
| for (int i = 0; i < NUM_THREADS; i++) | |||
| start_poller_thread(); | |||
| wait_for_signal(); | |||
| dbg("shutting down"); | |||
| wait_threads_finish(); | |||
| cleanup(); | |||
| } | |||
| @ -0,0 +1,13 @@ | |||
| #ifndef _MAIN_H_ | |||
| #define _MAIN_H_ | |||
| #define SPOOL_DIR "/var/spool/rtpengine" | |||
| #define PROC_DIR "/proc/rtpengine/0/calls" | |||
| #define NUM_THREADS 8 | |||
| extern volatile int shutdown_flag; | |||
| #endif | |||
| @ -0,0 +1,241 @@ | |||
| #include "metafile.h" | |||
| #include <glib.h> | |||
| #include <pthread.h> | |||
| #include <sys/types.h> | |||
| #include <fcntl.h> | |||
| #include <stdlib.h> | |||
| #include <unistd.h> | |||
| #include <limits.h> | |||
| #include "log.h" | |||
| #include "stream.h" | |||
| #include "garbage.h" | |||
| #include "main.h" | |||
| #include "aux.h" | |||
| static pthread_mutex_t metafiles_lock = PTHREAD_MUTEX_INITIALIZER; | |||
| static GHashTable *metafiles; | |||
| //static pcre_t stream_interface_re, | |||
| //stream_details_re; | |||
| static void meta_free(void *ptr) { | |||
| metafile_t *mf = ptr; | |||
| dbg("freeing metafile info for %s", mf->name); | |||
| g_string_chunk_free(mf->gsc); | |||
| for (int i = 0; i < mf->streams->len; i++) { | |||
| stream_t *stream = g_ptr_array_index(mf->streams, i); | |||
| stream_close(stream); // should be closed already | |||
| stream_free(stream); | |||
| } | |||
| g_ptr_array_free(mf->streams, TRUE); | |||
| g_slice_free1(sizeof(*mf), mf); | |||
| } | |||
| // mf is locked | |||
| static void meta_destroy(metafile_t *mf) { | |||
| // close all streams | |||
| for (int i = 0; i < mf->streams->len; i++) { | |||
| stream_t *stream = g_ptr_array_index(mf->streams, i); | |||
| pthread_mutex_lock(&stream->lock); | |||
| stream_close(stream); | |||
| pthread_mutex_unlock(&stream->lock); | |||
| } | |||
| } | |||
| // mf is locked | |||
| static void meta_stream_interface(metafile_t *mf, unsigned long snum, char *content) { | |||
| dbg("stream %lu interface %s", snum, content); | |||
| stream_open(mf, snum, content); | |||
| } | |||
| // mf is locked | |||
| static void meta_stream_details(metafile_t *mf, unsigned long snum, char *content) { | |||
| dbg("stream %lu details %s", snum, content); | |||
| } | |||
| // mf is locked | |||
| static void meta_rtp_payload_type(metafile_t *mf, unsigned long mnum, unsigned int payload_num, | |||
| char *payload_type) | |||
| { | |||
| dbg("payload type in media %lu num %u is %s", mnum, payload_num, payload_type); | |||
| } | |||
| // mf is locked | |||
| static void meta_section(metafile_t *mf, char *section, char *content, unsigned long len) { | |||
| unsigned long lu; | |||
| unsigned int u; | |||
| if (!strcmp(section, "CALL-ID")) | |||
| mf->call_id = g_string_chunk_insert(mf->gsc, content); | |||
| else if (!strcmp(section, "PARENT")) | |||
| mf->parent = g_string_chunk_insert(mf->gsc, content); | |||
| else if (sscanf_match(section, "STREAM %lu interface", &lu) == 1) | |||
| meta_stream_interface(mf, lu, content); | |||
| else if (sscanf_match(section, "STREAM %lu details", &lu) == 1) | |||
| meta_stream_details(mf, lu, content); | |||
| else if (sscanf_match(section, "MEDIA %lu PAYLOAD TYPE %u", &lu, &u) == 2) | |||
| meta_rtp_payload_type(mf, lu, u, content); | |||
| } | |||
| void metafile_change(char *name) { | |||
| // get or create metafile metadata | |||
| pthread_mutex_lock(&metafiles_lock); | |||
| metafile_t *mf = g_hash_table_lookup(metafiles, name); | |||
| if (!mf) { | |||
| dbg("allocating metafile info for %s", name); | |||
| mf = g_slice_alloc0(sizeof(*mf)); | |||
| mf->gsc = g_string_chunk_new(0); | |||
| mf->name = g_string_chunk_insert(mf->gsc, name); | |||
| pthread_mutex_init(&mf->lock, NULL); | |||
| mf->streams = g_ptr_array_new(); | |||
| g_hash_table_insert(metafiles, mf->name, mf); | |||
| } | |||
| // switch locks | |||
| pthread_mutex_lock(&mf->lock); | |||
| pthread_mutex_unlock(&metafiles_lock); | |||
| char fnbuf[PATH_MAX]; | |||
| snprintf(fnbuf, sizeof(fnbuf), "%s/%s", SPOOL_DIR, name); | |||
| // open file and seek to last known position | |||
| int fd = open(fnbuf, O_RDONLY); | |||
| if (fd == -1) { | |||
| ilog(LOG_ERR, "Failed to open %s: %s\n", fnbuf, strerror(errno)); | |||
| goto out; | |||
| } | |||
| lseek(fd, mf->pos, SEEK_SET); | |||
| // read the entire file | |||
| GString *s = g_string_new(NULL); | |||
| char buf[1024]; | |||
| while (1) { | |||
| int ret = read(fd, buf, sizeof(buf)); | |||
| if (ret == 0) | |||
| break; | |||
| if (ret == -1) | |||
| die_errno("read on metadata file failed"); | |||
| g_string_append_len(s, buf, ret); | |||
| } | |||
| // save read position and close file | |||
| mf->pos = lseek(fd, 0, SEEK_CUR); | |||
| close(fd); | |||
| // process contents of metadata file | |||
| char *head = s->str; | |||
| char *endp = s->str + s->len; | |||
| while (head < endp) { | |||
| // section header | |||
| char *nl = memchr(head, '\n', endp - head); | |||
| if (!nl || nl == head) { | |||
| ilog(LOG_WARN, "Missing section header in %s", name); | |||
| break; | |||
| } | |||
| if (memchr(head, '\0', nl - head)) { | |||
| ilog(LOG_WARN, "NUL character in section header in %s", name); | |||
| break; | |||
| } | |||
| *(nl++) = '\0'; | |||
| char *section = head; | |||
| dbg("section %s", section); | |||
| head = nl; | |||
| // content length | |||
| nl = memchr(head, ':', endp - head); | |||
| if (!nl || nl == head) { | |||
| ilog(LOG_WARN, "Content length for section %s missing in %s", section, name); | |||
| break; | |||
| } | |||
| *(nl++) = '\0'; | |||
| if (*(nl++) != '\n') { | |||
| ilog(LOG_WARN, "Unterminated content length for section %s in %s", section, name); | |||
| break; | |||
| } | |||
| char *errp; | |||
| unsigned long slen = strtoul(head, &errp, 10); | |||
| if (*errp != '\0') { | |||
| ilog(LOG_WARN, "Invalid content length for section %s in %s", section, name); | |||
| break; | |||
| } | |||
| dbg("content length %lu", slen); | |||
| head = nl; | |||
| // content | |||
| if (endp - head < slen) { | |||
| ilog(LOG_WARN, "Content truncated in section %s in %s", section, name); | |||
| break; | |||
| } | |||
| char *content = head; | |||
| if (memchr(content, '\0', slen)) { | |||
| ilog(LOG_WARN, "NUL character in content in section %s in %s", section, name); | |||
| break; | |||
| } | |||
| // double newline separator | |||
| head += slen; | |||
| if (*head != '\n' || *(head + 1) != '\n') { | |||
| ilog(LOG_WARN, "Separator missing after section %s in %s", section, name); | |||
| break; | |||
| } | |||
| *head = '\0'; | |||
| head += 2; | |||
| meta_section(mf, section, content, slen); | |||
| } | |||
| g_string_free(s, TRUE); | |||
| out: | |||
| pthread_mutex_unlock(&mf->lock); | |||
| } | |||
| void metafile_delete(char *name) { | |||
| // get metafile metadata | |||
| pthread_mutex_lock(&metafiles_lock); | |||
| metafile_t *mf = g_hash_table_lookup(metafiles, name); | |||
| if (!mf) { | |||
| // nothing to do | |||
| pthread_mutex_unlock(&metafiles_lock); | |||
| return; | |||
| } | |||
| // switch locks and remove entry | |||
| pthread_mutex_lock(&mf->lock); | |||
| g_hash_table_remove(metafiles, name); | |||
| pthread_mutex_unlock(&metafiles_lock); | |||
| meta_destroy(mf); | |||
| // add to garbage | |||
| garbage_add(mf, meta_free); | |||
| pthread_mutex_unlock(&mf->lock); | |||
| } | |||
| void metafile_setup(void) { | |||
| metafiles = g_hash_table_new(g_str_hash, g_str_equal); | |||
| //pcre_build(&stream_interface_re, "^STREAM (\\d+) interface$"); | |||
| //pcre_build(&stream_interface_re, "^STREAM (\\d+) details$"); | |||
| } | |||
| void metafile_cleanup(void) { | |||
| GList *mflist = g_hash_table_get_values(metafiles); | |||
| for (GList *l = mflist; l; l = l->next) { | |||
| metafile_t *mf = l->data; | |||
| meta_destroy(mf); | |||
| meta_free(mf); | |||
| } | |||
| g_list_free(mflist); | |||
| g_hash_table_destroy(metafiles); | |||
| } | |||
| @ -0,0 +1,12 @@ | |||
| #ifndef _METAFILE_H_ | |||
| #define _METAFILE_H_ | |||
| #include "types.h" | |||
| void metafile_setup(void); | |||
| void metafile_cleanup(void); | |||
| void metafile_change(char *name); | |||
| void metafile_delete(char *name); | |||
| #endif | |||
| @ -0,0 +1,14 @@ | |||
| #include "pcre.h" | |||
| #include <pcre.h> | |||
| #include "log.h" | |||
| void pcre_build(pcre_t *out, const char *pattern) { | |||
| const char *errptr; | |||
| int erroff; | |||
| out->re = pcre_compile(pattern, PCRE_DOLLAR_ENDONLY | PCRE_DOTALL, &errptr, &erroff, NULL); | |||
| if (!out->re) | |||
| die("Failed to compile PCRE '%s': %s (at %i)", pattern, errptr, erroff); | |||
| out->extra = pcre_study(out->re, 0, &errptr); | |||
| } | |||
| @ -0,0 +1,8 @@ | |||
| #ifndef _PCRE_H_ | |||
| #define _PCRE_H_ | |||
| #include "types.h" | |||
| void pcre_build(pcre_t *out, const char *pattern); | |||
| #endif | |||
| @ -0,0 +1,95 @@ | |||
| #include "stream.h" | |||
| #include <glib.h> | |||
| #include <pthread.h> | |||
| #include <unistd.h> | |||
| #include <limits.h> | |||
| #include <fcntl.h> | |||
| #include "metafile.h" | |||
| #include "epoll.h" | |||
| #include "log.h" | |||
| #include "main.h" | |||
| // stream is locked | |||
| void stream_close(stream_t *stream) { | |||
| if (stream->fd == -1) | |||
| return; | |||
| epoll_del(stream->fd); | |||
| close(stream->fd); | |||
| stream->fd = -1; | |||
| } | |||
| void stream_free(stream_t *stream) { | |||
| g_slice_free1(sizeof(*stream), stream); | |||
| } | |||
| static void stream_handler(handler_t *handler) { | |||
| stream_t *stream = handler->ptr; | |||
| //dbg("poll event for %s", stream->name); | |||
| pthread_mutex_lock(&stream->lock); | |||
| if (stream->fd == -1) | |||
| goto out; | |||
| char buf[65535]; | |||
| int ret = read(stream->fd, buf, sizeof(buf)); | |||
| if (ret == 0) { | |||
| ilog(LOG_INFO, "EOF on stream %s", stream->name); | |||
| stream_close(stream); | |||
| goto out; | |||
| } | |||
| else if (ret < 0) { | |||
| ilog(LOG_INFO, "Read error on stream %s: %s", stream->name, strerror(errno)); | |||
| stream_close(stream); | |||
| goto out; | |||
| } | |||
| out: | |||
| pthread_mutex_unlock(&stream->lock); | |||
| } | |||
| // mf is locked | |||
| static stream_t *stream_get(metafile_t *mf, unsigned long id) { | |||
| if (mf->streams->len <= id) | |||
| g_ptr_array_set_size(mf->streams, id + 1); | |||
| stream_t *ret = g_ptr_array_index(mf->streams, id); | |||
| if (ret) | |||
| goto out; | |||
| ret = g_slice_alloc0(sizeof(*ret)); | |||
| g_ptr_array_index(mf->streams, id) = ret; | |||
| pthread_mutex_init(&ret->lock, NULL); | |||
| ret->fd = -1; | |||
| ret->id = id; | |||
| out: | |||
| return ret; | |||
| } | |||
| // mf is locked | |||
| void stream_open(metafile_t *mf, unsigned long id, char *name) { | |||
| dbg("opening stream %lu/%s", id, name); | |||
| stream_t *stream = stream_get(mf, id); | |||
| stream->name = g_string_chunk_insert(mf->gsc, name); | |||
| char fnbuf[PATH_MAX]; | |||
| snprintf(fnbuf, sizeof(fnbuf), "%s/%s/%s", PROC_DIR, mf->parent, name); | |||
| stream->fd = open(fnbuf, O_RDONLY | O_NONBLOCK); | |||
| if (stream->fd == -1) { | |||
| ilog(LOG_ERR, "Failed to open kernel stream %s: %s", fnbuf, strerror(errno)); | |||
| return; | |||
| } | |||
| // add to epoll | |||
| stream->handler.ptr = stream; | |||
| stream->handler.func = stream_handler; | |||
| epoll_add(stream->fd, EPOLLIN, &stream->handler); | |||
| } | |||
| @ -0,0 +1,10 @@ | |||
| #ifndef _STREAM_H_ | |||
| #define _STREAM_H_ | |||
| #include "types.h" | |||
| void stream_open(metafile_t *mf, unsigned long id, char *name); | |||
| void stream_close(stream_t *stream); | |||
| void stream_free(stream_t *stream); | |||
| #endif | |||
| @ -0,0 +1,46 @@ | |||
| #ifndef _TYPES_H_ | |||
| #define _TYPES_H_ | |||
| #include <pthread.h> | |||
| #include <sys/types.h> | |||
| #include <glib.h> | |||
| #include <pcre.h> | |||
| typedef struct handler_s handler_t; | |||
| typedef void handler_func(handler_t *); | |||
| struct handler_s { | |||
| handler_func *func; | |||
| void *ptr; | |||
| }; | |||
| struct stream_s { | |||
| pthread_mutex_t lock; | |||
| char *name; | |||
| unsigned long id; | |||
| int fd; | |||
| handler_t handler; | |||
| }; | |||
| typedef struct stream_s stream_t; | |||
| struct metafile_s { | |||
| pthread_mutex_t lock; | |||
| char *name; | |||
| char *parent; | |||
| char *call_id; | |||
| off_t pos; | |||
| GStringChunk *gsc; // XXX limit max size | |||
| GPtrArray *streams; | |||
| }; | |||
| typedef struct metafile_s metafile_t; | |||
| // struct pcre_s { | |||
| // pcre *re; | |||
| // pcre_extra *extra; | |||
| // }; | |||
| // typedef struct pcre_s pcre_t; | |||
| #endif | |||