sintonia/library/ecasound-2.7.2/libecasoundc/ecasoundc_sa.c

1333 lines
37 KiB
C

/**
* @file ecasoundc_sa.cpp Standalone C implementation of the
* ecasound control interface
*/
/* FIXME: add check for big sync-error -> ecasound probably
* died so better to give an error */
/* FIXME: add check for msgsize errors */
/** ------------------------------------------------------------------------
* ecasoundc.cpp: Standalone C implementation of the
* ecasound control interface
* Copyright (C) 2000-2006,2008,2009 Kai Vehmanen
* Copyright (C) 2003 Michael Ewe
* Copyright (C) 2001 Aymeric Jeanneau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* -------------------------------------------------------------------------
* History of major changes:
*
* 2009-02-08 Kai Vehmanen
* - Finally got rid of the fixed-size parsing buffers.
* - Added handling (or proper ignoring) of SIGPIPE signals.
* 2006-12-06 Kai Vehmanen
* - Fixed severe string termination bug in handling lists of
* strings.
* - Fixed mechanism for waiting on grandchild ecasound process to exit.
* 2003-12-24 Michael Ewe
* - Fixed signaling issues on FreeBSD. Modified to perform a
* double-fork to better decouple ECI stack and the ecasound
* engine process.
* 2002-10-04 Kai Vehmanen
* - Rewritten as a standalone implementation.
* 2001-06-04 Aymeric Jeanneau
* - Added reentrant versions of all public ECI functions.
* 2000-12-06 Kai Vehmanen
* - Initial version.
*
* -------------------------------------------------------------------------
*/
#include <assert.h>
#include <stdio.h> /* ANSI-C: printf(), ... */
#include <stdlib.h> /* ANSI-C: calloc(), free() */
#include <string.h> /* ANSI-C: strlen() */
#include <errno.h> /* ANSI-C: errno */
#include <stdbool.h>
#include <fcntl.h> /* POSIX: fcntl() */
#include <sys/poll.h> /* XPG4-UNIX: poll() */
#include <unistd.h> /* POSIX: pipe(), fork() */
#include <sys/stat.h> /* POSIX: stat() */
#include <sys/types.h> /* POSIX: fork() */
#include <sys/wait.h> /* POSIX: wait() */
#include <signal.h> /* POSIX: signal handling */
#include "ecasoundc.h"
/* ---------------------------------------------------------------------
* Options
*/
// #define ECI_ENABLE_DEBUG
/* ---------------------------------------------------------------------
* Definitions and constants
*/
#define ECI_PARSER_BUF_SIZE 65536
#define ECI_MAX_DYN_ALLOC_SIZE 16777216 /* assert if reached */
#define ECI_MAX_FLOAT_BUF_SIZE 32
#define ECI_MAX_RETURN_TYPE_SIZE 4
#define ECI_INI_STRING_SIZE 64
#define ECI_MAX_RESYNC_ATTEMPTS 9
#define ECI_MAX_LAST_COMMAND_SIZE 64
#define ECI_READ_TIMEOUT_MS 5000
#define ECI_READ_RETVAL_TIMEOUT_MS 30000
#define ECI_STATE_INIT 0
#define ECI_STATE_LOGLEVEL 1
#define ECI_STATE_MSGSIZE 2
#define ECI_STATE_COMMON_CR_1 3
#define ECI_STATE_COMMON_LF_1 4
#define ECI_STATE_RET_TYPE 5
#define ECI_STATE_COMMON_CONTENT 6
#define ECI_STATE_COMMON_CR_2 7
#define ECI_STATE_COMMON_LF_2 8
#define ECI_STATE_COMMON_CR_3 9
#define ECI_STATE_COMMON_LF_3 10
#define ECI_STATE_SEEK_TO_LF 11
#define ECI_STATE_MSG_GEN 0
#define ECI_STATE_MSG_RETURN 1
#define ECI_TOKEN_PHASE_NONE 0
#define ECI_TOKEN_PHASE_READING 1
#define ECI_TOKEN_PHASE_VALIDATE 2
#define ECI_RETURN_TYPE_LOGLEVEL 256
#ifdef ECI_ENABLE_DEBUG
#define ECI_DEBUG(x) fprintf(stderr,x)
#define ECI_DEBUG_1(x,y) fprintf(stderr,x,y)
#define ECI_DEBUG_2(x,y,z) fprintf(stderr,x,y,z)
#define ECI_DEBUG_3(x,y,z,t) fprintf(stderr,x,y,z,t)
#else
#define ECI_DEBUG(x) ((void) 0)
#define ECI_DEBUG_1(x,y) ((void) 0)
#define ECI_DEBUG_2(x,y,z) ((void) 0)
#define ECI_DEBUG_3(x,y,z,t) ((void) 0)
#endif
#define DBC_REQUIRE(expr) \
(expr) ? (void)(0) : (void)(fprintf(stderr, "Warning: DBC_REQUIRE failed - \"%s\", %s, %d.\n", #expr,__FILE__, __LINE__))
#define DBC_ENSURE(expr) \
(expr) ? (void)(0) : (void)(fprintf(stderr, "Warning: DBC_ENSURE failed - \"%s\", %s, %d.\n", #expr,__FILE__, __LINE__))
#define DBC_CHECK(expr) \
(expr) ? (void)(0) : (void)(fprintf(stderr, "Warning: DBC_CHECK failed - \"%s\", %s, %d.\n", #expr,__FILE__, __LINE__))
#define DBC_DECLARE(expr) expr
/* ---------------------------------------------------------------------
* Data structures
*/
struct eci_string_s {
char *d; /* buffer to string contents */
int slen; /* string length in octets, including terminating null */
int size; /* buffer length in octets, including terminating null */
};
typedef struct eci_string_s eci_string;
struct eci_los_list {
struct eci_los_list* prev_repp;
struct eci_los_list* next_repp;
eci_string data_repp;
};
struct eci_parser {
int state_rep;
int state_msg_rep;
double last_f_rep;
long int last_li_rep;
int last_i_rep;
int last_counter_rep;
char last_type_repp[ECI_MAX_RETURN_TYPE_SIZE];
struct eci_los_list* last_los_repp;
eci_string last_error_repp;
eci_string last_s_repp;
eci_string buffer_rep;
int msgsize_rep;
int loglevel_rep;
int token_phase_rep;
int buffer_current_rep;
bool sync_lost_rep;
};
struct eci_internal {
int pid_of_child_rep;
int pid_of_parent_rep;
int cmd_read_fd_rep;
int cmd_write_fd_rep;
char last_command_repp[ECI_MAX_LAST_COMMAND_SIZE];
int commands_counter_rep;
struct eci_parser* parser_repp;
char farg_buf_repp[ECI_MAX_FLOAT_BUF_SIZE];
char raw_buffer_repp[ECI_PARSER_BUF_SIZE];
};
/* ---------------------------------------------------------------------
* Global variables
*/
static eci_handle_t static_eci_rep = 0;
/**
* Message shown if ECASOUND is not defined.
*/
const char* eci_str_no_ecasound_env =
"\n"
"***********************************************************************\n"
"* Message from libecasoundc:\n"
"* \n"
"* 'ECASOUND' environment variable not set. Using the default value \n"
"* value 'ECASOUND=ecasound'.\n"
"***********************************************************************\n"
"\n";
const char* eci_str_null_handle =
"\n"
"***********************************************************************\n"
"* Message from libecasoundc:\n"
"* \n"
"* A null client handle detected. This is usually caused by a bug \n"
"* in the ECI application. Please report this bug to the author of\n"
"* the program.\n"
"***********************************************************************\n"
"\n";
const char* eci_str_sync_lost =
"\n"
"***********************************************************************\n"
"* Message from libecasoundc:\n"
"* \n"
"* Connection to the processing engine was lost. Check that ecasound \n"
"* is correctly installed. Also make sure that ecasound is either \n"
"* in some directory listed in PATH, or the environment variable\n"
"* 'ECASOUND' contains the path to a working ecasound executable.\n"
"***********************************************************************\n"
"\n";
/* ---------------------------------------------------------------------
* Declarations of static functions
*/
static void eci_impl_check_handle(struct eci_internal* eci_rep);
static void eci_impl_free_parser(struct eci_internal* eci_rep);
static void eci_impl_clean_last_values(struct eci_parser* parser);
static void eci_impl_dump_parser_state(eci_handle_t ptr, const char* message);
static ssize_t eci_impl_fd_read(int fd, void *buf, size_t count, int timeout);
static const char* eci_impl_get_ecasound_path(void);
static struct eci_los_list *eci_impl_los_list_add_item(struct eci_los_list* headptr, char* stmp, int len);
static struct eci_los_list *eci_impl_los_list_alloc_item(void);
static void eci_impl_los_list_clear(struct eci_los_list *ptr);
static void eci_impl_read_return_value(struct eci_internal* eci_rep, int timeout);
static void eci_impl_set_last_los_value(struct eci_parser* parser);
static void eci_impl_set_last_values(struct eci_parser* parser);
static void eci_impl_update_state(struct eci_parser* eci_rep, char c);
/* ---------------------------------------------------------------------
* Constructing and destructing
*/
/**
* Initializes session. This call clears all status info and
* prepares ecasound for processing. Can be used to "restart"
* the library.
*/
void eci_init(void)
{
DBC_CHECK(static_eci_rep == NULL);
static_eci_rep = eci_init_r();
}
/**
* Initializes session. This call creates a new ecasound
* instance and prepares it for processing.
*
* @return NULL if initialization fails
*/
eci_handle_t eci_init_r(void)
{
struct eci_internal* eci_rep = NULL;
int cmd_send_pipe[2], cmd_receive_pipe[2];
const char* ecasound_exec = eci_impl_get_ecasound_path();
/* step: launch ecasound process and setup two-way communication */
if (ecasound_exec != NULL &&
(pipe(cmd_receive_pipe) == 0 && pipe(cmd_send_pipe) == 0)) {
int fork_pid = fork();
/* step: 1st fork */
if (fork_pid == 0) {
/* first child (phase-1) */
/* -c = interactive mode, -D = direct prompts and banners to stderr */
const char* args[4] = { NULL, "-c", "-D", NULL };
int res = 0;
struct sigaction sa;
pid_t pid;
sa.sa_handler=SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags=0;
sigaction(SIGHUP, &sa, NULL);
setsid();
/* step: 2nd fork (to detach from parent) */
if (fork() != 0)
_exit(0); /* first child terminates here (phase-2) */
/* second child continues (phase-2) */
args[0] = ecasound_exec;
/* close all unused descriptors and resources */
close(0);
close(1);
dup2(cmd_send_pipe[0], 0);
dup2(cmd_receive_pipe[1], 1);
close(cmd_receive_pipe[0]);
close(cmd_receive_pipe[1]);
close(cmd_send_pipe[0]);
close(cmd_send_pipe[1]);
freopen("/dev/null", "w", stderr);
/* step: write second child's pid to the (grand) parent */
pid = getpid();
write(1, &pid, sizeof(pid));
/* step: notify the parent that we're up */
res = write(1, args, 1);
res = execvp(args[0], (char**)args);
if (res < 0) printf("(ecasoundc_sa) launching ecasound FAILED!\n");
close(0);
close(1);
_exit(res);
ECI_DEBUG("(ecasoundc_sa) You shouldn't see this!\n");
}
else {
/* step: parent (phase-1) */
int res;
char buf[1];
int status;
int pid;
/* set up signal handling */
struct sigaction ign_handler;
ign_handler.sa_handler = SIG_IGN;
sigemptyset(&ign_handler.sa_mask);
ign_handler.sa_flags = 0;
/* ignore the following signals */
sigaction(SIGPIPE, &ign_handler, 0);
sigaction(SIGFPE, &ign_handler, 0);
eci_rep = (struct eci_internal*)calloc(1, sizeof(struct eci_internal));
eci_rep->parser_repp = (struct eci_parser*)calloc(1, sizeof(struct eci_parser));
/* step: initialize variables */
eci_rep->pid_of_child_rep = fork_pid;
eci_rep->commands_counter_rep = 0;
eci_rep->parser_repp->last_counter_rep = 0;
eci_rep->parser_repp->token_phase_rep = ECI_TOKEN_PHASE_NONE;
eci_rep->parser_repp->buffer_current_rep = 0;
eci_rep->parser_repp->sync_lost_rep = false;
eci_impl_clean_last_values(eci_rep->parser_repp);
/*
waits for first child to prevent the zombie
read grand child prozess id from pipe
*/
waitpid(eci_rep->pid_of_child_rep, &status, 0);
res = read(cmd_receive_pipe[0], &pid, sizeof(pid));
if ( res != sizeof(pid) ) {
ECI_DEBUG_1("(ecasoundc_sa) fork() of %s FAILED!\n", ecasound_exec);
eci_impl_free_parser(eci_rep);
free(eci_rep);
eci_rep = NULL;
}
eci_rep->pid_of_child_rep = pid;
eci_rep->pid_of_parent_rep = getpid();
eci_rep->cmd_read_fd_rep = cmd_receive_pipe[0];
close(cmd_receive_pipe[1]);
eci_rep->cmd_write_fd_rep = cmd_send_pipe[1];
close(cmd_send_pipe[0]);
/* step: switch to non-blocking mode for read */
fcntl(eci_rep->cmd_read_fd_rep, F_SETFL, O_NONBLOCK);
fcntl(eci_rep->cmd_write_fd_rep, F_SETFL, O_NONBLOCK);
/* step: check that fork succeeded() */
res = eci_impl_fd_read(eci_rep->cmd_read_fd_rep, buf, 1, ECI_READ_TIMEOUT_MS);
if (res != 1) {
ECI_DEBUG_1("(ecasoundc_sa) fork() of %s FAILED!\n", ecasound_exec);
eci_impl_free_parser(eci_rep);
free(eci_rep);
eci_rep = NULL;
}
else {
write(eci_rep->cmd_write_fd_rep, "debug 256\n", strlen("debug 256\n"));
write(eci_rep->cmd_write_fd_rep, "int-set-float-to-string-precision 17\n", strlen("int-set-float-to-string-precision 17\n"));
write(eci_rep->cmd_write_fd_rep, "int-output-mode-wellformed\n", strlen("int-output-mode-wellformed\n"));
eci_rep->commands_counter_rep ++;
/* step: check that exec() succeeded */
eci_impl_read_return_value(eci_rep, ECI_READ_TIMEOUT_MS);
if (eci_rep->commands_counter_rep != eci_rep->parser_repp->last_counter_rep) {
ECI_DEBUG_3("(ecasoundc_sa) exec() of %s FAILED (%d=%d)!\n", ecasound_exec, eci_rep->commands_counter_rep, eci_rep->parser_repp->last_counter_rep);
eci_impl_free_parser(eci_rep);
free(eci_rep);
eci_rep = NULL;
}
}
}
}
return (eci_handle_t)eci_rep;
}
/**
* Checks whether ECI is ready for use.
*
* @return non-zero if ready, zero otherwise
*/
int eci_ready(void)
{
return eci_ready_r(static_eci_rep);
}
/**
* Checks whether ECI is ready for use.
*
* @return non-zero if ready, zero otherwise
*/
int eci_ready_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
if (!ptr)
return 0;
if (eci_rep->pid_of_child_rep <= 0 ||
eci_rep->cmd_read_fd_rep < 0 ||
eci_rep->cmd_write_fd_rep < 0)
return 0;
return 1;
}
/**
* Frees all resources.
*/
void eci_cleanup(void)
{
if (static_eci_rep != NULL) {
eci_cleanup_r(static_eci_rep);
static_eci_rep = NULL;
}
}
/**
* Frees all resources.
*/
void eci_cleanup_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
ssize_t resread = 1, respoll;
char buf[1];
struct pollfd fds[1];
eci_impl_check_handle(eci_rep);
ECI_DEBUG("\n(ecasoundc_sa) requesting to terminatte ecasound process.\n");
write(eci_rep->cmd_write_fd_rep, "quit\n", strlen("quit\n"));
eci_rep->commands_counter_rep++;
/* as we use double-fork, we cannot use waitpid() --
* to block until ecasound grandchild has exited, we
* use a combination of poll+read(),
* ref: http://www.greenend.org.uk/rjk/2001/06/poll.html
*/
ECI_DEBUG_1("\n(ecasoundc_sa) cleaning up. waiting for grandchild ecasound process %d.\n", eci_rep->pid_of_child_rep);
while (resread > 0) {
fds[0].fd = eci_rep->cmd_read_fd_rep;
fds[0].events = POLLIN;
fds[0].revents = 0;
respoll = poll(fds, 1, ECI_READ_RETVAL_TIMEOUT_MS);
if (fds[0].revents & (POLLIN | POLLHUP))
resread = read(eci_rep->cmd_read_fd_rep, buf, 1);
else if (fds[0].revents & POLLERR)
resread = -2;
ECI_DEBUG_3("(ecasoundc_sa) waiting for ecasound, poll=%d, read=%d, revents=0x%02x)\n", respoll, resread, fds[0].revents);
}
ECI_DEBUG("(ecasoundc_sa) child exited\n");
if (eci_rep != 0) {
/* close descriptors */
close(eci_rep->cmd_read_fd_rep);
close(eci_rep->cmd_write_fd_rep);
/* free lists of strings, if any */
eci_impl_clean_last_values(eci_rep->parser_repp);
eci_impl_free_parser(eci_rep);
free(eci_rep);
}
}
/* ---------------------------------------------------------------------
* Issuing EIAM commands
*/
/**
* Sends a command to the ecasound engine. See ecasound-iam(5) for
* more info.
*/
void eci_command(const char* command) { eci_command_r(static_eci_rep, command); }
/**
* Sends a command to the ecasound engine. See ecasound-iam(5) for
* more info.
*/
void eci_command_r(eci_handle_t ptr, const char* command)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
int timeout = ECI_READ_RETVAL_TIMEOUT_MS;
eci_impl_check_handle(eci_rep);
if (eci_ready_r(ptr) == 0) {
ECI_DEBUG("(ecasoundc_sa) not ready, unable to process commands\n");
return;
}
ECI_DEBUG_2("(ecasoundc_sa) writing command '%s' (cmd-counter=%d).\n",
command, eci_rep->commands_counter_rep + 1);
memcpy(eci_rep->last_command_repp, command, ECI_MAX_LAST_COMMAND_SIZE);
eci_impl_clean_last_values(eci_rep->parser_repp);
write(eci_rep->cmd_write_fd_rep, command, strlen(command));
write(eci_rep->cmd_write_fd_rep, "\n", 1);
/* 'run' is the only blocking function */
if (strncmp(command, "run", 3) == 0) {
ECI_DEBUG("(ecasoundc_sa) 'run' detected; disabling reply timeout!\n");
timeout = -1;
}
eci_rep->commands_counter_rep++;
if (eci_rep->commands_counter_rep - 1 !=
eci_rep->parser_repp->last_counter_rep) {
eci_impl_dump_parser_state(ptr, "sync error");
eci_rep->parser_repp->sync_lost_rep = true;
}
if (eci_rep->commands_counter_rep >=
eci_rep->parser_repp->last_counter_rep) {
eci_impl_read_return_value(eci_rep, timeout);
}
ECI_DEBUG_2("(ecasoundc_sa) set return value type='%s' (read-counter=%d).\n",
eci_rep->parser_repp->last_type_repp, eci_rep->parser_repp->last_counter_rep);
if (eci_rep->commands_counter_rep >
eci_rep->parser_repp->last_counter_rep) {
fprintf(stderr, "%s", eci_str_sync_lost);
eci_rep->parser_repp->sync_lost_rep = true;
}
}
/**
* A specialized version of 'eci_command()' taking a double value
* as the 2nd argument.
*/
void eci_command_float_arg(const char* command, double arg) { eci_command_float_arg_r(static_eci_rep, command, arg); }
/**
* A specialized version of 'eci_command()' taking a double value
* as the 2nd argument.
*/
void eci_command_float_arg_r(eci_handle_t ptr, const char* command, double arg)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
eci_impl_check_handle(eci_rep);
snprintf(eci_rep->farg_buf_repp, ECI_MAX_FLOAT_BUF_SIZE-1, "%s %.32f", command, arg);
eci_command_r(ptr, eci_rep->farg_buf_repp);
}
/* ---------------------------------------------------------------------
* Getting return values
*/
/**
* Returns the number of strings returned by the
* last ECI command.
*/
int eci_last_string_list_count(void) { return(eci_last_string_list_count_r(static_eci_rep)); }
/**
* Returns the number of strings returned by the
* last ECI command.
*/
int eci_last_string_list_count_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
struct eci_los_list* i;
int count = 0;
eci_impl_check_handle(eci_rep);
for(i = eci_rep->parser_repp->last_los_repp;
i != NULL;
i = i->next_repp) {
++count;
}
return count;
}
/**
* Returns the nth item of the list containing
* strings returned by the last ECI command.
*
* require:
* n >= 0 && n < eci_last_string_list_count()
*/
const char* eci_last_string_list_item(int n) { return(eci_last_string_list_item_r(static_eci_rep, n)); }
/**
* Returns the nth item of the list containing
* strings returned by the last ECI command.
*
* require:
* n >= 0 && n < eci_last_string_list_count()
*/
const char* eci_last_string_list_item_r(eci_handle_t ptr, int n)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
struct eci_los_list* i;
int count = 0;
eci_impl_check_handle(eci_rep);
for(i = eci_rep->parser_repp->last_los_repp;
i != NULL;
i = i->next_repp) {
if (count++ == n) {
return i->data_repp.d;
}
}
return NULL;
}
const char* eci_last_string(void) { return(eci_last_string_r(static_eci_rep)); }
const char* eci_last_string_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
eci_impl_check_handle(eci_rep);
return eci_rep->parser_repp->last_s_repp.d;
}
double eci_last_float(void) { return(eci_last_float_r(static_eci_rep)); }
double eci_last_float_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
eci_impl_check_handle(eci_rep);
return eci_rep->parser_repp->last_f_rep;
}
int eci_last_integer(void) { return(eci_last_integer_r(static_eci_rep)); }
int eci_last_integer_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
eci_impl_check_handle(eci_rep);
return eci_rep->parser_repp->last_i_rep;
}
long int eci_last_long_integer(void) { return(eci_last_long_integer_r(static_eci_rep)); }
long int eci_last_long_integer_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
eci_impl_check_handle(eci_rep);
return eci_rep->parser_repp->last_li_rep;
}
/**
* Returns pointer to a null-terminated string containing
* information about the last occured error.
*/
const char* eci_last_error(void) { return(eci_last_error_r(static_eci_rep)); }
/**
* Returns pointer to a null-terminated string containing
* information about the last occured error.
*/
const char* eci_last_error_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
eci_impl_check_handle(eci_rep);
return eci_rep->parser_repp->last_error_repp.d;
}
const char* eci_last_type(void) { return(eci_last_type_r(static_eci_rep)); }
const char* eci_last_type_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
eci_impl_check_handle(eci_rep);
return eci_rep->parser_repp->last_type_repp;
}
/**
* Whether an error has occured?
*
* @return zero if not in error state
*/
int eci_error(void) { return(eci_error_r(static_eci_rep)); }
/**
* Whether an error has occured?
*
* @return zero if not in error state
*/
int eci_error_r(eci_handle_t ptr)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
int res;
eci_impl_check_handle(eci_rep);
if (eci_ready_r(ptr) == 0) {
ECI_DEBUG("(ecasoundc_sa) not ready, raising an error\n");
return 1;
}
if (eci_rep->parser_repp->sync_lost_rep == true) {
ECI_DEBUG("(ecasoundc_sa) sync lost, raising an error\n");
return 1;
}
res = (eci_rep->parser_repp->last_type_repp[0] == 'e') ? 1 : 0;
ECI_DEBUG_1("(ecasoundc_sa) checking for error, returning %d", res);
return res;
}
/* ---------------------------------------------------------------------
* Events
*/
int eci_events_available(void) { return(eci_events_available_r(static_eci_rep)); }
int eci_events_available_r(eci_handle_t ptr) { return(0); }
void eci_next_event(void) { eci_next_event_r(static_eci_rep); }
void eci_next_event_r(eci_handle_t ptr) { }
const char* eci_current_event(void) { return(eci_current_event_r(static_eci_rep)); }
const char* eci_current_event_r(eci_handle_t ptr) { return(0); }
/* ---------------------------------------------------------------------
* Implementation of static functions
*/
static void eci_string_add(eci_string *dst, int at, char const *src, int len);
/**
* Clears the string contents.
*
* @post eci_string_len(str)==0
* @post strlen(str->d)==0
*/
static void eci_string_clear(eci_string *str)
{
DBC_CHECK(str);
str->slen = 0;
if (str->size == 0)
eci_string_add(str, 0, NULL, 0);
else
str->d[0] = 0;
DBC_CHECK(str->d[0] == 0);
}
/**
* Initializes the string object for use.
* This must only be called after initial
* object allocation.
*/
static void eci_string_init(eci_string *str)
{
DBC_CHECK(str);
str->slen = 0;
str->size = 0;
str->d = 0;
}
static void eci_string_free(eci_string *str)
{
DBC_CHECK(str);
free(str->d);
str->size = 0;
str->slen = 0;
}
/**
* Returns the string length.
*/
static int eci_string_len(eci_string *str)
{
DBC_CHECK(str);
return str->slen;
}
/**
* Adds 'len' octets from buffer 'src' to the string
* at position 'at' (position 0 being the first character).
*/
static void eci_string_add(eci_string *dst, int at, char const *src, int len)
{
int space_needed = at + len + 1;
DBC_CHECK(dst);
if (space_needed > dst->size) {
int newsize =
dst->size ? dst->size * 2 : ECI_INI_STRING_SIZE;
char *newbuf;
while (space_needed > newsize) {
newsize *= 2;
}
assert(newsize <= ECI_MAX_DYN_ALLOC_SIZE);
newbuf = realloc(dst->d, newsize);
assert(newbuf);
dst->size = newsize;
dst->d = newbuf;
}
DBC_CHECK(space_needed <= dst->size);
memcpy(&dst->d[at], src, len);
dst->d[at + len] = 0;
}
static void eci_impl_check_handle(struct eci_internal* eci_rep)
{
if (eci_rep == NULL) {
fprintf(stderr, "%s", eci_str_null_handle);
DBC_CHECK(eci_rep != NULL);
exit(-1);
}
}
static void eci_impl_free_parser(struct eci_internal* eci_rep)
{
DBC_CHECK(eci_rep);
eci_string_free(&eci_rep->parser_repp->last_error_repp);
eci_string_free(&eci_rep->parser_repp->last_s_repp);
eci_string_free(&eci_rep->parser_repp->buffer_rep);
free(eci_rep->parser_repp);
eci_rep->parser_repp = 0;
}
static void eci_impl_clean_last_values(struct eci_parser* parser)
{
DBC_CHECK(parser != 0);
eci_impl_los_list_clear(parser->last_los_repp);
parser->last_los_repp = NULL;
parser->last_i_rep = 0;
parser->last_li_rep = 0;
parser->last_f_rep = 0.0f;
eci_string_clear(&parser->last_error_repp);
eci_string_clear(&parser->last_s_repp);
}
static void eci_impl_dump_parser_state(eci_handle_t ptr, const char* message)
{
struct eci_internal* eci_rep = (struct eci_internal*)ptr;
fprintf(stderr, "\n(ecasoundc_sa) Error='%s', cmd='%s' last_error='%s' cmd_cnt=%d last_cnt=%d.\n",
message,
eci_rep->last_command_repp,
eci_last_error_r(ptr),
eci_rep->commands_counter_rep,
eci_rep->parser_repp->last_counter_rep);
}
/**
* Attempts to read up to 'count' bytes from file descriptor 'fd'
* into the buffer starting at 'buf'. If no data is available
* for reading, up to 'timeout' milliseconds will be waited.
* A negative value means infinite timeout.
*/
static ssize_t eci_impl_fd_read(int fd, void *buf, size_t count, int timeout)
{
int nfds = 1;
struct pollfd ufds;
ssize_t rescount = 0;
int ret;
ufds.fd = fd;
ufds.events = POLLIN | POLLPRI;
ufds.revents = 0;
ret = poll(&ufds, nfds, timeout);
if (ret > 0) {
if (ufds.revents & POLLIN ||
ufds.revents & POLLPRI) {
rescount = read(fd, buf, count);
}
}
else if (ret == 0) {
/* timeout */
rescount = -1;
}
return rescount;
}
static const char* eci_impl_get_ecasound_path(void)
{
const char* result = getenv("ECASOUND");
if (result == NULL) {
fprintf(stderr, "%s", eci_str_no_ecasound_env);
result = "ecasound";
}
return result;
}
static struct eci_los_list *eci_impl_los_list_add_item(struct eci_los_list* head, char* stmp, int len)
{
struct eci_los_list* i = head;
struct eci_los_list* last = NULL;
/* find end of list */
while(i != NULL) {
last = i;
i = i->next_repp;
}
/* add to the end, copy data */
i = eci_impl_los_list_alloc_item();
eci_string_add(&i->data_repp, 0, stmp, len);
if (last != NULL) last->next_repp = i;
/* ECI_DEBUG_3("(ecasoundc_sa) adding item '%s' to los list; head=%p, i=%p\n", stmp, (void*)head, (void*)i); */
/* created a new list, return the new item */
if (head == NULL)
return i;
/* return the old head */
return head;
}
struct eci_los_list *eci_impl_los_list_alloc_item(void)
{
struct eci_los_list *item;
/* ECI_DEBUG("(ecasoundc_sa) list alloc item\n"); */
item = (struct eci_los_list*)calloc(1, sizeof(struct eci_los_list));
DBC_CHECK(item != NULL);
item->next_repp = item->prev_repp = NULL;
eci_string_clear(&item->data_repp);
return item;
}
static void eci_impl_los_list_clear(struct eci_los_list *ptr)
{
struct eci_los_list *i = ptr;
ECI_DEBUG_1("(ecasoundc_sa) clearing list, i=%p\n", (void*)i);
while(i != NULL) {
/* ECI_DEBUG_1("(ecasoundc_sa) freeing list item %p\n", (void*)i); */
struct eci_los_list* next = i->next_repp;
eci_string_free(&i->data_repp);
free(i);
i = next;
}
}
static void eci_impl_read_return_value(struct eci_internal* eci_rep, int timeout)
{
char* raw_buffer = eci_rep->raw_buffer_repp;
int attempts = 0;
DBC_CHECK(eci_rep->commands_counter_rep >=
eci_rep->parser_repp->last_counter_rep);
while(attempts < ECI_MAX_RESYNC_ATTEMPTS) {
int res = eci_impl_fd_read(eci_rep->cmd_read_fd_rep, raw_buffer, ECI_PARSER_BUF_SIZE-1, timeout);
if (res > 0) {
int n;
raw_buffer[res] = 0;
/* ECI_DEBUG_2("\n(ecasoundc_sa) read %u bytes:\n--cut--\n%s\n--cut--\n", res, raw_buffer); */
for(n = 0; n < res; n++) {
/* int old = eci_rep->parser_repp->state_rep; */
eci_impl_update_state(eci_rep->parser_repp, raw_buffer[n]);
/* if (old != eci_rep->parser_repp->state_rep) ECI_DEBUG_3("state change %d-%d, c=[%02X].\n", old, eci_rep->parser_repp->state_rep, raw_buffer[n]); */
}
if (eci_rep->commands_counter_rep ==
eci_rep->parser_repp->last_counter_rep) break;
/* read return values until the correct one is found */
}
else {
if (res < 0) {
ECI_DEBUG_1("(ecasoundc_sa) timeout when reading return values (attempts=%d)!\n", attempts);
eci_rep->parser_repp->sync_lost_rep = true;
break;
}
}
++attempts;
}
if (eci_rep->commands_counter_rep !=
eci_rep->parser_repp->last_counter_rep) {
eci_impl_dump_parser_state(eci_rep, "read() error");
eci_rep->parser_repp->sync_lost_rep = true;
}
}
/**
* Sets the last 'list of strings' values.
*
* @pre parser != 0
* @pre parser->state_rep == ECI_STATE_COMMON_LF_3
*/
static void eci_impl_set_last_los_value(struct eci_parser* parser)
{
struct eci_los_list* i = parser->last_los_repp;
int quoteflag = 0, m = 0, n;
eci_string stmp;
eci_string_init(&stmp);
DBC_CHECK(parser != 0);
DBC_CHECK(parser->state_rep == ECI_STATE_COMMON_LF_3);
ECI_DEBUG_2("(ecasoundc_sa) parsing a list '%s' (count=%d)\n", parser->buffer_rep.d, parser->buffer_current_rep);
eci_impl_los_list_clear(i);
parser->last_los_repp = NULL;
for(n = 0; n < parser->buffer_current_rep && n < parser->msgsize_rep; n++) {
char c = parser->buffer_rep.d[n];
if (c == '\"') {
quoteflag = !quoteflag;
}
else if (c == '\\') {
n++;
eci_string_add(&stmp, m++, &parser->buffer_rep.d[n], 1);
}
else if (c != ',' || quoteflag == 1) {
eci_string_add(&stmp, m++, &parser->buffer_rep.d[n], 1);
}
else {
if (m == 0) continue;
i = eci_impl_los_list_add_item(i, stmp.d, m);
m = 0;
}
}
if (m > 0) {
i = eci_impl_los_list_add_item(i, stmp.d, m);
}
parser->last_los_repp = i;
eci_string_free(&stmp);
}
/**
* Sets the 'last value' fields in the given 'parser'
* object.
*
* @pre parser != 0
* @pre parser->state_rep == ECI_STATE_COMMON_LF_3
*/
static void eci_impl_set_last_values(struct eci_parser* parser)
{
DBC_CHECK(parser != 0);
DBC_CHECK(parser->state_rep == ECI_STATE_COMMON_LF_3);
switch(parser->last_type_repp[0])
{
case 's':
eci_string_add(&parser->last_s_repp, 0, parser->buffer_rep.d, parser->buffer_current_rep);
break;
case 'S':
eci_impl_set_last_los_value(parser);
break;
case 'i':
parser->last_i_rep = atoi(parser->buffer_rep.d);
break;
case 'l':
parser->last_li_rep = atol(parser->buffer_rep.d);
break;
case 'f':
parser->last_f_rep = atof(parser->buffer_rep.d);
break;
case 'e':
eci_string_add(&parser->last_error_repp, 0, parser->buffer_rep.d, parser->buffer_current_rep);
break;
default: {}
}
}
static void eci_impl_update_state(struct eci_parser* parser, char c)
{
switch(parser->state_rep)
{
case ECI_STATE_INIT:
if (c >= 0x30 && c <= 0x39) {
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
parser->buffer_current_rep = 0;
eci_string_clear(&parser->buffer_rep);
parser->state_rep = ECI_STATE_LOGLEVEL;
}
else {
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
}
break;
case ECI_STATE_LOGLEVEL:
if (c == ' ') {
parser->loglevel_rep = atoi(parser->buffer_rep.d);
if (parser->loglevel_rep == ECI_RETURN_TYPE_LOGLEVEL) {
/* ECI_DEBUG_3("\n(ecasoundc_sa) found rettype loglevel '%s' (i=%d,len=%d).\n", parser->buffer_repp, parser->loglevel_rep, parser->buffer_current_rep); */
parser->state_msg_rep = ECI_STATE_MSG_RETURN;
}
else {
/* ECI_DEBUG_3("\n(ecasoundc_sa) found loglevel '%s' (i=%d,parser->buffer_current_rep=%d).\n", buf, parser->loglevel_rep, parser->buffer_current_rep); */
parser->state_msg_rep = ECI_STATE_MSG_GEN;
}
parser->state_rep = ECI_STATE_MSGSIZE;
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
}
else if (c < 0x30 && c > 0x39) {
parser->state_rep = ECI_STATE_SEEK_TO_LF;
}
break;
case ECI_STATE_MSGSIZE:
if ((c == ' ' && parser->state_msg_rep == ECI_STATE_MSG_RETURN) ||
(c == 0x0d && parser->state_msg_rep == ECI_STATE_MSG_GEN)) {
parser->msgsize_rep = atoi(parser->buffer_rep.d);
/* ECI_DEBUG_3("(ecasoundc_sa) found msgsize '%s' (i=%d,len=%d).\n", parser->buffer_repp, parser->msgsize_rep, parser->buffer_current_rep); */
if (parser->state_msg_rep == ECI_STATE_MSG_GEN) {
parser->state_rep = ECI_STATE_COMMON_LF_1;
}
else {
parser->state_rep = ECI_STATE_RET_TYPE;
}
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
}
else if (c < 0x30 && c > 0x39) {
parser->state_rep = ECI_STATE_SEEK_TO_LF;
}
else if (parser->token_phase_rep == ECI_TOKEN_PHASE_NONE) {
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
parser->buffer_current_rep = 0;
eci_string_clear(&parser->buffer_rep);
}
break;
case ECI_STATE_COMMON_CR_1:
if (c == 0x0d)
parser->state_rep = ECI_STATE_COMMON_LF_1;
else
parser->state_rep = ECI_STATE_INIT;
break;
case ECI_STATE_COMMON_LF_1:
if (c == 0x0a) {
parser->state_rep = ECI_STATE_COMMON_CONTENT;
}
else
parser->state_rep = ECI_STATE_INIT;
break;
case ECI_STATE_RET_TYPE:
if (c == 0x0d) {
/* parse return type */
/* set 'parser->last_type_repp' */
int len = (parser->buffer_current_rep < ECI_MAX_RETURN_TYPE_SIZE) ? parser->buffer_current_rep : (ECI_MAX_RETURN_TYPE_SIZE - 1);
memcpy(parser->last_type_repp, parser->buffer_rep.d, len);
parser->last_type_repp[len] = 0;
ECI_DEBUG_2("(ecasoundc_sa) found rettype '%s' (len=%d)\n", parser->last_type_repp, parser->buffer_current_rep);
parser->state_rep = ECI_STATE_COMMON_LF_1;
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
}
else if (parser->token_phase_rep == ECI_TOKEN_PHASE_NONE) {
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
parser->buffer_current_rep = 0;
eci_string_clear(&parser->buffer_rep);
}
break;
case ECI_STATE_COMMON_CONTENT:
if (c == 0x0d) {
/* parse return type */
/* set 'parser->last_xxx_yyy' */
/* handle empty content */
if (parser->msgsize_rep == 0)
eci_string_clear(&parser->buffer_rep);
ECI_DEBUG_2("(ecasoundc_sa) found content, loglevel=%d, msgsize=%d", parser->loglevel_rep, parser->msgsize_rep);
if (parser->state_msg_rep == ECI_STATE_MSG_GEN)
ECI_DEBUG(".\n");
else
ECI_DEBUG_1(" type='%s'.\n", parser->last_type_repp);
parser->state_rep = ECI_STATE_COMMON_LF_2;
parser->token_phase_rep = ECI_TOKEN_PHASE_VALIDATE;
}
else if (parser->token_phase_rep == ECI_TOKEN_PHASE_NONE) {
parser->token_phase_rep = ECI_TOKEN_PHASE_READING;
parser->buffer_current_rep = 0;
eci_string_clear(&parser->buffer_rep);
}
break;
case ECI_STATE_COMMON_CR_2:
if (c == 0x0d)
parser->state_rep = ECI_STATE_COMMON_LF_2;
else
parser->state_rep = ECI_STATE_COMMON_CONTENT;
break;
case ECI_STATE_COMMON_LF_2:
if (c == 0x0a)
parser->state_rep = ECI_STATE_COMMON_CR_3;
else
parser->state_rep = ECI_STATE_COMMON_CONTENT;
break;
case ECI_STATE_COMMON_CR_3:
if (c == 0x0d)
parser->state_rep = ECI_STATE_COMMON_LF_3;
else
parser->state_rep = ECI_STATE_COMMON_CONTENT;
break;
case ECI_STATE_COMMON_LF_3:
if (c == 0x0a) {
if (parser->state_msg_rep == ECI_STATE_MSG_RETURN) {
ECI_DEBUG_1("(ecasoundc_sa) rettype-content validated: <<<%s>>>\n", parser->buffer_rep.d);
eci_impl_set_last_values(parser);
parser->last_counter_rep++;
}
else {
ECI_DEBUG_1("(ecasoundc_sa) gen-content validated: <<<%s>>>\n", parser->buffer_rep.d);
}
parser->state_rep = ECI_STATE_INIT;
}
else
parser->state_rep = ECI_STATE_COMMON_CONTENT;
break;
case ECI_STATE_SEEK_TO_LF:
if (c == 0x0a) {
parser->token_phase_rep = ECI_TOKEN_PHASE_NONE;
parser->state_rep = ECI_STATE_INIT;
}
break;
default: {}
} /* end of switch() */
if (parser->token_phase_rep == ECI_TOKEN_PHASE_READING) {
eci_string_add(&parser->buffer_rep, parser->buffer_current_rep, &c, 1);
++parser->buffer_current_rep;
}
//ECI_DEBUG_2("(ecasoundc_sa) parser buf contents: '%s' (cur=%d)\n.", parser->buffer_rep.d, parser->buffer_current_rep);
}