Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
Console.cxx
Go to the documentation of this file.
1
34#ifndef _POSIX_C_SOURCE
35#define _POSIX_C_SOURCE 200112L
36#endif
37
38#include "console/Console.hxx"
39
40#if defined (CONSOLE_NETWORKING)
41#include <netinet/tcp.h>
42#include <sys/socket.h>
43#include <arpa/inet.h>
44#endif
45
46#include <sys/stat.h>
47#include <unistd.h>
48#include <fcntl.h>
49#include <cstdio>
50
51/*
52 * Console::Console()
53 */
54Console::Console(ExecutorBase *executor, uint16_t port)
55 : Service(executor)
56 , help("help", help_command, this, &helpMark)
57 , helpMark("?", help_command, this)
58#if defined (CONSOLE_NETWORKING)
59 , listen(this, port)
60#endif
61{
62 add_command("quit", quit_command, this);
63}
64
65/*
66 * Console::Console()
67 */
68Console::Console(ExecutorBase *executor, int fd_in, int fd_out, int port)
69 : Service(executor)
70 , help("help", help_command, this, &helpMark)
71 , helpMark("?", help_command, this)
72#if defined (CONSOLE_NETWORKING)
73 , listen(this, port)
74#endif
75{
76 open_session(fd_in, fd_out);
77 add_command("quit", quit_command, this);
78}
79
80/*
81 * Console::open_session()
82 */
83void Console::open_session(int fd_in, int fd_out)
84{
85#if OPENMRN_FEATURE_BSD_SOCKETS
86 fcntl(fd_in, F_SETFL, fcntl(fd_in, F_GETFL, 0) | O_NONBLOCK);
87#endif
88 new Session(this, fd_in, fd_out);
89}
90
91/*
92 * Console::add_command()
93 */
94void Console::add_command(const char *name, Callback callback, void *context)
95{
96 Command *current = &helpMark;
97
98 /* seek to the end of the list */
99 while (current->next)
100 {
101 current = current->next;
102 }
103
105 current->next = new Command(name, callback, context);
106}
107
108/*
109 * Console::help_command()
110 */
111Console::CommandStatus Console::help_command(FILE *fp, int argc, const char *argv[])
112{
113 fprintf(fp, "%10s : print out this help menu\n", "help | ?");
114
115 /* call each of the commands with argc = 0 */
116 for (Command *current = helpMark.next; current; current = current->next)
117 {
118 fprintf(fp, "%10s : ", current->name);
119 const char *argv[2] = {current->name, " "};
120 (*current->callback)(fp, 0, argv, NULL);
121 }
122
123 return COMMAND_OK;
124}
125
126/*
127 * Console::quit_command()
128 */
129Console::CommandStatus Console::quit_command(FILE *fp, int argc, const char *argv[])
130{
131 switch (argc)
132 {
133 case 0:
134 fprintf(fp, "terminate the current login session, only\n%s"
135 "has an effect on socket based logins sessions\n",
136 argv[1]);
137 return COMMAND_OK;
138 case 1:
139 return COMMAND_CLOSE;
140 default:
141 return COMMAND_ERROR;
142 }
143}
144
145/*
146 * Console::CommandFlow::CommandFlow()
147 */
149 : StateFlowBase(console)
150{
151 Command *current = &console->helpMark;
152
153 /* seek to the end of the list */
154 while (current->next)
155 {
156 current = current->next;
157 }
158
160 command = new Command(name, this);
161 current->next = command;
162}
163
164/*
165 * Console::CommandFlow::CommandFlow()
166 */
168{
169 Console *console = static_cast<Console *>(service());
170 Command *current = &console->helpMark;
171
172 /* seek to just before our command instance */
173 while (current)
174 {
175 if (current->next == command)
176 {
177 /* remove and our command from the list and delete instance */
178 current->next = command->next;
179 delete command;
180 break;
181 }
182 current = current->next;
183 }
184}
185
186#if defined (CONSOLE_NETWORKING)
187/*
188 * Console::Listen::Listen()
189 */
190Console::Listen::Listen(Service *service, int port)
191 : StateFlowBase(service)
192 , fdListen(-1)
193 , selectHelper(this)
194{
195 if (port < 0)
196 {
197 /* invalid port number, this class will do nothing */
198 return;
199 }
200
201 int yes = 1;
202 struct sockaddr_in sockaddr;
203 int result;
204
205 memset(&sockaddr, 0, sizeof(sockaddr));
206 sockaddr.sin_family = AF_INET;
207 sockaddr.sin_addr.s_addr = INADDR_ANY;
208 sockaddr.sin_port = htons(port);
209
210 /* open TCP socket */
212 HASSERT(fdListen >= 0);
213
214 /* reuse socket address if already in lingering use */
215 result = setsockopt(fdListen, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
216 HASSERT(result == 0);
217
218 /* turn off the nagel alogrithm */
219 result = setsockopt(fdListen, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(int));
220 HASSERT(result == 0);
221
222 /* bind the address parameters to the socket */
223 result = bind(fdListen, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
224 HASSERT(result == 0);
225
226 /* set socket non-blocking */
227 result = fcntl(fdListen, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK);
228 HASSERT(result == 0);
229
230 /* mark socket as listen */
231 result = ::listen(fdListen, 10);
232 HASSERT(result == 0);
233
234 /* start state flow */
236}
237
238/*
239 * Console::Listen::accept()
240 */
241StateFlowBase::Action Console::Listen::accept()
242{
243 int newfd = ::accept(fdListen, NULL, NULL);
244 if (newfd >= 0)
245 {
246 int yes = 1;
247 int result = setsockopt(newfd, IPPROTO_TCP,
248 TCP_NODELAY, &yes, sizeof(int));
249 HASSERT(result == 0);
250 static_cast<Console *>(service())->open_session(newfd, newfd);
251 }
252
253 return listen_and_call(&selectHelper, fdListen, STATE(accept));
254}
255#endif
256
257/*
258 * Console::Session::Session()
259 */
260Console::Session::Session(Service *service, int fd_in, int fd_out)
261 : StateFlowBase(service)
262 , fdIn(fd_in)
263 , fdOut(fd_out)
264 , fp(fdopen(fd_out, "w"))
265 , line((char*)malloc(64))
266 , line_size(64)
267 , pos(0)
268 , selectHelper(this)
269 , command(nullptr)
270{
271 prompt(fp);
273}
274
275/*
276 * Console::Session::process_read()
277 */
279{
280 size_t count = (line_size - pos) - selectHelper.remaining_;
281
282 if (count == 0)
283 {
284 struct stat stat;
285 fstat(fdIn, &stat);
286#if OPENMRN_FEATURE_BSD_SOCKETS
287 if (S_ISSOCK(stat.st_mode))
288 {
289 /* Socket connection closed */
290 return delete_this();
291 }
292#endif
293 }
294
295 while(count--)
296 {
297 if (line[pos++] == '\n')
298 {
299 /* parse the line input into individual arguments */
300 unsigned argc = 0;
301 char last = '\0';
302
303 for (size_t i = 0; i < pos; ++i)
304 {
305 switch (line[i])
306 {
307 case '\r':
308 case '\n':
309 case ' ':
310 line[i] = '\0';
311 break;
312 case '"':
314 default:
315 if (last == '\0')
316 {
317 args[argc] = &line[i];
318 argc = (argc == MAX_ARGS) ? MAX_ARGS : argc + 1;
319 }
320 break;
321 }
322 last = line[i];
323 }
324
325 if (command != nullptr)
326 {
327 command->flow->notify();
328 command->flow->argc = argc;
329 command->flow->argv = args;
330 //printf("%s", args[0]);
331 return wait_and_call(STATE(exit_interactive));
332 }
333
334 switch (argc)
335 {
336 case 0:
337 break;
338 case MAX_ARGS:
339 fprintf(fp, "too many arguments\n");
340 break;
341 default:
342 {
343 CommandStatus status = callback(argc, args);
344 if (status == COMMAND_NEXT)
345 {
346 return wait_and_call(STATE(exit_interactive));
347 }
348 else
349 {
350 if (callback_result_process(status, args[0]) == false)
351 {
352 return delete_this();
353 }
354 }
355 }
356 }
357 pos = 0;
358 prompt(fp);
359 }
360 else
361 {
362 if (pos >= line_size)
363 {
364 /* double the line buffer size */
365 line_size *= 2;
366 char *new_line = (char*)malloc(line_size);
367 memcpy(new_line, line, pos);
368 free(line);
369 line = new_line;
370 }
371 }
372 }
373 return call_immediately(STATE(entry));
374}
375
376/*
377 * Console::Session::Callback()
378 */
380{
381 Console *console = static_cast<Console *>(service());
382
383 /* run through each command */
384 for (Command *current = &console->help; current; current = current->next)
385 {
386 /* look for a command match */
387 if (strcmp(current->name, argv[0]) == 0)
388 {
389 /* found a match, call the registered callback */
390 if (current->interactive)
391 {
392 command = current;
393 return current->flow->callback(this, fdIn, fp, argc, argv);
394 }
395 else
396 {
397 return (*current->callback)(fp, argc, argv, current->context);
398 }
399 }
400 }
401
402 return COMMAND_NOT_FOUND;
403}
404
405/*
406 * Console::Session::exit_interactive()
407 */
409{
410 if (command->flow->status == COMMAND_NEXT)
411 {
412 pos = 0;
413 return call_immediately(STATE(entry));
414 }
415 HASSERT(callback_result_process(command->flow->status, command->name) == true);
416
417 pos = 0;
418 prompt(fp);
419
420 command = nullptr;
421
422 return call_immediately(STATE(entry));
423}
424
425/*
426 * Console::Session::callback_result_process()
427 */
429 const char *name)
430{
431 switch (status)
432 {
433 default:
434 break;
435 case COMMAND_ERROR:
436 fprintf(fp, "invalid arguments\n");
437 break;
438 case COMMAND_CLOSE:
439 {
440 struct stat stat;
441 fstat(fdIn, &stat);
442#if OPENMRN_FEATURE_BSD_SOCKETS
443 if (S_ISSOCK(stat.st_mode))
444 {
445 fprintf(fp, "shutting down session\n");
446 return false;
447 }
448#endif
449 fprintf(fp, "session not a socket, "
450 "aborting session shutdown\n");
451 break;
452 }
454 fprintf(fp, "%s: command not found\n", name);
455 }
456
457 return true;
458}
int fcntl(int fd, int cmd,...)
Manipulate a file descriptor.
Definition Fileio.cxx:494
int accept(int socket, struct sockaddr *address, socklen_t *address_len)
Accept a new connection on a socket.
Definition Socket.cxx:194
int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len)
Set the socket options.
Definition Socket.cxx:255
int bind(int socket, const struct sockaddr *address, socklen_t address_len)
Bind a name to a socket.
Definition Socket.cxx:159
int listen(int socket, int backlog)
Mark a connection-mode socket, specified by the socket argument, as accepting connections.
Definition Socket.cxx:174
int socket(int domain, int type, int protocol)
Create an unbound socket in a communications domain.
Definition Socket.cxx:144
#define STATE(_fn)
Turns a function name into an argument to be supplied to functions expecting a state.
Definition StateFlow.hxx:61
~CommandFlow()
Destructor.
Definition Console.cxx:167
Command * command
Keep track of Command instance for destruction time.
Definition Console.hxx:245
virtual StateFlowBase::Action entry()=0
Entry point to command flow.
CommandFlow(Console *console, const char *name)
Constructor.
Definition Console.cxx:148
Console session metadata.
Definition Console.hxx:354
StateFlowBase::Action entry()
Entry point to the state machine.
Definition Console.hxx:382
StateFlowBase::Action exit_interactive()
Wait for completion of an interactive command in order to cleanup based on result.
Definition Console.cxx:408
void prompt(FILE *fp)
Print the standard prompt.
Definition Console.hxx:410
CommandStatus callback(int argc, const char *argv[])
Process a potential callback for a given command.
Definition Console.cxx:379
FILE * fp
file pointer of session
Definition Console.hxx:425
Session(Service *service, int fd_in, int fd_out)
Constructor.
Definition Console.cxx:260
StateFlowBase::Action process_read()
Process the incoming command line input.
Definition Console.cxx:278
bool callback_result_process(CommandStatus status, const char *name)
Process the result of the command callback.
Definition Console.cxx:428
This class provides a console available from stdin/stdout as well as via telnet.
Definition Console.hxx:68
Console(ExecutorBase *executor, uint16_t port)
Constructor.
Definition Console.cxx:54
void add_command(const char *name, Callback callback, void *context=NULL)
Add a new command to the console.
Definition Console.cxx:94
CommandStatus
Enumeration of recognized command callback results.
Definition Console.hxx:80
@ COMMAND_NOT_FOUND
Command not found.
Definition Console.hxx:85
@ COMMAND_ERROR
Command had some kind of error.
Definition Console.hxx:83
@ COMMAND_NEXT
Command waiting for input.
Definition Console.hxx:82
@ COMMAND_CLOSE
Command wants to close the session.
Definition Console.hxx:84
@ COMMAND_OK
Command executed successfully.
Definition Console.hxx:81
static CommandStatus help_command(FILE *fp, int argc, const char *argv[], void *context)
Print out the help menu by calling the in context helper function.
Definition Console.hxx:447
Command help
the "help" command instance
Definition Console.hxx:487
static CommandStatus quit_command(FILE *fp, int argc, const char *argv[], void *context)
Quit out of the current login session by calling the in context helper function.
Definition Console.hxx:468
Command helpMark
the help "?" command instance
Definition Console.hxx:488
static const size_t MAX_ARGS
Maximum number of supported arguments including the command itself.
Definition Console.hxx:267
void open_session(int fd_in, int fd_out)
Open and initialize a new session.
Definition Console.cxx:83
This class implements an execution of tasks pulled off an input queue.
Definition Executor.hxx:64
Collection of related state machines that pend on incoming messages.
Return type for a state flow callback.
Base class for state machines.
void start_flow(Callback c)
Resets the flow to the specified state and starts it.
#define htons(x)
Converts a host endian short value to network endian.
Definition in.h:93
#define INADDR_ANY
Listen on all network interfaces for incoming connections.
Definition in.h:67
#define IPPROTO_TCP
TCP Raw Socket.
Definition in.h:70
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
#define SOCK_STREAM
TCP Socket.
Definition socket.h:45
#define SO_REUSEADDR
socket option to reuse address
Definition socket.h:67
#define SOL_SOCKET
socket option category
Definition socket.h:64
#define AF_INET
IPv4 Socket (UDP, TCP, etc...)
Definition socket.h:54
Console command metadata.
Definition Console.hxx:272
Command * next
next Command in list
Definition Console.hxx:312
Structure describing an Internet socket address.
Definition in.h:56
IPv4 socket address.
Definition socket.h:83
#define TCP_NODELAY
don't delay send to coalesce packets
Definition tcp.h:42