예제 #1
0
/**
 * check_for_overlayfs:
 *
 * Determine if the mount point used by the tests for creating temporary
 * files is using overlayfs.
 *
 * Returns: TRUE if temporary work area is on overlayfs, else FALSE.
 **/
int
check_for_overlayfs (void)
{
	struct statfs  statbuf;
	char           path[PATH_MAX];
	int            found = FALSE;

	/* Create a file in the temporary work area */
	TEST_FILENAME (path);
	fclose (fopen (path, "w"));

	/* Check it exits */
	assert0 (statfs (path, &statbuf));

	if (statbuf.f_type == OVERLAYFS_SUPER_MAGIC) {
		nih_warn ("Mountpoint for '%s' (needed by the Upstart tests) is an overlayfs "
				"filesystem, which does not support inotify.",
				path);
		found = TRUE;
	}

	assert0 (unlink (path));

	return found;
}
예제 #2
0
int check_cgroup_sandbox(void)
{
	char *cg_prev = NULL, *cg_post = NULL;
	int ret = -1;

	cg_prev = get_my_cgroup();
	if (!cg_prev)
		return -1;
	if (setup_cgroup_sandbox() < 0) {
		nih_free(cg_prev);
		return -1;
	}
	cg_post = get_my_cgroup();
	if (!cg_post) {
		nih_free(cg_prev);
		return -1;
	}
	/* we should have moved cgroups, so the two should be different */
	if (strcmp(cg_prev, cg_post) != 0) {
		nih_warn("setup_cgroup_sandbox moved me from %s to %s",
				cg_prev, cg_post);
		ret = 0;
	}
	nih_free(cg_prev);
	nih_free(cg_post);
	return ret;
}
예제 #3
0
/**
 * test_checks:
 *
 * Perform any checks necessary before real tests are run.
 **/
void
test_checks (void)
{
	int ret;

	TEST_GROUP ("test environment");

	/*
	 * Warn (*) if overlayfs detected.
	 *
	 * (*) - Don't fail in the hope that one day someone might fix
	 * overlayfs.
	 */
	TEST_FEATURE ("checking for overlayfs");
	if (check_for_overlayfs ()) {
		nih_warn ("Found overlayfs mounts");
		nih_warn ("This environment will probably cause tests to fail mysteriously!!");
		nih_warn ("See bug LP:#882147 for further details.");
	}


#ifdef ENABLE_CGROUPS
	if (file_exists ("/sys/fs/cgroup/cgmanager/sock")) {
		TEST_FEATURE ("checking for cgmanager");
		ret = connect_to_cgmanager ();
		switch (ret) {
		case -2:
			nih_warn ("Found no cgroup manager");
			goto out_skip;
		case -1:
			nih_warn ("Error connecting to cgmanager");
			goto out_skip;
		case 0:
			print_my_cgroup ();
			break;
		default: nih_warn ("Unknown error from connect_to_cgmanager: %d", ret);
			goto out_skip;
		}

		TEST_FEATURE ("cgroup sandbox");
		if (check_cgroup_sandbox() != 0)
			nih_warn ("Could not create cgroup sandbox");
	} else {
		nih_warn ("Skipping CGManager tests, CGManager socket not found");
	}
out_skip:
	disconnect_cgmanager();
	if (ret)
		nih_warn ("Skipping CGManager tests, CGManager not properly configured");
#endif /* ENABLE_CGROUPS */

}
예제 #4
0
static void
emit_event_error (void *          data,
		  NihDBusMessage *message)
{
	NihError *err;

	err = nih_error_get ();
	nih_warn ("%s", err->message);
	nih_free (err);
}
예제 #5
0
void print_my_cgroup(void)
{
	char *str;
	str = get_pid_cgroup("freezer", getpid());
	if (str) {
		nih_warn("I am in freezer cgroup: %s", str);
		TEST_EQ_STR(str, "/");
		nih_free(str);
	} else {
		TEST_FAILED("Failed to get my freezer cgroup");
	}
}
예제 #6
0
int
main (int argc, char *argv[])
{
	char **		args;
	int		ret;
	DBusServer *	server;
	struct stat sb;
	struct rlimit newrlimit;

	nih_main_init (argv[0]);

	nih_option_set_synopsis (_("Control group manager"));
	nih_option_set_help (_("The cgroup manager daemon"));

	args = nih_option_parser (NULL, argc, argv, options, FALSE);
	if (! args)
		exit (1);

	if (!setup_cgroup_dir()) {
		nih_fatal("Failed to set up cgmanager socket");
		exit(1);
	}

	/* Setup the DBus server */
	server = nih_dbus_server (CGMANAGER_DBUS_PATH, client_connect,
				  client_disconnect);
	nih_assert (server != NULL);

	if (!setup_base_run_path()) {
		nih_fatal("Error setting up base cgroup path");
		return -1;
	}

	if (collect_subsystems(extra_cgroup_mounts) < 0)
	{
		nih_fatal("failed to collect cgroup subsystems");
		exit(1);
	}

	if (!create_agent_symlinks()) {
		nih_fatal("Error creating release agent symlinks");
		exit(1);
	}

	if (setup_cgroup_mounts() < 0) {
		nih_fatal ("Failed to set up cgroup mounts");
		exit(1);
	}

	if (!move_self_to_root()) {
		nih_fatal ("Failed to move self to root cgroup");
		exit(1);
	}

	if (stat("/proc/self/ns/pid", &sb) == 0) {
		mypidns = read_pid_ns_link(getpid());
		setns_pid_supported = true;
	}

	if (stat("/proc/self/ns/user", &sb) == 0) {
		myuserns = read_user_ns_link(getpid());
		setns_user_supported = true;
	}

	newrlimit.rlim_cur = 10000;
	newrlimit.rlim_max = 10000;
	if (setrlimit(RLIMIT_NOFILE, &newrlimit) < 0)
		nih_warn("Failed to increase open file limit: %s",
			strerror(errno));

	/* Become daemon */
	if (daemonise) {
		if (nih_main_daemonise () < 0) {
			NihError *err;

			err = nih_error_get ();
			nih_fatal ("%s: %s", _("Unable to become daemon"),
					err->message);
			nih_free (err);

			exit (1);
		}
	}

	if (sigstop)
		raise(SIGSTOP);

	ret = nih_main_loop ();

	/* Destroy any PID file we may have created */
	if (daemonise) {
		nih_main_unlink_pidfile();
	}

	return ret;
}
예제 #7
0
int get_tasks_recursive_main (void *parent, const char *controller,
		const char *cgroup, struct ucred p, struct ucred r,
		int32_t **pids)
{
	DBusMessage *message;
	DBusMessageIter iter;
	int sv[2], ret = -1;
	uint32_t nrpids;
	struct ucred tcred;
	int i;

	if (memcmp(&p, &r, sizeof(struct ucred)) != 0) {
		nih_error("%s: proxy != requestor", __func__);
		return -1;
	}

	if (!sane_cgroup(cgroup)) {
		nih_error("%s: unsafe cgroup", __func__);
		return -1;
	}

	if (!(message = start_dbus_request("GetTasksRecursiveScm", sv))) {
		nih_error("%s: error starting dbus request", __func__);
		return -1;
	}

	dbus_message_iter_init_append(message, &iter);
	if (! dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &controller)) {
		nih_error("%s: out of memory", __func__);
		dbus_message_unref(message);
		goto out;
	}
	if (! dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &cgroup)) {
		nih_error("%s: out of memory", __func__);
		dbus_message_unref(message);
		goto out;
	}
	if (! dbus_message_iter_append_basic (&iter, DBUS_TYPE_UNIX_FD, &sv[1])) {
		nih_error("%s: out of memory", __func__);
		dbus_message_unref(message);
		goto out;
	}

	if (!complete_dbus_request(message, sv, &r, NULL)) {
		nih_error("%s: error completing dbus request", __func__);
		goto out;
	}
	if (proxyrecv(sv[0], &nrpids, sizeof(uint32_t)) != sizeof(uint32_t))
		goto out;
	if (nrpids == -1) {
		nih_error("%s: bad cgroup: %s:%s", __func__, controller, cgroup);
		ret = -1;
		goto out;
	}
	if (nrpids == 0) {
		ret = 0;
		goto out;
	}

	*pids = NIH_MUST( nih_alloc(parent, nrpids * sizeof(uint32_t)) );
	for (i=0; i<nrpids; i++) {
		get_scm_creds_sync(sv[0], &tcred);
		if (tcred.pid == -1) {
			nih_warn("%s: Failed getting pid from server",
				__func__);
			goto out;
		}
		(*pids)[i] = tcred.pid;
	}
	ret = nrpids;
out:
	close(sv[0]);
	close(sv[1]);
	return ret;
}
예제 #8
0
int setup_proxy(void)
{
	bool exists_upper = false, exists_lower = false;
	NihError *err;

	/* When running in container, /sys/fs/cgroup will have been
	   already mounted.  But it may be ro */
	if (is_ro_mount(CGDIR))
		turn_mount_rw(CGDIR);

	/*
	 * If /sys/fs/cgroup/cgmanager.lower exists,
	 *    if /sys/fs/cgroup/cgmanager exists, then exit (proxy already running)
	 *    start up, connect to .lower
	 * else
	 *    if /sys/fs/cgroup/cgmanager exists, move it to /sys/fs/cgroup/cgmanager.lower
	 *    start up and connect to .lower
	 */
	server_conn = nih_dbus_connect(CGMANAGER_DBUS_PATH, NULL);
	if (server_conn) {
		exists_upper = true;
		dbus_connection_unref (server_conn);
	} else {
		err = nih_error_get();
		nih_free(err);
	}
	server_conn = nih_dbus_connect(CGPROXY_DBUS_PATH, cgm_dbus_disconnected);
	if (server_conn) {
		exists_lower = true;
	} else {
		err = nih_error_get();
		nih_free(err);
	}
	if (exists_upper && exists_lower) {
		dbus_connection_unref (server_conn);
		nih_fatal("proxy already running");
		return -1;  // proxy already running
	}
	if (exists_lower)
		// we've got the sock we need, all set.
		return 0;
	if (exists_upper) {
		//move /sys/fs/cgroup/cgmanager to /sys/fs/cgroup/cgmanager.lower
		if (mkdir(CGPROXY_DIR, 0755) < 0 && errno != EEXIST) {
			nih_fatal("failed to create lower sock");
			return -1;
		}
		if (mount(CGMANAGER_DIR, CGPROXY_DIR, "none", MS_MOVE, 0) < 0) {
			/* it wasn't a mount, meaning we are at the host
			 * level on an old kernel.  So rename it */
			if (unlink(CGPROXY_SOCK) && errno != ENOENT)
				nih_warn("failed to remove %s: %s", CGPROXY_SOCK,
					strerror(errno));
			if (rmdir(CGPROXY_DIR) && errno != ENOENT)
				nih_warn("failed to remove %s: %s", CGPROXY_DIR,
					strerror(errno));
			if (rename(CGMANAGER_DIR, CGPROXY_DIR) < 0) {
				nih_fatal("unable to rename the socket");
				return -1;
			}
			if (mkdir(CGMANAGER_DIR, 0755) < 0) {
				nih_fatal("unable to create socket dir");
				return -1;
			}
		}
	}
	server_conn = nih_dbus_connect(CGPROXY_DBUS_PATH, cgm_dbus_disconnected);
	if (!server_conn) {
		err = nih_error_get();
		nih_fatal("Failed to open connection to %s: %s",
				CGPROXY_DBUS_PATH, err->message);
		nih_free(err);

		return -1;
	}
	return 0;
}
예제 #9
0
/**
 * wall:
 * @message: message to send.
 *
 * Send a message to all logged in users; based largely on the code from
 * bsdutils.  This is done in a child process to stop anything blocking.
 **/
static void
wall (const char *message)
{
	struct sigaction act;
	struct utmpx *   ent;
	pid_t            pid;
	time_t           now;
	struct tm *      tm;
	char *           user;
	char *           tty;
	char             hostname[MAXHOSTNAMELEN];
	char *           banner1;
	char *           banner2;

	pid = fork ();
	if (pid < 0) {
		nih_warn (_("Unable to fork child-process to warn users: %s"),
			  strerror (errno));
		return;
	} else if (pid > 0) {
		return;
	}

	/* Break syscalls with SIGALRM */
	act.sa_handler = alarm_handler;
	act.sa_flags = 0;
	sigemptyset (&act.sa_mask);
	sigaction (SIGALRM, &act, NULL);


	/* Get username for banner */
	user = getlogin ();
	if (! user) {
		struct passwd *pw;

		pw = getpwuid (getuid ());
		if (pw)
			user = pw->pw_name;
	}
	if (! user) {
		if (getuid ()) {
			user = NIH_MUST (nih_sprintf (NULL, "uid %d",
						      getuid ()));
		} else {
			user = "******";
		}
	}

	/* Get hostname for banner */
	gethostname (hostname, sizeof (hostname));

	/* Get terminal for banner */
	tty = ttyname (0);
	if (! tty)
		tty = "unknown";

	/* Get time */
	now = time (NULL);
	tm = localtime (&now);

	/* Construct banner */
	banner1 = nih_sprintf (NULL, _("Broadcast message from %s@%s"),
			       user, hostname);
	banner2 = nih_sprintf (NULL, _("(%s) at %d:%02d ..."),
			       tty, tm->tm_hour, tm->tm_min);


	/* Iterate entries in the utmp file */
	setutxent ();
	while ((ent = getutxent ()) != NULL) {
		char dev[PATH_MAX + 1];
		int  fd;

		/* Ignore entries without a name, or not a user process */
		if ((ent->ut_type != USER_PROCESS)
		    || (! strlen (ent->ut_user)))
			continue;

		/* Construct the device path */
		if (strncmp (ent->ut_line, DEV "/", 5)) {
			snprintf (dev, sizeof (dev),
				  "%s/%s", DEV, ent->ut_line);
		} else {
			snprintf (dev, sizeof (dev), "%s", ent->ut_line);
		}

		alarm (2);
		fd = open (dev, O_WRONLY | O_NDELAY | O_NOCTTY);
		if ((fd >= 0) && isatty (fd)) {
			FILE *term;

			term = fdopen (fd, "w");
			if (term) {
				fprintf (term, "\007\r\n%s\r\n\t%s\r\n\r\n",
					 banner1, banner2);
				fputs (message, term);
				fflush (term);
				fclose (term);
			}
		}
		alarm (0);
	}
	endutxent ();

	nih_free (banner1);
	nih_free (banner2);

	exit (0);
}
예제 #10
0
int
main (int   argc,
      char *argv[])
{
	char **         args;
	nih_local char *message = NULL;
	size_t          messagelen;
	nih_local char *msg = NULL;
	int             arg;
	pid_t           pid = 0;

	nih_main_init (argv[0]);

	nih_option_set_usage (_("TIME [MESSAGE]"));
	nih_option_set_synopsis (_("Bring the system down."));
	nih_option_set_help (
		_("TIME may have different formats, the most common is simply "
		  "the word 'now' which will bring the system down "
		  "immediately.  Other valid formats are +m, where m is the "
		  "number of minutes to wait until shutting down and hh:mm "
		  "which specifies the time on the 24hr clock.\n"
		  "\n"
		  "Logged in users are warned by a message sent to their "
		  "terminal, you may include an optional MESSAGE included "
		  "with this.  Messages can be sent without actually "
		  "bringing the system down by using the -k option.\n"
		  "\n"
		  "If TIME is given, the command will remain in the "
		  "foreground until the shutdown occurs.  It can be cancelled "
		  "by Control-C, or by another user using the -c option.\n"
		  "\n"
		  "The system is brought down into maintenance (single-user) "
		  "mode by default, you can change this with either the -r or "
		  "-h option which specify a reboot or system halt "
		  "respectively.  The -h option can be further modified with "
		  "-H or -P to specify whether to halt the system, or to "
		  "power it off afterwards.  The default is left up to the "
		  "shutdown scripts."));

	args = nih_option_parser (NULL, argc, argv, options, FALSE);
	if (! args)
		exit (1);

	/* If the runlevel wasn't given explicitly, set it to 1 so we go
	 * down into single-user mode.
	 */
	if (! runlevel) {
		runlevel = '1';
		init_halt = NULL;
	}


	/* When may be specified with -g, or must be first argument */
	if (! (cancel || when || args[0])) {
		fprintf (stderr, _("%s: time expected\n"), program_name);
		nih_main_suggest_help ();
		exit (1);
	} else if (! (cancel || when)) {
		when = NIH_MUST (nih_strdup (NULL, args[0]));
		arg = 1;
	} else {
		arg = 0;
	}

	/* Parse the time argument */
	if (when) {
		if (! strcmp (when, "now")) {
			/* "now" means, err, now */
			delay = 0;
		} else if (strchr (when, ':')) {
			/* Clock time */
			long       hours, mins;
			char      *endptr;
			struct tm *tm;
			time_t     now;

			hours = strtoul (when, &endptr, 10);
			if ((*endptr != ':') || (hours < 0) || (hours > 23)) {
				fprintf (stderr, _("%s: illegal hour value\n"),
					 program_name);
				nih_main_suggest_help ();
				exit (1);
			}

			mins = strtoul (endptr + 1, &endptr, 10);
			if (*endptr || (mins < 0) || (mins > 59)) {
				fprintf (stderr,
					 _("%s: illegal minute value\n"),
					 program_name);
				nih_main_suggest_help ();
				exit (1);
			}

			/* Subtract the current time to get the delay.
			 * Add a whole day if we go negative */
			now = time (NULL);
			tm = localtime (&now);
			delay = (((hours * 60) + mins)
				 - ((tm->tm_hour * 60) + tm->tm_min));
			if (delay < 0)
				delay += 1440;
		} else {
			/* Delay in minutes */
			char *endptr;

			delay = strtoul (when, &endptr, 10);
			if (*endptr || (delay < 0)) {
				fprintf (stderr, _("%s: illegal time value\n"),
					 program_name);
				nih_main_suggest_help ();
				exit (1);
			}
		}
		nih_free (when);
	}


	/* The rest of the arguments are a message.
	 * Really this should be just the next argument, but that's not
	 * how this has been traditionally done *sigh*
	 */
	message = NIH_MUST (nih_strdup (NULL, ""));
	messagelen = 0;
	for (; args[arg]; arg++) {
		message = NIH_MUST (nih_realloc (
				  message, NULL,
				  messagelen + strlen(args[arg]) + 4));

		strcat (message, args[arg]);
		strcat (message, " ");
		messagelen += strlen (args[arg]) + 1;
	}

	/* Terminate with \r\n */
	if (messagelen)
		strcat (message, "\r\n");


	/* Check we're root, or setuid root */
	setuid (geteuid ());
	if (getuid ()) {
		nih_fatal (_("Need to be root"));
		exit (1);
	}

	/* Look for an existing pid file and deal with the existing
	 * process if there is one.
	 */
	pid = nih_main_read_pidfile ();
	if (pid > 0) {
		if (cancel) {
			if (kill (pid, SIGINT) < 0) {
				nih_error (_("Shutdown is not running"));
				exit (1);
			}

			if (messagelen)
				wall (message);

			exit (0);
		} else if (kill (pid, 0) == 0) {
			nih_error (_("Another shutdown is already running"));
			exit (1);
		}
	} else if (cancel) {
		nih_error (_("Cannot find pid of running shutdown"));
		exit (1);
	}

	/* Send an initial message */
	msg = NIH_MUST (warning_message (message));
	wall (msg);

	if (warn_only)
		exit (0);


	/* Give us a sane environment */
	if (chdir ("/") < 0)
		nih_warn ("%s: %s", _("Unable to change directory"),
			  strerror (errno));
	umask (022);

	/* Shutdown now? */
	if (! delay)
		shutdown_now ();

	/* Save our pid so we can be interrupted later */
	if (nih_main_write_pidfile (getpid ()) < 0) {
		NihError *err;

		err = nih_error_get ();
		nih_warn ("%s: %s: %s", nih_main_get_pidfile(),
			  _("Unable to write pid file"), err->message);
		nih_free (err);
	}


	/* Ignore a whole bunch of signals */
	nih_signal_set_ignore (SIGCHLD);
	nih_signal_set_ignore (SIGHUP);
	nih_signal_set_ignore (SIGTSTP);
	nih_signal_set_ignore (SIGTTIN);
	nih_signal_set_ignore (SIGTTOU);

	/* Catch the usual quit signals */
	nih_signal_set_handler (SIGINT, nih_signal_handler);
	NIH_MUST (nih_signal_add_handler (NULL, SIGINT,
					  cancel_callback, NULL));
	nih_signal_set_handler (SIGQUIT, nih_signal_handler);
	NIH_MUST (nih_signal_add_handler (NULL, SIGQUIT,
					  cancel_callback, NULL));
	nih_signal_set_handler (SIGTERM, nih_signal_handler);
	NIH_MUST (nih_signal_add_handler (NULL, SIGTERM,
					  cancel_callback, NULL));

	/* Call a timer every minute until we shutdown */
	NIH_MUST (nih_timer_add_periodic (NULL, 60,
					  (NihTimerCb)timer_callback,
					  message));

	/* Hang around */
	nih_main_loop ();

	return 0;
}
예제 #11
0
static void
udev_monitor_watcher (struct udev_monitor *udev_monitor,
		      NihIoWatch *         watch,
		      NihIoEvents          events)
{
	struct udev_device *    udev_device;
	nih_local char *        subsystem = NULL;
	nih_local char *        action = NULL;
	nih_local char *        kernel = NULL;
	nih_local char *        devpath = NULL;
	nih_local char *        devname = NULL;
	nih_local char *        name = NULL;
	nih_local char **       env = NULL;
	const char *            value = NULL;
	size_t                  env_len = 0;
	DBusPendingCall *       pending_call;
	char                 *(*copy_string)(const void *, const char *) = NULL;


	udev_device = udev_monitor_receive_device (udev_monitor);
	if (! udev_device)
		return;

	copy_string = no_strip_udev_data ? nih_strdup : make_safe_string;

	value = udev_device_get_subsystem (udev_device);
	subsystem = value ? copy_string (NULL, value) : NULL;

	value = udev_device_get_action (udev_device);
	action = value ? copy_string (NULL, value) : NULL;

	value = udev_device_get_sysname (udev_device);
	kernel = value ? copy_string (NULL, value) : NULL;

	value = udev_device_get_devpath (udev_device);
	devpath = value ? copy_string (NULL, value) : NULL;

	value = udev_device_get_devnode (udev_device);
	devname = value ? copy_string (NULL, value) : NULL;

	/* Protect against the "impossible" */
	if (! action)
		goto out;

	if (! strcmp (action, "add")) {
		name = NIH_MUST (nih_sprintf (NULL, "%s-device-added",
					      subsystem));
	} else if (! strcmp (action, "change")) {
		name = NIH_MUST (nih_sprintf (NULL, "%s-device-changed",
					      subsystem));
	} else if (! strcmp (action, "remove")) {
		name = NIH_MUST (nih_sprintf (NULL, "%s-device-removed",
					      subsystem));
	} else {
		name = NIH_MUST (nih_sprintf (NULL, "%s-device-%s",
					      subsystem, action));
	}

	env = NIH_MUST (nih_str_array_new (NULL));

	if (kernel) {
		nih_local char *var = NULL;

		var = NIH_MUST (nih_sprintf (NULL, "KERNEL=%s", kernel));
		NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var));
	}

	if (devpath) {
		nih_local char *var = NULL;

		var = NIH_MUST (nih_sprintf (NULL, "DEVPATH=%s", devpath));
		NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var));
	}

	if (devname) {
		nih_local char *var = NULL;

		var = NIH_MUST (nih_sprintf (NULL, "DEVNAME=%s", devname));
		NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var));
	}

	if (subsystem) {
		nih_local char *var = NULL;

		var = NIH_MUST (nih_sprintf (NULL, "SUBSYSTEM=%s", subsystem));
		NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var));
	}

	if (action) {
		nih_local char *var = NULL;

		var = NIH_MUST (nih_sprintf (NULL, "ACTION=%s", action));
		NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var));
	}

	for (struct udev_list_entry *list_entry = udev_device_get_properties_list_entry (udev_device);
	     list_entry != NULL;
	     list_entry = udev_list_entry_get_next (list_entry)) {
		nih_local char *udev_name = NULL;
		nih_local char *udev_value = NULL;
		nih_local char *var = NULL;

		udev_name = copy_string (NULL, udev_list_entry_get_name (list_entry));

		if (! strcmp (udev_name, "DEVPATH"))
			continue;
		if (! strcmp (udev_name, "DEVNAME"))
			continue;
		if (! strcmp (udev_name, "SUBSYSTEM"))
			continue;
		if (! strcmp (udev_name, "ACTION"))
			continue;

		udev_value = copy_string (NULL, udev_list_entry_get_value (list_entry));

		var = NIH_MUST (nih_sprintf (NULL, "%s=%s", udev_name, udev_value));
		NIH_MUST (nih_str_array_addp (&env, NULL, &env_len, var));
	}

	nih_debug ("%s %s", name, devname ? devname : "");

	pending_call = upstart_emit_event (upstart,
			name, env, FALSE,
			NULL, emit_event_error, NULL,
			NIH_DBUS_TIMEOUT_NEVER);

	if (! pending_call) {
		NihError *err;
		int saved = errno;

		err = nih_error_get ();
		nih_warn ("%s", err->message);

		if (saved != ENOMEM && subsystem)
			nih_warn ("Likely that udev '%s' event contains binary garbage", subsystem);

		nih_free (err);
	}

	dbus_pending_call_unref (pending_call);

out:
	udev_device_unref (udev_device);
}