blc_command.cpp 13.2 KB
Newer Older
Arnaud Blanchard's avatar
Arnaud Blanchard committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* Basic Library for C/C++ (blclib)
 Copyright  ETIS — ENSEA, Université de Cergy-Pontoise, CNRS (2011 - 2014)
 
 Author:  Arnaud Blanchard
 
 This software is governed by the CeCILL v2.1 license under French law and abiding by the rules of distribution of free software.
 You can use, modify and/ or redistribute the software under the terms of the CeCILL v2.1 license as circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info".
 As a counterpart to the access to the source code and  rights to copy, modify and redistribute granted by the license,
  users are provided only with a limited warranty and the software's author, the holder of the economic rights,  and the successive licensors have only limited liability.
  In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or developing or reproducing the software by the user in light of its specific status of free software,
  that may mean  that it is complicated to manipulate, and that also therefore means that it is reserved for developers and experienced professionals having in-depth computer knowledge.
 Users are therefore encouraged to load and test the software's suitability as regards their requirements in conditions enabling the security of their systems and/or data to be ensured
  and, more generally, to use and operate it in the same conditions as regards security.
  The fact that you are presently reading this means that you have had knowledge of the CeCILL v2.1 license and that you accept its terms. */

#include "blc_command.h"
#include "blc_realtime.h" //us_

#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <termios.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h> //errno and EINT
#include <libgen.h> //basename
#include <unistd.h> //usleep
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/select.h>

#include "blc_text.h"
#include "blc_tools.h"
#include "blc_program.h"


//Identical to argparse in Python
#define POSITIONAL_ARGUMENTS_TITLE "\npositional arguments:\n"
#define OPTIONAL_ARGUMENTS_TITLE "\noptional arguments:\n"

typedef void*(*type_pthread_cb)(void*);

typedef struct
{
    const char *name;
    type_blc_command_cb callback;
    const char *prompt;
    const char *help;
    void *user_data;
}blc_command;

// Static means it is only existing in this file
static blc_command *blc_commands = NULL;
static int blc_commands_nb = 0;
static struct timeval timer;
static int iterations_nb=0;
static uint blc_period=0;
static uint blc_duration=0;
static long blc_current_duration;

long blc_command_loop_period;

type_blc_status blc_status=BLC_RUN;

START_EXTERN_C

void blc_command_display_help()
{
    const blc_command *command;
    size_t command_length_max = 0;
    size_t command_length=0;
    
    FOR_EACH(command, blc_commands, blc_commands_nb)
    {
        command_length = strlen(command->name);
        if (command->prompt) command_length+=strlen(command->prompt)+2;
        command_length_max = MAX(command_length, command_length_max);
    }
    
    FOR_EACH(command, blc_commands, blc_commands_nb)
    {
        if (command->prompt) fprintf(stderr, "%s<%s>%*c:%s\n", command->name, command->prompt, (int)(command_length_max - strlen(command->name)-strlen(command->prompt)-1), ' ', command->help);
        else    fprintf(stderr, "%-*s :%s\n", (int)command_length_max, command->name, command->help);
    }
}

void blc_fprint_stats(FILE *file)
{
    fprintf(file, "%d iterations, average duration: %.3fms, average frequency: %.3fHz period: %.3fms\n", iterations_nb, blc_duration/(1000.*iterations_nb), 1000000.*iterations_nb/(double)blc_period, blc_period/(1000.*iterations_nb));
    iterations_nb=0;
    blc_period=0;
    blc_duration=0;
    us_time_diff(&timer);
}

static void pause_cb()
{
    if (blc_status==BLC_RUN){
        blc_status=BLC_PAUSE;
        fprintf(stderr, "=== %s: pause ===\n", blc_program_name);
        blc_command_interpret();
    }else {
        fprintf(stderr, "=== %s: running ===\n", blc_program_name);
        blc_status=BLC_RUN;
    }
}

static void blc_command_ask_quit(){
    blc_status=BLC_QUIT;
    if (blc_command_loop_period==-2){ //thread
        sleep(3);
        fprintf(stderr, "=== %s: error program not quitting ! We force it. ===\n", blc_program_name);
        exit(EXIT_FAILURE);
    }
}

static void display_stats()
{
    blc_fprint_stats(stderr);
}

void blc_command_add(const char *command_name, type_blc_command_cb callback, const char *prompt, const char *help, void *user_data){
    blc_command tmp_command;
    int i;
    
    tmp_command.name = command_name;
    tmp_command.callback = callback;
    tmp_command.prompt = prompt;
    tmp_command.help = help;
    tmp_command.user_data = user_data;
    
    FOR_INV(i, blc_commands_nb)
    if (strcmp(blc_commands[i].name, command_name) == 0) EXIT_ON_ERROR("Command '%s' is already defined.", command_name);
    
    APPEND_ITEM(&blc_commands, &blc_commands_nb, &tmp_command);
}
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

void blc_command_interpret_string(char const *input_string, size_t size){
	char const*parameter;
	char *parameter2;

	size_t parameter_size, parameter2_size;
    const blc_command *command;
    size_t name_size, line_capability=0;

	FOR_EACH_INV(command, blc_commands, blc_commands_nb)
	    {
	        name_size = strlen(command->name);
	        if (strncmp(command->name, input_string, name_size) == 0)
	        {
	            parameter = input_string + name_size;
	            parameter_size = size - name_size;

	            if (parameter_size == 0) //No text after the command
	            {
	                parameter2 = NULL;
	                if (command->prompt != NULL) //A text was expected
	                {
	                    fprintf(stderr, "%s:", command->prompt);
	                    parameter2_size = getline(&parameter2, &line_capability, stdin)-1;
	                    parameter2[parameter2_size]=0;
	                }
	                if (command->callback) command->callback(parameter2, command->user_data); //In which callback is NULL ??
	                FREE(parameter2);
	                line_capability=0;
	                break;
	            }
	            else
	            {
	                if (command->prompt == NULL) continue; // If we do not wait for parameter, the command must be exact.
	                else command->callback( parameter, command->user_data);
	                break;
	            }
	        }
	    }
	    if (command < blc_commands) fprintf(stderr, "Unknown command in: %s\n", input_string);
}

Arnaud Blanchard's avatar
Arnaud Blanchard committed
179
180
181
182
183
184
185
186
187
188
189
190
191
static void blc_command_interpret_block(){
    blc_mem line, parameter;
    size_t line_capability=0;
    ssize_t tmp_size;
    const blc_command *command;
    int name_size;
    
    do{
        tmp_size = getline(&line.chars, &line_capability, stdin);
    }while ((tmp_size==-1) && (errno==EINTR));
    
    if (tmp_size==-1)
    {
192
        if ((errno==0) || (errno==ETIMEDOUT)) exit(EXIT_SUCCESS); //stdin closed
Arnaud Blanchard's avatar
Arnaud Blanchard committed
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
        else EXIT_ON_SYSTEM_ERROR("Command interpret: getline");
    }
    
    line.size=tmp_size-1;
    line.chars[line.size]=0; // Removing the last return char (\n)
    
    FOR_EACH_INV(command, blc_commands, blc_commands_nb)
    {
        name_size = strlen(command->name);
        if (strncmp(command->name, line.chars, name_size) == 0)
        {
            parameter.chars = line.chars + name_size;
            parameter.size = line.size - name_size;
            
            if (parameter.size == 0) //No text after the command
            {
209
                parameter.chars = NULL;
Arnaud Blanchard's avatar
Arnaud Blanchard committed
210
211
212
213
214
215
216
                if (command->prompt != NULL) //A text was expected
                {
                    fprintf(stderr, "%s:", command->prompt);
                    parameter.size = getline(&parameter.chars, &line_capability, stdin)-1;
                    parameter.chars[parameter.size]=0;
                }
                if (command->callback) command->callback( parameter.chars, command->user_data); //In which callback is NULL ??
217
                parameter.~blc_mem();
Arnaud Blanchard's avatar
Arnaud Blanchard committed
218
219
220
221
222
223
224
225
226
227
228
229
                line_capability=0;
                break;
            }
            else
            {
                if (command->prompt == NULL) continue; // If we do not wait for parameter, the command must be exact.
                else command->callback( parameter.chars, command->user_data);
                break;
            }
        }
    }
    if (command < blc_commands) fprintf(stderr, "Unknown command in: %s\n", line.chars);
230
    parameter.chars=NULL; //Avoid to be destructed twice
Arnaud Blanchard's avatar
Arnaud Blanchard committed
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
}

void blc_command_interpret(){
    fd_set rfds;
    int retval=-1;
    
    /* Surveiller stdin (fd 0) en attente d'entrées */
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);
    
    //Block until there is data or there is an interuption (Ctrl+C, Terminate, ..)  which is not the case with getline.
    while (retval==-1){
        retval = select(1, &rfds, NULL, NULL, NULL);
        if (retval!=-1) blc_command_interpret_block();
        else if (errno!=EINTR) EXIT_ON_SYSTEM_ERROR("Waiting to interpret a command");
    }
}

int blc_command_try_to_interpret(){
    fd_set rfds;
    struct timeval time_val={0,0};
    int retval;
    
    /* Surveiller stdin (fd 0) en attente d'entrées */
    FD_ZERO(&rfds);
    FD_SET(STDIN_FILENO, &rfds);
    
    SYSTEM_ERROR_CHECK(retval = select(STDIN_FILENO+1, &rfds, NULL, NULL, &time_val), -1, NULL);
    if (retval) blc_command_interpret_block();
    else return 0;
    
    return 1;
}

/// if the parameter is not null we should display the help at each command.
void *command_thread_interpret_loop(void *){
    while(blc_status==BLC_RUN){
        blc_command_interpret();
    }
    return NULL;
}

void blc_command_interpret_thread(char const *option, void (*ask_quit_funcion)()){
    pthread_t thread;

    if (ask_quit_funcion==NULL) ask_quit_funcion=blc_command_ask_quit;
    blc_command_loop_period=-2;
    if (strchr(option, 'h')) blc_command_add("h",  (type_blc_command_cb)blc_command_display_help, NULL, "display this help", NULL);
    if (strchr(option, 'q')) blc_command_add("q",  (type_blc_command_cb)ask_quit_funcion, NULL, "quit the application", NULL);
    SYSTEM_ERROR_CHECK(pthread_create(&thread, NULL, command_thread_interpret_loop, NULL), -1, NULL);
}


void blc_command_loop_init(long loop_period)
{
    iterations_nb=0;
    blc_command_loop_period=loop_period;
    if (blc_input_terminal && (blc_command_loop_period!=-1) && (blc_command_loop_period!=-2)) blc_command_add("", (type_blc_command_cb)pause_cb, NULL, "set on/off pause", NULL);
    if (blc_command_loop_period!=-2){
        blc_command_add("h", (type_blc_command_cb)blc_command_display_help, NULL, "display this help", NULL);
        blc_command_add("q", (type_blc_command_cb)blc_command_ask_quit, NULL, "quit the application", NULL);
    }
    if (blc_command_loop_period!=-1) blc_command_add("s", (type_blc_command_cb)display_stats, NULL, "display time stats", NULL);
    
    if (blc_input_terminal) blc_command_display_help();
    if (blc_command_loop_period!=-1) fprintf(stderr, "=== %s: running ===\n", blc_program_name);
    if (blc_command_loop_period==-2) blc_command_interpret_thread("hq", NULL);
}

int blc_command_loop_start(){
    if (iterations_nb==0) us_time_diff(&timer);
    else  blc_period+=blc_current_duration+us_time_diff(&timer);
   
    switch (blc_command_loop_period){
        case -1: blc_command_interpret();break;
        case -2:break;
        default:blc_command_try_to_interpret();
    }
    return blc_status;
}

void blc_command_loop_end(){
    struct timespec time_left={0,0};
    
    blc_current_duration=us_time_diff(&timer);
    blc_duration+=blc_current_duration;
    
    if (blc_command_loop_period > 0){
        time_left.tv_nsec = (blc_command_loop_period - blc_current_duration)*1000;
        if (time_left.tv_nsec < 0) color_fprintf(BLC_YELLOW, stderr, "\rMissing %.3fms in the BLC_COMMAND_LOOP", -time_left.tv_nsec/1000000.f);
        else SYSTEM_SUCCESS_CHECK(nanosleep(&time_left, NULL), 0, "Program loop interrupted. Time left %ldµs", time_left.tv_nsec/1000.f);
        
        /* Warning the nanosleep can be overtimed of 10ms !!! it is the same for usleep !! */
    }
    iterations_nb++;
}

void blc_command_update_int_cb(char const *argument, void *int_pt){
    char *endptr;
    int value;
    
    if (argument==NULL || argument[0]==0) fprintf(stderr, "%d\n", *(int*)int_pt);
    else{
    value=strtod(argument, &endptr);
    if (endptr!=argument+strlen(argument)) fprintf(stderr, "Error reading '%s' as an int. The value is still:%d\n", argument,  *(int*)int_pt);
    else *(int*)int_pt=value;
    }
}

void blc_command_update_float_cb(char const *argument, void *float_pt){
    char *endptr;
    float value;
    
    if (argument==NULL || argument[0]==0) fprintf(stderr, "%f\n", *(float*)float_pt);
    else{
    value=strtof(argument, &endptr);
    if (endptr!=argument+strlen(argument)) fprintf(stderr, "Error reading '%s' as a float. The value is still:%f\n", argument,  *(float*)float_pt);
    else *(float*)float_pt=value;
    }
}

void blc_command_update_double_cb(char const *argument, void *double_pt){
    char *endptr;
    double value;
    
    if (argument==NULL || argument[0]==0) fprintf(stderr, "%lf\n", *(double*)double_pt);
    else{
    value=strtod(argument, &endptr);
    if (endptr!=argument+strlen(argument)) fprintf(stderr, "Error reading '%s' as a double. The value is still:%lf\n", argument,  *(double*)double_pt);
    else *(double*)double_pt=value;
    }
}


END_EXTERN_C