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
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#include <async.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <adt/prodcons.h>
39#include <errno.h>
40#include <macros.h>
41#include <mem.h>
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>
49#include <inet/tcp.h>
50#include <io/console.h>
51#include <inttypes.h>
52#include <assert.h>
53#include "remcons.h"
54#include "user.h"
55#include "telnet.h"
56
57static FIBRIL_MUTEX_INITIALIZE(users_guard);
58static LIST_INITIALIZE(users);
59
60/** Create new telnet user.
61 *
62 * @param conn Incoming connection.
63 * @return New telnet user or NULL when out of memory.
64 */
65telnet_user_t *telnet_user_create(tcp_conn_t *conn)
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
76 int rc = asprintf(&user->service_name, "%s/telnet%u.%d", NAMESPACE,
77 (unsigned)task_get_id(), user->id);
78 if (rc < 0) {
79 free(user);
80 return NULL;
81 }
82
83 user->conn = conn;
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;
89 user->send_buf_used = 0;
90
91 fibril_condvar_initialize(&user->refcount_cv);
92 fibril_mutex_initialize(&user->guard);
93 user->task_finished = false;
94 user->socket_closed = false;
95 user->locsrv_connection_count = 0;
96
97 user->cursor_x = 0;
98 user->cursor_y = 0;
99
100 return user;
101}
102
103void telnet_user_add(telnet_user_t *user)
104{
105 fibril_mutex_lock(&users_guard);
106 list_append(&user->link, &users);
107 fibril_mutex_unlock(&users_guard);
108}
109
110/** Destroy telnet user structure.
111 *
112 * @param user User to be destroyed.
113 */
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
125/** Find user by service id and increments reference counter.
126 *
127 * @param id Location service id of the telnet user's terminal.
128 */
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);
134 list_foreach(users, link, telnet_user_t, tmp) {
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;
146 fibril_mutex_lock(&tmp->guard);
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
158 fibril_mutex_unlock(&tmp->guard);
159
160 fibril_mutex_unlock(&users_guard);
161
162 return user;
163}
164
165/** Notify that client disconnected from the remote terminal.
166 *
167 * @param user To which user the client was connected.
168 */
169void telnet_user_notify_client_disconnected(telnet_user_t *user)
170{
171 fibril_mutex_lock(&user->guard);
172 assert(user->locsrv_connection_count > 0);
173 user->locsrv_connection_count--;
174 fibril_condvar_signal(&user->refcount_cv);
175 fibril_mutex_unlock(&user->guard);
176}
177
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
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
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 */
217static errno_t telnet_user_recv_next_byte_no_lock(telnet_user_t *user, char *byte)
218{
219 errno_t rc;
220
221 /* No more buffered data? */
222 if (user->socket_buffer_len <= user->socket_buffer_pos) {
223 rc = telnet_user_fill_recv_buf(user);
224 if (rc != EOK)
225 return rc;
226 }
227
228 *byte = user->socket_buffer[user->socket_buffer_pos++];
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;
237
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;
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 */
257static kbd_event_t *new_kbd_event(kbd_event_type_t type, char32_t c)
258{
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;
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 }
283
284 return event;
285}
286
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 */
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
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 */
311errno_t telnet_user_get_next_keyboard_event(telnet_user_t *user, kbd_event_t *event)
312{
313 fibril_mutex_lock(&user->guard);
314 if (list_empty(&user->in_events.list)) {
315 char next_byte = 0;
316 bool inside_telnet_command = false;
317
318 telnet_cmd_t telnet_option_code = 0;
319
320 /* Skip zeros, bail-out on error. */
321 while (next_byte == 0) {
322 fibril_mutex_unlock(&user->guard);
323
324 errno_t rc = telnet_user_recv_next_byte_no_lock(user, &next_byte);
325 if (rc != EOK)
326 return rc;
327
328 uint8_t byte = (uint8_t) next_byte;
329 fibril_mutex_lock(&user->guard);
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 }
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}
373
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
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 */
410static errno_t telnet_user_send_data_no_lock(telnet_user_t *user,
411 const char *data, size_t size)
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;
423 if (user->cursor_y < (int)user->rows - 1)
424 ++user->cursor_y;
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
435 errno_t rc = telnet_user_send_raw(user, converted, converted_size);
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 */
447errno_t telnet_user_send_data(telnet_user_t *user, const char *data,
448 size_t size)
449{
450 fibril_mutex_lock(&user->guard);
451
452 errno_t rc = telnet_user_send_data_no_lock(user, data, size);
453
454 fibril_mutex_unlock(&user->guard);
455
456 return rc;
457}
458
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
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) {
487 char data = '\b';
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
496/**
497 * @}
498 */
Note: See TracBrowser for help on using the repository browser.