-
Ghislain MARY authored24c2a4cf
/*
daemon.cc
Copyright (C) 2016 Belledonne Communications, Grenoble, France
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <cstdio>
#ifndef _WIN32
#include <sys/ioctl.h>
#endif
#include <iostream>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <functional>
#include <limits>
#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
#ifndef _WIN32
#include <poll.h>
#endif
#include "daemon.h"
#include "commands/adaptive-jitter-compensation.h"
#include "commands/jitterbuffer.h"
#include "commands/answer.h"
#include "commands/audio-codec-get.h"
#include "commands/audio-codec-move.h"
#include "commands/audio-codec-set.h"
#include "commands/audio-codec-toggle.h"
#include "commands/audio-stream-start.h"
#include "commands/audio-stream-stop.h"
#include "commands/audio-stream-stats.h"
#include "commands/auth-infos-clear.h"
#include "commands/call.h"
#include "commands/call-stats.h"
#include "commands/call-status.h"
#include "commands/call-pause.h"
#include "commands/call-mute.h"
#include "commands/call-resume.h"
#include "commands/video.h"
#include "commands/call-transfer.h"
#include "commands/conference.h"
#include "commands/contact.h"
#include "commands/dtmf.h"
#include "commands/firewall-policy.h"
#include "commands/help.h"
#include "commands/ipv6.h"
#include "commands/media-encryption.h"
#include "commands/msfilter-add-fmtp.h"
#include "commands/play-wav.h"
#include "commands/pop-event.h"
#include "commands/port.h"
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
#include "commands/ptime.h"
#include "commands/register.h"
#include "commands/register-info.h"
#include "commands/register-status.h"
#include "commands/terminate.h"
#include "commands/unregister.h"
#include "commands/quit.h"
#include "commands/configcommand.h"
#include "commands/netsim.h"
#include "commands/cn.h"
#include "commands/version.h"
#include "private.h"
using namespace std;
#define INT_TO_VOIDPTR(i) ((void*)(intptr_t)(i))
#define VOIDPTR_TO_INT(p) ((int)(intptr_t)(p))
#ifndef WIN32
#else
#include <windows.h>
void usleep(int waitTime) {
Sleep(waitTime/1000);
}
#endif
#ifdef HAVE_READLINE
#define LICENCE_GPL
#else
#define LICENCE_COMMERCIAL
#endif
const char * const ice_state_str[] = {
"Not activated", /* LinphoneIceStateNotActivated */
"Failed", /* LinphoneIceStateFailed */
"In progress", /* LinphoneIceStateInProgress */
"Host connection", /* LinphoneIceStateHostConnection */
"Reflexive connection", /* LinphoneIceStateReflexiveConnection */
"Relayed connection" /* LinphoneIceStateRelayConnection */
};
void *Daemon::iterateThread(void *arg) {
Daemon *daemon = (Daemon *) arg;
while (daemon->mRunning) {
ms_mutex_lock(&daemon->mMutex);
daemon->iterate();
ms_mutex_unlock(&daemon->mMutex);
usleep(20000);
}
return 0;
}
EventResponse::EventResponse(Daemon *daemon, LinphoneCall *call, LinphoneCallState state) {
ostringstream ostr;
char *remote = linphone_call_get_remote_address_as_string(call);
ostr << "Event-type: call-state-changed\nEvent: " << linphone_call_state_to_string(state) << "\n";
ostr << "From: " << remote << "\n";
ostr << "Id: " << daemon->updateCallId(call) << "\n";
setBody(ostr.str().c_str());
ms_free(remote);
}
DtmfResponse::DtmfResponse(Daemon *daemon, LinphoneCall *call, int dtmf) {
ostringstream ostr;
char *remote = linphone_call_get_remote_address_as_string(call);
ostr << "Event-type: receiving-tone\nTone: " << (char) dtmf << "\n";
ostr << "From: " << remote << "\n";
ostr << "Id: " << daemon->updateCallId(call) << "\n";
setBody(ostr.str().c_str());
ms_free(remote);
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
}
static ostream &printCallStatsHelper(ostream &ostr, const LinphoneCallStats *stats, const string &prefix) {
ostr << prefix << "ICE state: " << ice_state_str[linphone_call_stats_get_ice_state(stats)] << "\n";
ostr << prefix << "RoundTripDelay: " << linphone_call_stats_get_round_trip_delay(stats) << "\n";
// ostr << prefix << "Jitter: " << stats->jitter_stats.jitter << "\n";
// ostr << prefix << "MaxJitter: " << stats->jitter_stats.max_jitter << "\n";
// ostr << prefix << "SumJitter: " << stats->jitter_stats.sum_jitter << "\n";
// ostr << prefix << "MaxJitterTs: " << stats->jitter_stats.max_jitter_ts << "\n";
ostr << prefix << "JitterBufferSizeMs: " << linphone_call_stats_get_jitter_buffer_size_ms(stats) << "\n";
ostr << prefix << "Received-InterarrivalJitter: " << linphone_call_stats_get_receiver_interarrival_jitter(stats) << "\n";
ostr << prefix << "Received-FractionLost: " << linphone_call_stats_get_receiver_loss_rate(stats) << "\n";
ostr << prefix << "Sent-InterarrivalJitter: " << linphone_call_stats_get_sender_interarrival_jitter(stats) << "\n";
ostr << prefix << "Sent-FractionLost: " << linphone_call_stats_get_sender_loss_rate(stats) << "\n";
return ostr;
}
CallStatsResponse::CallStatsResponse(Daemon *daemon, LinphoneCall *call, const LinphoneCallStats *stats, bool event) {
const LinphoneCallParams *callParams = linphone_call_get_current_params(call);
const char *prefix = "";
ostringstream ostr;
if (event) {
ostr << "Event-type: call-stats\n";
ostr << "Id: " << daemon->updateCallId(call) << "\n";
ostr << "Type: ";
if (linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) {
ostr << "Audio";
} else {
ostr << "Video";
}
ostr << "\n";
} else {
prefix = ((linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) ? "Audio-" : "Video-");
}
printCallStatsHelper(ostr, stats, prefix);
if (linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) {
const PayloadType *audioCodec = linphone_call_params_get_used_audio_codec(callParams);
ostr << PayloadTypeResponse(linphone_call_get_core(call), audioCodec, -1, prefix, false).getBody() << "\n";
} else {
const PayloadType *videoCodec = linphone_call_params_get_used_video_codec(callParams);
ostr << PayloadTypeResponse(linphone_call_get_core(call), videoCodec, -1, prefix, false).getBody() << "\n";
}
setBody(ostr.str().c_str());
}
AudioStreamStatsResponse::AudioStreamStatsResponse(Daemon* daemon, AudioStream* stream,
const LinphoneCallStats *stats, bool event) {
const char *prefix = "";
ostringstream ostr;
if (event) {
ostr << "Event-type: audio-stream-stats\n";
ostr << "Id: " << daemon->updateAudioStreamId(stream) << "\n";
ostr << "Type: ";
if (linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) {
ostr << "Audio";
} else {
ostr << "Video";
}
ostr << "\n";
} else {
prefix = ((linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) ? "Audio-" : "Video-");
}
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
printCallStatsHelper(ostr, stats, prefix);
setBody(ostr.str().c_str());
}
PayloadTypeResponse::PayloadTypeResponse(LinphoneCore *core, const PayloadType *payloadType, int index, const string &prefix, bool enabled_status) {
ostringstream ostr;
if (payloadType != NULL) {
if (index >= 0)
ostr << prefix << "Index: " << index << "\n";
ostr << prefix << "Payload-type-number: " << payload_type_get_number(payloadType) << "\n";
ostr << prefix << "Clock-rate: " << payloadType->clock_rate << "\n";
ostr << prefix << "Bitrate: " << payloadType->normal_bitrate << "\n";
ostr << prefix << "Mime: " << payloadType->mime_type << "\n";
ostr << prefix << "Channels: " << payloadType->channels << "\n";
ostr << prefix << "Recv-fmtp: " << ((payloadType->recv_fmtp) ? payloadType->recv_fmtp : "") << "\n";
ostr << prefix << "Send-fmtp: " << ((payloadType->send_fmtp) ? payloadType->send_fmtp : "") << "\n";
if (enabled_status)
ostr << prefix << "Enabled: " << (linphone_core_payload_type_enabled(core, payloadType) == TRUE ? "true" : "false") << "\n";
setBody(ostr.str().c_str());
}
}
PayloadTypeParser::PayloadTypeParser(LinphoneCore *core, const string &mime_type, bool accept_all) : mAll(false), mSuccesful(true), mPayloadType(NULL),mPosition(-1){
int number=-1;
if (accept_all && (mime_type.compare("ALL") == 0)) {
mAll = true;
return;
}
istringstream ist(mime_type);
ist >> number;
if (ist.fail()) {
char type[64]={0};
int rate, channels;
if (sscanf(mime_type.c_str(), "%63[^/]/%u/%u", type, &rate, &channels) != 3) {
mSuccesful = false;
return;
}
mPayloadType = linphone_core_find_payload_type(core, type, rate, channels);
if (mPayloadType) mPosition=bctbx_list_index(linphone_core_get_audio_codecs(core), mPayloadType);
}else if (number!=-1){
const bctbx_list_t *elem;
for(elem=linphone_core_get_audio_codecs(core);elem!=NULL;elem=elem->next){
if (number==payload_type_get_number((PayloadType*)elem->data)){
mPayloadType=(PayloadType*)elem->data;
break;
}
}
}
}
DaemonCommandExample::DaemonCommandExample(const string& command, const string& output)
: mCommand(command), mOutput(output) {}
DaemonCommand::DaemonCommand(const string& name, const string& proto, const string& description) :
mName(name), mProto(proto), mDescription(description) {
}
void DaemonCommand::addExample(const DaemonCommandExample *example) {
mExamples.push_back(example);
}
const string DaemonCommand::getHelp() const {
ostringstream ost;
ost << getProto() << endl << endl;
ost << "Description:" << endl << getDescription() << endl << endl;
list<const DaemonCommandExample*> examples = getExamples();
int c = 1;
for (list<const DaemonCommandExample*>::iterator it = examples.begin(); it != examples.end(); ++it, ++c) {
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
ost << "Example " << c << ":" << endl;
ost << ">" << (*it)->getCommand() << endl;
ost << (*it)->getOutput() << endl;
ost << endl;
}
return ost.str();
}
bool DaemonCommand::matches(const string& name) const {
return mName.compare(name) == 0;
}
Daemon::Daemon(const char *config_path, const char *factory_config_path, const char *log_file, const char *pipe_name, bool display_video, bool capture_video) :
mLSD(0), mLogFile(NULL), mAutoVideo(0), mCallIds(0), mProxyIds(0), mAudioStreamIds(0) {
ms_mutex_init(&mMutex, NULL);
mServerFd = (ortp_pipe_t)-1;
mChildFd = (ortp_pipe_t)-1;
if (pipe_name == NULL) {
#ifdef HAVE_READLINE
const char *homedir = getenv("HOME");
rl_readline_name = (char*)"daemon";
if (homedir == NULL)
homedir = ".";
mHistfile = string(homedir) + string("/.linphone_history");
read_history(mHistfile.c_str());
setlinebuf(stdout);
#endif
} else {
mServerFd = ortp_server_pipe_create(pipe_name);
#ifndef _WIN32
listen(mServerFd, 2);
fprintf(stdout, "Server unix socket created, name=%s fd=%i\n", pipe_name, (int)mServerFd);
#else
fprintf(stdout, "Named pipe created, name=%s fd=%p\n", pipe_name, mServerFd);
#endif
}
if (log_file != NULL) {
mLogFile = fopen(log_file, "a+");
linphone_core_enable_logs(mLogFile);
} else {
linphone_core_disable_logs();
}
LinphoneCoreVTable vtable;
memset(&vtable, 0, sizeof(vtable));
vtable.call_state_changed = callStateChanged;
vtable.call_stats_updated = callStatsUpdated;
vtable.dtmf_received = dtmfReceived;
mLc = linphone_core_new(&vtable, config_path, factory_config_path, this);
linphone_core_set_user_data(mLc, this);
linphone_core_enable_video_capture(mLc,capture_video);
linphone_core_enable_video_display(mLc,display_video);
for(const bctbx_list_t *proxy = linphone_core_get_proxy_config_list(mLc); proxy != NULL; proxy = bctbx_list_next(proxy)) {
updateProxyId((LinphoneProxyConfig *)bctbx_list_get_data(proxy));
}
initCommands();
mUseStatsEvents=true;
}
const list<DaemonCommand*> &Daemon::getCommandList() const {
return mCommands;
}
LinphoneCore *Daemon::getCore() {
return mLc;
}
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
LinphoneSoundDaemon *Daemon::getLSD() {
return mLSD;
}
int Daemon::updateCallId(LinphoneCall *call) {
int val = VOIDPTR_TO_INT(linphone_call_get_user_data(call));
if (val == 0) {
linphone_call_set_user_data(call, INT_TO_VOIDPTR(++mCallIds));
return mCallIds;
}
return val;
}
LinphoneCall *Daemon::findCall(int id) {
const bctbx_list_t *elem = linphone_core_get_calls(mLc);
for (; elem != NULL; elem = elem->next) {
LinphoneCall *call = (LinphoneCall *) elem->data;
if (VOIDPTR_TO_INT(linphone_call_get_user_data(call)) == id)
return call;
}
return NULL;
}
int Daemon::updateProxyId(LinphoneProxyConfig *cfg) {
int val = VOIDPTR_TO_INT(linphone_proxy_config_get_user_data(cfg));
if (val == 0) {
linphone_proxy_config_set_user_data(cfg, INT_TO_VOIDPTR(++mProxyIds));
return mProxyIds;
}
return val;
}
LinphoneProxyConfig *Daemon::findProxy(int id) {
const bctbx_list_t *elem = linphone_core_get_proxy_config_list(mLc);
for (; elem != NULL; elem = elem->next) {
LinphoneProxyConfig *proxy = (LinphoneProxyConfig *) elem->data;
if (VOIDPTR_TO_INT(linphone_proxy_config_get_user_data(proxy)) == id)
return proxy;
}
return NULL;
}
LinphoneAuthInfo *Daemon::findAuthInfo(int id) {
const bctbx_list_t *elem = linphone_core_get_auth_info_list(mLc);
if (elem == NULL || id < 1 || (unsigned int)id > bctbx_list_size(elem)) {
return NULL;
}
while (id > 1) {
elem = elem->next;
--id;
}
return (LinphoneAuthInfo *) elem->data;
}
int Daemon::updateAudioStreamId(AudioStream *audio_stream) {
for (std::map<int, AudioStreamAndOther*>::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) {
if (it->second->stream == audio_stream)
return it->first;
}
++mAudioStreamIds;
mAudioStreams.insert(make_pair(mAudioStreamIds, new AudioStreamAndOther(audio_stream)));
return mAudioStreamIds;
}
AudioStreamAndOther *Daemon::findAudioStreamAndOther(int id) {
std::map<int, AudioStreamAndOther*>::iterator it = mAudioStreams.find(id);
if (it != mAudioStreams.end())
return it->second;
return NULL;
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
}
AudioStream *Daemon::findAudioStream(int id) {
std::map<int, AudioStreamAndOther*>::iterator it = mAudioStreams.find(id);
if (it != mAudioStreams.end())
return it->second->stream;
return NULL;
}
void Daemon::removeAudioStream(int id) {
std::map<int, AudioStreamAndOther*>::iterator it = mAudioStreams.find(id);
if (it != mAudioStreams.end()) {
mAudioStreams.erase(it);
delete(it->second);
}
}
static bool compareCommands(const DaemonCommand *command1, const DaemonCommand *command2) {
return (command1->getProto() < command2->getProto());
}
void Daemon::initCommands() {
mCommands.push_back(new RegisterCommand());
mCommands.push_back(new ContactCommand());
mCommands.push_back(new RegisterStatusCommand());
mCommands.push_back(new RegisterInfoCommand());
mCommands.push_back(new UnregisterCommand());
mCommands.push_back(new AuthInfosClearCommand());
mCommands.push_back(new CallCommand());
mCommands.push_back(new TerminateCommand());
mCommands.push_back(new DtmfCommand());
mCommands.push_back(new PlayWavCommand());
mCommands.push_back(new PopEventCommand());
mCommands.push_back(new AnswerCommand());
mCommands.push_back(new CallStatusCommand());
mCommands.push_back(new CallStatsCommand());
mCommands.push_back(new CallPauseCommand());
mCommands.push_back(new CallMuteCommand());
mCommands.push_back(new CallResumeCommand());
mCommands.push_back(new CallTransferCommand());
mCommands.push_back(new Video());
mCommands.push_back(new VideoSource());
mCommands.push_back(new AutoVideo());
mCommands.push_back(new ConferenceCommand());
mCommands.push_back(new AudioCodecGetCommand());
mCommands.push_back(new AudioCodecEnableCommand());
mCommands.push_back(new AudioCodecDisableCommand());
mCommands.push_back(new AudioCodecMoveCommand());
mCommands.push_back(new AudioCodecSetCommand());
mCommands.push_back(new AudioStreamStartCommand());
mCommands.push_back(new AudioStreamStopCommand());
mCommands.push_back(new AudioStreamStatsCommand());
mCommands.push_back(new MSFilterAddFmtpCommand());
mCommands.push_back(new PtimeCommand());
mCommands.push_back(new IPv6Command());
mCommands.push_back(new FirewallPolicyCommand());
mCommands.push_back(new MediaEncryptionCommand());
mCommands.push_back(new PortCommand());
mCommands.push_back(new AdaptiveBufferCompensationCommand());
mCommands.push_back(new JitterBufferCommand());
mCommands.push_back(new JitterBufferResetCommand());
mCommands.push_back(new VersionCommand());
mCommands.push_back(new QuitCommand());
mCommands.push_back(new HelpCommand());
mCommands.push_back(new ConfigGetCommand());
mCommands.push_back(new ConfigSetCommand());
mCommands.push_back(new NetsimCommand());
mCommands.push_back(new CNCommand());
mCommands.sort(compareCommands);
}
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
void Daemon::uninitCommands() {
while (!mCommands.empty()) {
delete mCommands.front();
mCommands.pop_front();
}
}
bool Daemon::pullEvent() {
bool status = false;
ostringstream ostr;
if (!mEventQueue.empty()) {
Response *r = mEventQueue.front();
mEventQueue.pop();
ostr << r->getBody() << "\n";
delete r;
status = true;
}
ostr << "Size: " << mEventQueue.size() << "\n";
sendResponse(Response(ostr.str().c_str(), Response::Ok));
return status;
}
void Daemon::callStateChanged(LinphoneCall *call, LinphoneCallState state, const char *msg) {
mEventQueue.push(new EventResponse(this, call, state));
}
void Daemon::callStatsUpdated(LinphoneCall *call, const LinphoneCallStats *stats) {
if (mUseStatsEvents) {
/* don't queue periodical updates (3 per seconds for just bandwidth updates) */
if (!(_linphone_call_stats_get_updated(stats) & LINPHONE_CALL_STATS_PERIODICAL_UPDATE)){
mEventQueue.push(new CallStatsResponse(this, call, stats, true));
}
}
}
void Daemon::dtmfReceived(LinphoneCall *call, int dtmf) {
mEventQueue.push(new DtmfResponse(this, call, dtmf));
}
void Daemon::callStateChanged(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState state, const char *msg) {
Daemon *app = (Daemon*) linphone_core_get_user_data(lc);
app->callStateChanged(call, state, msg);
}
void Daemon::callStatsUpdated(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallStats *stats) {
Daemon *app = (Daemon*) linphone_core_get_user_data(lc);
app->callStatsUpdated(call, stats);
}
void Daemon::dtmfReceived(LinphoneCore *lc, LinphoneCall *call, int dtmf) {
Daemon *app = (Daemon*) linphone_core_get_user_data(lc);
app->dtmfReceived(call, dtmf);
}
void Daemon::iterateStreamStats() {
for (std::map<int, AudioStreamAndOther*>::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) {
OrtpEvent *ev;
while (it->second->queue && (NULL != (ev=ortp_ev_queue_get(it->second->queue)))){
OrtpEventType evt=ortp_event_get_type(ev);
if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED || evt == ORTP_EVENT_RTCP_PACKET_EMITTED) {
linphone_call_stats_fill(it->second->stats, &it->second->stream->ms, ev);
if (mUseStatsEvents) mEventQueue.push(new AudioStreamStatsResponse(this,
it->second->stream, it->second->stats, true));
}
ortp_event_destroy(ev);
}
}
}
void Daemon::iterate() {
linphone_core_iterate(mLc);
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
iterateStreamStats();
if (mChildFd == (ortp_pipe_t)-1) {
if (!mEventQueue.empty()) {
Response *r = mEventQueue.front();
mEventQueue.pop();
fprintf(stdout, "%s\n", r->getBody().c_str());
fflush(stdout);
delete r;
}
}
}
void Daemon::execCommand(const string &command) {
istringstream ist(command);
string name;
ist >> name;
stringbuf argsbuf;
ist.get(argsbuf);
string args = argsbuf.str();
if (!args.empty() && (args[0] == ' ')) args.erase(0, 1);
list<DaemonCommand*>::iterator it = find_if(mCommands.begin(), mCommands.end(), bind2nd(mem_fun(&DaemonCommand::matches), name));
if (it != mCommands.end()) {
ms_mutex_lock(&mMutex);
(*it)->exec(this, args);
ms_mutex_unlock(&mMutex);
} else {
sendResponse(Response("Unknown command."));
}
}
void Daemon::sendResponse(const Response &resp) {
string buf = resp.toBuf();
if (mChildFd != (ortp_pipe_t)-1) {
if (ortp_pipe_write(mChildFd, (uint8_t *)buf.c_str(), (int)buf.size()) == -1) {
ms_error("Fail to write to pipe: %s", strerror(errno));
}
} else {
cout << buf << flush;
}
}
string Daemon::readPipe() {
char buffer[32768];
memset(buffer, '\0', sizeof(buffer));
#ifdef _WIN32
if (mChildFd == (ortp_pipe_t)-1) {
mChildFd = ortp_server_pipe_accept_client(mServerFd);
ms_message("Client accepted");
}
if (mChildFd != (ortp_pipe_t)-1) {
int ret = ortp_pipe_read(mChildFd, (uint8_t *)buffer, sizeof(buffer));
if (ret == -1) {
ms_error("Fail to read from pipe: %s", strerror(errno));
mChildFd = (ortp_pipe_t)-1;
} else {
if (ret == 0) {
ms_message("Client disconnected");
mChildFd = (ortp_pipe_t)-1;
return "";
}
buffer[ret] = '\0';
return buffer;
}
}
#else
struct pollfd pfd[2];
int nfds = 1;
memset(&pfd[0], 0, sizeof(pfd));
if (mServerFd != (ortp_pipe_t)-1) {
pfd[0].events = POLLIN;
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
pfd[0].fd = mServerFd;
}
if (mChildFd != (ortp_pipe_t)-1) {
pfd[1].events = POLLIN;
pfd[1].fd = mChildFd;
nfds++;
}
int err = poll(pfd, nfds, 50);
if (err > 0) {
if (mServerFd != (ortp_pipe_t)-1 && (pfd[0].revents & POLLIN)) {
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
int childfd = accept(mServerFd, (struct sockaddr*) &addr, &addrlen);
if (childfd != -1) {
if (mChildFd != (ortp_pipe_t)-1) {
ms_error("Cannot accept two client at the same time");
close(childfd);
} else {
mChildFd = (ortp_pipe_t)childfd;
return "";
}
}
}
if (mChildFd != (ortp_pipe_t)-1 && (pfd[1].revents & POLLIN)) {
int ret;
if ((ret = ortp_pipe_read(mChildFd, (uint8_t *)buffer, sizeof(buffer))) == -1) {
ms_error("Fail to read from pipe: %s", strerror(errno));
} else {
if (ret == 0) {
ms_message("Client disconnected");
ortp_server_pipe_close_client(mChildFd);
mChildFd = (ortp_pipe_t)-1;
return "";
}
buffer[ret] = '\0';
return buffer;
}
}
}
#endif
return "";
}
void Daemon::dumpCommandsHelp() {
int cols = 80;
#ifdef TIOCGSIZE
struct ttysize ts;
ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
cols = ts.ts_cols;
#elif defined(TIOCGWINSZ)
struct winsize ts;
ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
cols = ts.ws_col;
#endif
cout << endl;
for (list<DaemonCommand*>::iterator it = mCommands.begin(); it != mCommands.end(); ++it) {
cout << setfill('-') << setw(cols) << "-" << endl << endl;
cout << (*it)->getHelp();
}
}
static string htmlEscape(const string &orig){
string ret=orig;
size_t pos;
while(1){
pos=ret.find('<');
if (pos!=string::npos){
ret.erase(pos,1);
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
ret.insert(pos,"<");
continue;
}
pos=ret.find('>');
if (pos!=string::npos){
ret.erase(pos,1);
ret.insert(pos,">");
continue;
}
break;
}
while(1){
pos=ret.find('\n');
if (pos!=string::npos){
ret.erase(pos,1);
ret.insert(pos,"<br>");
continue;
}
break;
}
return ret;
}
void Daemon::dumpCommandsHelpHtml(){
cout << endl;
cout << "<!DOCTYPE html><html><body>"<<endl;
cout << "<h1>List of linphone-daemon commands.</h1>"<<endl;
for (list<DaemonCommand*>::iterator it = mCommands.begin(); it != mCommands.end(); ++it) {
cout<<"<h2>"<<htmlEscape((*it)->getProto())<<"</h2>"<<endl;
cout<<"<h3>"<<"Description"<<"</h3>"<<endl;
cout<<"<p>"<<htmlEscape((*it)->getDescription())<<"</p>"<<endl;
cout<<"<h3>"<<"Examples"<<"</h3>"<<endl;
const std::list<const DaemonCommandExample*> &examples=(*it)->getExamples();
cout<<"<p><i>";
for(list<const DaemonCommandExample*>::const_iterator ex_it=examples.begin();ex_it!=examples.end();++ex_it){
cout<<"<b>"<<htmlEscape("Linphone-daemon>")<<htmlEscape((*ex_it)->getCommand())<<"</b><br>"<<endl;
cout<<htmlEscape((*ex_it)->getOutput())<<"<br>"<<endl;
cout<<"<br><br>";
}
cout<<"</i></p>"<<endl;
}
cout << "</body></html>"<<endl;
}
static void printHelp() {
cout << "daemon-linphone [<options>]" << endl <<
#if defined(LICENCE_GPL) || defined(LICENCE_COMMERCIAL)
"Licence: "
#ifdef LICENCE_GPL
"GPL"
#endif
#ifdef LICENCE_COMMERCIAL
"Commercial"
#endif
<< endl <<
#endif
"where options are :" << endl <<
"\t--help Print this notice." << endl <<
"\t--dump-commands-help Dump the help of every available commands." << endl <<
"\t--dump-commands-html-help Dump the help of every available commands." << endl <<
"\t--pipe <pipename> Create an unix server socket to receive commands." << endl <<
"\t--log <path> Supply a file where the log will be saved." << endl <<
"\t--factory-config <path> Supply a readonly linphonerc style config file to start with." << endl <<
"\t--config <path> Supply a linphonerc style config file to start with." << endl <<
"\t--disable-stats-events Do not automatically raise RTP statistics events." << endl <<
"\t--enable-lsd Use the linphone sound daemon." << endl <<
"\t-C Enable video capture." << endl <<
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
"\t-D Enable video display." << endl;
}
void Daemon::startThread() {
ms_thread_create(&this->mThread, NULL, Daemon::iterateThread, this);
}
#ifdef max
#undef max
#endif
string Daemon::readLine(const string& prompt, bool *eof) {
*eof=false;
#ifdef HAVE_READLINE
return readline(prompt.c_str());
#else
if (cin.eof()) {
*eof=true;
return "";
}
cout << prompt;
stringbuf outbuf;
cin.get(outbuf);
cin.clear();
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return outbuf.str();
#endif
}
int Daemon::run() {
const string prompt("daemon-linphone>");
mRunning = true;
startThread();
while (mRunning) {
string line;
bool eof=false;
if (mServerFd == (ortp_pipe_t)-1) {
line = readLine(prompt, &eof);
if (!line.empty()) {
#ifdef HAVE_READLINE
add_history(line.c_str());
#endif
}
} else {
line = readPipe();
}
if (!line.empty()) {
execCommand(line);
}
if (eof && mRunning) {
mRunning = false; // ctrl+d
cout << "Quitting..." << endl;
}
}
stopThread();
return 0;
}
void Daemon::stopThread() {
void *ret;
ms_thread_join(mThread, &ret);
}
void Daemon::quit() {
mRunning = false;
}
void Daemon::enableStatsEvents(bool enabled){
mUseStatsEvents=enabled;
}
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
void Daemon::enableLSD(bool enabled) {
if (mLSD) linphone_sound_daemon_destroy(mLSD);
linphone_core_use_sound_daemon(mLc, NULL);
if (enabled) {
mLSD = linphone_sound_daemon_new(mLc->factory,NULL, 44100, 1);
linphone_core_use_sound_daemon(mLc, mLSD);
}
}
Daemon::~Daemon() {
uninitCommands();
for (std::map<int, AudioStreamAndOther *>::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) {
audio_stream_stop(it->second->stream);
}
enableLSD(false);
linphone_core_unref(mLc);
if (mChildFd != (ortp_pipe_t)-1) {
ortp_server_pipe_close_client(mChildFd);
}
if (mServerFd != (ortp_pipe_t)-1) {
ortp_server_pipe_close(mServerFd);
}
if (mLogFile != NULL) {
linphone_core_enable_logs(NULL);
fclose(mLogFile);
}
ms_mutex_destroy(&mMutex);
#ifdef HAVE_READLINE
stifle_history(30);
write_history(mHistfile.c_str());
#endif
}
int main(int argc, char *argv[]) {
const char *config_path = NULL;
const char *factory_config_path = NULL;
const char *pipe_name = NULL;
const char *log_file = NULL;
bool capture_video = false;
bool display_video = false;
bool stats_enabled = true;
bool lsd_enabled = false;
int i;
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--help") == 0) {
printHelp();
return 0;
} else if (strcmp(argv[i], "--dump-commands-help") == 0) {
Daemon app(NULL, NULL, NULL, NULL, false, false);
app.dumpCommandsHelp();
return 0;
}else if (strcmp(argv[i], "--dump-commands-html-help") == 0) {
Daemon app(NULL, NULL, NULL, NULL, false, false);
app.dumpCommandsHelpHtml();
return 0;
} else if (strcmp(argv[i], "--pipe") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "no pipe name specify after --pipe\n");
return -1;
}
pipe_name = argv[++i];
stats_enabled = false;
} else if (strcmp(argv[i], "--factory-config") == 0) {
if (i + 1 >= argc) {
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
fprintf(stderr, "no file specify after --factory-config\n");
return -1;
}
factory_config_path = argv[i + 1];
i++;
} else if (strcmp(argv[i], "--config") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "no file specify after --config\n");
return -1;
}
config_path = argv[i + 1];
i++;
} else if (strcmp(argv[i], "--log") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "no file specify after --log\n");
return -1;
}
log_file = argv[i + 1];
i++;
} else if (strcmp(argv[i], "-C") == 0) {
capture_video = true;
} else if (strcmp(argv[i], "-D") == 0) {
display_video = true;
}else if (strcmp(argv[i],"--disable-stats-events")==0){
stats_enabled = false;
} else if (strcmp(argv[i], "--enable-lsd") == 0) {
lsd_enabled = true;
}
else{
fprintf(stderr, "Unrecognized option : %s", argv[i]);
}
}
Daemon app(config_path, factory_config_path, log_file, pipe_name, display_video, capture_video);
app.enableStatsEvents(stats_enabled);
app.enableLSD(lsd_enabled);
return app.run();
}