blob: 6791590b0f3592fff1e9414c67bc6c502589f9fa [file] [log] [blame]
/* upstart
*
* job_class.c - job class definition handling
*
* Copyright 2011 Canonical Ltd.
* Author: Scott James Remnant <scott@netsplit.com>.
*
* 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 <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/string.h>
#include <nih/list.h>
#include <nih/hash.h>
#include <nih/tree.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 "environ.h"
#include "process.h"
#include "session.h"
#include "job_class.h"
#include "job.h"
#include "event_operator.h"
#include "blocked.h"
#include "conf.h"
#include "control.h"
#include "parse_job.h"
#ifdef ENABLE_CGROUPS
#include "cgroup.h"
#endif /* ENABLE_CGROUPS */
#include "com.ubuntu.Upstart.h"
#include "com.ubuntu.Upstart.Job.h"
#include <json.h>
extern json_object *json_classes;
extern int user_mode;
extern int no_inherit_env;
extern char **environ;
/* Prototypes for static functions */
static void job_class_add (JobClass *class);
static int job_class_remove (JobClass *class, const Session *session);
/**
* default_console:
*
* If a job does not specify a value for the 'console' stanza, use this value.
*
* Only used if value is >= 0;
**/
int default_console = -1;
/**
* job_classes:
*
* This hash table holds the list of known job classes indexed by their name.
* Each entry is a JobClass structure; multiple entries with the same name
* are not permitted.
**/
NihHash *job_classes = NULL;
/**
* job_environ:
*
* Array of environment variables that will be set in the jobs
* environment.
**/
static char **job_environ = NULL;
/**
* initial_umask:
*
* Value of umask at startup.
**/
mode_t initial_umask;
/**
* job_class_init:
*
* Initialise the job classes hash table.
**/
void
job_class_init (void)
{
if (! job_classes)
job_classes = NIH_MUST (nih_hash_string_new (NULL, 0));
}
/**
* job_class_environ_init:
*
* Initialise the job_environ array.
**/
void
job_class_environment_init (void)
{
char * const default_environ[] = { JOB_DEFAULT_ENVIRONMENT, NULL };
if (job_environ)
return;
job_environ = NIH_MUST (nih_str_array_new (NULL));
NIH_MUST (environ_append (&job_environ, NULL, 0, TRUE, default_environ));
if (user_mode && ! no_inherit_env)
NIH_MUST(environ_append (&job_environ, NULL, 0, TRUE, environ));
}
/**
* job_class_environment_reset:
*
* Reset the environment back to defaults.
*
* Note: not applied to running job instances.
**/
void
job_class_environment_reset (void)
{
job_class_environment_clear ();
job_class_environment_init ();
}
/**
* job_class_environment_clear:
*
* Clear the environment table.
**/
void
job_class_environment_clear (void)
{
if (job_environ) {
nih_free (job_environ);
job_environ = NULL;
}
}
/**
* job_class_environment_set:
*
* @var: environment variable to set in form 'name[=value]',
* @replace: TRUE if @name should be overwritten if already set, else
* FALSE.
*
* Set specified variable in job environment.
*
* Returns: 0 on success, -1 on error.
**/
int
job_class_environment_set (const char *var, int replace)
{
nih_assert (var);
nih_assert (job_environ);
if (! environ_add (&job_environ, NULL, NULL, replace, var))
return -1;
/* Update all running jobs */
NIH_HASH_FOREACH (job_classes, iter) {
JobClass *class = (JobClass *)iter;
NIH_HASH_FOREACH (class->instances, job_iter) {
Job *job = (Job *)job_iter;
if (! environ_add (&job->env, job, NULL, replace, var))
return -1;
}
}
return 0;
}
/**
* job_class_environment_unset:
*
* @var: name of environment variable to unset.
*
* Remove specified variable from job environment array.
*
* Returns: 0 on success, -1 on error.
**/
int
job_class_environment_unset (const char *name)
{
char **ret = NULL;
nih_assert (name);
nih_assert (job_environ);
ret = environ_remove (&job_environ, NULL, NULL, name);
if (! ret)
return -1;
job_environ = ret;
/* Update all running jobs */
NIH_HASH_FOREACH (job_classes, iter) {
JobClass *class = (JobClass *)iter;
NIH_HASH_FOREACH (class->instances, job_iter) {
Job *job = (Job *)job_iter;
ret = environ_remove (&job->env, job, NULL, name);
if (! ret)
return -1;
job->env = ret;
}
}
return 0;
}
/**
* job_class_environment_get_all:
*
* @parent: parent for new environment array.
*
* Obtain a copy of the entire environment a job will be provided with.
*
* Returns: Newly-allocated copy of the job environment array,
* or NULL on error.
**/
char **
job_class_environment_get_all (const void *parent)
{
nih_assert (job_environ);
return nih_str_array_copy (parent, NULL, job_environ);
}
/**
* job_class_environment_get:
*
* @name: name of variable to query.
*
* Determine value of variable @name in job environment.
*
* XXX: The returned value must not be freed.
*
* Returns: pointer to static storage value of @name, or NULL if @name
* does not exist in job environment.
**/
const char *
job_class_environment_get (const char *name)
{
nih_assert (name);
nih_assert (job_environ);
return environ_get (job_environ, name);
}
/**
* job_class_new:
*
* @parent: parent for new job class,
* @name: name of new job class,
* @session: session.
*
* Allocates and returns a new JobClass structure with the given @name
* and @session. It will not be automatically added to the job classes
* table, it is up to the caller to ensure this is done using
* job_class_register() once the class has been set up.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned job class. When all parents
* of the returned job class are freed, the returned job class will also be
* freed.
*
* Returns: newly allocated JobClass structure or NULL if insufficient memory.
**/
JobClass *
job_class_new (const void *parent,
const char *name,
Session *session)
{
JobClass *class;
int i;
nih_assert (name != NULL);
nih_assert (strlen (name) > 0);
class = nih_new (parent, JobClass);
if (! class)
return NULL;
nih_list_init (&class->entry);
nih_alloc_set_destructor (class, nih_list_destroy);
class->name = nih_strdup (class, name);
if (! class->name)
goto error;
class->session = session;
if (class->session && class->session->chroot) {
class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
session->chroot,
class->name, NULL);
} else {
class->path = nih_dbus_path (class, DBUS_PATH_UPSTART, "jobs",
class->name, NULL);
}
if (! class->path)
goto error;
class->instance = nih_strdup (class, "");
if (! class->instance)
goto error;
class->instances = nih_hash_string_new (class, 0);
if (! class->instances)
goto error;
class->description = NULL;
class->author = NULL;
class->version = NULL;
class->env = NULL;
class->export = NULL;
class->import = NULL;
class->start_on = NULL;
class->stop_on = NULL;
class->emits = NULL;
class->process = nih_alloc (class, sizeof (Process *) * PROCESS_LAST);
if (! class->process)
goto error;
for (i = 0; i < PROCESS_LAST; i++)
class->process[i] = NULL;
class->expect = EXPECT_NONE;
class->task = FALSE;
class->kill_timeout = JOB_DEFAULT_KILL_TIMEOUT;
class->kill_signal = SIGTERM;
class->reload_signal = SIGHUP;
class->respawn = FALSE;
class->respawn_limit = JOB_DEFAULT_RESPAWN_LIMIT;
class->respawn_interval = JOB_DEFAULT_RESPAWN_INTERVAL;
class->normalexit = NULL;
class->normalexit_len = 0;
class->console = default_console >= 0 ? default_console : CONSOLE_NONE;
class->umask = (user_mode && ! no_inherit_env) ? initial_umask : JOB_DEFAULT_UMASK;
class->nice = JOB_NICE_INVALID;
class->oom_score_adj = JOB_DEFAULT_OOM_SCORE_ADJ;
for (i = 0; i < RLIMIT_NLIMITS; i++)
class->limits[i] = NULL;
class->chroot = NULL;
class->chdir = NULL;
class->setuid = NULL;
class->setgid = NULL;
class->deleted = FALSE;
class->debug = FALSE;
class->usage = NULL;
class->apparmor_switch = NULL;
class->cgmanager_wait = FALSE;
nih_list_init (&class->cgroups);
return class;
error:
nih_free (class);
return NULL;
}
/**
* job_class_get_registered:
*
* @name: name of JobClass to search for,
* @session: Session of @class.
*
* Determine the currently registered JobClass with name @name for
* session @session.
*
* Returns: JobClass or NULL if no JobClass with name @name and
* session @session is registered.
**/
JobClass *
job_class_get_registered (const char *name, const Session *session)
{
JobClass *registered = NULL;
nih_assert (name);
job_class_init ();
/* If we found an entry, ensure we only consider the appropriate session */
do {
registered = (JobClass *)nih_hash_search (job_classes,
name, registered ? &registered->entry : NULL);
} while (registered && registered->session != session);
return registered;
}
/**
* job_class_consider:
* @class: job class to consider.
*
* Considers adding @class to the job classes hash table as the best
* available class, if there is no existing class with the name or the
* existing class can be replaced.
*
* Returns: TRUE if @class is now the registered class, FALSE otherwise.
**/
int
job_class_consider (JobClass *class)
{
JobClass *registered = NULL;
JobClass *best = NULL;
nih_assert (class != NULL);
job_class_init ();
best = conf_select_job (class->name, class->session);
nih_assert (best != NULL);
nih_assert (best->session == class->session);
registered = job_class_get_registered (class->name, class->session);
if (registered != best) {
if (registered) {
job_class_event_block (NULL, registered, best);
if (! job_class_remove (registered, class->session)) {
/* Couldn't deregister, so undo */
if (best->start_on)
event_operator_reset (best->start_on);
return FALSE;
}
}
job_class_add (best);
}
return (class == best ? TRUE : FALSE);
}
/**
* job_class_reconsider:
* @class: job class to reconsider.
*
* Reconsiders whether @class should be the best available class in the
* job classes hash table, if it is the existing class and can be
* replaced by a better then it will be.
*
* Note that the best class may be itself unless you have first removed
* @class from any configuration sources before calling.
*
* Returns: FALSE if @class is still the hash table member, TRUE otherwise.
**/
int
job_class_reconsider (JobClass *class)
{
JobClass *registered = NULL;
JobClass *best = NULL;
nih_assert (class != NULL);
job_class_init ();
best = conf_select_job (class->name, class->session);
registered = job_class_get_registered (class->name, class->session);
if (registered == class) {
if (class != best) {
if (! job_class_remove (class, class->session))
return FALSE;
job_class_add (best);
return TRUE;
} else {
return FALSE;
}
}
return TRUE;
}
/**
* job_class_event_block:
*
* @parent: parent object for list,
* @old: original JobClass currently registered in job_classes,
* @new: new "best" JobClass that is not yet present in job_classes.
*
* Compare @old and @new start on EventOperator trees looking for
* matching events that occur in both (_and_ which implicitly still exist
* in the global events list). Events that satisfy these criteria will have
* their reference count elevated to allow @new to replace @old in job_classes
* without the destruction of @old freeing the events in question.
*
* Note that the reference count never needs to be decremented back
* again since this function effectively passes "ownership" of the event
* block from @old to @new, since @old will be replaced by @new but @new
* should replicate the EventOperator state of @old.
**/
void
job_class_event_block (void *parent, JobClass *old, JobClass *new)
{
EventOperator *old_root;
EventOperator *new_root;
if (! old || ! new)
return;
old_root = old->start_on;
new_root = new->start_on;
/* If either @old or @new are NULL, or have no start_on
* condition, there is no need to modify any events.
*/
if (! old_root || ! new_root)
return;
/* The old JobClass has associated instances meaning it
* will not be possible for job_class_remove() to replace it, so
* we don't need to manipulate any event reference counts.
*/
NIH_HASH_FOREACH (old->instances, iter)
return;
NIH_TREE_FOREACH_POST (&old_root->node, iter) {
EventOperator *old_oper = (EventOperator *)iter;
Event *event;
if (old_oper->type != EVENT_MATCH)
continue;
/* Ignore nodes that are not blocking events */
if (! old_oper->event)
continue;
/* Since the JobClass is blocking an event,
* that event must be valid.
*/
event = old_oper->event;
NIH_TREE_FOREACH_POST (&new_root->node, niter) {
EventOperator *new_oper = (EventOperator *)niter;
if (new_oper->type != EVENT_MATCH)
continue;
/* ignore the return - we just want to ensure
* that any events in @new that match those in
* @old have identical nodes.
*/
(void)event_operator_handle (new_oper, event, NULL);
}
}
}
/**
* job_class_add:
* @class: new class to select.
*
* Adds @class to the hash table and registers it with all current D-Bus
* connections. @class may be NULL.
**/
static void
job_class_add (JobClass *class)
{
control_init ();
if (! class)
return;
nih_hash_add (job_classes, &class->entry);
NIH_LIST_FOREACH (control_conns, iter) {
NihListEntry *entry = (NihListEntry *)iter;
DBusConnection *conn = (DBusConnection *)entry->data;
job_class_register (class, conn, TRUE);
}
}
/**
* job_class_add_safe:
* @class: new class to select.
*
* Adds @class to the hash table iff no existing entry of the
* same name exists for the same session.
**/
void
job_class_add_safe (JobClass *class)
{
JobClass *registered = NULL;
nih_assert (class);
nih_assert (class->name);
control_init ();
registered = job_class_get_registered (class->name, class->session);
nih_assert (! registered);
job_class_add (class);
}
/**
* job_class_remove:
* @class: class to remove,
* @session: Session of @class.
*
* Removes @class from the hash table and unregisters it from all current
* D-Bus connections.
*
* Returns: TRUE if class could be unregistered, FALSE if there are
* active instances that prevent unregistration, or if @session
* does not match the session associated with @class.
**/
static int
job_class_remove (JobClass *class, const Session *session)
{
nih_assert (class != NULL);
if (class->session != session)
return FALSE;
control_init ();
/* Return if we have any active instances */
NIH_HASH_FOREACH (class->instances, iter)
return FALSE;
nih_list_remove (&class->entry);
NIH_LIST_FOREACH (control_conns, iter) {
NihListEntry *entry = (NihListEntry *)iter;
DBusConnection *conn = (DBusConnection *)entry->data;
job_class_unregister (class, conn);
}
return TRUE;
}
/**
* job_class_register:
* @class: class to register,
* @conn: connection to register for
* @signal: emit the JobAdded signal.
*
* Register the job @class with the D-Bus connection @conn, using the
* path set when the class was created. Since multiple classes with the
* same name may exist, this should only ever be called with the current
* class of that name, and job_class_unregister() should be used before
* registering a new one with the same name.
**/
void
job_class_register (JobClass *class,
DBusConnection *conn,
int signal)
{
nih_assert (class != NULL);
nih_assert (conn != NULL);
NIH_MUST (nih_dbus_object_new (class, conn, class->path,
job_class_interfaces, class));
nih_debug ("Registered job %s", class->path);
if (signal)
NIH_ZERO (control_emit_job_added (conn, DBUS_PATH_UPSTART,
class->path));
NIH_HASH_FOREACH (class->instances, iter) {
Job *job = (Job *)iter;
job_register (job, conn, signal);
}
}
/**
* job_class_unregister:
* @class: class to unregistered,
* @conn: connection to unregister from.
*
* Unregister the job @class from the D-Bus connection @conn, which must
* have already been registered with job_class_register().
**/
void
job_class_unregister (JobClass *class,
DBusConnection *conn)
{
nih_assert (class != NULL);
nih_assert (conn != NULL);
NIH_HASH_FOREACH (class->instances, iter)
nih_assert_not_reached ();
NIH_MUST (dbus_connection_unregister_object_path (conn, class->path));
nih_debug ("Unregistered job %s", class->path);
NIH_ZERO (control_emit_job_removed (conn, DBUS_PATH_UPSTART,
class->path));
}
/**
* job_class_environment:
* @parent: parent object for new table,
* @class: job class,
* @len: pointer to variable to store table length.
*
* Constructs an environment table containing the standard environment
* variables and defined in the job's @class.
*
* This table is suitable for storing in @job's env member so that it is
* used for all processes spawned by the job.
*
* If @len is not NULL it will be updated to contain the new array length.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned array. When all parents
* of the returned array are freed, the returned array will also be
* freed.
*
* Returns: new environment table or NULL if insufficient memory.
**/
char **
job_class_environment (const void *parent,
JobClass *class,
size_t *len)
{
char **env;
nih_assert (class != NULL);
nih_assert (job_environ);
env = nih_str_array_new (parent);
if (! env)
return NULL;
if (len)
*len = 0;
/* Copy the set of environment variables, usually these just
* pick up the values from init's own environment.
*/
if (! environ_append (&env, parent, len, TRUE, job_environ))
goto error;
/* Copy the set of environment variables from the job configuration,
* these often have values but also often don't and we want them to
* override the builtins.
*/
if (! environ_append (&env, parent, len, TRUE, class->env))
goto error;
return env;
error:
nih_free (env);
return NULL;
}
/**
* job_class_import_environment:
* @class: class to import environment for.
* @env: pointer to environment table,
* @len: length of @env,
* @new_env: environment table to append to import into @env.
*
* Updates the environment table @env to add any entries in @new_env
* that are imported per import declarations in @class.
*
* Both the array and the new strings within it are allocated using
* nih_alloc().
*
* @len will be updated to contain the new array length and @env will
* be updated to point to the new array pointer; use the return value
* simply to check for success.
*
* Returns: new array pointer or NULL if insufficient memory.
**/
char**
job_class_import_environment (JobClass *class,
char ***env,
void *parent,
size_t *len,
char * const *new_env)
{
char * const *e;
nih_assert (env != NULL);
if (! *env) {
*env = nih_str_array_new (parent);
if (! *env)
return NULL;
if (len)
*len = 0;
}
for (e = new_env; e && *e; e++) {
char * const *match = NULL;
size_t elen;
if (environ_is_upstart_key (*e))
continue;
elen = strcspn(*e, "=");
for (match = class->import; match && *match; match++) {
if ((strncmp (*match, *e, elen) == 0) && ! (*match)[elen])
break;
}
if (! match || ! *match)
continue;
if (! environ_add (env, parent, len, TRUE, *e))
return NULL;
}
return *env;
}
/**
* job_class_get_instance:
* @class: job class to be query,
* @message: D-Bus connection and message received,
* @env: NULL-terminated array of environment variables,
* @instance: pointer for instance name.
*
* Implements the GetInstance method of the com.ubuntu.Upstart.Job
* interface.
*
* Called to obtain the path of an instance based on @env, which is used
* to locate the instance in the same way that Start, Stop and Restart do.
*
* If no such instance is found, the com.ubuntu.Upstart.Error.UnknownInstance
* D-Bus error will be returned immediately.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_instance (JobClass *class,
NihDBusMessage *message,
char * const *env,
char **instance)
{
Job *job;
nih_local char **instance_env = NULL;
nih_local char *name = NULL;
size_t len;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (env != NULL);
/* 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"));
return -1;
}
/* Construct the full environment for the instance based on the class
* and that provided.
*/
instance_env = job_class_environment (NULL, class, &len);
if (! instance_env)
nih_return_system_error (-1);
if (! job_class_import_environment (class, &instance_env, NULL, &len,
env))
nih_return_system_error (-1);
/* Use the environment to expand the instance name and look it up
* in the job.
*/
name = environ_expand (NULL, class->instance, instance_env);
if (! name) {
NihError *error;
nih_local char *error_message = NULL;
error = nih_error_get ();
if (error->number != ENOMEM) {
error = nih_error_steal ();
error_message = nih_strdup (NULL, error->message);
if (! error_message)
nih_return_system_error (-1);
if (class->usage) {
if (! nih_strcat_sprintf (&error_message, NULL,
"\n%s: %s", _("Usage"), class->usage)) {
nih_return_system_error (-1);
}
}
nih_dbus_error_raise (DBUS_ERROR_INVALID_ARGS,
error_message);
nih_free (error);
}
return -1;
}
job = (Job *)nih_hash_lookup (class->instances, name);
if (! job) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.UnknownInstance",
_("Unknown instance: %s"), name);
return -1;
}
*instance = nih_strdup (message, job->path);
if (! *instance)
nih_return_system_error (-1);
return 0;
}
/**
* job_class_get_instance_by_name:
* @class: class to obtain instance from,
* @message: D-Bus connection and message received,
* @name: name of instance to get,
* @instance: pointer for object path reply.
*
* Implements the GetInstanceByName method of the com.ubuntu.Upstart.Job
* interface.
*
* Called to obtain the path to a D-Bus object for the instance named
* @name of this job which will be stored in @job. If no instance with
* that name exists, the com.ubuntu.Upstart.Error.UnknownInstance D-Bus
* error will be raised.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_instance_by_name (JobClass *class,
NihDBusMessage *message,
const char *name,
char **instance)
{
Job *job;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (name != NULL);
nih_assert (instance != NULL);
job = (Job *)nih_hash_lookup (class->instances, name);
if (! job) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.UnknownInstance",
_("Unknown instance: %s"), name);
return -1;
}
*instance = nih_strdup (message, job->path);
if (! *instance)
nih_return_system_error (-1);
return 0;
}
/**
* job_class_get_all_instances:
* @class: class to obtain instance from,
* @message: D-Bus connection and message received,
* @instances: pointer for array of object paths reply.
*
* Implements the GetAllInstances method of the com.ubuntu.Upstart.Job
* interface.
*
* Called to obtain the paths of all instances for the given @class, which
* will be stored in @instances. If no instances exist, @instances will
* point to an empty array.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_all_instances (JobClass *class,
NihDBusMessage *message,
char ***instances)
{
char **list;
size_t len;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (instances != NULL);
len = 0;
list = nih_str_array_new (message);
if (! list)
nih_return_system_error (-1);
NIH_HASH_FOREACH (class->instances, iter) {
Job *job = (Job *)iter;
if (! nih_str_array_add (&list, message, &len, job->path)) {
nih_error_raise_system ();
nih_free (list);
return -1;
}
}
*instances = list;
return 0;
}
/**
* job_class_start:
* @class: job class to be started,
* @message: D-Bus connection and message received,
* @env: NULL-terminated array of environment variables,
* @wait: whether to wait for command to finish before returning.
*
* Implements the top half of the Start method of the com.ubuntu.Upstart.Job
* interface, the bottom half may be found in job_finished().
*
* This is the primary method to start new instances of jobs. The given
* @env will be used to locate an existing instance, or create a new one
* if necessary; in either case, the instance will be set to be started
* (or restarted if it is currently stopping) with @env as its new
* environment.
*
* 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, 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_class_start (JobClass *class,
NihDBusMessage *message,
char * const *env,
int wait)
{
Session *session;
Blocked *blocked = NULL;
Job *job;
nih_local char **start_env = NULL;
nih_local char *name = NULL;
size_t len;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (env != NULL);
/* Don't permit out-of-session modification */
session = session_from_dbus (NULL, message);
if (session != class->session) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
_("You do not have permission to modify job: %s"),
class->name);
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 (class) && ! cgroup_manager_available ()) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.CGroupManagerNotAvailable",
_("Job cannot be started as cgroup manager not available: %s"),
class->name);
return -1;
}
#endif /* ENABLE_CGROUPS */
/* 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"));
return -1;
}
/* Construct the full environment for the instance based on the class
* and that provided.
*/
start_env = job_class_environment (NULL, class, &len);
if (! start_env)
nih_return_system_error (-1);
if (! job_class_import_environment (class, &start_env, NULL, &len, env))
nih_return_system_error (-1);
/* Use the environment to expand the instance name and look it up
* in the job.
*/
name = environ_expand (NULL, class->instance, start_env);
if (! name) {
NihError *error;
nih_local char *error_message = NULL;
error = nih_error_get ();
if (error->number != ENOMEM) {
error = nih_error_steal ();
error_message = nih_strdup (NULL, error->message);
if (! error_message)
nih_return_system_error (-1);
if (class->usage) {
if (! nih_strcat_sprintf (&error_message, NULL,
"\n%s: %s", _("Usage"), class->usage)) {
nih_return_system_error (-1);
}
}
nih_dbus_error_raise (DBUS_ERROR_INVALID_ARGS,
error_message);
nih_free (error);
}
return -1;
}
job = (Job *)nih_hash_lookup (class->instances, name);
/* If no instance exists with the expanded name, create a new
* instance.
*/
if (! job) {
job = job_new (class, name);
if (! job)
nih_return_system_error (-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;
}
if (wait)
blocked = NIH_MUST (blocked_new (job, BLOCKED_JOB_START_METHOD,
message));
if (job->start_env)
nih_unref (job->start_env, job);
job->start_env = start_env;
nih_ref (job->start_env, job);
job_finished (job, FALSE);
if (blocked)
nih_list_add (&job->blocking, &blocked->entry);
job_change_goal (job, JOB_START);
if (! wait)
NIH_ZERO (job_class_start_reply (message, job->path));
return 0;
}
/**
* job_class_stop:
* @class: job class to be stopped,
* @message: D-Bus connection and message received,
* @env: NULL-terminated array of environment variables,
* @wait: whether to wait for command to finish before returning.
*
* Implements the top half of the Stop method of the com.ubuntu.Upstart.Job
* interface, the bottom half may be found in job_finished().
*
* This is the primary method to stop instances of jobs. The given @env
* will be used to locate an existing instance which will be set to be
* stopped with @env as the environment passed to the pre-stop script.
*
* If no such instance is found, the com.ubuntu.Upstart.Error.UnknownInstance
* D-Bus error will be returned immediately. 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 stop, 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_class_stop (JobClass *class,
NihDBusMessage *message,
char * const *env,
int wait)
{
Session *session;
Blocked *blocked = NULL;
Job *job;
nih_local char **stop_env = NULL;
nih_local char *name = NULL;
size_t len;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (env != NULL);
/* Don't permit out-of-session modification */
session = session_from_dbus (NULL, message);
if (session != class->session) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
_("You do not have permission to modify job: %s"),
class->name);
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"));
return -1;
}
/* Construct the full environment for the instance based on the class
* and that provided; while we don't pass this to the instance itself,
* we need this to look up the instance in the first place.
*/
stop_env = job_class_environment (NULL, class, &len);
if (! stop_env)
nih_return_system_error (-1);
if (! job_class_import_environment (class, &stop_env, NULL, &len, env))
nih_return_system_error (-1);
/* Use the environment to expand the instance name and look it up
* in the job.
*/
name = environ_expand (NULL, class->instance, stop_env);
if (! name) {
NihError *error;
error = nih_error_get ();
if (error->number != ENOMEM) {
error = nih_error_steal ();
nih_dbus_error_raise (DBUS_ERROR_INVALID_ARGS,
error->message);
nih_free (error);
}
return -1;
}
job = (Job *)nih_hash_lookup (class->instances, name);
if (! job) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.UnknownInstance",
_("Unknown instance: %s"), name);
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 = NIH_MUST (blocked_new (job, BLOCKED_JOB_STOP_METHOD,
message));
if (job->stop_env)
nih_unref (job->stop_env, job);
job->stop_env = nih_str_array_copy (job, NULL, stop_env);
if (! job->stop_env)
nih_return_system_error (-1);
job_finished (job, FALSE);
if (blocked)
nih_list_add (&job->blocking, &blocked->entry);
job_change_goal (job, JOB_STOP);
if (! wait)
NIH_ZERO (job_class_stop_reply (message));
return 0;
}
/**
* job_restart:
* @class: job class to be restarted,
* @message: D-Bus connection and message received,
* @env: NULL-terminated array of environment variables,
* @wait: whether to wait for command to finish before returning.
*
* Implements the top half of the Restart method of the com.ubuntu.Upstart.Job
* interface, the bottom half may be found in job_finished().
*
* This is the primary method to restart existing instances of jobs; while
* calling both "Stop" and "Start" may have the same effect, there is no
* guarantee of atomicity.
*
* The given @env will be used to locate the existing instance, which will
* be stopped and then restarted with @env as its new environment.
*
* If no such instance is found, the com.ubuntu.Upstart.Error.UnknownInstance
* D-Bus error will be returned immediately. 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_class_restart (JobClass *class,
NihDBusMessage *message,
char * const *env,
int wait)
{
Session *session;
Blocked *blocked = NULL;
Job *job;
nih_local char **restart_env = NULL;
nih_local char *name = NULL;
size_t len;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (env != NULL);
/* Don't permit out-of-session modification */
session = session_from_dbus (NULL, message);
if (session != class->session) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.PermissionDenied",
_("You do not have permission to modify job: %s"),
class->name);
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"));
return -1;
}
/* Construct the full environment for the instance based on the class
* and that provided.
*/
restart_env = job_class_environment (NULL, class, &len);
if (! restart_env)
nih_return_system_error (-1);
if (! job_class_import_environment (class, &restart_env, NULL, &len,
env))
nih_return_system_error (-1);
/* Use the environment to expand the instance name and look it up
* in the job.
*/
name = environ_expand (NULL, class->instance, restart_env);
if (! name) {
NihError *error;
error = nih_error_get ();
if (error->number != ENOMEM) {
error = nih_error_steal ();
nih_dbus_error_raise (DBUS_ERROR_INVALID_ARGS,
error->message);
nih_free (error);
}
return -1;
}
job = (Job *)nih_hash_lookup (class->instances, name);
if (! job) {
nih_dbus_error_raise_printf (
DBUS_INTERFACE_UPSTART ".Error.UnknownInstance",
_("Unknown instance: %s"), name);
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);
return -1;
}
if (wait)
blocked = NIH_MUST (blocked_new (job,
BLOCKED_JOB_RESTART_METHOD,
message));
if (job->start_env)
nih_unref (job->start_env, job);
job->start_env = restart_env;
nih_ref (job->start_env, job);
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_class_restart_reply (message, job->path));
return 0;
}
/**
* job_class_get:
*
* @name: name of job class,
* @session: session of job class.
*
* Obtain JobClass with name @name and session @session.
*
* Returns: JobClass, or NULL if no matching job class found.
**/
JobClass *
job_class_get (const char *name, Session *session)
{
JobClass *class = NULL;
NihList *prev = NULL;
nih_assert (name);
job_class_init ();
do {
class = (JobClass *)nih_hash_search (job_classes, name, prev);
if (! class)
return NULL;
if (class && class->session == session)
return class;
nih_assert (class);
prev = (NihList *)class;
} while (TRUE);
nih_assert_not_reached ();
}
/**
* job_class_get_name:
* @class: class 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.Job interface.
*
* Called to obtain the name of the given @class, which will be stored in
* @name.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_name (JobClass *class,
NihDBusMessage *message,
char **name)
{
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (name != NULL);
*name = class->name;
nih_ref (*name, message);
return 0;
}
/**
* job_class_get_description:
* @class: class to obtain name from,
* @message: D-Bus connection and message received,
* @description: pointer for reply string.
*
* Implements the get method for the description property of the
* com.ubuntu.Upstart.Job interface.
*
* Called to obtain the description of the given @class, which will be stored
* in @description.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_description (JobClass *class,
NihDBusMessage *message,
char **description)
{
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (description != NULL);
if (class->description) {
*description = class->description;
nih_ref (*description, message);
} else {
*description = nih_strdup (message, "");
if (! *description)
nih_return_no_memory_error (-1);
}
return 0;
}
/**
* job_class_get_author:
* @class: class to obtain name from,
* @message: D-Bus connection and message received,
* @author: pointer for reply string.
*
* Implements the get method for the author property of the
* com.ubuntu.Upstart.Job interface.
*
* Called to obtain the author of the given @class, which will be stored
* in @author.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_author (JobClass *class,
NihDBusMessage *message,
char **author)
{
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (author != NULL);
if (class->author) {
*author = class->author;
nih_ref (*author, message);
} else {
*author = nih_strdup (message, "");
if (! *author)
nih_return_no_memory_error (-1);
}
return 0;
}
/**
* job_class_get_version:
* @class: class to obtain name from,
* @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.Job interface.
*
* Called to obtain the version of the given @class, which will be stored
* in @version.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_version (JobClass *class,
NihDBusMessage *message,
char **version)
{
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (version != NULL);
if (class->version) {
*version = class->version;
nih_ref (*version, message);
} else {
*version = nih_strdup (message, "");
if (! *version)
nih_return_no_memory_error (-1);
}
return 0;
}
/**
* job_class_get_start_on:
* @class: class to obtain events from,
* @message: D-Bus connection and message received,
* @start_on: pointer for reply array.
*
* Implements the get method for the start_on property of the
* com.ubuntu.Upstart.Job interface.
*
* Called to obtain the set of events that will start jobs of the given
* @class, this is returned as an array of the event tree flattened into
* reverse polish form.
*
* Each array element is an array of strings representing the events,
* or a single element containing "/OR" or "/AND" to represent the
* operators.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_start_on (JobClass * class,
NihDBusMessage *message,
char **** start_on)
{
size_t len = 0;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (start_on != NULL);
*start_on = nih_alloc (message, sizeof (char ***));
if (! *start_on)
nih_return_no_memory_error (-1);
len = 0;
(*start_on)[len] = NULL;
if (class->start_on) {
NIH_TREE_FOREACH_POST (&class->start_on->node, iter) {
EventOperator *oper = (EventOperator *)iter;
*start_on = nih_realloc (*start_on, message,
sizeof (char ***) * (len + 2));
if (! *start_on)
nih_return_no_memory_error (-1);
(*start_on)[len] = nih_str_array_new (*start_on);
if (! (*start_on)[len])
nih_return_no_memory_error (-1);
switch (oper->type) {
case EVENT_OR:
if (! nih_str_array_add (&(*start_on)[len], *start_on,
NULL, "/OR"))
nih_return_no_memory_error (-1);
break;
case EVENT_AND:
if (! nih_str_array_add (&(*start_on)[len], *start_on,
NULL, "/AND"))
nih_return_no_memory_error (-1);
break;
case EVENT_MATCH:
if (! nih_str_array_add (&(*start_on)[len], *start_on,
NULL, oper->name))
nih_return_no_memory_error (-1);
if (oper->env)
if (! nih_str_array_append (&(*start_on)[len], *start_on,
NULL, oper->env))
nih_return_no_memory_error (-1);
break;
}
(*start_on)[++len] = NULL;
}
}
return 0;
}
/**
* job_class_get_stop_on:
* @class: class to obtain events from,
* @message: D-Bus connection and message received,
* @stop_on: pointer for reply array.
*
* Implements the get method for the stop_on property of the
* com.ubuntu.Upstart.Job interface.
*
* Called to obtain the set of events that will stop jobs of the given
* @class, this is returned as an array of the event tree flattened into
* reverse polish form.
*
* Each array element is an array of strings representing the events,
* or a single element containing "/OR" or "/AND" to represent the
* operators.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_stop_on (JobClass * class,
NihDBusMessage *message,
char **** stop_on)
{
size_t len = 0;
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (stop_on != NULL);
*stop_on = nih_alloc (message, sizeof (char ***));
if (! *stop_on)
nih_return_no_memory_error (-1);
len = 0;
(*stop_on)[len] = NULL;
if (class->stop_on) {
NIH_TREE_FOREACH_POST (&class->stop_on->node, iter) {
EventOperator *oper = (EventOperator *)iter;
*stop_on = nih_realloc (*stop_on, message,
sizeof (char ***) * (len + 2));
if (! *stop_on)
nih_return_no_memory_error (-1);
(*stop_on)[len] = nih_str_array_new (*stop_on);
if (! (*stop_on)[len])
nih_return_no_memory_error (-1);
switch (oper->type) {
case EVENT_OR:
if (! nih_str_array_add (&(*stop_on)[len], *stop_on,
NULL, "/OR"))
nih_return_no_memory_error (-1);
break;
case EVENT_AND:
if (! nih_str_array_add (&(*stop_on)[len], *stop_on,
NULL, "/AND"))
nih_return_no_memory_error (-1);
break;
case EVENT_MATCH:
if (! nih_str_array_add (&(*stop_on)[len], *stop_on,
NULL, oper->name))
nih_return_no_memory_error (-1);
if (oper->env)
if (! nih_str_array_append (&(*stop_on)[len], *stop_on,
NULL, oper->env))
nih_return_no_memory_error (-1);
break;
}
(*stop_on)[++len] = NULL;
}
}
return 0;
}
/**
* job_class_get_emits:
* @class: class to obtain events from,
* @message: D-Bus connection and message received,
* @emits: pointer for reply array.
*
* Implements the get method for the emits property of the
* com.ubuntu.Upstart.Job interface.
*
* Called to obtain the list of additional events of the given @class
* which will be stored as an array in @emits.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_emits (JobClass * class,
NihDBusMessage *message,
char *** emits)
{
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (emits != NULL);
if (class->emits) {
*emits = nih_str_array_copy (message, NULL, class->emits);
if (! *emits)
nih_return_no_memory_error (-1);
} else {
*emits = nih_str_array_new (message);
if (! *emits)
nih_return_no_memory_error (-1);
}
return 0;
}
/**
* job_class_console_type:
* @console: string representing console type.
*
* Returns: ConsoleType equivalent of @string, or -1 on invalid @console.
**/
ConsoleType
job_class_console_type (const char *console)
{
if (! strcmp (console, "none")) {
return CONSOLE_NONE;
} else if (! strcmp (console, "output")) {
return CONSOLE_OUTPUT;
} else if (! strcmp (console, "owner")) {
return CONSOLE_OWNER;
} else if (! strcmp (console, "log")) {
return CONSOLE_LOG;
}
return (ConsoleType)-1;
}
/**
* job_class_get_usage:
* @class: class to obtain usage from,
* @message: D-Bus connection and message received,
* @usage: pointer for reply string.
*
* Implements the get method for the usage property of the
* com.ubuntu.Upstart.Job interface.
*
* Called to obtain the usage of the given @class
* which will be stored as an string in @usage.
*
* Returns: zero on success, negative value on raised error.
**/
int
job_class_get_usage (JobClass * class,
NihDBusMessage *message,
char ** usage)
{
nih_assert (class != NULL);
nih_assert (message != NULL);
nih_assert (usage != NULL);
if (class->usage) {
*usage = nih_strdup (message, class->usage);
}
else {
*usage = nih_strdup (message, "");
}
if (! *usage) {
nih_return_no_memory_error (-1);
}
return 0;
}
/**
* job_class_serialise_job_environ:
*
* Serialise the global job environment table.
*
* Returns: JSON-serialised global job environment table, or NULL on error.
**/
json_object *
job_class_serialise_job_environ (void)
{
json_object *json;
job_class_environment_init ();
json = state_serialise_str_array (job_environ);
if (! json)
goto error;
return json;
error:
json_object_put (json);
return NULL;
}
/**
* job_class_deserialise_job_environ
* @json: JSON-serialised global job environment table to deserialise.
*
* Create the global job environment table from provided JSON.
*
* Returns: 0 on success, < 0 on error.
**/
int
job_class_deserialise_job_environ (json_object *json)
{
nih_assert (json);
nih_assert (! job_environ);
if (! state_check_json_type (json, array))
goto error;
if (! state_deserialise_str_array (NULL, json, &job_environ))
goto error;
return 0;
error:
return -1;
}
/**
* job_class_serialise:
* @class: job class to serialise.
*
* Convert @class into a JSON representation for serialisation.
* Caller must free returned value using json_object_put().
*
* Returns: JSON-serialised JobClass object, or NULL on error.
**/
json_object *
job_class_serialise (JobClass *class)
{
json_object *json;
json_object *json_export;
json_object *json_emits;
json_object *json_processes;
json_object *json_normalexit;
json_object *json_limits;
json_object *json_jobs;
json_object *json_start_on;
json_object *json_stop_on;
int session_index;
#ifdef ENABLE_CGROUPS
json_object *json_cgroups;
#endif /* ENABLE_CGROUPS */
nih_assert (class);
nih_assert (job_classes);
json = json_object_new_object ();
if (! json)
return NULL;
session_index = session_get_index (class->session);
if (session_index < 0)
goto error;
if (! state_set_json_int_var (json, "session", session_index))
goto error;
if (! state_set_json_string_var_from_obj (json, class, name))
goto error;
if (! state_set_json_string_var_from_obj (json, class, path))
goto error;
if (! state_set_json_string_var_from_obj (json, class, instance))
goto error;
json_jobs = job_serialise_all (class->instances);
if (! json_jobs)
goto error;
json_object_object_add (json, "jobs", json_jobs);
if (! state_set_json_string_var_from_obj (json, class, description))
goto error;
if (! state_set_json_string_var_from_obj (json, class, author))
goto error;
if (! state_set_json_string_var_from_obj (json, class, version))
goto error;
if (! state_set_json_str_array_from_obj (json, class, env))
goto error;
json_export = class->export
? state_serialise_str_array (class->export)
: json_object_new_array ();
if (! json_export)
goto error;
json_object_object_add (json, "export", json_export);
if (class->start_on) {
json_start_on = event_operator_serialise_all (class->start_on);
if (! json_start_on)
goto error;
json_object_object_add (json, "start_on", json_start_on);
}
if (class->stop_on) {
json_stop_on = event_operator_serialise_all (class->stop_on);
if (! json_stop_on)
goto error;
json_object_object_add (json, "stop_on", json_stop_on);
}
json_emits = class->emits
? state_serialise_str_array (class->emits)
: json_object_new_array ();
if (! json_emits)
goto error;
json_object_object_add (json, "emits", json_emits);
json_processes = process_serialise_all (
(const Process * const * const)class->process);
if (! json_processes)
goto error;
json_object_object_add (json, "process", json_processes);
if (! state_set_json_enum_var (json,
job_class_expect_type_enum_to_str,
"expect", class->expect))
goto error;
if (! state_set_json_int_var_from_obj (json, class, task))
goto error;
if (! state_set_json_int_var_from_obj (json, class, kill_timeout))
goto error;
if (! state_set_json_int_var_from_obj (json, class, kill_signal))
goto error;
if (! state_set_json_int_var_from_obj (json, class, reload_signal))
goto error;
if (! state_set_json_int_var_from_obj (json, class, respawn))
goto error;
if (! state_set_json_int_var_from_obj (json, class, respawn_limit))
goto error;
if (! state_set_json_int_var_from_obj (json, class, respawn_interval))
goto error;
json_normalexit = state_serialise_int_array (int, class->normalexit,
class->normalexit_len);
if (! json_normalexit)
goto error;
json_object_object_add (json, "normalexit", json_normalexit);
if (! state_set_json_enum_var (json,
job_class_console_type_enum_to_str,
"console", class->console))
goto error;
if (! state_set_json_int_var_from_obj (json, class, umask))
goto error;
if (! state_set_json_int_var_from_obj (json, class, nice))
goto error;
if (! state_set_json_int_var_from_obj (json, class, oom_score_adj))
goto error;
json_limits = state_rlimit_serialise_all (class->limits);
if (! json_limits)
goto error;
json_object_object_add (json, "limits", json_limits);
if (! state_set_json_string_var_from_obj (json, class, chroot))
goto error;
if (! state_set_json_string_var_from_obj (json, class, chdir))
goto error;
if (! state_set_json_string_var_from_obj (json, class, setuid))
goto error;
if (! state_set_json_string_var_from_obj (json, class, setgid))
goto error;
if (! state_set_json_int_var_from_obj (json, class, deleted))
goto error;
if (! state_set_json_int_var_from_obj (json, class, debug))
goto error;
if (! state_set_json_string_var_from_obj (json, class, usage))
goto error;
if (! state_set_json_string_var_from_obj (json, class, apparmor_switch))
goto error;
if (! state_set_json_int_var_from_obj (json, class, cgmanager_wait))
goto error;
#ifdef ENABLE_CGROUPS
json_cgroups = cgroup_serialise_all (&class->cgroups);
if (! json_cgroups)
goto error;
json_object_object_add (json, "cgroups", json_cgroups);
#endif /* ENABLE_CGROUPS */
return json;
error:
json_object_put (json);
return NULL;
}
/**
* job_class_serialise_all:
*
* Convert existing JobClass objects in job classes hash to JSON
* representation.
*
* NOTE: despite its name, this function does not _necessarily_
* serialise all JobClasses - there may be "best" (ie newer) JobClasses
* associated with ConfFiles that have not yet replaced the existing
* entries in the job classes hash if the JobClass has running instances.
*
* However, this is academic since although such data is not serialised,
* after the re-exec conf_reload() is called to recreate these "best"
* JobClasses. This also has the nice side-effect of ensuring that
* should jobs get created in the window when Upstart is statefully
* re-exec'ing, it will always see the newest versions of on-disk files
* (which is what the user expects).
*
* Returns: JSON object containing array of JobClass objects,
* or NULL on error.
**/
json_object *
job_class_serialise_all (void)
{
json_object *json;
job_class_init ();
json = json_object_new_array ();
if (! json)
return NULL;
NIH_HASH_FOREACH (job_classes, iter) {
json_object *json_class;
JobClass *class = (JobClass *)iter;
json_class = job_class_serialise (class);
if (! json_class)
goto error;
json_object_array_add (json, json_class);
}
return json;
error:
json_object_put (json);
return NULL;
}
/**
* job_class_deserialise:
* @json: JSON-serialised JobClass object to deserialise.
*
* Create JobClass from provided JSON and add to the
* job classes table.
*
* Returns: JobClass object, or NULL on error.
**/
JobClass *
job_class_deserialise (json_object *json)
{
json_object *json_normalexit;
JobClass *class = NULL;
ConfFile *file = NULL;
Session *session;
int session_index = -1;
int ret;
nih_local char *name = NULL;
nih_local char *path = NULL;
json_object *json_start_on = NULL;
json_object *json_stop_on = NULL;
nih_assert (json);
nih_assert (job_classes);
if (! state_check_json_type (json, object))
goto error;
if (! state_get_json_int_var (json, "session", session_index))
goto error;
if (session_index < 0)
goto error;
session = session_from_index (session_index);
/* XXX: chroot and old user session jobs not currently supported */
if (session) {
nih_info ("WARNING: deserialisation of user/chroot "
"sessions not currently supported");
goto error;
}
if (! state_get_json_string_var_strict (json, "name", NULL, name))
goto error;
/* Create the class and associate it with the ConfFile */
class = job_class_new (NULL, name, session);
if (! class)
goto error;
/* Lookup the ConfFile associated with this class.
*
* Don't error if this fails since previous serialisation data
* formats did not encode ConfSources and ConfFiles.
*/
file = conf_file_find (name, session);
if (file)
file->job = class;
/* job_class_new() sets path */
if (! state_get_json_string_var_strict (json, "path", NULL, path))
goto error;
nih_assert (! strcmp (class->path, path));
/* Discard default instance as we're about to be handed a fresh
* string from the JSON.
*/
nih_free (class->instance);
if (! state_get_json_string_var_to_obj (json, class, instance))
goto error;
if (! state_get_json_string_var_to_obj (json, class, description))
goto error;
if (! state_get_json_string_var_to_obj (json, class, author))
goto error;
if (! state_get_json_string_var_to_obj (json, class, version))
goto error;
if (! state_get_json_env_array_to_obj (json, class, env))
goto error;
if (! state_get_json_env_array_to_obj (json, class, export))
goto error;
/* start and stop conditions are optional */
if (json_object_object_get_ex (json, "start_on", &json_start_on)) {
if (state_check_json_type (json_start_on, array)) {
class->start_on = event_operator_deserialise_all (class, json_start_on);
if (! class->start_on)
goto error;
} else {
nih_local char *start_on = NULL;
/* Old format (string).
*
* Note that we re-search for the JSON key here
* (json, rather than json_start_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, "start_on", NULL, start_on))
goto error;
if (*start_on) {
class->start_on = parse_on_simple (class, "start", start_on);
if (! class->start_on) {
NihError *err;
err = nih_error_get ();
nih_error ("%s %s: %s",
_("BUG"),
_("'start on' parse error"),
err->message);
nih_free (err);
goto error;
}
}
}
}
if (json_object_object_get_ex (json, "stop_on", &json_stop_on)) {
if (state_check_json_type (json_stop_on, array)) {
class->stop_on = event_operator_deserialise_all (class, json_stop_on);
if (! class->stop_on)
goto error;
} else {
nih_local char *stop_on = NULL;
/* Old format (string) - re-search as above */
if (! state_get_json_string_var_strict (json, "stop_on", NULL, stop_on))
goto error;
if (*stop_on) {
class->stop_on = parse_on_simple (class, "stop", stop_on);
if (! class->stop_on) {
NihError *err;
err = nih_error_get ();
nih_error ("%s %s: %s",
_("BUG"),
_("'stop on' parse error"),
err->message);
nih_free (err);
goto error;
}
}
}
}
if (! state_get_json_str_array_to_obj (json, class, emits))
goto error;
if (! state_get_json_enum_var (json,
job_class_expect_type_str_to_enum,
"expect", class->expect))
goto error;
if (! state_get_json_int_var_to_obj (json, class, task))
goto error;
if (! state_get_json_int_var_to_obj (json, class, kill_timeout))
goto error;
if (! state_get_json_int_var_to_obj (json, class, kill_signal))
goto error;
/* reload_signal is new in upstart 1.10+ */
if (json_object_object_get_ex (json, "reload_signal", NULL)) {
if (! state_get_json_int_var_to_obj (json, class, reload_signal))
goto error;
}
if (! state_get_json_int_var_to_obj (json, class, respawn))
goto error;
if (! state_get_json_int_var_to_obj (json, class, respawn_limit))
goto error;
if (! state_get_json_int_var_to_obj (json, class, respawn_interval))
goto error;
if (! state_get_json_enum_var (json,
job_class_console_type_str_to_enum,
"console", class->console))
goto error;
if (! state_get_json_int_var_to_obj (json, class, umask))
goto error;
if (! state_get_json_int_var_to_obj (json, class, nice))
goto error;
if (! state_get_json_int_var_to_obj (json, class, oom_score_adj))
goto error;
if (! state_get_json_string_var_to_obj (json, class, chroot))
goto error;
if (! state_get_json_string_var_to_obj (json, class, chdir))
goto error;
if (! state_get_json_string_var_to_obj (json, class, setuid))
goto error;
if (! state_get_json_string_var_to_obj (json, class, setgid))
goto error;
if (! state_get_json_int_var_to_obj (json, class, deleted))
goto error;
if (! state_get_json_int_var_to_obj (json, class, debug))
goto error;
if (! state_get_json_string_var_to_obj (json, class, usage))
goto error;
/* If we are missing this, we're probably importing from a
* previous version that didn't include PROCESS_SECURITY.
*/
if (json_object_object_get_ex (json, "apparmor_switch", NULL)) {
if (! state_get_json_string_var_to_obj (json, class, apparmor_switch))
goto error;
}
if (! json_object_object_get_ex (json, "normalexit", &json_normalexit))
goto error;
ret = state_deserialise_int_array (class, json_normalexit,
int, &class->normalexit, &class->normalexit_len);
if (ret < 0)
goto error;
if (state_rlimit_deserialise_all (json, class, &class->limits) < 0)
goto error;
if (process_deserialise_all (json, class->process, class->process) < 0)
goto error;
if (file) {
/* Add the class to the job_classes hash if ConfFiles were
* available in the serialisation data.
*/
job_class_consider (class);
} else {
/* No ConfSources and ConfFiles were available in the
* serialisation data, so special-case the insertion.
*/
job_class_add_safe (class);
}
/* Any jobs must be added after the class is registered
* (since you cannot add a job to a partially-created
* class).
*/
if (job_deserialise_all (class, json) < 0)
goto error;
#ifdef ENABLE_CGROUPS
if (json_object_object_get_ex (json, "cgmanager_wait", NULL)) {
if (cgroup_deserialise_all (class, &class->cgroups, json) < 0)
goto error;
if (! state_get_json_int_var_to_obj (json, class, cgmanager_wait))
goto error;
}
#endif /* ENABLE_CGROUPS */
return class;
error:
if (class)
nih_free (class);
return NULL;
}
/**
* job_class_deserialise_all:
*
* @json: root of JSON-serialised state.
*
* Convert JSON representation of JobClasses back into JobClass objects.
*
* Returns: 0 on success, -1 on error.
**/
int
job_class_deserialise_all (json_object *json)
{
JobClass *class = NULL;
nih_assert (json);
job_class_init ();
if (! json_object_object_get_ex (json, "job_classes", &json_classes))
goto error;
if (! state_check_json_type (json_classes, array))
goto error;
for (int i = 0; i < json_object_array_length (json_classes); i++) {
json_object *json_class;
json_class = json_object_array_get_idx (json_classes, i);
if (! json_class)
goto error;
if (! state_check_json_type (json_class, object))
goto error;
/* Responsible for associating a JobClass with its
* parent ConfFile.
*/
class = job_class_deserialise (json_class);
/* Either memory is low or -- more likely -- a JobClass
* with a session was encountered, so keep going.
*/
if (! class) {
int session_index = -1;
if (state_get_json_int_var (json_class, "session", session_index)
&& session_index > 0) {
/* Although ConfSources are now serialised, ignore
* JobClasses with associated user/chroot sessions to avoid
* behavioural changes for the time being.
*/
continue;
} else {
goto error;
}
}
}
return 0;
error:
if (class)
nih_free (class);
return -1;
}
/**
* job_class_expect_type_enum_to_str:
*
* @expect: ExpectType.
*
* Convert ExpectType to a string representation.
*
* Returns: string representation of @expect, or NULL if not known.
**/
const char *
job_class_expect_type_enum_to_str (ExpectType expect)
{
state_enum_to_str (EXPECT_NONE, expect);
state_enum_to_str (EXPECT_STOP, expect);
state_enum_to_str (EXPECT_DAEMON, expect);
state_enum_to_str (EXPECT_FORK, expect);
return NULL;
}
/**
* job_class_expect_type_str_to_enum:
*
* @expect: string ExpectType value.
*
* Convert @expect back into an enum value.
*
* Returns: ExpectType representing @expect, or -1 if not known.
**/
ExpectType
job_class_expect_type_str_to_enum (const char *expect)
{
nih_assert (expect);
state_str_to_enum (EXPECT_NONE, expect);
state_str_to_enum (EXPECT_STOP, expect);
state_str_to_enum (EXPECT_DAEMON, expect);
state_str_to_enum (EXPECT_FORK, expect);
return -1;
}
/**
* job_class_console_type_enum_to_str:
*
* @console: ConsoleType.
*
* Convert ConsoleType to a string representation.
*
* Returns: string representation of @console, or NULL if not known.
**/
const char *
job_class_console_type_enum_to_str (ConsoleType console)
{
state_enum_to_str (CONSOLE_NONE, console);
state_enum_to_str (CONSOLE_OUTPUT, console);
state_enum_to_str (CONSOLE_OWNER, console);
state_enum_to_str (CONSOLE_LOG, console);
return NULL;
}
/**
* job_class_console_type_str_to_enum:
*
* @console: string ConsoleType value.
*
* Convert @console back into enum value.
*
* Returns: ExpectType representing @console, or -1 if not known.
**/
ConsoleType
job_class_console_type_str_to_enum (const char *console)
{
if (! console)
goto error;
state_str_to_enum (CONSOLE_NONE, console);
state_str_to_enum (CONSOLE_OUTPUT, console);
state_str_to_enum (CONSOLE_OWNER, console);
state_str_to_enum (CONSOLE_LOG, console);
error:
return -1;
}
/**
* job_class_prepare_reexec:
*
* Prepare for a re-exec by clearing the CLOEXEC bit on all log object
* file descriptors associated with their parent jobs.
**/
void
job_class_prepare_reexec (void)
{
job_class_init ();
NIH_HASH_FOREACH (job_classes, iter) {
JobClass *class = (JobClass *)iter;
NIH_HASH_FOREACH (class->instances, job_iter) {
Job *job = (Job *)job_iter;
nih_assert (job->log);
for (int process = 0; process < PROCESS_LAST; process++) {
int fd;
Log *log;
log = job->log[process];
/* No associated job process or logger has detected
* remote end of pty has closed.
*/
if (! log || ! log->io)
continue;
nih_assert (log->io->watch);
fd = log->io->watch->fd;
if (fd < 0)
continue;
if (state_modify_cloexec (fd, FALSE) < 0)
goto error;
fd = log->fd;
if (fd < 0)
continue;
if (state_modify_cloexec (fd, FALSE) < 0)
goto error;
}
}
}
return;
error:
nih_warn (_("unable to clear CLOEXEC bit on log fd"));
}
/**
* job_class_max_kill_timeout:
*
* Determine maximum kill timeout for all running jobs.
*
* Returns: Maximum kill timeout (seconds).
**/
time_t
job_class_max_kill_timeout (void)
{
time_t kill_timeout = 1;
job_class_init ();
NIH_HASH_FOREACH (job_classes, iter) {
JobClass *class = (JobClass *)iter;
NIH_HASH_FOREACH (class->instances, job_iter) {
Job *job = (Job *)job_iter;
time_t timeout = 0;
if (job->class->process[PROCESS_MAIN])
timeout += job->class->kill_timeout;
if (job->class->process[PROCESS_PRE_STOP])
timeout += job->class->kill_timeout;
if (job->class->process[PROCESS_POST_STOP])
timeout += job->class->kill_timeout;
if (timeout > kill_timeout) {
kill_timeout = timeout;
break;
}
}
}
return kill_timeout;
}
/**
* job_class_get_index:
* @class: JobClass to search for.
*
* Returns: index of @class in the job classes hash,
* or -1 if not found.
**/
ssize_t
job_class_get_index (const JobClass *class)
{
ssize_t i = 0;
nih_assert (class);
NIH_HASH_FOREACH (job_classes, iter) {
JobClass *c = (JobClass *)iter;
if (! strcmp (c->name, class->name)
&& c->session == class->session)
return i;
i++;
}
return -1;
}
/**
* job_class_induct_job:
* @class: Start a job of a given class
*
* Returns: TRUE on success, otherwise FALSE.
**/
int
job_class_induct_job (JobClass *class)
{
nih_local char **env = NULL;
nih_local char *name = NULL;
size_t len;
Job *job;
nih_assert (class);
job_class_init ();
/* Construct the environment for the new instance
* from the class and the start events.
*/
env = NIH_MUST (job_class_environment (
NULL, class, &len));
event_environment_init (class->start_on, class, &env,
NULL, &len, "UPSTART_EVENTS");
/* Expand the instance name against the environment */
name = NIH_SHOULD (environ_expand (NULL,
class->instance,
env));
if (! name) {
NihError *err;
err = nih_error_get ();
nih_warn (_("Failed to obtain %s instance: %s"),
class->name, err->message);
nih_free (err);
event_operator_reset (class->start_on);
return FALSE;
}
/* Locate the current instance or create a new one */
job = (Job *)nih_hash_lookup (class->instances, name);
if (! job)
job = NIH_MUST (job_new (class, name));
nih_debug ("New instance %s", job_name (job));
/* Start the job with the environment we want */
if (job->goal != JOB_START) {
if (job->start_env)
nih_unref (job->start_env, job);
job->start_env = env;
nih_ref (job->start_env, job);
nih_discard (env);
env = NULL;
job_finished (job, FALSE);
NIH_MUST (event_operator_fds (class->start_on, job,
&job->fds, &job->num_fds,
&job->start_env, &len,
"UPSTART_FDS"));
event_operator_events (job->class->start_on,
job, &job->blocking);
job_change_goal (job, JOB_START);
}
event_operator_reset (class->start_on);
return TRUE;
}
#ifdef ENABLE_CGROUPS
/**
* job_class_induct_jobs:
*
* Start all jobs waiting on a cgmanager.
*
* Returns: TRUE on success, if induction of any job fails returns FALSE.
**/
int
job_class_induct_jobs (void)
{
nih_assert (cgroup_manager_available ());
job_class_init ();
int success = TRUE;
NIH_HASH_FOREACH_SAFE (job_classes, iter) {
JobClass *class = (JobClass *)iter;
if (! class->start_on)
continue;
if (! class->cgmanager_wait)
continue;
nih_assert (class->start_on->value);
if (! job_class_induct_job (class))
success = FALSE;
/* Unref the events that were ref'ed
* whilst waiting for the cgroup manager
* to become available.
*/
event_operator_reset (class->start_on);
class->cgmanager_wait = FALSE;
}
return success;
}
/**
* job_class_cgroups:
*
* @class: JobClass.
*
* Determine if the specified class needs cgroup support.
*
* Returns TRUE if cgroups are required, else FALSE.
*
**/
int
job_class_cgroups (JobClass *class)
{
nih_assert (class);
if (NIH_LIST_EMPTY (&class->cgroups))
return FALSE;
return TRUE;
}
#endif /* ENABLE_CGROUPS */