Last active
August 28, 2015 15:29
-
-
Save lstoll/2a88cbfa1f84fdcc1511 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* upstart | |
* | |
* control.c - D-Bus connections, objects and methods | |
* | |
* Copyright 2009-2011 Canonical Ltd. | |
* Author: Scott James Remnant <[email protected]>. | |
* | |
* This program is free software; you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License version 2, as | |
* published by the Free Software Foundation. | |
* | |
* This program 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 this program; if not, write to the Free Software Foundation, Inc., | |
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
*/ | |
#ifdef HAVE_CONFIG_H | |
# include <config.h> | |
#endif /* HAVE_CONFIG_H */ | |
#include <dbus/dbus.h> | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <nih/macros.h> | |
#include <nih/alloc.h> | |
#include <nih/string.h> | |
#include <nih/list.h> | |
#include <nih/io.h> | |
#include <nih/main.h> | |
#include <nih/logging.h> | |
#include <nih/error.h> | |
#include <nih/errors.h> | |
#include <nih-dbus/dbus_error.h> | |
#include <nih-dbus/dbus_connection.h> | |
#include <nih-dbus/dbus_message.h> | |
#include <nih-dbus/dbus_object.h> | |
#include "dbus/upstart.h" | |
#include "environ.h" | |
#include "session.h" | |
#include "job_class.h" | |
#include "job.h" | |
#include "blocked.h" | |
#include "conf.h" | |
#include "control.h" | |
#include "errors.h" | |
#include "state.h" | |
#include "event.h" | |
#include "events.h" | |
#include "paths.h" | |
#include "xdg.h" | |
#include "com.ubuntu.Upstart.h" | |
#ifdef ENABLE_CGROUPS | |
#include "cgroup.h" | |
#endif /* ENABLE_CGROUPS */ | |
/* Prototypes for static functions */ | |
static int control_server_connect (DBusServer *server, DBusConnection *conn); | |
static void control_disconnected (DBusConnection *conn); | |
static void control_register_all (DBusConnection *conn); | |
static void control_bus_flush (void); | |
static int control_get_origin_uid (NihDBusMessage *message, uid_t *uid) | |
__attribute__ ((warn_unused_result)); | |
static int control_check_permission (NihDBusMessage *message) | |
__attribute__ ((warn_unused_result)); | |
static void control_session_file_create (void); | |
static void control_session_file_remove (void); | |
/** | |
* use_session_bus: | |
* | |
* If TRUE, connect to the D-Bus session bus rather than the system bus. | |
* | |
* Used for testing to simulate (as far as possible) a system-like init | |
* when running as a non-priv user (but not as a Session Init). | |
**/ | |
int use_session_bus = FALSE; | |
/** | |
* dbus_bus_type: | |
* | |
* Type of D-Bus bus to connect to. | |
**/ | |
DBusBusType dbus_bus_type; | |
/** | |
* control_server_address: | |
* | |
* Address on which the control server may be reached. | |
**/ | |
char *control_server_address = NULL; | |
/** | |
* control_server: | |
* | |
* D-Bus server listening for new direct connections. | |
**/ | |
DBusServer *control_server = NULL; | |
/** | |
* control_bus_address: | |
* | |
* Address on which the control bus may be reached. | |
**/ | |
char *control_bus_address = NULL; | |
/** | |
* control_bus: | |
* | |
* Open connection to a D-Bus bus. The connection may be opened with | |
* control_bus_open() and if lost will become NULL. | |
**/ | |
DBusConnection *control_bus = NULL; | |
/** | |
* control_conns: | |
* | |
* Open control connections, including the connection to a D-Bus | |
* bus and any private client connections. | |
**/ | |
NihList *control_conns = NULL; | |
/* External definitions */ | |
extern int user_mode; | |
extern int disable_respawn; | |
extern char *session_file; | |
/** | |
* control_init: | |
* | |
* Initialise the control connections list. | |
**/ | |
void | |
control_init (void) | |
{ | |
if (! control_conns) | |
control_conns = NIH_MUST (nih_list_new (NULL)); | |
if (! control_server_address) { | |
if (user_mode) { | |
NIH_MUST (nih_strcat_sprintf (&control_server_address, NULL, | |
"%s-session/%d/%d", DBUS_ADDRESS_UPSTART, getuid (), getpid ())); | |
control_session_file_create (); | |
} else { | |
control_server_address = NIH_MUST (nih_strdup (NULL, DBUS_ADDRESS_UPSTART)); | |
} | |
} | |
} | |
/** | |
* control_cleanup: | |
* | |
* Perform cleanup operations. | |
**/ | |
void | |
control_cleanup (void) | |
{ | |
control_session_file_remove (); | |
} | |
/** | |
* control_server_open: | |
* | |
* Open a listening D-Bus server and store it in the control_server global. | |
* New connections are permitted from the root user, and handled | |
* automatically in the main loop. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_server_open (void) | |
{ | |
DBusServer *server; | |
nih_assert (control_server == NULL); | |
control_init (); | |
server = nih_dbus_server (control_server_address, | |
control_server_connect, | |
control_disconnected); | |
if (! server) | |
return -1; | |
control_server = server; | |
return 0; | |
} | |
/** | |
* control_server_connect: | |
* | |
* Called when a new client connects to our server and is used to register | |
* objects on the new connection. | |
* | |
* Returns: always TRUE. | |
**/ | |
static int | |
control_server_connect (DBusServer *server, | |
DBusConnection *conn) | |
{ | |
NihListEntry *entry; | |
nih_assert (server != NULL); | |
nih_assert (server == control_server); | |
nih_assert (conn != NULL); | |
nih_info (_("Connection from private client")); | |
/* Register objects on the connection. */ | |
control_register_all (conn); | |
/* Add the connection to the list */ | |
entry = NIH_MUST (nih_list_entry_new (NULL)); | |
entry->data = conn; | |
nih_list_add (control_conns, &entry->entry); | |
return TRUE; | |
} | |
/** | |
* control_server_close: | |
* | |
* Close the connection to the D-Bus system bus. Since the connection is | |
* shared inside libdbus, this really only drops our reference to it so | |
* it's possible to have method and signal handlers called even after calling | |
* this (normally to dispatch what's in the queue). | |
**/ | |
void | |
control_server_close (void) | |
{ | |
if (! control_server) | |
return; | |
dbus_server_disconnect (control_server); | |
dbus_server_unref (control_server); | |
control_server = NULL; | |
} | |
/** | |
* control_bus_open: | |
* | |
* Open a connection to the appropriate D-Bus bus and store it in the | |
* control_bus global. The connection is handled automatically | |
* in the main loop. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_bus_open (void) | |
{ | |
DBusConnection *conn; | |
DBusError error; | |
NihListEntry *entry; | |
int ret; | |
nih_assert (control_bus == NULL); | |
dbus_error_init (&error); | |
control_init (); | |
dbus_bus_type = control_get_bus_type (); | |
/* Connect to the appropriate D-Bus bus and hook everything up into | |
* our own main loop automatically. | |
*/ | |
if (user_mode && control_bus_address) { | |
conn = nih_dbus_connect (control_bus_address, control_disconnected); | |
if (! conn) | |
return -1; | |
if (! dbus_bus_register (conn, &error)) { | |
nih_dbus_error_raise (error.name, error.message); | |
dbus_error_free (&error); | |
return -1; | |
} | |
nih_debug ("Connected to notified D-Bus bus"); | |
} else { | |
conn = nih_dbus_bus (use_session_bus ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, | |
control_disconnected); | |
if (! conn) | |
return -1; | |
nih_debug ("Connected to D-Bus %s bus", | |
dbus_bus_type == DBUS_BUS_SESSION | |
? "session" : "system"); | |
} | |
/* Register objects on the bus. */ | |
control_register_all (conn); | |
/* Request our well-known name. We do this last so that once it | |
* appears on the bus, clients can assume we're ready to talk to | |
* them. | |
*/ | |
ret = dbus_bus_request_name (conn, DBUS_SERVICE_UPSTART, | |
DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); | |
if (ret < 0) { | |
/* Error while requesting the name */ | |
nih_dbus_error_raise (error.name, error.message); | |
dbus_error_free (&error); | |
dbus_connection_unref (conn); | |
return -1; | |
} else if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { | |
/* Failed to obtain the name (already taken usually) */ | |
nih_error_raise (CONTROL_NAME_TAKEN, | |
_(CONTROL_NAME_TAKEN_STR)); | |
dbus_connection_unref (conn); | |
return -1; | |
} | |
/* Add the connection to the list */ | |
entry = NIH_MUST (nih_list_entry_new (NULL)); | |
entry->data = conn; | |
nih_list_add (control_conns, &entry->entry); | |
control_bus = conn; | |
return 0; | |
} | |
/** | |
* control_bus_close: | |
* | |
* Close the connection to the D-Bus system bus. Since the connection is | |
* shared inside libdbus, this really only drops our reference to it so | |
* it's possible to have method and signal handlers called even after calling | |
* this (normally to dispatch what's in the queue). | |
**/ | |
void | |
control_bus_close (void) | |
{ | |
nih_assert (control_bus != NULL); | |
dbus_connection_unref (control_bus); | |
control_disconnected (control_bus); | |
} | |
/** | |
* control_disconnected: | |
* | |
* This function is called when the connection to the D-Bus system bus, | |
* or a client connection to our D-Bus server, is dropped and our reference | |
* is about to be lost. We clear the connection from our current list | |
* and drop the control_bus global if relevant. | |
**/ | |
static void | |
control_disconnected (DBusConnection *conn) | |
{ | |
nih_assert (conn != NULL); | |
if (conn == control_bus) { | |
DBusError error; | |
dbus_error_init (&error); | |
if (user_mode && control_bus_address) { | |
nih_warn (_("Disconnected from notified D-Bus bus")); | |
} else { | |
nih_warn (_("Disconnected from D-Bus %s bus"), | |
dbus_bus_type == DBUS_BUS_SESSION | |
? "session" : "system"); | |
} | |
control_bus = NULL; | |
} | |
/* Remove from the connections list */ | |
NIH_LIST_FOREACH_SAFE (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
if (entry->data == conn) | |
nih_free (entry); | |
} | |
} | |
/** | |
* control_register_all: | |
* @conn: connection to register objects for. | |
* | |
* Registers the manager object and objects for all jobs and instances on | |
* the given connection. | |
**/ | |
static void | |
control_register_all (DBusConnection *conn) | |
{ | |
nih_assert (conn != NULL); | |
job_class_init (); | |
/* Register the manager object, this is the primary point of contact | |
* for clients. We only check for success, otherwise we're happy | |
* to let this object be tied to the lifetime of the connection. | |
*/ | |
NIH_MUST (nih_dbus_object_new (NULL, conn, DBUS_PATH_UPSTART, | |
control_interfaces, NULL)); | |
/* Register objects for each currently registered job and its | |
* instances. | |
*/ | |
NIH_HASH_FOREACH (job_classes, iter) { | |
JobClass *class = (JobClass *)iter; | |
job_class_register (class, conn, FALSE); | |
} | |
} | |
/** | |
* control_reload_configuration: | |
* @data: not used, | |
* @message: D-Bus connection and message received. | |
* | |
* Implements the ReloadConfiguration method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to request that Upstart reloads its configuration from disk, | |
* useful when inotify is not available or the user is generally paranoid. | |
* | |
* Notes: chroot sessions are permitted to make this call. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_reload_configuration (void *data, | |
NihDBusMessage *message) | |
{ | |
nih_assert (message != NULL); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to reload configuration")); | |
return -1; | |
} | |
nih_info (_("Reloading configuration")); | |
/* This can only be called after deserialisation */ | |
conf_reload (); | |
return 0; | |
} | |
/** | |
* control_get_job_by_name: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @name: name of job to get, | |
* @job: pointer for object path reply. | |
* | |
* Implements the GetJobByName method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to obtain the path to a D-Bus object for the job named @name, | |
* which will be stored in @job. If no job class with that name exists, | |
* the com.ubuntu.Upstart.Error.UnknownJob D-Bus error will be raised. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_get_job_by_name (void *data, | |
NihDBusMessage *message, | |
const char *name, | |
char **job) | |
{ | |
Session *session; | |
JobClass *class = NULL; | |
JobClass *global_class = NULL; | |
nih_assert (message != NULL); | |
nih_assert (name != NULL); | |
nih_assert (job != NULL); | |
job_class_init (); | |
/* Verify that the name is valid */ | |
if (! strlen (name)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Name may not be empty string")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* Lookup the job */ | |
class = (JobClass *)nih_hash_search (job_classes, name, NULL); | |
while (class && (class->session != session)) { | |
/* Found a match in the global session which may be used | |
* later if no matching user session job exists. | |
*/ | |
if ((! class->session) && (session && ! session->chroot)) | |
global_class = class; | |
class = (JobClass *)nih_hash_search (job_classes, name, | |
&class->entry); | |
} | |
/* If no job with the given name exists in the appropriate | |
* session, look in the global namespace (aka the NULL session). | |
*/ | |
if (! class) | |
class = global_class; | |
if (! class) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.UnknownJob", | |
_("Unknown job: %s"), name); | |
return -1; | |
} | |
/* Copy the path */ | |
*job = nih_strdup (message, class->path); | |
if (! *job) | |
nih_return_system_error (-1); | |
return 0; | |
} | |
/** | |
* control_get_all_jobs: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @jobs: pointer for array of object paths reply. | |
* | |
* Implements the GetAllJobs method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to obtain the paths of all known jobs, which will be stored in | |
* @jobs. If no jobs are registered, @jobs will point to an empty array. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_get_all_jobs (void *data, | |
NihDBusMessage *message, | |
char ***jobs) | |
{ | |
Session *session; | |
char **list; | |
size_t len; | |
nih_assert (message != NULL); | |
nih_assert (jobs != NULL); | |
job_class_init (); | |
len = 0; | |
list = nih_str_array_new (message); | |
if (! list) | |
nih_return_system_error (-1); | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
NIH_HASH_FOREACH (job_classes, iter) { | |
JobClass *class = (JobClass *)iter; | |
if ((class->session || (session && session->chroot)) | |
&& (class->session != session)) | |
continue; | |
if (! nih_str_array_add (&list, message, &len, | |
class->path)) { | |
nih_error_raise_system (); | |
nih_free (list); | |
return -1; | |
} | |
} | |
*jobs = list; | |
return 0; | |
} | |
int | |
control_emit_event (void *data, | |
NihDBusMessage *message, | |
const char *name, | |
char * const *env, | |
int wait) | |
{ | |
return control_emit_event_with_file (data, message, name, env, wait, -1); | |
} | |
/** | |
* control_emit_event_with_file: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @name: name of event to emit, | |
* @env: environment of environment, | |
* @wait: whether to wait for event completion before returning, | |
* @file: file descriptor. | |
* | |
* Implements the top half of the EmitEvent method of the com.ubuntu.Upstart | |
* interface, the bottom half may be found in event_finished(). | |
* | |
* Called to emit an event with a given @name and @env, which will be | |
* added to the event queue and processed asynchronously. If @name or | |
* @env are not valid, the org.freedesktop.DBus.Error.InvalidArgs D-Bus | |
* error will be returned immediately. If the event fails, the | |
* com.ubuntu.Upstart.Error.EventFailed D-Bus error will be returned when | |
* the event finishes. | |
* | |
* When @wait is TRUE the method call will not return until the event | |
* has completed, which means that all jobs affected by the event have | |
* finished starting (running for tasks) or stopping; when @wait is FALSE, | |
* the method call returns once the event has been queued. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_emit_event_with_file (void *data, | |
NihDBusMessage *message, | |
const char *name, | |
char * const *env, | |
int wait, | |
int file) | |
{ | |
Event *event; | |
Blocked *blocked; | |
nih_assert (message != NULL); | |
nih_assert (name != NULL); | |
nih_assert (env != NULL); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to emit an event")); | |
return -1; | |
} | |
/* Verify that the name is valid */ | |
if (! strlen (name)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Name may not be empty string")); | |
close (file); | |
return -1; | |
} | |
/* Verify that the environment is valid */ | |
if (! environ_all_valid (env)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Env must be KEY=VALUE pairs")); | |
close (file); | |
return -1; | |
} | |
/* Make the event and block the message on it */ | |
event = event_new (NULL, name, (char **)env); | |
if (! event) { | |
nih_error_raise_system (); | |
close (file); | |
return -1; | |
} | |
event->fd = file; | |
if (event->fd >= 0) { | |
long flags; | |
flags = fcntl (event->fd, F_GETFD); | |
flags &= ~FD_CLOEXEC; | |
fcntl (event->fd, F_SETFD, flags); | |
} | |
/* Obtain the session */ | |
event->session = session_from_dbus (NULL, message); | |
if (wait) { | |
blocked = blocked_new (event, BLOCKED_EMIT_METHOD, message); | |
if (! blocked) { | |
nih_error_raise_system (); | |
nih_free (event); | |
close (file); | |
return -1; | |
} | |
nih_list_add (&event->blocking, &blocked->entry); | |
} else { | |
NIH_ZERO (control_emit_event_reply (message)); | |
} | |
return 0; | |
} | |
/** | |
* control_get_version: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @version: pointer for reply string. | |
* | |
* Implements the get method for the version property of the | |
* com.ubuntu.Upstart interface. | |
* | |
* Called to obtain the version of the init daemon, which will be stored | |
* as a string in @version. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_get_version (void * data, | |
NihDBusMessage *message, | |
char ** version) | |
{ | |
nih_assert (message != NULL); | |
nih_assert (version != NULL); | |
*version = nih_strdup (message, package_string); | |
if (! *version) | |
nih_return_no_memory_error (-1); | |
return 0; | |
} | |
/** | |
* control_get_log_priority: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @log_priority: pointer for reply string. | |
* | |
* Implements the get method for the log_priority property of the | |
* com.ubuntu.Upstart interface. | |
* | |
* Called to obtain the init daemon's current logging level, which will | |
* be stored as a string in @log_priority. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_get_log_priority (void * data, | |
NihDBusMessage *message, | |
char ** log_priority) | |
{ | |
const char *priority; | |
nih_assert (message != NULL); | |
nih_assert (log_priority != NULL); | |
switch (nih_log_priority) { | |
case NIH_LOG_DEBUG: | |
priority = "debug"; | |
break; | |
case NIH_LOG_INFO: | |
priority = "info"; | |
break; | |
case NIH_LOG_MESSAGE: | |
priority = "message"; | |
break; | |
case NIH_LOG_WARN: | |
priority = "warn"; | |
break; | |
case NIH_LOG_ERROR: | |
priority = "error"; | |
break; | |
case NIH_LOG_FATAL: | |
priority = "fatal"; | |
break; | |
default: | |
nih_assert_not_reached (); | |
} | |
*log_priority = nih_strdup (message, priority); | |
if (! *log_priority) | |
nih_return_no_memory_error (-1); | |
return 0; | |
} | |
/** | |
* control_set_log_priority: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @log_priority: string log priority to be set. | |
* | |
* Implements the get method for the log_priority property of the | |
* com.ubuntu.Upstart interface. | |
* | |
* Called to change the init daemon's current logging level to that given | |
* as a string in @log_priority. If the string is not recognised, the | |
* com.ubuntu.Upstart.Error.InvalidLogPriority error will be returned. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_set_log_priority (void * data, | |
NihDBusMessage *message, | |
const char * log_priority) | |
{ | |
nih_assert (message != NULL); | |
nih_assert (log_priority != NULL); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to set log priority")); | |
return -1; | |
} | |
if (! strcmp (log_priority, "debug")) { | |
nih_log_set_priority (NIH_LOG_DEBUG); | |
} else if (! strcmp (log_priority, "info")) { | |
nih_log_set_priority (NIH_LOG_INFO); | |
} else if (! strcmp (log_priority, "message")) { | |
nih_log_set_priority (NIH_LOG_MESSAGE); | |
} else if (! strcmp (log_priority, "warn")) { | |
nih_log_set_priority (NIH_LOG_WARN); | |
} else if (! strcmp (log_priority, "error")) { | |
nih_log_set_priority (NIH_LOG_ERROR); | |
} else if (! strcmp (log_priority, "fatal")) { | |
nih_log_set_priority (NIH_LOG_FATAL); | |
} else { | |
nih_dbus_error_raise (DBUS_ERROR_INVALID_ARGS, | |
_("The log priority given was not recognised")); | |
return -1; | |
} | |
return 0; | |
} | |
/** | |
* control_get_bus_type: | |
* | |
* Determine D-Bus bus type to connect to. | |
* | |
* Returns: Type of D-Bus bus to connect to. | |
**/ | |
DBusBusType | |
control_get_bus_type (void) | |
{ | |
return (use_session_bus || user_mode) | |
? DBUS_BUS_SESSION | |
: DBUS_BUS_SYSTEM; | |
} | |
/** | |
* control_notify_disk_writeable: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* | |
* Implements the NotifyDiskWriteable method of the | |
* com.ubuntu.Upstart interface. | |
* | |
* Called to flush the job logs for all jobs that ended before the log | |
* disk became writeable. | |
* | |
* Notes: Session Inits are permitted to make this call. In the common | |
* case of starting a Session Init as a child of a Display Manager this | |
* is somewhat meaningless, but it does mean that if a Session Init were | |
* started from a system job, behaviour would be as expected. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_notify_disk_writeable (void *data, | |
NihDBusMessage *message) | |
{ | |
int ret; | |
Session *session; | |
nih_assert (message != NULL); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to notify disk is writeable")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* "nop" when run from a chroot */ | |
if (session && session->chroot) | |
return 0; | |
ret = log_clear_unflushed (); | |
if (ret < 0) { | |
nih_error_raise_system (); | |
return -1; | |
} | |
return 0; | |
} | |
/** | |
* control_notify_dbus_address: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @address: Address of D-Bus to connect to. | |
* | |
* Implements the NotifyDBusAddress method of the | |
* com.ubuntu.Upstart interface. | |
* | |
* Called to allow the Session Init to connect to the D-Bus | |
* Session Bus when available. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_notify_dbus_address (void *data, | |
NihDBusMessage *message, | |
const char *address) | |
{ | |
nih_assert (message); | |
nih_assert (address); | |
if (getpid () == 1) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("Not permissible to notify D-Bus address for PID 1")); | |
return -1; | |
} | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to notify D-Bus address")); | |
return -1; | |
} | |
/* Ignore as already connected */ | |
if (control_bus) | |
return 0; | |
control_bus_address = nih_strdup (NULL, address); | |
if (! control_bus_address) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_NO_MEMORY, | |
_("Out of Memory")); | |
return -1; | |
} | |
if (control_bus_open () < 0) | |
return -1; | |
return 0; | |
} | |
/** | |
* control_notify_cgroup_manager_address: | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @address: D-Bus address that cgroup manager is connected to. | |
* | |
* Implements the NotifyCGroupManagerAddress method of the | |
* com.ubuntu.Upstart interface. | |
* | |
* Called to allow the cgroup manager to be contacted, | |
* thus enabling the cgroup stanza. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_notify_cgroup_manager_address (void *data, | |
NihDBusMessage *message, | |
const char *address) | |
{ | |
nih_assert (message); | |
nih_assert (address); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to notify cgroup manager address")); | |
return -1; | |
} | |
#ifdef ENABLE_CGROUPS | |
if (! cgroup_support_enabled ()) | |
return 0; | |
/* Already called */ | |
if (cgroup_manager_available ()) | |
return 0; | |
if (! cgroup_manager_set_address (address)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_NO_MEMORY, | |
_("Out of Memory")); | |
return -1; | |
} | |
nih_debug ("set cgroup manager address"); | |
if (! job_class_induct_jobs ()) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_NO_MEMORY, | |
_("Out of Memory")); | |
return -1; | |
} | |
#else | |
nih_debug ("cgroup support not available"); | |
#endif /* ENABLE_CGROUPS */ | |
return 0; | |
} | |
/** | |
* control_bus_flush: | |
* | |
* Drain any remaining messages in the D-Bus queue. | |
**/ | |
static void | |
control_bus_flush (void) | |
{ | |
control_init (); | |
if (! control_bus) | |
return; | |
while (dbus_connection_dispatch (control_bus) == DBUS_DISPATCH_DATA_REMAINS) | |
; | |
} | |
/** | |
* control_prepare_reexec: | |
* | |
* Prepare for a re-exec by allowing the bus connection to be retained | |
* over re-exec and clearing all queued messages. | |
**/ | |
void | |
control_prepare_reexec (void) | |
{ | |
control_init (); | |
/* Necessary to disallow further commands but also to allow the | |
* new instance to open the control server. | |
*/ | |
if (control_server) | |
control_server_close (); | |
control_bus_flush (); | |
} | |
/** | |
* control_conn_to_index: | |
* | |
* @connection: D-Bus connection. | |
* | |
* Convert a control (DBusConnection) connection to an index number | |
* the list of control connections. | |
* | |
* Returns: connection index, or -1 on error. | |
**/ | |
int | |
control_conn_to_index (const DBusConnection *connection) | |
{ | |
int conn_index = 0; | |
int found = FALSE; | |
nih_assert (connection); | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
if (connection == conn) { | |
found = TRUE; | |
break; | |
} | |
conn_index++; | |
} | |
if (! found) | |
return -1; | |
return conn_index; | |
} | |
/** | |
* control_conn_from_index: | |
* | |
* @conn_index: control connection index number. | |
* | |
* Lookup control connection based on index number. | |
* | |
* Returns: existing connection on success, or NULL if connection | |
* not found. | |
**/ | |
DBusConnection * | |
control_conn_from_index (int conn_index) | |
{ | |
int i = 0; | |
nih_assert (conn_index >= 0); | |
nih_assert (control_conns); | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
if (i == conn_index) | |
return conn; | |
i++; | |
} | |
return NULL; | |
} | |
/** | |
* control_bus_release_name: | |
* | |
* Unregister well-known D-Bus name. | |
* | |
* Returns: 0 on success, -1 on raised error. | |
**/ | |
int | |
control_bus_release_name (void) | |
{ | |
DBusError error; | |
int ret; | |
if (! control_bus) | |
return 0; | |
dbus_error_init (&error); | |
ret = dbus_bus_release_name (control_bus, | |
DBUS_SERVICE_UPSTART, | |
&error); | |
if (ret < 0) { | |
nih_dbus_error_raise (error.name, error.message); | |
dbus_error_free (&error); | |
return -1; | |
} | |
return 0; | |
} | |
/** | |
* control_get_state: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @state: output string returned to client. | |
* | |
* Convert internal state to JSON string. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_get_state (void *data, | |
NihDBusMessage *message, | |
char **state) | |
{ | |
Session *session; | |
size_t len; | |
nih_assert (message); | |
nih_assert (state); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to request state")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* We don't want chroot sessions snooping outside their domain. | |
* | |
* Ideally, we'd allow them to query their own session, but the | |
* current implementation doesn't lend itself to that. | |
*/ | |
if (session && session->chroot) { | |
nih_warn (_("Ignoring state query from chroot session")); | |
return 0; | |
} | |
if (state_to_string (state, &len) < 0) | |
goto error; | |
nih_ref (*state, message); | |
return 0; | |
error: | |
nih_dbus_error_raise_printf (DBUS_ERROR_NO_MEMORY, | |
_("Out of Memory")); | |
return -1; | |
} | |
/** | |
* control_restart: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received. | |
* | |
* Implements the Restart method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to request that Upstart performs a stateful re-exec. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_restart (void *data, | |
NihDBusMessage *message) | |
{ | |
Session *session; | |
nih_assert (message != NULL); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to request restart")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* Chroot sessions must not be able to influence | |
* the outside system. | |
* | |
* Making this a NOP is safe since it is the Upstart outside the | |
* chroot which manages all chroot jobs. | |
*/ | |
if (session && session->chroot) { | |
nih_warn (_("Ignoring restart request from chroot session")); | |
return 0; | |
} | |
nih_info (_("Restarting")); | |
stateful_reexec (); | |
return 0; | |
} | |
/** | |
* control_notify_event_emitted | |
* | |
* @event: Event. | |
* | |
* Re-emits an event over DBUS using the EventEmitted signal | |
**/ | |
void | |
control_notify_event_emitted (Event *event) | |
{ | |
nih_assert (event != NULL); | |
control_init (); | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
NIH_ZERO (control_emit_event_emitted (conn, DBUS_PATH_UPSTART, | |
event->name, event->env)); | |
} | |
} | |
/** | |
* control_notify_restarted | |
* | |
* DBUS signal sent when upstart has re-executed itself. | |
**/ | |
void | |
control_notify_restarted (void) | |
{ | |
control_init (); | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
NIH_ZERO (control_emit_restarted (conn, DBUS_PATH_UPSTART)); | |
} | |
} | |
/** | |
* control_set_env_list: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @job_details: name and instance of job to apply operation to, | |
* @vars: array of name[/value] pairs of environment variables to set, | |
* @replace: TRUE if @name should be overwritten if already set, else | |
* FALSE. | |
* | |
* Implements the SetEnvList method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to request Upstart store one or more name/value pairs. | |
* | |
* If @job_details is empty, change will be applied to all job | |
* environments, else only apply changes to specific job environment | |
* encoded within @job_details. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_set_env_list (void *data, | |
NihDBusMessage *message, | |
char * const *job_details, | |
char * const *vars, | |
int replace) | |
{ | |
Session *session; | |
Job *job = NULL; | |
char *job_name = NULL; | |
char *instance = NULL; | |
char * const *var; | |
nih_assert (message); | |
nih_assert (job_details); | |
nih_assert (vars); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to modify job environment")); | |
return -1; | |
} | |
if (getpid () == 1) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("Not permissible to modify PID 1 job environment")); | |
return -1; | |
} | |
if (job_details[0]) { | |
job_name = job_details[0]; | |
/* this can be a null value */ | |
instance = job_details[1]; | |
} | |
/* Verify that job name is valid */ | |
if (job_name && ! strlen (job_name)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Job may not be empty string")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* Chroot sessions must not be able to influence | |
* the outside system. | |
*/ | |
if (session && session->chroot) { | |
nih_warn (_("Ignoring set env request from chroot session")); | |
return 0; | |
} | |
/* Lookup the job */ | |
control_get_job (session, job, job_name, instance); | |
for (var = vars; var && *var; var++) { | |
nih_local char *envvar = NULL; | |
if (! *var) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Variable may not be empty string")); | |
return -1; | |
} | |
/* If variable does not contain a delimiter, add one to ensure | |
* it gets entered into the job environment table. Without the | |
* delimiter, the variable will be silently ignored unless it's | |
* already set in inits environment. But in that case there is | |
* no point in setting such a variable to its already existing | |
* value. | |
*/ | |
if (! strchr (*var, '=')) { | |
envvar = NIH_MUST (nih_sprintf (NULL, "%s=", *var)); | |
} else { | |
envvar = NIH_MUST (nih_strdup (NULL, *var)); | |
} | |
if (job) { | |
/* Modify job-specific environment */ | |
nih_assert (job->env); | |
NIH_MUST (environ_add (&job->env, job, NULL, replace, envvar)); | |
} else if (job_class_environment_set (envvar, replace) < 0) { | |
nih_return_no_memory_error (-1); | |
} | |
} | |
return 0; | |
} | |
/** | |
* control_set_env: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @job_details: name and instance of job to apply operation to, | |
* @var: name[/value] pair of environment variable to set, | |
* @replace: TRUE if @name should be overwritten if already set, else | |
* FALSE. | |
* | |
* Implements the SetEnv method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to request Upstart store a particular name/value pair. | |
* | |
* If @job_details is empty, change will be applied to all job | |
* environments, else only apply changes to specific job environment | |
* encoded within @job_details. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_set_env (void *data, | |
NihDBusMessage *message, | |
char * const *job_details, | |
const char *var, | |
int replace) | |
{ | |
nih_local char **vars = NULL; | |
if (! var) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Variable may not be empty string")); | |
return -1; | |
} | |
vars = NIH_MUST (nih_str_array_new (NULL)); | |
NIH_MUST (nih_str_array_add (&vars, NULL, NULL, var)); | |
return control_set_env_list (data, message, job_details, vars, replace); | |
} | |
/** | |
* control_unset_env_list: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @job_details: name and instance of job to apply operation to, | |
* @names: array of variables to clear from the job environment array. | |
* | |
* Implements the UnsetEnvList method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to request Upstart remove one or more variables from the job | |
* environment array. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_unset_env_list (void *data, | |
NihDBusMessage *message, | |
char * const *job_details, | |
char * const *names) | |
{ | |
Session *session; | |
Job *job = NULL; | |
char *job_name = NULL; | |
char *instance = NULL; | |
char * const *name; | |
nih_assert (message); | |
nih_assert (job_details); | |
nih_assert (names); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to modify job environment")); | |
return -1; | |
} | |
if (getpid () == 1) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("Not permissible to modify PID 1 job environment")); | |
return -1; | |
} | |
if (job_details[0]) { | |
job_name = job_details[0]; | |
/* this can be a null value */ | |
instance = job_details[1]; | |
} | |
/* Verify that job name is valid */ | |
if (job_name && ! strlen (job_name)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Job may not be empty string")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* Chroot sessions must not be able to influence | |
* the outside system. | |
*/ | |
if (session && session->chroot) { | |
nih_warn (_("Ignoring unset env request from chroot session")); | |
return 0; | |
} | |
/* Lookup the job */ | |
control_get_job (session, job, job_name, instance); | |
for (name = names; name && *name; name++) { | |
if (! *name) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Variable may not be empty string")); | |
return -1; | |
} | |
if (job) { | |
/* Modify job-specific environment */ | |
nih_assert (job->env); | |
if (! environ_remove (&job->env, job, NULL, *name)) | |
return -1; | |
} else if (job_class_environment_unset (*name) < 0) { | |
goto error; | |
} | |
} | |
return 0; | |
error: | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
"%s: %s", | |
_("No such variable"), *name); | |
return -1; | |
} | |
/** | |
* control_unset_env: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @job_details: name and instance of job to apply operation to, | |
* @name: variable to clear from the job environment array. | |
* | |
* Implements the UnsetEnv method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to request Upstart remove a particular variable from the job | |
* environment array. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_unset_env (void *data, | |
NihDBusMessage *message, | |
char * const *job_details, | |
const char *name) | |
{ | |
nih_local char **names = NULL; | |
if (! name) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Variable may not be empty string")); | |
return -1; | |
} | |
names = NIH_MUST (nih_str_array_new (NULL)); | |
NIH_MUST (nih_str_array_add (&names, NULL, NULL, name)); | |
return control_unset_env_list (data, message, job_details, names); | |
} | |
/** | |
* control_get_env: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @job_details: name and instance of job to apply operation to, | |
* @name: name of environment variable to retrieve, | |
* @value: value of @name. | |
* | |
* Implements the GetEnv method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to obtain the value of a specified job environment variable. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_get_env (void *data, | |
NihDBusMessage *message, | |
char * const *job_details, | |
const char *name, | |
char **value) | |
{ | |
Session *session; | |
const char *tmp; | |
Job *job = NULL; | |
char *job_name = NULL; | |
char *instance = NULL; | |
nih_assert (message != NULL); | |
nih_assert (job_details); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to query job environment")); | |
return -1; | |
} | |
if (! name || ! *name) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Variable may not be empty string")); | |
return -1; | |
} | |
if (job_details[0]) { | |
job_name = job_details[0]; | |
/* this can be a null value */ | |
instance = job_details[1]; | |
} | |
/* Verify that job name is valid */ | |
if (job_name && ! strlen (job_name)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Job may not be empty string")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* Chroot sessions must not be able to influence | |
* the outside system. | |
*/ | |
if (session && session->chroot) { | |
nih_warn (_("Ignoring get env request from chroot session")); | |
return 0; | |
} | |
/* Lookup the job */ | |
control_get_job (session, job, job_name, instance); | |
if (job) { | |
tmp = environ_get (job->env, name); | |
if (! tmp) | |
goto error; | |
*value = nih_strdup (message, tmp); | |
if (! *value) | |
nih_return_no_memory_error (-1); | |
return 0; | |
} | |
tmp = job_class_environment_get (name); | |
if (! tmp) | |
goto error; | |
*value = nih_strdup (message, tmp); | |
if (! *value) | |
nih_return_no_memory_error (-1); | |
return 0; | |
error: | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
"%s: %s", | |
_("No such variable"), name); | |
return -1; | |
} | |
/** | |
* control_list_env: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @job_details: name and instance of job to apply operation to, | |
* @env: pointer to array of all job environment variables. | |
* | |
* Implements the ListEnv method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to obtain an unsorted array of all environment variables | |
* that will be set in a jobs environment. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_list_env (void *data, | |
NihDBusMessage *message, | |
char * const *job_details, | |
char ***env) | |
{ | |
Session *session; | |
Job *job = NULL; | |
char *job_name = NULL; | |
char *instance = NULL; | |
nih_assert (message); | |
nih_assert (job_details); | |
nih_assert (env); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to query job environment")); | |
return -1; | |
} | |
if (job_details[0]) { | |
job_name = job_details[0]; | |
/* this can be a null value */ | |
instance = job_details[1]; | |
} | |
/* Verify that job name is valid */ | |
if (job_name && ! strlen (job_name)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Job may not be empty string")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* Lookup the job */ | |
control_get_job (session, job, job_name, instance); | |
if (job) { | |
*env = nih_str_array_copy (job, NULL, job->env); | |
if (! *env) | |
nih_return_no_memory_error (-1); | |
return 0; | |
} | |
*env = job_class_environment_get_all (message); | |
if (! *env) | |
nih_return_no_memory_error (-1); | |
return 0; | |
} | |
/** | |
* control_reset_env: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received, | |
* @job_details: name and instance of job to apply operation to. | |
* | |
* Implements the ResetEnv method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to reset the environment all subsequent jobs will run in to | |
* the default minimal environment. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_reset_env (void *data, | |
NihDBusMessage *message, | |
char * const *job_details) | |
{ | |
Session *session; | |
Job *job = NULL; | |
char *job_name = NULL; | |
char *instance = NULL; | |
nih_assert (message); | |
nih_assert (job_details); | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to modify job environment")); | |
return -1; | |
} | |
if (getpid () == 1) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("Not permissible to modify PID 1 job environment")); | |
return -1; | |
} | |
if (job_details[0]) { | |
job_name = job_details[0]; | |
/* this can be a null value */ | |
instance = job_details[1]; | |
} | |
/* Verify that job name is valid */ | |
if (job_name && ! strlen (job_name)) { | |
nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS, | |
_("Job may not be empty string")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
/* Chroot sessions must not be able to influence | |
* the outside system. | |
*/ | |
if (session && session->chroot) { | |
nih_warn (_("Ignoring reset env request from chroot session")); | |
return 0; | |
} | |
/* Lookup the job */ | |
control_get_job (session, job, job_name, instance); | |
if (job) { | |
size_t len; | |
if (job->env) { | |
nih_free (job->env); | |
job->env = NULL; | |
} | |
job->env = job_class_environment (job, job->class, &len); | |
if (! job->env) | |
nih_return_system_error (-1); | |
return 0; | |
} | |
job_class_environment_reset (); | |
return 0; | |
} | |
/** | |
* control_get_origin_uid: | |
* @message: D-Bus connection and message received, | |
* @uid: returned uid value. | |
* | |
* Returns TRUE: if @uid now contains uid corresponding to @message, | |
* else FALSE. | |
**/ | |
static int | |
control_get_origin_uid (NihDBusMessage *message, uid_t *uid) | |
{ | |
DBusError dbus_error; | |
unsigned long unix_user = 0; | |
const char *sender; | |
nih_assert (message); | |
nih_assert (uid); | |
dbus_error_init (&dbus_error); | |
if (! message->message || ! message->connection) | |
return FALSE; | |
sender = dbus_message_get_sender (message->message); | |
if (sender) { | |
unix_user = dbus_bus_get_unix_user (message->connection, sender, | |
&dbus_error); | |
if (unix_user == (unsigned long)-1) { | |
dbus_error_free (&dbus_error); | |
return FALSE; | |
} | |
} else { | |
if (! dbus_connection_get_unix_user (message->connection, | |
&unix_user)) { | |
return FALSE; | |
} | |
} | |
*uid = (uid_t)unix_user; | |
return TRUE; | |
} | |
/** | |
* control_check_permission: | |
* | |
* @message: D-Bus connection and message received. | |
* | |
* Determine if caller should be allowed to make a control request. | |
* | |
* Note that these permission checks rely on D-Bus to limit | |
* session bus access to the same user. | |
* | |
* Returns: TRUE if permission is granted, else FALSE. | |
**/ | |
static int | |
control_check_permission (NihDBusMessage *message) | |
{ | |
int ret; | |
uid_t uid; | |
pid_t pid; | |
uid_t origin_uid = 0; | |
nih_assert (message); | |
uid = getuid (); | |
pid = getpid (); | |
ret = control_get_origin_uid (message, &origin_uid); | |
/* Its possible that D-Bus might be unable to determine the user | |
* making the request. In this case, deny the request unless | |
* we're running as a Session Init or via the test harness. | |
*/ | |
if ((ret && origin_uid == uid) || user_mode || (uid && pid != 1)) | |
return TRUE; | |
return FALSE; | |
} | |
/** | |
* control_session_file_create: | |
* | |
* Create session file if possible. | |
* | |
* Errors are not fatal - the file is just not created. | |
**/ | |
static void | |
control_session_file_create (void) | |
{ | |
nih_local char *session_dir = NULL; | |
FILE *f; | |
int ret; | |
nih_assert (control_server_address); | |
session_dir = get_session_dir (); | |
if (! session_dir) | |
return; | |
NIH_MUST (nih_strcat_sprintf (&session_file, NULL, "%s/%d%s", | |
session_dir, (int)getpid (), SESSION_EXT)); | |
f = fopen (session_file, "w"); | |
if (! f) { | |
nih_error ("%s: %s", _("unable to create session file"), session_file); | |
return; | |
} | |
ret = fprintf (f, SESSION_ENV "=%s\n", control_server_address); | |
if (ret < 0) | |
nih_error ("%s: %s", _("unable to write session file"), session_file); | |
fclose (f); | |
} | |
/** | |
* control_session_file_remove: | |
* | |
* Delete session file. | |
* | |
* Errors are not fatal. | |
**/ | |
static void | |
control_session_file_remove (void) | |
{ | |
if (session_file) | |
(void)unlink (session_file); | |
} | |
/** | |
* control_session_end: | |
* | |
* @data: not used, | |
* @message: D-Bus connection and message received. | |
* | |
* Implements the EndSession method of the com.ubuntu.Upstart | |
* interface. | |
* | |
* Called to request that Upstart stop all jobs and exit. Only | |
* appropriate when running as a Session Init and user wishes to | |
* 'logout'. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
control_end_session (void *data, | |
NihDBusMessage *message) | |
{ | |
Session *session; | |
nih_assert (message); | |
/* Not supported at the system level */ | |
if (getpid () == 1) | |
return 0; | |
if (! control_check_permission (message)) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to end session")); | |
return -1; | |
} | |
/* Get the relevant session */ | |
session = session_from_dbus (NULL, message); | |
if (session && session->chroot) { | |
nih_warn (_("Ignoring session end request from chroot session")); | |
return 0; | |
} | |
quiesce (QUIESCE_REQUESTER_SESSION); | |
return 0; | |
} | |
/** | |
* control_serialise_bus_address: | |
* | |
* Convert control_bus_address into JSON representation. | |
* | |
* Returns: JSON string representing control_bus_address or NULL if | |
* control_bus_address not set or on error. | |
* | |
* Note: If NULL is returned, check the value of control_bus_address | |
* itself to determine if the error is real. | |
**/ | |
json_object * | |
control_serialise_bus_address (void) | |
{ | |
control_init (); | |
/* A NULL return represents a JSON null */ | |
return control_bus_address | |
? json_object_new_string (control_bus_address) | |
: NULL; | |
} | |
/** | |
* control_deserialise_bus_address: | |
* | |
* @json: root of JSON-serialised state. | |
* | |
* Convert JSON representation of control_bus_address back into a native | |
* string. | |
* | |
* Returns: 0 on success, -1 on error. | |
**/ | |
int | |
control_deserialise_bus_address (json_object *json) | |
{ | |
const char *address; | |
nih_assert (json); | |
nih_assert (! control_bus_address); | |
control_init (); | |
/* control_bus_address was never set */ | |
if (state_check_json_type (json, null)) | |
return 0; | |
if (! state_check_json_type (json, string)) | |
goto error; | |
address = json_object_get_string (json); | |
if (! address) | |
goto error; | |
control_bus_address = nih_strdup (NULL, address); | |
if (! control_bus_address) | |
goto error; | |
return 0; | |
error: | |
return -1; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* upstart | |
* | |
* job.c - core state machine of tasks and services | |
* | |
* Copyright © 2010,2011 Canonical Ltd. | |
* Author: Scott James Remnant <[email protected]>. | |
* | |
* This program is free software; you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License version 2, as | |
* published by the Free Software Foundation. | |
* | |
* This program 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 this program; if not, write to the Free Software Foundation, Inc., | |
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
*/ | |
#ifdef HAVE_CONFIG_H | |
# include <config.h> | |
#endif /* HAVE_CONFIG_H */ | |
#include <sys/types.h> | |
#include <errno.h> | |
#include <string.h> | |
#include <nih/macros.h> | |
#include <nih/alloc.h> | |
#include <nih/string.h> | |
#include <nih/list.h> | |
#include <nih/hash.h> | |
#include <nih/signal.h> | |
#include <nih/logging.h> | |
#include <nih-dbus/dbus_error.h> | |
#include <nih-dbus/dbus_message.h> | |
#include <nih-dbus/dbus_object.h> | |
#include <nih-dbus/dbus_util.h> | |
#include "dbus/upstart.h" | |
#include "events.h" | |
#include "environ.h" | |
#include "process.h" | |
#include "session.h" | |
#include "job_class.h" | |
#include "job.h" | |
#include "job_process.h" | |
#include "event.h" | |
#include "event_operator.h" | |
#include "blocked.h" | |
#include "control.h" | |
#include "parse_job.h" | |
#include "state.h" | |
#include "apparmor.h" | |
#ifdef ENABLE_CGROUPS | |
#include "cgroup.h" | |
#endif /* ENABLE_CGROUPS */ | |
#include "com.ubuntu.Upstart.Job.h" | |
#include "com.ubuntu.Upstart.Instance.h" | |
/* Prototypes for static functions */ | |
static const char * | |
job_goal_enum_to_str (JobGoal goal) | |
__attribute__ ((warn_unused_result)); | |
static JobGoal job_goal_str_to_enum (const char *goal) | |
__attribute__ ((warn_unused_result)); | |
static const char * | |
job_trace_state_enum_to_str (TraceState state) | |
__attribute__ ((warn_unused_result)); | |
static TraceState | |
job_trace_state_str_to_enum (const char *state) | |
__attribute__ ((warn_unused_result)); | |
static json_object * | |
job_serialise_kill_timer (NihTimer *timer) | |
__attribute__ ((warn_unused_result)); | |
static NihTimer * | |
job_deserialise_kill_timer (json_object *json) | |
__attribute__ ((warn_unused_result)); | |
static int | |
job_destroy (Job *job); | |
/** | |
* job_destroy: | |
* | |
* @job: Job. | |
* | |
* Called automatically when Job is being destroyed. | |
* | |
* Returns: 0 always. | |
**/ | |
static int | |
job_destroy (Job *job) | |
{ | |
int i; | |
nih_assert (job); | |
/* Free any associated NihIo's to avoid the handlers getting | |
* called potentially after the job has been freed. | |
*/ | |
if (job->process_data) { | |
for (i = 0; i < PROCESS_LAST; i++) { | |
if (job->process_data[i]) { | |
nih_free (job->process_data[i]); | |
job->process_data[i] = NULL; | |
} | |
} | |
} | |
nih_list_destroy (&job->entry); | |
return 0; | |
} | |
/** | |
* job_new: | |
* @class: class of job, | |
* @name: name for new instance. | |
* | |
* Allocates and returns a new Job structure for the @class given, | |
* appending it to the list of instances for @class. The returned job | |
* will also be an nih_alloc() child of @class. | |
* | |
* @name is used to uniquely identify the instance and is normally | |
* generated by expanding the @class's instance member. | |
* | |
* Returns: newly allocated job structure or NULL if insufficient memory. | |
**/ | |
Job * | |
job_new (JobClass *class, | |
const char *name) | |
{ | |
Job *job; | |
int i; | |
nih_assert (class != NULL); | |
nih_assert (name != NULL); | |
control_init (); | |
job = nih_new (class, Job); | |
if (! job) | |
return NULL; | |
nih_list_init (&job->entry); | |
/* Ensure unset before destructor could possibly be called */ | |
job->process_data = NULL; | |
nih_alloc_set_destructor (job, job_destroy); | |
job->name = nih_strdup (job, name); | |
if (! job->name) | |
goto error; | |
job->class = class; | |
if (job->class->session && job->class->session->chroot) { | |
/* JobClass already contains a valid D-Bus path prefix for the job */ | |
job->path = nih_dbus_path (job, class->path, job->name, NULL); | |
} else { | |
job->path = nih_dbus_path (job, DBUS_PATH_UPSTART, "jobs", | |
class->name, job->name, NULL); | |
} | |
if (! job->path) | |
goto error; | |
job->goal = JOB_STOP; | |
job->state = JOB_WAITING; | |
job->env = NULL; | |
job->start_env = NULL; | |
job->stop_env = NULL; | |
job->stop_on = NULL; | |
if (class->stop_on) { | |
job->stop_on = event_operator_copy (job, class->stop_on); | |
if (! job->stop_on) | |
goto error; | |
} | |
job->fds = NULL; | |
job->num_fds = 0; | |
job->pid = nih_alloc (job, sizeof (pid_t) * PROCESS_LAST); | |
if (! job->pid) | |
goto error; | |
for (i = 0; i < PROCESS_LAST; i++) | |
job->pid[i] = 0; | |
/* Each job process needs its own log object to ensure sane | |
* behaviour: consider a post-start that starts and ends | |
* before the main process ends: it will be reaped (and its log | |
* flushed) before the main process has a chance to have its log | |
* drained. | |
*/ | |
job->log = nih_alloc (job, sizeof (Log *) * PROCESS_LAST); | |
if (! job->log) | |
goto error; | |
for (i = 0; i < PROCESS_LAST; i++) | |
job->log[i] = NULL; | |
job->blocker = NULL; | |
nih_list_init (&job->blocking); | |
job->kill_timer = NULL; | |
job->kill_process = PROCESS_INVALID; | |
job->failed = FALSE; | |
job->failed_process = PROCESS_INVALID; | |
job->exit_status = 0; | |
job->respawn_time = 0; | |
job->respawn_count = 0; | |
job->trace_forks = 0; | |
job->trace_state = TRACE_NONE; | |
nih_hash_add (class->instances, &job->entry); | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
job_register (job, conn, TRUE); | |
} | |
/* Since some job processes can run in parallel, we must ensure | |
* that the asynchronous-spawning of such job processes is | |
* handled by providing a handler for each pid. | |
* | |
* Strictly, this is only necessary for certain combinations of | |
* job processes (such as PROCESS_MAIN and PROCESS_POST_START), | |
* however for consistency with other entities (such as Log), we | |
* create a slot for all job processes since there is minimal | |
* overhead (a single pointer) for those job processes tha | |
* cannot run in parallel with others. | |
*/ | |
job->process_data = nih_alloc (job, sizeof (JobProcessData *) * PROCESS_LAST); | |
if (! job->process_data) | |
goto error; | |
for (i = 0; i < PROCESS_LAST; i++) | |
job->process_data[i] = NULL; | |
return job; | |
error: | |
nih_free (job); | |
return NULL; | |
} | |
/** | |
* job_register: | |
* @job: job to register, | |
* @conn: connection to register for, | |
* @signal: emit the InstanceAdded signal. | |
* | |
* Register the @job instance with the D-Bus connection @conn, using | |
* the path set when the job was created. | |
**/ | |
void | |
job_register (Job *job, | |
DBusConnection *conn, | |
int signal) | |
{ | |
nih_assert (job != NULL); | |
nih_assert (conn != NULL); | |
NIH_MUST (nih_dbus_object_new (job, conn, job->path, | |
job_interfaces, job)); | |
nih_debug ("Registered instance %s", job->path); | |
if (signal) | |
NIH_ZERO (job_class_emit_instance_added (conn, job->class->path, | |
job->path)); | |
} | |
/** | |
* job_change_goal: | |
* @job: job to change goal of, | |
* @goal: goal to change to. | |
* | |
* This function changes the current goal of a @job to the new @goal given, | |
* performing any necessary state changes or actions (such as killing | |
* the running process) to correctly enter the new goal. | |
* | |
* If the job is not in a rest state (WAITING or RUNNING), this has no | |
* other effect than changing the goal; since the job is waiting on some | |
* other event. The goal change will cause it to take action to head | |
* towards stopped. | |
* | |
* If the job is in the WAITING state and @goal is START, the job will | |
* begin to be started and will block in the STARTING state for an event | |
* to finish. | |
* | |
* If the job is in the RUNNING state and @goal is STOP, the job will | |
* begin to be stopped and will either block in the PRE-STOP state for | |
* the pre-stop script or the STOPPING state for an event to finish. | |
* | |
* Thus in all circumstances, @job is safe to use once this function | |
* returns. Though further calls to job_change_state may change that as | |
* noted. | |
**/ | |
void | |
job_change_goal (Job *job, | |
JobGoal goal) | |
{ | |
nih_assert (job != NULL); | |
if (job->goal == goal) | |
return; | |
nih_info (_("%s goal changed from %s to %s"), job_name (job), | |
job_goal_name (job->goal), job_goal_name (goal)); | |
job->goal = goal; | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
NIH_ZERO (job_emit_goal_changed ( | |
conn, job->path, | |
job_goal_name (job->goal))); | |
} | |
/* Normally whatever process or event is associated with the state | |
* will finish naturally, so all we need do is change the goal and | |
* we'll change direction through the state machine at that point. | |
* | |
* The exceptions are the natural rest states of waiting and a | |
* running process; these need induction to get them moving. | |
*/ | |
switch (goal) { | |
case JOB_START: | |
if (job->state == JOB_WAITING) | |
job_change_state (job, job_next_state (job)); | |
break; | |
case JOB_STOP: | |
if (job->state == JOB_RUNNING) | |
job_change_state (job, job_next_state (job)); | |
break; | |
case JOB_RESPAWN: | |
break; | |
default: | |
nih_assert_not_reached (); | |
} | |
} | |
/** | |
* job_change_state: | |
* @job: job to change state of, | |
* @state: state to change to. | |
* | |
* This function changes the current state of a @job to the new @state | |
* given, performing any actions to correctly enter the new state (such | |
* as spawning scripts or processes). | |
* | |
* The associated event is also queued by this function. | |
* | |
* Some state transitions are not be permitted and will result in an | |
* assertion failure. Also some state transitions may result in further | |
* transitions, so the state when this function returns may not be the | |
* state requested. | |
* | |
* WARNING: On return from this function, @job may no longer be valid | |
* since it will be freed once it becomes fully stopped. | |
**/ | |
void | |
job_change_state (Job *job, | |
JobState state) | |
{ | |
nih_assert (job != NULL); | |
while (job->state != state) { | |
JobState old_state; | |
int unused; | |
/* If we got blocked during async spawns, stop | |
* transitions. | |
*/ | |
if (job->blocker) | |
return; | |
nih_info (_("%s state changed from %s to %s"), job_name (job), | |
job_state_name (job->state), job_state_name (state)); | |
old_state = job->state; | |
job->state = state; | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
NIH_ZERO (job_emit_state_changed ( | |
conn, job->path, | |
job_state_name (job->state))); | |
} | |
/* Perform whatever action is necessary to enter the new | |
* state, such as executing a process or emitting an event. | |
*/ | |
switch (job->state) { | |
case JOB_STARTING: | |
nih_assert (job->goal == JOB_START); | |
nih_assert ((old_state == JOB_WAITING) | |
|| (old_state == JOB_POST_STOP)); | |
/* Throw away our old environment and use the newly | |
* set environment from now on; unless that's NULL | |
* in which case we just keep our old environment. | |
*/ | |
if (job->start_env) { | |
if (job->env) | |
nih_unref (job->env, job); | |
job->env = job->start_env; | |
job->start_env = NULL; | |
} | |
/* Throw away the stop environment */ | |
if (job->stop_env) { | |
nih_unref (job->stop_env, job); | |
job->stop_env = NULL; | |
} | |
/* Clear any old failed information */ | |
job->failed = FALSE; | |
job->failed_process = PROCESS_INVALID; | |
job->exit_status = 0; | |
job->blocker = job_emit_event (job); | |
break; | |
case JOB_SECURITY_SPAWNING: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_STARTING); | |
if (job->class->process[PROCESS_SECURITY] | |
&& apparmor_available()) { | |
job_process_start (job, PROCESS_SECURITY); | |
} | |
state = job_next_state (job); | |
break; | |
case JOB_SECURITY: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_SECURITY_SPAWNING); | |
if (! (job->class->process[PROCESS_SECURITY] | |
&& apparmor_available())) { | |
state = job_next_state (job); | |
} | |
break; | |
case JOB_PRE_STARTING: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_SECURITY); | |
/* spawn pre-start asynchronously, child | |
* watcher asynchronously will change goal to | |
* stop if spawning fails. | |
*/ | |
if (job->class->process[PROCESS_PRE_START]) { | |
job_process_start (job, PROCESS_PRE_START); | |
} | |
state = job_next_state (job); | |
break; | |
case JOB_PRE_START: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_PRE_STARTING); | |
/* if no pre-start process, go to next | |
* state. otherwise async child watcher will | |
* trigger us to go to the next state */ | |
if (! job->class->process[PROCESS_PRE_START]) | |
state = job_next_state (job); | |
break; | |
case JOB_SPAWNING: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_PRE_START); | |
if (job->class->process[PROCESS_MAIN]) { | |
job_process_start (job, PROCESS_MAIN); | |
} | |
state = job_next_state (job); | |
break; | |
case JOB_SPAWNED: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_SPAWNING); | |
if (! job->class->process[PROCESS_MAIN]) { | |
state = job_next_state (job); | |
} | |
break; | |
case JOB_POST_STARTING: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_SPAWNED); | |
if (job->class->process[PROCESS_POST_START]) { | |
job_process_start (job, PROCESS_POST_START); | |
} | |
state = job_next_state (job); | |
break; | |
case JOB_POST_START: | |
nih_assert (job->goal == JOB_START); | |
nih_assert (old_state == JOB_POST_STARTING); | |
if (! job->class->process[PROCESS_POST_START]) { | |
state = job_next_state (job); | |
} | |
break; | |
case JOB_RUNNING: | |
nih_assert (job->goal == JOB_START); | |
nih_assert ((old_state == JOB_POST_START) | |
|| (old_state == JOB_PRE_STOP)); | |
if (old_state == JOB_PRE_STOP) { | |
/* Throw away the stop environment */ | |
if (job->stop_env) { | |
nih_unref (job->stop_env, job); | |
job->stop_env = NULL; | |
} | |
/* Cancel the stop attempt */ | |
job_finished (job, FALSE); | |
} else { | |
job_emit_event (job); | |
/* If we're not a task, our goal is to be | |
* running. | |
*/ | |
if (! job->class->task) | |
job_finished (job, FALSE); | |
} | |
break; | |
case JOB_PRE_STOPPING: | |
nih_assert (job->goal == JOB_STOP); | |
nih_assert (old_state == JOB_RUNNING); | |
if (job->class->process[PROCESS_PRE_STOP]) { | |
job_process_start (job, PROCESS_PRE_STOP); | |
} | |
state = job_next_state (job); | |
break; | |
case JOB_PRE_STOP: | |
nih_assert (job->goal == JOB_STOP); | |
nih_assert (old_state == JOB_PRE_STOPPING); | |
if (! job->class->process[PROCESS_PRE_STOP]) { | |
state = job_next_state (job); | |
} | |
break; | |
case JOB_STOPPING: | |
nih_assert ((old_state == JOB_STARTING) | |
|| (old_state == JOB_PRE_STARTING) | |
|| (old_state == JOB_PRE_START) | |
|| (old_state == JOB_SECURITY) | |
|| (old_state == JOB_SPAWNED) | |
|| (old_state == JOB_POST_START) | |
|| (old_state == JOB_RUNNING) | |
|| (old_state == JOB_PRE_STOP)); | |
job->blocker = job_emit_event (job); | |
break; | |
case JOB_KILLED: | |
nih_assert (old_state == JOB_STOPPING); | |
if (job->class->process[PROCESS_MAIN] | |
&& (job->pid[PROCESS_MAIN] > 0)) { | |
job_process_kill (job, PROCESS_MAIN); | |
} else { | |
state = job_next_state (job); | |
} | |
break; | |
case JOB_POST_STOPPING: | |
nih_assert (old_state == JOB_KILLED); | |
if (job->class->process[PROCESS_POST_STOP]) { | |
job_process_start (job, PROCESS_POST_STOP); | |
} | |
state = job_next_state (job); | |
break; | |
case JOB_POST_STOP: | |
nih_assert (old_state == JOB_POST_STOPPING); | |
if (! job->class->process[PROCESS_POST_STOP]) { | |
state = job_next_state (job); | |
} | |
break; | |
case JOB_WAITING: | |
nih_assert (job->goal == JOB_STOP); | |
nih_assert ((old_state == JOB_POST_STOP) | |
|| (old_state == JOB_STARTING)); | |
job_emit_event (job); | |
job_finished (job, FALSE); | |
/* Remove the job from the list of instances and | |
* then allow a better class to replace us | |
* in the hash table if we have no other instances | |
* and there is one. | |
*/ | |
nih_list_remove (&job->entry); | |
unused = job_class_reconsider (job->class); | |
/* If the class is due to be deleted, free it | |
* taking the job with it; otherwise free the | |
* job. | |
*/ | |
if (job->class->deleted && unused) { | |
nih_debug ("Destroyed unused job %s", | |
job->class->name); | |
nih_free (job->class); | |
} else { | |
nih_debug ("Destroyed inactive instance %s", | |
job_name (job)); | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
NIH_ZERO (job_class_emit_instance_removed ( | |
conn, | |
job->class->path, | |
job->path)); | |
} | |
/* Destroy the instance */ | |
nih_free (job); | |
} | |
return; | |
} | |
} | |
} | |
/** | |
* job_next_state: | |
* @job: job undergoing state change. | |
* | |
* The next state a job needs to change into is not always obvious as it | |
* depends both on the current state and the ultimate goal of the job, ie. | |
* whether we're moving towards stop or start. | |
* | |
* This function contains the logic to decide the next state the job should | |
* be in based on the current state and goal. | |
* | |
* It is up to the caller to ensure the goal is set appropriately before | |
* calling this function, for example setting it to JOB_STOP if something | |
* failed. It is also up to the caller to actually set the new state as | |
* this simply returns the suggested one. | |
* | |
* Returns: suggested state to change to. | |
**/ | |
JobState | |
job_next_state (Job *job) | |
{ | |
nih_assert (job != NULL); | |
switch (job->state) { | |
case JOB_WAITING: | |
switch (job->goal) { | |
case JOB_STOP: | |
nih_assert_not_reached (); | |
case JOB_START: | |
return JOB_STARTING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_STARTING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_SECURITY_SPAWNING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_SECURITY_SPAWNING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_SECURITY; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_SECURITY: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_PRE_STARTING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_PRE_STARTING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_PRE_START; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_PRE_START: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_SPAWNING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_SPAWNING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_SPAWNED; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_SPAWNED: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_POST_STARTING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_POST_STARTING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_POST_START; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_POST_START: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_RUNNING; | |
case JOB_RESPAWN: | |
job_change_goal (job, JOB_START); | |
return JOB_STOPPING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_RUNNING: | |
switch (job->goal) { | |
case JOB_STOP: | |
if (job->class->process[PROCESS_MAIN] | |
&& (job->pid[PROCESS_MAIN] > 0)) { | |
return JOB_PRE_STOPPING; | |
} else { | |
return JOB_STOPPING; | |
} | |
case JOB_START: | |
return JOB_STOPPING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_PRE_STOPPING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_PRE_STOP; | |
case JOB_START: | |
return JOB_PRE_STOP; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_PRE_STOP: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_STOPPING; | |
case JOB_START: | |
return JOB_RUNNING; | |
case JOB_RESPAWN: | |
job_change_goal (job, JOB_START); | |
return JOB_STOPPING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_STOPPING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_KILLED; | |
case JOB_START: | |
return JOB_KILLED; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_KILLED: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_POST_STOPPING; | |
case JOB_START: | |
return JOB_POST_STOPPING; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_POST_STOPPING: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_POST_STOP; | |
case JOB_START: | |
return JOB_POST_STOP; | |
default: | |
nih_assert_not_reached (); | |
} | |
case JOB_POST_STOP: | |
switch (job->goal) { | |
case JOB_STOP: | |
return JOB_WAITING; | |
case JOB_START: | |
return JOB_STARTING; | |
default: | |
nih_assert_not_reached (); | |
} | |
default: | |
nih_assert_not_reached (); | |
} | |
} | |
/** | |
* job_failed: | |
* @job: job that has failed, | |
* @process: process that failed, | |
* @status: status of @process at failure. | |
* | |
* Mark @job as having failed, unless it already has been marked so, storing | |
* @process and @status so that they may show up as arguments and environment | |
* to the stop and stopped events generated for the job. | |
* | |
* Additionally this marks the start and stop events as failed as well; this | |
* is reported to the emitter of the event, and will also cause a failed event | |
* to be generated after the event completes. | |
* | |
* @process may be -1 to indicate a failure to respawn, and @exit_status | |
* may be -1 to indicate a spawn failure. | |
**/ | |
void | |
job_failed (Job *job, | |
ProcessType process, | |
int status) | |
{ | |
nih_assert (job != NULL); | |
if (job->failed) | |
return; | |
job->failed = TRUE; | |
job->failed_process = process; | |
job->exit_status = status; | |
NIH_LIST_FOREACH (control_conns, iter) { | |
NihListEntry *entry = (NihListEntry *)iter; | |
DBusConnection *conn = (DBusConnection *)entry->data; | |
NIH_ZERO (job_emit_failed (conn, job->path, status)); | |
} | |
job_finished (job, TRUE); | |
} | |
/** | |
* job_finished: | |
* @job: job that is blocking, | |
* @failed: mark events as failed. | |
* | |
* This function unblocks any events blocking on @job; it is called when the | |
* job reaches a rest state (waiting for all, running for services), when a | |
* new command is received or when the job fails. | |
* | |
* If @failed is TRUE then the events that are blocking will be marked as | |
* failed. | |
**/ | |
void | |
job_finished (Job *job, | |
int failed) | |
{ | |
nih_assert (job != NULL); | |
NIH_LIST_FOREACH_SAFE (&job->blocking, iter) { | |
Blocked *blocked = (Blocked *)iter; | |
switch (blocked->type) { | |
case BLOCKED_EVENT: | |
if (failed) | |
blocked->event->failed = TRUE; | |
event_unblock (blocked->event); | |
break; | |
case BLOCKED_JOB_START_METHOD: | |
if (failed) { | |
NIH_ZERO (nih_dbus_message_error ( | |
blocked->message, | |
DBUS_INTERFACE_UPSTART ".Error.JobFailed", | |
_("Job failed to start"))); | |
} else { | |
NIH_ZERO (job_class_start_reply ( | |
blocked->message, | |
job->path)); | |
} | |
break; | |
case BLOCKED_JOB_STOP_METHOD: | |
if (failed) { | |
NIH_ZERO (nih_dbus_message_error ( | |
blocked->message, | |
DBUS_INTERFACE_UPSTART ".Error.JobFailed", | |
_("Job failed while stopping"))); | |
} else { | |
NIH_ZERO (job_class_stop_reply ( | |
blocked->message)); | |
} | |
break; | |
case BLOCKED_JOB_RESTART_METHOD: | |
if (failed) { | |
NIH_ZERO (nih_dbus_message_error ( | |
blocked->message, | |
DBUS_INTERFACE_UPSTART ".Error.JobFailed", | |
_("Job failed to restart"))); | |
} else { | |
NIH_ZERO (job_class_restart_reply ( | |
blocked->message, | |
job->path)); | |
} | |
break; | |
case BLOCKED_INSTANCE_START_METHOD: | |
if (failed) { | |
NIH_ZERO (nih_dbus_message_error ( | |
blocked->message, | |
DBUS_INTERFACE_UPSTART ".Error.JobFailed", | |
_("Job failed to start"))); | |
} else { | |
NIH_ZERO (job_start_reply (blocked->message)); | |
} | |
break; | |
case BLOCKED_INSTANCE_STOP_METHOD: | |
if (failed) { | |
NIH_ZERO (nih_dbus_message_error ( | |
blocked->message, | |
DBUS_INTERFACE_UPSTART ".Error.JobFailed", | |
_("Job failed while stopping"))); | |
} else { | |
NIH_ZERO (job_stop_reply (blocked->message)); | |
} | |
break; | |
case BLOCKED_INSTANCE_RESTART_METHOD: | |
if (failed) { | |
NIH_ZERO (nih_dbus_message_error ( | |
blocked->message, | |
DBUS_INTERFACE_UPSTART ".Error.JobFailed", | |
_("Job failed to restart"))); | |
} else { | |
NIH_ZERO (job_restart_reply (blocked->message)); | |
} | |
break; | |
default: | |
nih_assert_not_reached (); | |
} | |
nih_free (blocked); | |
} | |
} | |
/** | |
* job_emit_event: | |
* @job: job generating the event. | |
* | |
* Called from a state change because it believes an event should be | |
* emitted. Constructs the event with the right arguments and environment | |
* and adds it to the pending queue. | |
* | |
* The starting and stopping events will record the job as blocking on | |
* the event, and will change the job's state when they finish. | |
* | |
* The stopping and stopped events have an extra argument that is "ok" if | |
* the job terminated successfully, or "failed" if it terminated with an | |
* error. If failed, a further argument indicates which process it was | |
* that caused the failure and either an EXIT_STATUS or EXIT_SIGNAL | |
* environment variable detailing it. | |
* | |
* Returns: new Event in the queue. | |
**/ | |
Event * | |
job_emit_event (Job *job) | |
{ | |
Event *event; | |
const char *name; | |
int block = FALSE, stop = FALSE; | |
nih_local char **env = NULL; | |
char **e; | |
size_t len; | |
nih_assert (job != NULL); | |
switch (job->state) { | |
case JOB_STARTING: | |
name = JOB_STARTING_EVENT; | |
block = TRUE; | |
break; | |
case JOB_RUNNING: | |
name = JOB_STARTED_EVENT; | |
break; | |
case JOB_STOPPING: | |
name = JOB_STOPPING_EVENT; | |
block = TRUE; | |
stop = TRUE; | |
break; | |
case JOB_WAITING: | |
name = JOB_STOPPED_EVENT; | |
stop = TRUE; | |
break; | |
default: | |
nih_assert_not_reached (); | |
} | |
len = 0; | |
env = NIH_MUST (nih_str_array_new (NULL)); | |
/* Add the job and instance name */ | |
NIH_MUST (environ_set (&env, NULL, &len, TRUE, | |
"JOB=%s", job->class->name)); | |
NIH_MUST (environ_set (&env, NULL, &len, TRUE, | |
"INSTANCE=%s", job->name)); | |
/* Stop events include a "failed" argument if a process failed, | |
* otherwise stop events have an "ok" argument. | |
*/ | |
if (stop && job->failed) { | |
NIH_MUST (environ_add (&env, NULL, &len, TRUE, | |
"RESULT=failed")); | |
/* Include information about the process that failed, and | |
* the signal/exit information. If it was the spawn itself | |
* that failed, we don't include signal/exit information and | |
* if it was a respawn failure, we use the special "respawn" | |
* argument instead of the process name, | |
*/ | |
if ((job->failed_process != PROCESS_INVALID) | |
&& (job->exit_status != -1)) { | |
NIH_MUST (environ_set (&env, NULL, &len, TRUE, | |
"PROCESS=%s", | |
process_name (job->failed_process))); | |
/* If the job was terminated by a signal, that | |
* will be stored in the higher byte and we | |
* set EXIT_SIGNAL instead of EXIT_STATUS. | |
*/ | |
if (job->exit_status & ~0xff) { | |
const char *sig; | |
sig = nih_signal_to_name (job->exit_status >> 8); | |
if (sig) { | |
NIH_MUST (environ_set (&env, NULL, &len, TRUE, | |
"EXIT_SIGNAL=%s", sig)); | |
} else { | |
NIH_MUST (environ_set (&env, NULL, &len, TRUE, | |
"EXIT_SIGNAL=%d", job->exit_status >> 8)); | |
} | |
} else { | |
NIH_MUST (environ_set (&env, NULL, &len, TRUE, | |
"EXIT_STATUS=%d", job->exit_status)); | |
} | |
} else if (job->failed_process != PROCESS_INVALID) { | |
NIH_MUST (environ_set (&env, NULL, &len, TRUE, | |
"PROCESS=%s", | |
process_name (job->failed_process))); | |
} else { | |
NIH_MUST (environ_add (&env, NULL, &len, TRUE, | |
"PROCESS=respawn")); | |
} | |
} else if (stop) { | |
NIH_MUST (environ_add (&env, NULL, &len, TRUE, "RESULT=ok")); | |
} | |
/* Add any exported variables from the job environment */ | |
for (e = job->class->export; e && *e; e++) { | |
char * const *str; | |
str = environ_lookup (job->env, *e, strlen (*e)); | |
if (str) | |
NIH_MUST (environ_add (&env, NULL, &len, FALSE, *str)); | |
} | |
event = NIH_MUST (event_new (NULL, name, env)); | |
event->session = job->class->session; | |
if (block) { | |
Blocked *blocked; | |
blocked = NIH_MUST (blocked_new (event, BLOCKED_JOB, job)); | |
nih_list_add (&event->blocking, &blocked->entry); | |
} | |
return event; | |
} | |
/** | |
* job_name: | |
* @job: job to return name of. | |
* | |
* Returns a string used in messages that contains the job name; this | |
* always begins with the name from the class, and then if set, | |
* has the name of the instance appended in brackets. | |
* | |
* Returns: internal copy of the string. | |
**/ | |
const char * | |
job_name (Job *job) | |
{ | |
static char *name = NULL; | |
nih_assert (job != NULL); | |
if (name) | |
nih_discard (name); | |
if (*job->name) { | |
name = NIH_MUST (nih_sprintf (NULL, "%s (%s)", | |
job->class->name, job->name)); | |
} else { | |
name = NIH_MUST (nih_strdup (NULL, job->class->name)); | |
} | |
return name; | |
} | |
/** | |
* job_goal_name: | |
* @goal: goal to convert. | |
* | |
* Converts an enumerated job goal into the string used for the status | |
* and for logging purposes. | |
* | |
* Returns: static string or NULL if goal not known. | |
**/ | |
const char * | |
job_goal_name (JobGoal goal) | |
{ | |
switch (goal) { | |
case JOB_STOP: | |
return N_("stop"); | |
case JOB_START: | |
return N_("start"); | |
case JOB_RESPAWN: | |
return N_("respawn"); | |
default: | |
return NULL; | |
} | |
} | |
/** | |
* job_goal_from_name: | |
* @goal: goal to convert. | |
* | |
* Converts a job goal string into the enumeration. | |
* | |
* Returns: enumerated goal or -1 if not known. | |
**/ | |
JobGoal | |
job_goal_from_name (const char *goal) | |
{ | |
nih_assert (goal != NULL); | |
if (! strcmp (goal, "stop")) { | |
return JOB_STOP; | |
} else if (! strcmp (goal, "start")) { | |
return JOB_START; | |
} else if (! strcmp (goal, "respawn")) { | |
return JOB_RESPAWN; | |
} else { | |
return -1; | |
} | |
} | |
/** | |
* job_state_name: | |
* @state: state to convert. | |
* | |
* Converts an enumerated job state into the string used for the status | |
* and for logging purposes. | |
* | |
* Returns: static string or NULL if state not known. | |
**/ | |
const char * | |
job_state_name (JobState state) | |
{ | |
switch (state) { | |
case JOB_WAITING: | |
return N_("waiting"); | |
case JOB_STARTING: | |
return N_("starting"); | |
case JOB_SECURITY_SPAWNING: | |
return N_("security-spawning"); | |
case JOB_SECURITY: | |
return N_("security"); | |
case JOB_PRE_STARTING: | |
return N_("pre-starting"); | |
case JOB_PRE_START: | |
return N_("pre-start"); | |
case JOB_SPAWNING: | |
return N_("spawning"); | |
case JOB_SPAWNED: | |
return N_("spawned"); | |
case JOB_POST_STARTING: | |
return N_("post-starting"); | |
case JOB_POST_START: | |
return N_("post-start"); | |
case JOB_RUNNING: | |
return N_("running"); | |
case JOB_PRE_STOPPING: | |
return N_("pre-stopping"); | |
case JOB_PRE_STOP: | |
return N_("pre-stop"); | |
case JOB_STOPPING: | |
return N_("stopping"); | |
case JOB_KILLED: | |
return N_("killed"); | |
case JOB_POST_STOPPING: | |
return N_("post-stopping"); | |
case JOB_POST_STOP: | |
return N_("post-stop"); | |
default: | |
return NULL; | |
} | |
} | |
/** | |
* job_state_from_name: | |
* @state: state to convert. | |
* | |
* Converts a job state string into the enumeration. | |
* | |
* Returns: enumerated state or -1 if not known. | |
**/ | |
JobState | |
job_state_from_name (const char *state) | |
{ | |
nih_assert (state != NULL); | |
if (! strcmp (state, "waiting")) { | |
return JOB_WAITING; | |
} else if (! strcmp (state, "starting")) { | |
return JOB_STARTING; | |
} else if (! strcmp (state, "security-spawning")) { | |
return JOB_SECURITY_SPAWNING; | |
} else if (! strcmp (state, "security")) { | |
return JOB_SECURITY; | |
} else if (! strcmp (state, "pre-starting")) { | |
return JOB_PRE_STARTING; | |
} else if (! strcmp (state, "pre-start")) { | |
return JOB_PRE_START; | |
} else if (! strcmp (state, "spawning")) { | |
return JOB_SPAWNING; | |
} else if (! strcmp (state, "spawned")) { | |
return JOB_SPAWNED; | |
} else if (! strcmp (state, "post-starting")) { | |
return JOB_POST_STARTING; | |
} else if (! strcmp (state, "post-start")) { | |
return JOB_POST_START; | |
} else if (! strcmp (state, "running")) { | |
return JOB_RUNNING; | |
} else if (! strcmp (state, "pre-stopping")) { | |
return JOB_PRE_STOPPING; | |
} else if (! strcmp (state, "pre-stop")) { | |
return JOB_PRE_STOP; | |
} else if (! strcmp (state, "stopping")) { | |
return JOB_STOPPING; | |
} else if (! strcmp (state, "killed")) { | |
return JOB_KILLED; | |
} else if (! strcmp (state, "post-stopping")) { | |
return JOB_POST_STOPPING; | |
} else if (! strcmp (state, "post-stop")) { | |
return JOB_POST_STOP; | |
} else { | |
return -1; | |
} | |
} | |
/** | |
* job_start: | |
* @job: job to be started, | |
* @message: D-Bus connection and message received, | |
* @wait: whether to wait for command to finish before returning. | |
* | |
* Implements the top half of the Start method of the | |
* com.ubuntu.Upstart.Instance interface, the bottom half may be found in | |
* job_finished(). | |
* | |
* Called on a stopping instance @job to cause it to be restarted. If the | |
* instance goal is already start, the com.ubuntu.Upstart.Error.AlreadyStarted | |
* D_Bus error will be returned immediately. If the instance fails to | |
* start again, the com.ubuntu.Upstart.Error.JobFailed D-Bus error will | |
* be returned when the problem occurs. | |
* | |
* When @wait is TRUE the method call will not return until the job has | |
* finished starting (running for tasks); when @wait is FALSE, the method | |
* call returns once the command has been processed and the goal changed. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_start (Job *job, | |
NihDBusMessage *message, | |
int wait) | |
{ | |
Session *session; | |
Blocked *blocked = NULL; | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
/* Don't permit out-of-session modification */ | |
session = session_from_dbus (NULL, message); | |
if (session != job->class->session) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to modify job: %s"), | |
job_name (job)); | |
return -1; | |
} | |
if (job->goal == JOB_START) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.AlreadyStarted", | |
_("Job is already running: %s"), | |
job_name (job)); | |
return -1; | |
} | |
#ifdef ENABLE_CGROUPS | |
/* Job has specified a cgroup stanza but since the cgroup | |
* manager has not yet been contacted, the job cannot be started. | |
*/ | |
if (job_class_cgroups (job->class) && ! cgroup_manager_available ()) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.CGroupManagerNotAvailable", | |
_("Job cannot be started as cgroup manager not available: %s"), | |
job_name (job)); | |
return -1; | |
} | |
#endif /* ENABLE_CGROUPS */ | |
if (wait) { | |
blocked = blocked_new (job, BLOCKED_INSTANCE_START_METHOD, | |
message); | |
if (! blocked) | |
nih_return_system_error (-1); | |
} | |
if (job->start_env) | |
nih_unref (job->start_env, job); | |
job->start_env = NULL; | |
job_finished (job, FALSE); | |
if (blocked) | |
nih_list_add (&job->blocking, &blocked->entry); | |
job_change_goal (job, JOB_START); | |
if (! wait) | |
NIH_ZERO (job_start_reply (message)); | |
return 0; | |
} | |
/** | |
* job_stop: | |
* @job: job to be stopped, | |
* @message: D-Bus connection and message received, | |
* @wait: whether to wait for command to finish before returning. | |
* | |
* Implements the top half of the Stop method of the | |
* com.ubuntu.Upstart.Instance interface, the bottom half may be found in | |
* job_finished(). | |
* | |
* Called on a running instance @job to cause it to be stopped. If the | |
* instance goal is already stop, the com.ubuntu.Upstart.Error.AlreadyStopped | |
* D_Bus error will be returned immediately. If the instance fails while | |
* stopping, the com.ubuntu.Upstart.Error.JobFailed D-Bus error will | |
* be returned when the problem occurs. | |
* | |
* When @wait is TRUE the method call will not return until the job has | |
* finished stopping; when @wait is FALSE, the method call returns once | |
* the command has been processed and the goal changed. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_stop (Job *job, | |
NihDBusMessage *message, | |
int wait) | |
{ | |
Session *session; | |
Blocked *blocked = NULL; | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
/* Don't permit out-of-session modification */ | |
session = session_from_dbus (NULL, message); | |
if (session != job->class->session) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to modify job: %s"), | |
job_name (job)); | |
return -1; | |
} | |
if (job->goal == JOB_STOP) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.AlreadyStopped", | |
_("Job has already been stopped: %s"), | |
job_name (job)); | |
return -1; | |
} | |
if (wait) { | |
blocked = blocked_new (job, BLOCKED_INSTANCE_STOP_METHOD, | |
message); | |
if (! blocked) | |
nih_return_system_error (-1); | |
} | |
if (job->stop_env) | |
nih_unref (job->stop_env, job); | |
job->stop_env = NULL; | |
job_finished (job, FALSE); | |
if (blocked) | |
nih_list_add (&job->blocking, &blocked->entry); | |
job_change_goal (job, JOB_STOP); | |
if (! wait) | |
NIH_ZERO (job_stop_reply (message)); | |
return 0; | |
} | |
/** | |
* job_restart: | |
* @job: job to be restarted, | |
* @message: D-Bus connection and message received, | |
* @wait: whether to wait for command to finish before returning. | |
* | |
* Implements the top half of the Restart method of the | |
* com.ubuntu.Upstart.Instance interface, the bottom half may be found in | |
* job_finished(). | |
* | |
* Called on a running instance @job to cause it to be restarted. If the | |
* instance goal is already stop, the com.ubuntu.Upstart.Error.AlreadyStopped | |
* D-Bus error will be returned immediately. If the instance fails to | |
* restart, the com.ubuntu.Upstart.Error.JobFailed D-Bus error will | |
* be returned when the problem occurs. | |
* | |
* When @wait is TRUE the method call will not return until the job has | |
* finished starting again (running for tasks); when @wait is FALSE, the | |
* method call returns once the command has been processed and the goal | |
* changed. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_restart (Job *job, | |
NihDBusMessage *message, | |
int wait) | |
{ | |
Session *session; | |
Blocked *blocked = NULL; | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
/* Don't permit out-of-session modification */ | |
session = session_from_dbus (NULL, message); | |
if (session != job->class->session) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to modify job: %s"), | |
job_name (job)); | |
return -1; | |
} | |
if (job->goal == JOB_STOP) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.AlreadyStopped", | |
_("Job has already been stopped: %s"), | |
job_name (job)); | |
return -1; | |
} | |
if (wait) { | |
blocked = blocked_new (job, BLOCKED_INSTANCE_RESTART_METHOD, | |
message); | |
if (! blocked) | |
nih_return_system_error (-1); | |
} | |
if (job->start_env) | |
nih_unref (job->start_env, job); | |
job->start_env = NULL; | |
if (job->stop_env) | |
nih_unref (job->stop_env, job); | |
job->stop_env = NULL; | |
job_finished (job, FALSE); | |
if (blocked) | |
nih_list_add (&job->blocking, &blocked->entry); | |
job_change_goal (job, JOB_STOP); | |
job_change_goal (job, JOB_START); | |
if (! wait) | |
NIH_ZERO (job_restart_reply (message)); | |
return 0; | |
} | |
/** | |
* job_reload: | |
* @job: job to reload, | |
* @message: D-Bus connection and message received, | |
* | |
* Implements the Reload method of the com.ubuntu.Upstart.Instance | |
* interface. | |
* | |
* Called on a running instance @job to reload. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_reload (Job *job, | |
NihDBusMessage *message) | |
{ | |
Session *session; | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
/* Don't permit out-of-session modification */ | |
session = session_from_dbus (NULL, message); | |
if (session != job->class->session) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied", | |
_("You do not have permission to modify job: %s"), | |
job_name (job)); | |
return -1; | |
} | |
if (job->pid[PROCESS_MAIN] <= 0) { | |
nih_dbus_error_raise_printf ( | |
DBUS_INTERFACE_UPSTART ".Error.NotRunning", | |
_("Job is not running: %s"), | |
job_name (job)); | |
return -1; | |
} | |
if (kill (job->pid[PROCESS_MAIN], job->class->reload_signal) < 0) | |
nih_return_system_error (-1); | |
NIH_ZERO (job_reload_reply (message)); | |
return 0; | |
} | |
/** | |
* job_get_name: | |
* @job: job to obtain name from, | |
* @message: D-Bus connection and message received, | |
* @name: pointer for reply string. | |
* | |
* Implements the get method for the name property of the | |
* com.ubuntu.Upstart.Instance interface. | |
* | |
* Called to obtain the instance name of the given @job, which will be stored | |
* in @name. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_get_name (Job *job, | |
NihDBusMessage *message, | |
char **name) | |
{ | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
nih_assert (name != NULL); | |
*name = job->name; | |
nih_ref (*name, message); | |
return 0; | |
} | |
/** | |
* job_get_goal: | |
* @job: job to obtain goal from, | |
* @message: D-Bus connection and message received, | |
* @goal: pointer for reply string. | |
* | |
* Implements the get method for the goal property of the | |
* com.ubuntu.Upstart.Instance interface. | |
* | |
* Called to obtain the goal of the given @job as a string, which will be | |
* stored in @goal. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_get_goal (Job *job, | |
NihDBusMessage *message, | |
char **goal) | |
{ | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
nih_assert (goal != NULL); | |
*goal = nih_strdup (message, job_goal_name (job->goal)); | |
if (! *goal) | |
nih_return_no_memory_error (-1); | |
return 0; | |
} | |
/** | |
* job_get_state: | |
* @job: job to obtain state from, | |
* @message: D-Bus connection and message received, | |
* @state: pointer for reply string. | |
* | |
* Implements the get method for the state property of the | |
* com.ubuntu.Upstart.Instance interface. | |
* | |
* Called to obtain the state of the given @job as a string, which will be | |
* stored in @state. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_get_state (Job *job, | |
NihDBusMessage *message, | |
char **state) | |
{ | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
nih_assert (state != NULL); | |
*state = nih_strdup (message, job_state_name (job->state)); | |
if (! *state) | |
nih_return_no_memory_error (-1); | |
return 0; | |
} | |
/** | |
* job_get_processes: | |
* @job: job to obtain state from, | |
* @message: D-Bus connection and message received, | |
* @processes: pointer for reply array. | |
* | |
* Implements the get method for the processes property of the | |
* com.ubuntu.Upstart.Instance interface. | |
* | |
* Called to obtain the current set of processes for the given @job as an | |
* array of process names and pids, which will be stored in @processes. | |
* | |
* Returns: zero on success, negative value on raised error. | |
**/ | |
int | |
job_get_processes (Job * job, | |
NihDBusMessage * message, | |
JobProcessesElement ***processes) | |
{ | |
size_t num_processes; | |
nih_assert (job != NULL); | |
nih_assert (message != NULL); | |
nih_assert (processes != NULL); | |
*processes = nih_alloc (message, sizeof (JobProcessesElement *) * 1); | |
if (! *processes) | |
nih_return_no_memory_error (-1); | |
num_processes = 0; | |
(*processes)[num_processes] = NULL; | |
for (int i = 0; i < PROCESS_LAST; i++) { | |
JobProcessesElement * process; | |
JobProcessesElement **tmp; | |
if (job->pid[i] <= 0) | |
continue; | |
process = nih_new (*processes, JobProcessesElement); | |
if (! process) { | |
nih_error_raise_no_memory (); | |
nih_free (*processes); | |
return -1; | |
} | |
process->item0 = nih_strdup (process, process_name (i)); | |
if (! process->item0) { | |
nih_error_raise_no_memory (); | |
nih_free (*processes); | |
return -1; | |
} | |
process->item1 = job->pid[i]; | |
tmp = nih_realloc (*processes, message, | |
(sizeof (JobProcessesElement *) | |
* (num_processes + 2))); | |
if (! tmp) { | |
nih_error_raise_no_memory (); | |
nih_free (*processes); | |
return -1; | |
} | |
*processes = tmp; | |
(*processes)[num_processes++] = process; | |
(*processes)[num_processes] = NULL; | |
} | |
return 0; | |
} | |
/** | |
* job_serialise: | |
* @job: job serialise. | |
* | |
* Convert @job into a JSON representation for serialisation. | |
* Caller must free returned value using json_object_put(). | |
* | |
* Note that the 'class' element is not encoded - it is assumed the | |
* caller will encode the returned JSON Job object as a child of the | |
* parent JSON-encoded JobClass object so a reference is not required. | |
* | |
* Returns: JSON-serialised Job object, or NULL on error. | |
**/ | |
json_object * | |
job_serialise (const Job *job) | |
{ | |
json_object *json; | |
json_object *json_pid; | |
json_object *json_fds; | |
json_object *json_logs; | |
json_object *json_handler_data; | |
nih_assert (job); | |
json = json_object_new_object (); | |
if (! json) | |
return NULL; | |
if (! state_set_json_string_var_from_obj (json, job, name)) | |
goto error; | |
if (! state_set_json_string_var_from_obj (json, job, path)) | |
goto error; | |
if (! state_set_json_enum_var (json, | |
job_goal_enum_to_str, | |
"goal", job->goal)) | |
goto error; | |
if (! state_set_json_enum_var (json, | |
job_state_enum_to_str, | |
"state", job->state)) | |
goto error; | |
if (! state_set_json_str_array_from_obj (json, job, env)) | |
goto error; | |
if (! state_set_json_str_array_from_obj (json, job, start_env)) | |
goto error; | |
if (! state_set_json_str_array_from_obj (json, job, stop_env)) | |
goto error; | |
if (job->stop_on) { | |
json_object *json_stop_on; | |
json_stop_on = event_operator_serialise_all (job->stop_on); | |
if (! json_stop_on) | |
goto error; | |
json_object_object_add (json, "stop_on", json_stop_on); | |
} | |
json_fds = state_serialise_int_array (int, job->fds, job->num_fds); | |
if (! json_fds) | |
goto error; | |
json_object_object_add (json, "fds", json_fds); | |
json_pid = state_serialise_int_array (pid_t, job->pid, | |
PROCESS_LAST); | |
if (! json_pid) | |
goto error; | |
json_object_object_add (json, "pid", json_pid); | |
/* Encode the blocking event as an index number which represents | |
* the event's position in the JSON events array. | |
*/ | |
if (job->blocker) { | |
int event_index; | |
event_index = event_to_index (job->blocker); | |
if (event_index < 0) | |
goto error; | |
/* For consistency, it would be preferable to encode the | |
* event name, but the index is actually better since it is | |
* simple and unambiguous - encoding the name would also require | |
* us to encode the env to make the event unique. | |
*/ | |
if (! state_set_json_int_var (json, "blocker", event_index)) | |
goto error; | |
} | |
if (! NIH_LIST_EMPTY (&job->blocking)) { | |
json_object *json_blocking; | |
json_blocking = state_serialise_blocking (&job->blocking); | |
if (! json_blocking) | |
goto error; | |
json_object_object_add (json, "blocking", json_blocking); | |
} | |
/* conditionally encode kill timer */ | |
if (job->kill_timer) { | |
json_object *kill_timer; | |
kill_timer = job_serialise_kill_timer (job->kill_timer); | |
if (! kill_timer) | |
goto error; | |
json_object_object_add (json, "kill_timer", kill_timer); | |
} | |
if (! state_set_json_enum_var (json, | |
process_type_enum_to_str, | |
"kill_process", job->kill_process)) | |
goto error; | |
if (! state_set_json_int_var_from_obj (json, job, failed)) | |
goto error; | |
if (! state_set_json_enum_var (json, | |
process_type_enum_to_str, | |
"failed_process", job->failed_process)) | |
goto error; | |
if (! state_set_json_int_var_from_obj (json, job, exit_status)) | |
goto error; | |
if (! state_set_json_int_var_from_obj (json, job, respawn_time)) | |
goto error; | |
if (! state_set_json_int_var_from_obj (json, job, respawn_count)) | |
goto error; | |
if (! state_set_json_int_var_from_obj (json, job, trace_forks)) | |
goto error; | |
if (! state_set_json_enum_var (json, | |
job_trace_state_enum_to_str, | |
"trace_state", job->trace_state)) | |
goto error; | |
json_logs = json_object_new_array (); | |
if (! json_logs) | |
return json; | |
for (int process = 0; process < PROCESS_LAST; process++) { | |
json_object *json_log; | |
json_log = log_serialise (job->log[process]); | |
if (! json_log) | |
goto error; | |
if (json_object_array_add (json_logs, json_log) < 0) | |
goto error; | |
} | |
json_object_object_add (json, "log", json_logs); | |
json_handler_data = json_object_new_array (); | |
if (! json_handler_data) | |
return json; | |
for (int process = 0; process < PROCESS_LAST; process++) { | |
json_object *json_data = NULL; | |
/* Only bother serialising if the process data hasn't | |
* been handled yet. | |
*/ | |
if (job->process_data[process] && job->process_data[process]->valid) { | |
json_data = job_process_data_serialise (job, | |
job->process_data[process]); | |
if (! json_data) | |
goto error; | |
} | |
if (json_object_array_add (json_handler_data, json_data) < 0) | |
goto error; | |
} | |
json_object_object_add (json, "process_data", json_handler_data); | |
return json; | |
error: | |
json_object_put (json); | |
return NULL; | |
} | |
/** | |
* job_serialise_all: | |
* | |
* Convert existing Job objects to JSON representation. | |
* | |
* Returns: JSON object containing array of Job objects, or NULL on error. | |
**/ | |
json_object * | |
job_serialise_all (const NihHash *jobs) | |
{ | |
json_object *json; | |
nih_assert (jobs); | |
json = json_object_new_array (); | |
if (! json) | |
return NULL; | |
NIH_HASH_FOREACH (jobs, iter) { | |
json_object *json_job; | |
Job *job = (Job *)iter; | |
json_job = job_serialise (job); | |
if (! json_job) | |
goto error; | |
json_object_array_add (json, json_job); | |
} | |
return json; | |
error: | |
json_object_put (json); | |
return NULL; | |
} | |
/** | |
* job_deserialise: | |
* @parent: job class for JSON-encoded jobs, | |
* @json: JSON-serialised Job object to deserialise. | |
* | |
* XXX: All events must have been deserialised prior to this function | |
* XXX: being called. | |
* | |
* Returns: Job object, or NULL on error. | |
**/ | |
Job * | |
job_deserialise (JobClass *parent, json_object *json) | |
{ | |
nih_local char *name = NULL; | |
Job *job = NULL; | |
json_object *json_kill_timer; | |
json_object *json_fds; | |
json_object *json_pid; | |
json_object *json_logs; | |
json_object *json_process_data; | |
json_object *json_stop_on = NULL; | |
size_t len; | |
int ret; | |
nih_assert (parent); | |
nih_assert (json); | |
if (! state_check_json_type (json, object)) | |
return NULL; | |
if (! state_get_json_string_var_strict (json, "name", NULL, name)) | |
goto error; | |
job = NIH_MUST (job_new (parent, name)); | |
if (! job) | |
return NULL; | |
if (! state_get_json_string_var_to_obj_strict (json, job, path)) | |
goto error; | |
if (! state_get_json_enum_var (json, | |
job_goal_str_to_enum, | |
"goal", job->goal)) | |
goto error; | |
if (! state_get_json_enum_var (json, | |
job_state_str_to_enum, | |
"state", job->state)) | |
goto error; | |
if (! state_get_json_env_array_to_obj (json, job, env)) | |
goto error; | |
if (! state_get_json_env_array_to_obj (json, job, start_env)) | |
goto error; | |
if (! state_get_json_env_array_to_obj (json, job, stop_env)) | |
goto error; | |
if (json_object_object_get_ex (json, "stop_on", &json_stop_on)) { | |
if (state_check_json_type (json_stop_on, array)) { | |
job->stop_on = event_operator_deserialise_all (job, json_stop_on); | |
if (! job->stop_on) | |
goto error; | |
} else { | |
nih_local char *stop_on = NULL; | |
/* Old format (string) | |
* | |
* Note that we re-search for the JSON key here | |
* (json, rather than json_stop_on) to allow | |
* the use of the convenience macro. This is | |
* of course slower, but its a legacy scenario. | |
*/ | |
if (! state_get_json_string_var_strict (json, "stop_on", NULL, stop_on)) | |
goto error; | |
if (*stop_on) { | |
nih_local JobClass *tmp = NULL; | |
tmp = NIH_MUST (job_class_new (NULL, "tmp", NULL)); | |
tmp->stop_on = parse_on_simple (tmp, "stop", stop_on); | |
if (! tmp->stop_on) { | |
NihError *err; | |
err = nih_error_get (); | |
nih_error ("%s %s: %s", | |
_("BUG"), | |
_("instance 'stop on' parse error"), | |
err->message); | |
nih_free (err); | |
goto error; | |
} | |
nih_free (job->stop_on); | |
job->stop_on = event_operator_copy (job, tmp->stop_on); | |
if (! job->stop_on) | |
goto error; | |
} | |
} | |
} | |
/* fds and num_fds handled by caller */ | |
/* pid handled by caller */ | |
/* blocking is handled by state_deserialise_blocking() */ | |
if (json_object_object_get_ex (json, "blocker", NULL)) { | |
int event_index = -1; | |
if (! state_get_json_int_var (json, "blocker", event_index)) | |
goto error; | |
job->blocker = event_from_index (event_index); | |
if (! job->blocker) | |
goto error; | |
} | |
if (! state_get_json_enum_var (json, | |
process_type_str_to_enum, | |
"kill_process", job->kill_process)) | |
goto error; | |
/* Check to see if a kill timer exists first since we do not | |
* want to end up creating a real but empty timer. | |
*/ | |
if (json_object_object_get_ex (json, "kill_timer", &json_kill_timer)) { | |
/* Found a partial kill timer, so create a new one and | |
* adjust its due time. By the time the main loop gets | |
* called, the due time will probably be in the past | |
* such that the job will be stopped. | |
* | |
* To be completely fair we should: | |
* | |
* - encode the time at the point of serialisation in a | |
* JSON 'meta' header. | |
* - query the time post-deserialisation and calculate | |
* the delta (being the time to perform the stateful | |
* re-exec). | |
* - add that time to all jobs with active kill timers | |
* to give their processes the full amount of time to | |
* end. | |
*/ | |
nih_local NihTimer *kill_timer = job_deserialise_kill_timer (json_kill_timer); | |
if (! kill_timer) | |
goto error; | |
nih_assert (job->kill_process); | |
job_process_set_kill_timer (job, job->kill_process, | |
kill_timer->timeout); | |
job_process_adj_kill_timer (job, kill_timer->due); | |
} | |
if (! state_get_json_int_var_to_obj (json, job, failed)) | |
goto error; | |
if (! state_get_json_enum_var (json, | |
process_type_str_to_enum, | |
"failed_process", job->failed_process)) | |
goto error; | |
if (! state_get_json_int_var_to_obj (json, job, exit_status)) | |
goto error; | |
if (! state_get_json_int_var_to_obj (json, job, respawn_time)) | |
goto error; | |
if (! state_get_json_int_var_to_obj (json, job, respawn_count)) | |
goto error; | |
if (! json_object_object_get_ex (json, "fds", &json_fds)) | |
goto error; | |
ret = state_deserialise_int_array (job, json_fds, | |
int, &job->fds, &job->num_fds); | |
if (ret < 0) | |
goto error; | |
if (! json_object_object_get_ex (json, "pid", &json_pid)) | |
goto error; | |
ret = state_deserialise_int_array (job, json_pid, | |
pid_t, &job->pid, &len); | |
if (ret < 0) | |
goto error; | |
/* If we are missing one, we're probably importing from a | |
* previous version that didn't include PROCESS_SECURITY. | |
* Simply add the missing one. | |
*/ | |
if (len == PROCESS_LAST - 1) { | |
job->pid = nih_realloc (job->pid, job, sizeof (pid_t) * PROCESS_LAST); | |
if (! job->pid) | |
goto error; | |
job->pid[PROCESS_LAST - 1] = 0; | |
} else if (len != PROCESS_LAST) { | |
goto error; | |
} | |
if (! state_get_json_int_var_to_obj (json, job, trace_forks)) | |
goto error; | |
if (! state_get_json_enum_var (json, | |
job_trace_state_str_to_enum, | |
"trace_state", job->trace_state)) | |
goto error; | |
if (! json_object_object_get_ex (json, "log", &json_logs)) | |
goto error; | |
if (! state_check_json_type (json_logs, array)) | |
goto error; | |
for (int process = 0; process < PROCESS_LAST; process++) { | |
json_object *json_log; | |
json_log = json_object_array_get_idx (json_logs, process); | |
if (json_log) { | |
/* NULL if there was no log configured, or we failed to | |
* deserialise it; either way, this should be non-fatal. | |
*/ | |
job->log[process] = log_deserialise (job->log, json_log); | |
} else { | |
/* If we are missing one, we're probably importing from a | |
* previous version that didn't include PROCESS_SECURITY. | |
* Simply ignore the missing one. | |
*/ | |
if (process == PROCESS_LAST - 1) { | |
job->log[process] = NULL; | |
} else { | |
goto error; | |
} | |
} | |
} | |
if (json_object_object_get_ex (json, "process_data", &json_process_data)) { | |
if (! state_check_json_type (json_process_data, array)) | |
goto error; | |
for (int process = 0; process < PROCESS_LAST; process++) { | |
json_object *json_data = NULL; | |
json_data = json_object_array_get_idx (json_process_data, process); | |
if (json_data) { | |
/* NULL if there was no process_data for this job process, or we failed to | |
* deserialise it; either way, this should be non-fatal. | |
*/ | |
job->process_data[process] = | |
job_process_data_deserialise (job->process_data, job, json_data); | |
if (! job->process_data[process]) | |
goto error; | |
/* Recreate watch */ | |
if (job->process_data[process]->valid) { | |
job_register_child_handler (job->process_data[process], | |
job->process_data[process]->job_process_fd, | |
job->process_data[process]); | |
} | |
} else { | |
job->process_data[process] = NULL; | |
} | |
} | |
} | |
return job; | |
error: | |
nih_free (job); | |
return NULL; | |
} | |
/** | |
* job_deserialise_all: | |
* | |
* @parent: job class for JSON-encoded jobs, | |
* @json: root of JSON-serialised state. | |
* | |
* Convert JSON representation of jobs back into Job objects associated | |
* with @parent. | |
* | |
* Returns: 0 on success, -1 on error. | |
**/ | |
int | |
job_deserialise_all (JobClass *parent, json_object *json) | |
{ | |
json_object *json_jobs; | |
Job *job; | |
nih_assert (parent); | |
nih_assert (json); | |
if (! json_object_object_get_ex (json, "jobs", &json_jobs)) | |
goto error; | |
if (! state_check_json_type (json_jobs, array)) | |
goto error; | |
for (int i = 0; i < json_object_array_length (json_jobs); i++) { | |
json_object *json_job; | |
json_job = json_object_array_get_idx (json_jobs, i); | |
if (! json_job) | |
goto error; | |
if (! state_check_json_type (json_job, object)) | |
goto error; | |
job = job_deserialise (parent, json_job); | |
if (! job) | |
goto error; | |
} | |
return 0; | |
error: | |
return -1; | |
} | |
/** | |
* job_goal_enum_to_str: | |
* | |
* @goal: JobGoal. | |
* | |
* Convert JobGoal to a string representation. | |
* | |
* Returns: string representation of @goal, or NULL if not known. | |
**/ | |
static const char * | |
job_goal_enum_to_str (JobGoal goal) | |
{ | |
state_enum_to_str (JOB_STOP, goal); | |
state_enum_to_str (JOB_START, goal); | |
state_enum_to_str (JOB_RESPAWN, goal); | |
return NULL; | |
} | |
/** | |
* job_goal_str_to_enum: | |
* | |
* @goal: string JobGoal value. | |
* | |
* Convert @goal back into enum value. | |
* | |
* Returns: JobGoal representation of @goal, or -1 if not known. | |
**/ | |
static JobGoal | |
job_goal_str_to_enum (const char *goal) | |
{ | |
state_str_to_enum (JOB_STOP, goal); | |
state_str_to_enum (JOB_START, goal); | |
state_str_to_enum (JOB_RESPAWN, goal); | |
return -1; | |
} | |
/** | |
* job_state_enum_to_str: | |
* | |
* @state: JobState. | |
* | |
* Convert JobState to a string representation. | |
* | |
* Returns: string representation of @state, or NULL if not known. | |
**/ | |
const char * | |
job_state_enum_to_str (JobState state) | |
{ | |
state_enum_to_str (JOB_WAITING, state); | |
state_enum_to_str (JOB_STARTING, state); | |
state_enum_to_str (JOB_SECURITY_SPAWNING, state); | |
state_enum_to_str (JOB_SECURITY, state); | |
state_enum_to_str (JOB_PRE_STARTING, state); | |
state_enum_to_str (JOB_PRE_START, state); | |
state_enum_to_str (JOB_SPAWNING, state); | |
state_enum_to_str (JOB_SPAWNED, state); | |
state_enum_to_str (JOB_POST_STARTING, state); | |
state_enum_to_str (JOB_POST_START, state); | |
state_enum_to_str (JOB_RUNNING, state); | |
state_enum_to_str (JOB_PRE_STOPPING, state); | |
state_enum_to_str (JOB_PRE_STOP, state); | |
state_enum_to_str (JOB_STOPPING, state); | |
state_enum_to_str (JOB_KILLED, state); | |
state_enum_to_str (JOB_POST_STOPPING, state); | |
state_enum_to_str (JOB_POST_STOP, state); | |
return NULL; | |
} | |
/** | |
* job_state_str_to_enum: | |
* | |
* @state: string JobState value. | |
* | |
* Convert @state back into enum value. | |
* | |
* Returns: JobState representation of @state, or -1 if not known. | |
**/ | |
JobState | |
job_state_str_to_enum (const char *state) | |
{ | |
state_str_to_enum (JOB_WAITING, state); | |
state_str_to_enum (JOB_STARTING, state); | |
state_str_to_enum (JOB_SECURITY_SPAWNING, state); | |
state_str_to_enum (JOB_SECURITY, state); | |
state_str_to_enum (JOB_PRE_STARTING, state); | |
state_str_to_enum (JOB_PRE_START, state); | |
state_str_to_enum (JOB_SPAWNING, state); | |
state_str_to_enum (JOB_SPAWNED, state); | |
state_str_to_enum (JOB_POST_STARTING, state); | |
state_str_to_enum (JOB_POST_START, state); | |
state_str_to_enum (JOB_RUNNING, state); | |
state_str_to_enum (JOB_PRE_STOPPING, state); | |
state_str_to_enum (JOB_PRE_STOP, state); | |
state_str_to_enum (JOB_STOPPING, state); | |
state_str_to_enum (JOB_KILLED, state); | |
state_str_to_enum (JOB_POST_STOPPING, state); | |
state_str_to_enum (JOB_POST_STOP, state); | |
return -1; | |
} | |
/** | |
* job_state_enum_to_str: | |
* | |
* @state: TraceState. | |
* | |
* Convert TraceState to a string representation. | |
* | |
* Returns: string representation of @state, or NULL if not known. | |
**/ | |
static const char * | |
job_trace_state_enum_to_str (TraceState state) | |
{ | |
state_enum_to_str (TRACE_NONE, state); | |
state_enum_to_str (TRACE_NEW, state); | |
state_enum_to_str (TRACE_NEW_CHILD, state); | |
state_enum_to_str (TRACE_NORMAL, state); | |
return NULL; | |
} | |
/** | |
* job_trace_state_str_to_enum: | |
* | |
* @state: string TraceState value. | |
* | |
* Convert @state back into enum value. | |
* | |
* Returns: TraceState representation of @state, or -1 if not known. | |
**/ | |
static TraceState | |
job_trace_state_str_to_enum (const char *state) | |
{ | |
state_str_to_enum (TRACE_NONE, state); | |
state_str_to_enum (TRACE_NEW, state); | |
state_str_to_enum (TRACE_NEW_CHILD, state); | |
state_str_to_enum (TRACE_NORMAL, state); | |
return -1; | |
} | |
/** | |
* job_serialise_kill_timer: | |
* | |
* @timer: NihTimer to serialise. | |
* | |
* Serialise @timer into JSON. | |
* | |
* Returns: JSON-serialised NihTimer object, or NULL on error. | |
**/ | |
static json_object * | |
job_serialise_kill_timer (NihTimer *timer) | |
{ | |
json_object *json; | |
nih_assert (timer); | |
json = json_object_new_object (); | |
if (! json) | |
return NULL; | |
if (! state_set_json_int_var_from_obj (json, timer, timeout)) | |
goto error; | |
if (! state_set_json_int_var_from_obj (json, timer, due)) | |
goto error; | |
return json; | |
error: | |
json_object_put (json); | |
return NULL; | |
} | |
/** | |
* job_deserialise_kill_timer: | |
* | |
* @json: JSON representation of NihTimer. | |
* | |
* Deserialise @json back into an NihTimer. | |
* | |
* Returns: NihTimer on NULL on error. | |
**/ | |
static NihTimer * | |
job_deserialise_kill_timer (json_object *json) | |
{ | |
NihTimer *timer; | |
nih_assert (json); | |
timer = nih_new (NULL, NihTimer); | |
if (! timer) | |
return NULL; | |
memset (timer, '\0', sizeof (NihTimer)); | |
if (! state_get_json_int_var_to_obj (json, timer, due)) | |
goto error; | |
if (! state_get_json_int_var_to_obj (json, timer, timeout)) | |
goto error; | |
return timer; | |
error: | |
nih_free (timer); | |
return NULL; | |
} | |
/** | |
* job_find: | |
* | |
* @session: session of job class, | |
* @job_class: name of job class, | |
* @job_name: name of job instance. | |
* | |
* Lookup job based on parent class name and | |
* job instance name. | |
* | |
* Returns: existing Job on success, or NULL if job class or | |
* job are not found in @session. | |
**/ | |
Job * | |
job_find (const Session *session, | |
JobClass *class, | |
const char *job_class, | |
const char *job_name) | |
{ | |
Job *job; | |
nih_assert (class || job_class); | |
nih_assert (job_classes); | |
if (! job_name) | |
goto error; | |
if (! class) | |
class = job_class_get_registered (job_class, session); | |
if (! class) | |
goto error; | |
job = (Job *)nih_hash_lookup (class->instances, job_name); | |
if (! job) | |
goto error; | |
return job; | |
error: | |
return NULL; | |
} | |
/** | |
* job_needs_cgroups: | |
* | |
* @job: job. | |
* | |
* Determine if specified job requires cgroups. | |
* | |
* Returns: TRUE if @job needs atleast 1 cgroup, else FALSE. | |
**/ | |
int | |
job_needs_cgroups (const Job *job) | |
{ | |
nih_assert (job); | |
#ifdef ENABLE_CGROUPS | |
return job_class_cgroups (job->class); | |
#else | |
/* No cgroup support */ | |
return FALSE; | |
#endif /* ENABLE_CGROUPS */ | |
} | |
/** | |
* job_child_error_handler: | |
* | |
* @job: job, | |
* @process: process that failed to start. | |
* | |
* JobProcessErrorHandler that deals with errors resulting from | |
* a failure to start a job process. | |
**/ | |
void | |
job_child_error_handler (Job *job, ProcessType process) | |
{ | |
nih_assert (job); | |
nih_assert (process > PROCESS_INVALID); | |
nih_assert (process < PROCESS_LAST); | |
job->pid[process] = 0; | |
switch (process) { | |
case PROCESS_SECURITY: | |
job_failed (job, PROCESS_SECURITY, -1); | |
job_change_goal (job, JOB_STOP); | |
break; | |
case PROCESS_PRE_START: | |
job_failed (job, PROCESS_PRE_START, -1); | |
job_change_goal (job, JOB_STOP); | |
job_change_state (job, job_next_state (job)); | |
break; | |
case PROCESS_MAIN: | |
job_failed (job, PROCESS_MAIN, -1); | |
job_change_goal (job, JOB_STOP); | |
job_change_state (job, job_next_state (job)); | |
break; | |
case PROCESS_POST_START: | |
job_change_state (job, job_next_state (job)); | |
break; | |
case PROCESS_PRE_STOP: | |
job_change_state (job, job_next_state (job)); | |
break; | |
case PROCESS_POST_STOP: | |
job_failed (job, PROCESS_POST_STOP, -1); | |
job_change_goal (job, JOB_STOP); | |
job_change_state (job, job_next_state (job)); | |
break; | |
default: | |
nih_assert_not_reached (); | |
} | |
} | |
#ifdef ENABLE_CGROUPS | |
/** | |
* job_last_process: | |
* | |
* @job: job, | |
* @process: process. | |
* | |
* Returns: TRUE if the last defined process for @job is @process, | |
* else FALSE. | |
**/ | |
int | |
job_last_process (const Job *job, ProcessType process) | |
{ | |
ProcessType i; | |
ProcessType last = PROCESS_INVALID; | |
nih_assert (job); | |
nih_assert (process >= PROCESS_MAIN); | |
nih_assert (process < PROCESS_LAST); | |
for (i = 0; i < PROCESS_LAST; i++) { | |
if (job->class->process[i]) | |
last = i; | |
} | |
return last == process ? TRUE : FALSE; | |
} | |
#endif /* ENABLE_CGROUPS */ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment