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