3
* Copyright © 2010 Canonical Ltd.
4
* Author: Scott James Remnant <scott@netsplit.com>.
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License version 2, as
8
* published by the Free Software Foundation.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License along
16
* with this program; if not, write to the Free Software Foundation, Inc.,
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
#endif /* HAVE_CONFIG_H */
25
#include <sys/epoll.h>
26
#include <sys/types.h>
27
#include <sys/socket.h>
30
#include <netinet/in.h>
31
#include <arpa/inet.h>
39
#include <nih/macros.h>
40
#include <nih/alloc.h>
43
#include <nih/string.h>
45
#include <nih/option.h>
47
#include <nih/logging.h>
48
#include <nih/error.h>
50
#include <nih-dbus/dbus_connection.h>
51
#include <nih-dbus/dbus_proxy.h>
53
#include "dbus/upstart.h"
54
#include "com.ubuntu.Upstart.h"
55
#include "com.ubuntu.Upstart.Job.h"
58
/* Structure we use for tracking jobs */
65
/* Structure we use for tracking listening sockets */
66
typedef struct socket {
71
struct sockaddr_in sin_addr;
72
struct sockaddr_un sun_addr;
80
/* Prototypes for static functions */
81
static void epoll_watcher (void *data, NihIoWatch *watch,
83
static void upstart_job_added (void *data, NihDBusMessage *message,
85
static void upstart_job_removed (void *data, NihDBusMessage *message,
87
static void job_add_socket (Job *job, char **socket_info);
88
static void socket_destroy (Socket *socket);
89
static void upstart_disconnected (DBusConnection *connection);
90
static void emit_event_reply (Socket *sock, NihDBusMessage *message);
91
static void emit_event_error (Socket *sock, NihDBusMessage *message);
97
* Set to TRUE if we should become a daemon, rather than just running
100
static int daemonise = FALSE;
105
* Shared epoll file descriptor for listening on.
107
static int epoll_fd = -1;
112
* Jobs that we're monitoring.
114
static NihHash *jobs = NULL;
119
* Proxy to Upstart daemon.
121
static NihDBusProxy *upstart = NULL;
127
* Command-line options accepted by this program.
129
static NihOption options[] = {
130
{ 0, "daemon", N_("Detach and run in the background"),
131
NULL, NULL, &daemonise, NULL },
142
DBusConnection *connection;
143
char ** job_class_paths;
146
nih_main_init (argv[0]);
148
nih_option_set_synopsis (_("Bridge socket events into upstart"));
149
nih_option_set_help (
150
_("By default, upstart-socket-bridge does not detach from the "
151
"console and remains in the foreground. Use the --daemon "
152
"option to have it detach."));
154
args = nih_option_parser (NULL, argc, argv, options, FALSE);
158
/* Create an epoll file descriptor for listening on; use this so
159
* we can do edge triggering rather than level.
161
epoll_fd = epoll_create1 (0);
163
nih_fatal ("%s: %s", _("Could not create epoll descriptor"),
168
NIH_MUST (nih_io_add_watch (NULL, epoll_fd, NIH_IO_READ,
169
epoll_watcher, NULL));
171
/* Allocate jobs hash table */
172
jobs = NIH_MUST (nih_hash_string_new (NULL, 0));
174
/* Initialise the connection to Upstart */
175
connection = NIH_SHOULD (nih_dbus_connect (DBUS_ADDRESS_UPSTART, upstart_disconnected));
179
err = nih_error_get ();
180
nih_fatal ("%s: %s", _("Could not connect to Upstart"),
187
upstart = NIH_SHOULD (nih_dbus_proxy_new (NULL, connection,
188
NULL, DBUS_PATH_UPSTART,
193
err = nih_error_get ();
194
nih_fatal ("%s: %s", _("Could not create Upstart proxy"),
201
/* Connect signals to be notified when jobs come and go */
202
if (! nih_dbus_proxy_connect (upstart, &upstart_com_ubuntu_Upstart0_6, "JobAdded",
203
(NihDBusSignalHandler)upstart_job_added, NULL)) {
206
err = nih_error_get ();
207
nih_fatal ("%s: %s", _("Could not create JobAdded signal connection"),
214
if (! nih_dbus_proxy_connect (upstart, &upstart_com_ubuntu_Upstart0_6, "JobRemoved",
215
(NihDBusSignalHandler)upstart_job_removed, NULL)) {
218
err = nih_error_get ();
219
nih_fatal ("%s: %s", _("Could not create JobRemoved signal connection"),
226
/* Request a list of all current jobs */
227
if (upstart_get_all_jobs_sync (NULL, upstart, &job_class_paths) < 0) {
230
err = nih_error_get ();
231
nih_fatal ("%s: %s", _("Could not obtain job list"),
238
for (char **job_class_path = job_class_paths;
239
job_class_path && *job_class_path; job_class_path++)
240
upstart_job_added (NULL, NULL, *job_class_path);
242
nih_free (job_class_paths);
246
if (nih_main_daemonise () < 0) {
249
err = nih_error_get ();
250
nih_fatal ("%s: %s", _("Unable to become daemon"),
257
/* Send all logging output to syslog */
258
openlog (program_name, LOG_PID, LOG_DAEMON);
259
nih_log_set_logger (nih_logger_syslog);
262
/* Handle TERM and INT signals gracefully */
263
nih_signal_set_handler (SIGTERM, nih_signal_handler);
264
NIH_MUST (nih_signal_add_handler (NULL, SIGTERM, nih_main_term_signal, NULL));
267
nih_signal_set_handler (SIGINT, nih_signal_handler);
268
NIH_MUST (nih_signal_add_handler (NULL, SIGINT, nih_main_term_signal, NULL));
271
ret = nih_main_loop ();
278
epoll_watcher (void * data,
282
struct epoll_event event[1024];
285
num_events = epoll_wait (epoll_fd, event, 1024, 0);
286
if (num_events < 0) {
287
nih_error ("%s: %s", _("Error from epoll"), strerror (errno));
289
} else if (num_events == 0)
292
for (int i = 0; i < num_events; i++) {
293
Socket *sock = (Socket *)event[i].data.ptr;
294
nih_local char **env = NULL;
297
DBusPendingCall *pending_call;
299
if (event[i].events & EPOLLIN)
300
nih_debug ("%p EPOLLIN", sock);
301
if (event[i].events & EPOLLERR)
302
nih_debug ("%p EPOLLERR", sock);
303
if (event[i].events & EPOLLHUP)
304
nih_debug ("%p EPOLLHUP", sock);
306
env = NIH_MUST (nih_str_array_new (NULL));
308
switch (sock->addr.sa_family) {
310
NIH_MUST (nih_str_array_add (&env, NULL, &env_len,
313
var = NIH_MUST (nih_sprintf (NULL, "PORT=%d",
314
ntohs (sock->sin_addr.sin_port)));
315
NIH_MUST (nih_str_array_addp (&env, NULL, &env_len,
319
var = NIH_MUST (nih_sprintf (NULL, "ADDR=%s",
320
inet_ntoa (sock->sin_addr.sin_addr)));
321
NIH_MUST (nih_str_array_addp (&env, NULL, &env_len,
326
NIH_MUST (nih_str_array_add (&env, NULL, &env_len,
329
var = NIH_MUST (nih_sprintf (NULL, "PATH=%s",
330
sock->sun_addr.sun_path));
331
NIH_MUST (nih_str_array_addp (&env, NULL, &env_len,
336
nih_assert_not_reached ();
339
pending_call = NIH_SHOULD (upstart_emit_event_with_file (
340
upstart, "socket", env, TRUE,
342
(UpstartEmitEventWithFileReply)emit_event_reply,
343
(NihDBusErrorHandler)emit_event_error,
345
NIH_DBUS_TIMEOUT_NEVER));
346
if (! pending_call) {
349
err = nih_error_get ();
350
nih_warn ("%s: %s", _("Could not send socket event"),
355
dbus_pending_call_unref (pending_call);
365
upstart_job_added (void * data,
366
NihDBusMessage *message,
367
const char * job_class_path)
369
nih_local NihDBusProxy *job_class = NULL;
370
nih_local char ***start_on = NULL;
371
nih_local char ***stop_on = NULL;
374
nih_assert (job_class_path != NULL);
376
/* Obtain a proxy to the job */
377
job_class = nih_dbus_proxy_new (NULL, upstart->connection,
378
upstart->name, job_class_path,
383
err = nih_error_get ();
384
nih_error ("Could not create proxy for job %s: %s",
385
job_class_path, err->message);
391
job_class->auto_start = FALSE;
393
/* Obtain the start_on and stop_on properties of the job */
394
if (job_class_get_start_on_sync (NULL, job_class, &start_on) < 0) {
397
err = nih_error_get ();
398
nih_error ("Could not obtain job start condition %s: %s",
399
job_class_path, err->message);
405
if (job_class_get_stop_on_sync (NULL, job_class, &stop_on) < 0) {
408
err = nih_error_get ();
409
nih_error ("Could not obtain job stop condition %s: %s",
410
job_class_path, err->message);
416
/* Free any existing record for the job (should never happen,
417
* but worth being safe).
419
job = (Job *)nih_hash_lookup (jobs, job_class_path);
423
/* Create new record for the job */
424
job = NIH_MUST (nih_new (NULL, Job));
425
job->path = NIH_MUST (nih_strdup (job, job_class_path));
427
nih_list_init (&job->entry);
428
nih_list_init (&job->sockets);
430
/* Find out whether this job listens for any socket events */
431
for (char ***event = start_on; event && *event && **event; event++)
432
if (! strcmp (**event, "socket"))
433
job_add_socket (job, *event);
434
for (char ***event = stop_on; event && *event && **event; event++)
435
if (! strcmp (**event, "socket"))
436
job_add_socket (job, *event);
438
/* If we didn't end up with any sockets, free the job and move on */
439
if (NIH_LIST_EMPTY (&job->sockets)) {
444
nih_debug ("Job got added %s", job_class_path);
446
nih_alloc_set_destructor (job, nih_list_destroy);
447
nih_hash_add (jobs, &job->entry);
451
upstart_job_removed (void * data,
452
NihDBusMessage *message,
453
const char * job_path)
457
nih_assert (job_path != NULL);
459
job = (Job *)nih_hash_lookup (jobs, job_path);
461
nih_debug ("Job went away %s", job_path);
468
job_add_socket (Job * job,
472
nih_local char *error = NULL;
474
struct epoll_event event;
476
nih_assert (job != NULL);
477
nih_assert (socket_info != NULL);
478
nih_assert (! strcmp(socket_info[0], "socket"));
480
sock = NIH_MUST (nih_new (job, Socket));
481
memset (sock, 0, sizeof (Socket));
484
nih_list_init (&sock->entry);
486
nih_debug ("Found socket");
487
for (char **env = socket_info + 1; env && *env; env++) {
491
val = strchr (*env, '=');
493
nih_warn ("Ignored socket event without variable name in %s",
498
name_len = val - *env;
501
if (! strncmp (*env, "PROTO", name_len)) {
502
if (! strcmp (val, "inet")) {
503
sock->addrlen = sizeof sock->sin_addr;
504
sock->sin_addr.sin_family = AF_INET;
505
sock->sin_addr.sin_addr.s_addr = INADDR_ANY;
507
} else if (! strcmp (val, "unix")) {
508
sock->addrlen = sizeof sock->sun_addr;
509
sock->sun_addr.sun_family = AF_UNIX;
512
nih_warn ("Ignored socket event with unknown PROTO=%s in %s",
517
} else if (! strncmp (*env, "PORT", name_len)
518
&& (sock->sin_addr.sin_family == AF_INET)) {
519
sock->sin_addr.sin_port = htons (atoi (val));
522
} else if (! strncmp (*env, "ADDR", name_len)
523
&& (sock->sin_addr.sin_family == AF_INET)) {
524
if (inet_aton (val, &(sock->sin_addr.sin_addr)) == 0) {
525
nih_warn ("Ignored socket event with invalid ADDR=%s in %s",
530
} else if (! strncmp (*env, "PATH", name_len)
531
&& (sock->sun_addr.sun_family == AF_UNIX)) {
532
strncpy (sock->sun_addr.sun_path, val,
533
sizeof sock->sun_addr.sun_path);
535
if (sock->sun_addr.sun_path[0] == '@')
536
sock->sun_addr.sun_path[0] = '\0';
541
nih_warn ("Ignored socket event with unknown variable %.*s in %s",
542
(int)name_len, *env, job->path);
547
/* Missing any required components? */
549
nih_warn ("Ignored incomplete socket event in %s",
554
/* Let's try and set this baby up */
555
sock->sock = socket (sock->addr.sa_family, SOCK_STREAM, 0);
556
if (sock->sock < 0) {
557
nih_warn ("Failed to create socket in %s: %s",
558
job->path, strerror (errno));
563
if (setsockopt (sock->sock, SOL_SOCKET, SO_REUSEADDR,
564
&opt, sizeof opt) < 0) {
565
nih_warn ("Failed to set socket reuse in %s: %s",
566
job->path, strerror (errno));
570
if (bind (sock->sock, &sock->addr, sock->addrlen) < 0) {
571
nih_warn ("Failed to bind socket in %s: %s",
572
job->path, strerror (errno));
576
if (listen (sock->sock, SOMAXCONN) < 0) {
577
nih_warn ("Failed to listen on socket in %s: %s",
578
job->path, strerror (errno));
582
/* We have a listening socket, now we want to be notified when someone
583
* connects; but we just want one notification, we don't want to get
584
* a DDoS of wake-ups while waiting for the service to start.
586
* The solution is to use epoll in edge-triggered mode, this will
587
* fire only on initial connection until a new one comes in.
589
event.events = EPOLLIN | EPOLLET;
590
event.data.ptr = sock;
592
if (epoll_ctl (epoll_fd, EPOLL_CTL_ADD, sock->sock, &event) < 0) {
593
nih_warn ("Failed to watch socket in %s: %s",
594
job->path, strerror (errno));
598
/* Okay then, add to the job */
599
nih_alloc_set_destructor (sock, socket_destroy);
600
nih_list_add (&job->sockets, &sock->entry);
605
if (sock->sock != -1)
611
socket_destroy (Socket *sock)
613
epoll_ctl (epoll_fd, EPOLL_CTL_DEL, sock->sock, NULL);
616
nih_list_destroy (&sock->entry);
621
upstart_disconnected (DBusConnection *connection)
623
nih_fatal (_("Disconnected from Upstart"));
624
nih_main_loop_exit (1);
629
emit_event_reply (Socket * sock,
630
NihDBusMessage *message)
632
nih_debug ("Event completed");
636
emit_event_error (Socket * sock,
637
NihDBusMessage *message)
641
err = nih_error_get ();
642
nih_warn ("%s: %s", _("Error emitting socket event"), err->message);