source: mainline/uspace/srv/hid/remcons/user.c@ c23a1fe

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

Remote console mapping

  • Property mode set to 100644
File size: 11.6 KB
RevLine 
[30d4706]1/*
[d3109ff]2 * Copyright (c) 2024 Jiri Svoboda
[30d4706]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#include <async.h>
36#include <stdio.h>
[38d150e]37#include <stdlib.h>
[30d4706]38#include <adt/prodcons.h>
39#include <errno.h>
[c23a1fe]40#include <macros.h>
[d3109ff]41#include <mem.h>
[30d4706]42#include <str_error.h>
43#include <loc.h>
44#include <io/keycode.h>
45#include <align.h>
46#include <as.h>
47#include <fibril_synch.h>
48#include <task.h>
[fab2746]49#include <inet/tcp.h>
[30d4706]50#include <io/console.h>
51#include <inttypes.h>
[8562724]52#include <assert.h>
[d3109ff]53#include "remcons.h"
[30d4706]54#include "user.h"
[2a180307]55#include "telnet.h"
[30d4706]56
57static FIBRIL_MUTEX_INITIALIZE(users_guard);
58static LIST_INITIALIZE(users);
59
[5923cf82]60/** Create new telnet user.
61 *
[fab2746]62 * @param conn Incoming connection.
[5923cf82]63 * @return New telnet user or NULL when out of memory.
64 */
[fab2746]65telnet_user_t *telnet_user_create(tcp_conn_t *conn)
[30d4706]66{
67 static int telnet_user_id_counter = 0;
68
69 telnet_user_t *user = malloc(sizeof(telnet_user_t));
70 if (user == NULL) {
71 return NULL;
72 }
73
74 user->id = ++telnet_user_id_counter;
75
[6a753a9c]76 int rc = asprintf(&user->service_name, "%s/telnet%u.%d", NAMESPACE,
77 (unsigned)task_get_id(), user->id);
[30d4706]78 if (rc < 0) {
79 free(user);
80 return NULL;
81 }
82
[fab2746]83 user->conn = conn;
[30d4706]84 user->service_id = (service_id_t) -1;
85 prodcons_initialize(&user->in_events);
86 link_initialize(&user->link);
87 user->socket_buffer_len = 0;
88 user->socket_buffer_pos = 0;
[c23a1fe]89 user->send_buf_used = 0;
[30d4706]90
91 fibril_condvar_initialize(&user->refcount_cv);
[261bbdc]92 fibril_mutex_initialize(&user->guard);
[30d4706]93 user->task_finished = false;
94 user->socket_closed = false;
95 user->locsrv_connection_count = 0;
96
[5d94b16c]97 user->cursor_x = 0;
[d3109ff]98 user->cursor_y = 0;
[30d4706]99
[5d94b16c]100 return user;
101}
102
103void telnet_user_add(telnet_user_t *user)
104{
[30d4706]105 fibril_mutex_lock(&users_guard);
106 list_append(&user->link, &users);
107 fibril_mutex_unlock(&users_guard);
108}
109
[5923cf82]110/** Destroy telnet user structure.
111 *
112 * @param user User to be destroyed.
113 */
[30d4706]114void telnet_user_destroy(telnet_user_t *user)
115{
116 assert(user);
117
118 fibril_mutex_lock(&users_guard);
119 list_remove(&user->link);
120 fibril_mutex_unlock(&users_guard);
121
122 free(user);
123}
124
[5923cf82]125/** Find user by service id and increments reference counter.
126 *
127 * @param id Location service id of the telnet user's terminal.
128 */
[30d4706]129telnet_user_t *telnet_user_get_for_client_connection(service_id_t id)
130{
131 telnet_user_t *user = NULL;
132
133 fibril_mutex_lock(&users_guard);
[feeac0d]134 list_foreach(users, link, telnet_user_t, tmp) {
[30d4706]135 if (tmp->service_id == id) {
136 user = tmp;
137 break;
138 }
139 }
140 if (user == NULL) {
141 fibril_mutex_unlock(&users_guard);
142 return NULL;
143 }
144
145 telnet_user_t *tmp = user;
[261bbdc]146 fibril_mutex_lock(&tmp->guard);
[30d4706]147 user->locsrv_connection_count++;
148
149 /*
150 * Refuse to return user whose task already finished or when
151 * the socket is already closed().
152 */
153 if (user->task_finished || user->socket_closed) {
154 user = NULL;
155 user->locsrv_connection_count--;
156 }
157
[261bbdc]158 fibril_mutex_unlock(&tmp->guard);
[30d4706]159
160 fibril_mutex_unlock(&users_guard);
161
162 return user;
163}
164
[5923cf82]165/** Notify that client disconnected from the remote terminal.
166 *
167 * @param user To which user the client was connected.
168 */
[8562724]169void telnet_user_notify_client_disconnected(telnet_user_t *user)
170{
[261bbdc]171 fibril_mutex_lock(&user->guard);
[8562724]172 assert(user->locsrv_connection_count > 0);
173 user->locsrv_connection_count--;
174 fibril_condvar_signal(&user->refcount_cv);
[261bbdc]175 fibril_mutex_unlock(&user->guard);
[8562724]176}
[30d4706]177
[99c2e9f3]178/** Tell whether the launched task already exited and socket is already closed.
179 *
180 * @param user Telnet user in question.
181 */
182bool telnet_user_is_zombie(telnet_user_t *user)
183{
184 fibril_mutex_lock(&user->guard);
185 bool zombie = user->socket_closed || user->task_finished;
186 fibril_mutex_unlock(&user->guard);
187
188 return zombie;
189}
190
[d3109ff]191static errno_t telnet_user_fill_recv_buf(telnet_user_t *user)
192{
193 errno_t rc;
194 size_t recv_length;
195
196 rc = tcp_conn_recv_wait(user->conn, user->socket_buffer,
197 BUFFER_SIZE, &recv_length);
198 if (rc != EOK)
199 return rc;
200
201 if (recv_length == 0) {
202 user->socket_closed = true;
203 user->srvs.aborted = true;
204 return ENOENT;
205 }
206
207 user->socket_buffer_len = recv_length;
208 user->socket_buffer_pos = 0;
209
210 return EOK;
211}
212
[99c2e9f3]213/** Receive next byte from a socket (use buffering.
214 * We need to return the value via extra argument because the read byte
215 * might be negative.
216 */
[b7fd2a0]217static errno_t telnet_user_recv_next_byte_no_lock(telnet_user_t *user, char *byte)
[99c2e9f3]218{
[d3109ff]219 errno_t rc;
220
[99c2e9f3]221 /* No more buffered data? */
222 if (user->socket_buffer_len <= user->socket_buffer_pos) {
[d3109ff]223 rc = telnet_user_fill_recv_buf(user);
[fab2746]224 if (rc != EOK)
225 return rc;
[99c2e9f3]226 }
227
228 *byte = user->socket_buffer[user->socket_buffer_pos++];
[d3109ff]229 return EOK;
230}
231
232errno_t telnet_user_recv(telnet_user_t *user, void *data, size_t size,
233 size_t *nread)
234{
235 errno_t rc;
236 size_t nb;
[99c2e9f3]237
[d3109ff]238 /* No more buffered data? */
239 if (user->socket_buffer_len <= user->socket_buffer_pos) {
240 rc = telnet_user_fill_recv_buf(user);
241 if (rc != EOK)
242 return rc;
243 }
244
245 nb = user->socket_buffer_len - user->socket_buffer_pos;
246 memcpy(data, user->socket_buffer + user->socket_buffer_pos, nb);
247 user->socket_buffer_pos += nb;
248 *nread = nb;
[99c2e9f3]249 return EOK;
250}
251
252/** Creates new keyboard event from given char.
253 *
254 * @param type Event type (press / release).
255 * @param c Pressed character.
256 */
[28a5ebd]257static kbd_event_t *new_kbd_event(kbd_event_type_t type, char32_t c)
[1433ecda]258{
[99c2e9f3]259 kbd_event_t *event = malloc(sizeof(kbd_event_t));
260 assert(event);
261
262 link_initialize(&event->link);
263 event->type = type;
264 event->c = c;
265 event->mods = 0;
[c17c4e28]266
267 switch (c) {
268 case '\n':
269 event->key = KC_ENTER;
270 break;
271 case '\t':
272 event->key = KC_TAB;
273 break;
274 case '\b':
275 case 127: /* This is what Linux telnet sends. */
276 event->key = KC_BACKSPACE;
277 event->c = '\b';
278 break;
279 default:
280 event->key = KC_A;
281 break;
282 }
[99c2e9f3]283
284 return event;
285}
286
[178d6a3]287/** Process telnet command (currently only print to screen).
288 *
289 * @param user Telnet user structure.
290 * @param option_code Command option code.
291 * @param cmd Telnet command.
292 */
[2a180307]293static void process_telnet_command(telnet_user_t *user,
294 telnet_cmd_t option_code, telnet_cmd_t cmd)
295{
296 if (option_code != 0) {
297 telnet_user_log(user, "Ignoring telnet command %u %u %u.",
298 TELNET_IAC, option_code, cmd);
299 } else {
300 telnet_user_log(user, "Ignoring telnet command %u %u.",
301 TELNET_IAC, cmd);
302 }
303}
304
[178d6a3]305/** Get next keyboard event.
306 *
307 * @param user Telnet user.
308 * @param event Where to store the keyboard event.
309 * @return Error code.
310 */
[b7fd2a0]311errno_t telnet_user_get_next_keyboard_event(telnet_user_t *user, kbd_event_t *event)
[99c2e9f3]312{
313 fibril_mutex_lock(&user->guard);
314 if (list_empty(&user->in_events.list)) {
315 char next_byte = 0;
[2a180307]316 bool inside_telnet_command = false;
317
318 telnet_cmd_t telnet_option_code = 0;
319
[99c2e9f3]320 /* Skip zeros, bail-out on error. */
321 while (next_byte == 0) {
[d3109ff]322 fibril_mutex_unlock(&user->guard);
323
[b7fd2a0]324 errno_t rc = telnet_user_recv_next_byte_no_lock(user, &next_byte);
[d3109ff]325 if (rc != EOK)
[99c2e9f3]326 return rc;
[d3109ff]327
[2a180307]328 uint8_t byte = (uint8_t) next_byte;
[d3109ff]329 fibril_mutex_lock(&user->guard);
[2a180307]330
331 /* Skip telnet commands. */
332 if (inside_telnet_command) {
333 inside_telnet_command = false;
334 next_byte = 0;
335 if (TELNET_IS_OPTION_CODE(byte)) {
336 telnet_option_code = byte;
337 inside_telnet_command = true;
338 } else {
339 process_telnet_command(user,
340 telnet_option_code, byte);
341 }
342 }
343 if (byte == TELNET_IAC) {
344 inside_telnet_command = true;
345 next_byte = 0;
346 }
[99c2e9f3]347 }
348
349 /* CR-LF conversions. */
350 if (next_byte == 13) {
351 next_byte = 10;
352 }
353
354 kbd_event_t *down = new_kbd_event(KEY_PRESS, next_byte);
355 kbd_event_t *up = new_kbd_event(KEY_RELEASE, next_byte);
356 assert(down);
357 assert(up);
358 prodcons_produce(&user->in_events, &down->link);
359 prodcons_produce(&user->in_events, &up->link);
360 }
361
362 link_t *link = prodcons_consume(&user->in_events);
363 kbd_event_t *tmp = list_get_instance(link, kbd_event_t, link);
364
365 fibril_mutex_unlock(&user->guard);
366
367 *event = *tmp;
368
369 free(tmp);
370
371 return EOK;
372}
[30d4706]373
[c23a1fe]374static errno_t telnet_user_send_raw(telnet_user_t *user,
375 const void *data, size_t size)
376{
377 size_t remain;
378 size_t now;
379 errno_t rc;
380
381 remain = sizeof(user->send_buf) - user->send_buf_used;
382 while (size > 0) {
383 if (remain == 0) {
384 rc = tcp_conn_send(user->conn, user->send_buf,
385 sizeof(user->send_buf));
386 if (rc != EOK)
387 return rc;
388
389 user->send_buf_used = 0;
390 remain = sizeof(user->send_buf);
391 }
392
393 now = min(remain, size);
394 memcpy(user->send_buf + user->send_buf_used, data, now);
395 user->send_buf_used += now;
396 remain -= now;
397 data += now;
398 size -= now;
399 }
400
401 return EOK;
402}
403
[178d6a3]404/** Send data (convert them first) to the socket, no locking.
405 *
406 * @param user Telnet user.
407 * @param data Data buffer (not zero terminated).
408 * @param size Size of @p data buffer in bytes.
409 */
[d3109ff]410static errno_t telnet_user_send_data_no_lock(telnet_user_t *user,
411 const char *data, size_t size)
[178d6a3]412{
413 uint8_t *converted = malloc(3 * size + 1);
414 assert(converted);
415 int converted_size = 0;
416
417 /* Convert new-lines. */
418 for (size_t i = 0; i < size; i++) {
419 if (data[i] == 10) {
420 converted[converted_size++] = 13;
421 converted[converted_size++] = 10;
422 user->cursor_x = 0;
[c23a1fe]423 if (user->cursor_y < (int)user->rows - 1)
[d3109ff]424 ++user->cursor_y;
[178d6a3]425 } else {
426 converted[converted_size++] = data[i];
427 if (data[i] == '\b') {
428 user->cursor_x--;
429 } else {
430 user->cursor_x++;
431 }
432 }
433 }
434
[c23a1fe]435 errno_t rc = telnet_user_send_raw(user, converted, converted_size);
[178d6a3]436 free(converted);
437
438 return rc;
439}
440
441/** Send data (convert them first) to the socket.
442 *
443 * @param user Telnet user.
444 * @param data Data buffer (not zero terminated).
445 * @param size Size of @p data buffer in bytes.
446 */
[d3109ff]447errno_t telnet_user_send_data(telnet_user_t *user, const char *data,
448 size_t size)
[178d6a3]449{
450 fibril_mutex_lock(&user->guard);
451
[b7fd2a0]452 errno_t rc = telnet_user_send_data_no_lock(user, data, size);
[178d6a3]453
454 fibril_mutex_unlock(&user->guard);
455
456 return rc;
457}
458
[c23a1fe]459errno_t telnet_user_flush(telnet_user_t *user)
460{
461 errno_t rc;
462
463 fibril_mutex_lock(&user->guard);
464 rc = tcp_conn_send(user->conn, user->send_buf, user->send_buf_used);
465
466 if (rc != EOK) {
467 fibril_mutex_unlock(&user->guard);
468 return rc;
469 }
470
471 user->send_buf_used = 0;
472 fibril_mutex_unlock(&user->guard);
473 return EOK;
474}
475
[178d6a3]476/** Update cursor X position.
477 *
478 * This call may result in sending control commands over socket.
479 *
480 * @param user Telnet user.
481 * @param new_x New cursor location.
482 */
483void telnet_user_update_cursor_x(telnet_user_t *user, int new_x)
484{
485 fibril_mutex_lock(&user->guard);
486 if (user->cursor_x - 1 == new_x) {
[d3109ff]487 char data = '\b';
[178d6a3]488 /* Ignore errors. */
489 telnet_user_send_data_no_lock(user, &data, 1);
490 }
491 user->cursor_x = new_x;
492 fibril_mutex_unlock(&user->guard);
493
494}
495
[30d4706]496/**
497 * @}
498 */
Note: See TracBrowser for help on using the repository browser.