static void s1_ready_async(ErlDrvData drv_data, ErlDrvThreadData thread_data) { descriptor_t *desc = (descriptor_t *) drv_data; callstate_t *c = (callstate_t *) thread_data; int bytes, offset, i; char *p = NULL; unsigned long index = 0; edtk_debug("%s: cmd = %d", __FUNCTION__, c->cmd); if (c == NULL) { edtk_debug("%s: c == NULL", __FUNCTION__); return; } switch (c->cmd) { case S1_NULL: reply_ok(desc); break; case S1_OPEN: if (! c->o.__expect) { reply_error(desc, c->o.__expect_errval); break; } if (find_unused_fd_index(desc, &index) < 0) { reply_error(desc, ENOMEM); } else { desc->valmap_fd[index] = c->o.ret_int; reply_ok_valmap(desc, am_valmap_fd, index); } break; case S1_GETFD: reply_ok_num(desc, c->o.ret_int); break; case S1_SENDFD: if (c->o.__expect) { reply_ok_num(desc, c->o.ret_int_t); } else { reply_error(desc, c->o.__expect_errval); } break; case S1_RECEIVEFD: if (c->o.__expect) { reply_ok_num(desc, c->o.ret_int_t); } else { reply_error(desc, c->o.__expect_errval); } break; case S1_CLOSE: if (! c->o.__expect) { reply_error(desc, c->o.__expect_errval); break; } cleanup_valmap_fd_index(desc, c->i.__valmap_fd_index, 0); reply_ok_num(desc, c->o.ret_int); break; default: edtk_debug("%s: bogus command, should never happen", __FUNCTION__); break; } sys_free(c); }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_close(zmq_drv_t *drv) { ErlDrvTermData caller = driver_caller(drv->port); zmq_sock_info* si = drv->get_socket_info(caller); if (!si) { reply_error(drv->port, caller, ENODEV); return; } zmqdrv_fprintf("close %p\r\n", si->socket); driver_demonitor_process(drv->port, &si->monitor); if (si->busy) { // Remove socket from vm polling driver_select(drv->port, si->fd, ERL_DRV_READ, 0); } drv->zmq_pid_socket.erase(caller); drv->zmq_fd_socket.erase(si->fd); //zmq_close(Socket) is called in ~zmq_sock_info delete si; reply_ok(drv->port, caller); }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_socket(zmq_drv_t *drv, const uint8_t* bytes, size_t size) { int type = *bytes; assert(sizeof(uint8_t) == size); zmqdrv_fprintf("socket (type: %d)\r\n", type); ErlDrvTermData caller = driver_caller(drv->port); if (drv->terminating) { reply_error(drv->port, caller, ETERM); return; } assert(NULL == drv->get_socket_info(caller)); // Runtime validation as well in case zmq_drv.erl is used directly rather // than through zmq_socket.erl gen_server. if (NULL != drv->get_socket_info(caller)) { reply_error(drv->port, caller, EBUSY); return; } void* s = zmq_socket(drv->zmq_context, type); if (!s) { reply_error(drv->port, caller, zmq_errno()); return; } //TODO: Support Windows 'SOCKET' type? int fd; size_t fd_size = sizeof(fd); if (0 != zmq_getsockopt(s, ZMQ_FD, &fd, &fd_size)) { reply_error(drv->port, caller, zmq_errno()); zmq_close(s); return; } zmq_sock_info* si = new zmq_sock_info(s, (ErlDrvEvent)fd); if (!si) { driver_failure_posix(drv->port, ENOMEM); return; } driver_monitor_process(drv->port, caller, &si->monitor); drv->zmq_pid_socket[caller] = si; drv->zmq_fd_socket[si->fd] = si; zmqdrv_fprintf("socket %p owner %lu\r\n", si->socket, caller); reply_ok(drv->port, caller); }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_connect(zmq_drv_t *drv, const uint8_t* bytes, size_t size) { // expects the endpoint to be zero terminated char* endpoint = (char*)bytes; // TODO: check for zero termination within size limit assert(sizeof(char) <= size); // Must always have at least the 0 terminating char. ErlDrvTermData caller = driver_caller(drv->port); if (drv->terminating) { reply_error(drv->port, caller, ETERM); return; } zmq_sock_info* si = drv->get_socket_info(caller); if (!si) { reply_error(drv->port, caller, ENODEV); return; } zmqdrv_fprintf("connect %p (endpoint: %s)\r\n", si->socket, endpoint); if (0 != zmq_connect(si->socket, endpoint)) { reply_error(drv->port, caller, zmq_errno()); return; } reply_ok(drv->port, caller); }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_init(zmq_drv_t *drv, const uint8_t* bytes, size_t size) { int io_threads = *bytes; assert(sizeof(uint8_t) == size); zmqdrv_fprintf("init (io_threads: %d)\r\n", io_threads); // We only support a single zmq context, but zeromq itself supports multiple if (drv->zmq_context) { reply_error(drv->port, driver_caller(drv->port), EBUSY); return; } drv->terminating = false; drv->zmq_context = zmq_init(io_threads); if (!drv->zmq_context) { reply_error(drv->port, driver_caller(drv->port), zmq_errno()); return; } zmqdrv_fprintf("init %p\r\n", drv->zmq_context); reply_ok(drv->port, driver_caller(drv->port)); }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_term(zmq_drv_t *drv) { zmqdrv_fprintf("term %p\r\n", drv->zmq_context); if (0 < drv->zmq_pid_socket.size()) { for (zmq_pid_socket_map_t::iterator it = drv->zmq_pid_socket.begin(); it != drv->zmq_pid_socket.end(); ++it) { zmq_sock_info* si = it->second; if (si->busy) { // Remove socket from erlang vm polling driver_select(drv->port, si->fd, ERL_DRV_READ, 0); if (si->out_caller) { reply_error(drv->port, si->out_caller, ETERM); si->out_caller = 0; zmq_msg_close(&si->out_msg); } if (si->in_caller) { reply_error(drv->port, si->in_caller, ETERM); si->in_caller = 0; } if (si->poll_caller) { send_events(drv->port, si->poll_caller, (uint32_t)ZMQ_POLLERR); si->poll_caller = 0; } si->busy = false; } } // TODO: Remove if zeromq itself ever gets fixed. As zmq_term() is a // blocking call, and will not return until all sockets are closed, // so do not allow it to be called while there are open sockets. drv->terminating = true; reply_error(drv->port, driver_caller(drv->port), EAGAIN); return; } // cross fingers and hope zmq_term() doesn't block, else we hardlock. if (0 != zmq_term(drv->zmq_context)) { reply_error(drv->port, driver_caller(drv->port), zmq_errno()); return; } drv->zmq_context = NULL; reply_ok(drv->port, driver_caller(drv->port)); }
void erl_lua_settable(lua_drv_t *driver_data, char *buf, int index) { long i; ei_decode_long(buf, &index, &i); lua_settable(driver_data->L, i); reply_ok(driver_data); }
void erl_lua_concat(lua_drv_t *driver_data, char *buf, int index) { long n; ei_decode_long(buf, &index, &n); lua_concat(driver_data->L, n); reply_ok(driver_data); }
void erl_lua_pushinteger(lua_drv_t *driver_data, char *buf, int index) { long long num; ei_decode_longlong(buf, &index, &num); lua_pushinteger(driver_data->L, num); reply_ok(driver_data); }
void erl_lua_pushboolean(lua_drv_t *driver_data, char *buf, int index) { int b; ei_decode_boolean(buf, &index, &b); lua_pushboolean(driver_data->L, b); reply_ok(driver_data); }
void erl_lua_createtable(lua_drv_t *driver_data, char *buf, int index) { long narr, nrec; ei_decode_long(buf, &index, &narr); ei_decode_long(buf, &index, &nrec); lua_createtable(driver_data->L, narr, nrec); reply_ok(driver_data); }
void erl_lua_setglobal(lua_drv_t *driver_data, char *buf, int index) { char *name; name = decode_string(buf, &index); lua_setglobal(driver_data->L, name); reply_ok(driver_data); free(name); }
void erl_lua_call(lua_drv_t *driver_data, char *buf, int index) { long args, results; ei_decode_long(buf, &index, &args); ei_decode_long(buf, &index, &results); lua_call(driver_data->L, args, results); reply_ok(driver_data); }
void erl_lua_pushlstring(lua_drv_t *driver_data, char *buf, int index) { char *str; int len; str = decode_binary(buf, &index, &len); lua_pushlstring(driver_data->L, str, len); reply_ok(driver_data); free(str); }
void erl_lua_setfield(lua_drv_t *driver_data, char *buf, int index) { long i; char *name; ei_decode_long(buf, &index, &i); name = decode_string(buf, &index); lua_setfield(driver_data->L, i, name); reply_ok(driver_data); free(name); }
void erl_lual_dostring(lua_drv_t *driver_data, char *buf, int index) { char *code; int len; code = decode_binary(buf, &index, &len); code[len] = '\0'; if (!luaL_dostring(driver_data->L, code)) reply_ok(driver_data); else reply_throw(driver_data, lua_tostring(driver_data->L, -1)); free(code); }
void erl_lua_pushnumber(lua_drv_t *driver_data, char *buf, int index) { double dnum; long long lnum; int type, len; ei_get_type(buf, &index, &type, &len); switch (type) { case ERL_FLOAT_EXT: ei_decode_double(buf, &index, &dnum); lua_pushnumber(driver_data->L, dnum); break; default: ei_decode_longlong(buf, &index, &lnum); lua_pushnumber(driver_data->L, lnum); break; } reply_ok(driver_data); }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_send(zmq_drv_t *drv, const uint8_t* bytes, size_t size, ErlDrvBinary* bin) { int flags = *bytes; void* data = (void *)(bytes+sizeof(uint8_t)); size_t data_size = size - sizeof(uint8_t); assert(sizeof(uint8_t) <= size); ErlDrvTermData caller = driver_caller(drv->port); if (drv->terminating) { reply_error(drv->port, caller, ETERM); return; } zmq_sock_info* si = drv->get_socket_info(caller); if (!si) { reply_error(drv->port, caller, ENODEV); return; } assert(0 == si->out_caller); zmqdrv_fprintf("send %p (flags: %d bytes: %u)\r\n", si->socket, flags, data_size); // Increment the reference count on binary so that zmq can take ownership of it. driver_binary_inc_refc(bin); if (zmq_msg_init_data(&si->out_msg, data, data_size, &zmqcb_free_binary, bin)) { reply_error(drv->port, caller, zmq_errno()); driver_binary_dec_refc(bin); return; } if (0 == zmq_send(si->socket, &si->out_msg, flags|ZMQ_NOBLOCK)) { reply_ok(drv->port, caller); zmq_msg_close(&si->out_msg); } else if (ZMQ_NOBLOCK != (ZMQ_NOBLOCK & flags) && EAGAIN == zmq_errno()) { // Caller requested blocking send // Can't send right now. Make the caller wait by not returning result zmqdrv_fprintf("send %p blocking\r\n", si->socket); si->out_flags = flags; si->out_caller = caller; if (!si->busy) { driver_select(drv->port, si->fd, ERL_DRV_READ, 1); si->busy = true; } } else { reply_error(drv->port, caller, zmq_errno()); zmq_msg_close(&si->out_msg); } }
int handle_command(struct cfg *cf, struct rtpp_command *cmd) { int i, verbose, rval; int playcount; char *cp, *tcp; char *pname, *codecs, *recording_name; struct rtpp_session *spa; int record_single_file; struct ul_opts *ulop; struct d_opts dopt; spa = NULL; recording_name = NULL; codecs = NULL; /* Step II: parse parameters that are specific to a particular op and run simple ops */ switch (cmd->cca.op) { case VER_FEATURE: handle_ver_feature(cf, cmd); return 0; case GET_VER: /* This returns base version. */ reply_number(cf, cmd, CPROTOVER); return 0; case DELETE_ALL: /* Delete all active sessions */ rtpp_log_write(RTPP_LOG_INFO, cf->stable->glog, "deleting all active sessions"); pthread_mutex_lock(&cf->sessinfo.lock); for (i = 0; i < cf->sessinfo.nsessions; i++) { spa = cf->sessinfo.sessions[i]; if (spa == NULL || spa->sidx[0] != i) continue; remove_session(cf, spa); } pthread_mutex_unlock(&cf->sessinfo.lock); reply_ok(cf, cmd); return 0; case INFO: handle_info(cf, cmd, &cmd->argv[0][1]); return 0; case PLAY: /* * P callid pname codecs from_tag to_tag * * <codecs> could be either comma-separated list of supported * payload types or word "session" (without quotes), in which * case list saved on last session update will be used instead. */ playcount = 1; pname = cmd->argv[2]; codecs = cmd->argv[3]; tcp = &(cmd->argv[0][1]); if (*tcp != '\0') { playcount = strtol(tcp, &cp, 10); if (cp == tcp || *cp != '\0') { rtpp_log_write(RTPP_LOG_ERR, cf->stable->glog, "command syntax error"); reply_error(cf, cmd, ECODE_PARSE_6); return 0; } } break; case COPY: recording_name = cmd->argv[2]; /* Fallthrough */ case RECORD: if (cmd->argv[0][1] == 'S' || cmd->argv[0][1] == 's') { if (cmd->argv[0][2] != '\0') { rtpp_log_write(RTPP_LOG_ERR, cf->stable->glog, "command syntax error"); reply_error(cf, cmd, ECODE_PARSE_2); return 0; } record_single_file = (cf->stable->record_pcap == 0) ? 0 : 1; } else { if (cmd->argv[0][1] != '\0') { rtpp_log_write(RTPP_LOG_ERR, cf->stable->glog, "command syntax error"); reply_error(cf, cmd, ECODE_PARSE_3); return 0; } record_single_file = 0; } break; case DELETE: /* D[w] call_id from_tag [to_tag] */ dopt.weak = 0; for (cp = cmd->argv[0] + 1; *cp != '\0'; cp++) { switch (*cp) { case 'w': case 'W': dopt.weak = 1; break; default: rtpp_log_write(RTPP_LOG_ERR, cf->stable->glog, "DELETE: unknown command modifier `%c'", *cp); reply_error(cf, cmd, ECODE_PARSE_4); return 0; } } break; case UPDATE: case LOOKUP: ulop = rtpp_command_ul_opts_parse(cf, cmd); if (ulop == NULL) { return 0; } break; case GET_STATS: verbose = 0; for (cp = cmd->argv[0] + 1; *cp != '\0'; cp++) { switch (*cp) { case 'v': case 'V': verbose = 1; break; default: rtpp_log_write(RTPP_LOG_ERR, cf->stable->glog, "STATS: unknown command modifier `%c'", *cp); reply_error(cf, cmd, ECODE_PARSE_5); return 0; } } i = handle_get_stats(cf, cmd, verbose); if (i != 0) { reply_error(cf, cmd, i); } return 0; default: break; } /* * Record and delete need special handling since they apply to all * streams in the session. */ switch (cmd->cca.op) { case DELETE: i = handle_delete(cf, &cmd->cca, dopt.weak); break; case RECORD: i = handle_record(cf, &cmd->cca, record_single_file); break; default: i = find_stream(cf, cmd->cca.call_id, cmd->cca.from_tag, cmd->cca.to_tag, &spa); if (i != -1 && cmd->cca.op != UPDATE) i = NOT(i); break; } if (i == -1 && cmd->cca.op != UPDATE) { rtpp_log_write(RTPP_LOG_INFO, cf->stable->glog, "%s request failed: session %s, tags %s/%s not found", cmd->cca.rname, cmd->cca.call_id, cmd->cca.from_tag, cmd->cca.to_tag != NULL ? cmd->cca.to_tag : "NONE"); if (cmd->cca.op == LOOKUP) { rtpp_command_ul_opts_free(ulop); ul_reply_port(cf, cmd, NULL); return 0; } reply_error(cf, cmd, ECODE_SESUNKN); return 0; } switch (cmd->cca.op) { case DELETE: case RECORD: reply_ok(cf, cmd); break; case NOPLAY: handle_noplay(cf, spa, i, cmd); reply_ok(cf, cmd); break; case PLAY: handle_noplay(cf, spa, i, cmd); if (strcmp(codecs, "session") == 0) { if (spa->codecs[i] == NULL) { reply_error(cf, cmd, ECODE_INVLARG_5); return 0; } codecs = spa->codecs[i]; } if (playcount != 0 && handle_play(cf, spa, i, codecs, pname, playcount, cmd) != 0) { reply_error(cf, cmd, ECODE_PLRFAIL); return 0; } reply_ok(cf, cmd); break; case COPY: if (handle_copy(cf, spa, i, recording_name, record_single_file) != 0) { reply_error(cf, cmd, ECODE_CPYFAIL); return 0; } reply_ok(cf, cmd); break; case QUERY: rval = handle_query(cf, cmd, spa, i); if (rval != 0) { reply_error(cf, cmd, rval); } break; case LOOKUP: case UPDATE: rtpp_command_ul_handle(cf, cmd, ulop, spa, i); break; default: /* Programmatic error, should not happen */ abort(); } return 0; }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_setsockopt(zmq_drv_t *drv, const uint8_t* bytes, size_t size) { uint8_t n = *bytes; const uint8_t* p = bytes+sizeof(uint8_t); ErlDrvTermData caller = driver_caller(drv->port); if (drv->terminating) { reply_error(drv->port, caller, ETERM); return; } zmq_sock_info* si = drv->get_socket_info(caller); if (!si) { reply_error(drv->port, caller, ENODEV); return; } zmqdrv_fprintf("setsockopt %p (setting %d options)\r\n", si->socket, (int)n); for (uint8_t j = 0; j < n; ++j) { int opt = *p++; uint64_t optvallen = *p++; const void* optval = p; switch (opt) { case ZMQ_HWM: assert(optvallen == sizeof(uint64_t));break; case ZMQ_SWAP: assert(optvallen == sizeof(int64_t)); break; case ZMQ_AFFINITY: assert(optvallen == sizeof(uint64_t));break; case ZMQ_IDENTITY: assert(optvallen <= 255); break; case ZMQ_SUBSCRIBE: break; // While the erlang layer limits this to 255, there is no zmq limit case ZMQ_UNSUBSCRIBE: break; // While the erlang layer limits this to 255, there is no zmq limit case ZMQ_RATE: assert(optvallen == sizeof(int64_t)); break; case ZMQ_RECOVERY_IVL: assert(optvallen == sizeof(int64_t)); break; case ZMQ_RECOVERY_IVL_MSEC: assert(optvallen == sizeof(int64_t)); break; case ZMQ_MCAST_LOOP: assert(optvallen == sizeof(int64_t)); break; case ZMQ_SNDBUF: assert(optvallen == sizeof(uint64_t));break; case ZMQ_RCVBUF: assert(optvallen == sizeof(uint64_t));break; case ZMQ_LINGER: assert(optvallen == sizeof(int)); break; // Erlang layer assumes 32bit case ZMQ_RECONNECT_IVL: assert(optvallen == sizeof(int)); break; // Erlang layer assumes 32bit case ZMQ_RECONNECT_IVL_MAX: assert(optvallen == sizeof(int)); break; // Erlang layer assumes 32bit case ZMQ_BACKLOG: assert(optvallen == sizeof(int)); break; // Erlang layer assumes 32bit default: assert(true); } assert((size_t)((uint8_t*)(p + optvallen) - bytes) <= size); zmqdrv_fprintf("setsockopt %p (opt: %d)\r\n", si->socket, opt); if (0 != zmq_setsockopt(si->socket, opt, optval, optvallen)) { reply_error(drv->port, caller, zmq_errno()); return; } p += optvallen; } reply_ok(drv->port, caller); }
void erl_lua_pushnil(lua_drv_t *driver_data, char *buf, int index) { lua_pushnil(driver_data->L); reply_ok(driver_data); }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void wrap_zmq_poll(zmq_drv_t *drv, const uint8_t* bytes, size_t size) { uint32_t events = *bytes; assert(sizeof(uint8_t) == size); ErlDrvTermData caller = driver_caller(drv->port); if (drv->terminating) { reply_error(drv->port, caller, ETERM); return; } zmq_sock_info* si = drv->get_socket_info(caller); if (!si) { reply_error(drv->port, caller, ENODEV); return; } zmqdrv_fprintf("poll %p (events: %u)\r\n", si->socket, events); if (0 == events) { si->poll_events = 0; si->poll_caller = 0; if (si->busy && 0 == si->in_caller && 0 == si->out_caller) { driver_select(drv->port, si->fd, ERL_DRV_READ, 0); si->busy = false; } reply_ok(drv->port, caller); return; } if (si->busy) { reply_error(drv->port, caller, EBUSY); return; } assert((ZMQ_POLLIN|ZMQ_POLLOUT) & events); uint32_t revents; size_t revents_size = sizeof(revents); if (0 == zmq_getsockopt(si->socket, ZMQ_EVENTS, &revents, &revents_size)) { // reply immediately; poll event notification happens out of band. reply_ok(drv->port, caller); revents &= events; if (0 != revents) { send_events(drv->port, caller, revents); } else { // No matching pending event, wait for one. zmqdrv_fprintf("poll %p blocking\r\n", si->socket); si->poll_events = events; si->poll_caller = caller; si->busy = true; driver_select(drv->port, si->fd, ERL_DRV_READ, 1); } } else { // EINVAL will only occur if our getsockopt call was invalid assert(EINVAL != zmq_errno()); // else the problem was with the context/socket, and should be returned reply_error(drv->port, caller, zmq_errno()); } }
void erl_lua_newtable(lua_drv_t *driver_data, char *buf, int index) { lua_newtable(driver_data->L); reply_ok(driver_data); }
int handle_command(struct cfg *cf, int controlfd, double dtime) { int len, argc, i, pidx, asymmetric; int external, pf, lidx, playcount, weak, tpf; int fds[2], lport, n; socklen_t rlen; char buf[1024 * 8]; char *cp, *call_id, *from_tag, *to_tag, *addr, *port, *cookie; char *pname, *codecs, *recording_name, *t; struct rtpp_session *spa, *spb; char **ap, *argv[10]; const char *rname, *errmsg; struct sockaddr *ia[2], *lia[2]; struct sockaddr_storage raddr; int requested_nsamples; enum {DELETE, RECORD, PLAY, NOPLAY, COPY, UPDATE, LOOKUP, QUERY} op; int max_argc; char *socket_name_u, *notify_tag; struct sockaddr *local_addr; char c; requested_nsamples = -1; ia[0] = ia[1] = NULL; spa = spb = NULL; lia[0] = lia[1] = cf->bindaddr[0]; lidx = 1; fds[0] = fds[1] = -1; recording_name = NULL; socket_name_u = notify_tag = NULL; local_addr = NULL; codecs = NULL; if (cf->umode == 0) { for (;;) { len = read(controlfd, buf, sizeof(buf) - 1); if (len != -1 || (errno != EAGAIN && errno != EINTR)) break; sched_yield(); } } else { rlen = sizeof(raddr); len = recvfrom(controlfd, buf, sizeof(buf) - 1, 0, sstosa(&raddr), &rlen); } if (len == -1) { if (errno != EAGAIN && errno != EINTR) rtpp_log_ewrite(RTPP_LOG_ERR, cf->glog, "can't read from control socket"); return -1; } buf[len] = '\0'; rtpp_log_write(RTPP_LOG_DBUG, cf->glog, "received command \"%s\"", buf); cp = buf; argc = 0; memset(argv, 0, sizeof(argv)); for (ap = argv; (*ap = rtpp_strsep(&cp, "\r\n\t ")) != NULL;) if (**ap != '\0') { argc++; if (++ap >= &argv[10]) break; } cookie = NULL; if (argc < 1 || (cf->umode != 0 && argc < 2)) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 0); return 0; } /* Stream communication mode doesn't use cookie */ if (cf->umode != 0) { cookie = argv[0]; for (i = 1; i < argc; i++) argv[i - 1] = argv[i]; argc--; argv[argc] = NULL; } else { cookie = NULL; } addr = port = NULL; switch (argv[0][0]) { case 'u': case 'U': /* U[opts] callid remote_ip remote_port from_tag [to_tag] */ op = UPDATE; rname = "update/create"; break; case 'l': case 'L': op = LOOKUP; rname = "lookup"; break; case 'd': case 'D': op = DELETE; rname = "delete"; break; case 'p': case 'P': /* * P callid pname codecs from_tag to_tag * * <codecs> could be either comma-separated list of supported * payload types or word "session" (without quotes), in which * case list saved on last session update will be used instead. */ op = PLAY; rname = "play"; playcount = 1; pname = argv[2]; codecs = argv[3]; break; case 'r': case 'R': op = RECORD; rname = "record"; break; case 'c': case 'C': op = COPY; rname = "copy"; break; case 's': case 'S': op = NOPLAY; rname = "noplay"; break; case 'v': case 'V': if (argv[0][1] == 'F' || argv[0][1] == 'f') { int i, known; /* * Wait for protocol version datestamp and check whether we * know it. */ if (argc != 2 && argc != 3) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 2); return 0; } /* * Only list 20081224 protocol mod as supported if * user actually enabled notification with -n */ if (strcmp(argv[1], "20081224") == 0 && cf->timeout_handler.socket_name == NULL) { reply_number(cf, controlfd, &raddr, rlen, cookie, 0); return 0; } for (known = i = 0; proto_caps[i].pc_id != NULL; ++i) { if (!strcmp(argv[1], proto_caps[i].pc_id)) { known = 1; break; } } reply_number(cf, controlfd, &raddr, rlen, cookie, known); return 0; } if (argc != 1 && argc != 2) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 2); return 0; } /* This returns base version. */ reply_number(cf, controlfd, &raddr, rlen, cookie, CPROTOVER); return 0; case 'i': case 'I': if (cookie == NULL) len = sprintf(buf, "sessions created: %llu\nactive sessions: %d\n" "active streams: %d\n", cf->sessions_created, cf->sessions_active, cf->nsessions / 2); else len = sprintf(buf, "%s sessions created: %llu\nactive sessions: %d\n" "active streams: %d\n", cookie, cf->sessions_created, cf->sessions_active, cf->nsessions / 2); for (i = 1; i < cf->nsessions; i++) { char addrs[4][256]; spa = cf->sessions[i]; if (spa == NULL || spa->sidx[0] != i) continue; /* RTCP twin session */ if (spa->rtcp == NULL) { spb = spa->rtp; buf[len++] = '\t'; } else { spb = spa->rtcp; buf[len++] = '\t'; buf[len++] = 'C'; buf[len++] = ' '; } addr2char_r(spb->laddr[1], addrs[0], sizeof(addrs[0])); if (spb->addr[1] == NULL) { strcpy(addrs[1], "NONE"); } else { sprintf(addrs[1], "%s:%d", addr2char(spb->addr[1]), addr2port(spb->addr[1])); } addr2char_r(spb->laddr[0], addrs[2], sizeof(addrs[2])); if (spb->addr[0] == NULL) { strcpy(addrs[3], "NONE"); } else { sprintf(addrs[3], "%s:%d", addr2char(spb->addr[0]), addr2port(spb->addr[0])); } len += sprintf(buf + len, "%s/%s: caller = %s:%d/%s, callee = %s:%d/%s, " "stats = %lu/%lu/%lu/%lu, ttl = %d/%d\n", spb->call_id, spb->tag, addrs[0], spb->ports[1], addrs[1], addrs[2], spb->ports[0], addrs[3], spa->pcount[0], spa->pcount[1], spa->pcount[2], spa->pcount[3], spb->ttl[0], spb->ttl[1]); if (len + 512 > sizeof(buf)) { doreply(cf, controlfd, buf, len, &raddr, rlen); len = 0; } } if (len > 0) doreply(cf, controlfd, buf, len, &raddr, rlen);; return 0; break; case 'q': case 'Q': op = QUERY; rname = "query"; break; case 'x': case 'X': /* Delete all active sessions */ rtpp_log_write(RTPP_LOG_INFO, cf->glog, "deleting all active sessions"); for (i = 1; i < cf->nsessions; i++) { spa = cf->sessions[i]; if (spa == NULL || spa->sidx[0] != i) continue; /* Skip RTCP twin session */ if (spa->rtcp != NULL) { remove_session(cf, spa); } } reply_ok(cf, controlfd, &raddr, rlen, cookie); return 0; break; default: rtpp_log_write(RTPP_LOG_ERR, cf->glog, "unknown command"); reply_error(cf, controlfd, &raddr, rlen, cookie, 3); return 0; } call_id = argv[1]; if (op == UPDATE || op == LOOKUP || op == PLAY) { max_argc = (op == UPDATE ? 8 : 6); if (argc < 5 || argc > max_argc) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 4); return 0; } from_tag = argv[4]; to_tag = argv[5]; if (op == PLAY && argv[0][1] != '\0') playcount = atoi(argv[0] + 1); if (op == UPDATE && argc > 6) { socket_name_u = argv[6]; if (strncmp("unix:", socket_name_u, 5) == 0) socket_name_u += 5; if (argc == 8) { notify_tag = argv[7]; len = url_unquote((uint8_t *)notify_tag, strlen(notify_tag)); if (len == -1) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error - invalid URL encoding"); reply_error(cf, controlfd, &raddr, rlen, cookie, 4); return 0; } notify_tag[len] = '\0'; } } } if (op == COPY) { if (argc < 4 || argc > 5) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } recording_name = argv[2]; from_tag = argv[3]; to_tag = argv[4]; } if (op == DELETE || op == RECORD || op == NOPLAY || op == QUERY) { if (argc < 3 || argc > 4) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } from_tag = argv[2]; to_tag = argv[3]; } if (op == DELETE || op == RECORD || op == COPY || op == NOPLAY) { /* D, R and S commands don't take any modifiers */ if (argv[0][1] != '\0') { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } } if (op == UPDATE || op == LOOKUP || op == DELETE) { addr = argv[2]; port = argv[3]; /* Process additional command modifiers */ external = 1; /* In bridge mode all clients are assumed to be asymmetric */ asymmetric = (cf->bmode != 0) ? 1 : 0; pf = AF_INET; weak = 0; for (cp = argv[0] + 1; *cp != '\0'; cp++) { switch (*cp) { case 'a': case 'A': asymmetric = 1; break; case 'e': case 'E': if (lidx < 0) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } lia[lidx] = cf->bindaddr[1]; lidx--; break; case 'i': case 'I': if (lidx < 0) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } lia[lidx] = cf->bindaddr[0]; lidx--; break; case '6': pf = AF_INET6; break; case 's': case 'S': asymmetric = 0; break; case 'w': case 'W': weak = 1; break; case 'z': case 'Z': requested_nsamples = (strtol(cp + 1, &cp, 10) / 10) * 80; if (requested_nsamples <= 0) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } cp--; break; case 'c': case 'C': cp += 1; for (t = cp; *cp != '\0'; cp++) { if (!isdigit(*cp) && *cp != ',') break; } if (t == cp) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } codecs = alloca(cp - t + 1); memcpy(codecs, t, cp - t); codecs[cp - t] = '\0'; cp--; break; case 'l': case 'L': len = extractaddr(cp + 1, &t, &cp, &tpf); if (len == -1) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } c = t[len]; t[len] = '\0'; local_addr = host2bindaddr(cf, t, tpf, &errmsg); if (local_addr == NULL) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "invalid local address: %s: %s", t, errmsg); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } t[len] = c; cp--; break; case 'r': case 'R': len = extractaddr(cp + 1, &t, &cp, &tpf); if (len == -1) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "command syntax error"); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } c = t[len]; t[len] = '\0'; local_addr = alloca(sizeof(struct sockaddr_storage)); n = resolve(local_addr, tpf, t, SERVICE, AI_PASSIVE); if (n != 0) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "invalid remote address: %s: %s", t, gai_strerror(n)); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } if (local4remote(cf, local_addr, satoss(local_addr)) == -1) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "can't find local address for remote address: %s", t); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } local_addr = addr2bindaddr(cf, local_addr, &errmsg); if (local_addr == NULL) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "invalid local address: %s", errmsg); reply_error(cf, controlfd, &raddr, rlen, cookie, 1); return 0; } t[len] = c; cp--; break; default: rtpp_log_write(RTPP_LOG_ERR, cf->glog, "unknown command modifier `%c'", *cp); break; } } if (op != DELETE && addr != NULL && port != NULL && strlen(addr) >= 7) { struct sockaddr_storage tia; if ((n = resolve(sstosa(&tia), pf, addr, port, AI_NUMERICHOST)) == 0) { if (!ishostnull(sstosa(&tia))) { for (i = 0; i < 2; i++) { ia[i] = malloc(SS_LEN(&tia)); if (ia[i] == NULL) { handle_nomem(cf, controlfd, &raddr, rlen, cookie, 5, ia, fds, spa, spb); return 0; } memcpy(ia[i], &tia, SS_LEN(&tia)); } /* Set port for RTCP, will work both for IPv4 and IPv6 */ n = ntohs(satosin(ia[1])->sin_port); satosin(ia[1])->sin_port = htons(n + 1); } } else { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "getaddrinfo: %s", gai_strerror(n)); } } } /* * Record and delete need special handling since they apply to all * streams in the session. */ switch (op) { case DELETE: i = handle_delete(cf, call_id, from_tag, to_tag, weak); break; case RECORD: i = handle_record(cf, call_id, from_tag, to_tag); break; default: i = find_stream(cf, call_id, from_tag, to_tag, &spa); if (i != -1 && op != UPDATE) i = NOT(i); break; } if (i == -1 && op != UPDATE) { rtpp_log_write(RTPP_LOG_INFO, cf->glog, "%s request failed: session %s, tags %s/%s not found", rname, call_id, from_tag, to_tag != NULL ? to_tag : "NONE"); if (op == LOOKUP) { for (i = 0; i < 2; i++) if (ia[i] != NULL) free(ia[i]); reply_port(cf, controlfd, &raddr, rlen, cookie, 0, lia); return 0; } reply_error(cf, controlfd, &raddr, rlen, cookie, 8); return 0; } switch (op) { case DELETE: case RECORD: reply_ok(cf, controlfd, &raddr, rlen, cookie); return 0; case NOPLAY: handle_noplay(cf, spa, i); reply_ok(cf, controlfd, &raddr, rlen, cookie); return 0; case PLAY: handle_noplay(cf, spa, i); if (strcmp(codecs, "session") == 0) { if (spa->codecs[i] == NULL) { reply_error(cf, controlfd, &raddr, rlen, cookie, 6); return 0; } codecs = spa->codecs[i]; } if (playcount != 0 && handle_play(cf, spa, i, codecs, pname, playcount) != 0) { reply_error(cf, controlfd, &raddr, rlen, cookie, 6); return 0; } reply_ok(cf, controlfd, &raddr, rlen, cookie); return 0; case COPY: handle_copy(cf, spa, i, recording_name); reply_ok(cf, controlfd, &raddr, rlen, cookie); return 0; case QUERY: handle_query(cf, controlfd, &raddr, rlen, cookie, spa, i); return 0; case LOOKUP: case UPDATE: /* those are handled below */ break; default: /* Programmatic error, should not happen */ abort(); } pidx = 1; lport = 0; if (i != -1) { assert(op == UPDATE || op == LOOKUP); if (spa->fds[i] == -1) { if (local_addr != NULL) { spa->laddr[i] = local_addr; } if (create_listener(cf, spa->laddr[i], &lport, fds) == -1) { rtpp_log_write(RTPP_LOG_ERR, spa->log, "can't create listener"); reply_error(cf, controlfd, &raddr, rlen, cookie, 7); return 0; } assert(spa->fds[i] == -1); spa->fds[i] = fds[0]; assert(spa->rtcp->fds[i] == -1); spa->rtcp->fds[i] = fds[1]; spa->ports[i] = lport; spa->rtcp->ports[i] = lport + 1; spa->complete = spa->rtcp->complete = 1; append_session(cf, spa, i); append_session(cf, spa->rtcp, i); } if (weak) spa->weak[i] = 1; else if (op == UPDATE) spa->strong = 1; lport = spa->ports[i]; lia[0] = spa->laddr[i]; pidx = (i == 0) ? 1 : 0; spa->ttl_mode = cf->ttl_mode; spa->ttl[0] = cf->max_ttl; spa->ttl[1] = cf->max_ttl; if (op == UPDATE) { rtpp_log_write(RTPP_LOG_INFO, spa->log, "adding %s flag to existing session, new=%d/%d/%d", weak ? ( i ? "weak[1]" : "weak[0]" ) : "strong", spa->strong, spa->weak[0], spa->weak[1]); } rtpp_log_write(RTPP_LOG_INFO, spa->log, "lookup on ports %d/%d, session timer restarted", spa->ports[0], spa->ports[1]); } else { assert(op == UPDATE); rtpp_log_write(RTPP_LOG_INFO, cf->glog, "new session %s, tag %s requested, type %s", call_id, from_tag, weak ? "weak" : "strong"); if (local_addr != NULL) { lia[0] = lia[1] = local_addr; if (lia[0] == NULL) { rtpp_log_write(RTPP_LOG_ERR, spa->log, "can't create listener: %s", t); reply_error(cf, controlfd, &raddr, rlen, cookie, 10); return 0; } } if (create_listener(cf, lia[0], &lport, fds) == -1) { rtpp_log_write(RTPP_LOG_ERR, cf->glog, "can't create listener"); reply_error(cf, controlfd, &raddr, rlen, cookie, 10); return 0; } /* * Session creation. If creation is requested with weak flag, * set weak[0]. */ spa = malloc(sizeof(*spa)); if (spa == NULL) { handle_nomem(cf, controlfd, &raddr, rlen, cookie, 11, ia, fds, spa, spb); return 0; } /* spb is RTCP twin session for this one. */ spb = malloc(sizeof(*spb)); if (spb == NULL) { handle_nomem(cf, controlfd, &raddr, rlen, cookie, 12, ia, fds, spa, spb); return 0; } memset(spa, 0, sizeof(*spa)); memset(spb, 0, sizeof(*spb)); for (i = 0; i < 2; i++) { spa->fds[i] = spb->fds[i] = -1; spa->last_update[i] = 0; spb->last_update[i] = 0; } spa->call_id = strdup(call_id); if (spa->call_id == NULL) { handle_nomem(cf, controlfd, &raddr, rlen, cookie, 13, ia, fds, spa, spb); return 0; } spb->call_id = spa->call_id; spa->tag = strdup(from_tag); if (spa->tag == NULL) { handle_nomem(cf, controlfd, &raddr, rlen, cookie, 14, ia, fds, spa, spb); return 0; } spb->tag = spa->tag; for (i = 0; i < 2; i++) { spa->rrcs[i] = NULL; spb->rrcs[i] = NULL; spa->laddr[i] = lia[i]; spb->laddr[i] = lia[i]; } spa->strong = spa->weak[0] = spa->weak[1] = 0; if (weak) spa->weak[0] = 1; else spa->strong = 1; assert(spa->fds[0] == -1); spa->fds[0] = fds[0]; assert(spb->fds[0] == -1); spb->fds[0] = fds[1]; spa->ports[0] = lport; spb->ports[0] = lport + 1; spa->ttl[0] = cf->max_ttl; spa->ttl[1] = cf->max_ttl; spb->ttl[0] = -1; spb->ttl[1] = -1; spa->log = rtpp_log_open(cf, "rtpproxy", spa->call_id, 0); spb->log = spa->log; spa->rtcp = spb; spb->rtcp = NULL; spa->rtp = NULL; spb->rtp = spa; spa->sridx = spb->sridx = -1; append_session(cf, spa, 0); append_session(cf, spa, 1); append_session(cf, spb, 0); append_session(cf, spb, 1); hash_table_append(cf, spa); cf->sessions_created++; cf->sessions_active++; /* * Each session can consume up to 5 open file descriptors (2 RTP, * 2 RTCP and 1 logging) so that warn user when he is likely to * exceed 80% mark on hard limit. */ if (cf->sessions_active > (cf->nofile_limit.rlim_max * 80 / (100 * 5)) && cf->nofile_limit_warned == 0) { cf->nofile_limit_warned = 1; rtpp_log_write(RTPP_LOG_WARN, cf->glog, "passed 80%% " "threshold on the open file descriptors limit (%d), " "consider increasing the limit using -L command line " "option", (int)cf->nofile_limit.rlim_max); } rtpp_log_write(RTPP_LOG_INFO, spa->log, "new session on a port %d created, " "tag %s", lport, from_tag); if (cf->record_all != 0) { handle_copy(cf, spa, 0, NULL); handle_copy(cf, spa, 1, NULL); } } if (op == UPDATE) { if (cf->timeout_handler.socket_name == NULL && socket_name_u != NULL) rtpp_log_write(RTPP_LOG_ERR, spa->log, "must permit notification socket with -n"); if (spa->timeout_data.notify_tag != NULL) { free(spa->timeout_data.notify_tag); spa->timeout_data.notify_tag = NULL; } if (cf->timeout_handler.socket_name != NULL && socket_name_u != NULL) { if (strcmp(cf->timeout_handler.socket_name, socket_name_u) != 0) { rtpp_log_write(RTPP_LOG_ERR, spa->log, "invalid socket name %s", socket_name_u); socket_name_u = NULL; } else { rtpp_log_write(RTPP_LOG_INFO, spa->log, "setting timeout handler"); spa->timeout_data.handler = &cf->timeout_handler; spa->timeout_data.notify_tag = strdup(notify_tag); } } else if (socket_name_u == NULL && spa->timeout_data.handler != NULL) { spa->timeout_data.handler = NULL; rtpp_log_write(RTPP_LOG_INFO, spa->log, "disabling timeout handler"); } } if (ia[0] != NULL && ia[1] != NULL) { if (spa->addr[pidx] != NULL) spa->last_update[pidx] = dtime; if (spa->rtcp->addr[pidx] != NULL) spa->rtcp->last_update[pidx] = dtime; /* * Unless the address provided by client historically * cannot be trusted and address is different from one * that we recorded update it. */ if (spa->untrusted_addr[pidx] == 0 && !(spa->addr[pidx] != NULL && SA_LEN(ia[0]) == SA_LEN(spa->addr[pidx]) && memcmp(ia[0], spa->addr[pidx], SA_LEN(ia[0])) == 0)) { rtpp_log_write(RTPP_LOG_INFO, spa->log, "pre-filling %s's address " "with %s:%s", (pidx == 0) ? "callee" : "caller", addr, port); if (spa->addr[pidx] != NULL) { if (spa->canupdate[pidx] == 0) { if (spa->prev_addr[pidx] != NULL) free(spa->prev_addr[pidx]); spa->prev_addr[pidx] = spa->addr[pidx]; } else { free(spa->addr[pidx]); } } spa->addr[pidx] = ia[0]; ia[0] = NULL; } if (spa->rtcp->untrusted_addr[pidx] == 0 && !(spa->rtcp->addr[pidx] != NULL && SA_LEN(ia[1]) == SA_LEN(spa->rtcp->addr[pidx]) && memcmp(ia[1], spa->rtcp->addr[pidx], SA_LEN(ia[1])) == 0)) { if (spa->rtcp->addr[pidx] != NULL) { if (spa->rtcp->canupdate[pidx] == 0) { if (spa->rtcp->prev_addr[pidx] != NULL) free(spa->rtcp->prev_addr[pidx]); spa->rtcp->prev_addr[pidx] = spa->rtcp->addr[pidx]; } else { free(spa->rtcp->addr[pidx]); } } spa->rtcp->addr[pidx] = ia[1]; ia[1] = NULL; } } spa->asymmetric[pidx] = spa->rtcp->asymmetric[pidx] = asymmetric; spa->canupdate[pidx] = spa->rtcp->canupdate[pidx] = NOT(asymmetric); if (spa->codecs[pidx] != NULL) { free(spa->codecs[pidx]); spa->codecs[pidx] = NULL; } if (codecs != NULL) spa->codecs[pidx] = strdup(codecs); if (requested_nsamples > 0) { rtpp_log_write(RTPP_LOG_INFO, spa->log, "RTP packets from %s " "will be resized to %d milliseconds", (pidx == 0) ? "callee" : "caller", requested_nsamples / 8); } else if (spa->resizers[pidx].output_nsamples > 0) { rtpp_log_write(RTPP_LOG_INFO, spa->log, "Resizing of RTP " "packets from %s has been disabled", (pidx == 0) ? "callee" : "caller"); } spa->resizers[pidx].output_nsamples = requested_nsamples; for (i = 0; i < 2; i++) if (ia[i] != NULL) free(ia[i]); assert(lport != 0); reply_port(cf, controlfd, &raddr, rlen, cookie, lport, lia); return 0; }
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~ static void zmqdrv_ready_input(ErlDrvData handle, ErlDrvEvent event) { zmq_drv_t *drv = reinterpret_cast<zmq_drv_t*>(handle); zmq_sock_info *si = drv->get_socket_info(event); // I'm not sure if a race condition could develop here or not // So let's assert and see if we ever hit it. Hopefully not. assert(!drv->terminating); assert(NULL != si); assert(si->busy); // unregister event with erlang vm while we work with the socket driver_select(drv->port, si->fd, ERL_DRV_READ, 0); // Finish blocking recv request if input is ready if (si->in_caller) { zmq_msg_t msg; zmq_msg_init(&msg); if (0 == zmq_recv(si->socket, &msg, si->in_flags|ZMQ_NOBLOCK)) { // Unblock the waiting caller's pid by returning result reply_ok_binary(drv->port, si->in_caller, zmq_msg_data(&msg), zmq_msg_size(&msg)); si->in_caller = 0; si->in_flags = 0; } else if (zmq_errno() != EAGAIN) { // Unblock the waiting caller's pid by returning error reply_error(drv->port, si->in_caller, zmq_errno()); si->in_caller = 0; si->in_flags = 0; } // else no input was ready, continue waiting zmq_msg_close(&msg); } // Finish blocking send request if able if (si->out_caller) { if (0 == zmq_send(si->socket, &si->out_msg, si->out_flags|ZMQ_NOBLOCK)) { // Unblock the waiting caller's pid by returning result reply_ok(drv->port, si->out_caller); si->out_caller = 0; si->out_flags = 0; zmq_msg_close(&si->out_msg); } else if (zmq_errno() != EAGAIN) { // Unblock the waiting caller's pid by returning error reply_error(drv->port, si->out_caller, zmq_errno()); si->out_caller = 0; si->out_flags = 0; zmq_msg_close(&si->out_msg); } // else not able to send, continue waiting } // Finish poll request if events available if (si->poll_caller) { uint32_t revents = 0; size_t revents_size = sizeof(revents); if (0 == zmq_getsockopt(si->socket, ZMQ_EVENTS, &revents, &revents_size)) { revents &= si->poll_events; if (0 != revents) { send_events(drv->port, si->poll_caller, revents); si->poll_caller = 0; si->poll_events = 0; } // else no requested events pending, continue waiting } else { // EINVAL will only occur if our getsockopt call was invalid assert(EINVAL != zmq_errno()); // send out of band event error notification send_events(drv->port, si->poll_caller, (uint32_t)ZMQ_POLLERR); si->poll_caller = 0; si->poll_events = 0; } } // reregister event with erlang vm if any pending operations exist if (si->poll_caller || si->in_caller || si->out_caller) { driver_select(drv->port, si->fd, ERL_DRV_READ, 1); } else { si->busy = false; } }
static void from_erlang(ErlDrvData drv_data, char* buf, int n) { ErlDrvPort port = (ErlDrvPort) drv_data; char *tp; int i, j; int slavefd, fd, afd; switch (*buf++) { case 'L': /* Bind and listen to new af_unix socket to path */ fd = fd_listen_path(port, buf); if ((i = find_free_slave()) == -1) { reply_err(port); return; } if (fd > 0) { if ((tp = (char*) driver_alloc(n)) != NULL) { slaves[i].type = LISTEN; slaves[i].path = tp; strcpy(slaves[i].path, buf); slaves[i].fd = fd; reply_int(port, fd); return; } } reply_err(port); return; case 'A': /* accept the af_unix path socket */ fd = get_int32(buf); if ((i = find_slave(fd)) == -1) { reply_err(port); return; } if ((j = find_free_slave()) == -1) { reply_err(port); return; } tp = slaves[i].path; if ((afd = fd_accept_path(port, fd, tp)) > 0) { slaves[j].path = tp; slaves[j].type = ACCEPT; slaves[j].fd = afd; reply_int(port, afd); } else { reply_err(port); } return; case 'l': case 'S': /* send a socket to slave */ fd = get_int32(buf); slavefd = get_int32(buf+4); if (send_fd(&fd, slavefd) == -1) reply_err(port); else { reply_ok(port); } return; case 'C': /* close */ fd = get_int32(buf); if ((i = find_slave(fd)) == -1) { reply_err(port); return; } if (slaves[i].path && slaves[i].type == LISTEN) driver_free(slaves[i].path); slaves[i].path = NULL; close(fd); slaves[i].fd = -1; reply_ok(port); return; } return; }