source: mainline/uspace/srv/hid/remcons/remcons.c@ 5923cf82

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5923cf82 was 5923cf82, checked in by Vojtech Horky <vojtechhorky@…>, 14 years ago

remcons: add comments

  • Property mode set to 100644
File size: 11.6 KB
Line 
1/*
2 * Copyright (c) 2012 Vojtech Horky
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup remcons
30 * @{
31 */
32/** @file
33 */
34
35#include <async.h>
36#include <stdio.h>
37#include <adt/prodcons.h>
38#include <ipc/input.h>
39#include <ipc/console.h>
40#include <ipc/vfs.h>
41#include <errno.h>
42#include <str_error.h>
43#include <loc.h>
44#include <event.h>
45#include <io/keycode.h>
46#include <align.h>
47#include <malloc.h>
48#include <as.h>
49#include <fibril_synch.h>
50#include <task.h>
51#include <net/in.h>
52#include <net/inet.h>
53#include <net/socket.h>
54#include <io/console.h>
55#include <inttypes.h>
56#include "telnet.h"
57#include "user.h"
58
59#define APP_GETTERM "/app/getterm"
60
61
62/** Telnet commands to force character mode
63 * (redundant to be on the safe side).
64 * See
65 * http://stackoverflow.com/questions/273261/force-telnet-user-into-character-mode
66 * for discussion.
67 */
68static const telnet_cmd_t telnet_force_character_mode_command[] = {
69 TELNET_IAC, TELNET_WILL, TELNET_ECHO,
70 TELNET_IAC, TELNET_WILL, TELNET_SUPPRESS_GO_AHEAD,
71 TELNET_IAC, TELNET_WONT, TELNET_LINEMODE
72};
73static const size_t telnet_force_character_mode_command_count =
74 sizeof(telnet_force_character_mode_command) / sizeof(telnet_cmd_t);
75
76/** Creates new keyboard event from given char.
77 *
78 * @param type Event type (press / release).
79 * @param c Pressed character.
80 */
81static kbd_event_t* new_kbd_event(kbd_event_type_t type, wchar_t c) {
82 kbd_event_t *event = malloc(sizeof(kbd_event_t));
83 assert(event);
84
85 link_initialize(&event->link);
86 event->type = type;
87 event->c = c;
88 event->mods = 0;
89 event->key = (c == '\n' ? KC_ENTER : KC_A);
90
91 return event;
92}
93
94/** Handling client requests (VFS and console interface).
95 *
96 * @param user Telnet user the requests belong to.
97 */
98static void client_connection_message_loop(telnet_user_t *user)
99{
100 while (true) {
101 ipc_call_t call;
102 ipc_callid_t callid = 0;
103 while (callid == 0) {
104 callid = async_get_call_timeout(&call, 1000);
105
106 fibril_mutex_lock(&user->refcount_mutex);
107 bool bail_out = user->socket_closed || user->task_finished;
108 fibril_mutex_unlock(&user->refcount_mutex);
109
110 if (bail_out) {
111 if (callid != 0) {
112 async_answer_0(callid, EINTR);
113 }
114 return;
115 }
116 }
117
118 if (!IPC_GET_IMETHOD(call)) {
119 /* Clean-up. */
120 return;
121 }
122
123 switch (IPC_GET_IMETHOD(call)) {
124 case CONSOLE_GET_SIZE:
125 async_answer_2(callid, EOK, 100, 1);
126 break;
127 case CONSOLE_GET_POS:
128 async_answer_2(callid, EOK, 0, 0);
129 break;
130 case CONSOLE_GET_EVENT: {
131 if (list_empty(&user->in_events.list)) {
132 retry:
133 if (user->socket_buffer_len <= user->socket_buffer_pos) {
134 int recv_length = recv(user->socket, user->socket_buffer, BUFFER_SIZE, 0);
135 if ((recv_length == 0) || (recv_length == ENOTCONN)) {
136 fibril_mutex_lock(&user->refcount_mutex);
137 user->socket_closed = true;
138 fibril_mutex_unlock(&user->refcount_mutex);
139 async_answer_0(callid, ENOENT);
140 return;
141 }
142 if (recv_length < 0) {
143 async_answer_0(callid, EINVAL);
144 return;
145 }
146 user->socket_buffer_len = recv_length;
147 user->socket_buffer_pos = 0;
148 }
149 char data = user->socket_buffer[user->socket_buffer_pos++];
150 if (data == 13) {
151 data = 10;
152 }
153 if (data == 0)
154 goto retry;
155
156 kbd_event_t *down = new_kbd_event(KEY_PRESS, data);
157 kbd_event_t *up = new_kbd_event(KEY_RELEASE, data);
158 assert(down);
159 assert(up);
160 prodcons_produce(&user->in_events, &down->link);
161 prodcons_produce(&user->in_events, &up->link);
162 }
163
164
165 link_t *link = prodcons_consume(&user->in_events);
166 kbd_event_t *event = list_get_instance(link, kbd_event_t, link);
167 async_answer_4(callid, EOK, event->type, event->key, event->mods, event->c);
168 free(event);
169 break;
170 }
171 case CONSOLE_GOTO:
172 async_answer_0(callid, ENOTSUP);
173 break;
174 case VFS_OUT_READ:
175 async_answer_0(callid, ENOTSUP);
176 break;
177 case VFS_OUT_WRITE: {
178 char *buf;
179 char *buf_converted;
180 size_t size;
181 int rc = async_data_write_accept((void **)&buf, false, 0, 0, 0, &size);
182
183 if (rc != EOK) {
184 async_answer_0(callid, rc);
185 break;
186 }
187 buf_converted = malloc(2 * size);
188 assert(buf_converted);
189 int buf_converted_size = 0;
190 /* Convert new-lines. */
191 for (size_t i = 0; i < size; i++) {
192 if (buf[i] == 10) {
193 buf_converted[buf_converted_size++] = 13;
194 buf_converted[buf_converted_size++] = 10;
195 } else {
196 buf_converted[buf_converted_size++] = buf[i];
197 }
198 }
199 rc = send(user->socket, buf_converted, buf_converted_size, 0);
200 free(buf);
201
202 if (rc != EOK) {
203 async_answer_0(callid, rc);
204 break;
205 }
206
207 async_answer_1(callid, EOK, size);
208
209 break;
210 }
211 case VFS_OUT_SYNC:
212 async_answer_0(callid, EOK);
213 break;
214 case CONSOLE_CLEAR:
215 async_answer_0(callid, EOK);
216 break;
217
218 case CONSOLE_GET_COLOR_CAP:
219 async_answer_1(callid, EOK, CONSOLE_CAP_NONE);
220 break;
221 case CONSOLE_SET_STYLE:
222 async_answer_0(callid, ENOTSUP);
223 break;
224 case CONSOLE_SET_COLOR:
225 async_answer_0(callid, ENOTSUP);
226 break;
227 case CONSOLE_SET_RGB_COLOR:
228 async_answer_0(callid, ENOTSUP);
229 break;
230
231 case CONSOLE_CURSOR_VISIBILITY:
232 async_answer_0(callid, ENOTSUP);
233 break;
234
235 default:
236 async_answer_0(callid, EINVAL);
237 break;
238 }
239 }
240}
241
242/** Callback when client connects to a telnet terminal. */
243static void client_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
244{
245 /* Find the user. */
246 telnet_user_t *user = telnet_user_get_for_client_connection(IPC_GET_ARG1(*icall));
247 if (user == NULL) {
248 async_answer_0(iid, ENOENT);
249 return;
250 }
251
252 telnet_user_log(user, "New client connected (%" PRIxn").", iid);
253
254 /* Accept the connection, increment reference. */
255 async_answer_0(iid, EOK);
256
257 /* Force character mode. */
258 send(user->socket, (void *)telnet_force_character_mode_command,
259 telnet_force_character_mode_command_count, 0);
260
261 client_connection_message_loop(user);
262
263 /* Announce user disconnection. */
264 telnet_user_notify_client_disconnected(user);
265 telnet_user_log(user, "Client disconnected (%" PRIxn").", iid);
266}
267
268/** Fibril for spawning the task running after user connects.
269 *
270 * @param arg Corresponding @c telnet_user_t structure.
271 */
272static int spawn_task_fibril(void *arg)
273{
274 telnet_user_t *user = arg;
275 int rc;
276
277 char term[LOC_NAME_MAXLEN];
278 snprintf(term, LOC_NAME_MAXLEN, "%s/%s", "/loc", user->service_name);
279
280 task_id_t task;
281 rc = task_spawnl(&task, APP_GETTERM, APP_GETTERM, term, "/app/bdsh", NULL);
282 if (rc != EOK) {
283 telnet_user_error(user, "Spawning %s %s %s failed: %s.",
284 APP_GETTERM, term, "/app/bdsh", str_error(rc));
285 fibril_mutex_lock(&user->refcount_mutex);
286 user->task_finished = true;
287 fibril_condvar_signal(&user->refcount_cv);
288 fibril_mutex_unlock(&user->refcount_mutex);
289 return EOK;
290 }
291
292 fibril_mutex_lock(&user->refcount_mutex);
293 user->task_id = task;
294 fibril_mutex_unlock(&user->refcount_mutex);
295
296 task_exit_t task_exit;
297 int task_retval;
298 task_wait(task, &task_exit, &task_retval);
299 telnet_user_log(user, "%s terminated %s, exit code %d.", APP_GETTERM,
300 task_exit == TASK_EXIT_NORMAL ? "normally" : "unexpectedly",
301 task_retval);
302
303 /* Announce destruction. */
304 fibril_mutex_lock(&user->refcount_mutex);
305 user->task_finished = true;
306 fibril_condvar_signal(&user->refcount_cv);
307 fibril_mutex_unlock(&user->refcount_mutex);
308
309 return EOK;
310}
311
312/** Tell whether given user can be destroyed (has no active clients).
313 *
314 * @param user The telnet user in question.
315 */
316static bool user_can_be_destroyed_no_lock(telnet_user_t *user)
317{
318 return user->task_finished && user->socket_closed &&
319 (user->locsrv_connection_count == 0);
320}
321
322/** Fibril for each accepted socket.
323 *
324 * @param arg Corresponding @c telnet_user_t structure.
325 */
326static int network_user_fibril(void *arg)
327{
328 int rc;
329 telnet_user_t *user = arg;
330
331 rc = loc_service_register(user->service_name, &user->service_id);
332 if (rc != EOK) {
333 telnet_user_error(user, "Unable to register %s with loc: %s.",
334 user->service_name, str_error(rc));
335 return EOK;
336 }
337
338 telnet_user_log(user, "Service %s registerd with id %" PRIun ".",
339 user->service_name, user->service_id);
340
341 fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
342 assert(spawn_fibril);
343 fibril_add_ready(spawn_fibril);
344
345 /* Wait for all clients to exit. */
346 fibril_mutex_lock(&user->refcount_mutex);
347 while (!user_can_be_destroyed_no_lock(user)) {
348 if (user->task_finished) {
349 closesocket(user->socket);
350 user->socket_closed = true;
351 continue;
352 } else if (user->socket_closed) {
353 if (user->task_id != 0) {
354 task_kill(user->task_id);
355 }
356 }
357 fibril_condvar_wait_timeout(&user->refcount_cv, &user->refcount_mutex, 1000);
358 }
359 fibril_mutex_unlock(&user->refcount_mutex);
360
361 rc = loc_service_unregister(user->service_id);
362 if (rc != EOK) {
363 telnet_user_error(user,
364 "Unable to unregister %s from loc: %s (ignored).",
365 user->service_name, str_error(rc));
366 }
367
368 telnet_user_log(user, "Destroying...");
369 telnet_user_destroy(user);
370
371 return EOK;
372}
373
374int main(int argc, char *argv[])
375{
376 int port = 2223;
377
378 int rc = loc_server_register(NAME, client_connection);
379 if (rc < 0) {
380 fprintf(stderr, NAME ": Unable to register server: %s.\n",
381 str_error(rc));
382 return 1;
383 }
384
385 struct sockaddr_in addr;
386
387 addr.sin_family = AF_INET;
388 addr.sin_port = htons(port);
389
390 rc = inet_pton(AF_INET, "127.0.0.1", (void *)
391 &addr.sin_addr.s_addr);
392 if (rc != EOK) {
393 fprintf(stderr, "Error parsing network address: %s.\n",
394 str_error(rc));
395 return 2;
396 }
397
398 int listen_sd = socket(PF_INET, SOCK_STREAM, 0);
399 if (listen_sd < 0) {
400 fprintf(stderr, "Error creating listening socket: %s.\n",
401 str_error(listen_sd));
402 return 3;
403 }
404
405 rc = bind(listen_sd, (struct sockaddr *) &addr, sizeof(addr));
406 if (rc != EOK) {
407 fprintf(stderr, "Error binding socket: %s.\n",
408 str_error(rc));
409 return 4;
410 }
411
412 rc = listen(listen_sd, BACKLOG_SIZE);
413 if (rc != EOK) {
414 fprintf(stderr, "listen() failed: %s.\n", str_error(rc));
415 return 5;
416 }
417
418 printf("%s: HelenOS Remote console service\n", NAME);
419
420 while (true) {
421 struct sockaddr_in raddr;
422 socklen_t raddr_len = sizeof(raddr);
423 int conn_sd = accept(listen_sd, (struct sockaddr *) &raddr,
424 &raddr_len);
425
426 if (conn_sd < 0) {
427 fprintf(stderr, "accept() failed: %s.\n",
428 str_error(rc));
429 continue;
430 }
431
432 telnet_user_t *user = telnet_user_create(conn_sd);
433 assert(user);
434
435 fid_t fid = fibril_create(network_user_fibril, user);
436 assert(fid);
437 fibril_add_ready(fid);
438 }
439
440 return 0;
441}
442
443/** @}
444 */
Note: See TracBrowser for help on using the repository browser.