/* ply-boot-server.c - listens for and processes boot-status events
 *
 * Copyright (C) 2007 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * Written by: Ray Strode <rstrode@redhat.com>
 */
#include "ply-boot-server.h"

#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "ply-buffer.h"
#include "ply-event-loop.h"
#include "ply-list.h"
#include "ply-logger.h"
#include "ply-trigger.h"
#include "ply-utils.h"

typedef struct
{
        int                fd;
        ply_fd_watch_t    *watch;
        ply_boot_server_t *server;
        uid_t              uid;
        pid_t              pid;

        int                reference_count;

        uint32_t           credentials_read : 1;
        uint32_t           disconnected : 1;
} ply_boot_connection_t;

struct _ply_boot_server
{
        ply_event_loop_t                             *loop;
        ply_list_t                                   *connections;
        ply_list_t                                   *cached_passwords;
        int                                           socket_fd;

        ply_boot_server_update_handler_t              update_handler;
        ply_boot_server_change_mode_handler_t         change_mode_handler;
        ply_boot_server_system_update_handler_t       system_update_handler;
        ply_boot_server_newroot_handler_t             newroot_handler;
        ply_boot_server_system_initialized_handler_t  system_initialized_handler;
        ply_boot_server_error_handler_t               error_handler;
        ply_boot_server_show_splash_handler_t         show_splash_handler;
        ply_boot_server_hide_splash_handler_t         hide_splash_handler;
        ply_boot_server_ask_for_password_handler_t    ask_for_password_handler;
        ply_boot_server_ask_question_handler_t        ask_question_handler;
        ply_boot_server_display_message_handler_t     display_message_handler;
        ply_boot_server_hide_message_handler_t        hide_message_handler;
        ply_boot_server_watch_for_keystroke_handler_t watch_for_keystroke_handler;
        ply_boot_server_ignore_keystroke_handler_t    ignore_keystroke_handler;
        ply_boot_server_progress_pause_handler_t      progress_pause_handler;
        ply_boot_server_progress_unpause_handler_t    progress_unpause_handler;
        ply_boot_server_deactivate_handler_t          deactivate_handler;
        ply_boot_server_reactivate_handler_t          reactivate_handler;
        ply_boot_server_quit_handler_t                quit_handler;
        ply_boot_server_has_active_vt_handler_t       has_active_vt_handler;
        ply_boot_server_reload_handler_t              reload_handler;
        void                                         *user_data;

        uint32_t                                      is_listening : 1;
};

ply_boot_server_t *
ply_boot_server_new (ply_boot_server_update_handler_t              update_handler,
                     ply_boot_server_change_mode_handler_t         change_mode_handler,
                     ply_boot_server_system_update_handler_t       system_update_handler,
                     ply_boot_server_ask_for_password_handler_t    ask_for_password_handler,
                     ply_boot_server_ask_question_handler_t        ask_question_handler,
                     ply_boot_server_display_message_handler_t     display_message_handler,
                     ply_boot_server_hide_message_handler_t        hide_message_handler,
                     ply_boot_server_watch_for_keystroke_handler_t watch_for_keystroke_handler,
                     ply_boot_server_ignore_keystroke_handler_t    ignore_keystroke_handler,
                     ply_boot_server_progress_pause_handler_t      progress_pause_handler,
                     ply_boot_server_progress_unpause_handler_t    progress_unpause_handler,
                     ply_boot_server_show_splash_handler_t         show_splash_handler,
                     ply_boot_server_hide_splash_handler_t         hide_splash_handler,
                     ply_boot_server_newroot_handler_t             newroot_handler,
                     ply_boot_server_system_initialized_handler_t  initialized_handler,
                     ply_boot_server_error_handler_t               error_handler,
                     ply_boot_server_deactivate_handler_t          deactivate_handler,
                     ply_boot_server_reactivate_handler_t          reactivate_handler,
                     ply_boot_server_quit_handler_t                quit_handler,
                     ply_boot_server_has_active_vt_handler_t       has_active_vt_handler,
                     ply_boot_server_reload_handler_t              reload_handler,
                     void                                         *user_data)
{
        ply_boot_server_t *server;

        server = calloc (1, sizeof(ply_boot_server_t));
        server->connections = ply_list_new ();
        server->cached_passwords = ply_list_new ();
        server->loop = NULL;
        server->is_listening = false;
        server->update_handler = update_handler;
        server->change_mode_handler = change_mode_handler;
        server->system_update_handler = system_update_handler;
        server->ask_for_password_handler = ask_for_password_handler;
        server->ask_question_handler = ask_question_handler;
        server->display_message_handler = display_message_handler;
        server->hide_message_handler = hide_message_handler;
        server->watch_for_keystroke_handler = watch_for_keystroke_handler;
        server->ignore_keystroke_handler = ignore_keystroke_handler;
        server->progress_pause_handler = progress_pause_handler;
        server->progress_unpause_handler = progress_unpause_handler;
        server->newroot_handler = newroot_handler;
        server->error_handler = error_handler;
        server->system_initialized_handler = initialized_handler;
        server->show_splash_handler = show_splash_handler;
        server->hide_splash_handler = hide_splash_handler;
        server->deactivate_handler = deactivate_handler;
        server->reactivate_handler = reactivate_handler;
        server->quit_handler = quit_handler;
        server->has_active_vt_handler = has_active_vt_handler;
        server->reload_handler = reload_handler;
        server->user_data = user_data;

        return server;
}

static void ply_boot_connection_on_hangup (ply_boot_connection_t *connection);

void
ply_boot_server_free (ply_boot_server_t *server)
{
        ply_list_node_t *node;

        if (server == NULL)
                return;
        while ((node = ply_list_get_first_node (server->connections))) {
                ply_boot_connection_t *connection = ply_list_node_get_data (node);
                ply_boot_connection_on_hangup (connection);
        }
        ply_list_free (server->connections);
        ply_list_free (server->cached_passwords);
        free (server);
}

static ply_boot_connection_t *
ply_boot_connection_new (ply_boot_server_t *server,
                         int                fd)
{
        ply_boot_connection_t *connection;

        connection = calloc (1, sizeof(ply_boot_connection_t));
        connection->fd = fd;
        connection->server = server;
        connection->watch = NULL;
        connection->reference_count = 1;

        return connection;
}

static void
ply_boot_connection_free (ply_boot_connection_t *connection)
{
        if (connection == NULL)
                return;

        close (connection->fd);
        free (connection);
}

static void
ply_boot_connection_take_reference (ply_boot_connection_t *connection)
{
        connection->reference_count++;
}

static void
ply_boot_connection_drop_reference (ply_boot_connection_t *connection)
{
        if (connection == NULL)
                return;

        connection->reference_count--;

        assert (connection->reference_count >= 0);

        if (connection->reference_count == 0)
                ply_boot_connection_free (connection);
}

bool
ply_boot_server_listen (ply_boot_server_t *server)
{
        assert (server != NULL);

        server->socket_fd =
                ply_listen_to_unix_socket (PLY_BOOT_PROTOCOL_TRIMMED_ABSTRACT_SOCKET_PATH,
                                           PLY_UNIX_SOCKET_TYPE_TRIMMED_ABSTRACT);

        if (server->socket_fd < 0)
                return false;

        return true;
}

void
ply_boot_server_stop_listening (ply_boot_server_t *server)
{
        assert (server != NULL);
}

static bool
ply_boot_connection_read_request (ply_boot_connection_t *connection,
                                  char                 **command,
                                  char                 **argument)
{
        uint8_t header[2];

        assert (connection != NULL);
        assert (connection->fd >= 0);

        connection->credentials_read = false;

        if (!ply_read (connection->fd, header, sizeof(header)))
                return false;

        *command = calloc (2, sizeof(char));
        *command[0] = header[0];

        *argument = NULL;
        if (header[1] == '\002') {
                uint8_t argument_size;

                if (!ply_read (connection->fd, &argument_size, sizeof(uint8_t))) {
                        free (*command);
                        return false;
                }

                *argument = calloc (argument_size, sizeof(char));

                if (!ply_read (connection->fd, *argument, argument_size)) {
                        free (*argument);
                        free (*command);
                        return false;
                }
        }

        if (!ply_get_credentials_from_fd (connection->fd, &connection->pid, &connection->uid, NULL)) {
                ply_trace ("couldn't read credentials from connection: %m");
                free (*argument);
                free (*command);
                return false;
        }
        connection->credentials_read = true;

        return true;
}

static bool
ply_boot_connection_is_from_root (ply_boot_connection_t *connection)
{
        if (!connection->credentials_read) {
                ply_trace ("Asked if connection is from root, but haven't checked credentials yet");
                return false;
        }

        return connection->uid == 0;
}

static void
ply_boot_connection_send_answer (ply_boot_connection_t *connection,
                                 const char            *answer)
{
        uint32_t size;

        /* splash plugin isn't able to ask for password,
         * punt to client
         */
        if (answer == NULL) {
                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NO_ANSWER,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NO_ANSWER)))
                        ply_trace ("could not finish writing no answer reply: %m");
        } else {
                size = strlen (answer);

                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ANSWER,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ANSWER)) ||
                    !ply_write_uint32 (connection->fd,
                                       size) ||
                    !ply_write (connection->fd,
                                answer, size))
                        ply_trace ("could not finish writing answer: %m");
        }
}

static void
ply_boot_connection_on_password_answer (ply_boot_connection_t *connection,
                                        const char            *password)
{
        ply_trace ("got password answer");

        if (!connection->disconnected)
                ply_boot_connection_send_answer (connection, password);

        if (password != NULL)
                ply_list_append_data (connection->server->cached_passwords,
                                      strdup (password));

        ply_boot_connection_drop_reference (connection);
}

static void
ply_boot_connection_on_deactivated (ply_boot_connection_t *connection)
{
        ply_trace ("deactivated");

        if (!connection->disconnected) {
                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK)))
                        ply_trace ("could not finish writing deactivate reply: %m");
        }

        ply_boot_connection_drop_reference (connection);
}

static void
ply_boot_connection_on_quit_complete (ply_boot_connection_t *connection)
{
        ply_trace ("quit complete");
        if (!connection->disconnected) {
                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK)))
                        ply_trace ("could not finish writing quit reply: %m");
        }

        ply_boot_connection_drop_reference (connection);
}

static void
ply_boot_connection_on_question_answer (ply_boot_connection_t *connection,
                                        const char            *answer)
{
        ply_trace ("got question answer: %s", answer);
        if (!connection->disconnected)
                ply_boot_connection_send_answer (connection, answer);

        ply_boot_connection_drop_reference (connection);
}

static void
ply_boot_connection_on_keystroke_answer (ply_boot_connection_t *connection,
                                         const char            *key)
{
        ply_trace ("got key: %s", key);
        if (!connection->disconnected)
                ply_boot_connection_send_answer (connection, key);

        ply_boot_connection_drop_reference (connection);
}

static void
print_connection_process_identity (ply_boot_connection_t *connection)
{
        char *command_line, *parent_command_line;
        pid_t parent_pid;

        command_line = ply_get_process_command_line (connection->pid);

        if (connection->pid == 1) {
                ply_trace ("connection is from toplevel init process (%s)", command_line);
        } else {
                parent_pid = ply_get_process_parent_pid (connection->pid); parent_command_line = ply_get_process_command_line (parent_pid);

                ply_trace ("connection is from pid %ld (%s) with parent pid %ld (%s)",
                           (long) connection->pid, command_line,
                           (long) parent_pid, parent_command_line);

                free (parent_command_line);
        }

        free (command_line);
}

static void
ply_boot_connection_on_request (ply_boot_connection_t *connection)
{
        ply_boot_server_t *server;
        char *command, *argument;

        assert (connection != NULL);
        assert (connection->fd >= 0);

        server = connection->server;
        assert (server != NULL);

        if (!ply_boot_connection_read_request (connection,
                                               &command, &argument)) {
                ply_trace ("could not read connection request");
                return;
        }

        if (ply_is_tracing ())
                print_connection_process_identity (connection);

        if (!ply_boot_connection_is_from_root (connection)) {
                ply_error ("request came from non-root user");

                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NAK,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NAK)))
                        ply_trace ("could not finish writing is-not-root nak: %m");

                free (argument);
                free (command);
                return;
        }

        if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_UPDATE) == 0) {
                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK)) &&
                    errno != EPIPE)
                        ply_trace ("could not finish writing update reply: %m");

                ply_trace ("got update request");
                if (server->update_handler != NULL)
                        server->update_handler (server->user_data, argument, server);
                free (argument);
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_CHANGE_MODE) == 0) {
                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK)))
                        ply_trace ("could not finish writing update reply: %m");

                ply_trace ("got change mode notification");
                if (server->change_mode_handler != NULL)
                        server->change_mode_handler (server->user_data, argument, server);
                free (argument);
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_SYSTEM_UPDATE) == 0) {
                long int value;
                char *endptr = NULL;

                value = strtol (argument, &endptr, 10);
                if (endptr == NULL || *endptr != '\0' || value < 0 || value > 100) {
                        ply_error ("failed to parse percentage %s", argument);
                        value = 0;
                }

                ply_trace ("got system-update notification %li%%", value);
                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK)))
                        ply_trace ("could not finish writing update reply: %m");

                if (server->system_update_handler != NULL)
                        server->system_update_handler (server->user_data, value, server);
                free (argument);
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_SYSTEM_INITIALIZED) == 0) {
                ply_trace ("got system initialized notification");
                if (server->system_initialized_handler != NULL)
                        server->system_initialized_handler (server->user_data, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_ERROR) == 0) {
                ply_trace ("got error notification");
                if (server->error_handler != NULL)
                        server->error_handler (server->user_data, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_SHOW_SPLASH) == 0) {
                ply_trace ("got show splash request");
                if (server->show_splash_handler != NULL)
                        server->show_splash_handler (server->user_data, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_HIDE_SPLASH) == 0) {
                ply_trace ("got hide splash request");
                if (server->hide_splash_handler != NULL)
                        server->hide_splash_handler (server->user_data, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_DEACTIVATE) == 0) {
                ply_trigger_t *deactivate_trigger;

                ply_trace ("got deactivate request");

                deactivate_trigger = ply_trigger_new (NULL);

                ply_trigger_add_handler (deactivate_trigger,
                                         (ply_trigger_handler_t)
                                         ply_boot_connection_on_deactivated,
                                         connection);
                ply_boot_connection_take_reference (connection);

                if (server->deactivate_handler != NULL)
                        server->deactivate_handler (server->user_data, deactivate_trigger, server);
                else
                        ply_trigger_free (deactivate_trigger);

                free (argument);
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_REACTIVATE) == 0) {
                ply_trace ("got reactivate request");
                if (server->reactivate_handler != NULL)
                        server->reactivate_handler (server->user_data, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_QUIT) == 0) {
                bool retain_splash;
                ply_trigger_t *quit_trigger;

                retain_splash = (bool) argument[0];

                ply_trace ("got quit %srequest", retain_splash ? "--retain-splash " : "");

                quit_trigger = ply_trigger_new (NULL);

                ply_trigger_add_handler (quit_trigger,
                                         (ply_trigger_handler_t)
                                         ply_boot_connection_on_quit_complete,
                                         connection);
                ply_boot_connection_take_reference (connection);

                if (server->quit_handler != NULL)
                        server->quit_handler (server->user_data, retain_splash, quit_trigger, server);
                else
                        ply_trigger_free (quit_trigger);

                free (argument);
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_RELOAD) == 0) {
                ply_trace ("got reload request");
                if (server->reload_handler != NULL)
                        server->reload_handler (server->user_data, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_PASSWORD) == 0) {
                ply_trigger_t *answer;

                ply_trace ("got password request");

                answer = ply_trigger_new (NULL);
                ply_trigger_add_handler (answer,
                                         (ply_trigger_handler_t)
                                         ply_boot_connection_on_password_answer,
                                         connection);
                ply_boot_connection_take_reference (connection);

                if (server->ask_for_password_handler != NULL) {
                        server->ask_for_password_handler (server->user_data,
                                                          argument,
                                                          answer,
                                                          server);
                } else {
                        ply_trigger_free (answer);
                        free (argument);
                }
                /* will reply later
                 */
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_CACHED_PASSWORD) == 0) {
                ply_list_node_t *node;
                ply_buffer_t *buffer;
                size_t buffer_size;
                uint32_t size;

                ply_trace ("got cached password request");

                buffer = ply_buffer_new ();

                node = ply_list_get_first_node (server->cached_passwords);

                ply_trace ("There are %d cached passwords",
                           ply_list_get_length (server->cached_passwords));

                /* Add each answer separated by their NUL terminators into
                 * a buffer that we write out to the client
                 */
                while (node != NULL) {
                        ply_list_node_t *next_node;
                        const char *password;

                        next_node = ply_list_get_next_node (server->cached_passwords, node);
                        password = (const char *) ply_list_node_get_data (node);

                        ply_buffer_append_bytes (buffer,
                                                 password,
                                                 strlen (password) + 1);
                        node = next_node;
                }

                buffer_size = ply_buffer_get_size (buffer);

                /* splash plugin doesn't have any cached passwords
                 */
                if (buffer_size == 0) {
                        ply_trace ("Responding with 'no answer' reply since there are currently "
                                   "no cached answers");
                        if (!ply_write (connection->fd,
                                        PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NO_ANSWER,
                                        strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NO_ANSWER)))
                                ply_trace ("could not finish writing no answer reply: %m");
                } else {
                        size = buffer_size;

                        ply_trace ("writing %d cached answers",
                                   ply_list_get_length (server->cached_passwords));
                        if (!ply_write (connection->fd,
                                        PLY_BOOT_PROTOCOL_RESPONSE_TYPE_MULTIPLE_ANSWERS,
                                        strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_MULTIPLE_ANSWERS)) ||
                            !ply_write_uint32 (connection->fd,
                                               size) ||
                            !ply_write (connection->fd,
                                        ply_buffer_get_bytes (buffer), size))
                                ply_trace ("could not finish writing cached answer reply: %m");
                }

                ply_buffer_free (buffer);
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_QUESTION) == 0) {
                ply_trigger_t *answer;

                ply_trace ("got question request");

                answer = ply_trigger_new (NULL);
                ply_trigger_add_handler (answer,
                                         (ply_trigger_handler_t)
                                         ply_boot_connection_on_question_answer,
                                         connection);
                ply_boot_connection_take_reference (connection);

                if (server->ask_question_handler != NULL) {
                        server->ask_question_handler (server->user_data,
                                                      argument,
                                                      answer,
                                                      server);
                } else {
                        ply_trigger_free (answer);
                        free (argument);
                }
                /* will reply later
                 */
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_SHOW_MESSAGE) == 0) {
                ply_trace ("got show message request");
                if (server->display_message_handler != NULL)
                        server->display_message_handler (server->user_data, argument, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_HIDE_MESSAGE) == 0) {
                ply_trace ("got hide message request");
                if (server->hide_message_handler != NULL)
                        server->hide_message_handler (server->user_data, argument, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_KEYSTROKE) == 0) {
                ply_trigger_t *answer;

                ply_trace ("got keystroke request");

                answer = ply_trigger_new (NULL);
                ply_trigger_add_handler (answer,
                                         (ply_trigger_handler_t)
                                         ply_boot_connection_on_keystroke_answer,
                                         connection);
                ply_boot_connection_take_reference (connection);

                if (server->watch_for_keystroke_handler != NULL) {
                        server->watch_for_keystroke_handler (server->user_data,
                                                             argument,
                                                             answer,
                                                             server);
                } else {
                        ply_trigger_free (answer);
                        free (argument);
                }
                /* will reply later
                 */
                free (command);
                return;
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_KEYSTROKE_REMOVE) == 0) {
                ply_trace ("got keystroke remove request");
                if (server->ignore_keystroke_handler != NULL)
                        server->ignore_keystroke_handler (server->user_data,
                                                          argument,
                                                          server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_PROGRESS_PAUSE) == 0) {
                ply_trace ("got progress pause request");
                if (server->progress_pause_handler != NULL)
                        server->progress_pause_handler (server->user_data,
                                                        server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_PROGRESS_UNPAUSE) == 0) {
                ply_trace ("got progress unpause request");
                if (server->progress_unpause_handler != NULL)
                        server->progress_unpause_handler (server->user_data,
                                                          server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_NEWROOT) == 0) {
                ply_trace ("got newroot request");
                if (server->newroot_handler != NULL)
                        server->newroot_handler (server->user_data, argument, server);
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_HAS_ACTIVE_VT) == 0) {
                bool answer = false;

                ply_trace ("got has_active vt? request");
                if (server->has_active_vt_handler != NULL)
                        answer = server->has_active_vt_handler (server->user_data, server);

                if (!answer) {
                        if (!ply_write (connection->fd,
                                        PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NAK,
                                        strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NAK)))
                                ply_trace ("could not finish writing nak: %m");

                        free (argument);
                        free (command);
                        return;
                }
        } else if (strcmp (command, PLY_BOOT_PROTOCOL_REQUEST_TYPE_PING) != 0) {
                ply_error ("received unknown command '%s' from client", command);

                if (!ply_write (connection->fd,
                                PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NAK,
                                strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_NAK)))
                        ply_trace ("could not finish writing ping reply: %m");

                free (argument);
                free (command);
                return;
        }

        if (!ply_write (connection->fd,
                        PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK,
                        strlen (PLY_BOOT_PROTOCOL_RESPONSE_TYPE_ACK)))
                ply_trace ("could not finish writing ack: %m");
        free (argument);
        free (command);
}

static void
ply_boot_connection_on_hangup (ply_boot_connection_t *connection)
{
        ply_list_node_t *node;
        ply_boot_server_t *server;

        assert (connection != NULL);
        assert (connection->server != NULL);

        connection->disconnected = true;

        server = connection->server;

        node = ply_list_find_node (server->connections, connection);

        assert (node != NULL);

        ply_boot_connection_drop_reference (connection);
        ply_list_remove_node (server->connections, node);
}

static void
ply_boot_server_on_new_connection (ply_boot_server_t *server)
{
        ply_boot_connection_t *connection;
        int fd;

        assert (server != NULL);

        fd = accept4 (server->socket_fd, NULL, NULL, SOCK_CLOEXEC);

        if (fd < 0)
                return;

        connection = ply_boot_connection_new (server, fd);

        connection->watch =
                ply_event_loop_watch_fd (server->loop, fd,
                                         PLY_EVENT_LOOP_FD_STATUS_HAS_DATA,
                                         (ply_event_handler_t)
                                         ply_boot_connection_on_request,
                                         (ply_event_handler_t)
                                         ply_boot_connection_on_hangup,
                                         connection);

        ply_list_append_data (server->connections, connection);
}

static void
ply_boot_server_on_hangup (ply_boot_server_t *server)
{
        assert (server != NULL);
}

static void
ply_boot_server_detach_from_event_loop (ply_boot_server_t *server)
{
        assert (server != NULL);
        server->loop = NULL;
}

void
ply_boot_server_attach_to_event_loop (ply_boot_server_t *server,
                                      ply_event_loop_t  *loop)
{
        assert (server != NULL);
        assert (loop != NULL);
        assert (server->loop == NULL);
        assert (server->socket_fd >= 0);

        server->loop = loop;

        ply_event_loop_watch_fd (loop, server->socket_fd,
                                 PLY_EVENT_LOOP_FD_STATUS_HAS_DATA,
                                 (ply_event_handler_t)
                                 ply_boot_server_on_new_connection,
                                 (ply_event_handler_t)
                                 ply_boot_server_on_hangup,
                                 server);
        ply_event_loop_watch_for_exit (loop, (ply_event_loop_exit_handler_t)
                                       ply_boot_server_detach_from_event_loop,
                                       server);
}

