| Committer: James Hunt |
| Date: 2011-06-01 11:13:42 UTC |
| mto: This revision was merged to the branch mainline in revision 1289. |
| Revision ID: james.hunt@ubuntu.com-20110601111342-3iotzheeb9vd05a2 |
| Add "show-config" command to initctl. |
| |
| * util/initctl.c: |
| - New functions: |
| - job_class_condition_handler(): Handler function to retrieve job conditions. |
| - job_class_condition_err_handler(): Handler error function for |
| job_class_condition_handler(). |
| - job_class_parse_events(): Convert RPN "start on" and "stop on" conditions to |
| human-readable format. |
| - job_class_show_emits(): Display events which job emits. |
| - job_class_show_conditions(): Make D-Bus calls to retrieve "start on" and |
| "stop on" conditions. |
| - show_config_action: Handle "show-config" command.. |
| * util/initctl.h: New file providing stack-handling functionality for |
| RPN parsing for "show-config" command. |
| * util/Makefile.am: Added initctl.h to initctl_SOURCES. |
| * util/man/initctl.8: Updated for "show-config" command and associated |
| options. |
| * util/tests/test_initctl.c: |
| - New macros START_UPSTART, STOP_UPSTART, RUN_COMMAND, CREATE_FILE and DELETE_FILE. |
| These are required since due to the introduction of the |
| "show-config" initctl command, initctl is no longer solely a proxy |
| to Upstart: it now has some intelligence (it parses the |
| "emits", "start on" and "stop on" conditions) and thus must be |
| tested directly. |
| - test_show_config(): New function to test "initctl show-config". |
| - in_chroot(): New function to detect if tests are being run from |
| within a chroot environment. |
| - dbus_configured(): New function which performs a basic check to |
| establish if D-Bus is configured correctly. |
| - main(): Added call to test_show_config(), conditional on |
| a non-chroot environment and a working D-Bus system. |
| |
| https://bazaar.launchpad.net/~upstart-devel/upstart/trunk/revision/1288.1.1 |
| |
| === modified file 'util/Makefile.am' |
| --- util/Makefile.am 2011-03-16 22:49:20 +0000 |
| +++ util/Makefile.am 2011-06-01 11:13:42 +0000 |
| @@ -29,7 +29,7 @@ |
| telinit |
| |
| initctl_SOURCES = \ |
| - initctl.c |
| + initctl.c initctl.h |
| nodist_initctl_SOURCES = \ |
| $(com_ubuntu_Upstart_OUTPUTS) \ |
| $(com_ubuntu_Upstart_Job_OUTPUTS) \ |
| |
| === modified file 'util/initctl.c' |
| --- util/initctl.c 2011-06-01 09:42:05 +0000 |
| +++ util/initctl.c 2011-06-01 11:13:42 +0000 |
| @@ -38,6 +38,8 @@ |
| #include <nih/command.h> |
| #include <nih/logging.h> |
| #include <nih/error.h> |
| +#include <nih/hash.h> |
| +#include <nih/tree.h> |
| |
| #include <nih-dbus/dbus_error.h> |
| #include <nih-dbus/dbus_proxy.h> |
| @@ -49,6 +51,9 @@ |
| #include "com.ubuntu.Upstart.Job.h" |
| #include "com.ubuntu.Upstart.Instance.h" |
| |
| +#include "../init/events.h" |
| +#include "initctl.h" |
| + |
| |
| /* Prototypes for local functions */ |
| NihDBusProxy *upstart_open (const void *parent) |
| @@ -63,6 +68,22 @@ |
| static void reply_handler (int *ret, NihDBusMessage *message); |
| static void error_handler (void *data, NihDBusMessage *message); |
| |
| +static void job_class_condition_handler (void *data, |
| + NihDBusMessage *message, |
| + char ** const *value); |
| + |
| +static void job_class_condition_err_handler (void *data, |
| + NihDBusMessage *message); |
| + |
| +static void job_class_parse_events (const ConditionHandlerData *data, |
| + char ** const *variant_array); |
| + |
| +static void job_class_show_emits (const void *parent, |
| + NihDBusProxy *job_class_proxy, const char *job_class_name); |
| + |
| +static void job_class_show_conditions (NihDBusProxy *job_class_proxy, |
| + const char *job_class_name); |
| + |
| #ifndef TEST |
| |
| static int dbus_bus_type_setter (NihOption *option, const char *arg); |
| @@ -80,6 +101,7 @@ |
| int reload_configuration_action (NihCommand *command, char * const *args); |
| int version_action (NihCommand *command, char * const *args); |
| int log_priority_action (NihCommand *command, char * const *args); |
| +int show_config_action (NihCommand *command, char * const *args); |
| |
| |
| /** |
| @@ -122,6 +144,16 @@ |
| int no_wait = FALSE; |
| |
| /** |
| + * enumerate_events: |
| + * |
| + * If TRUE, list out all events/jobs that a particular job *may require* to |
| + * be run: essentially any event/job mentioned in a job configuration files |
| + * "start on" / "stop on" condition. Used for showing dependencies |
| + * between jobs and events. |
| + **/ |
| +int enumerate_events = FALSE; |
| + |
| +/** |
| * NihOption setter function to handle selection of appropriate D-Bus |
| * bus. |
| * |
| @@ -250,7 +282,7 @@ |
| * of the returned string are freed, the returned string will also be |
| * freed. |
| * |
| - * Returns: newly allocated string or NULL on raised error.. |
| + * Returns: newly allocated string or NULL on raised error. |
| **/ |
| char * |
| job_status (const void * parent, |
| @@ -704,6 +736,7 @@ |
| (JobClassRestartReply)start_reply_handler, |
| error_handler, &job_path, |
| NIH_DBUS_TIMEOUT_NEVER); |
| + |
| if (! pending_call) |
| goto error; |
| } |
| @@ -1080,6 +1113,77 @@ |
| return 1; |
| } |
| |
| +/** |
| + * show_config_action: |
| + * @command: NihCommand invoked, |
| + * @args: command-line arguments. |
| + * |
| + * This function is called for the "show-config" command. |
| + * |
| + * Returns: command exit status. |
| + **/ |
| +int |
| +show_config_action (NihCommand * command, |
| + char * const *args) |
| +{ |
| + nih_local NihDBusProxy *upstart = NULL; |
| + nih_local char **job_class_paths = NULL; |
| + const char *upstart_job_class = NULL; |
| + NihError *err; |
| + |
| + nih_assert (command != NULL); |
| + nih_assert (args != NULL); |
| + |
| + upstart = upstart_open (NULL); |
| + if (! upstart) |
| + return 1; |
| + |
| + if (args[0]) { |
| + /* Single job specified */ |
| + upstart_job_class = args[0]; |
| + job_class_paths = NIH_MUST (nih_alloc (NULL, 2*sizeof (char *))); |
| + job_class_paths[1] = NULL; |
| + |
| + if (upstart_get_job_by_name_sync (NULL, upstart, upstart_job_class, |
| + job_class_paths) < 0) |
| + goto error; |
| + } else { |
| + /* Obtain a list of jobs */ |
| + if (upstart_get_all_jobs_sync (NULL, upstart, &job_class_paths) < 0) |
| + goto error; |
| + } |
| + |
| + for (char **job_class_path = job_class_paths; |
| + job_class_path && *job_class_path; job_class_path++) { |
| + nih_local NihDBusProxy *job_class = NULL; |
| + nih_local char *job_class_name = NULL; |
| + |
| + job_class = nih_dbus_proxy_new (NULL, upstart->connection, |
| + upstart->name, *job_class_path, |
| + NULL, NULL); |
| + if (! job_class) |
| + goto error; |
| + |
| + job_class->auto_start = FALSE; |
| + |
| + if (job_class_get_name_sync (NULL, job_class, &job_class_name) < 0) |
| + goto error; |
| + |
| + nih_message ("%s", job_class_name); |
| + |
| + job_class_show_emits (NULL, job_class, job_class_name); |
| + job_class_show_conditions (job_class, job_class_name); |
| + } |
| + |
| + return 0; |
| + |
| +error: |
| + err = nih_error_get (); |
| + nih_error ("%s", err->message); |
| + nih_free (err); |
| + |
| + return 1; |
| +} |
| |
| /** |
| * emit_action: |
| @@ -1291,6 +1395,268 @@ |
| nih_free (err); |
| } |
| |
| +/** |
| + * job_class_parse_events: |
| + * @condition_data: type of condition we are parsing (used as an indicator to |
| + * user) and name of job, |
| + * @variant_array: pointer to array of variants. |
| + * |
| + * The array of variants encodes the event operator tree in |
| + * Reverse Polish Notation (RPN). |
| + * |
| + * Each variant is itself an array. There are two types: |
| + * |
| + * - An "Operator" (array length == 1). |
| + * |
| + * Operators are: "/AND" and "/OR". |
| + * |
| + * - An "Event" (array length >= 1). |
| + * |
| + * Each Event comprises a name (array element zero), followed by zero or |
| + * more "Event Matches". If the first Event Match is of the form "JOB=name", |
| + * or is a single token "name" (crucially not containining "="), then |
| + * 'name' refers to the job which emitted the event. |
| + **/ |
| +void |
| +job_class_parse_events (const ConditionHandlerData *data, char ** const *variant_array) |
| +{ |
| + char ** const *variant; |
| + char **arg; |
| + char *token; |
| + nih_local NihList *rpn_stack = NULL; |
| + char *name = NULL; |
| + const char *stanza_name; |
| + const char *job_class_name; |
| + |
| + nih_assert (data); |
| + |
| + stanza_name = ((ConditionHandlerData *)data)->condition_name; |
| + job_class_name = ((ConditionHandlerData *)data)->job_class_name; |
| + |
| + if (! variant_array || ! *variant_array || ! **variant_array) |
| + return; |
| + |
| + STACK_CREATE (rpn_stack); |
| + STACK_SHOW (rpn_stack); |
| + |
| + for (variant = variant_array; variant && *variant && **variant; variant++, name = NULL) { |
| + |
| + /* token is either the first token beyond the stanza name (ie the event name), |
| + * or an operator. |
| + */ |
| + token = **variant; |
| + |
| + if (IS_OPERATOR (token)) { |
| + /* Used to hold result of combining top two stack elements. */ |
| + nih_local char *new_token = NULL; |
| + |
| + nih_local NihList *first = NULL; |
| + nih_local NihList *second = NULL; |
| + |
| + if (enumerate_events) { |
| + /* We only care about operands in this mode. */ |
| + continue; |
| + } |
| + |
| + first = NIH_MUST (nih_list_new (NULL)); |
| + second = NIH_MUST (nih_list_new (NULL)); |
| + |
| + /* Found an operator, so pop 2 values off stack, |
| + * combine them and push back onto stack. |
| + */ |
| + STACK_POP (rpn_stack, first); |
| + STACK_POP (rpn_stack, second); |
| + |
| + new_token = NIH_MUST (nih_strdup (NULL, "")); |
| + new_token = NIH_MUST (nih_strcat_sprintf (&new_token, |
| + NULL, |
| + "(%s %s %s)", |
| + ((NihListEntry *)second)->str, |
| + IS_OP_AND (token) ? "and" : "or", |
| + ((NihListEntry *)first)->str)); |
| + |
| + STACK_PUSH_NEW_ELEM (rpn_stack, new_token); |
| + } else { |
| + /* Save operand token (event or job), add |
| + * arguments (job names and env vars) and push |
| + * onto stack. If we are enumerating_events, |
| + * this records the environment only. |
| + */ |
| + nih_local char *element = NULL; |
| + int i; |
| + |
| + element = NIH_MUST (nih_strdup (NULL, |
| + enumerate_events ? "" : token)); |
| + |
| + /* Handle arguments (job names and env vars). */ |
| + arg = (*variant)+1; |
| + |
| + for (i=0; arg[i] && *arg[i]; i++) { |
| + if (enumerate_events && IS_JOB_EVENT (token)) { |
| + if (!name) { |
| + GET_JOB_NAME (name, i, arg[i]); |
| + if (name) |
| + continue; |
| + } |
| + } |
| + |
| + element = NIH_MUST (nih_strcat (&element, NULL, " ")); |
| + element = NIH_MUST (nih_strcat (&element, NULL, arg[i])); |
| + } |
| + |
| + if (enumerate_events) { |
| + nih_message (" %s %s (job:%s%s, env:%s)", |
| + stanza_name, |
| + token, |
| + name ? " " : "", |
| + name ? name : "", |
| + element); |
| + } else { |
| + STACK_PUSH_NEW_ELEM (rpn_stack, element); |
| + } |
| + |
| + } |
| + } |
| + |
| + if (enumerate_events) |
| + return; |
| + |
| + /* Handle case where a single event was specified (there |
| + * was no operator to pop the entry off the stack). |
| + */ |
| + if (! STACK_EMPTY (rpn_stack)) { |
| + if (! enumerate_events) { |
| + /* Our job is done: show the user what we found. */ |
| + nih_message (" %s %s", stanza_name, |
| + STACK_PEEK (rpn_stack)); |
| + } |
| + } |
| +} |
| + |
| +/** |
| + * job_class_show_conditions: |
| + * @job_class_proxy: D-Bus proxy for job class. |
| + * @job_class_name: Name of config whose conditions we wish to display. |
| + * |
| + * Register D-Bus call-backs to display job classes start on and stop on |
| + * conditions. |
| + **/ |
| +void |
| +job_class_show_conditions (NihDBusProxy *job_class_proxy, const char *job_class_name) |
| +{ |
| + DBusPendingCall *pending_call; |
| + NihError *err; |
| + ConditionHandlerData start_data, stop_data; |
| + |
| + nih_assert (job_class_proxy); |
| + nih_assert (job_class_name); |
| + |
| + start_data.condition_name = "start on"; |
| + start_data.job_class_name = job_class_name; |
| + |
| + stop_data.condition_name = "stop on"; |
| + stop_data.job_class_name = job_class_name; |
| + |
| + pending_call = job_class_get_start_on (job_class_proxy, |
| + job_class_condition_handler, |
| + job_class_condition_err_handler, |
| + &start_data, |
| + NIH_DBUS_TIMEOUT_NEVER); |
| + |
| + if (!pending_call) |
| + goto error; |
| + |
| + /* wait for completion */ |
| + dbus_pending_call_block (pending_call); |
| + dbus_pending_call_unref (pending_call); |
| + |
| + pending_call = job_class_get_stop_on (job_class_proxy, |
| + job_class_condition_handler, |
| + job_class_condition_err_handler, |
| + &stop_data, |
| + NIH_DBUS_TIMEOUT_NEVER); |
| + |
| + if (!pending_call) |
| + goto error; |
| + |
| + /* wait for completion */ |
| + dbus_pending_call_block (pending_call); |
| + dbus_pending_call_unref (pending_call); |
| + |
| + return; |
| + |
| +error: |
| + err = nih_error_get (); |
| + nih_error ("%s", err->message); |
| + nih_free (err); |
| +} |
| + |
| +/** |
| + * job_class_show_emits: |
| + * @parent: parent object, |
| + * @job_class_proxy: D-Bus proxy for job class, |
| + * @job_class_name: Name of job class that emits an event. |
| + * |
| + * Display events job class emits to user. |
| + **/ |
| +void |
| +job_class_show_emits (const void *parent, NihDBusProxy *job_class_proxy, const char *job_class_name) |
| +{ |
| + NihError *err; |
| + nih_local char **job_emits = NULL; |
| + |
| + nih_assert (job_class_proxy); |
| + |
| + if (job_class_get_emits_sync (parent, job_class_proxy, &job_emits) < 0) { |
| + goto error; |
| + } |
| + |
| + if (job_emits && *job_emits) { |
| + char **p = job_emits; |
| + while (*p) { |
| + nih_message (" emits %s", *p); |
| + p++; |
| + } |
| + } |
| + |
| + return; |
| + |
| +error: |
| + err = nih_error_get (); |
| + nih_error ("%s", err->message); |
| + nih_free (err); |
| +} |
| + |
| +/** |
| + * job_class_condition_handler: |
| + * @data: data passed via job_class_get_start_on() or job_class_get_stop_on(), |
| + * @message: D-Bus message received, |
| + * @value: array of variants generated by D-Bus call we are registering |
| + * with. |
| + * |
| + * Handler for D-Bus message encoding a job classes "start on" or |
| + * "stop on" condtions. |
| + **/ |
| +void |
| +job_class_condition_handler (void *data, NihDBusMessage *message, char ** const *value) |
| +{ |
| + job_class_parse_events ((const ConditionHandlerData *)data, value); |
| +} |
| + |
| +/** |
| + * job_class_condition_err_handler: |
| + * |
| + * @data data passed via job_class_get_start_on() or job_class_get_stop_on(), |
| + * @message: D-Bus message received. |
| + * |
| + * Error handler for D-Bus message encoding a job classes "start on" or |
| + * "stop on" conditions. |
| + **/ |
| +void |
| +job_class_condition_err_handler (void *data, NihDBusMessage *message) |
| +{ |
| + /* no remedial action possible */ |
| +} |
| |
| #ifndef TEST |
| /** |
| @@ -1414,6 +1780,20 @@ |
| |
| |
| /** |
| + * show_config_options: |
| + * |
| + * Command-line options accepted for the show-config command. |
| + **/ |
| +NihOption show_config_options[] = { |
| + { 'e', "enumerate", |
| + N_("enumerate list of events and jobs causing job " |
| + "created from job config to start/stop"), |
| + NULL, NULL, &enumerate_events, NULL }, |
| + |
| + NIH_OPTION_LAST |
| +}; |
| + |
| +/** |
| * job_group: |
| * |
| * Group of commands related to jobs. |
| @@ -1521,6 +1901,12 @@ |
| "Without arguments, this outputs the current log priority."), |
| NULL, log_priority_options, log_priority_action }, |
| |
| + { "show-config", N_("[CONF]"), |
| + N_("Show emits, start on and stop on details for job configurations."), |
| + N_("If CONF specified, show configuration details for single job " |
| + "configuration, else show details for all jobs configurations.\n"), |
| + NULL, show_config_options, show_config_action }, |
| + |
| NIH_COMMAND_LAST |
| }; |
| |
| |
| === added file 'util/initctl.h' |
| --- util/initctl.h 1970-01-01 00:00:00 +0000 |
| +++ util/initctl.h 2011-06-01 11:13:42 +0000 |
| @@ -0,0 +1,330 @@ |
| +/* upstart |
| + * |
| + * Copyright © 2011 Canonical Ltd. |
| + * Author: James Hunt <james.hunt@canonical.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. |
| + */ |
| + |
| +#ifndef INITCTL_H |
| +#define INITCTL_H |
| + |
| +/** |
| + * IS_OP_AND: |
| + * @token: string token to check. |
| + * |
| + * Return TRUE if @token is an 'AND' operator, else FALSE. |
| + **/ |
| +#define IS_OP_AND(token) \ |
| + (! strcmp ((token), "/AND")) |
| + |
| +/** |
| + * IS_OP_OR: |
| + * @token: string token to check. |
| + * |
| + * Return TRUE if @token is an 'OR' operator, else FALSE. |
| + **/ |
| +#define IS_OP_OR(token) \ |
| + (! strcmp ((token), "/OR")) |
| + |
| +/** |
| + * IS_OPERATOR |
| + * @token: string token to check. |
| + * |
| + * Return TRUE if @token is either an 'AND' or an 'OR' operator, |
| + * else FALSE. |
| + **/ |
| +#define IS_OPERATOR(token) \ |
| + IS_OP_AND (token) || IS_OP_OR (token) |
| + |
| + |
| +/** |
| + * GET_JOB_NAME: |
| + * |
| + * @var: char pointer variable which will be set to the name |
| + * of a job, or NULL, |
| + * @index: zero-based index of tokens where zero represents |
| + * the first token after the initial token, |
| + * @token: string to check. |
| + * |
| + * Determine name of job considering the specified token and its |
| + * index. If not a job, sets @name to NULL. |
| + * |
| + * Handles the following cases: |
| + * |
| + * [start|stop] on <job_event> foo |
| + * (index==0, job="foo" => name="foo") |
| + * |
| + * [start|stop] on <job_event> JOB=foo |
| + * (index==0, job="JOB=foo" => name="foo") |
| + * |
| + * [start|stop] on <job_event> A=B JOB=foo |
| + * (index==1, job="JOB=foo" => name="foo") |
| + * |
| + * [start|stop] on <job_event> A=B c=hello JOB=foo |
| + * (index==2, job="JOB=foo" => name="foo") |
| + * |
| + * [start|stop] on <job_event> $JOB A=B c=hello |
| + * (index==0, job="$JOB" => name="$JOB") |
| + * |
| + **/ |
| +#define GET_JOB_NAME(var, index, token) \ |
| +{ \ |
| + char *_##var; \ |
| + \ |
| + nih_assert (index >= 0); \ |
| + nih_assert (token); \ |
| + \ |
| + var = NULL; \ |
| + \ |
| + _##var = strstr (token, "JOB="); \ |
| + \ |
| + if (_##var && _##var == token) \ |
| + var = _##var + strlen ("JOB="); \ |
| + else if (index == 0 ) { \ |
| + if (!strstr (token, "=")) \ |
| + var = token; \ |
| + } \ |
| +} |
| + |
| + |
| +/** |
| + * IS_JOB_EVENT: |
| + * @token: string to check. |
| + * |
| + * Return TRUE if specified token refers to a standard job event, else |
| + * FALSE. |
| + **/ |
| +#define IS_JOB_EVENT(token) \ |
| + (!strcmp (token, JOB_STARTING_EVENT) || \ |
| + !strcmp (token, JOB_STARTED_EVENT) || \ |
| + !strcmp (token, JOB_STOPPING_EVENT) || \ |
| + !strcmp (token, JOB_STOPPED_EVENT)) |
| + |
| +/** |
| + * IS_INIT_EVENT: |
| + * @token: string to check. |
| + * |
| + * Return TRUE if specified token refers to an event emitted internally, |
| + * else FALSE. |
| + * |
| + * Note: the raw string entries below are required to accommodate |
| + * production versus debug builds (STARTUP_EVENT changes name depending |
| + * on build type). |
| + **/ |
| +#define IS_INIT_EVENT(token) \ |
| + (!strcmp (token, STARTUP_EVENT) || \ |
| + !strcmp (token, "debug") || \ |
| + !strcmp (token, "startup") || \ |
| + !strcmp (token, CTRLALTDEL_EVENT) || \ |
| + !strcmp (token, KBDREQUEST_EVENT) || \ |
| + !strcmp (token, PWRSTATUS_EVENT) || \ |
| + IS_JOB_EVENT (token)) |
| + |
| +/** |
| + * STACK_EMPTY: |
| + * @stack: address of stack to check. |
| + * |
| + * Return TRUE if @stack is empty, else FALSE. |
| + **/ |
| +#define STACK_EMPTY(stack) \ |
| + (NIH_LIST_EMPTY (stack)) |
| + |
| +/** |
| + * STACK_CREATE: |
| + * @name: nih_list variable to assign stack to. |
| + **/ |
| +#define STACK_CREATE(name) \ |
| + name = NIH_MUST (nih_list_new (NULL)) |
| +/** |
| + * STACK_SHOW_POP: |
| + * @stack: Address of stack, |
| + * @str: string representing element popped off stack. |
| + * |
| + * Display message to denote that @str has been popped off @stack. |
| + * Does nothing if debug build not enabled. |
| + * |
| + * Note: we cannot assert that stack is not empty since a caller may |
| + * have just removed the last entry before calling us. Thus, it is up to |
| + * the caller to perform such checks. |
| + * |
| + * Does nothing if debug build not enabled. |
| + **/ |
| +#ifdef DEBUG_STACK |
| +#define STACK_SHOW_POP(stack, str) \ |
| + STACK_SHOW_CHANGE (stack, "popped", str) |
| +#else |
| +#define STACK_SHOW_POP(stack, str) |
| +#endif |
| + |
| +/** |
| + * STACK_SHOW_PUSH: |
| + * @stack: Address of stack, |
| + * @str: string representing element pushed onto stack. |
| + * |
| + * Display message to denote that @str has been pushed onto @stack. |
| + * |
| + * Does nothing if debug build not enabled. |
| + **/ |
| +#ifdef DEBUG_STACK |
| +#define STACK_SHOW_PUSH(stack, str) \ |
| + STACK_SHOW_CHANGE (stack, "pushed", str) \ |
| + \ |
| + nih_assert (! STACK_EMPTY (stack)) |
| +#else |
| +#define STACK_SHOW_PUSH(stack, str) |
| +#endif |
| + |
| +/** |
| + * STACK_SHOW: |
| + * @stack: Address of stack. |
| + * |
| + * Display contents of @stack. |
| + * |
| + * Does nothing if debug build not enabled. |
| + **/ |
| +#ifdef DEBUG_STACK |
| +#define STACK_SHOW(stack) \ |
| +{ \ |
| + size_t depth = 0; \ |
| + \ |
| + nih_assert (stack); \ |
| + \ |
| + NIH_LIST_FOREACH (stack, iter) { \ |
| + depth++; \ |
| + } \ |
| + \ |
| + if (STACK_EMPTY (stack)) { \ |
| + nih_message ("STACK@%p: empty", stack); \ |
| + } else { \ |
| + for (NihList *iter = (stack)->next; \ |
| + iter != (stack) && \ |
| + ((NihListEntry *)iter)->str; \ |
| + iter = iter->next, depth--) { \ |
| + nih_message ("STACK@%p[%u]='%s'", \ |
| + (void *)stack, \ |
| + (unsigned int)(depth-1), \ |
| + ((NihListEntry *)iter)->str); \ |
| + } \ |
| + } \ |
| +} |
| +#else |
| +#define STACK_SHOW(stack) |
| +#endif |
| + |
| +/** |
| + * STACK_SHOW_CHANGE: |
| + * @stack: Address of stack, |
| + * @msg: message to show a stack change, |
| + * @element_str: string representing element changed on @stack. |
| + * |
| + * Display a message showing that the stack has been changed. |
| + * |
| + * Does nothing if debug build not enabled. |
| + **/ |
| +#ifdef DEBUG_STACK |
| +#define STACK_SHOW_CHANGE(stack, msg, element_str) \ |
| + nih_assert (msg); \ |
| + nih_assert (element_str); \ |
| + \ |
| + nih_message ("STACK@%p: %s '%s'", \ |
| + (void *)stack, msg, element_str); \ |
| + \ |
| + STACK_SHOW (stack); \ |
| + \ |
| + nih_message (" "); /* spacer */ |
| +#else |
| +#define STACK_SHOW_CHANGE(stack, msg, element_str) |
| +#endif |
| + |
| +/** |
| + * STACK_PUSH: |
| + * @stack: Address of stack, |
| + * @elem: element of type NihListEntry to add to stack. |
| + * |
| + * Add @elem to @stack and display message showing how stack changed. |
| + **/ |
| +#define STACK_PUSH(stack, elem) \ |
| + nih_list_add_after (stack, &(elem)->entry); \ |
| + \ |
| + STACK_SHOW_PUSH (stack, \ |
| + ((NihListEntry *)(elem))->str); \ |
| + \ |
| + nih_assert (! STACK_EMPTY (stack)) |
| + |
| +/** |
| + * STACK_PUSH_NEW_ELEM: |
| + * @stack: Address of stack, |
| + * @string: String to convert into a new NihListEntry |
| + * stack element and push onto @stack. |
| + * |
| + * Create new stack element from @string and push onto @stack, |
| + * displaying a message showing how stack changed. |
| + **/ |
| +#define STACK_PUSH_NEW_ELEM(stack, string) \ |
| +{ \ |
| + NihListEntry *e; \ |
| + \ |
| + nih_assert (stack); \ |
| + nih_assert (string); \ |
| + \ |
| + e = NIH_MUST (nih_new (stack, NihListEntry)); \ |
| + nih_list_init (&e->entry); \ |
| + e->str = NIH_MUST (nih_strdup (e, string)); \ |
| + STACK_PUSH (stack, e); \ |
| +} |
| + |
| +/** |
| + * STACK_POP: |
| + * @stack: Address of stack, |
| + * @list: list which top stack element will be added to. |
| + * |
| + * Remove top element from @stack, returning to caller as @list |
| + * and display message showing how stack changed. |
| + * |
| + * Note that @list is assumed to have had nih_list_new() called |
| + * on it already. |
| + **/ |
| +#define STACK_POP(stack, list) \ |
| + nih_assert (stack); \ |
| + nih_assert ((stack)->next); \ |
| + \ |
| + list = nih_list_add (list, (stack)->next); \ |
| + STACK_SHOW_POP (stack, \ |
| + ((NihListEntry *)(list))->str) |
| + |
| +/** |
| + * STACK_PEEK: |
| + * @stack: Address of stack. |
| + * |
| + * Return string value of top element on stack. |
| + **/ |
| +#define STACK_PEEK(stack) \ |
| + (((NihListEntry *)(stack)->next)->str) |
| + |
| +/** |
| + * ConditionHandlerData: |
| + * |
| + * @condition_name: "start on" or "stop on", |
| + * @job_class_name: name of *.conf file less the extension. |
| + * |
| + * Used to pass multiple values to job_class_get_start_on() / |
| + * job_class_get_stop_on() handlers. |
| + * |
| + **/ |
| +typedef struct condition_handler_data { |
| + const char *condition_name; |
| + const char *job_class_name; |
| +} ConditionHandlerData; |
| +#endif /* INITCTL_H */ |
| |
| === modified file 'util/man/initctl.8' |
| --- util/man/initctl.8 2011-06-01 09:42:05 +0000 |
| +++ util/man/initctl.8 2011-06-01 11:13:42 +0000 |
| @@ -345,6 +345,81 @@ |
| .BR init (8) |
| daemon will log and ouputs to standard output. |
| .\" |
| +.TP |
| +.B show-config |
| +.RI [ OPTIONS "] [" CONF "]" |
| + |
| +Display emits, start on and stop on job configuration details (in that |
| +order) for specified job configuration, \fICONF\fP. If \fICONF\fP is not |
| +specified, list information for all valid job configurations. |
| + |
| +Note that a job configuration is the name of a job configuration file, |
| +without the extension. Note too that this information is static: it |
| +does not refer to any running job. |
| + |
| +For each event emitted, a separate line is displayed beginning with two |
| +space characters followed by, \'emits \fIevent\fP\' where \'\fIevent\fP\' |
| +denotes a single emitted event. |
| + |
| +The \fBstart on\fP and \fBstop on\fP conditions |
| +are listed on separate lines beginning with two space characters and |
| +followed by \'start on\' and \'stop on\' respectively and ending with |
| +the appropriate condition. |
| + |
| +If a job configuration has no emits, start on, or stop on conditions, |
| +the name of the job configuration will be displayed with no further |
| +details. |
| + |
| +Note that the \fBstart on\fP and \fBstop on\fP conditions will be fully |
| +bracketed, regardless of whether they appear like this in the job |
| +configuration file. This is useful to see how the |
| +.BR init (8) |
| +daemon perceives the condition. |
| + |
| +Example output: |
| + |
| +.nf |
| +foo |
| + emits boing |
| + emits blip |
| + start on (starting A and (B or C var=2)) |
| + stop on (bar HELLO=world testing=123 or stopping wibble) |
| +.fi |
| + |
| +.B OPTIONS |
| +.RS |
| +.IP "\fB\-e\fP, \fB\-\-enumerate\fP" |
| + |
| +If specified, rather than listing the precise \fBstart on\fP and \fBstop |
| +on\fP conditions, outputs the emits lines along with one line for each |
| +event or job the \fICONF\fP in question \fImay\fP be started or stopped |
| +by if it were to become a job. If the start on condition specifies a |
| +non-job event, this will be listed verbatim, whereas for a job event, |
| +the name of the \fIjob\fP as opposed to the event the job emits will be |
| +listed. |
| + |
| +The type of entity, its triggering event (if appropriate) and its full |
| +environment is displayed in brackets following its name for clarity. |
| + |
| +This option is useful for tools which generate graphs of relationships |
| +between jobs and events. It is also instructive since it shows how the |
| +.BR init (8) |
| +daemon has parsed the job configuration file. |
| + |
| +Example output (an analog of the default output format above): |
| + |
| +.nf |
| +foo |
| + emits boing |
| + emits blip |
| + start on starting (job: A, env:) |
| + start on B (job:, env:) |
| + start on C (job:, env: var=2) |
| + stop on bar (job:, env: HELLO=world testing=123) |
| + stop on stopping (job: wibble, event: stopping, env:) |
| +.fi |
| +.RE |
| +.\" |
| .SH AUTHOR |
| Written by Scott James Remnant |
| .RB < scott@netsplit.com > |
| |
| === modified file 'util/tests/test_initctl.c' |
| --- util/tests/test_initctl.c 2011-06-01 09:42:05 +0000 |
| +++ util/tests/test_initctl.c 2011-06-01 11:13:42 +0000 |
| @@ -27,6 +27,8 @@ |
| #include <stdio.h> |
| #include <signal.h> |
| #include <unistd.h> |
| +#include <sys/types.h> |
| +#include <sys/stat.h> |
| |
| #include <nih-dbus/dbus_error.h> |
| #include <nih-dbus/dbus_connection.h> |
| @@ -40,9 +42,189 @@ |
| #include <nih/main.h> |
| #include <nih/command.h> |
| #include <nih/error.h> |
| +#include <nih/string.h> |
| |
| #include "dbus/upstart.h" |
| |
| +/* remember we run from the 'util' directory */ |
| +#define UPSTART_BINARY "../init/init" |
| +#define INITCTL_BINARY "./initctl --session" |
| + |
| +#define BUFFER_SIZE 1024 |
| + |
| +/** |
| + * START_UPSTART: |
| + * |
| + * @pid: pid_t that will contain pid of running instance on success. |
| + * |
| + * Start an instance of Upstart. Fork errors are fatal, but after |
| + * a successful fork, waits for up to a somewhat arbitrary (but |
| + * more than adequate!) amount of time for Upstart to initialize. |
| + **/ |
| +#define START_UPSTART(pid) \ |
| +{ \ |
| + nih_local NihDBusProxy *upstart = NULL; \ |
| + DBusConnection *connection; \ |
| + DBusError dbus_error; \ |
| + \ |
| + /* XXX: arbitrary value */ \ |
| + int attempts = 10; \ |
| + \ |
| + \ |
| + TEST_NE (pid = fork (), -1); \ |
| + \ |
| + if (pid == 0) \ |
| + execlp (UPSTART_BINARY, UPSTART_BINARY, \ |
| + "--session", \ |
| + "--no-startup-event", NULL); \ |
| + \ |
| + while (attempts) { \ |
| + attempts--; \ |
| + sleep (1); \ |
| + dbus_error_init (&dbus_error); \ |
| + connection = dbus_bus_get (DBUS_BUS_SESSION, \ |
| + &dbus_error); \ |
| + \ |
| + if (! connection) { \ |
| + dbus_error_free (&dbus_error); \ |
| + continue; \ |
| + } \ |
| + dbus_error_free (&dbus_error); \ |
| + \ |
| + upstart = nih_dbus_proxy_new (NULL, connection, \ |
| + DBUS_SERVICE_UPSTART, \ |
| + DBUS_PATH_UPSTART, \ |
| + NULL, NULL); \ |
| + \ |
| + if (! upstart) { \ |
| + NihError *err; \ |
| + err = nih_error_get (); \ |
| + nih_free (err); \ |
| + dbus_connection_unref (connection); \ |
| + } \ |
| + else { \ |
| + break; \ |
| + } \ |
| + } \ |
| +} |
| + |
| +/** |
| + * STOP_UPSTART: |
| + * |
| + * @pid: pid of upstart to kill. |
| + * |
| + * Stop upstart process @pid. |
| + **/ |
| +#define STOP_UPSTART(pid) \ |
| +{ \ |
| + assert (pid); \ |
| + \ |
| + if (kill (pid, 0) == 0) { \ |
| + kill (pid, SIGTERM); \ |
| + sleep (1); \ |
| + } \ |
| + \ |
| + if (kill (pid, 0) == 0) { \ |
| + kill (pid, SIGKILL); \ |
| + } \ |
| +} |
| + |
| +/** |
| + * RUN_COMMAND: |
| + * |
| + * @parent: pointer to parent object, |
| + * @cmd: string representing command to run, |
| + * @result: "char ***" pointer which will contain an array of string |
| + * values corresponding to lines of standard output generated by @cmd, |
| + * @len: size_t pointer which will be set to length of @result. |
| + * |
| + * Run a command and return its standard output. It is the callers |
| + * responsibility to free @result. Errors from running @cmd are fatal. |
| + **/ |
| +#define RUN_COMMAND(parent, cmd, result, len) \ |
| +{ \ |
| + FILE *f; \ |
| + char buffer[BUFFER_SIZE]; \ |
| + char **ret; \ |
| + \ |
| + assert (cmd[0]); \ |
| + \ |
| + *(result) = nih_str_array_new (parent); \ |
| + TEST_NE_P (*result, NULL); \ |
| + *(len) = 0; \ |
| + \ |
| + f = popen (cmd, "r"); \ |
| + TEST_NE_P (f, NULL); \ |
| + \ |
| + while (fgets (buffer, BUFFER_SIZE, f)) { \ |
| + size_t l = strlen (buffer)-1; \ |
| + \ |
| + if ( buffer[l] == '\n') \ |
| + buffer[l] = '\0'; \ |
| + ret = nih_str_array_add (result, parent, len, \ |
| + buffer); \ |
| + TEST_NE_P (ret, NULL); \ |
| + } \ |
| + \ |
| + TEST_NE ( pclose (f), -1); \ |
| +} |
| + |
| +/** |
| + * CREATE_FILE: |
| + * |
| + * @dirname: directory name (assumed to already exist), |
| + * @name: name of file to create (no leading slash), |
| + * @contents: string contents of @name. |
| + * |
| + * Create a file in the specified directory with the specified |
| + * contents. |
| + * |
| + * Notes: A newline character is added in the case where @contents does |
| + * not end with one. |
| + **/ |
| +#define CREATE_FILE(dirname, name, contents) \ |
| +{ \ |
| + FILE *f; \ |
| + char filename[PATH_MAX]; \ |
| + \ |
| + assert (dirname[0]); \ |
| + assert (name[0]); \ |
| + \ |
| + strcpy (filename, dirname); \ |
| + if ( name[0] != '/' ) \ |
| + strcat (filename, "/"); \ |
| + strcat (filename, name); \ |
| + f = fopen (filename, "w"); \ |
| + TEST_NE_P (f, NULL); \ |
| + fprintf (f, contents); \ |
| + if ( contents[strlen(contents)-1] != '\n') \ |
| + fprintf (f, "\n"); \ |
| + fclose (f); \ |
| +} |
| + |
| +/** |
| + * DELETE_FILE: |
| + * |
| + * @dirname: directory in which file to delete exists, |
| + * @name: name of file in @dirname to delete. |
| + * |
| + * Delete specified file. |
| + * |
| + **/ |
| +#define DELETE_FILE(dirname, name) \ |
| +{ \ |
| + char filename[PATH_MAX]; \ |
| + \ |
| + assert (dirname[0]); \ |
| + assert (name[0]); \ |
| + \ |
| + strcpy (filename, dirname); \ |
| + if ( name[0] != '/' ) \ |
| + strcat (filename, "/"); \ |
| + strcat (filename, name); \ |
| + \ |
| + TEST_EQ (unlink (filename), 0); \ |
| +} |
| |
| extern int use_dbus; |
| extern int dbus_bus_type; |
| @@ -10739,6 +10921,466 @@ |
| dbus_shutdown (); |
| } |
| |
| +void |
| +test_show_config (void) |
| +{ |
| + char dirname[PATH_MAX]; |
| + nih_local char *cmd; |
| + pid_t upstart_pid; |
| + char **output; |
| + size_t lines; |
| + char expected_output[] = "foo"; |
| + |
| + TEST_GROUP ("show_config"); |
| + |
| + TEST_FILENAME (dirname); |
| + TEST_EQ (mkdir (dirname, 0755), 0); |
| + |
| + /* Use the "secret" interface */ |
| + TEST_EQ (setenv ("UPSTART_CONFDIR", dirname, 1), 0); |
| + |
| + START_UPSTART (upstart_pid); |
| + |
| + TEST_FEATURE ("no emits, no start on, no stop on"); |
| + CREATE_FILE (dirname, "foo.conf", |
| + "author \"foo\"\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ (lines, 1); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ (lines, 1); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("1 emits, no start on, no stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "author \"foo\"\n" |
| + "emits \"thing\"\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits thing"); |
| + TEST_EQ (lines, 2); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits thing"); |
| + TEST_EQ (lines, 2); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("2 emits, no start on, no stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "author \"foo\"\n" |
| + "emits \"thing\"\n" |
| + "emits \"thong\"\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits thing"); |
| + TEST_EQ_STR (output[2], " emits thong"); |
| + TEST_EQ (lines, 3); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits thing"); |
| + TEST_EQ_STR (output[2], " emits thong"); |
| + TEST_EQ (lines, 3); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("no emits, start on, no stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "author \"foo\"\n" |
| + "start on (A and B)\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " start on (A and B)"); |
| + TEST_EQ (lines, 2); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " start on A (job:, env:)"); |
| + TEST_EQ_STR (output[2], " start on B (job:, env:)"); |
| + TEST_EQ (lines, 3); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("1 emits, start on, no stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "author \"foo\"\n" |
| + "emits \"bong\"\n" |
| + "start on (A and B)\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " start on (A and B)"); |
| + TEST_EQ (lines, 3); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " start on A (job:, env:)"); |
| + TEST_EQ_STR (output[3], " start on B (job:, env:)"); |
| + TEST_EQ (lines, 4); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("2 emits, start on, no stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "emits \"bong\"\n" |
| + "author \"foo\"\n" |
| + "start on (A and B)\n" |
| + "emits \"stime\"\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " start on (A and B)"); |
| + TEST_EQ (lines, 4); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " start on A (job:, env:)"); |
| + TEST_EQ_STR (output[4], " start on B (job:, env:)"); |
| + TEST_EQ (lines, 5); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("no emits, no start on, stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "author \"foo\"\n" |
| + "stop on (A or B)\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " stop on (A or B)"); |
| + TEST_EQ (lines, 2); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " stop on A (job:, env:)"); |
| + TEST_EQ_STR (output[2], " stop on B (job:, env:)"); |
| + TEST_EQ (lines, 3); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("1 emits, no start on, stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "author \"foo\"\n" |
| + "emits \"bong\"\n" |
| + "stop on (A or B)\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " stop on (A or B)"); |
| + TEST_EQ (lines, 3); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " stop on A (job:, env:)"); |
| + TEST_EQ_STR (output[3], " stop on B (job:, env:)"); |
| + TEST_EQ (lines, 4); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("2 emits, no start on, stop on"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "emits \"bong\"\n" |
| + "author \"foo\"\n" |
| + "stop on (A or B)\n" |
| + "emits \"stime\"\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " stop on (A or B)"); |
| + TEST_EQ (lines, 4); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " stop on A (job:, env:)"); |
| + TEST_EQ_STR (output[4], " stop on B (job:, env:)"); |
| + TEST_EQ (lines, 5); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("2 emits, start on with only initial JOB, stop on with JOB at end of env"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "emits \"bong\"\n" |
| + "author \"foo\"\n" |
| + "stop on (A or stopping c=d e=f g=h JOB=\"bang\")\n" |
| + "emits \"stime\"\n" |
| + "start on (starting JOB=\"boo\" or B x=y)\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " start on (starting JOB=boo or B x=y)"); |
| + TEST_EQ_STR (output[4], " stop on (A or stopping c=d e=f g=h JOB=bang)"); |
| + TEST_EQ (lines, 5); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " start on starting (job: boo, env:)"); |
| + TEST_EQ_STR (output[4], " start on B (job:, env: x=y)"); |
| + TEST_EQ_STR (output[5], " stop on A (job:, env:)"); |
| + TEST_EQ_STR (output[6], " stop on stopping (job: bang, env: c=d e=f g=h)"); |
| + TEST_EQ (lines, 7); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("2 emits, start on with initial JOB+env, stop on with JOB at end of env"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "emits \"bong\"\n" |
| + "author \"foo\"\n" |
| + "stop on (A or stopping c=d e=f g=h JOB=\"bang\")\n" |
| + "emits \"stime\"\n" |
| + "start on (starting JOB=\"boo\" P=Q c=sea or B x=y)\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " start on (starting JOB=boo P=Q c=sea or B x=y)"); |
| + TEST_EQ_STR (output[4], " stop on (A or stopping c=d e=f g=h JOB=bang)"); |
| + TEST_EQ (lines, 5); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits stime"); |
| + TEST_EQ_STR (output[3], " start on starting (job: boo, env: P=Q c=sea)"); |
| + TEST_EQ_STR (output[4], " start on B (job:, env: x=y)"); |
| + TEST_EQ_STR (output[5], " stop on A (job:, env:)"); |
| + TEST_EQ_STR (output[6], " stop on stopping (job: bang, env: c=d e=f g=h)"); |
| + TEST_EQ (lines, 7); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("3 emits, start on (with env), stop on (with env)"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "emits \"bong\"\n" |
| + "stop on starting D and (stopping E or F hello=world)\n" |
| + "author \"foo\"\n" |
| + "emits \"bar\"\n" |
| + "emits \"stime\"\n" |
| + "start on A and (B FOO=BAR or starting C x=y)\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits bar"); |
| + TEST_EQ_STR (output[3], " emits stime"); |
| + /* note the extra brackets! */ |
| + TEST_EQ_STR (output[4], " start on (A and (B FOO=BAR or starting C x=y))"); |
| + /* note the extra brackets! */ |
| + TEST_EQ_STR (output[5], " stop on (starting D and (stopping E or F hello=world))"); |
| + TEST_EQ (lines, 6); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits bar"); |
| + TEST_EQ_STR (output[3], " emits stime"); |
| + TEST_EQ_STR (output[4], " start on A (job:, env:)"); |
| + TEST_EQ_STR (output[5], " start on B (job:, env: FOO=BAR)"); |
| + TEST_EQ_STR (output[6], " start on starting (job: C, env: x=y)"); |
| + TEST_EQ_STR (output[7], " stop on starting (job: D, env:)"); |
| + TEST_EQ_STR (output[8], " stop on stopping (job: E, env:)"); |
| + TEST_EQ_STR (output[9], " stop on F (job:, env: hello=world)"); |
| + TEST_EQ (lines, 10); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + TEST_FEATURE ("3 emits, complex start on (with env), complex stop on (with env)"); |
| + |
| + CREATE_FILE (dirname, "foo.conf", |
| + "emits \"bong\"\n" |
| + "stop on runlevel [!2345] colour=blue or starting rocket\n" |
| + "author \"foo\"\n" |
| + "emits \"bar\"\n" |
| + "emits \"stime\"\n" |
| + "start on (starting mountall or (runlevel [016] and " |
| + "(stopped gdm or stopped kdm or stopped xdm A=B or stopping lxdm)))\n" |
| + "description \"wibble\""); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits bar"); |
| + TEST_EQ_STR (output[3], " emits stime"); |
| + /* note the extra brackets! */ |
| + TEST_EQ_STR (output[4], " start on (starting mountall or (runlevel [016] and " |
| + "(((stopped gdm or stopped kdm) or stopped xdm A=B) or stopping lxdm)))"); |
| + /* note the extra brackets! */ |
| + TEST_EQ_STR (output[5], " stop on (runlevel [!2345] colour=blue or starting rocket)"); |
| + TEST_EQ (lines, 6); |
| + nih_free (output); |
| + |
| + cmd = nih_sprintf (NULL, "%s show-config -e foo 2>&1", INITCTL_BINARY); |
| + TEST_NE_P (cmd, NULL); |
| + RUN_COMMAND (NULL, cmd, &output, &lines); |
| + TEST_EQ_STR (output[0], expected_output); |
| + TEST_EQ_STR (output[1], " emits bong"); |
| + TEST_EQ_STR (output[2], " emits bar"); |
| + TEST_EQ_STR (output[3], " emits stime"); |
| + TEST_EQ_STR (output[4], " start on starting (job: mountall, env:)"); |
| + TEST_EQ_STR (output[5], " start on runlevel (job:, env: [016])"); |
| + TEST_EQ_STR (output[6], " start on stopped (job: gdm, env:)"); |
| + TEST_EQ_STR (output[7], " start on stopped (job: kdm, env:)"); |
| + TEST_EQ_STR (output[8], " start on stopped (job: xdm, env: A=B)"); |
| + TEST_EQ_STR (output[9], " start on stopping (job: lxdm, env:)"); |
| + TEST_EQ_STR (output[10], " stop on runlevel (job:, env: [!2345] colour=blue)"); |
| + TEST_EQ_STR (output[11], " stop on starting (job: rocket, env:)"); |
| + TEST_EQ (lines, 12); |
| + nih_free (output); |
| + |
| + DELETE_FILE (dirname, "foo.conf"); |
| + |
| + /*******************************************************************/ |
| + |
| + STOP_UPSTART (upstart_pid); |
| + TEST_EQ (unsetenv ("UPSTART_CONFDIR"), 0); |
| +} |
| + |
| |
| void |
| test_list_action (void) |
| @@ -13292,6 +13934,52 @@ |
| } |
| |
| |
| +/** |
| + * in_chroot: |
| + * |
| + * Determine if running inside a chroot environment. |
| + * |
| + * Failures are fatal. |
| + * |
| + * Returns TRUE if within a chroot, else FALSE. |
| + **/ |
| +int |
| +in_chroot (void) |
| +{ |
| + struct stat st; |
| + int i; |
| + char dir[] = "/"; |
| + |
| + i = stat(dir, &st); |
| + |
| + if ( i != 0 ) { |
| + fprintf (stderr, "ERROR: cannot stat '%s'\n", dir); |
| + exit (EXIT_FAILURE); |
| + } |
| + |
| + if ( st.st_ino == 2 ) |
| + return FALSE; |
| + |
| + return TRUE; |
| +} |
| + |
| +/** |
| + * dbus_configured |
| + * |
| + * Determine if D-Bus has been configured (with dbus-uuidgen). |
| + * |
| + * Returns TRUE if D-Bus appears to have been configured, |
| + * else FALSE. |
| + **/ |
| +int |
| +dbus_configured (void) |
| +{ |
| + struct stat st; |
| + char path[] = "/var/lib/dbus/machine-id"; |
| + |
| + return !stat (path, &st); |
| +} |
| + |
| int |
| main (int argc, |
| char *argv[]) |
| @@ -13317,5 +14005,15 @@ |
| test_version_action (); |
| test_log_priority_action (); |
| |
| + if (in_chroot () && !dbus_configured ()) { |
| + fprintf(stderr, "\n\n" |
| + "WARNING: not running show-config " |
| + "and check-config tests within chroot " |
| + "as no D-Bus, or D-Bus not configured (lp:#728988)" |
| + "\n\n"); |
| + } else { |
| + test_show_config (); |
| + } |
| + |
| return 0; |
| } |
| |