// ------------------------------------------------------------------------ // eca-neteci-server.c: NetECI server implementation. // Copyright (C) 2002,2004,2009 Kai Vehmanen // // Attributes: // eca-style-version: 3 // // 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // ------------------------------------------------------------------------ #include #include /* memcpy() */ #include #include #include /* POSIX: fcntl() */ #include /* POSIX: pthread_* */ #include /* POSIX: fcntl() */ #include /* BSD: inet_ntoa() */ #include /* BSD: inet_ntoa() */ #include /* POSIX: poll() */ #include /* BSD: getpeername() */ #include /* OSX: u_int32_t (INADDR_ANY) */ #include #include #include #include #include #include #include #include "ecasound.h" #include "eca-neteci-server.h" /** * Options */ // #define NETECI_DEBUG_ENABLED #define ECA_NETECI_START_BUFFER_SIZE 128 #define ECA_NETECI_MAX_BUFFER_SIZE 65536 /** * Macro definitions */ #ifdef NETECI_DEBUG_ENABLED #define NETECI_DEBUG(x) x #else #define NETECI_DEBUG(x) ((void) 0) #endif /** * Import namespaces */ using namespace std; ECA_NETECI_SERVER::ECA_NETECI_SERVER(ECASOUND_RUN_STATE* state) : state_repp(state), srvfd_rep(-1), server_listening_rep(false), unix_sockets_rep(false), cleanup_request_rep(false) { } ECA_NETECI_SERVER::~ECA_NETECI_SERVER(void) { if (server_listening_rep == true) { close_server_socket(); } } /** * Launches the server thread. * * @param arg pointer to a ECA_NETECI_SERVER object */ void* ECA_NETECI_SERVER::launch_server_thread(void* arg) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "Server thread started"); ECA_NETECI_SERVER* self = reinterpret_cast(arg); self->run(); return 0; } /** * Starts running the NetECI server. * * After calling this function, the ECA_CONTROL_MAIN object * may be used at any time from the NetECI server thread. */ void ECA_NETECI_SERVER::run(void) { create_server_socket(); open_server_socket(); if (server_listening_rep == true) { listen_for_events(); } else { ECA_LOG_MSG(ECA_LOGGER::info, "Unable to start NetECI server. Please check that no other program is using the TCP port " + kvu_numtostr(state_repp->neteci_tcp_port) + "."); } close_server_socket(); ECA_LOG_MSG(ECA_LOGGER::user_objects, "server thread exiting"); } /** * Creates a server socket with 'socket()'. Depending on * object configuration either UNIX or IP socket is * created. */ void ECA_NETECI_SERVER::create_server_socket(void) { DBC_REQUIRE(server_listening_rep != true); DBC_REQUIRE(srvfd_rep <= 0); if (unix_sockets_rep == true) { srvfd_rep = socket(AF_UNIX, SOCK_STREAM, 0); if (srvfd_rep >= 0) { /* create a temporary filename for the socket in a secure way */ socketpath_rep = "/tmp/neteci_server_1"; addr_un_rep.sun_family = AF_UNIX; memcpy(addr_un_rep.sun_path, socketpath_rep.c_str(), socketpath_rep.size() + 1); addr_repp = reinterpret_cast(&addr_un_rep); } } else { srvfd_rep = socket(PF_INET, SOCK_STREAM, 0); if (srvfd_rep >= 0) { addr_in_rep.sin_family = AF_INET; addr_in_rep.sin_port = htons(state_repp->neteci_tcp_port); addr_in_rep.sin_addr.s_addr = INADDR_ANY; addr_repp = reinterpret_cast(&addr_in_rep); } } } /** * Opens the server socket for listening. If succesful, * 'server_listening_rep' will be true after the call. */ void ECA_NETECI_SERVER::open_server_socket(void) { DBC_REQUIRE(server_listening_rep != true); DBC_REQUIRE(srvfd_rep > 0); int val = 1; int ret = setsockopt(srvfd_rep, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(val)); if (ret < 0) std::cerr << "setsockopt() failed." << endl; // int res = bind(srvfd_rep, (struct sockaddr*)addr_repp, sizeof(*addr_repp)); int res = 0; if (unix_sockets_rep == true) res = bind(srvfd_rep, (struct sockaddr*)&addr_un_rep, sizeof(addr_un_rep)); else res = bind(srvfd_rep, (struct sockaddr*)&addr_in_rep, sizeof(addr_in_rep)); if (res == 0) { res = listen(srvfd_rep, 5); if (res == 0) { int res = fcntl(srvfd_rep, F_SETFL, O_NONBLOCK); if (res == -1) std::cerr << "fcntl() failed." << endl; NETECI_DEBUG(std::cout << "server socket created." << endl); server_listening_rep = true; } else std::cerr << "listen() failed." << endl; } else { if (unix_sockets_rep == true) { unlink(socketpath_rep.c_str()); } socketpath_rep.resize(0); std::cerr << "bind() failed." << endl; } DBC_ENSURE((unix_sockets_rep == true && (((server_listening_rep == true && socketpath_rep.size() > 0) || (server_listening_rep != true && socketpath_rep.size() == 0)))) || (unix_sockets_rep != true)); } /** * Closes the server socket. */ void ECA_NETECI_SERVER::close_server_socket(void) { DBC_REQUIRE(srvfd_rep > 0); DBC_REQUIRE(server_listening_rep == true); NETECI_DEBUG(cerr << "closing socket " << kvu_numtostr(srvfd_rep) << "." << endl); close(srvfd_rep); srvfd_rep = -1; server_listening_rep = false; DBC_ENSURE(srvfd_rep == -1); DBC_ENSURE(server_listening_rep != true); } /** * Listens for and accepts incoming connections. */ void ECA_NETECI_SERVER::listen_for_events(void) { /* * - loop until we get an exit request from network or from * ecasound_state */ /* - enter poll * - if new connections, accept them and add the new client to * client list * - if incoming bytes, grab ecasound_state lock, send command, * store retval, release lock, send the reply to client * - return to poll */ while(state_repp->exit_request == 0) { // NETECI_DEBUG(cerr << "checking for events" << endl); check_for_events(2000); } if (state_repp->exit_request != 0) { NETECI_DEBUG(cerr << "exit_request received" << endl); } } /** * Checks for new connections and messages from * clients. * * @param timeout upper-limit in ms for how long * function waits for events; if -1, * call will return immediately * (ie. is non-blocking) */ void ECA_NETECI_SERVER::check_for_events(int timeout) { int nfds = clients_rep.size() + 1; struct pollfd* ufds = new struct pollfd [nfds]; ufds[0].fd = srvfd_rep; ufds[0].events = POLLIN; ufds[0].revents = 0; std::list::iterator p = clients_rep.begin(); for(int n = 1; n < nfds; n++) { ufds[n].fd = (*p)->fd; ufds[n].events = POLLIN; ufds[n].revents = 0; ++p; } DBC_CHECK(nfds == 1 || p == clients_rep.end()); int ret = poll(ufds, nfds, timeout); if (ret > 0) { if (ufds[0].revents & POLLIN) { /* 1. new incoming connection */ handle_connection(srvfd_rep); } p = clients_rep.begin(); for(int n = 1; n < nfds; n++) { if (ufds[n].revents & POLLIN) { /* 2. client has sent a message */ handle_client_messages(*p); } else if (ufds[n].revents == POLLERR || ufds[n].revents == POLLHUP || ufds[n].revents == POLLNVAL) { /* 3. error, remove client */ remove_client(*p); } if (p != clients_rep.end()) ++p; } } if (cleanup_request_rep == true) { clean_removed_clients(); } delete[] ufds; } void ECA_NETECI_SERVER::handle_connection(int fd) { socklen_t bytes = 0; string peername; int connfd = 0; if (unix_sockets_rep == true) { bytes = static_cast(sizeof(addr_un_rep)); connfd = accept(fd, reinterpret_cast(&addr_un_rep), &bytes); peername = "UNIX:" + socketpath_rep; } else { bytes = static_cast(sizeof(addr_in_rep)); connfd = accept(fd, reinterpret_cast(&addr_in_rep), &bytes); if (connfd > 0) { struct sockaddr_in peeraddr; socklen_t peernamelen; // struct in_addr peerip; peername = "TCP/IP:"; int res = getpeername(connfd, reinterpret_cast(&peeraddr), reinterpret_cast(&peernamelen)); if (res == 0) peername += string(inet_ntoa(peeraddr.sin_addr)); else peername += string(inet_ntoa(addr_in_rep.sin_addr)); } } ECA_LOG_MSG(ECA_LOGGER::info, "New connection from " + peername + "."); if (connfd >= 0) { NETECI_DEBUG(cerr << "incoming connection accepted" << endl); struct ecasound_neteci_server_client* client = new struct ecasound_neteci_server_client; /* add a new client */ client->fd = connfd; client->buffer_length = ECA_NETECI_START_BUFFER_SIZE; client->buffer = new char [client->buffer_length]; client->buffer_current_ptr = 0; client->peername = peername; clients_rep.push_back(client); } } /** * Handle incoming messages for client 'client'. */ void ECA_NETECI_SERVER::handle_client_messages(struct ecasound_neteci_server_client* client) { char* buf[128]; int connfd = client->fd; NETECI_DEBUG(cerr << "handle_client_messages for fd " << connfd << endl); ssize_t c = kvu_fd_read(connfd, buf, 128, 5000); if (c > 0) { parse_raw_incoming_data(reinterpret_cast(buf), c, client); while(parsed_cmd_queue_rep.size() > 0) { const string& nextcmd = parsed_cmd_queue_rep.front(); if (nextcmd == "quit" || nextcmd == "q") { NETECI_DEBUG(cerr << "client initiated quit, removing client-fd " << connfd << "." << endl); remove_client(client); } else { handle_eci_command(nextcmd, client); } parsed_cmd_queue_rep.pop_front(); } /* ... */ } else { /* read() <= 0 */ NETECI_DEBUG(cerr << "read error, removing client-fd " << connfd << "." << endl); remove_client(client); } } void ECA_NETECI_SERVER::parse_raw_incoming_data(const char* buffer, ssize_t bytes, struct ecasound_neteci_server_client* client) { DBC_REQUIRE(buffer != 0); DBC_REQUIRE(bytes >= 0); DBC_REQUIRE(client != 0); DBC_DECLARE(int old_client_ptr = client->buffer_current_ptr); DBC_DECLARE(unsigned int old_cmd_queue_size = parsed_cmd_queue_rep.size()); NETECI_DEBUG(cerr << "parse incoming data; " << bytes << " bytes. Buffer length is " << client->buffer_length << endl); for(int n = 0; n < bytes; n++) { DBC_CHECK(client->buffer_current_ptr <= client->buffer_length); if (client->buffer_current_ptr == client->buffer_length) { int new_buffer_length = client->buffer_length * 2; char *new_buffer = new char [new_buffer_length]; if (new_buffer_length > ECA_NETECI_MAX_BUFFER_SIZE) { cerr << "client buffer overflow, unable to increase buffer size. flushing..." << endl; client->buffer_current_ptr = 0; } else { NETECI_DEBUG(cerr << "client buffer overflow, increasing buffer size from " << client->buffer_length << " to " << new_buffer_length << " bytes." << endl); for(int i = 0; i < client->buffer_length; i++) new_buffer[i] = client->buffer[i]; delete[] client->buffer; client->buffer = new_buffer; client->buffer_length = new_buffer_length; } } NETECI_DEBUG(cerr << "copying '" << buffer[n] << "'\n"); client->buffer[client->buffer_current_ptr] = buffer[n]; if (client->buffer_current_ptr > 0 && client->buffer[client->buffer_current_ptr] == '\n' && client->buffer[client->buffer_current_ptr - 1] == '\r') { string cmd (client->buffer, client->buffer_current_ptr - 1); NETECI_DEBUG(cerr << "storing command '" << cmd << "'" << endl); parsed_cmd_queue_rep.push_back(cmd); NETECI_DEBUG(cerr << "copying " << client->buffer_length - client->buffer_current_ptr - 1 << " bytes from " << client->buffer_current_ptr + 1 << " to the beginning of the buffer." << " Index is " << client->buffer_current_ptr << endl); DBC_CHECK(client->buffer_current_ptr < client->buffer_length); #if 0 /* must not use memcpy() as the affected areas may overlap! */ for(int o = 0, p = index + 1; p < client->buffer_length; o++, p++) { client->buffer[o] = client->buffer[p]; } #endif client->buffer_current_ptr = 0; } else { // NETECI_DEBUG(cerr << "crlf not found, index=" << index << ", n=" << n << "cur_ptr=" << client->buffer_current_ptr << ".\n"); client->buffer_current_ptr++; } } DBC_ENSURE(client->buffer_current_ptr > old_client_ptr || parsed_cmd_queue_rep.size() > old_cmd_queue_size); } void ECA_NETECI_SERVER::handle_eci_command(const string& cmd, struct ecasound_neteci_server_client* client) { ECA_CONTROL_MT* ctrl = state_repp->control; NETECI_DEBUG(cerr << "handle eci command: " << cmd << endl); assert(ctrl != 0); struct eci_return_value retval; ctrl->command(cmd, &retval); string strtosend = ECA_LOGGER_WELLFORMED::create_wellformed_message(ECA_LOGGER::eiam_return_values, std::string(ECA_CONTROL_MAIN::return_value_type_to_string(&retval)) + " " + ECA_CONTROL_MAIN::return_value_to_string(&retval)); int bytes_to_send = strtosend.size(); while(bytes_to_send > 0) { int ret = kvu_fd_write(client->fd, strtosend.c_str(), strtosend.size(), 5000); if (ret < 0) { cerr << "error in kvu_fd_write(), removing client.\n"; remove_client(client); break; } else { bytes_to_send -= ret; } } } /** * Removes 'client' from list of clients. * * Note! Internally, the 'fd' field of the deleted client * is marked to be -1. * * @see clean_removed_clients() */ void ECA_NETECI_SERVER::remove_client(struct ecasound_neteci_server_client* client) { NETECI_DEBUG(std::cout << "removing client." << std::endl); if (client != 0 && client->fd > 0) { ECA_LOG_MSG(ECA_LOGGER::info, "Closing connection " + client->peername + "."); close(client->fd); client->fd = -1; } cleanup_request_rep = true; } /** * Cleans the list of clients from removed objects. * * @see remove_client() */ void ECA_NETECI_SERVER::clean_removed_clients(void) { DBC_DECLARE(size_t oldsize = clients_rep.size()); DBC_DECLARE(size_t counter = 0); NETECI_DEBUG(std::cerr << "cleaning removed clients." << std::endl); list::iterator p = clients_rep.begin(); while(p != clients_rep.end()) { NETECI_DEBUG(std::cerr << "checking for delete, client " << *p << std::endl); if (*p != 0 && (*p)->fd == -1) { if ((*p)->buffer != 0) { delete[] (*p)->buffer; (*p)->buffer = 0; } std::list::iterator q = p; ++q; NETECI_DEBUG(std::cerr << "deleting client " << *p << std::endl); delete *p; NETECI_DEBUG(std::cerr << "erasing client " << *p << std::endl); *p = 0; clients_rep.erase(p); p = q; DBC_DECLARE(++counter); } else { ++p; } } cleanup_request_rep = false; DBC_ENSURE(clients_rep.size() == oldsize - counter); }