* Copyright 2006-2015, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
*/
#include "Services.h"
#include <new>
#include <errno.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <Autolock.h>
#include <NetworkAddress.h>
#include <NetworkSettings.h>
#include <NetServer.h>
using namespace std;
using namespace BNetworkKit;
struct service_connection {
struct service* owner;
int socket;
BNetworkServiceAddressSettings address;
int Family() const { return address.Family(); }
int Protocol() const { return address.Protocol(); }
int Type() const { return address.Type(); }
const BNetworkAddress& Address() const { return address.Address(); }
bool operator==(const struct service_connection& other) const;
};
typedef std::vector<service_connection> ConnectionList;
typedef std::vector<std::string> StringList;
struct service {
std::string name;
StringList arguments;
uid_t user;
gid_t group;
ConnectionList connections;
uint32 update;
bool stand_alone;
pid_t process;
~service();
bool operator!=(const struct service& other) const;
bool operator==(const struct service& other) const;
};
bool
service_connection::operator==(const struct service_connection& other) const
{
return address == other.address;
}
service::~service()
{
ConnectionList::const_iterator iterator = connections.begin();
for (; iterator != connections.end(); iterator++) {
const service_connection& connection = *iterator;
close(connection.socket);
}
}
bool
service::operator!=(const struct service& other) const
{
return !(*this == other);
}
bool
service::operator==(const struct service& other) const
{
if (name != other.name
|| arguments.size() != other.arguments.size()
|| connections.size() != other.connections.size()
|| stand_alone != other.stand_alone)
return false;
for(size_t i = 0; i < arguments.size(); i++) {
if (arguments[i] != other.arguments[i])
return false;
}
ConnectionList::const_iterator iterator = connections.begin();
for (; iterator != connections.end(); iterator++) {
const service_connection& connection = *iterator;
ConnectionList::const_iterator otherIterator
= other.connections.begin();
for (; otherIterator != other.connections.end(); otherIterator++) {
if (connection == *otherIterator)
break;
}
if (otherIterator == other.connections.end())
return false;
}
return true;
}
Services::Services(const BMessage& services)
:
fListener(-1),
fUpdate(0),
fMaxSocket(0)
{
if (pipe(&fReadPipe) < 0) {
fReadPipe = -1;
return;
}
fcntl(fReadPipe, F_SETFD, FD_CLOEXEC);
fcntl(fWritePipe, F_SETFD, FD_CLOEXEC);
FD_ZERO(&fSet);
FD_SET(fReadPipe, &fSet);
fMinSocket = fWritePipe + 1;
fMaxSocket = fWritePipe + 1;
_Update(services);
fListener = spawn_thread(_Listener, "services listener", B_NORMAL_PRIORITY,
this);
if (fListener >= B_OK)
resume_thread(fListener);
}
Services::~Services()
{
wait_for_thread(fListener, NULL);
close(fReadPipe);
close(fWritePipe);
while (!fNameMap.empty()) {
_StopService(fNameMap.begin()->second);
}
}
status_t
Services::InitCheck() const
{
return fListener >= B_OK ? B_OK : fListener;
}
void
Services::MessageReceived(BMessage* message)
{
switch (message->what) {
case kMsgUpdateServices:
_Update(*message);
break;
case kMsgIsServiceRunning:
{
const char* name = message->GetString("name");
if (name == NULL)
break;
BMessage reply(B_REPLY);
reply.AddString("name", name);
reply.AddBool("running", fNameMap.find(name) != fNameMap.end());
message->SendReply(&reply);
break;
}
default:
BHandler::MessageReceived(message);
}
}
void
Services::_NotifyListener(bool quit)
{
write(fWritePipe, quit ? "q" : "u", 1);
}
void
Services::_UpdateMinMaxSocket(int socket)
{
if (socket >= fMaxSocket)
fMaxSocket = socket + 1;
if (socket < fMinSocket)
fMinSocket = socket;
}
status_t
Services::_StartService(struct service& service)
{
if (service.stand_alone && service.process == -1) {
status_t status = _LaunchService(service, -1);
if (status == B_OK) {
fNameMap[service.name] = &service;
service.update = fUpdate;
}
return status;
}
bool failed = false;
ConnectionList::iterator iterator = service.connections.begin();
for (; iterator != service.connections.end(); iterator++) {
service_connection& connection = *iterator;
connection.socket = socket(connection.Family(),
connection.Type(), connection.Protocol());
if (connection.socket < 0
|| bind(connection.socket, connection.Address(),
connection.Address().Length()) < 0
|| fcntl(connection.socket, F_SETFD, FD_CLOEXEC) < 0) {
failed = true;
break;
}
if (connection.Type() == SOCK_STREAM
&& listen(connection.socket, 50) < 0) {
failed = true;
break;
}
}
if (failed) {
return errno;
}
fNameMap[service.name] = &service;
service.update = fUpdate;
iterator = service.connections.begin();
for (; iterator != service.connections.end(); iterator++) {
service_connection& connection = *iterator;
fSocketMap[connection.socket] = &connection;
_UpdateMinMaxSocket(connection.socket);
FD_SET(connection.socket, &fSet);
}
_NotifyListener();
printf("Starting service '%s'\n", service.name.c_str());
return B_OK;
}
status_t
Services::_StopService(struct service* service)
{
printf("Stop service '%s'\n", service->name.c_str());
{
ServiceNameMap::iterator iterator = fNameMap.find(service->name);
if (iterator != fNameMap.end())
fNameMap.erase(iterator);
}
if (!service->stand_alone) {
ConnectionList::const_iterator iterator = service->connections.begin();
for (; iterator != service->connections.end(); iterator++) {
const service_connection& connection = *iterator;
ServiceSocketMap::iterator socketIterator
= fSocketMap.find(connection.socket);
if (socketIterator != fSocketMap.end())
fSocketMap.erase(socketIterator);
close(connection.socket);
FD_CLR(connection.socket, &fSet);
}
}
if (service->process != -1) {
printf(" Sending SIGTERM to process %" B_PRId32 "\n", service->process);
kill(-service->process, SIGTERM);
}
delete service;
return B_OK;
}
status_t
Services::_ToService(const BMessage& message, struct service*& service)
{
BNetworkServiceSettings settings(message);
status_t status = settings.InitCheck();
if (status != B_OK)
return status;
if (!settings.IsEnabled())
return B_NAME_NOT_FOUND;
service = new (std::nothrow) ::service;
if (service == NULL)
return B_NO_MEMORY;
service->name = settings.Name();
service->stand_alone = settings.IsStandAlone();
service->process = -1;
for (int32 i = 0; i < settings.CountArguments(); i++)
service->arguments.push_back(settings.ArgumentAt(i));
for (int32 i = 0; i < settings.CountAddresses(); i++) {
const BNetworkServiceAddressSettings& address = settings.AddressAt(i);
service_connection connection;
connection.owner = service;
connection.socket = -1;
connection.address = address;
service->connections.push_back(connection);
}
return B_OK;
}
void
Services::_Update(const BMessage& services)
{
BAutolock locker(fLock);
fUpdate++;
BMessage message;
for (int32 index = 0; services.FindMessage("service", index,
&message) == B_OK; index++) {
struct service* service;
if (_ToService(message, service) != B_OK)
continue;
ServiceNameMap::iterator iterator = fNameMap.find(service->name);
if (iterator == fNameMap.end()) {
printf("New service %s\n", service->name.c_str());
_StartService(*service);
} else {
if (*service != *iterator->second) {
printf("Restart service %s\n", service->name.c_str());
_StopService(iterator->second);
_StartService(*service);
} else
iterator->second->update = fUpdate;
}
}
ServiceNameMap::iterator iterator = fNameMap.begin();
while (iterator != fNameMap.end()) {
struct service* service = iterator->second;
iterator++;
if (service->update != fUpdate) {
_StopService(service);
}
}
}
status_t
Services::_LaunchService(struct service& service, int socket)
{
printf("Launch service: %s\n", service.arguments[0].c_str());
if (socket != -1 && fcntl(socket, F_SETFD, 0) < 0) {
return errno;
}
pid_t child = fork();
if (child == 0) {
setsid();
if (socket != -1) {
dup2(socket, STDIN_FILENO);
dup2(socket, STDOUT_FILENO);
dup2(socket, STDERR_FILENO);
close(socket);
}
const char** args = (const char**)malloc(
(service.arguments.size() + 1) * sizeof(char*));
if (args == NULL)
exit(1);
for (size_t i = 0; i < service.arguments.size(); i++) {
args[i] = service.arguments[i].c_str();
}
args[service.arguments.size()] = NULL;
if (execv(service.arguments[0].c_str(), (char* const*)args) < 0) {
free(args);
exit(1);
}
} else {
if (socket != -1)
close(socket);
if (child < 0) {
fprintf(stderr, "Could not start service %s\n",
service.name.c_str());
} else if (service.stand_alone)
service.process = child;
}
return B_OK;
}
status_t
Services::_Listener()
{
while (true) {
fLock.Lock();
fd_set set = fSet;
fLock.Unlock();
if (select(fMaxSocket, &set, NULL, NULL, NULL) < 0) {
if (errno == EINTR)
continue;
snooze(1000000LL);
}
if (FD_ISSET(fReadPipe, &set)) {
char command;
if (read(fReadPipe, &command, 1) == 1 && command == 'q')
break;
}
BAutolock locker(fLock);
for (int i = fMinSocket; i < fMaxSocket; i++) {
if (!FD_ISSET(i, &set))
continue;
ServiceSocketMap::iterator iterator = fSocketMap.find(i);
if (iterator == fSocketMap.end())
continue;
struct service_connection& connection = *iterator->second;
int socket;
if (connection.Type() == SOCK_STREAM) {
int value = 1;
ioctl(i, FIONBIO, &value, sizeof(value));
socket = accept(connection.socket, NULL, NULL);
value = 0;
ioctl(i, FIONBIO, &value, sizeof(value));
if (socket < 0)
continue;
} else
socket = connection.socket;
_LaunchService(*connection.owner, socket);
}
}
return B_OK;
}
status_t
Services::_Listener(void* _self)
{
Services* self = (Services*)_self;
return self->_Listener();
}