diff options
-rw-r--r-- | Makefile | 129 | ||||
-rw-r--r-- | include/local-def.h | 160 | ||||
-rw-r--r-- | include/proto-def.h | 53 | ||||
-rw-r--r-- | nbproject/Makefile-Debug.mk | 126 | ||||
-rw-r--r-- | nbproject/Makefile-Release.mk | 126 | ||||
-rw-r--r-- | nbproject/Makefile-impl.mk | 133 | ||||
-rw-r--r-- | nbproject/Makefile-variables.mk | 35 | ||||
-rw-r--r-- | nbproject/Package-Debug.bash | 76 | ||||
-rw-r--r-- | nbproject/Package-Release.bash | 76 | ||||
-rw-r--r-- | nbproject/configurations.xml | 113 | ||||
-rw-r--r-- | nbproject/project.xml | 28 | ||||
-rw-r--r-- | src/afbs-api.c | 58 | ||||
-rw-r--r-- | src/alsa-api.c | 51 | ||||
-rw-r--r-- | src/config.c | 335 | ||||
-rw-r--r-- | src/dbus-api.c | 51 | ||||
-rw-r--r-- | src/http-svc.c | 283 | ||||
-rw-r--r-- | src/main.c | 611 | ||||
-rw-r--r-- | src/rest-api.c | 175 | ||||
-rw-r--r-- | src/session.c | 302 |
19 files changed, 2921 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..180d7a16 --- /dev/null +++ b/Makefile @@ -0,0 +1,129 @@ +# +# There exist several targets which are by default empty and which can be +# used for execution of your targets. These targets are usually executed +# before and after some main targets. They are: +# +# .build-pre: called before 'build' target +# .build-post: called after 'build' target +# .clean-pre: called before 'clean' target +# .clean-post: called after 'clean' target +# .clobber-pre: called before 'clobber' target +# .clobber-post: called after 'clobber' target +# .all-pre: called before 'all' target +# .all-post: called after 'all' target +# .help-pre: called before 'help' target +# .help-post: called after 'help' target +# +# Targets beginning with '.' are not intended to be called on their own. +# +# Main targets can be executed directly, and they are: +# +# build build a specific configuration +# clean remove built files from a configuration +# clobber remove all built files +# all build all configurations +# help print help mesage +# +# Targets .build-impl, .clean-impl, .clobber-impl, .all-impl, and +# .help-impl are implemented in nbproject/makefile-impl.mk. +# +# Available make variables: +# +# CND_BASEDIR base directory for relative paths +# CND_DISTDIR default top distribution directory (build artifacts) +# CND_BUILDDIR default top build directory (object files, ...) +# CONF name of current configuration +# CND_PLATFORM_${CONF} platform name (current configuration) +# CND_ARTIFACT_DIR_${CONF} directory of build artifact (current configuration) +# CND_ARTIFACT_NAME_${CONF} name of build artifact (current configuration) +# CND_ARTIFACT_PATH_${CONF} path to build artifact (current configuration) +# CND_PACKAGE_DIR_${CONF} directory of package (current configuration) +# CND_PACKAGE_NAME_${CONF} name of package (current configuration) +# CND_PACKAGE_PATH_${CONF} path to package (current configuration) +# +# NOCDDL +CND_BUILDDIR=./build +CND_DISTDIR=./build/dist + +# Environment +MKDIR=mkdir +CP=cp +CCADMIN=CCadmin + + +# build +build: .build-post + +.build-pre: +# Add your pre 'build' code here... + +.build-post: .build-impl +# Add your post 'build' code here... + + +# clean +clean: .clean-post + +.clean-pre: +# Add your pre 'clean' code here... + +.clean-post: .clean-impl +# Add your post 'clean' code here... + + +# clobber +clobber: .clobber-post + +.clobber-pre: +# Add your pre 'clobber' code here... + +.clobber-post: .clobber-impl +# Add your post 'clobber' code here... + + +# all +all: .all-post + +.all-pre: +# Add your pre 'all' code here... + +.all-post: .all-impl +# Add your post 'all' code here... + + +# build tests +build-tests: .build-tests-post + +.build-tests-pre: +# Add your pre 'build-tests' code here... + +.build-tests-post: .build-tests-impl +# Add your post 'build-tests' code here... + + +# run tests +test: .test-post + +.test-pre: build-tests +# Add your pre 'test' code here... + +.test-post: .test-impl +# Add your post 'test' code here... + + +# help +help: .help-post + +.help-pre: +# Add your pre 'help' code here... + +.help-post: .help-impl +# Add your post 'help' code here... + + + +# include project implementation makefile +include nbproject/Makefile-impl.mk + +# include project make variables +include nbproject/Makefile-variables.mk diff --git a/include/local-def.h b/include/local-def.h new file mode 100644 index 00000000..f69ddcbe --- /dev/null +++ b/include/local-def.h @@ -0,0 +1,160 @@ +/* + alsajson-gw -- provide a REST/HTTP interface to ALSA-Mixer + + Copyright (C) 2015, Fulup Ar Foll + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: $ +*/ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <time.h> +#include <json.h> +#include <microhttpd.h> + + +#define AJQ_VERSION "0.1" + +/* other definitions --------------------------------------------------- */ + +typedef int BOOL; +#ifndef FALSE + #define FALSE 0 +#endif +#ifndef TRUE + #define TRUE 1 +#endif + +#define PUBLIC +#define STATIC static +#define FAILED -1 + +// prebuild json error are constructed in config.c +typedef enum { AFB_FALSE, AFB_TRUE, AFB_FATAL, AFB_FAIL, AFB_WARNING, AFB_EMPTY, AFB_SUCCESS} AFB_ERROR; +extern char *ERROR_LABEL[]; +#define ERROR_LABEL_DEF {"false", "true","fatal", "fail", "warning", "empty", "success"} + +#define BANNER "<html><head><title>Application Framework Binder</title></head><body>Application Framework </body></html>" +#define JSON_CONTENT "application/json" +#define MAX_POST_SIZE 4096 // maximum size for POST data + +// use to check anonymous data when using dynamic loadable lib +typedef enum {AFB_PLUGIN=1234, AFB_REQUEST=5678} AFB_type; + +// Error code are requested through function to manage json usage count +typedef struct { + int level; + char* label; + json_object *json; +} AFB_ErrorT; + +// Post handler +typedef struct { + char* data; + int len; + int uid; +} AFB_HttpPost; + + +// some usefull static object initialized when entering listen loop. +extern int verbose; +// MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "value"); +typedef struct { + const char *url; + char *plugin; + char *api; + char *post; + struct MHD_Connection *connection; +} AFB_request; + +typedef struct { + char *msg; + int len; +} AFB_redirect_msg; + +// main config structure +typedef struct { + char *logname; // logfile path for info & error log + char *console; // console device name (can be a file or a tty) + int localhostOnly; + int httpdPort; + char *smack; // smack label + char *plugins; // list of requested plugins + char *rootdir; // base dir for httpd file download + char *rootbase; // Angular HTML5 base URL + char *rootapi; // Base URL for REST APIs + char *pidfile; // where to store pid when running background + char *sessiondir; // where to store mixer session files + char *configfile; // where to store configuration on gateway exit + uid_t setuid; + int cacheTimeout; + AFB_redirect_msg html5; // html5 redirect message +} AFB_config; + +// Command line structure hold cli --command + help text +typedef struct { + int val; // command number within application + int has_arg; // command number within application + char *name; // command as used in --xxxx cli + char *help; // help text +} AFB_options; + +typedef json_object* (*AFB_apiCB)(); + +// API definition +typedef struct { + char *name; + AFB_apiCB callback; + char *info; +} AFB_restapi; + +// Plugin definition +typedef struct { + AFB_type type; + char *info; + char *prefix; + json_object *jtype; + AFB_restapi *apis; +} AFB_plugin; + +typedef struct { + AFB_config *config; // pointer to current config + // List of commands to execute + int killPrevious; + int background; // run in backround mode + int foreground; // run in forground mode + int checkAlsa; // Display active Alsa Board + int configsave; // Save config on disk on start + char *cacheTimeout; // http require timeout to be a string + void *httpd; // anonymous structure for httpd handler + int fakemod; // respond to GET/POST request without interacting with sndboard + int forceexit; // when autoconfig from script force exit before starting server + AFB_plugin **plugins; // pointer to REST/API plugins +} AFB_session; + + +#include "proto-def.h" diff --git a/include/proto-def.h b/include/proto-def.h new file mode 100644 index 00000000..934bf30f --- /dev/null +++ b/include/proto-def.h @@ -0,0 +1,53 @@ +/* + alsajson-gw -- provide a REST/HTTP interface to ALSA-Mixer + + Copyright (C) 2015, Fulup Ar Foll + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: $ +*/ + +// Rest-api +PUBLIC json_object* pingSample (AFB_plugin *plugin, AFB_session *session, AFB_request *post); +PUBLIC const char* getQueryValue (AFB_request * request, char *name); +PUBLIC AFB_plugin *afsvRegister (AFB_session *session); +PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char *method, const char* url); + +// Session handling +PUBLIC AFB_ERROR sessionCheckdir (AFB_session *session); +PUBLIC json_object *sessionList (AFB_session *session, AFB_request *request); +PUBLIC json_object *sessionToDisk (AFB_session *session, AFB_request *request, char *name,json_object *jsonSession); +PUBLIC json_object *sessionFromDisk (AFB_session *session, AFB_request *request, char *name); + + +// Httpd server +PUBLIC AFB_ERROR httpdStart (AFB_session *session); +PUBLIC AFB_ERROR httpdLoop (AFB_session *session); +PUBLIC void httpdStop (AFB_session *session); + + +// config management +PUBLIC char *configTime (void); +PUBLIC AFB_session *configInit (void); +PUBLIC json_object *jsonNewMessage (AFB_ERROR level, char* format, ...); +PUBLIC json_object *jsonNewStatus (AFB_ERROR level); +PUBLIC json_object *jsonNewjtype (void); +PUBLIC json_object *jsonNewMessage (AFB_ERROR level, char* format, ...); +PUBLIC void jsonDumpObject (json_object * jObject); +PUBLIC AFB_ERROR configLoadFile (AFB_session * session, AFB_config *cliconfig); +PUBLIC void configStoreFile (AFB_session * session); + + diff --git a/nbproject/Makefile-Debug.mk b/nbproject/Makefile-Debug.mk new file mode 100644 index 00000000..fb778c72 --- /dev/null +++ b/nbproject/Makefile-Debug.mk @@ -0,0 +1,126 @@ +# +# Generated Makefile - do not edit! +# +# Edit the Makefile in the project folder instead (../Makefile). Each target +# has a -pre and a -post target defined where you can add customized code. +# +# This makefile implements configuration specific macros and targets. + + +# Environment +MKDIR=mkdir +CP=cp +GREP=grep +NM=nm +CCADMIN=CCadmin +RANLIB=ranlib +CC=gcc +CCC=g++ +CXX=g++ +FC=gfortran +AS=as + +# Macros +CND_PLATFORM=GNU-Linux +CND_DLIB_EXT=so +CND_CONF=Debug +CND_DISTDIR=dist +CND_BUILDDIR=build + +# Include project Makefile +include Makefile + +# Object Directory +OBJECTDIR=${CND_BUILDDIR}/${CND_CONF}/${CND_PLATFORM} + +# Object Files +OBJECTFILES= \ + ${OBJECTDIR}/src/afbs-api.o \ + ${OBJECTDIR}/src/alsa-api.o \ + ${OBJECTDIR}/src/config.o \ + ${OBJECTDIR}/src/dbus-api.o \ + ${OBJECTDIR}/src/http-svc.o \ + ${OBJECTDIR}/src/main.o \ + ${OBJECTDIR}/src/rest-api.o \ + ${OBJECTDIR}/src/session.o + + +# C Compiler Flags +CFLAGS= + +# CC Compiler Flags +CCFLAGS= +CXXFLAGS= + +# Fortran Compiler Flags +FFLAGS= + +# Assembler Flags +ASFLAGS= + +# Link Libraries and Options +LDLIBSOPTIONS=`pkg-config --libs libmicrohttpd` `pkg-config --libs json-c` + +# Build Targets +.build-conf: ${BUILD_SUBPROJECTS} + "${MAKE}" -f nbproject/Makefile-${CND_CONF}.mk ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder + +${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder: ${OBJECTFILES} + ${MKDIR} -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM} + ${LINK.c} -o ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder ${OBJECTFILES} ${LDLIBSOPTIONS} + +${OBJECTDIR}/src/afbs-api.o: src/afbs-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/afbs-api.o src/afbs-api.c + +${OBJECTDIR}/src/alsa-api.o: src/alsa-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/alsa-api.o src/alsa-api.c + +${OBJECTDIR}/src/config.o: src/config.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/config.o src/config.c + +${OBJECTDIR}/src/dbus-api.o: src/dbus-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/dbus-api.o src/dbus-api.c + +${OBJECTDIR}/src/http-svc.o: src/http-svc.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-svc.o src/http-svc.c + +${OBJECTDIR}/src/main.o: src/main.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/main.o src/main.c + +${OBJECTDIR}/src/rest-api.o: src/rest-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/rest-api.o src/rest-api.c + +${OBJECTDIR}/src/session.o: src/session.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -g -I/usr/include/json-c -Iinclude `pkg-config --cflags libmicrohttpd` `pkg-config --cflags json-c` -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/session.o src/session.c + +# Subprojects +.build-subprojects: + +# Clean Targets +.clean-conf: ${CLEAN_SUBPROJECTS} + ${RM} -r ${CND_BUILDDIR}/${CND_CONF} + ${RM} ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder + +# Subprojects +.clean-subprojects: + +# Enable dependency checking +.dep.inc: .depcheck-impl + +include .dep.inc diff --git a/nbproject/Makefile-Release.mk b/nbproject/Makefile-Release.mk new file mode 100644 index 00000000..6df0bda6 --- /dev/null +++ b/nbproject/Makefile-Release.mk @@ -0,0 +1,126 @@ +# +# Generated Makefile - do not edit! +# +# Edit the Makefile in the project folder instead (../Makefile). Each target +# has a -pre and a -post target defined where you can add customized code. +# +# This makefile implements configuration specific macros and targets. + + +# Environment +MKDIR=mkdir +CP=cp +GREP=grep +NM=nm +CCADMIN=CCadmin +RANLIB=ranlib +CC=gcc +CCC=g++ +CXX=g++ +FC=gfortran +AS=as + +# Macros +CND_PLATFORM=GNU-Linux +CND_DLIB_EXT=so +CND_CONF=Release +CND_DISTDIR=dist +CND_BUILDDIR=build + +# Include project Makefile +include Makefile + +# Object Directory +OBJECTDIR=${CND_BUILDDIR}/${CND_CONF}/${CND_PLATFORM} + +# Object Files +OBJECTFILES= \ + ${OBJECTDIR}/src/afbs-api.o \ + ${OBJECTDIR}/src/alsa-api.o \ + ${OBJECTDIR}/src/config.o \ + ${OBJECTDIR}/src/dbus-api.o \ + ${OBJECTDIR}/src/http-svc.o \ + ${OBJECTDIR}/src/main.o \ + ${OBJECTDIR}/src/rest-api.o \ + ${OBJECTDIR}/src/session.o + + +# C Compiler Flags +CFLAGS= + +# CC Compiler Flags +CCFLAGS= +CXXFLAGS= + +# Fortran Compiler Flags +FFLAGS= + +# Assembler Flags +ASFLAGS= + +# Link Libraries and Options +LDLIBSOPTIONS= + +# Build Targets +.build-conf: ${BUILD_SUBPROJECTS} + "${MAKE}" -f nbproject/Makefile-${CND_CONF}.mk ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder + +${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder: ${OBJECTFILES} + ${MKDIR} -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM} + ${LINK.c} -o ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder ${OBJECTFILES} ${LDLIBSOPTIONS} + +${OBJECTDIR}/src/afbs-api.o: src/afbs-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/afbs-api.o src/afbs-api.c + +${OBJECTDIR}/src/alsa-api.o: src/alsa-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/alsa-api.o src/alsa-api.c + +${OBJECTDIR}/src/config.o: src/config.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/config.o src/config.c + +${OBJECTDIR}/src/dbus-api.o: src/dbus-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/dbus-api.o src/dbus-api.c + +${OBJECTDIR}/src/http-svc.o: src/http-svc.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-svc.o src/http-svc.c + +${OBJECTDIR}/src/main.o: src/main.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/main.o src/main.c + +${OBJECTDIR}/src/rest-api.o: src/rest-api.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/rest-api.o src/rest-api.c + +${OBJECTDIR}/src/session.o: src/session.c + ${MKDIR} -p ${OBJECTDIR}/src + ${RM} "$@.d" + $(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/session.o src/session.c + +# Subprojects +.build-subprojects: + +# Clean Targets +.clean-conf: ${CLEAN_SUBPROJECTS} + ${RM} -r ${CND_BUILDDIR}/${CND_CONF} + ${RM} ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder + +# Subprojects +.clean-subprojects: + +# Enable dependency checking +.dep.inc: .depcheck-impl + +include .dep.inc diff --git a/nbproject/Makefile-impl.mk b/nbproject/Makefile-impl.mk new file mode 100644 index 00000000..185e4af2 --- /dev/null +++ b/nbproject/Makefile-impl.mk @@ -0,0 +1,133 @@ +# +# Generated Makefile - do not edit! +# +# Edit the Makefile in the project folder instead (../Makefile). Each target +# has a pre- and a post- target defined where you can add customization code. +# +# This makefile implements macros and targets common to all configurations. +# +# NOCDDL + + +# Building and Cleaning subprojects are done by default, but can be controlled with the SUB +# macro. If SUB=no, subprojects will not be built or cleaned. The following macro +# statements set BUILD_SUB-CONF and CLEAN_SUB-CONF to .build-reqprojects-conf +# and .clean-reqprojects-conf unless SUB has the value 'no' +SUB_no=NO +SUBPROJECTS=${SUB_${SUB}} +BUILD_SUBPROJECTS_=.build-subprojects +BUILD_SUBPROJECTS_NO= +BUILD_SUBPROJECTS=${BUILD_SUBPROJECTS_${SUBPROJECTS}} +CLEAN_SUBPROJECTS_=.clean-subprojects +CLEAN_SUBPROJECTS_NO= +CLEAN_SUBPROJECTS=${CLEAN_SUBPROJECTS_${SUBPROJECTS}} + + +# Project Name +PROJECTNAME=AppFrameworkBinder + +# Active Configuration +DEFAULTCONF=Debug +CONF=${DEFAULTCONF} + +# All Configurations +ALLCONFS=Debug Release + + +# build +.build-impl: .build-pre .validate-impl .depcheck-impl + @#echo "=> Running $@... Configuration=$(CONF)" + "${MAKE}" -f nbproject/Makefile-${CONF}.mk QMAKE=${QMAKE} SUBPROJECTS=${SUBPROJECTS} .build-conf + + +# clean +.clean-impl: .clean-pre .validate-impl .depcheck-impl + @#echo "=> Running $@... Configuration=$(CONF)" + "${MAKE}" -f nbproject/Makefile-${CONF}.mk QMAKE=${QMAKE} SUBPROJECTS=${SUBPROJECTS} .clean-conf + + +# clobber +.clobber-impl: .clobber-pre .depcheck-impl + @#echo "=> Running $@..." + for CONF in ${ALLCONFS}; \ + do \ + "${MAKE}" -f nbproject/Makefile-$${CONF}.mk QMAKE=${QMAKE} SUBPROJECTS=${SUBPROJECTS} .clean-conf; \ + done + +# all +.all-impl: .all-pre .depcheck-impl + @#echo "=> Running $@..." + for CONF in ${ALLCONFS}; \ + do \ + "${MAKE}" -f nbproject/Makefile-$${CONF}.mk QMAKE=${QMAKE} SUBPROJECTS=${SUBPROJECTS} .build-conf; \ + done + +# build tests +.build-tests-impl: .build-impl .build-tests-pre + @#echo "=> Running $@... Configuration=$(CONF)" + "${MAKE}" -f nbproject/Makefile-${CONF}.mk SUBPROJECTS=${SUBPROJECTS} .build-tests-conf + +# run tests +.test-impl: .build-tests-impl .test-pre + @#echo "=> Running $@... Configuration=$(CONF)" + "${MAKE}" -f nbproject/Makefile-${CONF}.mk SUBPROJECTS=${SUBPROJECTS} .test-conf + +# dependency checking support +.depcheck-impl: + @echo "# This code depends on make tool being used" >.dep.inc + @if [ -n "${MAKE_VERSION}" ]; then \ + echo "DEPFILES=\$$(wildcard \$$(addsuffix .d, \$${OBJECTFILES} \$${TESTOBJECTFILES}))" >>.dep.inc; \ + echo "ifneq (\$${DEPFILES},)" >>.dep.inc; \ + echo "include \$${DEPFILES}" >>.dep.inc; \ + echo "endif" >>.dep.inc; \ + else \ + echo ".KEEP_STATE:" >>.dep.inc; \ + echo ".KEEP_STATE_FILE:.make.state.\$${CONF}" >>.dep.inc; \ + fi + +# configuration validation +.validate-impl: + @if [ ! -f nbproject/Makefile-${CONF}.mk ]; \ + then \ + echo ""; \ + echo "Error: can not find the makefile for configuration '${CONF}' in project ${PROJECTNAME}"; \ + echo "See 'make help' for details."; \ + echo "Current directory: " `pwd`; \ + echo ""; \ + fi + @if [ ! -f nbproject/Makefile-${CONF}.mk ]; \ + then \ + exit 1; \ + fi + + +# help +.help-impl: .help-pre + @echo "This makefile supports the following configurations:" + @echo " ${ALLCONFS}" + @echo "" + @echo "and the following targets:" + @echo " build (default target)" + @echo " clean" + @echo " clobber" + @echo " all" + @echo " help" + @echo "" + @echo "Makefile Usage:" + @echo " make [CONF=<CONFIGURATION>] [SUB=no] build" + @echo " make [CONF=<CONFIGURATION>] [SUB=no] clean" + @echo " make [SUB=no] clobber" + @echo " make [SUB=no] all" + @echo " make help" + @echo "" + @echo "Target 'build' will build a specific configuration and, unless 'SUB=no'," + @echo " also build subprojects." + @echo "Target 'clean' will clean a specific configuration and, unless 'SUB=no'," + @echo " also clean subprojects." + @echo "Target 'clobber' will remove all built files from all configurations and," + @echo " unless 'SUB=no', also from subprojects." + @echo "Target 'all' will will build all configurations and, unless 'SUB=no'," + @echo " also build subprojects." + @echo "Target 'help' prints this message." + @echo "" + diff --git a/nbproject/Makefile-variables.mk b/nbproject/Makefile-variables.mk new file mode 100644 index 00000000..4e44a219 --- /dev/null +++ b/nbproject/Makefile-variables.mk @@ -0,0 +1,35 @@ +# +# Generated - do not edit! +# +# NOCDDL +# +CND_BASEDIR=`pwd` +CND_BUILDDIR=build +CND_DISTDIR=dist +# Debug configuration +CND_PLATFORM_Debug=GNU-Linux +CND_ARTIFACT_DIR_Debug=dist/Debug/GNU-Linux +CND_ARTIFACT_NAME_Debug=appframeworkbinder +CND_ARTIFACT_PATH_Debug=dist/Debug/GNU-Linux/appframeworkbinder +CND_PACKAGE_DIR_Debug=dist/Debug/GNU-Linux/package +CND_PACKAGE_NAME_Debug=appframeworkbinder.tar +CND_PACKAGE_PATH_Debug=dist/Debug/GNU-Linux/package/appframeworkbinder.tar +# Release configuration +CND_PLATFORM_Release=GNU-Linux +CND_ARTIFACT_DIR_Release=dist/Release/GNU-Linux +CND_ARTIFACT_NAME_Release=appframeworkbinder +CND_ARTIFACT_PATH_Release=dist/Release/GNU-Linux/appframeworkbinder +CND_PACKAGE_DIR_Release=dist/Release/GNU-Linux/package +CND_PACKAGE_NAME_Release=appframeworkbinder.tar +CND_PACKAGE_PATH_Release=dist/Release/GNU-Linux/package/appframeworkbinder.tar +# +# include compiler specific variables +# +# dmake command +ROOT:sh = test -f nbproject/private/Makefile-variables.mk || \ + (mkdir -p nbproject/private && touch nbproject/private/Makefile-variables.mk) +# +# gmake command +.PHONY: $(shell test -f nbproject/private/Makefile-variables.mk || (mkdir -p nbproject/private && touch nbproject/private/Makefile-variables.mk)) +# +include nbproject/private/Makefile-variables.mk diff --git a/nbproject/Package-Debug.bash b/nbproject/Package-Debug.bash new file mode 100644 index 00000000..26359837 --- /dev/null +++ b/nbproject/Package-Debug.bash @@ -0,0 +1,76 @@ +#!/bin/bash -x + +# +# Generated - do not edit! +# + +# Macros +TOP=`pwd` +CND_PLATFORM=GNU-Linux +CND_CONF=Debug +CND_DISTDIR=dist +CND_BUILDDIR=build +CND_DLIB_EXT=so +NBTMPDIR=${CND_BUILDDIR}/${CND_CONF}/${CND_PLATFORM}/tmp-packaging +TMPDIRNAME=tmp-packaging +OUTPUT_PATH=${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder +OUTPUT_BASENAME=appframeworkbinder +PACKAGE_TOP_DIR=appframeworkbinder/ + +# Functions +function checkReturnCode +{ + rc=$? + if [ $rc != 0 ] + then + exit $rc + fi +} +function makeDirectory +# $1 directory path +# $2 permission (optional) +{ + mkdir -p "$1" + checkReturnCode + if [ "$2" != "" ] + then + chmod $2 "$1" + checkReturnCode + fi +} +function copyFileToTmpDir +# $1 from-file path +# $2 to-file path +# $3 permission +{ + cp "$1" "$2" + checkReturnCode + if [ "$3" != "" ] + then + chmod $3 "$2" + checkReturnCode + fi +} + +# Setup +cd "${TOP}" +mkdir -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package +rm -rf ${NBTMPDIR} +mkdir -p ${NBTMPDIR} + +# Copy files and create directories and links +cd "${TOP}" +makeDirectory "${NBTMPDIR}/appframeworkbinder/bin" +copyFileToTmpDir "${OUTPUT_PATH}" "${NBTMPDIR}/${PACKAGE_TOP_DIR}bin/${OUTPUT_BASENAME}" 0755 + + +# Generate tar file +cd "${TOP}" +rm -f ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package/appframeworkbinder.tar +cd ${NBTMPDIR} +tar -vcf ../../../../${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package/appframeworkbinder.tar * +checkReturnCode + +# Cleanup +cd "${TOP}" +rm -rf ${NBTMPDIR} diff --git a/nbproject/Package-Release.bash b/nbproject/Package-Release.bash new file mode 100644 index 00000000..4ef9af39 --- /dev/null +++ b/nbproject/Package-Release.bash @@ -0,0 +1,76 @@ +#!/bin/bash -x + +# +# Generated - do not edit! +# + +# Macros +TOP=`pwd` +CND_PLATFORM=GNU-Linux +CND_CONF=Release +CND_DISTDIR=dist +CND_BUILDDIR=build +CND_DLIB_EXT=so +NBTMPDIR=${CND_BUILDDIR}/${CND_CONF}/${CND_PLATFORM}/tmp-packaging +TMPDIRNAME=tmp-packaging +OUTPUT_PATH=${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/appframeworkbinder +OUTPUT_BASENAME=appframeworkbinder +PACKAGE_TOP_DIR=appframeworkbinder/ + +# Functions +function checkReturnCode +{ + rc=$? + if [ $rc != 0 ] + then + exit $rc + fi +} +function makeDirectory +# $1 directory path +# $2 permission (optional) +{ + mkdir -p "$1" + checkReturnCode + if [ "$2" != "" ] + then + chmod $2 "$1" + checkReturnCode + fi +} +function copyFileToTmpDir +# $1 from-file path +# $2 to-file path +# $3 permission +{ + cp "$1" "$2" + checkReturnCode + if [ "$3" != "" ] + then + chmod $3 "$2" + checkReturnCode + fi +} + +# Setup +cd "${TOP}" +mkdir -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package +rm -rf ${NBTMPDIR} +mkdir -p ${NBTMPDIR} + +# Copy files and create directories and links +cd "${TOP}" +makeDirectory "${NBTMPDIR}/appframeworkbinder/bin" +copyFileToTmpDir "${OUTPUT_PATH}" "${NBTMPDIR}/${PACKAGE_TOP_DIR}bin/${OUTPUT_BASENAME}" 0755 + + +# Generate tar file +cd "${TOP}" +rm -f ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package/appframeworkbinder.tar +cd ${NBTMPDIR} +tar -vcf ../../../../${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package/appframeworkbinder.tar * +checkReturnCode + +# Cleanup +cd "${TOP}" +rm -rf ${NBTMPDIR} diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml new file mode 100644 index 00000000..f30d8c0b --- /dev/null +++ b/nbproject/configurations.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configurationDescriptor version="97"> + <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT"> + <logicalFolder name="HeaderFiles" + displayName="Header Files" + projectFiles="true"> + </logicalFolder> + <logicalFolder name="ResourceFiles" + displayName="Resource Files" + projectFiles="true"> + </logicalFolder> + <logicalFolder name="SourceFiles" + displayName="Source Files" + projectFiles="true"> + <itemPath>src/afbs-api.c</itemPath> + <itemPath>src/alsa-api.c</itemPath> + <itemPath>src/config.c</itemPath> + <itemPath>src/dbus-api.c</itemPath> + <itemPath>src/http-svc.c</itemPath> + <itemPath>src/main.c</itemPath> + <itemPath>src/rest-api.c</itemPath> + <itemPath>src/session.c</itemPath> + </logicalFolder> + <logicalFolder name="TestFiles" + displayName="Test Files" + projectFiles="false" + kind="TEST_LOGICAL_FOLDER"> + </logicalFolder> + <logicalFolder name="ExternalFiles" + displayName="Important Files" + projectFiles="false" + kind="IMPORTANT_FILES_FOLDER"> + <itemPath>Makefile</itemPath> + </logicalFolder> + </logicalFolder> + <projectmakefile>Makefile</projectmakefile> + <confs> + <conf name="Debug" type="1"> + <toolsSet> + <compilerSet>default</compilerSet> + <dependencyChecking>true</dependencyChecking> + <rebuildPropChanged>false</rebuildPropChanged> + </toolsSet> + <compileType> + <cTool> + <incDir> + <pElem>/usr/include/json-c</pElem> + <pElem>include</pElem> + </incDir> + </cTool> + <linkerTool> + <linkerLibItems> + <linkerOptionItem>`pkg-config --libs libmicrohttpd`</linkerOptionItem> + <linkerOptionItem>`pkg-config --libs json-c`</linkerOptionItem> + </linkerLibItems> + </linkerTool> + </compileType> + <item path="src/afbs-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/alsa-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/config.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/dbus-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/http-svc.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/main.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/rest-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/session.c" ex="false" tool="0" flavor2="0"> + </item> + </conf> + <conf name="Release" type="1"> + <toolsSet> + <compilerSet>default</compilerSet> + <dependencyChecking>true</dependencyChecking> + <rebuildPropChanged>false</rebuildPropChanged> + </toolsSet> + <compileType> + <cTool> + <developmentMode>5</developmentMode> + </cTool> + <ccTool> + <developmentMode>5</developmentMode> + </ccTool> + <fortranCompilerTool> + <developmentMode>5</developmentMode> + </fortranCompilerTool> + <asmTool> + <developmentMode>5</developmentMode> + </asmTool> + </compileType> + <item path="src/afbs-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/alsa-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/config.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/dbus-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/http-svc.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/main.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/rest-api.c" ex="false" tool="0" flavor2="0"> + </item> + <item path="src/session.c" ex="false" tool="0" flavor2="0"> + </item> + </conf> + </confs> +</configurationDescriptor> diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 00000000..394601fb --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://www.netbeans.org/ns/project/1"> + <type>org.netbeans.modules.cnd.makeproject</type> + <configuration> + <data xmlns="http://www.netbeans.org/ns/make-project/1"> + <name>AppFrameworkBinder</name> + <c-extensions>c</c-extensions> + <cpp-extensions/> + <header-extensions/> + <sourceEncoding>UTF-8</sourceEncoding> + <make-dep-projects/> + <sourceRootList/> + <confList> + <confElem> + <name>Debug</name> + <type>1</type> + </confElem> + <confElem> + <name>Release</name> + <type>1</type> + </confElem> + </confList> + <formatting> + <project-formatting-style>false</project-formatting-style> + </formatting> + </data> + </configuration> +</project> diff --git a/src/afbs-api.c b/src/afbs-api.c new file mode 100644 index 00000000..5b7d4896 --- /dev/null +++ b/src/afbs-api.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "local-def.h" + +STATIC json_object* pingAfbs (AFB_session *session, AFB_request *request) { + static pingcount=0; + json_object *response; + const char * argval; + + argval=getQueryValue (request, "arg"); + if (argval == NULL) { + argval="No present in query"; + }; + + response = jsonNewMessage(AFB_SUCCESS, "Ping Application Framework %d [arg=%s]", pingcount++, argval); + if (verbose) fprintf(stderr, "%d: \n", pingcount); + return (response); +}; + + +STATIC AFB_restapi pluginApis[]= { + {"/ping" , (AFB_apiCB)pingSample ,"Ping Service"}, + {"/get-all" , (AFB_apiCB)pingAfbs ,"Ping Application Framework"}, + {"/get-one" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/start-one", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/stop-one" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/probe-one", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/ctx-store", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/ctx-load" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {0,0,0} +}; + +PUBLIC AFB_plugin *afsvRegister (AFB_session *session) { + AFB_plugin plugin; + plugin.type = AFB_PLUGIN; + plugin.info = "Application Framework Binder Service"; + plugin.prefix= "afbs"; // url base + plugin.apis = pluginApis; + + return (&plugin); +};
\ No newline at end of file diff --git a/src/alsa-api.c b/src/alsa-api.c new file mode 100644 index 00000000..dca372ab --- /dev/null +++ b/src/alsa-api.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "local-def.h" + +STATIC json_object* pingAfbs (AFB_plugin *plugin, AFB_session *session, struct MHD_Connection *connection, AFB_request *request) { + static pingcount=0; + json_object *response; + response = jsonNewMessage(AFB_SUCCESS, "Ping Application Framework %d", pingcount++); + if (verbose) fprintf(stderr, "%d: \n", pingcount); + return (response); +}; + + +STATIC AFB_restapi pluginApis[]= { + {"/ping" , (AFB_apiCB)pingSample ,"Ping Service"}, + {"/get-all" , (AFB_apiCB)pingAfbs ,"Ping Application Framework"}, + {"/get-one" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/start-one", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/stop-one" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/probe-one", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/ctx-store", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/ctx-load" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {0,0,0} +}; + +PUBLIC AFB_plugin *alsaRegister (AFB_session *session) { + AFB_plugin *plugin = malloc (sizeof (AFB_plugin)); + + plugin->info = "Application Framework Binder Service"; + plugin->prefix = "alsa"; + plugin->apis = pluginApis; + + return (plugin); +};
\ No newline at end of file diff --git a/src/config.c b/src/config.c new file mode 100644 index 00000000..893b3a5f --- /dev/null +++ b/src/config.c @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + + References: + https://www.gnu.org/software/libmicrohttpd/manual/html_node/index.html#Top + http://www-01.ibm.com/support/knowledgecenter/SSB23S_1.1.0.9/com.ibm.ztpf-ztpfdf.doc_put.09/gtpc2/cpp_vsprintf.html?cp=SSB23S_1.1.0.9%2F0-3-8-1-0-16-8 + +*/ + + +#include "../include/local-def.h" +#include <stdarg.h> +#include <sys/stat.h> +#include <sys/types.h> + + +#define AFB_CONFIG_JTYPE "AFB_config" + +PUBLIC char *ERROR_LABEL[]=ERROR_LABEL_DEF; + +PUBLIC int verbose; +STATIC AFB_ErrorT AFB_Error [AFB_SUCCESS+1]; +STATIC json_object *AFBJsonType; + +/* ------------------------------------------------------------------------------ + * Get localtime and return in a string + * ------------------------------------------------------------------------------ */ + +PUBLIC char * configTime (void) { + static char reqTime [26]; + time_t tt; + struct tm *rt; + + /* Get actual Date and Time */ + time (&tt); + rt = localtime (&tt); + + strftime (reqTime, sizeof (reqTime), "(%d-%b %H:%M)",rt); + + // return pointer on static data + return (reqTime); +} + +// load config from disk and merge with CLI option +PUBLIC AFB_ERROR configLoadFile (AFB_session * session, AFB_config *cliconfig) { + static char cacheTimeout [10]; + int fd; + json_object * AFBConfig, *value; + + // fix config redirect message + session->config->html5.msg = "Angular/HTML5 redirect"; + session->config->html5.len = strlen(session->config->html5.msg); + + // default HTTP port + if (cliconfig->httpdPort == 0) session->config->httpdPort=1234; + else session->config->httpdPort=cliconfig->httpdPort; + + // cache timeout default one hour + if (cliconfig->cacheTimeout == 0) session->config->cacheTimeout=3600; + else session->config->cacheTimeout=cliconfig->cacheTimeout; + + if (cliconfig->rootdir == NULL) { + session->config->rootdir = getenv("AFBDIR"); + if (session->config->rootdir == NULL) { + session->config->rootdir = malloc (512); + strncpy (session->config->rootdir, getenv("HOME"),512); + strncat (session->config->rootdir, "/.AFB",512); + } + // if directory does not exist createit + mkdir (session->config->rootdir, O_RDWR | S_IRWXU | S_IRGRP); + } else { + session->config->rootdir = cliconfig->rootdir; + } + + // if no Angular/HTML5 rootbase let's try '/' as default + if (cliconfig->rootbase == NULL) { + session->config->rootbase = "/"; + } else { + session->config->console= cliconfig->console; + } + + // if no rootapi use '/api' + if (cliconfig->rootbase == NULL) { + session->config->rootbase = "/api"; + } else { + session->config->console= cliconfig->console; + } + + + + // if no session dir create a default path from rootdir + if (cliconfig->sessiondir == NULL) { + session->config->sessiondir = malloc (512); + strncpy (session->config->sessiondir, session->config->rootdir, 512); + strncat (session->config->sessiondir, "/sessions",512); + } else { + session->config->sessiondir = cliconfig->sessiondir; + } + + // if no config dir create a default path from sessiondir + if (cliconfig->configfile == NULL) { + session->config->configfile = malloc (512); + strncpy (session->config->configfile, session->config->sessiondir, 512); + strncat (session->config->configfile, "/AFB-config.json",512); + } else { + session->config->configfile = cliconfig->configfile; + } + + // if no config dir create a default path from sessiondir + if (cliconfig->pidfile == NULL) { + session->config->pidfile = malloc (512); + strncpy (session->config->pidfile, session->config->sessiondir, 512); + strncat (session->config->pidfile, "/AFB-process.pid",512); + } else { + session->config->pidfile= cliconfig->pidfile; + } + + // if no config dir create a default path from sessiondir + if (cliconfig->console == NULL) { + session->config->console = malloc (512); + strncpy (session->config->console, session->config->sessiondir, 512); + strncat (session->config->console, "/AFB-console.out",512); + } else { + session->config->console= cliconfig->console; + } + + // just upload json object and return without any further processing + if((fd = open(session->config->configfile, O_RDONLY)) < 0) { + if (verbose) fprintf (stderr, "AFB:notice: config at %s: %s\n", session->config->configfile, strerror(errno)); + return AFB_EMPTY; + } + + // openjson from FD is not public API we need to reopen it !!! + close(fd); + AFBConfig = json_object_from_file (session->config->configfile); + + // check it is an AFB_config + if (json_object_object_get_ex (AFBConfig, "jtype", &value)) { + if (strcmp (AFB_CONFIG_JTYPE, json_object_get_string (value))) { + fprintf (stderr,"AFB: Error file [%s] is not a valid [%s] type\n ", session->config->configfile, AFB_CONFIG_JTYPE); + return AFB_FAIL; + } + } + + if (!cliconfig->rootdir && json_object_object_get_ex (AFBConfig, "rootdir", &value)) { + session->config->rootdir = strdup (json_object_get_string (value)); + } + + if (!cliconfig->rootbase && json_object_object_get_ex (AFBConfig, "rootbase", &value)) { + session->config->rootbase = strdup (json_object_get_string (value)); + } + + if (!cliconfig->rootapi && json_object_object_get_ex (AFBConfig, "rootapi", &value)) { + session->config->rootapi = strdup (json_object_get_string (value)); + } + + if (!cliconfig->smack && json_object_object_get_ex (AFBConfig, "smack", &value)) { + session->config->smack = strdup (json_object_get_string (value)); + } + + if (!cliconfig->plugins && json_object_object_get_ex (AFBConfig, "plugins", &value)) { + session->config->plugins = strdup (json_object_get_string (value)); + } + + if (!cliconfig->sessiondir && json_object_object_get_ex (AFBConfig, "sessiondir", &value)) { + session->config->sessiondir = strdup (json_object_get_string (value)); + } + + if (!cliconfig->pidfile && json_object_object_get_ex (AFBConfig, "pidfile", &value)) { + session->config->pidfile = strdup (json_object_get_string (value)); + } + + if (!cliconfig->httpdPort && json_object_object_get_ex (AFBConfig, "httpdPort", &value)) { + session->config->httpdPort = json_object_get_int (value); + } + + if (!cliconfig->setuid && json_object_object_get_ex (AFBConfig, "setuid", &value)) { + session->config->setuid = json_object_get_int (value); + } + + if (!cliconfig->localhostOnly && json_object_object_get_ex (AFBConfig, "localhostonly", &value)) { + session->config->localhostOnly = json_object_get_int (value); + } + + if (!cliconfig->cacheTimeout && json_object_object_get_ex (AFBConfig, "cachetimeout", &value)) { + session->config->cacheTimeout = json_object_get_int (value); + } + // cacheTimeout is an interger but HTTPd wants it as a string + snprintf (cacheTimeout, sizeof (cacheTimeout),"%d", session->config->cacheTimeout); + session->cacheTimeout = cacheTimeout; // httpd uses cacheTimeout string version + json_object_put (AFBConfig); // decrease reference count to free the json object + + return AFB_SUCCESS; +} + +// Save the config on disk +PUBLIC void configStoreFile (AFB_session * session) { + json_object * AFBConfig; + time_t rawtime; + struct tm * timeinfo; + int err; + + AFBConfig = json_object_new_object(); + + // add a timestamp and store session on disk + time ( &rawtime ); timeinfo = localtime ( &rawtime ); + // A copy of the string is made and the memory is managed by the json_object + json_object_object_add (AFBConfig, "jtype" , json_object_new_string (AFB_CONFIG_JTYPE)); + json_object_object_add (AFBConfig, "timestamp" , json_object_new_string (asctime (timeinfo))); + json_object_object_add (AFBConfig, "rootdir" , json_object_new_string (session->config->rootdir)); + json_object_object_add (AFBConfig, "rootapi" , json_object_new_string (session->config->rootapi)); + json_object_object_add (AFBConfig, "rootbase" , json_object_new_string (session->config->rootbase)); + json_object_object_add (AFBConfig, "smack" , json_object_new_string (session->config->smack)); + json_object_object_add (AFBConfig, "plugins" , json_object_new_string (session->config->plugins)); + json_object_object_add (AFBConfig, "sessiondir" , json_object_new_string (session->config->sessiondir)); + json_object_object_add (AFBConfig, "pidfile" , json_object_new_string (session->config->pidfile)); + json_object_object_add (AFBConfig, "httpdPort" , json_object_new_int (session->config->httpdPort)); + json_object_object_add (AFBConfig, "setuid" , json_object_new_int (session->config->setuid)); + json_object_object_add (AFBConfig, "localhostonly" , json_object_new_int (session->config->localhostOnly)); + json_object_object_add (AFBConfig, "cachetimeout" , json_object_new_int (session->config->cacheTimeout)); + + err = json_object_to_file (session->config->configfile, AFBConfig); + json_object_put (AFBConfig); // decrease reference count to free the json object + if (err < 0) { + fprintf(stderr, "AFB: Fail to save config on disk [%s]\n ", session->config->configfile); + } +} + + +PUBLIC AFB_session *configInit () { + + AFB_session *session; + AFB_config *config; + int idx, verbosesav; + + + session = malloc (sizeof (AFB_session)); + memset (session,0, sizeof (AFB_session)); + + // create config handle + config = malloc (sizeof (AFB_config)); + memset (config,0, sizeof (AFB_config)); + + // stack config handle into session + session->config = config; + + AFBJsonType = json_object_new_string ("AFB_message"); + + // initialise JSON constant messages and increase reference count to make them permanent + verbosesav = verbose; + verbose = 0; // run initialisation in silent mode + + + + for (idx = 0; idx <= AFB_SUCCESS; idx++) { + AFB_Error[idx].level = idx; + AFB_Error[idx].label = ERROR_LABEL [idx]; + AFB_Error[idx].json = jsonNewMessage (idx, NULL); + } + verbose = verbosesav; + + return (session); +} + + +// get JSON object from error level and increase its reference count +PUBLIC json_object *jsonNewStatus (AFB_ERROR level) { + + json_object *target = AFB_Error[level].json; + json_object_get (target); + + return (target); +} + +// get AFB object type with adequate usage count +PUBLIC json_object *jsonNewjtype (void) { + json_object_get (AFBJsonType); // increase reference count + return (AFBJsonType); +} + +// build an ERROR message and return it as a valid json object +PUBLIC json_object *jsonNewMessage (AFB_ERROR level, char* format, ...) { + static int count = 0; + json_object * AFBResponse; + va_list args; + char message [512]; + + // format message + if (format != NULL) { + va_start(args, format); + vsnprintf (message, sizeof (message), format, args); + va_end(args); + } + + AFBResponse = json_object_new_object(); + json_object_object_add (AFBResponse, "jtype", jsonNewjtype ()); + json_object_object_add (AFBResponse, "status" , json_object_new_string (ERROR_LABEL[level])); + if (format != NULL) { + json_object_object_add (AFBResponse, "info" , json_object_new_string (message)); + } + if (verbose) { + fprintf (stderr, "AFB:%-6s [%3d]: ", AFB_Error [level].label, count++); + if (format != NULL) { + fprintf (stderr, "%s", message); + } else { + fprintf (stderr, "No Message"); + } + fprintf (stderr, "\n"); + } + + return (AFBResponse); +} + +// Dump a message on stderr +PUBLIC void jsonDumpObject (json_object * jObject) { + + if (verbose) { + fprintf (stderr, "AFB:dump [%s]\n", json_object_to_json_string(jObject)); + } +} + diff --git a/src/dbus-api.c b/src/dbus-api.c new file mode 100644 index 00000000..febe87d9 --- /dev/null +++ b/src/dbus-api.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "local-def.h" + +STATIC json_object* pingAfbs (AFB_plugin *plugin, AFB_session *session, struct MHD_Connection *connection, AFB_request *request) { + static pingcount=0; + json_object *response; + response = jsonNewMessage(AFB_SUCCESS, "Ping Application Framework %d", pingcount++); + if (verbose) fprintf(stderr, "%d: \n", pingcount); + return (response); +}; + + +STATIC AFB_restapi pluginApis[]= { + {"/ping" , (AFB_apiCB)pingSample ,"Ping Service"}, + {"/get-all" , (AFB_apiCB)pingAfbs ,"Ping Application Framework"}, + {"/get-one" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/start-one", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/stop-one" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/probe-one", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/ctx-store", (AFB_apiCB)pingSample ,"Verbose Mode"}, + {"/ctx-load" , (AFB_apiCB)pingSample ,"Verbose Mode"}, + {0,0,0} +}; + +PUBLIC AFB_plugin *dbusRegister (AFB_session *session) { + AFB_plugin *plugin = malloc (sizeof (AFB_plugin)); + + plugin->info = "Application Framework Binder Service"; + plugin->prefix= "dbus"; + plugin->apis = pluginApis; + + return (plugin); +};
\ No newline at end of file diff --git a/src/http-svc.c b/src/http-svc.c new file mode 100644 index 00000000..b29a4bb8 --- /dev/null +++ b/src/http-svc.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + * + * Handle standard HTTP request + * Features/Restriction: + - handle ETAG to limit upload to modified/new files [cache default 3600s] + - handles redirect to index.htlm when path is a directory [code 301] + - only support GET method + - does not follow link. + + References: https://www.gnu.org/software/libmicrohttpd/manual/html_node/index.html#Top + http://libmicrohttpd.sourcearchive.com/documentation/0.4.2/microhttpd_8h.html + https://gnunet.org/svn/libmicrohttpd/src/examples/fileserver_example_external_select.c + https://github.com/json-c/json-c + POST https://www.gnu.org/software/libmicrohttpd/manual/html_node/microhttpd_002dpost.html#microhttpd_002dpost + */ + + +#include <microhttpd.h> +#include <sys/stat.h> +#include "../include/local-def.h" + +// proto missing from GCC +char *strcasestr(const char *haystack, const char *needle); + +static int rqtcount = 0; // dummy request rqtcount to make each message be different +static int postcount = 0; +static int aipUrlLen=0; // do not compute apiurl for each call +static int baseUrlLen=0; // do not compute baseurl for each call + +// Because of POST call multiple time requestApi we need to free POST handle here +static void endRequest (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { + AFB_HttpPost *posthandle = *con_cls; + + // if post handle was used let's free everything + if (posthandle) { + if (verbose) fprintf (stderr, "End Post Request UID=%d\n", posthandle->uid); + free (posthandle->data); + free (posthandle); + } +} + + +// Create check etag value +STATIC void computeEtag(char *etag, int maxlen, struct stat *sbuf) { + int time; + time = sbuf->st_mtim.tv_sec; + snprintf(etag, maxlen, "%d", time); +} + +STATIC int servFile (struct MHD_Connection *connection, AFB_session *session, const char *url, char *filepath, int fd) { + const char *etagCache; + char etagValue[15]; + struct MHD_Response *response; + struct stat sbuf; + int ret; + + if (fstat (fd, &sbuf) != 0) { + fprintf(stderr, "Fail to stat file: [%s] error:%s\n", filepath, strerror(errno)); + return (FAILED); + } + + // if url is a directory let's add index.html and redirect client + if (S_ISDIR (sbuf.st_mode)) { + strncpy (filepath, url, sizeof (filepath)); + + if (url [strlen (url) -1] != '/') strncat (filepath, "/", sizeof (filepath)); + strncat (filepath, "index.html", sizeof (filepath)); + close (fd); + response = MHD_create_response_from_buffer (0,"", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header (response,MHD_HTTP_HEADER_LOCATION, filepath); + ret = MHD_queue_response (connection, MHD_HTTP_MOVED_PERMANENTLY, response); + + } else if (! S_ISREG (sbuf.st_mode)) { // only standard file any other one including symbolic links are refused. + + fprintf (stderr, "Fail file: [%s] is not a regular file\n", filepath); + const char *errorstr = "<html><body>Alsa-Json-Gateway Invalid file type</body></html>"; + response = MHD_create_response_from_buffer (strlen (errorstr), + (void *) errorstr, MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); + + } else { + + // https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=fr + // ftp://ftp.heanet.ie/disk1/www.gnu.org/software/libmicrohttpd/doxygen/dc/d0c/microhttpd_8h.html + + // Check etag value and load file only when modification date changes + etagCache = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH); + computeEtag(etagValue, sizeof (etagValue), &sbuf); + + if (etagCache != NULL && strcmp(etagValue, etagCache) == 0) { + close(fd); // file did not change since last upload + if (verbose) fprintf(stderr, "Not Modify: [%s]\n", filepath); + response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT); + MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache + MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue); + ret = MHD_queue_response(connection, MHD_HTTP_NOT_MODIFIED, response); + + } else { // it's a new file, we need to upload it to client + if (verbose) fprintf(stderr, "Serving: [%s]\n", filepath); + response = MHD_create_response_from_fd(sbuf.st_size, fd); + MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache + MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue); + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + } + } + MHD_destroy_response(response); + return (ret); + +} + +// minimal httpd file server for static HTML,JS,CSS,etc... +STATIC int requestFile(struct MHD_Connection *connection, AFB_session *session, const char* url) { + int fd; + int ret; + + char filepath [512]; + + // build full path from rootdir + url + strncpy(filepath, session->config->rootdir, sizeof (filepath)); + strncat(filepath, url, 511); + + // try to open file and get its size + if (-1 == (fd = open(filepath, O_RDONLY))) { + fprintf(stderr, "Fail to open file: [%s] error:%s\n", filepath, strerror(errno)); + return (FAILED); + + } + // open file is OK let use it + ret = servFile (connection, session, url, filepath, fd); + return ret; +} + +// this function return either Index.htlm or a redirect to /#!route to make angular happy +STATIC int checkHTML5(struct MHD_Connection *connection, AFB_session *session, const char* url) { + + int fd; + int ret; + struct MHD_Response *response; + char filepath [512]; + + // if requesting '/' serve index.html + if (strlen (url) == 0) { + strncpy(filepath, session->config->rootdir, sizeof (filepath)); + strncat(filepath, "/index.html", sizeof (filepath)); + // try to open file and get its size + if (-1 == (fd = open(filepath, O_RDONLY))) { + fprintf(stderr, "Fail to open file: [%s] error:%s\n", filepath, strerror(errno)); + // Nothing respond to this request Files, API, Angular Base + const char *errorstr = "<html><body>Alsa-Json-Gateway Unknown or Not readable file</body></html>"; + response = MHD_create_response_from_buffer(strlen(errorstr),(void *)errorstr, MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); + ret = MHD_YES; + return (FAILED); + } else { + ret = servFile (connection, session, url, filepath, fd); + return ret; + } + } + + // we are facing a internal route within the HTML5 OnePageApp let's redirect ex: /myapp/#!user/login + strncpy(filepath, session->config->rootbase, sizeof (filepath)); + strncat(filepath, "#!", sizeof (filepath)); + strncat(filepath, url, sizeof (filepath)); + response = MHD_create_response_from_buffer(session->config->html5.len,(void *)session->config->html5.msg, MHD_RESPMEM_PERSISTENT); + MHD_add_response_header (response, "Location", "http://somesite.com/page.html"); + MHD_queue_response (connection, MHD_HTTP_OK, response); +} + +// Check and Dispatch HTTP request +STATIC int newRequest(void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, size_t *upload_data_size, void **con_cls) { + + AFB_session *session = cls; + struct MHD_Response *response; + int ret; + + // this is an Angular request we change URL /!#xxxxx + if (0 == strncmp(url, session->config->rootapi, baseUrlLen)) { + ret = doRestApi(connection, session, method, &url[baseUrlLen]); + return ret; + } + + // From here only accept get request + if (0 != strcmp(method, MHD_HTTP_METHOD_GET)) return MHD_NO; /* unexpected method */ + + // If a static file exist serve it now + ret = requestFile(connection, session, url); + if (ret != FAILED) return ret; + + // no static was served let check for Angular redirect + if (0 == strncmp(url, session->config->rootbase, baseUrlLen)) { + ret = checkHTML5(connection, session, &url[baseUrlLen]); + return ret; + } + + // Nothing respond to this request Files, API, Angular Base + const char *errorstr = "<html><body>Alsa-Json-Gateway Unknown or Not readable file</body></html>"; + response = MHD_create_response_from_buffer(strlen(errorstr), (void*)errorstr, MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); + return (MHD_YES); +} + +STATIC int newClient(void *cls, const struct sockaddr * addr, socklen_t addrlen) { + // check if client is comming from an acceptable IP + return (MHD_YES); // MHD_NO +} + + +PUBLIC AFB_ERROR httpdStart(AFB_session *session) { + + // do this only once + aipUrlLen = strlen (session->config->rootapi); + baseUrlLen = strlen (session->config->rootbase); + + if (verbose) { + printf("AFB:notice Waiting port=%d rootdir=%s\n", session->config->httpdPort, session->config->rootdir); + printf("AFB:notice Browser URL= http://localhost:%d\n", session->config->httpdPort); + } + + session->httpd = (void*) MHD_start_daemon( + MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, // use request and not threads + session->config->httpdPort, // port + &newClient, NULL, // Tcp Accept call back + extra attribute + &newRequest, session, // Http Request Call back + extra attribute + MHD_OPTION_NOTIFY_COMPLETED, &endRequest, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, MHD_OPTION_END); // 15s + options-end + // TBD: MHD_OPTION_SOCK_ADDR + + if (session->httpd == NULL) { + printf("Error: httpStart invalid httpd port: %d", session->config->httpdPort); + return AFB_FATAL; + } + return AFB_SUCCESS; +} + +// infinite loop +PUBLIC AFB_ERROR httpdLoop(AFB_session *session) { + static int count = 0; + + if (verbose) fprintf(stderr, "AFB:notice entering httpd waiting loop\n"); + if (session->foreground) { + + while (TRUE) { + fprintf(stderr, "AFB:notice Use Ctrl-C to quit"); + (void) getc(stdin); + } + } else { + while (TRUE) { + sleep(3600); + if (verbose) fprintf(stderr, "AFB:notice httpd alive [%d]\n", count++); + } + } + + // should never return from here + return AFB_FATAL; +} + +PUBLIC int httpdStatus(AFB_session *session) { + return (MHD_run(session->httpd)); +} + +PUBLIC void httpdStop(AFB_session *session) { + MHD_stop_daemon(session->httpd); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 00000000..795c2a7b --- /dev/null +++ b/src/main.c @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * File: main.c + * Author: "Fulup Ar Foll" + * + * Created on 05 December 2015, 15:38 + */ + +#include "local-def.h" + +#include <syslog.h> +#include <setjmp.h> +#include <signal.h> +#include <getopt.h> + +static sigjmp_buf exitpoint; // context save for set/longjmp +static sigjmp_buf restartpoint; // context save for set/longjmp + +/*---------------------------------------------------------- + | printversion + | print version and copyright + +--------------------------------------------------------- */ + static void printVersion (void) { + + fprintf (stderr,"\n----------------------------------------- \n"); + fprintf (stderr,"| AFB [Application Framework Binder] version=%s |\n", AJQ_VERSION); + fprintf (stderr,"----------------------------------------- \n"); + fprintf (stderr,"| Copyright(C) 2015 Fulup Ar Foll /IoT.bzh [fulup -at- iot.bzh]\n"); + fprintf (stderr,"| AFB comes with ABSOLUTELY NO WARRANTY.\n"); + fprintf (stderr,"| Licence [what ever makes you happy] until you fix bugs by yourself :)\n\n"); + exit (0); + } // end printVersion + + +// Define command line option + #define SET_VERBOSE 101 + #define SET_BACKGROUND 105 + #define SET_FORGROUND 106 + #define KILL_PREV_EXIT 107 + #define KILL_PREV_REST 108 + #define SET_FAKE_MOD 109 + + #define SET_TCP_PORT 120 + #define SET_ROOT_DIR 121 + #define SET_ROOT_BASE 122 + #define SET_ROOT_API 123 + + #define SET_CACHE_TO 130 + #define SET_cardid 131 + #define SET_PID_FILE 132 + #define SET_SESSION_DIR 133 + #define SET_CONFIG_FILE 134 + #define SET_CONFIG_SAVE 135 + #define SET_CONFIG_EXIT 138 + + #define SET_SMACK 140 + #define SET_PLUGINS 141 + + #define DISPLAY_VERSION 150 + #define DISPLAY_HELP 151 + + +// Supported option +static AFB_options cliOptions [] = { + {SET_VERBOSE ,0,"verbose" , "Verbose Mode"}, + + {SET_FORGROUND ,0,"foreground" , "Get all in foreground mode"}, + {SET_BACKGROUND ,0,"daemon" , "Get all in background mode"}, + {KILL_PREV_EXIT ,0,"kill" , "Kill active process if any and exit"}, + {KILL_PREV_REST ,0,"restart" , "Kill active process if any and restart"}, + + {SET_TCP_PORT ,1,"port" , "HTTP listening TCP port [default 1234]"}, + {SET_ROOT_DIR ,1,"rootdir" , "HTTP Root Directory [default $HOME/.AFB"}, + {SET_ROOT_BASE ,1,"rootbase" , "Angular Base Root URL [default /"}, + {SET_ROOT_API ,1,"rootapi" , "HTML Root API URL [default /api"}, + + {SET_CACHE_TO ,1,"cache-eol" , "Client cache end of live [default 3600s]"}, + {SET_cardid ,1,"setuid" , "Change user id [default don't change]"}, + {SET_PID_FILE ,1,"pidfile" , "PID file path [default none]"}, + {SET_SESSION_DIR ,1,"sessiondir" , "Sessions file path [default rootdir/sessions]"}, + {SET_CONFIG_FILE ,1,"config" , "Config Filename [default rootdir/sessions/configs/default.AFB]"}, + {SET_CONFIG_SAVE ,0,"save" , "Save config on disk [default no]"}, + {SET_CONFIG_EXIT ,0,"saveonly" , "Save config on disk and then exit"}, + + {SET_SMACK ,1,"smack" , "Set Smack Label [default=demo"}, + {SET_PLUGINS ,1,"mods" , "Enable module [default=all"}, + + {DISPLAY_VERSION ,0,"version" , "Display version and copyright"}, + {DISPLAY_HELP ,0,"help" , "Display this help"}, + {0, 0, 0} + }; + +/*---------------------------------------------------------- + | signalQuit + | return to intitial exitpoint on order to close backend + | before exiting. + +--------------------------------------------------------- */ +void signalQuit (int signum) +{ + if (verbose) printf ("INF:signalQuit received signal to quit\n"); + longjmp (exitpoint, signum); +} + +/*---------------------------------------------------------- + | timeout signalQuit + | + +--------------------------------------------------------- */ +void signalFail (int signum) { + + sigset_t sigset; + + // unlock timeout signal to allow a new signal to come + sigemptyset (&sigset); + sigaddset (&sigset, SIGABRT); + sigprocmask (SIG_UNBLOCK, &sigset, 0); + + fprintf (stderr, "%s ERR:getAllBlock acquisition timeout\n",configTime()); + syslog (LOG_ERR, "Daemon fail and restart [please report bug]"); + longjmp (restartpoint, signum); +} + + +/*---------------------------------------------------------- + | printHelp + | print information from long option array + +--------------------------------------------------------- */ + + static void printHelp(char *name) { + int ind; + char command[20]; + + fprintf (stderr,"%s:\nallowed options\n", name); + for (ind=0; cliOptions [ind].name != NULL;ind++) + { + // display options + if (cliOptions [ind].has_arg == 0 ) + { + fprintf (stderr," --%-15s %s\n", cliOptions [ind].name, cliOptions[ind].help); + } else { + sprintf(command,"%s=xxxx", cliOptions [ind].name); + fprintf (stderr," --%-15s %s\n", command, cliOptions[ind].help); + } + } + fprintf (stderr,"Example:\n %s\\\n --verbose --port=1234 --smack=xxxx --mods=alsa:dbus\n", name); +} // end printHelp + +/*---------------------------------------------------------- + | writePidFile + | write a file in /var/run/AFB with pid + +--------------------------------------------------------- */ +static int writePidFile (AFB_config *config, int pid) { + FILE *file; + + // if no pid file configure just return + if (config->pidfile == NULL) return 0; + + // open pid file in write mode + file = fopen(config->pidfile,"w"); + if (file == NULL) { + fprintf (stderr,"%s ERR:writePidFile fail to open [%s]\n",configTime(), config->pidfile); + return -1; + } + + // write pid in file and close + fprintf (file, "%d\n", pid); + fclose (file); + return 0; +} + +/*---------------------------------------------------------- + | readPidFile + | read file in /var/run/AFB with pid + +--------------------------------------------------------- */ +static int readPidFile (AFB_config *config) { + int pid; + FILE *file; + int status; + + if (config->pidfile == NULL) return -1; + + // open pid file in write mode + file = fopen(config->pidfile,"r"); + if (file == NULL) { + fprintf (stderr,"%s ERR:readPidFile fail to open [%s]\n",configTime(), config->pidfile); + return -1; + } + + // write pid in file and close + status = fscanf (file, "%d\n", &pid); + fclose (file); + + // never kill pid 0 + if (status != 1) return -1; + + return (pid); +} + +/*---------------------------------------------------------- + | closeSession + | try to close everything before leaving + +--------------------------------------------------------- */ +static void closeSession (AFB_session *session) { + + +} + + +/*---------------------------------------------------------- + | listenLoop + | Main listening HTTP loop + +--------------------------------------------------------- */ +static void listenLoop (AFB_session *session) { + AFB_ERROR err; + + if (signal (SIGABRT, signalFail) == SIG_ERR) { + fprintf (stderr, "%s ERR: main fail to install Signal handler\n", configTime()); + return; + } + + // ------ Start httpd server + if (session->config->httpdPort > 0) { + + err = httpdStart (session); + if (err != AFB_SUCCESS) return; + + // infinite loop + httpdLoop(session); + + fprintf (stderr, "hoops returned from infinite loop [report bug]\n"); + } +} + + +/*--------------------------------------------------------- + | main + | Parse option and launch action + +--------------------------------------------------------- */ + +int main(int argc, char *argv[]) { + AFB_session *session; + char* programName = argv [0]; + int optionIndex = 0; + int optc, ind, consoleFD; + int pid, nbcmd, status; + AFB_config cliconfig; // temp structure to store CLI option before file config upload + + // ------------- Build session handler & init config ------- + session = configInit (); + memset (&cliconfig,0,sizeof(cliconfig)); + + // GNU CLI getopts nterface. + struct option ggcOption; + struct option *gnuOptions; + + // ------------------ Process Command Line ----------------------- + + // if no argument print help and return + if (argc < 2) { + printHelp(programName); + return (-1); + } + + // build GNU getopt info from cliOptions + nbcmd = sizeof (cliOptions) / sizeof (AFB_options); + gnuOptions = malloc (sizeof (ggcOption) * nbcmd); + for (ind=0; ind < nbcmd;ind++) { + gnuOptions [ind].name = cliOptions[ind].name; + gnuOptions [ind].has_arg = cliOptions[ind].has_arg; + gnuOptions [ind].flag = 0; + gnuOptions [ind].val = cliOptions[ind].val; + } + + // get all options from command line + while ((optc = getopt_long (argc, argv, "vsp?", gnuOptions, &optionIndex)) + != EOF) + { + switch (optc) + { + case SET_VERBOSE: + verbose = 1; + break; + + case SET_TCP_PORT: + if (optarg == 0) goto needValueForOption; + if (!sscanf (optarg, "%d", &cliconfig.httpdPort)) goto notAnInteger; + break; + + case SET_ROOT_DIR: + if (optarg == 0) goto needValueForOption; + cliconfig.rootdir = optarg; + break; + + case SET_ROOT_BASE: + if (optarg == 0) goto needValueForOption; + cliconfig.rootbase = optarg; + break; + + case SET_ROOT_API: + if (optarg == 0) goto needValueForOption; + cliconfig.rootapi = optarg; + break; + + case SET_SMACK: + if (optarg == 0) goto needValueForOption; + fprintf (stderr, "Not Implemented yet\n"); + cliconfig.smack = optarg; + break; + + case SET_PLUGINS: + if (optarg == 0) goto needValueForOption; + fprintf (stderr, "Not Implemented yet\n"); + cliconfig.plugins = optarg; + break; + + case SET_PID_FILE: + if (optarg == 0) goto needValueForOption; + cliconfig.pidfile = optarg; + break; + + case SET_SESSION_DIR: + if (optarg == 0) goto needValueForOption; + cliconfig.sessiondir = optarg; + break; + + case SET_CONFIG_FILE: + if (optarg == 0) goto needValueForOption; + cliconfig.configfile = optarg; + break; + + case SET_CACHE_TO: + if (optarg == 0) goto needValueForOption; + if (!sscanf (optarg, "%d", &cliconfig.cacheTimeout)) goto notAnInteger; + break; + + case SET_CONFIG_EXIT: + if (optarg != 0) goto noValueForOption; + session->configsave = 1; + session->forceexit = 1; + break; + + case SET_CONFIG_SAVE: + if (optarg != 0) goto noValueForOption; + session->configsave = 1; + break; + + case SET_cardid: + if (optarg == 0) goto needValueForOption; + if (!sscanf (optarg, "%d", &cliconfig.setuid)) goto notAnInteger; + break; + + case SET_FAKE_MOD: + if (optarg != 0) goto noValueForOption; + session->fakemod = 1; + break; + + case SET_FORGROUND: + if (optarg != 0) goto noValueForOption; + session->foreground = 1; + break; + + case SET_BACKGROUND: + if (optarg != 0) goto noValueForOption; + session->background = 1; + break; + + case KILL_PREV_REST: + if (optarg != 0) goto noValueForOption; + session->killPrevious = 1; + break; + + case KILL_PREV_EXIT: + if (optarg != 0) goto noValueForOption; + session->killPrevious = 2; + break; + + case DISPLAY_VERSION: + if (optarg != 0) goto noValueForOption; + printVersion(); + goto normalExit; + + case DISPLAY_HELP: + default: + printHelp(programName); + goto normalExit; + + } + } + // Create session config + configInit (/* session & config are initialized globally */); + + // if exist merge config file with CLI arguments + configLoadFile (session, &cliconfig); + + // ------------------ sanity check ---------------------------------------- + if ((session->background) && (session->foreground)) { + fprintf (stderr, "%s ERR: cannot select foreground & background at the same time\n",configTime()); + exit (-1); + } + + // ------------------ Some useful default values ------------------------- + if ((session->background == 0) && (session->foreground == 0)) session->foreground=1; + + // open syslog if ever needed + openlog("AGB-log", 0, LOG_DAEMON); + + // -------------- Try to kill any previsou process if asked --------------------- + if (session->killPrevious) { + pid = readPidFile (session->config); // enforce commandline option + switch (pid) { + case -1: + fprintf (stderr, "%s ERR:main --kill ignored no PID file [%s]\n",configTime(), session->config->pidfile); + break; + case 0: + fprintf (stderr, "%s ERR:main --kill ignored no active AFB process\n",configTime()); + break; + default: + status = kill (pid,SIGINT ); + if (status == 0) { + if (verbose) printf ("%s INF:main signal INTR sent to pid:%d \n", configTime(), pid); + } else { + // try kill -9 + status = kill (pid,9); + if (status != 0) fprintf (stderr, "%s ERR:main failled to killed pid=%d \n",configTime(), pid); + } + } // end switch pid + + if (session->killPrevious >= 2) goto normalExit; + } // end killPrevious + + + // ------------------ clean exit on CTR-C signal ------------------------ + if (signal (SIGINT, signalQuit) == SIG_ERR) { + fprintf (stderr, "%s Quit Signal received.",configTime()); + return (-1); + } + + // save exitpoint context when returning from longjmp closeSession and exit + status = setjmp (exitpoint); // return !+ when coming from longjmp + if (status != 0) { + if (verbose) printf ("INF:main returning from longjump after signal [%d]\n", status); + closeSession (session); + goto exitOnSignal; + } + + // let's run this program with a low priority + status=nice (20); + + + // ------------------ Finaly Process Commands ----------------------------- + + + + // if --save then store config on disk upfront + if (session->configsave) configStoreFile (session); + if (session->forceexit) exit (0); + + if (session->config->setuid) { + int err; + + err = setuid(session->config->setuid); + if (err) fprintf (stderr, "Fail to change program cardid error=%s", strerror(err)); + } + + // let's not take the risk to run as ROOT + if (getuid() == 0) status=setuid(65534); // run as nobody + + // check session dir and create if it does not exist + if (sessionCheckdir (session) != AFB_SUCCESS) goto errSessiondir; + if (verbose) fprintf (stderr, "AFB:notice Init config done\n"); + + + + // ---- run in foreground mode -------------------- + if (session->foreground) { + + if (verbose) fprintf (stderr,"AFB:notice Foreground mode\n"); + + // write a pid file for --kill-previous and --raise-debug option + status = writePidFile (session->config, getpid()); + if (status == -1) goto errorPidFile; + + // enter listening loop in foreground + listenLoop(session); + goto exitInitLoop; + } // end foreground + + + // --------- run in background mode ----------- + if (session->background) { + + // if (status != 0) goto errorCommand; + if (verbose) printf ("AFB: Entering background mode\n"); + + // open /dev/console to redirect output messAFBes + consoleFD = open(session->config->console, O_WRONLY | O_APPEND | O_CREAT , 0640); + if (consoleFD < 0) goto errConsole; + + // fork process when running background mode + pid = fork (); + + // son process get all data in standalone mode + if (pid == 0) { + + printf ("\nAFB: background mode [pid:%d console:%s]\n", getpid(),session->config->console); + if (verbose) printf ("AFB:info use '%s --restart --rootdir=%s # [--pidfile=%s] to restart daemon\n", programName,session->config->rootdir, session->config->pidfile); + + // redirect default I/O on console + close (2); status=dup(consoleFD); // redirect stderr + close (1); status=dup(consoleFD); // redirect stdout + close (0); // no need for stdin + close (consoleFD); + + setsid(); // allow father process to fully exit + sleep (2); // allow main to leave and release port + + fprintf (stderr, "----------------------------\n"); + fprintf (stderr, "%s INF:main background pid=%d\n", configTime(), getpid()); + fflush (stderr); + + // if everything look OK then look forever + syslog (LOG_ERR, "AFB: Entering infinite loop in background mode"); + + // should normally never return from this loop + listenLoop(session); + syslog (LOG_ERR, "AFB:FAIL background infinite loop exited check [%s]\n", session->config->console); + + goto exitInitLoop; + } + + // if fail nothing much to do + if (pid == -1) goto errorFork; + + // fork worked and we are in father process + status = writePidFile (session->config, pid); + if (status == -1) goto errorPidFile; + + // we are in father process, we don't need this one + exit (0); + + } // end background-foreground + +normalExit: + closeSession (session); // try to close everything before leaving + if (verbose) printf ("\n---- Application Framework Binder Normal End ------\n"); + exit (0); + +// ------------- Fatal ERROR display error and quit ------------- +errorPidFile: + fprintf (stderr,"\nERR:main Failled to write pid file [%s]\n\n", session->config->pidfile); + exit (-1); + +errorFork: + fprintf (stderr,"\nERR:main Failled to fork son process\n\n"); + exit (-1); + +needValueForOption: + fprintf (stderr,"\nERR:main option [--%s] need a value i.e. --%s=xxx\n\n" + ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name); + exit (-1); + +noValueForOption: + fprintf (stderr,"\nERR:main option [--%s] don't take value\n\n" + ,gnuOptions[optionIndex].name); + exit (-1); + +notAnInteger: + fprintf (stderr,"\nERR:main option [--%s] requirer an interger i.e. --%s=9\n\n" + ,gnuOptions[optionIndex].name, gnuOptions[optionIndex].name); + exit (-1); + +exitOnSignal: + fprintf (stderr,"\n%s INF:main pid=%d received exit signal (Hopefully crtl-C or --kill-previous !!!)\n\n" + ,configTime(), getpid()); + exit (-1); + +errConsole: + fprintf (stderr,"\nERR:cannot open /dev/console (use --foreground)\n\n"); + exit (-1); + +errSessiondir: + fprintf (stderr,"\nERR:cannot read/write session dir\n\n"); + exit (-1); + +errSoundCard: + fprintf (stderr,"\nERR:fail to probe sound cards\n\n"); + exit (-1); + +exitInitLoop: + // try to unlink pid file if any + if (session->background && session->config->pidfile != NULL) unlink (session->config->pidfile); + exit (-1); + +}; /* END main() */ + diff --git a/src/rest-api.c b/src/rest-api.c new file mode 100644 index 00000000..3b5c53e1 --- /dev/null +++ b/src/rest-api.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + * + * Contain all generic part to handle REST/API + */ + + +#include <microhttpd.h> +#include <sys/stat.h> +#include "../include/local-def.h" + +// proto missing from GCC +char *strcasestr(const char *haystack, const char *needle); + + +// Because of POST call multiple time requestApi we need to free POST handle here +STATIC void endRequest(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) { + AFB_HttpPost *posthandle = *con_cls; + + // if post handle was used let's free everything + if (posthandle) { + if (verbose) fprintf(stderr, "End Post Request UID=%d\n", posthandle->uid); + free(posthandle->data); + free(posthandle); + } +} + + +PUBLIC json_object* pingSample (AFB_plugin *plugin, AFB_session *session, AFB_request *post) { + static pingcount=0; + json_object *response; + response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon %d", pingcount++); + if (verbose) fprintf(stderr, "%d: \n", pingcount); + return (response); +} + +// Check of apiurl is declare in this plugin and call it +STATIC json_object * callPluginApi (AFB_plugin *plugin, AFB_session *session, AFB_request *request) { + json_object *response; + int idx; + + // If a plugin hold this urlpath call its callback + for (idx=0; plugin->apis[idx].callback != NULL; idx++) { + if (!strcmp (plugin->apis[idx].name, request->api)) { + response = plugin->apis[idx].callback (session, request); + if (response != NULL) { + json_object_object_add (response, "jtype" ,plugin->jtype); + } + return (response); + } + } + return (NULL); +} + + +// process rest API query +PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char *method, const char* url) { + + char *baseurl, *baseapi, *urlcpy; + json_object *jsonResponse, *errMessage; + struct MHD_Response *webResponse; + const char *serialized, parsedurl; + AFB_request request; + int idx, ret; + + // Extract plugin urlpath from request + urlcpy=strdup (url); + baseurl = strsep(&urlcpy, "/"); + if (baseurl == NULL) { + errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s", url); + goto ExitOnError; + } + + baseapi = strsep(&urlcpy, "/"); + if (baseapi == NULL) { + errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s/%s", baseurl, url); + goto ExitOnError; + } + + // build request structure + memset (&request, 0, sizeof (request)); + request.connection = connection; + request.url = url; + request.plugin = baseurl; + request.api = baseapi; + + // if post wait as data may come in multiple calls + if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) { + + request.post="TO Be DONE"; + } else { + request.post=NULL; + }; + + // Search for a plugin with this urlpath + for (idx=0; session->plugins[idx] != NULL; idx++) { + if (!strcmp (session->plugins[idx]->prefix, baseurl)) { + jsonResponse = callPluginApi (session->plugins[idx], session, &request ); + free (urlcpy); + break; + } + errMessage = jsonNewMessage(AFB_FATAL, "No Plugin for %s", baseurl); + free (urlcpy); + goto ExitOnError; + + } + + // plugin callback did not return a valid Json Object + if (jsonResponse == NULL) { + errMessage = jsonNewMessage(AFB_FATAL, "No Plugin/API for %s/%s", baseurl, baseapi); + goto ExitOnError; + } + + serialized = json_object_to_json_string(jsonResponse); + webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY); + + ret = MHD_queue_response(connection, MHD_HTTP_OK, webResponse); + MHD_destroy_response(webResponse); + json_object_put(jsonResponse); // decrease reference rqtcount to free the json object + return ret; + +ExitOnError: + serialized = json_object_to_json_string(errMessage); + webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY); + ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse); + MHD_destroy_response(webResponse); + json_object_put(errMessage); // decrease reference rqtcount to free the json object + return ret; +} + +// Helper to retreive argument from connection +PUBLIC const char* getQueryValue (AFB_request * request, char *name) { + const char *value; + + value=MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name); + return (value); +} + + +void *initPlugins (AFB_session *session) { + static AFB_plugin *plugins[10]; // no more than 10 plugins !!! + AFB_plugin *plugin; + int idx; + + // simulate dynamic library load for plugins + // need to implement mods argument to activate only requested mods + idx=0; + + // Minimal check before accepting a new plugin + plugin = afsvRegister (session); + if (plugin->type != AFB_PLUGIN) { + fprintf (stderr, "ERROR: AFSV plugin invalid type=%d!=%d\n", plugin->type, AFB_PLUGIN); + } else { + // Prepare Plugin name to be added to API response + plugin->jtype = json_object_new_string (plugin->prefix); + json_object_get (plugin->jtype); // increase reference count to make it permanent + plugins[idx++]= plugin; + } + + session->plugins= plugins; +}
\ No newline at end of file diff --git a/src/session.c b/src/session.c new file mode 100644 index 00000000..2bb5b442 --- /dev/null +++ b/src/session.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program 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 + * (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "local-def.h" +#include <dirent.h> +#include <string.h> +#include <time.h> +#include <sys/stat.h> +#include <sys/types.h> + +#define AFB_SESSION_JTYPE "AFB_session" +#define AFB_SESSION_JLIST "AFB_sessions" +#define AFB_SESSION_JINFO "AFB_infos" + +#define AFB_CURRENT_SESSION "active-session" // file link name within sndcard dir +#define AFB_DEFAULT_SESSION "current-session" // should be in sync with UI + + + + +// verify we can read/write in session dir +PUBLIC AFB_ERROR sessionCheckdir (AFB_session *session) { + + int err; + + // in case session dir would not exist create one + if (verbose) fprintf (stderr, "AFB:notice checking session dir [%s]\n", session->config->sessiondir); + mkdir(session->config->sessiondir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + // change for session directory + err = chdir(session->config->sessiondir); + if (err) { + fprintf(stderr,"AFB: Fail to chdir to %s error=%s\n", session->config->sessiondir, strerror(err)); + return err; + } + + // verify we can write session in directory + json_object *dummy= json_object_new_object(); + json_object_object_add (dummy, "checked" , json_object_new_int (getppid())); + err = json_object_to_file ("./AFB-probe.json", dummy); + if (err < 0) return err; + + return AFB_SUCCESS; +} + +// let's return only sessions files +STATIC int fileSelect (const struct dirent *entry) { + return (strstr (entry->d_name, ".afb") != NULL); +} + +STATIC json_object *checkCardDirExit (AFB_session *session, AFB_request *request ) { + int sessionDir, cardDir; + + // card name should be more than 3 character long !!!! + if (strlen (request->plugin) < 3) { + return (jsonNewMessage (AFB_FAIL,"Fail invalid plugin=%s", request->plugin)); + } + + // open session directory + sessionDir = open (session->config->sessiondir, O_DIRECTORY); + if (sessionDir < 0) { + return (jsonNewMessage (AFB_FAIL,"Fail to open directory [%s] error=%s", session->config->sessiondir, strerror(sessionDir))); + } + + // create session sndcard directory if it does not exit + cardDir = openat (sessionDir, request->plugin, O_DIRECTORY); + if (cardDir < 0) { + cardDir = mkdirat (sessionDir, request->plugin, O_RDWR | S_IRWXU | S_IRGRP); + if (cardDir < 0) { + return (jsonNewMessage (AFB_FAIL,"Fail to create directory [%s/%s] error=%s", session->config->sessiondir, request->plugin, strerror(cardDir))); + } + } + close (sessionDir); + return NULL; +} + +// create a session in current directory +PUBLIC json_object *sessionList (AFB_session *session, AFB_request *request) { + json_object *sessionsJ, *ajgResponse; + struct stat fstat; + struct dirent **namelist; + int count, sessionDir; + + // if directory for card's sessions does not exist create it + ajgResponse = checkCardDirExit (session, request); + if (ajgResponse != NULL) return ajgResponse; + + // open session directory + sessionDir = open (session->config->sessiondir, O_DIRECTORY); + if (sessionDir < 0) { + return (jsonNewMessage (AFB_FAIL,"Fail to open directory [%s] error=%s", session->config->sessiondir, strerror(sessionDir))); + } + + count = scandirat (sessionDir, request->plugin, &namelist, fileSelect, alphasort); + close (sessionDir); + + if (count < 0) { + return (jsonNewMessage (AFB_FAIL,"Fail to scan sessions directory [%s/%s] error=%s", session->config->sessiondir, request->plugin, strerror(sessionDir))); + } + if (count == 0) return (jsonNewMessage (AFB_EMPTY,"[%s] no session at [%s]", request->plugin, session->config->sessiondir)); + + // loop on each session file, retrieve its date and push it into json response object + sessionsJ = json_object_new_array(); + while (count--) { + json_object *sessioninfo; + char timestamp [64]; + char *filename; + + // extract file name and last modification date + filename = namelist[count]->d_name; + printf("%s\n", filename); + stat(filename,&fstat); + strftime (timestamp, sizeof(timestamp), "%c", localtime (&fstat.st_mtime)); + filename[strlen(filename)-4] = '\0'; // remove .afb extension from filename + + // create an object by session with last update date + sessioninfo = json_object_new_object(); + json_object_object_add (sessioninfo, "date" , json_object_new_string (timestamp)); + json_object_object_add (sessioninfo, "session" , json_object_new_string (filename)); + json_object_array_add (sessionsJ, sessioninfo); + + free(namelist[count]); + } + + // free scandir structure + free(namelist); + + // everything is OK let's build final response + ajgResponse = json_object_new_object(); + json_object_object_add (ajgResponse, "jtype" , json_object_new_string (AFB_SESSION_JLIST)); + json_object_object_add (ajgResponse, "status" , jsonNewStatus(AFB_SUCCESS)); + json_object_object_add (ajgResponse, "data" , sessionsJ); + + return (ajgResponse); +} + +// Create a link toward last used sessionname within sndcard directory +STATIC void makeSessionLink (const char *cardname, const char *sessionname) { + char linkname [256], filename [256]; + int err; + // create a link to keep track of last uploaded sessionname for this card + strncpy (filename, sessionname, sizeof(filename)); + strncat (filename, ".afb", sizeof(filename)); + + strncpy (linkname, cardname, sizeof(linkname)); + strncat (linkname, "/", sizeof(filename)); + strncat (linkname, AFB_CURRENT_SESSION, sizeof(linkname)); + strncat (linkname, ".afb", sizeof(filename)); + unlink (linkname); // remove previous link if any + err = symlink (filename, linkname); + if (err < 0) fprintf (stderr, "Fail to create link %s->%s error=%s\n", linkname, filename, strerror(errno)); +} + +// Load Json session object from disk +PUBLIC json_object *sessionFromDisk (AFB_session *session, AFB_request *request, char *name) { + json_object *jsonSession, *jtype, *response; + const char *ajglabel; + char filename [256]; + int defsession; + + if (name == NULL) { + return (jsonNewMessage (AFB_FATAL,"session name missing &session=MySessionName")); + } + + // check for current session request + defsession = (strcmp (name, AFB_DEFAULT_SESSION) ==0); + + // if directory for card's sessions does not exist create it + response = checkCardDirExit (session, request); + if (response != NULL) return response; + + // add name and file extension to session name + strncpy (filename, request->plugin, sizeof(filename)); + strncat (filename, "/", sizeof(filename)); + if (defsession) strncat (filename, AFB_CURRENT_SESSION, sizeof(filename)-1); + else strncat (filename, name, sizeof(filename)-1); + strncat (filename, ".afb", sizeof(filename)); + + // just upload json object and return without any further processing + jsonSession = json_object_from_file (filename); + + if (jsonSession == NULL) return (jsonNewMessage (AFB_EMPTY,"File [%s] not found", filename)); + + // verify that file is a JSON ALSA session type + if (!json_object_object_get_ex (jsonSession, "jtype", &jtype)) { + json_object_put (jsonSession); + return (jsonNewMessage (AFB_EMPTY,"File [%s] 'jtype' descriptor not found", filename)); + } + + // check type value is AFB_SESSION_JTYPE + ajglabel = json_object_get_string (jtype); + if (strcmp (AFB_SESSION_JTYPE, ajglabel)) { + json_object_put (jsonSession); + return (jsonNewMessage (AFB_FATAL,"File [%s] jtype=[%s] != [%s]", filename, ajglabel, AFB_SESSION_JTYPE)); + } + + // create a link to keep track of last uploaded session for this card + if (!defsession) makeSessionLink (request->plugin, name); + + return (jsonSession); +} + +// push Json session object to disk +PUBLIC json_object * sessionToDisk (AFB_session *session, AFB_request *request, char *name, json_object *jsonSession) { + char filename [256]; + time_t rawtime; + struct tm * timeinfo; + int err, defsession; + static json_object *response; + + // we should have a session name + if (name == NULL) return (jsonNewMessage (AFB_FATAL,"session name missing &session=MySessionName")); + + // check for current session request + defsession = (strcmp (name, AFB_DEFAULT_SESSION) ==0); + + // if directory for card's sessions does not exist create it + response = checkCardDirExit (session, request); + if (response != NULL) return response; + + // add cardname and file extension to session name + strncpy (filename, request->plugin, sizeof(filename)); + strncat (filename, "/", sizeof(filename)); + if (defsession) strncat (filename, AFB_CURRENT_SESSION, sizeof(filename)-1); + else strncat (filename, name, sizeof(filename)-1); + strncat (filename, ".afb", sizeof(filename)-1); + + + json_object_object_add(jsonSession, "jtype", json_object_new_string (AFB_SESSION_JTYPE)); + + // add a timestamp and store session on disk + time ( &rawtime ); timeinfo = localtime ( &rawtime ); + // A copy of the string is made and the memory is managed by the json_object + json_object_object_add (jsonSession, "timestamp", json_object_new_string (asctime (timeinfo))); + + + // do we have extra session info ? + if (request->post) { + static json_object *info, *jtype; + const char *ajglabel; + + // extract session info from args + info = json_tokener_parse (request->post); + if (!info) { + response = jsonNewMessage (AFB_FATAL,"sndcard=%s session=%s invalid json args=%s", request->plugin, name, request->post); + goto OnErrorExit; + } + + // info is a valid AFB_info type + if (!json_object_object_get_ex (info, "jtype", &jtype)) { + response = jsonNewMessage (AFB_EMPTY,"sndcard=%s session=%s No 'AFB_type' args=%s", request->plugin, name, request->post); + goto OnErrorExit; + } + + // check type value is AFB_INFO_JTYPE + ajglabel = json_object_get_string (jtype); + if (strcmp (AFB_SESSION_JINFO, ajglabel)) { + json_object_put (info); // release info json object + response = jsonNewMessage (AFB_FATAL,"File [%s] jtype=[%s] != [%s] data=%s", filename, ajglabel, AFB_SESSION_JTYPE, request->post); + goto OnErrorExit; + } + + // this is valid info data for our session + json_object_object_add (jsonSession, "info", info); + } + + // Finally save session on disk + err = json_object_to_file (filename, jsonSession); + if (err < 0) { + response = jsonNewMessage (AFB_FATAL,"Fail save session = [%s] to disk", filename); + goto OnErrorExit; + } + + + // create a link to keep track of last uploaded session for this card + if (!defsession) makeSessionLink (request->plugin, name); + + // we're donne let's return status message + response = jsonNewMessage (AFB_SUCCESS,"Session= [%s] saved on disk", filename); + json_object_put (jsonSession); + return (response); + +OnErrorExit: + json_object_put (jsonSession); + return response; +} |