/* Child watcher */
static void
child_watch_cb (GPid pid, gint status, PasswordDialog *pdialog)
{
	if (WIFEXITED (status)) {
		if (WEXITSTATUS (status) >= 255) {
			g_warning (_("Child exited unexpectedly"));
		}
	}

	free_passwd_resources (pdialog);
}
/* Child watcher */
static void
child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler)
{
        if (WIFEXITED (status)) {
                if (WEXITSTATUS (status) >= 255) {
                        g_warning ("Child exited unexpectedly");
                }
                if (WEXITSTATUS (status) == 0) {
                        if (passwd_handler->backend_state == PASSWD_STATE_RETYPE) {
                                passwd_handler->backend_state = PASSWD_STATE_DONE;
                                if (passwd_handler->chpasswd_cb)
                                                passwd_handler->chpasswd_cb (passwd_handler,
                                                                             NULL,
                                                                             passwd_handler->chpasswd_cb_data);
                        }
                }
        }

        free_passwd_resources (passwd_handler);
}
/* Stop passwd backend */
static void
stop_passwd (PasswordDialog *pdialog)
{
	/* This is the standard way of returning from the dialog with passwd.
	 * If we return this way we can safely kill passwd as it has completed
	 * its task.
	 */

	if (pdialog->backend_pid != -1) {
		kill (pdialog->backend_pid, 9);
	}

	/* We must run free_passwd_resources here and not let our child
	 * watcher do it, since it will access invalid memory after the
	 * dialog has been closed and cleaned up.
	 *
	 * If we had more than a single thread we'd need to remove
	 * the child watch before trying to kill the child.
	 */
	free_passwd_resources (pdialog);
}
/* Spawn passwd backend
 * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
static gboolean
spawn_passwd (PasswordDialog *pdialog, GError **error)
{
	gchar	*argv[2];
	gchar	*envp[1];
	gint	my_stdin, my_stdout, my_stderr;

	argv[0] = "/usr/bin/passwd";	/* Is it safe to rely on a hard-coded path? */
	argv[1] = NULL;

	envp[0] = NULL;					/* If we pass an empty array as the environment,
									 * will the childs environment be empty, and the
									 * locales set to the C default? From the manual:
									 * "If envp is NULL, the child inherits its
									 * parent'senvironment."
									 * If I'm wrong here, we somehow have to set
									 * the locales here.
									 */

	if (!g_spawn_async_with_pipes (NULL,						/* Working directory */
								   argv,						/* Argument vector */
								   envp,						/* Environment */
								   G_SPAWN_DO_NOT_REAP_CHILD,	/* Flags */
								   NULL,						/* Child setup */
								   NULL,						/* Data to child setup */
								   &pdialog->backend_pid,		/* PID */
								   &my_stdin,						/* Stdin */
								   &my_stdout,						/* Stdout */
								   &my_stderr,						/* Stderr */
								   error)) {					/* GError */

		/* An error occured */
		free_passwd_resources (pdialog);

		return FALSE;
	}

	/* 2>&1 */
	if (dup2 (my_stderr, my_stdout) == -1) {
		/* Failed! */
		g_set_error (error,
					 PASSDLG_ERROR,
					 PASSDLG_ERROR_BACKEND,
					 strerror (errno));

		/* Clean up */
		stop_passwd (pdialog);

		return FALSE;
	}

	/* Open IO Channels */
	pdialog->backend_stdin = g_io_channel_unix_new (my_stdin);
	pdialog->backend_stdout = g_io_channel_unix_new (my_stdout);

	/* Set raw encoding */
	/* Set nonblocking mode */
	if (g_io_channel_set_encoding (pdialog->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
		g_io_channel_set_encoding (pdialog->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
		g_io_channel_set_flags (pdialog->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
		g_io_channel_set_flags (pdialog->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {

		/* Clean up */
		stop_passwd (pdialog);
		return FALSE;
	}

	/* Turn off buffering */
	g_io_channel_set_buffered (pdialog->backend_stdin, FALSE);
	g_io_channel_set_buffered (pdialog->backend_stdout, FALSE);

	/* Add IO Channel watcher */
	pdialog->backend_stdout_watch_id = g_io_add_watch (pdialog->backend_stdout,
													   G_IO_IN | G_IO_PRI,
													   (GIOFunc) io_watch_stdout, pdialog);

	/* Add child watcher */
	pdialog->backend_child_watch_id = g_child_watch_add (pdialog->backend_pid, (GChildWatchFunc) child_watch_cb, pdialog);

	/* Success! */

	return TRUE;
}
/* Spawn passwd backend
 * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
static gboolean
spawn_passwd (PasswdHandler *passwd_handler, GError **error)
{
        gchar   *argv[2];
        gchar  **envp;
        gint    my_stdin, my_stdout, my_stderr;

        argv[0] = "/usr/bin/passwd";    /* Is it safe to rely on a hard-coded path? */
        argv[1] = NULL;

        envp = g_get_environ ();
        envp = g_environ_setenv (envp, "LC_ALL", "C", TRUE);

        if (!g_spawn_async_with_pipes (NULL,                            /* Working directory */
                                       argv,                            /* Argument vector */
                                       envp,                            /* Environment */
                                       G_SPAWN_DO_NOT_REAP_CHILD,       /* Flags */
                                       ignore_sigpipe,                  /* Child setup */
                                       NULL,                            /* Data to child setup */
                                       &passwd_handler->backend_pid,    /* PID */
                                       &my_stdin,                       /* Stdin */
                                       &my_stdout,                      /* Stdout */
                                       &my_stderr,                      /* Stderr */
                                       error)) {                        /* GError */

                /* An error occured */
                free_passwd_resources (passwd_handler);

                g_strfreev (envp);

                return FALSE;
        }

        g_strfreev (envp);

        /* 2>&1 */
        if (dup2 (my_stderr, my_stdout) == -1) {
                /* Failed! */
                g_set_error_literal (error,
                                     PASSWD_ERROR,
                                     PASSWD_ERROR_BACKEND,
                                     strerror (errno));

                /* Clean up */
                stop_passwd (passwd_handler);

                return FALSE;
        }

        /* Open IO Channels */
        passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin);
        passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout);

        /* Set raw encoding */
        /* Set nonblocking mode */
        if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
                g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
                g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
                g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {

                /* Clean up */
                stop_passwd (passwd_handler);
                return FALSE;
        }

        /* Turn off buffering */
        g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE);
        g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE);

        /* Add IO Channel watcher */
        passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout,
                                                                  G_IO_IN | G_IO_PRI,
                                                                  (GIOFunc) io_watch_stdout, passwd_handler);

        /* Add child watcher */
        passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler);

        /* Success! */

        return TRUE;
}