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
Line 
1/*
2 * Copyright (c) 2024 Jiri Svoboda
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>
38#include <io/con_srv.h>
39#include <stdio.h>
40#include <stdlib.h>
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>
47#include <inet/addr.h>
48#include <inet/endpoint.h>
49#include <inet/tcp.h>
50#include <io/console.h>
51#include <inttypes.h>
52#include <str.h>
53#include <vt/vt100.h>
54#include "telnet.h"
55#include "user.h"
56#include "remcons.h"
57
58#define APP_GETTERM "/app/getterm"
59#define APP_SHELL "/app/bdsh"
60
61#define DEF_PORT 2223
62
63/** Telnet commands to force character mode
64 * (redundant to be on the safe side).
65 * See
66 * http://stackoverflow.com/questions/273261/force-telnet-user-into-character-mode
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};
74
75static const size_t telnet_force_character_mode_command_count =
76 sizeof(telnet_force_character_mode_command) / sizeof(telnet_cmd_t);
77
78static errno_t remcons_open(con_srvs_t *, con_srv_t *);
79static errno_t remcons_close(con_srv_t *);
80static errno_t remcons_read(con_srv_t *, void *, size_t, size_t *);
81static errno_t remcons_write(con_srv_t *, void *, size_t, size_t *);
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);
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 *);
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);
95static errno_t remcons_get_event(con_srv_t *, cons_event_t *);
96
97static con_ops_t con_ops = {
98 .open = remcons_open,
99 .close = remcons_close,
100 .read = remcons_read,
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,
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,
112 .get_event = remcons_get_event
113};
114
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
125static loc_srv_t *remcons_srv;
126static bool no_ctl;
127static bool no_rgb;
128
129static telnet_user_t *srv_to_user(con_srv_t *srv)
130{
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;
139}
140
141static errno_t remcons_open(con_srvs_t *srvs, con_srv_t *srv)
142{
143 telnet_user_t *user = srv_to_user(srv);
144
145 telnet_user_log(user, "New client connected (%p).", srv);
146
147 /* Force character mode. */
148 (void) tcp_conn_send(user->conn, (void *)telnet_force_character_mode_command,
149 telnet_force_character_mode_command_count);
150
151 return EOK;
152}
153
154static errno_t remcons_close(con_srv_t *srv)
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
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
177static errno_t remcons_write(con_srv_t *srv, void *data, size_t size, size_t *nwritten)
178{
179 telnet_user_t *user = srv_to_user(srv);
180 errno_t rc;
181
182 rc = telnet_user_send_data(user, data, size);
183 if (rc != EOK)
184 return rc;
185
186 *nwritten = size;
187 return EOK;
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{
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 }
205}
206
207static void remcons_set_pos(con_srv_t *srv, sysarg_t col, sysarg_t row)
208{
209 remcons_t *remcons = srv_to_remcons(srv);
210 telnet_user_t *user = srv_to_user(srv);
211
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 }
219}
220
221static errno_t remcons_get_pos(con_srv_t *srv, sysarg_t *col, sysarg_t *row)
222{
223 telnet_user_t *user = srv_to_user(srv);
224
225 *col = user->cursor_x;
226 *row = user->cursor_y;
227
228 return EOK;
229}
230
231static errno_t remcons_get_size(con_srv_t *srv, sysarg_t *cols, sysarg_t *rows)
232{
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 }
242
243 return EOK;
244}
245
246static errno_t remcons_get_color_cap(con_srv_t *srv, console_caps_t *ccaps)
247{
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;
254
255 return EOK;
256}
257
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
307static errno_t remcons_get_event(con_srv_t *srv, cons_event_t *event)
308{
309 telnet_user_t *user = srv_to_user(srv);
310 kbd_event_t kevent;
311 errno_t rc;
312
313 rc = telnet_user_get_next_keyboard_event(user, &kevent);
314 if (rc != EOK) {
315 /* XXX What? */
316 memset(event, 0, sizeof(*event));
317 return EOK;
318 }
319
320 event->type = CEV_KEY;
321 event->ev.key = kevent;
322
323 return EOK;
324}
325
326/** Callback when client connects to a telnet terminal. */
327static void client_connection(ipc_call_t *icall, void *arg)
328{
329 /* Find the user. */
330 telnet_user_t *user = telnet_user_get_for_client_connection(ipc_get_arg2(icall));
331 if (user == NULL) {
332 async_answer_0(icall, ENOENT);
333 return;
334 }
335
336 /* Handle messages. */
337 con_conn(icall, &user->srvs);
338}
339
340/** Fibril for spawning the task running after user connects.
341 *
342 * @param arg Corresponding @c telnet_user_t structure.
343 */
344static errno_t spawn_task_fibril(void *arg)
345{
346 telnet_user_t *user = arg;
347
348 task_id_t task;
349 task_wait_t wait;
350 errno_t rc = task_spawnl(&task, &wait, APP_GETTERM, APP_GETTERM, user->service_name,
351 "/loc", "--msg", "--", APP_SHELL, NULL);
352 if (rc != EOK) {
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));
356 fibril_mutex_lock(&user->guard);
357 user->task_finished = true;
358 user->srvs.aborted = true;
359 fibril_condvar_signal(&user->refcount_cv);
360 fibril_mutex_unlock(&user->guard);
361 return EOK;
362 }
363
364 fibril_mutex_lock(&user->guard);
365 user->task_id = task;
366 fibril_mutex_unlock(&user->guard);
367
368 task_exit_t task_exit;
369 int task_retval;
370 task_wait(&wait, &task_exit, &task_retval);
371 telnet_user_log(user, "%s terminated %s, exit code %d.", APP_GETTERM,
372 task_exit == TASK_EXIT_NORMAL ? "normally" : "unexpectedly",
373 task_retval);
374
375 /* Announce destruction. */
376 fibril_mutex_lock(&user->guard);
377 user->task_finished = true;
378 user->srvs.aborted = true;
379 fibril_condvar_signal(&user->refcount_cv);
380 fibril_mutex_unlock(&user->guard);
381
382 return EOK;
383}
384
385/** Tell whether given user can be destroyed (has no active clients).
386 *
387 * @param user The telnet user in question.
388 */
389static bool user_can_be_destroyed_no_lock(telnet_user_t *user)
390{
391 return user->task_finished && user->socket_closed &&
392 (user->locsrv_connection_count == 0);
393}
394
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
425/** Handle network connection.
426 *
427 * @param lst Listener
428 * @param conn Connection
429 */
430static void remcons_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
431{
432 char_attrs_t attrs;
433 remcons_t *remcons = calloc(1, sizeof(remcons_t));
434 assert(remcons != NULL); // XXX
435 telnet_user_t *user = telnet_user_create(conn);
436 assert(user);
437
438 remcons->enable_ctl = !no_ctl;
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
450 remcons->vt->enable_rgb = !no_rgb;
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
460 con_srvs_init(&user->srvs);
461 user->srvs.ops = &con_ops;
462 user->srvs.sarg = remcons;
463 user->srvs.abort_timeout = 1000;
464
465 telnet_user_add(user);
466
467 errno_t rc = loc_service_register(remcons_srv, user->service_name,
468 &user->service_id);
469 if (rc != EOK) {
470 telnet_user_error(user, "Unable to register %s with loc: %s.",
471 user->service_name, str_error(rc));
472 return;
473 }
474
475 telnet_user_log(user, "Service %s registerd with id %" PRIun ".",
476 user->service_name, user->service_id);
477
478 fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
479 assert(spawn_fibril);
480 fibril_add_ready(spawn_fibril);
481
482 /* Wait for all clients to exit. */
483 fibril_mutex_lock(&user->guard);
484 while (!user_can_be_destroyed_no_lock(user)) {
485 if (user->task_finished) {
486 user->conn = NULL;
487 user->socket_closed = true;
488 user->srvs.aborted = true;
489 continue;
490 } else if (user->socket_closed) {
491 if (user->task_id != 0) {
492 task_kill(user->task_id);
493 }
494 }
495 fibril_condvar_wait_timeout(&user->refcount_cv, &user->guard, 1000);
496 }
497 fibril_mutex_unlock(&user->guard);
498
499 rc = loc_service_unregister(remcons_srv, user->service_id);
500 if (rc != EOK) {
501 telnet_user_error(user,
502 "Unable to unregister %s from loc: %s (ignored).",
503 user->service_name, str_error(rc));
504 }
505
506 telnet_user_log(user, "Destroying...");
507 telnet_user_destroy(user);
508}
509
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
519int main(int argc, char *argv[])
520{
521 errno_t rc;
522 tcp_listener_t *lst;
523 tcp_t *tcp;
524 inet_ep_t ep;
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 }
566
567 async_set_fallback_port_handler(client_connection, NULL);
568 rc = loc_server_register(NAME, &remcons_srv);
569 if (rc != EOK) {
570 fprintf(stderr, "%s: Unable to register server\n", NAME);
571 return rc;
572 }
573
574 rc = tcp_create(&tcp);
575 if (rc != EOK) {
576 fprintf(stderr, "%s: Error initializing TCP.\n", NAME);
577 return rc;
578 }
579
580 inet_ep_init(&ep);
581 ep.port = port;
582
583 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
584 &lst);
585 if (rc != EOK) {
586 fprintf(stderr, "%s: Error creating listener.\n", NAME);
587 return rc;
588 }
589
590 printf("%s: HelenOS Remote console service\n", NAME);
591 task_retval(0);
592 async_manager();
593
594 /* Not reached */
595 return 0;
596}
597
598/** @}
599 */
Note: See TracBrowser for help on using the repository browser.