/** * @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 #include /* ANSI-C: printf(), ... */ #include /* ANSI-C: calloc(), free() */ #include /* ANSI-C: strlen() */ #include /* ANSI-C: errno */ #include #include /* POSIX: fcntl() */ #include /* XPG4-UNIX: poll() */ #include /* POSIX: pipe(), fork() */ #include /* POSIX: stat() */ #include /* POSIX: fork() */ #include /* POSIX: wait() */ #include /* 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); }