source: mainline/uspace/srv/hid/remcons/remcons.c@ 6a753a9c

Last change on this file since 6a753a9c was 6a753a9c, checked in by Jiri Svoboda <jiri@…>, 9 months ago

Remcons options port, no-ctl, no-rgb, multiple instances.

  • Property mode set to 100644
File size: 14.9 KB
RevLine 
[21a9869]1/*
[d3109ff]2 * Copyright (c) 2024 Jiri Svoboda
[21a9869]3 * Copyright (c) 2012 Vojtech Horky
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup remcons
31 * @{
32 */
33/** @file
34 */
35
36#include <async.h>
37#include <errno.h>
[5d94b16c]38#include <io/con_srv.h>
39#include <stdio.h>
40#include <stdlib.h>
[21a9869]41#include <str_error.h>
42#include <loc.h>
43#include <io/keycode.h>
44#include <align.h>
45#include <fibril_synch.h>
46#include <task.h>
[fab2746]47#include <inet/addr.h>
48#include <inet/endpoint.h>
49#include <inet/tcp.h>
[21a9869]50#include <io/console.h>
51#include <inttypes.h>
[1d6dd2a]52#include <str.h>
[d3109ff]53#include <vt/vt100.h>
[3806317]54#include "telnet.h"
[30d4706]55#include "user.h"
[d3109ff]56#include "remcons.h"
[21a9869]57
58#define APP_GETTERM "/app/getterm"
[03e0a244]59#define APP_SHELL "/app/bdsh"
[5576358]60
[6a753a9c]61#define DEF_PORT 2223
62
[3806317]63/** Telnet commands to force character mode
64 * (redundant to be on the safe side).
65 * See
[787b65b]66 * http://stackoverflow.com/questions/273261/force-telnet-user-into-character-mode
[3806317]67 * for discussion.
68 */
69static const telnet_cmd_t telnet_force_character_mode_command[] = {
70 TELNET_IAC, TELNET_WILL, TELNET_ECHO,
71 TELNET_IAC, TELNET_WILL, TELNET_SUPPRESS_GO_AHEAD,
72 TELNET_IAC, TELNET_WONT, TELNET_LINEMODE
73};
[3123d2a]74
[3806317]75static const size_t telnet_force_character_mode_command_count =
76 sizeof(telnet_force_character_mode_command) / sizeof(telnet_cmd_t);
77
[b7fd2a0]78static errno_t remcons_open(con_srvs_t *, con_srv_t *);
79static errno_t remcons_close(con_srv_t *);
[d3109ff]80static errno_t remcons_read(con_srv_t *, void *, size_t, size_t *);
[b7fd2a0]81static errno_t remcons_write(con_srv_t *, void *, size_t, size_t *);
[5d94b16c]82static void remcons_sync(con_srv_t *);
83static void remcons_clear(con_srv_t *);
84static void remcons_set_pos(con_srv_t *, sysarg_t col, sysarg_t row);
[b7fd2a0]85static errno_t remcons_get_pos(con_srv_t *, sysarg_t *, sysarg_t *);
86static errno_t remcons_get_size(con_srv_t *, sysarg_t *, sysarg_t *);
87static errno_t remcons_get_color_cap(con_srv_t *, console_caps_t *);
[d3109ff]88static void remcons_set_style(con_srv_t *, console_style_t);
89static void remcons_set_color(con_srv_t *, console_color_t,
90 console_color_t, console_color_attr_t);
91static void remcons_set_color(con_srv_t *, console_color_t,
92 console_color_t, console_color_attr_t);
93static void remcons_set_rgb_color(con_srv_t *, pixel_t, pixel_t);
94static void remcons_cursor_visibility(con_srv_t *, bool);
[b7fd2a0]95static errno_t remcons_get_event(con_srv_t *, cons_event_t *);
[5d94b16c]96
97static con_ops_t con_ops = {
98 .open = remcons_open,
99 .close = remcons_close,
[d3109ff]100 .read = remcons_read,
[5d94b16c]101 .write = remcons_write,
102 .sync = remcons_sync,
103 .clear = remcons_clear,
104 .set_pos = remcons_set_pos,
105 .get_pos = remcons_get_pos,
106 .get_size = remcons_get_size,
107 .get_color_cap = remcons_get_color_cap,
[d3109ff]108 .set_style = remcons_set_style,
109 .set_color = remcons_set_color,
110 .set_rgb_color = remcons_set_rgb_color,
111 .set_cursor_visibility = remcons_cursor_visibility,
[5d94b16c]112 .get_event = remcons_get_event
113};
[21a9869]114
[fab2746]115static void remcons_new_conn(tcp_listener_t *lst, tcp_conn_t *conn);
116
117static tcp_listen_cb_t listen_cb = {
118 .new_conn = remcons_new_conn
119};
120
121static tcp_cb_t conn_cb = {
122 .connected = NULL
123};
124
[4c6fd56]125static loc_srv_t *remcons_srv;
[6a753a9c]126static bool no_ctl;
127static bool no_rgb;
[4c6fd56]128
[5d94b16c]129static telnet_user_t *srv_to_user(con_srv_t *srv)
[21a9869]130{
[d3109ff]131 remcons_t *remcons = (remcons_t *)srv->srvs->sarg;
132 return remcons->user;
133}
134
135static remcons_t *srv_to_remcons(con_srv_t *srv)
136{
137 remcons_t *remcons = (remcons_t *)srv->srvs->sarg;
138 return remcons;
[5d94b16c]139}
[21a9869]140
[b7fd2a0]141static errno_t remcons_open(con_srvs_t *srvs, con_srv_t *srv)
[5d94b16c]142{
143 telnet_user_t *user = srv_to_user(srv);
[99c2e9f3]144
[5d94b16c]145 telnet_user_log(user, "New client connected (%p).", srv);
[21a9869]146
[5d94b16c]147 /* Force character mode. */
[fab2746]148 (void) tcp_conn_send(user->conn, (void *)telnet_force_character_mode_command,
149 telnet_force_character_mode_command_count);
[21a9869]150
[5d94b16c]151 return EOK;
152}
153
[b7fd2a0]154static errno_t remcons_close(con_srv_t *srv)
[5d94b16c]155{
156 telnet_user_t *user = srv_to_user(srv);
157
158 telnet_user_notify_client_disconnected(user);
159 telnet_user_log(user, "Client disconnected (%p).", srv);
160
161 return EOK;
162}
163
[d3109ff]164static errno_t remcons_read(con_srv_t *srv, void *data, size_t size,
165 size_t *nread)
166{
167 telnet_user_t *user = srv_to_user(srv);
168 errno_t rc;
169
170 rc = telnet_user_recv(user, data, size, nread);
171 if (rc != EOK)
172 return rc;
173
174 return EOK;
175}
176
[b7fd2a0]177static errno_t remcons_write(con_srv_t *srv, void *data, size_t size, size_t *nwritten)
[5d94b16c]178{
179 telnet_user_t *user = srv_to_user(srv);
[b7fd2a0]180 errno_t rc;
[5d94b16c]181
182 rc = telnet_user_send_data(user, data, size);
183 if (rc != EOK)
184 return rc;
185
[c8211849]186 *nwritten = size;
187 return EOK;
[5d94b16c]188}
189
190static void remcons_sync(con_srv_t *srv)
191{
192 (void) srv;
193}
194
195static void remcons_clear(con_srv_t *srv)
196{
[d3109ff]197 remcons_t *remcons = srv_to_remcons(srv);
198
199 if (remcons->enable_ctl) {
200 vt100_cls(remcons->vt);
201 vt100_set_pos(remcons->vt, 0, 0);
202 remcons->user->cursor_x = 0;
203 remcons->user->cursor_y = 0;
204 }
[5d94b16c]205}
206
207static void remcons_set_pos(con_srv_t *srv, sysarg_t col, sysarg_t row)
208{
[d3109ff]209 remcons_t *remcons = srv_to_remcons(srv);
[5d94b16c]210 telnet_user_t *user = srv_to_user(srv);
211
[d3109ff]212 if (remcons->enable_ctl) {
213 vt100_set_pos(remcons->vt, col, row);
214 remcons->user->cursor_x = col;
215 remcons->user->cursor_y = row;
216 } else {
217 telnet_user_update_cursor_x(user, col);
218 }
[5d94b16c]219}
220
[b7fd2a0]221static errno_t remcons_get_pos(con_srv_t *srv, sysarg_t *col, sysarg_t *row)
[5d94b16c]222{
223 telnet_user_t *user = srv_to_user(srv);
224
225 *col = user->cursor_x;
[d3109ff]226 *row = user->cursor_y;
[5d94b16c]227
228 return EOK;
229}
230
[b7fd2a0]231static errno_t remcons_get_size(con_srv_t *srv, sysarg_t *cols, sysarg_t *rows)
[5d94b16c]232{
[d3109ff]233 remcons_t *remcons = srv_to_remcons(srv);
234
235 if (remcons->enable_ctl) {
236 *cols = 80;
237 *rows = 25;
238 } else {
239 *cols = 100;
240 *rows = 1;
241 }
[5d94b16c]242
243 return EOK;
244}
245
[b7fd2a0]246static errno_t remcons_get_color_cap(con_srv_t *srv, console_caps_t *ccaps)
[5d94b16c]247{
[d3109ff]248 remcons_t *remcons = srv_to_remcons(srv);
249
250 if (remcons->enable_ctl)
251 *ccaps = CONSOLE_CAP_INDEXED | CONSOLE_CAP_RGB;
252 else
253 *ccaps = 0;
[5d94b16c]254
255 return EOK;
256}
257
[d3109ff]258static void remcons_set_style(con_srv_t *srv, console_style_t style)
259{
260 remcons_t *remcons = srv_to_remcons(srv);
261 char_attrs_t attrs;
262
263 if (remcons->enable_ctl) {
264 attrs.type = CHAR_ATTR_STYLE;
265 attrs.val.style = style;
266 vt100_set_attr(remcons->vt, attrs);
267 }
268}
269
270static void remcons_set_color(con_srv_t *srv, console_color_t bgcolor,
271 console_color_t fgcolor, console_color_attr_t flags)
272{
273 remcons_t *remcons = srv_to_remcons(srv);
274 char_attrs_t attrs;
275
276 if (remcons->enable_ctl) {
277 attrs.type = CHAR_ATTR_INDEX;
278 attrs.val.index.bgcolor = bgcolor;
279 attrs.val.index.fgcolor = fgcolor;
280 attrs.val.index.attr = flags;
281 vt100_set_attr(remcons->vt, attrs);
282 }
283}
284
285static void remcons_set_rgb_color(con_srv_t *srv, pixel_t bgcolor,
286 pixel_t fgcolor)
287{
288 remcons_t *remcons = srv_to_remcons(srv);
289 char_attrs_t attrs;
290
291 if (remcons->enable_ctl) {
292 attrs.type = CHAR_ATTR_RGB;
293 attrs.val.rgb.bgcolor = bgcolor;
294 attrs.val.rgb.fgcolor = fgcolor;
295 vt100_set_attr(remcons->vt, attrs);
296 }
297}
298
299static void remcons_cursor_visibility(con_srv_t *srv, bool visible)
300{
301 remcons_t *remcons = srv_to_remcons(srv);
302
303 if (remcons->enable_ctl)
304 vt100_cursor_visibility(remcons->vt, visible);
305}
306
[b7fd2a0]307static errno_t remcons_get_event(con_srv_t *srv, cons_event_t *event)
[5d94b16c]308{
309 telnet_user_t *user = srv_to_user(srv);
[902f0906]310 kbd_event_t kevent;
[b7fd2a0]311 errno_t rc;
[5d94b16c]312
[902f0906]313 rc = telnet_user_get_next_keyboard_event(user, &kevent);
[5d94b16c]314 if (rc != EOK) {
315 /* XXX What? */
316 memset(event, 0, sizeof(*event));
317 return EOK;
[21a9869]318 }
[5d94b16c]319
[902f0906]320 event->type = CEV_KEY;
321 event->ev.key = kevent;
322
[5d94b16c]323 return EOK;
[21a9869]324}
325
[5923cf82]326/** Callback when client connects to a telnet terminal. */
[984a9ba]327static void client_connection(ipc_call_t *icall, void *arg)
[6f7cd5d]328{
[787b65b]329 /* Find the user. */
[fafb8e5]330 telnet_user_t *user = telnet_user_get_for_client_connection(ipc_get_arg2(icall));
[787b65b]331 if (user == NULL) {
[984a9ba]332 async_answer_0(icall, ENOENT);
[6f7cd5d]333 return;
334 }
335
[99c2e9f3]336 /* Handle messages. */
[984a9ba]337 con_conn(icall, &user->srvs);
[6f7cd5d]338}
339
[5923cf82]340/** Fibril for spawning the task running after user connects.
341 *
342 * @param arg Corresponding @c telnet_user_t structure.
343 */
[b7fd2a0]344static errno_t spawn_task_fibril(void *arg)
[21a9869]345{
[787b65b]346 telnet_user_t *user = arg;
[a35b458]347
[21a9869]348 task_id_t task;
[1c635d6]349 task_wait_t wait;
[b7fd2a0]350 errno_t rc = task_spawnl(&task, &wait, APP_GETTERM, APP_GETTERM, user->service_name,
[593e023]351 "/loc", "--msg", "--", APP_SHELL, NULL);
[21a9869]352 if (rc != EOK) {
[593e023]353 telnet_user_error(user, "Spawning `%s %s /loc --msg -- %s' "
354 "failed: %s.", APP_GETTERM, user->service_name, APP_SHELL,
355 str_error(rc));
[261bbdc]356 fibril_mutex_lock(&user->guard);
[787b65b]357 user->task_finished = true;
[5d94b16c]358 user->srvs.aborted = true;
[787b65b]359 fibril_condvar_signal(&user->refcount_cv);
[261bbdc]360 fibril_mutex_unlock(&user->guard);
[21a9869]361 return EOK;
362 }
363
[261bbdc]364 fibril_mutex_lock(&user->guard);
[787b65b]365 user->task_id = task;
[261bbdc]366 fibril_mutex_unlock(&user->guard);
[1870d81]367
[21a9869]368 task_exit_t task_exit;
369 int task_retval;
[1c635d6]370 task_wait(&wait, &task_exit, &task_retval);
[d545d03]371 telnet_user_log(user, "%s terminated %s, exit code %d.", APP_GETTERM,
372 task_exit == TASK_EXIT_NORMAL ? "normally" : "unexpectedly",
373 task_retval);
[21a9869]374
[7c2bb2c]375 /* Announce destruction. */
[261bbdc]376 fibril_mutex_lock(&user->guard);
[787b65b]377 user->task_finished = true;
[5d94b16c]378 user->srvs.aborted = true;
[787b65b]379 fibril_condvar_signal(&user->refcount_cv);
[261bbdc]380 fibril_mutex_unlock(&user->guard);
[7c2bb2c]381
382 return EOK;
383}
384
[5923cf82]385/** Tell whether given user can be destroyed (has no active clients).
386 *
387 * @param user The telnet user in question.
388 */
[787b65b]389static bool user_can_be_destroyed_no_lock(telnet_user_t *user)
[1870d81]390{
[787b65b]391 return user->task_finished && user->socket_closed &&
392 (user->locsrv_connection_count == 0);
[1870d81]393}
[7c2bb2c]394
[d3109ff]395static void remcons_vt_putchar(void *arg, char32_t c)
396{
397 remcons_t *remcons = (remcons_t *)arg;
398 char buf[STR_BOUNDS(1)];
399 size_t off;
400 errno_t rc;
401
402 (void)arg;
403
404 off = 0;
405 rc = chr_encode(c, buf, &off, sizeof(buf));
406 if (rc != EOK)
407 return;
408
409 (void)telnet_user_send_data(remcons->user, buf, off);
410}
411
412static void remcons_vt_cputs(void *arg, const char *str)
413{
414 remcons_t *remcons = (remcons_t *)arg;
415
416 (void)telnet_user_send_data(remcons->user, str, str_size(str));
417}
418
419static void remcons_vt_flush(void *arg)
420{
421 remcons_t *remcons = (remcons_t *)arg;
422 (void)remcons;
423}
424
[d6ff08a0]425/** Handle network connection.
[5923cf82]426 *
[d6ff08a0]427 * @param lst Listener
428 * @param conn Connection
[5923cf82]429 */
[d6ff08a0]430static void remcons_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
[7c2bb2c]431{
[d3109ff]432 char_attrs_t attrs;
433 remcons_t *remcons = calloc(1, sizeof(remcons_t));
434 assert(remcons != NULL); // XXX
[d6ff08a0]435 telnet_user_t *user = telnet_user_create(conn);
436 assert(user);
437
[6a753a9c]438 remcons->enable_ctl = !no_ctl;
[d3109ff]439 remcons->user = user;
440
441 if (remcons->enable_ctl) {
442 user->rows = 25;
443 } else {
444 user->rows = 1;
445 }
446
447 remcons->vt = vt100_state_create((void *)remcons, 80, 25,
448 remcons_vt_putchar, remcons_vt_cputs, remcons_vt_flush);
449 assert(remcons->vt != NULL); // XXX
[6a753a9c]450 remcons->vt->enable_rgb = !no_rgb;
[d3109ff]451
452 if (remcons->enable_ctl) {
453 attrs.type = CHAR_ATTR_STYLE;
454 attrs.val.style = STYLE_NORMAL;
455 vt100_set_sgr(remcons->vt, attrs);
456 vt100_cls(remcons->vt);
457 vt100_set_pos(remcons->vt, 0, 0);
458 }
459
[d6ff08a0]460 con_srvs_init(&user->srvs);
461 user->srvs.ops = &con_ops;
[d3109ff]462 user->srvs.sarg = remcons;
[d6ff08a0]463 user->srvs.abort_timeout = 1000;
464
465 telnet_user_add(user);
[7c2bb2c]466
[4c6fd56]467 errno_t rc = loc_service_register(remcons_srv, user->service_name,
468 &user->service_id);
[6f7cd5d]469 if (rc != EOK) {
[d545d03]470 telnet_user_error(user, "Unable to register %s with loc: %s.",
471 user->service_name, str_error(rc));
[d6ff08a0]472 return;
[6f7cd5d]473 }
[d545d03]474
475 telnet_user_log(user, "Service %s registerd with id %" PRIun ".",
476 user->service_name, user->service_id);
[d6ff08a0]477
[787b65b]478 fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
[7c2bb2c]479 assert(spawn_fibril);
480 fibril_add_ready(spawn_fibril);
[d6ff08a0]481
[6f7cd5d]482 /* Wait for all clients to exit. */
[261bbdc]483 fibril_mutex_lock(&user->guard);
[787b65b]484 while (!user_can_be_destroyed_no_lock(user)) {
485 if (user->task_finished) {
[fab2746]486 user->conn = NULL;
[787b65b]487 user->socket_closed = true;
[5d94b16c]488 user->srvs.aborted = true;
[1870d81]489 continue;
[787b65b]490 } else if (user->socket_closed) {
491 if (user->task_id != 0) {
492 task_kill(user->task_id);
[1870d81]493 }
494 }
[261bbdc]495 fibril_condvar_wait_timeout(&user->refcount_cv, &user->guard, 1000);
[6f7cd5d]496 }
[261bbdc]497 fibril_mutex_unlock(&user->guard);
[d6ff08a0]498
[4c6fd56]499 rc = loc_service_unregister(remcons_srv, user->service_id);
[7c2bb2c]500 if (rc != EOK) {
[d545d03]501 telnet_user_error(user,
502 "Unable to unregister %s from loc: %s (ignored).",
503 user->service_name, str_error(rc));
[7c2bb2c]504 }
505
[d545d03]506 telnet_user_log(user, "Destroying...");
[787b65b]507 telnet_user_destroy(user);
[fab2746]508}
509
[6a753a9c]510static void print_syntax(void)
511{
512 fprintf(stderr, "syntax: remcons [<options>]\n");
513 fprintf(stderr, "\t--no-ctl Disable all terminal control sequences\n");
514 fprintf(stderr, "\t--no-rgb Disable RGB colors\n");
515 fprintf(stderr, "\t--port <port> Listening port (default: %u)\n",
516 DEF_PORT);
517}
518
[21a9869]519int main(int argc, char *argv[])
520{
[b7fd2a0]521 errno_t rc;
[fab2746]522 tcp_listener_t *lst;
523 tcp_t *tcp;
524 inet_ep_t ep;
[6a753a9c]525 uint16_t port;
526 int i;
527
528 port = DEF_PORT;
529
530 i = 1;
531 while (i < argc) {
532 if (argv[i][0] == '-') {
533 if (str_cmp(argv[i], "--no-ctl") == 0) {
534 no_ctl = true;
535 } else if (str_cmp(argv[i], "--no-rgb") == 0) {
536 no_rgb = true;
537 } else if (str_cmp(argv[i], "--port") == 0) {
538 ++i;
539 if (i >= argc) {
540 fprintf(stderr, "Option argument "
541 "missing.\n");
542 print_syntax();
543 return EINVAL;
544 }
545 rc = str_uint16_t(argv[i], NULL, 10, true, &port);
546 if (rc != EOK) {
547 fprintf(stderr, "Invalid port number "
548 "'%s'.\n", argv[i]);
549 print_syntax();
550 return EINVAL;
551 }
552 } else {
553 fprintf(stderr, "Unknown option '%s'.\n",
554 argv[i]);
555 print_syntax();
556 return EINVAL;
557 }
558 } else {
559 fprintf(stderr, "Unexpected argument.\n");
560 print_syntax();
561 return EINVAL;
562 }
563
564 ++i;
565 }
[fab2746]566
[b688fd8]567 async_set_fallback_port_handler(client_connection, NULL);
[4c6fd56]568 rc = loc_server_register(NAME, &remcons_srv);
[3123d2a]569 if (rc != EOK) {
570 fprintf(stderr, "%s: Unable to register server\n", NAME);
571 return rc;
[21a9869]572 }
573
[fab2746]574 rc = tcp_create(&tcp);
[d6ff08a0]575 if (rc != EOK) {
576 fprintf(stderr, "%s: Error initializing TCP.\n", NAME);
[fab2746]577 return rc;
[21a9869]578 }
579
[fab2746]580 inet_ep_init(&ep);
[6a753a9c]581 ep.port = port;
[21a9869]582
[fab2746]583 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
584 &lst);
[21a9869]585 if (rc != EOK) {
[fab2746]586 fprintf(stderr, "%s: Error creating listener.\n", NAME);
587 return rc;
[21a9869]588 }
589
590 printf("%s: HelenOS Remote console service\n", NAME);
[6d5e378]591 task_retval(0);
[fab2746]592 async_manager();
[21a9869]593
[fab2746]594 /* Not reached */
[21a9869]595 return 0;
596}
597
598/** @}
599 */
Note: See TracBrowser for help on using the repository browser.