blob: fbca39fa5bbf587733b8a0381dcbfbfa581dd20c [file] [log] [blame]
/* upstart
*
* sysv.c - System V compatibility
*
* Copyright © 2009 Canonical Ltd.
* Author: Scott James Remnant <scott@netsplit.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <dbus/dbus.h>
#include <nih-dbus/dbus_error.h>
#include <nih-dbus/dbus_connection.h>
#include <nih-dbus/dbus_proxy.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/string.h>
#include <nih/logging.h>
#include <nih/error.h>
#include "dbus/upstart.h"
#include "utmp.h"
#include "sysv.h"
#include "com.ubuntu.Upstart.h"
/**
* RUNLEVEL_EVENT:
*
* Name of the event we emit on a runlevel change.
**/
#define RUNLEVEL_EVENT "runlevel"
/* Prototypes for static functions */
static void error_handler (NihError **err, NihDBusMessage *message);
/**
* dest_address:
*
* Address for private D-Bus connection.
**/
const char *dest_address = DBUS_ADDRESS_UPSTART;
/**
* sysv_change_runlevel:
* @runlevel: new runlevel,
* @extra_env: NULL-terminated array of additional environment.
*
* Returns: zero on success, negative value on raised error.
**/
int
sysv_change_runlevel (int runlevel,
char * const *extra_env,
const char * utmp_file,
const char * wtmp_file)
{
int prevlevel;
DBusError dbus_error;
DBusConnection * connection;
nih_local NihDBusProxy *upstart = NULL;
nih_local char ** env = NULL;
char * e;
DBusPendingCall * pending_call;
NihError * err;
nih_assert (runlevel > 0);
/* Get the previous runlevel from the environment or utmp */
prevlevel = utmp_get_runlevel (utmp_file, NULL);
if (prevlevel < 0) {
nih_free (nih_error_get ());
prevlevel = 'N';
}
/* Connect to Upstart via the private socket, establish a proxy and
* drop the initial connection reference since the proxy will hold
* one.
*/
dbus_error_init (&dbus_error);
connection = dbus_connection_open (dest_address, &dbus_error);
if (! connection) {
nih_dbus_error_raise (dbus_error.name, dbus_error.message);
dbus_error_free (&dbus_error);
return -1;
}
dbus_error_free (&dbus_error);
upstart = nih_dbus_proxy_new (NULL, connection,
NULL, DBUS_PATH_UPSTART,
NULL, NULL);
if (! upstart) {
dbus_connection_unref (connection);
return -1;
}
upstart->auto_start = FALSE;
dbus_connection_unref (connection);
/* Construct the environment to the event, which must include the
* new runlevel and previous runlevel as the first two arguments
* followed by any additional environment.
*/
env = nih_str_array_new (NULL);
if (! env)
nih_return_no_memory_error (-1);
e = nih_sprintf (NULL, "RUNLEVEL=%c", runlevel);
if (! e)
nih_return_no_memory_error (-1);
if (! nih_str_array_addp (&env, NULL, NULL, e)) {
nih_error_raise_no_memory ();
nih_free (e);
return -1;
}
e = nih_sprintf (NULL, "PREVLEVEL=%c", prevlevel);
if (! e)
nih_return_no_memory_error (-1);
if (! nih_str_array_addp (&env, NULL, NULL, e)) {
nih_error_raise_no_memory ();
nih_free (e);
return -1;
}
if (extra_env) {
if (! nih_str_array_append (&env, NULL, NULL, extra_env))
nih_return_no_memory_error (-1);
}
/* Write out the new runlevel record to utmp and wtmp, do this
* before calling EmitEvent so that the records are correct.
*/
if (utmp_write_runlevel (utmp_file, wtmp_file,
runlevel, prevlevel) < 0)
nih_free (nih_error_get ());
/* Make the EmitEvent call, we don't wait for the event to finish
* because sysvinit never did.
*/
err = NULL;
pending_call = NIH_SHOULD (upstart_emit_event (
upstart, "runlevel", env, FALSE,
NULL,
(NihDBusErrorHandler)error_handler,
&err,
NIH_DBUS_TIMEOUT_NEVER));
if (! pending_call)
return -1;
dbus_pending_call_block (pending_call);
dbus_pending_call_unref (pending_call);
if (err) {
nih_error_raise_error (err);
return -1;
}
return 0;
}
/**
* error_handler:
* @err: pointer to store error into,
* @message: D-Bus message received.
*
* This function is called in the event of an error from a D-Bus method
* call, it stashes the raised error in @err.
**/
static void
error_handler (NihError ** err,
NihDBusMessage *message)
{
*err = nih_error_steal ();
}