source: mainline/uspace/srv/hid/remcons/remcons.c@ d545d03

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

remcons: unify log/error messages

  • Property mode set to 100644
File size: 10.9 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
77static kbd_event_t* new_kbd_event(kbd_event_type_t type, wchar_t c) {
78 kbd_event_t *event = malloc(sizeof(kbd_event_t));
79 assert(event);
80
81 link_initialize(&event->link);
82 event->type = type;
83 event->c = c;
84 event->mods = 0;
85 event->key = (c == '\n' ? KC_ENTER : KC_A);
86
87 return event;
88}
89
90static void client_connection_message_loop(telnet_user_t *user)
91{
92 while (true) {
93 ipc_call_t call;
94 ipc_callid_t callid = 0;
95 while (callid == 0) {
96 callid = async_get_call_timeout(&call, 1000);
97
98 fibril_mutex_lock(&user->refcount_mutex);
99 bool bail_out = user->socket_closed || user->task_finished;
100 fibril_mutex_unlock(&user->refcount_mutex);
101
102 if (bail_out) {
103 if (callid != 0) {
104 async_answer_0(callid, EINTR);
105 }
106 return;
107 }
108 }
109
110 if (!IPC_GET_IMETHOD(call)) {
111 /* Clean-up. */
112 return;
113 }
114
115 switch (IPC_GET_IMETHOD(call)) {
116 case CONSOLE_GET_SIZE:
117 async_answer_2(callid, EOK, 100, 1);
118 break;
119 case CONSOLE_GET_POS:
120 async_answer_2(callid, EOK, 0, 0);
121 break;
122 case CONSOLE_GET_EVENT: {
123 if (list_empty(&user->in_events.list)) {
124 retry:
125 if (user->socket_buffer_len <= user->socket_buffer_pos) {
126 int recv_length = recv(user->socket, user->socket_buffer, BUFFER_SIZE, 0);
127 if ((recv_length == 0) || (recv_length == ENOTCONN)) {
128 fibril_mutex_lock(&user->refcount_mutex);
129 user->socket_closed = true;
130 fibril_mutex_unlock(&user->refcount_mutex);
131 async_answer_0(callid, ENOENT);
132 return;
133 }
134 if (recv_length < 0) {
135 async_answer_0(callid, EINVAL);
136 return;
137 }
138 user->socket_buffer_len = recv_length;
139 user->socket_buffer_pos = 0;
140 }
141 char data = user->socket_buffer[user->socket_buffer_pos++];
142 if (data == 13) {
143 data = 10;
144 }
145 if (data == 0)
146 goto retry;
147
148 kbd_event_t *down = new_kbd_event(KEY_PRESS, data);
149 kbd_event_t *up = new_kbd_event(KEY_RELEASE, data);
150 assert(down);
151 assert(up);
152 prodcons_produce(&user->in_events, &down->link);
153 prodcons_produce(&user->in_events, &up->link);
154 }
155
156
157 link_t *link = prodcons_consume(&user->in_events);
158 kbd_event_t *event = list_get_instance(link, kbd_event_t, link);
159 async_answer_4(callid, EOK, event->type, event->key, event->mods, event->c);
160 free(event);
161 break;
162 }
163 case CONSOLE_GOTO:
164 async_answer_0(callid, ENOTSUP);
165 break;
166 case VFS_OUT_READ:
167 async_answer_0(callid, ENOTSUP);
168 break;
169 case VFS_OUT_WRITE: {
170 char *buf;
171 char *buf_converted;
172 size_t size;
173 int rc = async_data_write_accept((void **)&buf, false, 0, 0, 0, &size);
174
175 if (rc != EOK) {
176 async_answer_0(callid, rc);
177 break;
178 }
179 buf_converted = malloc(2 * size);
180 assert(buf_converted);
181 int buf_converted_size = 0;
182 /* Convert new-lines. */
183 for (size_t i = 0; i < size; i++) {
184 if (buf[i] == 10) {
185 buf_converted[buf_converted_size++] = 13;
186 buf_converted[buf_converted_size++] = 10;
187 } else {
188 buf_converted[buf_converted_size++] = buf[i];
189 }
190 }
191 rc = send(user->socket, buf_converted, buf_converted_size, 0);
192 free(buf);
193
194 if (rc != EOK) {
195 async_answer_0(callid, rc);
196 break;
197 }
198
199 async_answer_1(callid, EOK, size);
200
201 break;
202 }
203 case VFS_OUT_SYNC:
204 async_answer_0(callid, EOK);
205 break;
206 case CONSOLE_CLEAR:
207 async_answer_0(callid, EOK);
208 break;
209
210 case CONSOLE_GET_COLOR_CAP:
211 async_answer_1(callid, EOK, CONSOLE_CAP_NONE);
212 break;
213 case CONSOLE_SET_STYLE:
214 async_answer_0(callid, ENOTSUP);
215 break;
216 case CONSOLE_SET_COLOR:
217 async_answer_0(callid, ENOTSUP);
218 break;
219 case CONSOLE_SET_RGB_COLOR:
220 async_answer_0(callid, ENOTSUP);
221 break;
222
223 case CONSOLE_CURSOR_VISIBILITY:
224 async_answer_0(callid, ENOTSUP);
225 break;
226
227 default:
228 async_answer_0(callid, EINVAL);
229 break;
230 }
231 }
232}
233
234static void client_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
235{
236 /* Find the user. */
237 telnet_user_t *user = telnet_user_get_for_client_connection(IPC_GET_ARG1(*icall));
238 if (user == NULL) {
239 async_answer_0(iid, ENOENT);
240 return;
241 }
242
243 telnet_user_log(user, "New client connected (%" PRIxn").", iid);
244
245 /* Accept the connection, increment reference. */
246 async_answer_0(iid, EOK);
247
248 /* Force character mode. */
249 send(user->socket, (void *)telnet_force_character_mode_command,
250 telnet_force_character_mode_command_count, 0);
251
252 client_connection_message_loop(user);
253
254 /* Announce user disconnection. */
255 telnet_user_notify_client_disconnected(user);
256 telnet_user_log(user, "Client disconnected (%" PRIxn").", iid);
257}
258
259static int spawn_task_fibril(void *arg)
260{
261 telnet_user_t *user = arg;
262 int rc;
263
264 char term[LOC_NAME_MAXLEN];
265 snprintf(term, LOC_NAME_MAXLEN, "%s/%s", "/loc", user->service_name);
266
267 task_id_t task;
268 rc = task_spawnl(&task, APP_GETTERM, APP_GETTERM, term, "/app/bdsh", NULL);
269 if (rc != EOK) {
270 telnet_user_error(user, "Spawning %s %s %s failed: %s.",
271 APP_GETTERM, term, "/app/bdsh", str_error(rc));
272 fibril_mutex_lock(&user->refcount_mutex);
273 user->task_finished = true;
274 fibril_condvar_signal(&user->refcount_cv);
275 fibril_mutex_unlock(&user->refcount_mutex);
276 return EOK;
277 }
278
279 fibril_mutex_lock(&user->refcount_mutex);
280 user->task_id = task;
281 fibril_mutex_unlock(&user->refcount_mutex);
282
283 task_exit_t task_exit;
284 int task_retval;
285 task_wait(task, &task_exit, &task_retval);
286 telnet_user_log(user, "%s terminated %s, exit code %d.", APP_GETTERM,
287 task_exit == TASK_EXIT_NORMAL ? "normally" : "unexpectedly",
288 task_retval);
289
290 /* Announce destruction. */
291 fibril_mutex_lock(&user->refcount_mutex);
292 user->task_finished = true;
293 fibril_condvar_signal(&user->refcount_cv);
294 fibril_mutex_unlock(&user->refcount_mutex);
295
296 return EOK;
297}
298
299static bool user_can_be_destroyed_no_lock(telnet_user_t *user)
300{
301 return user->task_finished && user->socket_closed &&
302 (user->locsrv_connection_count == 0);
303}
304
305static int network_user_fibril(void *arg)
306{
307 int rc;
308 telnet_user_t *user = arg;
309
310 rc = loc_service_register(user->service_name, &user->service_id);
311 if (rc != EOK) {
312 telnet_user_error(user, "Unable to register %s with loc: %s.",
313 user->service_name, str_error(rc));
314 return EOK;
315 }
316
317 telnet_user_log(user, "Service %s registerd with id %" PRIun ".",
318 user->service_name, user->service_id);
319
320 fid_t spawn_fibril = fibril_create(spawn_task_fibril, user);
321 assert(spawn_fibril);
322 fibril_add_ready(spawn_fibril);
323
324 /* Wait for all clients to exit. */
325 fibril_mutex_lock(&user->refcount_mutex);
326 while (!user_can_be_destroyed_no_lock(user)) {
327 if (user->task_finished) {
328 closesocket(user->socket);
329 user->socket_closed = true;
330 continue;
331 } else if (user->socket_closed) {
332 if (user->task_id != 0) {
333 task_kill(user->task_id);
334 }
335 }
336 fibril_condvar_wait_timeout(&user->refcount_cv, &user->refcount_mutex, 1000);
337 }
338 fibril_mutex_unlock(&user->refcount_mutex);
339
340 rc = loc_service_unregister(user->service_id);
341 if (rc != EOK) {
342 telnet_user_error(user,
343 "Unable to unregister %s from loc: %s (ignored).",
344 user->service_name, str_error(rc));
345 }
346
347 telnet_user_log(user, "Destroying...");
348 telnet_user_destroy(user);
349
350 return EOK;
351}
352
353int main(int argc, char *argv[])
354{
355 int port = 2223;
356
357 int rc = loc_server_register(NAME, client_connection);
358 if (rc < 0) {
359 fprintf(stderr, NAME ": Unable to register server: %s.\n",
360 str_error(rc));
361 return 1;
362 }
363
364 struct sockaddr_in addr;
365
366 addr.sin_family = AF_INET;
367 addr.sin_port = htons(port);
368
369 rc = inet_pton(AF_INET, "127.0.0.1", (void *)
370 &addr.sin_addr.s_addr);
371 if (rc != EOK) {
372 fprintf(stderr, "Error parsing network address: %s.\n",
373 str_error(rc));
374 return 2;
375 }
376
377 int listen_sd = socket(PF_INET, SOCK_STREAM, 0);
378 if (listen_sd < 0) {
379 fprintf(stderr, "Error creating listening socket: %s.\n",
380 str_error(listen_sd));
381 return 3;
382 }
383
384 rc = bind(listen_sd, (struct sockaddr *) &addr, sizeof(addr));
385 if (rc != EOK) {
386 fprintf(stderr, "Error binding socket: %s.\n",
387 str_error(rc));
388 return 4;
389 }
390
391 rc = listen(listen_sd, BACKLOG_SIZE);
392 if (rc != EOK) {
393 fprintf(stderr, "listen() failed: %s.\n", str_error(rc));
394 return 5;
395 }
396
397 printf("%s: HelenOS Remote console service\n", NAME);
398
399 while (true) {
400 struct sockaddr_in raddr;
401 socklen_t raddr_len = sizeof(raddr);
402 int conn_sd = accept(listen_sd, (struct sockaddr *) &raddr,
403 &raddr_len);
404
405 if (conn_sd < 0) {
406 fprintf(stderr, "accept() failed: %s.\n",
407 str_error(rc));
408 continue;
409 }
410
411 telnet_user_t *user = telnet_user_create(conn_sd);
412 assert(user);
413
414 fid_t fid = fibril_create(network_user_fibril, user);
415 assert(fid);
416 fibril_add_ready(fid);
417 }
418
419 return 0;
420}
421
422/** @}
423 */
Note: See TracBrowser for help on using the repository browser.