root/usr.sbin/installer/libdfui/connection.c
/*
 * Copyright (c)2004 Cat's Eye Technologies.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 *   Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 *   Neither the name of Cat's Eye Technologies nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * connection.c
 * $Id: connection.c,v 1.20 2005/02/07 06:39:59 cpressey Exp $
 * This code was derived in part from:
 * $_DragonFly: src/test/caps/client.c,v 1.3 2004/03/31 20:27:34 dillon Exp $
 * and is therefore also subject to the license conditions on that file.
 */

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include <libaura/mem.h>
#include <libaura/buffer.h>

#include "system.h"
#define NEEDS_DFUI_STRUCTURE_DEFINITIONS
#include "dfui.h"
#undef  NEEDS_DFUI_STRUCTURE_DEFINITIONS
#include "encoding.h"
#include "dump.h"

#include "conn_npipe.h"
#include "conn_tcp.h"

struct dfui_connection *
dfui_connection_new(int transport, const char *rendezvous)
{
        struct dfui_connection *c = NULL;

        if (
#ifdef HAS_NPIPE
            transport == DFUI_TRANSPORT_NPIPE ||
#endif
#ifdef HAS_TCP
            transport == DFUI_TRANSPORT_TCP ||
#endif
            0) {
                /* We're OK. */
        } else {
                return(NULL);
        }

        if (dfui_debug_file == NULL) {
                dfui_debug_file = stderr;
        } else {
                setvbuf(dfui_debug_file, NULL, _IOLBF, 0);
        }

        AURA_MALLOC(c, dfui_connection);
        c->rendezvous = aura_strdup(rendezvous);
        c->transport = transport;
        c->ebuf = aura_buffer_new(16384);
        c->is_connected = 0;
        c->t_data = NULL;

        switch (transport) {
#ifdef HAS_NPIPE
        case DFUI_TRANSPORT_NPIPE:
                AURA_MALLOC(c->t_data, dfui_conn_npipe);
                T_NPIPE(c)->in_pipename = NULL;
                T_NPIPE(c)->out_pipename = NULL;
                T_NPIPE(c)->in = NULL;
                T_NPIPE(c)->out = NULL;

                /*
                 * Set up dispatch functions.
                 */
                c->be_start = dfui_npipe_be_start;
                c->be_stop = dfui_npipe_be_stop;
                c->be_ll_exchange = dfui_npipe_be_ll_exchange;

                c->fe_connect = dfui_npipe_fe_connect;
                c->fe_disconnect = dfui_npipe_fe_disconnect;
                c->fe_ll_request = dfui_npipe_fe_ll_request;
                break;
#endif /* HAS_NPIPE */

#ifdef HAS_TCP
        case DFUI_TRANSPORT_TCP:
                AURA_MALLOC(c->t_data, dfui_conn_tcp);
                T_TCP(c)->listen_sd = -1;
                T_TCP(c)->connected_sd = -1;
                T_TCP(c)->is_connected = 0;

                /*
                 * Set up dispatch functions.
                 */
                c->be_start = dfui_tcp_be_start;
                c->be_stop = dfui_tcp_be_stop;
                c->be_ll_exchange = dfui_tcp_be_ll_exchange;

                c->fe_connect = dfui_tcp_fe_connect;
                c->fe_disconnect = dfui_tcp_fe_disconnect;
                c->fe_ll_request = dfui_tcp_fe_ll_request;
                break;
#endif /* HAS_TCP */
        }

        return(c);
}

void
dfui_connection_free(struct dfui_connection *c)
{
        if (c == NULL)
                return;

        switch (c->transport) {
#ifdef HAS_NPIPE
        case DFUI_TRANSPORT_NPIPE:
                if (T_NPIPE(c) != NULL) {
                        if (T_NPIPE(c)->in_pipename != NULL)
                                aura_free(T_NPIPE(c)->in_pipename, "pipename");
                        if (T_NPIPE(c)->out_pipename != NULL)
                                aura_free(T_NPIPE(c)->out_pipename, "pipename");
                        if (T_NPIPE(c)->in != NULL)
                                fclose(T_NPIPE(c)->in);
                        if (T_NPIPE(c)->out != NULL)
                                fclose(T_NPIPE(c)->out);
                        AURA_FREE(T_NPIPE(c), dfui_conn_npipe);
                }
                break;
#endif
#ifdef HAS_TCP
        case DFUI_TRANSPORT_TCP:
                if (T_TCP(c) != NULL) {
                        /* XXX close sockets/files here */
                        AURA_FREE(T_NPIPE(c), dfui_conn_tcp);
                }
                break;
#endif
        }

        if (c->rendezvous != NULL)
                free(c->rendezvous);
        AURA_FREE(c, dfui_connection);
}

/*
 * VERY HIGH LEVEL
 */

/*
 * Create and present a generic `dialog box'-type form for the user
 * and return their response.  actions is a pipe-seperated list of
 * actions to be put on the form (e.g. "OK|Cancel".)  The return
 * value is the ordinal position of the action that was selected,
 * starting at 1 for the first action.  A return value of 0 indicates
 * that an error occurred.  A return value of -1 indicates that the
 * front end aborted the communications.
 */
int
dfui_be_present_dialog(struct dfui_connection *c, const char *title,
                        const char *actions, const char *fmt, ...)
{
        struct dfui_form *f;
        struct dfui_response *r;
        va_list args;
        char *message;
        char action_id[256], action_name[256];
        size_t start, end, counter, i;

        va_start(args, fmt);
        vasprintf(&message, fmt, args);
        va_end(args);

        f = dfui_form_create("dialog", title, message, "", NULL);

        free(message);

        start = end = 0;
        while (actions[end] != '\0') {
                end = start;
                while (actions[end] != '|' && actions[end] != '\0')
                        end++;

                if ((end - start) >= 256)
                        break;
                strncpy(action_name, &actions[start], end - start);
                action_name[end - start] = '\0';
                strcpy(action_id, action_name);
                for(i = 0; action_id[i] != '\0'; i++) {
                        if (action_id[i] == ' ')
                                action_id[i] = '_';
                }
                dfui_form_action_add(f, action_id,
                    dfui_info_new(action_name, "", ""));

                start = end + 1;
        }

        if (!dfui_be_present(c, f, &r)) {
                dfui_form_free(f);
                dfui_response_free(r);
                return(-1);
        }

        strlcpy(action_name, dfui_response_get_action_id(r), 256);
        for(i = 0; action_name[i] != '\0'; i++) {
                if (action_name[i] == '_')
                        action_name[i] = ' ';
        }

        start = end = 0;
        counter = 1;
        while (actions[end] != '\0') {
                end = start;
                while (actions[end] != '|' && actions[end] != '\0')
                        end++;

                if ((end - start) >= 256)
                        break;
                if (strlen(action_name) == (end - start) &&
                    strncmp(action_name, &actions[start], end - start) == 0) {
                        break;
                }
                counter++;

                start = end + 1;
        }

        dfui_form_free(f);
        dfui_response_free(r);

        return(counter);
}

/******** BACKEND ********/

/*
 * Connect to the frontend.
 */
dfui_err_t
dfui_be_start(struct dfui_connection *c)
{
        if (c->is_connected) {
                return(DFUI_FAILURE);
        } else if (c->be_start(c)) {
                c->is_connected = 1;
                return(DFUI_SUCCESS);
        } else {
                return(DFUI_FAILURE);
        }
}

/*
 * Tell the frontend that we're done and disconnect from it.
 */
dfui_err_t
dfui_be_stop(struct dfui_connection *c)
{
        if (!c->is_connected) {
                return(DFUI_SUCCESS);
        } else if (c->be_stop(c)) {
                c->is_connected = 0;
                return(DFUI_SUCCESS);
        } else {
                return(DFUI_FAILURE);
        }
}

static struct aura_buffer *
dfui_be_handle_field_changed(struct dfui_connection *c,
                             struct dfui_form *f)
{
        struct aura_buffer *e;
        char *field_id, *new_value;
        int ret;

        e = aura_buffer_new(1024);
        aura_buffer_set(e, aura_buffer_buf(c->ebuf) + 1, aura_buffer_len(c->ebuf) - 1);
        field_id = dfui_decode_string(e);
        new_value = dfui_decode_string(e);
        aura_buffer_free(e);
        dfui_debug("handle changed field: id=`%s', new_value=`%s'\n",
                   field_id, new_value);

        ret = dfui_form_trigger_callbacks(f, field_id, new_value);
        free(field_id);
        free(new_value);

        e = aura_buffer_new(16384);
        if (ret == 0) {
                dfui_encode_datasets(e, f->dataset_head);
        } else {
                /* TODO: encode and send error to frontend */
        }

        return(e);
}

/*
 * Present a form to the user.  This call is synchronous;
 * it does not return until the user has selected an action.
 */
dfui_err_t
dfui_be_present(struct dfui_connection *c,
                struct dfui_form *f, struct dfui_response **r)
{
        struct aura_buffer *e;
        char msgtype;

        e = aura_buffer_new(16384);
        dfui_encode_form(e, f);
        c->be_ll_exchange(c, DFUI_BE_MSG_PRESENT, aura_buffer_buf(e));
        aura_buffer_free(e);

        while (1) {
                msgtype = aura_buffer_buf(c->ebuf)[0];
                if (msgtype == DFUI_FE_MSG_ABORT)
                        return(DFUI_FAILURE);

                if (msgtype == DFUI_FE_MSG_FIELD_CHANGED) {
                        e = dfui_be_handle_field_changed(c, f);
                        c->be_ll_exchange(c, DFUI_BE_MSG_FIELD_CHANGED_ACK, aura_buffer_buf(e));
                        aura_buffer_free(e);
                        continue;
                }

                /* Other response, like submit */
                e = aura_buffer_new(16384);
                aura_buffer_set(e, aura_buffer_buf(c->ebuf) + 1,
                                aura_buffer_len(c->ebuf) - 1);
                *r = dfui_decode_response(e);
                aura_buffer_free(e);
                return(DFUI_SUCCESS);
        }
}

/*
 * Begin showing a progress bar to the user.
 * This function is asynchronous; it returns immediately.
 * The assumption is that the backend will make subsequent
 * calls to dfui_be_progress_update() frequently, and in
 * them, check to see if the user cancelled.
 */
dfui_err_t
dfui_be_progress_begin(struct dfui_connection *c, struct dfui_progress *pr)
{
        struct aura_buffer *e;

        e = aura_buffer_new(16384);
        dfui_encode_progress(e, pr);

        c->be_ll_exchange(c, DFUI_BE_MSG_PROG_BEGIN, aura_buffer_buf(e));
        aura_buffer_free(e);

        /* response might have been be READY or ABORT */
        if (aura_buffer_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
                return(DFUI_FAILURE);
        } else {
                return(DFUI_SUCCESS);
        }
}

dfui_err_t
dfui_be_progress_update(struct dfui_connection *c,
                        struct dfui_progress *pr, int *cancelled)
{
        struct aura_buffer *e;

        e = aura_buffer_new(16384);
        dfui_encode_progress(e, pr);

        c->be_ll_exchange(c, DFUI_BE_MSG_PROG_UPDATE, aura_buffer_buf(e));
        aura_buffer_free(e);

        /* response might have been READY, CANCEL, or ABORT */

        *cancelled = 0;
        if (aura_buffer_buf(c->ebuf)[0] == DFUI_FE_MSG_CANCEL) {
                *cancelled = 1;
        }
        if (aura_buffer_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
                return(DFUI_FAILURE);
        } else {
                return(DFUI_SUCCESS);
        }
}

dfui_err_t
dfui_be_progress_end(struct dfui_connection *c)
{
        c->be_ll_exchange(c, DFUI_BE_MSG_PROG_END, "");

        /* response might have been be READY or ABORT */
        if (aura_buffer_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
                return(DFUI_FAILURE);
        } else {
                return(DFUI_SUCCESS);
        }
}

dfui_err_t
dfui_be_set_global_setting(struct dfui_connection *c,
                           const char *key, const char *value,
                           int *cancelled)
{
        struct aura_buffer *e;
        struct dfui_property *p;

        e = aura_buffer_new(16384);
        p = dfui_property_new(key, value);
        dfui_encode_property(e, p);
        c->be_ll_exchange(c, DFUI_BE_MSG_SET_GLOBAL, aura_buffer_buf(e));
        aura_buffer_free(e);
        dfui_property_free(p);

        /* response might have been READY, CANCEL, or ABORT */

        *cancelled = 0;
        if (aura_buffer_buf(c->ebuf)[0] == DFUI_FE_MSG_CANCEL) {
                *cancelled = 1;
        }
        if (aura_buffer_buf(c->ebuf)[0] == DFUI_FE_MSG_ABORT) {
                return(DFUI_FAILURE);
        } else {
                return(DFUI_SUCCESS);
        }
}

/******** FRONTEND ********/

dfui_err_t
dfui_fe_connect(struct dfui_connection *c)
{
        return(c->fe_connect(c));
}

dfui_err_t
dfui_fe_disconnect(struct dfui_connection *c)
{
        dfui_debug("DISCONNECTING<<>>\n");
        return(c->fe_disconnect(c));
}

/*
 * Receive a message from the backend.  This call is synchronous;
 * it does not return until a message comes in from the backend.
 * After this call, the message type is available in *msgtype,
 * and the message itself (if any) is available in *payload, ready
 * to be casted to its real type (as per *msgtype).
 */
dfui_err_t
dfui_fe_receive(struct dfui_connection *c, char *msgtype, void **payload)
{
        struct aura_buffer *e;

        c->fe_ll_request(c, DFUI_FE_MSG_READY, "");
        *msgtype = aura_buffer_buf(c->ebuf)[0];
        switch (*msgtype) {
        case DFUI_BE_MSG_PRESENT:
                e = aura_buffer_new(16384);
                aura_buffer_set(e, aura_buffer_buf(c->ebuf) + 1, aura_buffer_len(c->ebuf) - 1);
                *payload = dfui_decode_form(e);
                aura_buffer_free(e);
                return(DFUI_SUCCESS);

        case DFUI_BE_MSG_PROG_BEGIN:
                e = aura_buffer_new(16384);
                aura_buffer_set(e, aura_buffer_buf(c->ebuf) + 1, aura_buffer_len(c->ebuf) - 1);
                *payload = dfui_decode_progress(e);
                aura_buffer_free(e);
                return(DFUI_SUCCESS);

        case DFUI_BE_MSG_PROG_UPDATE:
                e = aura_buffer_new(16384);
                aura_buffer_set(e, aura_buffer_buf(c->ebuf) + 1, aura_buffer_len(c->ebuf) - 1);
                *payload = dfui_decode_progress(e);
                aura_buffer_free(e);
                return(DFUI_SUCCESS);

        case DFUI_BE_MSG_PROG_END:
                *payload = NULL;
                return(DFUI_SUCCESS);

        case DFUI_BE_MSG_SET_GLOBAL:
                e = aura_buffer_new(16384);
                aura_buffer_set(e, aura_buffer_buf(c->ebuf) + 1, aura_buffer_len(c->ebuf) - 1);
                *payload = dfui_decode_property(e);
                aura_buffer_free(e);
                return(DFUI_SUCCESS);

        case DFUI_BE_MSG_FIELD_CHANGED_ACK:
                e = aura_buffer_new(16384);
                aura_buffer_set(e, aura_buffer_buf(c->ebuf) + 1, aura_buffer_len(c->ebuf) - 1);
                *payload = dfui_decode_datasets(e);
                aura_buffer_free(e);
                return(DFUI_SUCCESS);

        case DFUI_BE_MSG_STOP:
                *payload = NULL;
                return(DFUI_SUCCESS);

        default:
                /* XXX ??? */
                return(DFUI_FAILURE);
        }
}

/*
 * Wrapper function for dfui_fe_receive for binding generators which
 * seem to (understandably) have problems wrapping void *'s themselves.
 */
struct dfui_payload *
dfui_fe_receive_payload(struct dfui_connection *c)
{
        char msgtype;
        void *v;
        struct dfui_payload *payload;

        if (!dfui_fe_receive(c, &msgtype, &v)) {
                return(NULL);
        }

        AURA_MALLOC(payload, dfui_payload);

        payload->msgtype = msgtype;
        payload->form = NULL;
        payload->progress = NULL;

        switch (msgtype) {
        case DFUI_BE_MSG_PRESENT:
                payload->form = v;
                break;

        case DFUI_BE_MSG_PROG_BEGIN:
        case DFUI_BE_MSG_PROG_UPDATE:
                payload->progress = v;
                break;

        case DFUI_BE_MSG_SET_GLOBAL:
                payload->global_setting = v;
                break;

        case DFUI_BE_MSG_FIELD_CHANGED_ACK:
                payload->datasets = v;
                break;

        case DFUI_BE_MSG_PROG_END:
        case DFUI_BE_MSG_STOP:
                break;
        }

        return(payload);
}

char
dfui_payload_get_msg_type(const struct dfui_payload *p)
{
        if (p == NULL)
                return(' ');
        return(p->msgtype);
}

struct dfui_form *
dfui_payload_get_form(const struct dfui_payload *p)
{
        if (p == NULL)
                return(NULL);
        return(p->form);
}

struct dfui_progress *
dfui_payload_get_progress(const struct dfui_payload *p)
{
        if (p == NULL)
                return(NULL);
        return(p->progress);
}

struct dfui_dataset *
dfui_payload_get_datasets(const struct dfui_payload *p)
{
        if (p == NULL)
                return(NULL);
        return(p->datasets);
}

void
dfui_payload_free(struct dfui_payload *p)
{
        if (p == NULL)
                return;
        if (p->form != NULL)
                dfui_form_free(p->form);
        if (p->progress != NULL)
                dfui_progress_free(p->progress);
        if (p->datasets != NULL)
                dfui_datasets_free(p->datasets);
        AURA_FREE(p, dfui_payload);
}

dfui_err_t
dfui_fe_send(struct dfui_connection *c, char msgtype, struct aura_buffer *e)
{
        c->fe_ll_request(c, msgtype, aura_buffer_buf(e));
        return(DFUI_SUCCESS);
}

/*
 * Submit the result of a form to the backend.
 */
dfui_err_t
dfui_fe_submit(struct dfui_connection *c, struct dfui_response *r)
{
        struct aura_buffer *e;
        dfui_err_t request_error;

        e = aura_buffer_new(16384);
        dfui_encode_response(e, r);

        dfui_debug("ENCODE<<%s>>\n", aura_buffer_buf(e));
        request_error = c->fe_ll_request(c, DFUI_FE_MSG_SUBMIT,
            aura_buffer_buf(e));
        /* XXX we should check for READY from the backend? */
        aura_buffer_free(e);

        return(request_error);
}

dfui_err_t
dfui_fe_progress_continue(struct dfui_connection *c)
{
        c->fe_ll_request(c, DFUI_FE_MSG_CONTINUE, "");
        return(DFUI_SUCCESS);
}

dfui_err_t
dfui_fe_progress_cancel(struct dfui_connection *c)
{
        c->fe_ll_request(c, DFUI_FE_MSG_CANCEL, "");
        return(DFUI_SUCCESS);
}

dfui_err_t
dfui_fe_confirm_set_global(struct dfui_connection *c)
{
        c->fe_ll_request(c, DFUI_FE_MSG_CONTINUE, "");
        return(DFUI_SUCCESS);
}

dfui_err_t
dfui_fe_cancel_set_global(struct dfui_connection *c)
{
        c->fe_ll_request(c, DFUI_FE_MSG_CANCEL, "");
        return(DFUI_SUCCESS);
}

dfui_err_t
dfui_fe_confirm_stop(struct dfui_connection *c)
{
        c->fe_ll_request(c, DFUI_FE_MSG_CONTINUE, "");
        return(DFUI_SUCCESS);
}

/*
 * Abort the backend.
 * Note that you still must call dfui_fe_disconnect after this.
 */
dfui_err_t
dfui_fe_abort(struct dfui_connection *c)
{
        c->fe_ll_request(c, DFUI_FE_MSG_ABORT, "");
        return(DFUI_SUCCESS);
}