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