1/**
2 * \file
3 * \brief Blocking I/O API for terminal client library.
4 */
5
6/*
7 * Copyright (c) 2012, ETH Zurich.
8 * All rights reserved.
9 *
10 * This file is distributed under the terms in the attached LICENSE file.
11 * If you do not find this file, copies can be found by writing to:
12 * ETH Zurich D-INFK, CAB F.78, Universitaetstr. 6, CH-8092 Zurich,
13 * Attn: Systems Group.
14 */
15
16#include <barrelfish/barrelfish.h>
17#include <barrelfish/caddr.h>
18#include <barrelfish/waitset.h>
19#include <collections/list.h>
20#include <if/monitor_defs.h>
21#include <if/octopus_defs.h>
22#include <if/octopus_defs.h>
23#include <if/terminal_defs.h>
24#include <if/terminal_config_defs.h>
25#include <octopus/getset.h>
26#include <octopus/trigger.h>
27#include <term/client/client_blocking.h>
28#include <term/client/default_filters.h>
29#include <term/client/default_triggers.h>
30
31#include "term_debug.h"
32#include "filter_priv.h"
33#include "trigger_priv.h"
34
35#include <assert.h>
36#include <string.h>
37
38/* internal functions */
39static errval_t get_irefs(struct capref sessionid, iref_t *in_iref,
40                          iref_t *out_iref, iref_t *conf_iref);
41static void struct_term_client_init(struct term_client *client);
42static void in_bind_cb(void *st, errval_t err, struct terminal_binding *b);
43static void out_bind_cb(void *st, errval_t err, struct terminal_binding *b);
44static void conf_bind_cb(void *st, errval_t err,
45                         struct terminal_config_binding *b);
46static errval_t handle_echo(struct term_client *client, char *data,
47                            size_t length);
48static void handle_triggers(struct term_client *client, char *data,
49                            size_t length);
50static void exit_cb(void *st);
51
52/**
53 * \brief Initialize a connection to a terminal server and block until
54 *        connection is established.
55 *
56 * \param client     Terminal client state, initialized by function to default
57 *                   values.
58 * \param session_id The session the domain is part of.
59 *
60 * Dispatches the monitor waitset until all the bindings to the terminal server
61 * are established.
62 */
63errval_t term_client_blocking_init(struct term_client *client,
64                                   struct capref session_id)
65{
66    errval_t err;
67    iref_t in_iref= 0;
68    iref_t out_iref= 0;
69    iref_t conf_iref= 0;
70
71    /* Initialize client state to default values. */
72    struct_term_client_init(client);
73
74    /* Get the interface references from octopus. */
75    err = get_irefs(session_id, &in_iref, &out_iref, &conf_iref);
76    if (err_is_fail(err)) {
77        return err;
78    }
79
80    /* Bind to interface for incoming characters. */
81    err = terminal_bind(in_iref, in_bind_cb, client, client->read_ws,
82                        IDC_BIND_FLAGS_DEFAULT);
83    if (err_is_fail(err)) {
84        return err_push(err, TERM_ERR_BIND_IN_INTERFACE);
85    }
86    TERM_DEBUG("Binding to terminal interface for incoming characters.\n");
87
88    /* Bind to interface for outgoing characters. */
89    err = terminal_bind(out_iref, out_bind_cb, client, client->write_ws,
90                        IDC_BIND_FLAGS_DEFAULT);
91    if (err_is_fail(err)) {
92        return err_push(err, TERM_ERR_BIND_OUT_INTERFACE);
93    }
94    TERM_DEBUG("Binding to terminal interface for outgoing characters.\n");
95
96    /* Bind to interface for incoming characters. */
97    err = terminal_config_bind(conf_iref, conf_bind_cb, client,
98                               client->conf_ws, IDC_BIND_FLAGS_DEFAULT);
99    if (err_is_fail(err)) {
100        return err_push(err, TERM_ERR_BIND_CONF_INTERFACE);
101    }
102    TERM_DEBUG("Binding to terminal configuration interface for configuration "
103               "messages.\n");
104
105    /*
106     * Dispatch on the monitor binding until the bind completes. Otherwise, we
107     * would have to check before every term_client_blocking_read and
108     * term_client_blocking_write if we're already connected.
109     */
110    struct monitor_binding *monitor_b = get_monitor_binding();
111    struct waitset *monitor_ws = monitor_b->waitset;
112    while (!client->connected) {
113        err = event_dispatch(monitor_ws);
114        if (err_is_fail(err)) {
115                USER_PANIC_ERR(err, "Error dispatching events.");
116        }
117    }
118    TERM_DEBUG("Connection to terminal server successfully established.\n");
119
120    return SYS_ERR_OK;
121}
122
123/**
124 * \brief Tear down connection to terminal server.
125 *
126 * \param client Terminal client state.
127 *
128 * Dispatches the control waitset until the message is sent.
129 */
130void term_client_blocking_exit(struct term_client *client)
131{
132    errval_t err;
133
134    TERM_DEBUG("Sending disconnect message to terminal device.\n");
135
136    /* Inform terminal device (server), that domain terminated. */
137    err = client->conf_binding->tx_vtbl.disconnect(client->conf_binding,
138                                                   MKCONT(exit_cb, client));
139    if (err_is_fail(err)) {
140        USER_PANIC_ERR(err, "Error sending disconnect to terminal device.\n");
141    }
142
143    /* Wait until message is sent. Necessary to ensure that message is sent
144     * before we terminate. */
145    TERM_DEBUG("Waiting until disconnect message is sent.\n");
146    while (client->connected) {
147        err = event_dispatch(client->conf_ws);
148        if (err_is_fail(err)) {
149            USER_PANIC_ERR(err, "Error dispatching events.");
150        }
151    }
152}
153
154/**
155 * \brief Blocking read from a terminal.
156 *
157 * \param client  Terminal client state.
158 * \param data    Buffer to hold read characters.
159 * \param length  The number of characters to read.
160 * \param read    Number of characters read. This might be less than length if
161 *                line_mode is enabled and the end of line was reached or if an
162 *                error occurred.
163 *
164 * \return SYS_ERR_OK if successful.
165 *         TERM_ERR_IO if an I/O error occurred.
166 *
167 * Dispatches the read if no data is available.
168 */
169errval_t term_client_blocking_read(struct term_client *client, char *data,
170                                   size_t length, size_t *read)
171{
172    errval_t err;
173    bool eol_reached = false;
174
175    assert(data != NULL);
176    assert(length > 0);
177    assert(read != NULL);
178
179    /*
180     * Copy as many characters to the user buffer as he requested but stop if
181     * line mode is enabled and the end of line is reached.
182     */
183    while ((*read < length) && !(client->line_mode && eol_reached)) {
184
185        if (client->readbuf == NULL) {
186
187            /*
188             * Dispatch events on the incoming interface until characters
189             * arrive.
190             */
191            while (client->readbuf == NULL) {
192                err = event_dispatch(client->read_ws);
193                if (err_is_fail(err)) {
194                    return err_push(err, TERM_ERR_IO);
195                }
196            }
197
198            /* handle echo */
199            if (client->echo) {
200                err = handle_echo(client, client->readbuf, client->readbuf_len);
201                if (err_is_fail(err)) {
202                    return err_push(err, TERM_ERR_IO);
203                }
204            }
205
206            /* handle triggers */
207            handle_triggers(client, client->readbuf, client->readbuf_len);
208
209            /* filter input */
210            term_filter_apply(client->input_filters, &client->readbuf,
211                              &client->readbuf_len);
212        }
213
214        /* copy data to user supplied buffer */
215        char *end = client->readbuf + client->readbuf_len;
216        while ((client->readbuf_pos < end) && (*read < length) &&
217               !(client->line_mode && eol_reached)) {
218            data[(*read)++] = *client->readbuf_pos;
219            if (client->line_mode &&
220                (*client->readbuf_pos == TERM_CLIENT_EOL_CHAR)) {
221                eol_reached = true;
222            }
223            client->readbuf_pos++;
224        }
225
226        /* free readbuf */
227        if (client->readbuf_pos == end) {
228            free(client->readbuf);
229            client->readbuf = NULL;
230            client->readbuf_pos = NULL;
231            client->readbuf_len = 0;
232        }
233    }
234
235    return SYS_ERR_OK;
236}
237
238/**
239 * \brief Blocking write to a terminal.
240 *
241 * \param client  Terminal client state.
242 * \param data    Buffer holding characters to write.
243 * \param length  The number of characters to write.
244 * \param written Number of characters written. This might be less than length
245 *                if an error occurred.
246 *
247 * \return SYS_ERR_OK if successful.
248 *         TERM_ERR_IO if an I/O error occurred.
249 *
250 * Dispatches the write waitset until data is sent.
251 */
252errval_t term_client_blocking_write(struct term_client *client,
253                                    const char *data, size_t length,
254                                    size_t *written)
255{
256    errval_t err;
257    char *outdata = NULL;
258
259    assert(data != NULL);
260    assert(length > 0);
261    assert(written != NULL);
262
263    /* Dispatch the outgoing waitset until we can send characters. */
264    while (!client->out_binding->can_send(client->out_binding)) {
265        err = event_dispatch(client->write_ws);
266        if (err_is_fail(err)) {
267            return err_push(err, TERM_ERR_IO);
268        }
269    }
270
271    /* Make a copy of characters, since the output filters might modify them. */
272    outdata = memdup(data, length);
273
274    /* tell user how much we've written (before applying filters) */
275    *written = length;
276
277    /* apply output filters */
278    term_filter_apply(client->output_filters, &outdata, &length);
279
280    /* send characters */
281    err = client->out_binding->tx_vtbl.characters(client->out_binding, NOP_CONT,
282                                                  outdata, length);
283    if (err_is_fail(err)) {
284        err = err_push(err, TERM_ERR_IO);
285        goto out;
286    }
287
288    /* Wait until characters are sent. */
289    while (!client->out_binding->can_send(client->out_binding)) {
290        err = event_dispatch(client->write_ws);
291        if (err_is_fail(err)) {
292            err = err_push(err, TERM_ERR_IO);
293            goto out;
294        }
295    }
296
297 out:
298    /* reset amount written if error */
299    if (err_is_fail(err)) {
300        *written = 0;
301    }
302    /* free data */
303    free(outdata);
304    return err;
305}
306
307/**
308 * \brief Send a configuration command to the terminal server.
309 *
310 * \param client Terminal client state.
311 * \param opt    Configuration option.
312 * \param arg    Optional argument.
313 *
314 * \return SYS_ERR_OK if successful.
315 *         TERM_ERR_UNKNOWN_CONFIG_OPT if opt is unknown.
316 *
317 * Dispatches the config waitset until configuration message is sent.
318 */
319errval_t term_client_blocking_config(struct term_client *client,
320                                     enum TerminalConfig opt, size_t arg)
321{
322    switch(opt) {
323        case TerminalConfig_ECHO:
324            client->echo = arg > 0;
325            return SYS_ERR_OK;
326        break;
327
328        case TerminalConfig_ICRNL:
329        {
330            if (arg == false && client->cr2lf_id > 0) {
331                errval_t err = term_client_remove_input_filter(client, client->cr2lf_id);
332                if (err_is_ok(err)) {
333                    client->cr2lf_id = 0;
334                }
335                return err;
336            }
337            else if(arg == true && client->cr2lf_id == 0) {
338                term_filter_id_t id = term_client_add_input_filter(client, term_filter_cr2lf);
339                client->cr2lf_id = id;
340            }
341
342            return SYS_ERR_OK;
343        }
344        break;
345
346        case TerminalConfig_CTRLC:
347        {
348            if (arg == false && client->ctrlc_id > 0) {
349                errval_t err = term_client_remove_trigger(client, client->ctrlc_id);
350                if (err_is_ok(err)) {
351                    client->ctrlc_id = 0;
352                }
353                return err;
354            }
355            else if(arg == true && client->ctrlc_id == 0) {
356                client->ctrlc_id = term_client_add_trigger_type(client, term_trigger_int,
357                                                                TERM_TRIGGER_TYPE_USER);
358            }
359            return SYS_ERR_OK;
360        }
361        break;
362
363
364        default:
365            return TERM_ERR_UNKNOWN_CONFIG_OPT;
366    }
367
368
369}
370
371errval_t term_client_blocking_tcgetattr(struct term_client *client,
372                                        struct termios* t)
373{
374    if (client->cr2lf_id > 0) {
375        t->c_iflag = ICRNL;
376    }
377    if (client->echo) {
378        t->c_lflag = ECHO;
379    }
380
381    return SYS_ERR_OK;
382}
383
384errval_t term_client_blocking_tcsetattr(struct term_client *client,
385                                        const struct termios* t)
386{
387    errval_t err = term_client_blocking_config(client, TerminalConfig_ECHO, (t->c_lflag & ECHO) > 0);
388    assert(err_is_ok(err));
389    err = term_client_blocking_config(client, TerminalConfig_ICRNL, (t->c_iflag & ICRNL) > 0);
390    assert(err_is_ok(err));
391
392    return err;
393}
394
395
396
397/**
398 * \privatesection
399 * Internal function follow.
400 */
401
402/**
403 * \brief Default asynchronous error handler.
404 */
405static void default_err_handler(void *st, errval_t err)
406{
407    if (err_is_fail(err)) {
408        USER_PANIC_ERR(err, "Error in libterm_client.");
409    }
410}
411
412/**
413 * \brief Initialize client state with default values.
414 */
415static void struct_term_client_init(struct term_client *client)
416{
417    client->read_ws = malloc(sizeof(struct waitset));
418    assert(client->read_ws != NULL);
419    waitset_init(client->read_ws);
420    client->write_ws = malloc(sizeof(struct waitset));
421    assert(client->write_ws != NULL);
422    waitset_init(client->write_ws);
423    client->conf_ws = malloc(sizeof(struct waitset));
424    assert(client->conf_ws);
425    waitset_init(client->conf_ws);
426    client->connected = false;
427    client->echo = true;
428    client->line_mode = true;
429    client->non_blocking_read = false;
430    client->chars_cb = NULL;
431    client->err_cb = default_err_handler,
432    client->in_binding = NULL;
433    client->out_binding = NULL;
434    client->conf_binding = NULL;
435    client->readbuf = NULL;
436    collections_list_create(&client->input_filters, term_filter_free);
437    collections_list_create(&client->output_filters, term_filter_free);
438    collections_list_create(&client->echo_filters, term_filter_free);
439    client->max_input_filter_id = 0;
440    client->max_output_filter_id = 0;
441    client->max_echo_filter_id = 0;
442    collections_list_create(&client->triggers, term_trigger_free);
443    client->max_trigger_id = 0;
444
445    /* add default input filters */
446    client->cr2lf_id = term_client_add_input_filter(client, term_filter_cr2lf);
447
448    /* add default output filters */
449    client->lf2crlf_id = term_client_add_output_filter(client, term_filter_lf2crlf);
450
451    /* add default echo filters */
452    client->ctrlhat_id = term_client_add_echo_filter(client, term_filter_ctrlhat);
453
454    /* add default triggers */
455    term_client_add_trigger_type(client, term_trigger_kill,
456                                 TERM_TRIGGER_TYPE_BUILT_IN);
457    client->ctrlc_id = term_client_add_trigger_type(client, term_trigger_int,
458                                                    TERM_TRIGGER_TYPE_USER);
459}
460
461/**
462 * \brief Retrieve interface references for incoming and outgoing characters
463 *        as well as for configuration messages from octopus.
464 */
465static errval_t get_irefs(struct capref session_id, iref_t *in_iref,
466                          iref_t *out_iref, iref_t *conf_iref)
467{
468    errval_t err;
469
470    struct octopus_binding *r = get_octopus_binding();
471    assert(r != NULL);
472
473    struct octopus_get_with_idcap_response__rx_args reply;
474
475    err = r->rpc_tx_vtbl.get_with_idcap(r, session_id, NOP_TRIGGER, reply.output, &reply.tid,
476                                 &reply.error_code);
477    if (err_is_fail(err)) {
478        err_push(err, TERM_ERR_LOOKUP_SESSION_RECORD);
479        goto out;
480    }
481    err = reply.error_code;
482    if (err_is_fail(err)) {
483        err_push(err, TERM_ERR_LOOKUP_SESSION_RECORD);
484        goto out;
485    }
486
487    TERM_DEBUG("Record retrieved from octopus: %s\n", record);
488
489    int64_t session_oct;
490    int64_t in_oct;
491    int64_t out_oct;
492    int64_t conf_oct;
493    // oct_read can only parse 64-bit values, we need to parse the irefs as 64bit
494    // then cast to 32bit
495    err = oct_read(reply.output, "_ { session_iref: %d, in_iref: %d, out_iref: %d, "
496                   "conf_iref: %d }", &session_oct, &in_oct, &out_oct,
497                    &conf_oct);
498    //iref_t session_iref = (iref_t)session_oct;
499    *in_iref = (iref_t)in_oct;
500    *out_iref = (iref_t)out_oct;
501    *conf_iref = (iref_t)conf_oct;
502    if (err_is_fail(err)) {
503        err_push(err, TERM_ERR_PARSE_SESSION_RECORD);
504        goto out;
505    }
506    if ((*in_iref == NULL_IREF) || (*out_iref == NULL_IREF) ||
507        (*conf_iref == NULL_IREF)) {
508        err = TERM_ERR_PARSE_SESSION_RECORD;
509        goto out;
510    }
511
512    TERM_DEBUG("Retrieved interface references from octopus. in_iref: %"
513               PRIuIREF ", out_iref: %" PRIuIREF ", conf_iref: %" PRIuIREF
514               "\n", *in_iref, *out_iref, *conf_iref);
515
516out:
517    return err;
518}
519
520static void check_connection_established(struct term_client *client)
521{
522    if (client->in_binding == NULL || client->out_binding == NULL ||
523        client->conf_binding == NULL) {
524        /* Not all three connections are already established. */
525        return;
526    }
527
528    client->connected = true;
529}
530
531static errval_t handle_echo(struct term_client *client, char *data,
532                            size_t length)
533{
534    errval_t err = SYS_ERR_OK;
535    char *echodata = NULL;
536
537    assert(client != NULL);
538    assert(data != NULL);
539    assert(length > 0);
540
541    /* Dispatch the outgoing waitset until we can send characters */
542    while (!client->out_binding->can_send(client->out_binding)) {
543        err = event_dispatch(client->write_ws);
544        if (err_is_fail(err)) {
545            return err;
546        }
547    }
548
549    /*
550     * Make a copy of the data, since the echo filters might modify it and the
551     * modification should not be seen by the application.
552     */
553    echodata = memdup(data, length);
554
555    /* apply echo filters */
556    term_filter_apply(client->echo_filters, &echodata, &length);
557
558    /* echo characters */
559    err = client->out_binding->tx_vtbl.characters(client->out_binding, NOP_CONT,
560                                                  echodata, length);
561    if (err_is_fail(err)) {
562        goto out;
563    }
564
565    /* Wait until characters echoed. */
566    while (!client->out_binding->can_send(client->out_binding)) {
567        err = event_dispatch(client->write_ws);
568        if (err_is_fail(err)) {
569            goto out;
570        }
571    }
572
573out:
574    /* free data*/
575    free(echodata);
576    return err;
577}
578
579static void handle_triggers(struct term_client *client, char *data,
580                            size_t length)
581{
582    struct term_trigger *trigger = NULL;
583
584    collections_list_traverse_start(client->triggers);
585
586    while ((trigger = collections_list_traverse_next(client->triggers)) != NULL)
587    {
588        for (int i = 0; i < length; i++) {
589            if (data[i] == trigger->trigger_character) {
590                /* call closure associated with trigger */
591                trigger->closure.handler(trigger->closure.arg);
592            }
593        }
594    }
595
596    collections_list_traverse_end(client->triggers);
597}
598
599static void in_characters_handler(struct terminal_binding *b, const char *data,
600                                  size_t length)
601{
602    struct term_client *client = b->st;
603
604
605    char *my_data = memdup(data, length);
606
607    if (client->non_blocking_read) {
608        assert(client->chars_cb != NULL);
609
610        /* handle triggers */
611        handle_triggers(client, my_data, length);
612
613        /* filter input */
614        term_filter_apply(client->input_filters, &my_data, &length);
615
616        /* call user supplied chars_cb */
617        client->chars_cb(client->st, my_data, length);
618    } else {
619        assert(client->readbuf == NULL);
620
621        client->readbuf = my_data;
622        client->readbuf_pos = my_data;
623        client->readbuf_len = length;
624    }
625}
626
627static void in_bind_cb(void *st, errval_t err, struct terminal_binding *b)
628{
629    struct term_client *client = st;
630
631    if (err_is_fail(err)) {
632        /* call async error callback */
633        err = err_push(err, TERM_ERR_BIND_IN_INTERFACE);
634        client->err_cb(client->st, err);
635        return;
636    }
637
638    client->in_binding = b;
639    b->st = client;
640    b->rx_vtbl.characters = in_characters_handler;
641
642    /* Check if all connections are already established. */
643    check_connection_established(client);
644}
645
646static void out_characters_handler(struct terminal_binding *b, const char *data,
647                                   size_t length)
648{
649    struct term_client *client = b->st;
650
651    /*
652     * It is an error if characters arrive at the interface for outgoing
653     * characters.
654     */
655    client->err_cb(client->st, TERM_ERR_RECV_CHARS);
656}
657
658static void out_bind_cb(void *st, errval_t err, struct terminal_binding *b)
659{
660    struct term_client *client = st;
661
662    if (err_is_fail(err)) {
663        /* call async error callback */
664        err = err_push(err, TERM_ERR_BIND_OUT_INTERFACE);
665        client->err_cb(client->st, err);
666        return;
667    }
668
669    client->out_binding = b;
670    b->st = client;
671    b->rx_vtbl.characters = out_characters_handler;
672
673    /* Check if all connections are already established. */
674    check_connection_established(client);
675}
676
677static void conf_configuration_handler(struct terminal_config_binding *b,
678                                       terminal_config_option_t opt,
679                                       const char *arguments)
680{
681    struct term_client *client = b->st;
682
683    /*
684     * Configuration messages only flow from the client to the server. It is
685     * an error if the server send a configuration message to the client.
686     */
687    client->err_cb(client->st, TERM_ERR_RECV_CONFIGURATION);
688}
689
690static void conf_bind_cb(void *st, errval_t err,
691                         struct terminal_config_binding *b)
692{
693    struct term_client *client = st;
694
695    if (err_is_fail(err)) {
696        /* call async error callback */
697        err = err_push(err, TERM_ERR_BIND_CONF_INTERFACE);
698        client->err_cb(client->st, err);
699        return;
700    }
701
702    client->conf_binding = b;
703    b->st = client;
704    b->rx_vtbl.configuration = conf_configuration_handler;
705
706    /* Check if all connections are already established. */
707    check_connection_established(client);
708}
709
710static void exit_cb(void *arg)
711{
712    struct term_client *client = arg;
713
714    client->connected = false;
715    TERM_DEBUG("Disconnect message sucessfully sent to terminal server.\n");
716}
717