| /* 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 ? ®istered->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 */ |