root/src/plugins/console.c @ 6

Revision 1, 12.6 kB (checked in by jinshiro, 17 years ago)
Line 
1// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
2// For more information, see LICENCE in the main folder
3
4#include "../common/plugin.h"
5
6#ifdef WIN32
7        #define WIN32_LEAN_AND_MEAN
8        #include <windows.h>
9#else
10        #define __USE_XOPEN
11        #include <sys/types.h>
12        #include <unistd.h>
13        #include <poll.h>
14        #include <string.h>
15#endif
16#include <stdio.h> // stdin, fgets
17
18#define INPUT_BUFSIZE 4096
19#define INPUT_INVALID 0
20#define INPUT_READY   1
21#define INPUT_WAITING 2
22#define INPUT_READING 3
23#define INPUT_CLOSED  4
24
25//////////////////////////////
26#ifdef WIN32
27//////////////////////////////
28
29// In windows the worker is a thread so it can access the same variables.
30#define WORKER_FUNC_DECLARE(name) DWORD WINAPI worker_ ## name(LPVOID lpParameter)
31#define WORKER_FUNC_START(name) DWORD WINAPI worker_ ## name(LPVOID lpParameter) { (void)lpParameter; {
32#define WORKER_FUNC_END(name) } ExitThread(0); return 0; }
33#define WORKER_EXECUTE(name,errvar) \
34        do{ \
35                buf.worker = CreateThread(NULL, 0, worker_ ## name, NULL, CREATE_SUSPENDED, NULL); \
36                if( errvar ) \
37                        *errvar = ( buf.worker == NULL ); \
38        }while(0)
39
40/// Buffer for asynchronous input
41typedef struct _buffer {
42        char arr[INPUT_BUFSIZE];
43        size_t len;
44        HANDLE worker;
45        HANDLE state_mux; // mutex for the state
46        char state;
47} BUFFER;
48
49//////////////////////////////
50#else
51//////////////////////////////
52
53/// In linux the worker is a process so it needs to comunicate through pipes.
54#define WORKER_FUNC_DECLARE(name) void worker_ ## name(void)
55#define WORKER_FUNC_START(name) void worker_ ## name(void) {
56#define WORKER_FUNC_END(name) _exit(0); }
57#define WORKER_EXECUTE(name,errvar) \
58        do{ \
59                int pid = fork(); \
60                if( pid == 0 ){ \
61                        worker_ ## name(); \
62                } \
63                if( errvar ) \
64                        *errvar = (pid == -1); \
65        }while(0)
66
67#define PIPE_READ 0
68#define PIPE_WRITE 1
69
70/// Buffer for asynchronous input
71typedef struct _buffer {
72        char arr[INPUT_BUFSIZE];
73        size_t len;
74        int data_pipe[2]; // pipe to receive data
75        int state_pipe[2]; // pipe to send state
76        char state;
77        unsigned close_unused_flag : 1;
78} BUFFER;
79
80//////////////////////////////
81#endif
82//////////////////////////////
83
84
85
86
87
88////// Plugin information ////////
89
90
91
92
93
94PLUGIN_INFO = {
95        "Console", // Name
96        PLUGIN_ALL, // Target servers
97        "0.1", // Version
98        "1.03", // Minimum plugin engine version to run
99        "Console parser" // Short description
100};
101
102////// Plugin event list //////////
103// Format: <plugin function>,<event name>
104// All registered functions to a event gets executed
105// (In descending order) when its called.
106// Multiple functions can be called by multiple events too,
107// So it's up to your creativity ^^
108//
109PLUGIN_EVENTS_TABLE = {
110        { "console_init",      EVENT_PLUGIN_INIT },
111        { "console_final",     EVENT_PLUGIN_FINAL },
112        { "console_autostart", EVENT_ATHENA_INIT },
113        //{ "console_start",     EVENT_CONSOLE_START },//## add these events to the plugins framework
114        //{ "console_stop",      EVENT_CONSOLE_STOP },
115        { "console_stop",      EVENT_ATHENA_FINAL },
116        { NULL, NULL }
117};
118
119
120
121
122
123///// Variables /////
124
125
126
127
128
129// Imported functions
130typedef int (*TimerFunc)(int tid, unsigned int tick, int id, intptr data);
131int (*add_timer_func_list)(TimerFunc func, char* name);
132int (*add_timer_interval)(unsigned int tick, TimerFunc func, int id, intptr data, int interval);
133int (*delete_timer)(int tid, TimerFunc func);
134unsigned int (*gettick)(void);
135int (*parse_console)(char* buf);
136
137// Locals
138int tid; // timer id
139BUFFER buf; // input buffer
140WORKER_FUNC_DECLARE(getinput); // worker for the input buffer
141
142
143
144
145
146//////// Asynchronous input functions //////////
147
148
149
150
151
152//////////////////////////////
153#ifdef WIN32
154//////////////////////////////
155//
156// --=== Asynchronous console input ===--
157//
158// On windows a thread is used (both threads have access to the same data).
159// The worker threads starts suspended and is resumed when data is required.
160// After getting the data, the worker thread updates the state variable and
161// suspends itself.
162//
163// A mutex is used to synchronize access to the state variable between the
164// threads. Access and updates to state are probably already atomic so the
165// mutex shouldn't be needed, but using it is more correct so it stays.
166//
167// Note: The Worker thread only starts to get input data when further data is
168//    requested. This is a design choise and brings no real advantage or
169//    disadvantage I can think of.
170//
171
172/// Returns the state of the input
173char input_getstate()
174{
175        char state;
176
177        WaitForSingleObject(buf.state_mux, INFINITE);
178        state = buf.state;
179        ReleaseMutex(buf.state_mux);
180
181        return state;
182}
183
184/// Sets the state of the input
185void input_setstate(char state)
186{
187        char oldstate;
188
189        // update state
190        WaitForSingleObject(buf.state_mux, INFINITE);
191        oldstate = buf.state;
192        buf.state = state;
193        ReleaseMutex(buf.state_mux);
194
195        if( state == INPUT_READY && oldstate == INPUT_READING )
196        {// data has become available
197                SuspendThread(buf.worker);
198        } else if( state == INPUT_WAITING )
199        {// input is waiting for data
200                ResumeThread(buf.worker);
201        //} else if( state == INPUT_READING )
202        //{// worker is reading data
203        } else if( state == INPUT_CLOSED )
204        {// end the input
205                CloseHandle(buf.state_mux);
206                TerminateThread(buf.worker, 0);
207        }
208}
209
210/// Gets the next state of the input
211#define input_nextstate() input_getstate()
212
213/// Returns if data is available from asynchronous input.
214/// Requests data if none is available.
215int input_hasdata(void)
216{
217        if( input_getstate() == INPUT_READY )
218        {// buffer is ready
219                if( buf.len > 0 )
220                        return 1; // data found ;)
221                // request data from the worker
222                input_setstate(INPUT_WAITING);
223        }
224        return 0; // no data
225}
226
227/// Initialize asynchronous input
228int input_init(void)
229{
230        int err = 0;
231
232        memset(&buf, 0, sizeof(buf));
233        buf.state_mux = CreateMutex(NULL, FALSE, NULL);
234        if( buf.state_mux == NULL )
235        {// failed to create state mutex
236                return 1;
237        }
238        buf.len = 0;
239        input_setstate(INPUT_READY);
240        WORKER_EXECUTE(getinput, &err);
241        if( err )
242        {// failed to start worker
243                input_setstate(INPUT_CLOSED);
244        }
245
246        return err;
247}
248
249/// Finalize asynchronous input
250int input_final(void)
251{
252        input_setstate(INPUT_CLOSED);
253        return 0;
254}
255
256//////////////////////////////
257#else
258//////////////////////////////
259//
260// --=== Asynchronous console input ===--
261//
262// On the other systems a process is used and pipes are used to comunicate.
263// The worker process receives status updates through one of the pipes either
264// requesting data or ending the worker.
265// The other pipe is used by the worker to send the input data and is checked
266// for data by the main thread in the timer function.
267//
268// Note: The Worker thread only starts to get input data when further data is
269//    requested. This is a design choise and brings no real advantage or
270//    disadvantage I can think of.
271//
272
273/// Returns the state of the input
274#define input_getstate() buf.state
275
276/// Sets the state of the input
277void input_setstate(char state)
278{
279        if( state == INPUT_READY && input_getstate() == INPUT_READING )
280        {// send data from the worker to the main process
281                write(buf.data_pipe[PIPE_WRITE], &buf.len, sizeof(buf.len));           
282                write(buf.data_pipe[PIPE_WRITE], &buf.arr, buf.len);
283        } else if( state == INPUT_WAITING ){
284                if( buf.close_unused_flag == 0 )
285                {// close unused pipe sides in the main process
286                        close(buf.data_pipe[PIPE_WRITE]);
287                        close(buf.state_pipe[PIPE_READ]);
288                        buf.close_unused_flag = 1;
289                }
290                // send the next state
291                write(buf.state_pipe[PIPE_WRITE], &state, sizeof(state));
292        } else if( state == INPUT_READING ){
293                if( buf.close_unused_flag == 0 )
294                {// close unused pipe sides in the worker process
295                        close(buf.data_pipe[PIPE_READ]);
296                        close(buf.state_pipe[PIPE_WRITE]);
297                        buf.close_unused_flag = 1;
298                }
299        } else if( state == INPUT_CLOSED )
300        {// send next state to the worker and close the pipes
301                write(buf.state_pipe[PIPE_WRITE], &state, sizeof(state));
302                close(buf.data_pipe[PIPE_WRITE]);
303                close(buf.data_pipe[PIPE_READ]);
304                close(buf.state_pipe[PIPE_WRITE]);
305                close(buf.state_pipe[PIPE_READ]);
306        }
307        buf.state = state;
308}
309
310/// Waits for the next state of the input
311char input_nextstate()
312{
313        char state = INPUT_CLOSED;
314        int bytes = 0;
315
316        while( bytes == 0 )
317                bytes = read(buf.state_pipe[PIPE_READ], &state, sizeof(state));
318        if( bytes == -1 )
319        {// error, terminate worker
320                input_setstate(INPUT_CLOSED);
321        }
322        return state;
323}
324
325/// Returns if data is available from asynchronous input.
326/// If data is available, it's put in the local buffer.
327int input_hasdata()
328{
329        struct pollfd fds;
330        int hasData;
331
332        if( input_getstate() == INPUT_READY )
333        {// start getting data
334                input_setstate(INPUT_WAITING);
335                return 0;
336        }
337        // check if data is available
338        fds.fd = buf.data_pipe[PIPE_READ];
339        fds.events = POLLRDNORM;
340        hasData = ( poll(&fds,1,0) > 0 );
341        if( hasData )
342        {// read the data from the pipe
343                read(buf.data_pipe[PIPE_READ], &buf.len, sizeof(buf.len));
344                read(buf.data_pipe[PIPE_READ], buf.arr, buf.len);
345                input_setstate(INPUT_READY);
346        }
347
348        return hasData;
349}
350
351/// Initialize asynchronous input
352int input_init(void)
353{
354        int err = 0;
355
356        memset(&buf, 0, sizeof(buf));
357        if( pipe(buf.data_pipe) )
358        {// error creating data pipe
359                return 1;
360        }
361        if( pipe(buf.state_pipe) )
362        {// error creating state pipe
363                close(buf.data_pipe[PIPE_READ]);
364                close(buf.data_pipe[PIPE_WRITE]);
365                return 1;
366        }
367        buf.len = 0;
368        input_setstate(INPUT_READY);
369        WORKER_EXECUTE(getinput, &err);
370        if( err ){
371                //printf("input_init failed to start worker (%d)\n", err);
372                input_setstate(INPUT_CLOSED);
373        }
374
375        return err;
376}
377
378/// Finalize asynchronous input
379int input_final(void)
380{
381        close(buf.data_pipe[PIPE_READ]);
382        close(buf.data_pipe[PIPE_WRITE]);
383        close(buf.state_pipe[PIPE_READ]);
384        close(buf.state_pipe[PIPE_WRITE]);
385        return 0;
386}
387
388//////////////////////////////
389#endif
390//////////////////////////////
391
392
393
394/// Returns the input data array
395#define input_getdata() buf.arr
396
397/// Returns the input data length
398#define input_getlen() buf.len
399
400/// Clear the input data
401#define input_clear() ( buf.len = 0 )
402
403/// Worker thread/process that gets input
404WORKER_FUNC_START(getinput)
405        while( input_nextstate() != INPUT_CLOSED )
406        {// get input
407                input_setstate(INPUT_READING);
408                buf.arr[0] = '\0';
409                fgets(buf.arr, INPUT_BUFSIZE, stdin);
410                buf.len = strlen(buf.arr);
411                input_setstate(INPUT_READY);
412        }
413WORKER_FUNC_END(getinput)
414
415
416
417
418
419//////// Plugin console functions //////////
420
421
422
423
424
425/// Timer function that checks if there's assynchronous input data and feeds parse_console()
426/// The input reads one line at a time and line terminators are removed.
427int console_getinputtimer(int tid, unsigned int tick, int id, intptr data)
428{
429        char* cmd;
430        size_t len;
431
432        if( input_hasdata() ){
433
434                // get data (removes line terminators)
435                cmd = input_getdata();
436                len = input_getlen();
437                while( len > 0 && (cmd[len-1] == '\r' || cmd[len-1] == '\n') )
438                        cmd[--len] = '\0';
439
440                // parse data
441                parse_console(cmd);
442                input_clear();
443        }
444
445        return 0;
446}
447
448/// Start the console
449void console_start(void)
450{
451        if( input_init() ){
452                return;
453        }
454        //##TODO add a 'startupcmd' config options
455        //parse_console("help");
456        add_timer_func_list(console_getinputtimer,"console_getinputtimer");
457        tid = add_timer_interval(gettick(),console_getinputtimer,0,0,250);//##TODO add a 'timerperiod' config option
458}
459
460void console_autostart(void)
461{//##TODO add an 'autostart' config option
462        console_start();
463}
464
465/// Stop the console
466void console_stop(void)
467{
468        if( tid != -1 ){
469                delete_timer(tid, console_getinputtimer);
470                input_final();
471        }
472        return;
473}
474
475/// Test the console for compatibility
476int console_test(void)
477{// always compatible at the moment, maybe test if standard input is available?
478        return 1;
479}
480
481
482/// Initialize the console
483void console_init(void)
484{
485        // import symbols
486        IMPORT_SYMBOL(add_timer_interval, SYMBOL_ADD_TIMER_INTERVAL);
487        IMPORT_SYMBOL(add_timer_func_list, SYMBOL_ADD_TIMER_FUNC_LIST);
488        IMPORT_SYMBOL(delete_timer, SYMBOL_DELETE_TIMER);
489        IMPORT_SYMBOL(gettick, SYMBOL_GETTICK);
490        IMPORT_SYMBOL(parse_console, SYMBOL_PARSE_CONSOLE);
491        //printf("%d -> add_timer_func_list=0x%x\n", SYMBOL_ADD_TIMER_FUNC_LIST, (int)add_timer_func_list);
492        //printf("%d -> add_timer_interval=0x%x\n", SYMBOL_ADD_TIMER_INTERVAL, (int)add_timer_interval);
493        //printf("%d -> delete_timer=0x%x\n", SYMBOL_DELETE_TIMER, (int)delete_timer);
494        //printf("%d -> gettick=0x%x\n", SYMBOL_GETTICK, (int)gettick);
495        //printf("%d -> parse_console=0x%x\n", SYMBOL_PARSE_CONSOLE, (int)parse_console);
496}
497
498/// Finalize the console
499void console_final(void)
500{
501}
Note: See TracBrowser for help on using the browser.