socket.c revision e99e38bbdcca3fe5956823bdb3d38544ccf93221
/*-*- Mode: C; c-basic-offset: 8 -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
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.
systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include "unit.h"
#include "socket.h"
#include "log.h"
#include "load-dropin.h"
#include "load-fragment.h"
#include "strv.h"
#include "unit-name.h"
#include "dbus-socket.h"
[SOCKET_DEAD] = UNIT_INACTIVE,
};
static void socket_init(Unit *u) {
assert(u);
s->directory_mode = 0755;
s->socket_mode = 0666;
}
static void socket_unwatch_control_pid(Socket *s) {
assert(s);
if (s->control_pid <= 0)
return;
s->control_pid = 0;
}
static void socket_done(Unit *u) {
SocketPort *p;
assert(s);
while ((p = s->ports)) {
if (p->fd >= 0) {
close_nointr_nofail(p->fd);
}
free(p);
}
s->control_command = NULL;
free(s->bind_to_device);
s->bind_to_device = NULL;
unit_unwatch_timer(u, &s->timer_watch);
}
static bool have_non_accept_socket(Socket *s) {
SocketPort *p;
assert(s);
if (!s->accept)
return true;
if (p->type != SOCKET_SOCKET)
return true;
if (!socket_address_can_accept(&p->address))
return true;
}
return false;
}
static int socket_verify(Socket *s) {
assert(s);
return 0;
if (!s->ports) {
return -EINVAL;
}
return 0;
}
SocketPort *p;
assert(s);
if (p->type == SOCKET_SOCKET) {
return true;
} else {
return true;
}
}
return false;
}
int r;
assert(s);
assert(m);
return 0;
if (!socket_needs_mount(s, m->where))
return 0;
return r;
return r;
return 0;
}
static int socket_add_mount_links(Socket *s) {
int r;
assert(s);
return r;
return 0;
}
static int socket_add_device_link(Socket *s) {
char *t;
int r;
assert(s);
if (!s->bind_to_device)
return 0;
return -ENOMEM;
r = unit_add_node_link(UNIT(s), t, false);
free(t);
return r;
}
static int socket_load(Unit *u) {
int r;
assert(u);
if ((r = unit_load_fragment_and_dropin(u)) < 0)
return r;
/* This is a new unit? Then let's add in some extras */
if (have_non_accept_socket(s)) {
return r;
return r;
}
if ((r = socket_add_mount_links(s)) < 0)
return r;
if ((r = socket_add_device_link(s)) < 0)
return r;
if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
return r;
if ((r = unit_add_default_cgroup(u)) < 0)
return r;
}
return socket_verify(s);
}
static const char* listen_lookup(int type) {
if (type == SOCK_STREAM)
return "ListenStream";
else if (type == SOCK_DGRAM)
return "ListenDatagram";
else if (type == SOCK_SEQPACKET)
return "ListenSequentialPacket";
assert_not_reached("Unknown socket type");
return NULL;
}
SocketPort *p;
const char *prefix2;
char *p2;
assert(s);
assert(f);
fprintf(f,
"%sSocket State: %s\n"
"%sBindIPv6Only: %s\n"
"%sBacklog: %u\n"
"%sKillMode: %s\n"
"%sSocketMode: %04o\n"
"%sDirectoryMode: %04o\n",
prefix, s->socket_mode,
prefix, s->directory_mode);
if (s->control_pid > 0)
fprintf(f,
"%sControl PID: %llu\n",
prefix, (unsigned long long) s->control_pid);
if (s->bind_to_device)
fprintf(f,
"%sBindToDevice: %s\n",
prefix, s->bind_to_device);
if (s->accept)
fprintf(f,
"%sAccepted: %u\n",
prefix, s->n_accepted);
if (p->type == SOCKET_SOCKET) {
const char *t;
int r;
char *k;
if ((r = socket_address_print(&p->address, &k)) < 0)
t = strerror(-r);
else
t = k;
free(k);
} else
}
for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) {
if (!s->exec_command[c])
continue;
fprintf(f, "%s-> %s:\n",
}
}
socklen_t l;
char *r;
union {
struct sockaddr_un un;
struct sockaddr_in in;
struct sockaddr_in6 in6;
struct sockaddr_storage storage;
l = sizeof(local);
return -errno;
l = sizeof(remote);
return -errno;
case AF_INET: {
if (asprintf(&r,
"%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u",
nr,
a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF,
return -ENOMEM;
break;
}
case AF_INET6: {
char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN];
if (asprintf(&r,
"%u-%s:%u-%s:%u",
nr,
return -ENOMEM;
break;
}
case AF_UNIX: {
l = sizeof(ucred);
return -errno;
if (asprintf(&r,
"%u-%llu-%llu",
nr,
return -ENOMEM;
break;
}
default:
assert_not_reached("Unhandled socket type.");
}
*instance = r;
return 0;
}
static void socket_close_fds(Socket *s) {
SocketPort *p;
assert(s);
if (p->fd < 0)
continue;
close_nointr_nofail(p->fd);
/* One little note: we should never delete any sockets
* in the file system here! After all some other
* process we spawned might still have a reference of
* this fd and wants to continue to use it. Therefore
* we delete sockets in the file system before we
* create a new one, not after we stopped using
* one! */
p->fd = -1;
}
}
static int socket_open_fds(Socket *s) {
SocketPort *p;
int r;
assert(s);
if (p->fd >= 0)
continue;
if (p->type == SOCKET_SOCKET) {
if ((r = socket_address_listen(
&p->address,
s->backlog,
s->bind_ipv6_only,
s->bind_to_device,
s->directory_mode,
s->socket_mode,
&p->fd)) < 0)
goto rollback;
} else {
r = -errno;
goto rollback;
}
r = -errno;
goto rollback;
}
r = -errno;
goto rollback;
}
/* FIXME verify user, access mode */
r = -EEXIST;
goto rollback;
}
}
}
return 0;
socket_close_fds(s);
return r;
}
static void socket_unwatch_fds(Socket *s) {
SocketPort *p;
assert(s);
if (p->fd < 0)
continue;
}
}
static int socket_watch_fds(Socket *s) {
SocketPort *p;
int r;
assert(s);
if (p->fd < 0)
continue;
p->fd_watch.socket_accept =
s->accept &&
p->type == SOCKET_SOCKET &&
goto fail;
}
return 0;
fail:
return r;
}
assert(s);
if (state != SOCKET_START_PRE &&
state != SOCKET_START_POST &&
state != SOCKET_STOP_PRE &&
state != SOCKET_STOP_PRE_SIGTERM &&
state != SOCKET_STOP_PRE_SIGKILL &&
state != SOCKET_STOP_POST &&
state != SOCKET_FINAL_SIGTERM &&
state != SOCKET_FINAL_SIGKILL) {
s->control_command = NULL;
}
if (state != SOCKET_LISTENING)
if (state != SOCKET_START_POST &&
state != SOCKET_LISTENING &&
state != SOCKET_RUNNING &&
state != SOCKET_STOP_PRE &&
state != SOCKET_STOP_PRE_SIGTERM &&
socket_close_fds(s);
log_debug("%s changed %s -> %s",
}
static int socket_coldplug(Unit *u) {
int r;
assert(s);
if (s->deserialized_state != s->state) {
if (s->deserialized_state == SOCKET_START_PRE ||
s->deserialized_state == SOCKET_START_POST ||
s->deserialized_state == SOCKET_STOP_PRE ||
s->deserialized_state == SOCKET_STOP_POST ||
s->deserialized_state == SOCKET_FINAL_SIGTERM ||
s->deserialized_state == SOCKET_FINAL_SIGKILL) {
if (s->control_pid <= 0)
return -EBADMSG;
return r;
return r;
}
if (s->deserialized_state == SOCKET_START_POST ||
s->deserialized_state == SOCKET_LISTENING ||
s->deserialized_state == SOCKET_RUNNING ||
s->deserialized_state == SOCKET_STOP_PRE ||
if ((r = socket_open_fds(s)) < 0)
return r;
if (s->deserialized_state == SOCKET_LISTENING)
if ((r = socket_watch_fds(s)) < 0)
return r;
socket_set_state(s, s->deserialized_state);
}
return 0;
}
int r;
char **argv;
assert(s);
assert(c);
goto fail;
r = -ENOMEM;
goto fail;
}
r = exec_spawn(c,
argv,
&s->exec_context,
NULL, 0,
true,
true,
&pid);
if (r < 0)
goto fail;
/* FIXME: we need to do something here */
goto fail;
return 0;
fail:
return r;
}
assert(s);
if (!success)
s->failure = true;
}
int r;
assert(s);
if (!success)
s->failure = true;
goto fail;
} else
socket_enter_signal(s, SOCKET_FINAL_SIGTERM, true);
return;
fail:
socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
}
int r;
bool sent = false;
assert(s);
if (!success)
s->failure = true;
if (s->kill_mode == KILL_CONTROL_GROUP) {
goto fail;
} else
sent = true;
}
if (!sent && s->control_pid > 0)
if (kill(s->kill_mode == KILL_PROCESS ? s->control_pid : -s->control_pid, sig) < 0 && errno != ESRCH) {
r = -errno;
goto fail;
}
}
if (sent && s->control_pid > 0) {
goto fail;
socket_set_state(s, state);
socket_enter_stop_post(s, true);
else
socket_enter_dead(s, true);
return;
fail:
socket_enter_stop_post(s, false);
else
socket_enter_dead(s, false);
}
int r;
assert(s);
if (!success)
s->failure = true;
goto fail;
} else
socket_enter_stop_post(s, true);
return;
fail:
socket_enter_stop_post(s, false);
}
static void socket_enter_listening(Socket *s) {
int r;
assert(s);
if ((r = socket_watch_fds(s)) < 0) {
goto fail;
}
return;
fail:
socket_enter_stop_pre(s, false);
}
static void socket_enter_start_post(Socket *s) {
int r;
assert(s);
if ((r = socket_open_fds(s)) < 0) {
goto fail;
}
goto fail;
}
} else
return;
fail:
socket_enter_stop_pre(s, false);
}
static void socket_enter_start_pre(Socket *s) {
int r;
assert(s);
goto fail;
} else
return;
fail:
socket_enter_dead(s, false);
}
int r;
assert(s);
if (cfd < 0) {
if ((r = manager_add_job(UNIT(s)->meta.manager, JOB_START, UNIT(s->service), JOB_REPLACE, true, NULL)) < 0)
goto fail;
} else {
Unit *u;
goto fail;
r = -ENOMEM;
goto fail;
}
if (!name)
r = -ENOMEM;
if (r < 0)
goto fail;
goto fail;
cfd = -1;
goto fail;
}
return;
fail:
socket_enter_stop_pre(s, false);
if (cfd >= 0)
}
int r;
assert(s);
assert(s->control_command);
if (!success)
s->failure = true;
goto fail;
return;
fail:
if (s->state == SOCKET_START_POST)
socket_enter_stop_pre(s, false);
else if (s->state == SOCKET_STOP_POST)
socket_enter_dead(s, false);
else
socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
}
static int socket_start(Unit *u) {
assert(s);
/* We cannot fulfill this request right now, try again later
* please! */
if (s->state == SOCKET_STOP_PRE ||
s->state == SOCKET_STOP_PRE_SIGKILL ||
s->state == SOCKET_STOP_PRE_SIGTERM ||
s->state == SOCKET_STOP_POST ||
s->state == SOCKET_FINAL_SIGTERM ||
s->state == SOCKET_FINAL_SIGKILL)
return -EAGAIN;
if (s->state == SOCKET_START_PRE ||
s->state == SOCKET_START_POST)
return 0;
/* Cannot run this without the service being around */
if (s->service) {
return -ENOENT;
/* If the service is alredy actvie we cannot start the
* socket */
return -EBUSY;
}
s->failure = false;
return 0;
}
static int socket_stop(Unit *u) {
assert(s);
/* We cannot fulfill this request right now, try again later
* please! */
if (s->state == SOCKET_START_PRE ||
s->state == SOCKET_START_POST)
return -EAGAIN;
/* Already on it */
if (s->state == SOCKET_STOP_PRE ||
s->state == SOCKET_STOP_PRE_SIGTERM ||
s->state == SOCKET_STOP_PRE_SIGKILL ||
s->state == SOCKET_STOP_POST ||
s->state == SOCKET_FINAL_SIGTERM ||
s->state == SOCKET_FINAL_SIGTERM)
return 0;
socket_enter_stop_pre(s, true);
return 0;
}
SocketPort *p;
int r;
assert(u);
assert(f);
if (s->control_pid > 0)
if (s->control_command_id >= 0)
int copy;
if (p->fd < 0)
continue;
return copy;
if (p->type == SOCKET_SOCKET) {
char *t;
if ((r = socket_address_print(&p->address, &t)) < 0)
return r;
free(t);
} else {
}
}
return 0;
}
int r;
assert(u);
else
s->deserialized_state = state;
int b;
if ((b = parse_boolean(value)) < 0)
else
unsigned k;
else
s->n_accepted += k;
unsigned pid;
else
else {
s->control_command_id = id;
}
SocketPort *p;
else {
break;
if (p) {
if (p->fd >= 0)
close_nointr_nofail(p->fd);
}
}
SocketPort *p;
else {
break;
if (p) {
if (p->fd >= 0)
close_nointr_nofail(p->fd);
}
}
} else
return 0;
}
assert(u);
}
static const char *socket_sub_state_to_string(Unit *u) {
assert(u);
}
int cfd = -1;
assert(s);
log_error("Got invalid poll event on socket.");
goto fail;
}
if (w->socket_accept) {
for (;;) {
continue;
log_error("Failed to accept socket: %m");
goto fail;
}
break;
}
}
socket_enter_running(s, cfd);
return;
fail:
socket_enter_stop_pre(s, false);
}
bool success;
assert(s);
s->control_pid = 0;
if (s->control_command)
log_debug("%s control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
socket_run_next(s, success);
} else {
s->control_command = NULL;
/* No further commands for this step, so let's figure
* out what to do next */
switch (s->state) {
case SOCKET_START_PRE:
if (success)
else
socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
break;
case SOCKET_START_POST:
if (success)
else
socket_enter_stop_pre(s, false);
break;
case SOCKET_STOP_PRE:
case SOCKET_STOP_PRE_SIGTERM:
case SOCKET_STOP_PRE_SIGKILL:
break;
case SOCKET_STOP_POST:
case SOCKET_FINAL_SIGTERM:
case SOCKET_FINAL_SIGKILL:
socket_enter_dead(s, success);
break;
default:
assert_not_reached("Uh, control process died at wrong time.");
}
}
}
assert(s);
assert(w == &s->timer_watch);
switch (s->state) {
case SOCKET_START_PRE:
socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
case SOCKET_START_POST:
socket_enter_stop_pre(s, false);
break;
case SOCKET_STOP_PRE:
socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, false);
break;
case SOCKET_STOP_PRE_SIGTERM:
socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, false);
break;
case SOCKET_STOP_PRE_SIGKILL:
socket_enter_stop_post(s, false);
break;
case SOCKET_STOP_POST:
socket_enter_signal(s, SOCKET_FINAL_SIGTERM, false);
break;
case SOCKET_FINAL_SIGTERM:
socket_enter_signal(s, SOCKET_FINAL_SIGKILL, false);
break;
case SOCKET_FINAL_SIGKILL:
socket_enter_dead(s, false);
break;
default:
assert_not_reached("Timeout at wrong time.");
}
}
int *rfds;
unsigned rn_fds, k;
SocketPort *p;
assert(s);
/* Called from the service code for requesting our fds */
rn_fds = 0;
if (p->fd >= 0)
rn_fds++;
return -ENOMEM;
k = 0;
if (p->fd >= 0)
return 0;
}
void socket_notify_service_dead(Socket *s) {
assert(s);
/* The service is dead. Dang. */
if (s->state == SOCKET_RUNNING) {
}
}
static const char* const socket_state_table[_SOCKET_STATE_MAX] = {
[SOCKET_DEAD] = "dead",
[SOCKET_START_PRE] = "start-pre",
[SOCKET_START_POST] = "start-post",
[SOCKET_LISTENING] = "listening",
[SOCKET_RUNNING] = "running",
[SOCKET_STOP_PRE] = "stop-pre",
[SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm",
[SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill",
[SOCKET_STOP_POST] = "stop-post",
[SOCKET_FINAL_SIGTERM] = "final-sigterm",
[SOCKET_FINAL_SIGKILL] = "final-sigkill",
[SOCKET_MAINTAINANCE] = "maintainance"
};
static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = {
[SOCKET_EXEC_START_PRE] = "StartPre",
[SOCKET_EXEC_START_POST] = "StartPost",
[SOCKET_EXEC_STOP_PRE] = "StopPre",
[SOCKET_EXEC_STOP_POST] = "StopPost"
};
const UnitVTable socket_vtable = {
.suffix = ".socket",
.init = socket_init,
.done = socket_done,
.load = socket_load,
.dump = socket_dump,
.start = socket_start,
.stop = socket_stop,
};