blob: 670dd953a466bd99f3e0ca9dad250bd79e55af8a [file] [log] [blame]
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;
}