iodine 0.4.8 → 0.4.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +26 -0
- data/README.md +18 -12
- data/SPEC-Websocket-Draft.md +9 -5
- data/bin/ws-echo +3 -0
- data/examples/config.ru +0 -1
- data/examples/echo.ru +3 -1
- data/examples/redis.ru +0 -1
- data/ext/iodine/base64.c +97 -105
- data/ext/iodine/defer.c +16 -1
- data/ext/iodine/defer.h +10 -0
- data/ext/iodine/evio.c +35 -13
- data/ext/iodine/extconf.rb +1 -1
- data/ext/iodine/facil.c +12 -1
- data/ext/iodine/facil.h +3 -1
- data/ext/iodine/fio2resp.c +71 -0
- data/ext/iodine/fio2resp.h +50 -0
- data/ext/iodine/fio_cli_helper.c +404 -0
- data/ext/iodine/fio_cli_helper.h +152 -0
- data/ext/iodine/fiobj.h +631 -0
- data/ext/iodine/fiobj_alloc.c +81 -0
- data/ext/iodine/fiobj_ary.c +290 -0
- data/ext/iodine/fiobj_generic.c +260 -0
- data/ext/iodine/fiobj_hash.c +447 -0
- data/ext/iodine/fiobj_io.c +58 -0
- data/ext/iodine/fiobj_json.c +779 -0
- data/ext/iodine/fiobj_misc.c +213 -0
- data/ext/iodine/fiobj_numbers.c +113 -0
- data/ext/iodine/fiobj_primitives.c +98 -0
- data/ext/iodine/fiobj_str.c +261 -0
- data/ext/iodine/fiobj_sym.c +213 -0
- data/ext/iodine/fiobj_tests.c +474 -0
- data/ext/iodine/fiobj_types.h +290 -0
- data/ext/iodine/http1.c +54 -36
- data/ext/iodine/http1_parser.c +143 -35
- data/ext/iodine/http1_parser.h +6 -3
- data/ext/iodine/http1_response.c +0 -1
- data/ext/iodine/http_response.c +1 -1
- data/ext/iodine/iodine.c +20 -4
- data/ext/iodine/iodine_protocol.c +5 -4
- data/ext/iodine/iodine_pubsub.c +1 -1
- data/ext/iodine/random.c +5 -5
- data/ext/iodine/sha1.c +5 -8
- data/ext/iodine/sha2.c +8 -11
- data/ext/iodine/sha2.h +3 -3
- data/ext/iodine/sock.c +29 -31
- data/ext/iodine/websocket_parser.h +428 -0
- data/ext/iodine/websockets.c +112 -377
- data/ext/iodine/xor-crypt.c +16 -12
- data/lib/iodine/version.rb +1 -1
- metadata +21 -3
- data/ext/iodine/empty.h +0 -26
data/ext/iodine/defer.c
CHANGED
@@ -201,6 +201,7 @@ void defer_thread_throttle(unsigned long microsec) { return; }
|
|
201
201
|
|
202
202
|
#endif /* DEBUG || pthread default */
|
203
203
|
|
204
|
+
/* thread pool data container */
|
204
205
|
struct defer_pool {
|
205
206
|
unsigned int flag;
|
206
207
|
unsigned int count;
|
@@ -352,8 +353,10 @@ int defer_perform_in_fork(unsigned int process_count,
|
|
352
353
|
goto finish;
|
353
354
|
};
|
354
355
|
|
355
|
-
|
356
|
+
/* setup zomie reaping */
|
357
|
+
#if !defined(NO_CHILD_REAPER) || NO_CHILD_REAPER == 0
|
356
358
|
reap_children();
|
359
|
+
#endif
|
357
360
|
|
358
361
|
if (!process_count)
|
359
362
|
process_count = 1;
|
@@ -469,6 +472,18 @@ void defer_test(void) {
|
|
469
472
|
time_t start, end;
|
470
473
|
fprintf(stderr, "Starting defer testing\n");
|
471
474
|
|
475
|
+
spn_lock(&i_lock);
|
476
|
+
i_count = 0;
|
477
|
+
spn_unlock(&i_lock);
|
478
|
+
start = clock();
|
479
|
+
for (size_t i = 0; i < (1024 * 1024); i++) {
|
480
|
+
sample_task(NULL, NULL);
|
481
|
+
}
|
482
|
+
end = clock();
|
483
|
+
fprintf(stderr,
|
484
|
+
"Deferless (direct call) counter: %lu cycles with i_count = %lu\n",
|
485
|
+
end - start, i_count);
|
486
|
+
|
472
487
|
spn_lock(&i_lock);
|
473
488
|
i_count = 0;
|
474
489
|
spn_unlock(&i_lock);
|
data/ext/iodine/defer.h
CHANGED
@@ -19,6 +19,11 @@ forked process.
|
|
19
19
|
#define LIB_DEFER_VERSION_MINOR 1
|
20
20
|
#define LIB_DEFER_VERSION_PATCH 2
|
21
21
|
|
22
|
+
/* child process reaping is enabled by default */
|
23
|
+
#ifndef NO_CHILD_REAPER
|
24
|
+
#define NO_CHILD_REAPER 0
|
25
|
+
#endif
|
26
|
+
|
22
27
|
#ifdef __cplusplus
|
23
28
|
extern "C" {
|
24
29
|
#endif
|
@@ -108,6 +113,11 @@ int defer_fork_is_active(void);
|
|
108
113
|
/** Returns the process number for the current working proceess. 0 == parent. */
|
109
114
|
int defer_fork_pid(void);
|
110
115
|
|
116
|
+
#ifdef DEBUG
|
117
|
+
/** minor testing facilities */
|
118
|
+
void defer_test(void);
|
119
|
+
#endif
|
120
|
+
|
111
121
|
#ifdef __cplusplus
|
112
122
|
} /* closing brace for extern "C" */
|
113
123
|
#endif
|
data/ext/iodine/evio.c
CHANGED
@@ -15,12 +15,6 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
15
15
|
#error This library currently supports only Unix based systems (i.e. Linux and BSD)
|
16
16
|
#endif
|
17
17
|
|
18
|
-
#if !defined(__linux__) && !defined(__CYGWIN__)
|
19
|
-
#include <sys/event.h>
|
20
|
-
#else
|
21
|
-
#include <sys/epoll.h>
|
22
|
-
#include <sys/timerfd.h>
|
23
|
-
#endif
|
24
18
|
#include <assert.h>
|
25
19
|
#include <errno.h>
|
26
20
|
#include <fcntl.h>
|
@@ -66,6 +60,7 @@ static int evio_fd = -1;
|
|
66
60
|
void evio_close() {
|
67
61
|
if (evio_fd != -1)
|
68
62
|
close(evio_fd);
|
63
|
+
evio_fd = -1;
|
69
64
|
}
|
70
65
|
|
71
66
|
/**
|
@@ -73,10 +68,13 @@ returns true if the evio is available for adding or removing file descriptors.
|
|
73
68
|
*/
|
74
69
|
int evio_isactive(void) { return evio_fd >= 0; }
|
75
70
|
|
71
|
+
#if defined(__linux__) || defined(__CYGWIN__)
|
76
72
|
/* *****************************************************************************
|
77
73
|
Linux `epoll` implementation
|
78
74
|
***************************************************************************** */
|
79
|
-
#
|
75
|
+
#include <sys/epoll.h>
|
76
|
+
#include <sys/timerfd.h>
|
77
|
+
|
80
78
|
/**
|
81
79
|
Creates the `epoll` or `kqueue` object.
|
82
80
|
*/
|
@@ -105,7 +103,30 @@ int evio_add(int fd, void *callback_arg) {
|
|
105
103
|
Creates a timer file descriptor, system dependent.
|
106
104
|
*/
|
107
105
|
intptr_t evio_open_timer(void) {
|
108
|
-
|
106
|
+
#ifndef TFD_NONBLOCK
|
107
|
+
intptr_t fd = timerfd_create(CLOCK_MONOTONIC, O_NONBLOCK);
|
108
|
+
if (fd != -1) { /* make sure it's a non-blocking timer. */
|
109
|
+
#if defined(O_NONBLOCK)
|
110
|
+
/* Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
|
111
|
+
int flags;
|
112
|
+
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
|
113
|
+
flags = 0;
|
114
|
+
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
|
115
|
+
goto error;
|
116
|
+
#else
|
117
|
+
/* no O_NONBLOCK, use the old way of doing it */
|
118
|
+
static int flags = 1;
|
119
|
+
if (ioctl(fd, FIOBIO, &flags) == -1)
|
120
|
+
goto error;
|
121
|
+
#endif
|
122
|
+
}
|
123
|
+
return fd;
|
124
|
+
error:
|
125
|
+
close(fd);
|
126
|
+
return -1;
|
127
|
+
#else
|
128
|
+
return timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
129
|
+
#endif
|
109
130
|
}
|
110
131
|
|
111
132
|
/**
|
@@ -113,16 +134,16 @@ Adds a timer file descriptor, so that callbacks will be called for it's events.
|
|
113
134
|
*/
|
114
135
|
intptr_t evio_add_timer(int fd, void *callback_arg,
|
115
136
|
unsigned long milliseconds) {
|
116
|
-
struct epoll_event chevent
|
117
|
-
|
118
|
-
|
119
|
-
EPOLLOUT | EPOLLIN | EPOLLET | EPOLLERR | EPOLLRDHUP | EPOLLHUP;
|
137
|
+
struct epoll_event chevent = {.data.ptr = (void *)callback_arg,
|
138
|
+
.events = (EPOLLOUT | EPOLLIN | EPOLLET |
|
139
|
+
EPOLLERR | EPOLLRDHUP | EPOLLHUP)};
|
120
140
|
struct itimerspec new_t_data;
|
121
141
|
new_t_data.it_value.tv_sec = new_t_data.it_interval.tv_sec =
|
122
142
|
milliseconds / 1000;
|
123
143
|
new_t_data.it_value.tv_nsec = new_t_data.it_interval.tv_nsec =
|
124
144
|
(milliseconds % 1000) * 1000000;
|
125
|
-
timerfd_settime(fd, 0, &new_t_data, NULL)
|
145
|
+
if (timerfd_settime(fd, 0, &new_t_data, NULL) == -1)
|
146
|
+
return -1;
|
126
147
|
return epoll_ctl(evio_fd, EPOLL_CTL_ADD, fd, &chevent);
|
127
148
|
}
|
128
149
|
|
@@ -168,6 +189,7 @@ int evio_review(const int timeout_millisec) {
|
|
168
189
|
/* *****************************************************************************
|
169
190
|
BSD `kqueue` implementation
|
170
191
|
***************************************************************************** */
|
192
|
+
#include <sys/event.h>
|
171
193
|
|
172
194
|
/**
|
173
195
|
Creates the `epoll` or `kqueue` object.
|
data/ext/iodine/extconf.rb
CHANGED
@@ -33,7 +33,7 @@ else
|
|
33
33
|
puts 'using an unknown (old?) compiler... who knows if this will work out... we hope.'
|
34
34
|
end
|
35
35
|
|
36
|
-
$CFLAGS = '-std=c11 -O3 -Wall -DSERVER_DELAY_IO=1'
|
36
|
+
$CFLAGS = '-std=c11 -O3 -Wall -DSERVER_DELAY_IO=1 -DNO_CHILD_REAPER=1'
|
37
37
|
RbConfig::MAKEFILE_CONFIG['CC'] = $CC = ENV['CC'] if ENV['CC']
|
38
38
|
RbConfig::MAKEFILE_CONFIG['CPP'] = $CPP = ENV['CPP'] if ENV['CPP']
|
39
39
|
|
data/ext/iodine/facil.c
CHANGED
@@ -578,6 +578,11 @@ static void timer_on_close(intptr_t uuid, protocol_s *protocol) {
|
|
578
578
|
(void)uuid;
|
579
579
|
}
|
580
580
|
|
581
|
+
static void timer_ping(intptr_t uuid, protocol_s *protocol) {
|
582
|
+
sock_touch(uuid);
|
583
|
+
(void)protocol;
|
584
|
+
}
|
585
|
+
|
581
586
|
static inline timer_protocol_s *timer_alloc(void (*task)(void *), void *arg,
|
582
587
|
size_t milliseconds,
|
583
588
|
size_t repetitions,
|
@@ -590,6 +595,7 @@ static inline timer_protocol_s *timer_alloc(void (*task)(void *), void *arg,
|
|
590
595
|
.protocol.service = timer_protocol_name,
|
591
596
|
.protocol.on_data = timer_on_data,
|
592
597
|
.protocol.on_close = timer_on_close,
|
598
|
+
.protocol.ping = timer_ping,
|
593
599
|
.arg = arg,
|
594
600
|
.task = task,
|
595
601
|
.on_finish = on_finish,
|
@@ -613,12 +619,14 @@ inline static void timer_on_server_start(int fd) {
|
|
613
619
|
* will repeat forever.
|
614
620
|
*
|
615
621
|
* Returns -1 on error or the new file descriptor on succeess.
|
622
|
+
*
|
623
|
+
* The `on_finish` handler is always called (even on error).
|
616
624
|
*/
|
617
625
|
int facil_run_every(size_t milliseconds, size_t repetitions,
|
618
626
|
void (*task)(void *), void *arg,
|
619
627
|
void (*on_finish)(void *)) {
|
620
628
|
if (task == NULL)
|
621
|
-
|
629
|
+
goto error_fin;
|
622
630
|
timer_protocol_s *protocol = NULL;
|
623
631
|
intptr_t uuid = -1;
|
624
632
|
int fd = evio_open_timer();
|
@@ -641,6 +649,9 @@ error:
|
|
641
649
|
sock_close(uuid);
|
642
650
|
else if (fd != -1)
|
643
651
|
close(fd);
|
652
|
+
error_fin:
|
653
|
+
if (on_finish)
|
654
|
+
on_finish(arg);
|
644
655
|
return -1;
|
645
656
|
}
|
646
657
|
|
data/ext/iodine/facil.h
CHANGED
@@ -11,7 +11,7 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
11
11
|
#define H_FACIL_H
|
12
12
|
#define FACIL_VERSION_MAJOR 0
|
13
13
|
#define FACIL_VERSION_MINOR 5
|
14
|
-
#define FACIL_VERSION_PATCH
|
14
|
+
#define FACIL_VERSION_PATCH 3
|
15
15
|
|
16
16
|
#ifndef FACIL_PRINT_STATE
|
17
17
|
/**
|
@@ -349,6 +349,8 @@ size_t facil_count(void *service);
|
|
349
349
|
* will repeat forever.
|
350
350
|
*
|
351
351
|
* Returns -1 on error or the new file descriptor on succeess.
|
352
|
+
*
|
353
|
+
* The `on_finish` handler is always called (even on error).
|
352
354
|
*/
|
353
355
|
int facil_run_every(size_t milliseconds, size_t repetitions,
|
354
356
|
void (*task)(void *), void *arg, void (*on_finish)(void *));
|
@@ -0,0 +1,71 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz Segev, 2017
|
3
|
+
License: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
|
7
|
+
Copyright refers to the parser, not the protocol.
|
8
|
+
*/
|
9
|
+
#ifndef _GNU_SOURCE
|
10
|
+
#define _GNU_SOURCE
|
11
|
+
#endif
|
12
|
+
|
13
|
+
#include "fio2resp.h"
|
14
|
+
#include "fiobj.h"
|
15
|
+
|
16
|
+
#include <stdio.h>
|
17
|
+
#include <stdlib.h>
|
18
|
+
#include <string.h>
|
19
|
+
|
20
|
+
static int resp_fioformat_task(fiobj_s *obj, void *s_) {
|
21
|
+
fiobj_s *str = s_;
|
22
|
+
|
23
|
+
switch (obj->type) {
|
24
|
+
case FIOBJ_T_FALSE:
|
25
|
+
fiobj_str_write(str, "false\r\n", 7);
|
26
|
+
break;
|
27
|
+
case FIOBJ_T_TRUE:
|
28
|
+
fiobj_str_write(str, "true\r\n", 6);
|
29
|
+
break;
|
30
|
+
case FIOBJ_T_STRING:
|
31
|
+
case FIOBJ_T_SYMBOL: {
|
32
|
+
/* use this opportunity to optimize memory allocation to page boundries */
|
33
|
+
fio_cstr_s s = fiobj_obj2cstr(str);
|
34
|
+
if (fiobj_str_capa(str) <= s.len + 128 + fiobj_obj2cstr(obj).len) {
|
35
|
+
fiobj_str_resize(str, (((fiobj_str_capa(str) >> 12) + 1) << 12) - 1);
|
36
|
+
fiobj_str_resize(str, s.len);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
/* fallthrough */
|
40
|
+
case FIOBJ_T_NUMBER:
|
41
|
+
case FIOBJ_T_FLOAT:
|
42
|
+
fiobj_str_join(str, obj);
|
43
|
+
fiobj_str_write(str, "\r\n", 2);
|
44
|
+
break;
|
45
|
+
case FIOBJ_T_IO:
|
46
|
+
case FIOBJ_T_NULL:
|
47
|
+
fiobj_str_write(str, "$-1\r\n", 4);
|
48
|
+
return 0;
|
49
|
+
case FIOBJ_T_ARRAY:
|
50
|
+
fiobj_str_write2(str, "*%lu\r\n", (unsigned long)fiobj_ary_count(obj));
|
51
|
+
break;
|
52
|
+
case FIOBJ_T_HASH:
|
53
|
+
fiobj_str_write2(str, "*%lu\r\n", (unsigned long)fiobj_hash_count(obj));
|
54
|
+
break;
|
55
|
+
case FIOBJ_T_COUPLET:
|
56
|
+
resp_fioformat_task(fiobj_couplet2key(obj), s_);
|
57
|
+
resp_fioformat_task(fiobj_couplet2obj(obj), s_);
|
58
|
+
return 0;
|
59
|
+
}
|
60
|
+
return 0;
|
61
|
+
}
|
62
|
+
#undef safe_write_eol
|
63
|
+
#undef safe_write1
|
64
|
+
#undef safe_write2
|
65
|
+
#undef safe_write_i
|
66
|
+
|
67
|
+
fiobj_pt resp_fioformat(fiobj_pt obj) {
|
68
|
+
fiobj_pt str = fiobj_str_buf(4096);
|
69
|
+
fiobj_each2(obj, resp_fioformat_task, str);
|
70
|
+
return str;
|
71
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz segev, 2017
|
3
|
+
License: MIT except for any non-public-domain algorithms (none that I'm aware
|
4
|
+
of), which might be subject to their own licenses.
|
5
|
+
|
6
|
+
Feel free to copy, use and enjoy in accordance with to the license(s).
|
7
|
+
*/
|
8
|
+
#ifndef H_FIO2RESP_FORMAT_H
|
9
|
+
/**
|
10
|
+
This is a neive implementation of the RESP protocol for Redis.
|
11
|
+
*/
|
12
|
+
#define H_FIO2RESP_FORMAT_H
|
13
|
+
|
14
|
+
#include "resp.h"
|
15
|
+
|
16
|
+
/* support C++ */
|
17
|
+
#ifdef __cplusplus
|
18
|
+
extern "C" {
|
19
|
+
#endif
|
20
|
+
|
21
|
+
/* *****************************************************************************
|
22
|
+
`fiobj_s` => RESP (formatting): implemented seperately; can be safely removed.
|
23
|
+
***************************************************************************** */
|
24
|
+
typedef struct fiobj_s *fiobj_pt;
|
25
|
+
/**
|
26
|
+
* Returns a **new** String object containing a RESP representation of `obj`.
|
27
|
+
*
|
28
|
+
* Returns NULL on failur.
|
29
|
+
*
|
30
|
+
* Obviously, RESP objects and `fiobj_s` objects aren't fully compatible,
|
31
|
+
* meaning that the RESP_OK and RESP_ERR aren't implemented.
|
32
|
+
*
|
33
|
+
* Also, `FIOBJ_T_HASH` objects are rendered as a flattened Array of `[key,
|
34
|
+
* value, key, value, ...]`, any `FIOBJ_T_FLOAT` will be converted to an Integet
|
35
|
+
* (the decimal information discarded) and any other object is converted to a
|
36
|
+
* String. `FIOBJ_T_IO` and `FIOBJ_T_FILE` are ignored.
|
37
|
+
*
|
38
|
+
* No `parser` argument is provided and extensions aren't supported for this
|
39
|
+
* format.
|
40
|
+
*
|
41
|
+
* To write a RESP OK or Error don;t use this function. Instead, simply ctreate
|
42
|
+
* a new String "OK" or "-error message".
|
43
|
+
*/
|
44
|
+
fiobj_pt resp_fioformat(fiobj_pt obj);
|
45
|
+
|
46
|
+
#ifdef __cplusplus
|
47
|
+
} /* extern "C" */
|
48
|
+
#endif
|
49
|
+
|
50
|
+
#endif
|
@@ -0,0 +1,404 @@
|
|
1
|
+
/*
|
2
|
+
Copyright: Boaz segev, 2017
|
3
|
+
License: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license provided.
|
6
|
+
*/
|
7
|
+
#include "fio_cli_helper.h"
|
8
|
+
#include "fiobj.h"
|
9
|
+
|
10
|
+
#include <string.h>
|
11
|
+
/* *****************************************************************************
|
12
|
+
State (static data)
|
13
|
+
***************************************************************************** */
|
14
|
+
|
15
|
+
/* static variables are automatically initialized to 0, which is what we need.*/
|
16
|
+
static int ARGC;
|
17
|
+
static const char **ARGV;
|
18
|
+
static fiobj_s *arg_aliases; /* a hash for translating aliases */
|
19
|
+
static fiobj_s *arg_type; /* a with information about each argument */
|
20
|
+
static fiobj_s *parsed; /* a with information about each argument */
|
21
|
+
static fiobj_s *help_str; /* The CLI help string */
|
22
|
+
static fiobj_s *info_str; /* The CLI information string */
|
23
|
+
static int is_parsed;
|
24
|
+
|
25
|
+
const char DEFAULT_CLI_INFO[] =
|
26
|
+
"This application accepts any of the following possible arguments:";
|
27
|
+
/* *****************************************************************************
|
28
|
+
Error / Help handling - printing the information and exiting.
|
29
|
+
***************************************************************************** */
|
30
|
+
|
31
|
+
static void fio_cli_handle_error(void) {
|
32
|
+
fio_cstr_s info = fiobj_obj2cstr(info_str);
|
33
|
+
fio_cstr_s args = fiobj_obj2cstr(help_str);
|
34
|
+
fprintf(stdout,
|
35
|
+
"\n"
|
36
|
+
"%s\n"
|
37
|
+
"%s\n"
|
38
|
+
"Use any of the following input formats:\n"
|
39
|
+
"\t-arg <value>\t-arg=<value>\t-arg<value>\n"
|
40
|
+
"\n"
|
41
|
+
"Use the -h, -help or -? to get this information again.\n"
|
42
|
+
"\n",
|
43
|
+
info.data, args.data);
|
44
|
+
fio_cli_end();
|
45
|
+
exit(0);
|
46
|
+
}
|
47
|
+
|
48
|
+
/* *****************************************************************************
|
49
|
+
Initializing the CLI data
|
50
|
+
***************************************************************************** */
|
51
|
+
|
52
|
+
static void fio_cli_init(void) {
|
53
|
+
/* if init is called after parsing, discard previous result */
|
54
|
+
if (parsed) {
|
55
|
+
fiobj_free(parsed);
|
56
|
+
parsed = NULL;
|
57
|
+
}
|
58
|
+
/* avoid overwriting existing data */
|
59
|
+
if (arg_aliases)
|
60
|
+
return;
|
61
|
+
arg_aliases = fiobj_hash_new();
|
62
|
+
arg_type = fiobj_hash_new();
|
63
|
+
help_str = fiobj_str_buf(1024);
|
64
|
+
if (!info_str) /* might exist through `fio_cli_start` */
|
65
|
+
info_str = fiobj_str_static(DEFAULT_CLI_INFO, sizeof(DEFAULT_CLI_INFO) - 1);
|
66
|
+
}
|
67
|
+
|
68
|
+
/* *****************************************************************************
|
69
|
+
Matching arguments to C string
|
70
|
+
***************************************************************************** */
|
71
|
+
|
72
|
+
/* returns the primamry symbol for the argument, of NULL (if none) */
|
73
|
+
static inline fiobj_s *fio_cli_get_name(const char *str, size_t len) {
|
74
|
+
return fiobj_hash_get2(arg_aliases, str, len);
|
75
|
+
}
|
76
|
+
|
77
|
+
/* *****************************************************************************
|
78
|
+
Setting an argument's type and alias.
|
79
|
+
***************************************************************************** */
|
80
|
+
typedef enum { CLI_BOOL, CLI_NUM, CLI_STR } cli_type;
|
81
|
+
static void fio_cli_set(const char *aliases, const char *desc, cli_type type) {
|
82
|
+
fio_cli_init();
|
83
|
+
const char *start = aliases;
|
84
|
+
size_t len = 0;
|
85
|
+
fiobj_s *arg_name = NULL;
|
86
|
+
|
87
|
+
while (1) {
|
88
|
+
/* get rid of any white space or commas */
|
89
|
+
while (start[0] == ' ' || start[0] == ',')
|
90
|
+
start++;
|
91
|
+
/* we're done */
|
92
|
+
if (!start[0])
|
93
|
+
return;
|
94
|
+
len = 0;
|
95
|
+
/* find the length of the argument name */
|
96
|
+
while (start[len] != 0 && start[len] != ' ' && start[len] != ',')
|
97
|
+
len++;
|
98
|
+
|
99
|
+
if (!arg_name) {
|
100
|
+
/* this is the main identifier */
|
101
|
+
arg_name = fiobj_sym_new(start, len);
|
102
|
+
/* add to aliases hash */
|
103
|
+
fiobj_hash_set(arg_aliases, arg_name, arg_name);
|
104
|
+
/* add the help section and set type*/
|
105
|
+
switch (type) {
|
106
|
+
case CLI_BOOL:
|
107
|
+
fiobj_str_write2(help_str, "\t\e[1m-%s\e[0m\t\t%s\n",
|
108
|
+
fiobj_obj2cstr(arg_name).data, desc);
|
109
|
+
fiobj_hash_set(arg_type, arg_name, fiobj_null());
|
110
|
+
break;
|
111
|
+
case CLI_NUM:
|
112
|
+
fiobj_str_write2(help_str, "\t\e[1m-%s\e[0m \e[2###\e[0m\t%s\n",
|
113
|
+
fiobj_obj2cstr(arg_name).data, desc);
|
114
|
+
fiobj_hash_set(arg_type, arg_name, fiobj_true());
|
115
|
+
break;
|
116
|
+
case CLI_STR:
|
117
|
+
fiobj_str_write2(help_str, "\t\e[1m-%s\e[0m \e[2<val>\e[0m\t%s\n",
|
118
|
+
fiobj_obj2cstr(arg_name).data, desc);
|
119
|
+
fiobj_hash_set(arg_type, arg_name, fiobj_false());
|
120
|
+
break;
|
121
|
+
}
|
122
|
+
} else {
|
123
|
+
/* this is an alias */
|
124
|
+
fiobj_s *tmp = fiobj_sym_new(start, len);
|
125
|
+
/* add to aliases hash */
|
126
|
+
fiobj_hash_set(arg_aliases, tmp, fiobj_dup(arg_name));
|
127
|
+
/* add to description + free it*/
|
128
|
+
fiobj_str_write2(help_str, "\t\t\e[1m-%s\e[0m\tsame as -%s\n",
|
129
|
+
fiobj_obj2cstr(tmp).data, fiobj_obj2cstr(arg_name).data);
|
130
|
+
fiobj_free(tmp);
|
131
|
+
}
|
132
|
+
start += len;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
/* *****************************************************************************
|
137
|
+
parsing the arguments
|
138
|
+
***************************************************************************** */
|
139
|
+
|
140
|
+
static void fio_cli_parse(void) {
|
141
|
+
if (!ARGC || !ARGV) {
|
142
|
+
fprintf(
|
143
|
+
stderr,
|
144
|
+
"ERROR: (fio_cli) fio_cli_get_* "
|
145
|
+
"can only be called after `fio_cli_start` and before `fio_cli_end`\n");
|
146
|
+
exit(-1);
|
147
|
+
}
|
148
|
+
if (!arg_aliases) {
|
149
|
+
fprintf(stderr, "WARNING: (fio_cli) fio_cli_get_* "
|
150
|
+
"should only be called after `fio_cli_accept_*`\n");
|
151
|
+
return;
|
152
|
+
}
|
153
|
+
if (parsed)
|
154
|
+
return;
|
155
|
+
parsed = fiobj_hash_new();
|
156
|
+
|
157
|
+
const char *start;
|
158
|
+
size_t len;
|
159
|
+
fiobj_s *arg_name;
|
160
|
+
|
161
|
+
/* ignore the first element, it's the program's name. */
|
162
|
+
for (int i = 1; i < ARGC; i++) {
|
163
|
+
/* test for errors or help requests */
|
164
|
+
if (ARGV[i][0] != '-' || ARGV[i][1] == 0) {
|
165
|
+
start = ARGV[i];
|
166
|
+
goto error;
|
167
|
+
}
|
168
|
+
if ((ARGV[i][1] == '?' && ARGV[i][2] == 0) ||
|
169
|
+
(ARGV[i][1] == 'h' &&
|
170
|
+
(ARGV[i][2] == 0 || (ARGV[i][2] == 'e' && ARGV[i][3] == 'l' &&
|
171
|
+
ARGV[i][4] == 'p' && ARGV[i][5] == 0)))) {
|
172
|
+
fio_cli_handle_error();
|
173
|
+
}
|
174
|
+
/* we walk the name backwards, so `name` is tested before `n` */
|
175
|
+
start = ARGV[i] + 1;
|
176
|
+
len = strlen(start);
|
177
|
+
while (len && !(arg_name = fio_cli_get_name(start, len))) {
|
178
|
+
len--;
|
179
|
+
}
|
180
|
+
if (!len)
|
181
|
+
goto error;
|
182
|
+
/* at this point arg_name is a handle to the argument's Symbol */
|
183
|
+
fiobj_s *type = fiobj_hash_get(arg_type, arg_name);
|
184
|
+
if (FIOBJ_ISNULL(type)) {
|
185
|
+
/* type is BOOL, no further processing required */
|
186
|
+
start = "1";
|
187
|
+
len = 1;
|
188
|
+
goto set_arg;
|
189
|
+
}
|
190
|
+
if (start[len] == 0) {
|
191
|
+
i++;
|
192
|
+
if (i == ARGC)
|
193
|
+
goto error;
|
194
|
+
start = ARGV[i];
|
195
|
+
} else if (start[len] == '=') {
|
196
|
+
start = start + len + 1;
|
197
|
+
} else
|
198
|
+
start = start + len;
|
199
|
+
len = 0;
|
200
|
+
if (FIOBJ_FALSE(type)) /* no restrictions on data */
|
201
|
+
goto set_arg;
|
202
|
+
/* test that the argument is numerical */
|
203
|
+
if (start[len] == '-') /* negative number? */
|
204
|
+
len++;
|
205
|
+
while (start[len] >= '0' && start[len] <= '9')
|
206
|
+
len++;
|
207
|
+
if (start[len] == '.') { /* float number? */
|
208
|
+
while (start[len] >= '0' && start[len] <= '9')
|
209
|
+
len++;
|
210
|
+
}
|
211
|
+
if (start[len]) /* if there's data left, this aint a number. */
|
212
|
+
goto error;
|
213
|
+
set_arg:
|
214
|
+
fiobj_hash_set(parsed, arg_name, fiobj_str_static(start, len));
|
215
|
+
continue;
|
216
|
+
error:
|
217
|
+
fprintf(stderr, "\n*** Argument Error: %s\n", start);
|
218
|
+
fio_cli_handle_error();
|
219
|
+
}
|
220
|
+
}
|
221
|
+
|
222
|
+
/* *****************************************************************************
|
223
|
+
CLI API
|
224
|
+
***************************************************************************** */
|
225
|
+
|
226
|
+
/** Initialize the CLI helper */
|
227
|
+
void fio_cli_start(int argc, const char **argv, const char *info) {
|
228
|
+
ARGV = argv;
|
229
|
+
ARGC = argc;
|
230
|
+
if (info_str)
|
231
|
+
fiobj_free(info_str);
|
232
|
+
if (info) {
|
233
|
+
info_str = fiobj_str_static(info, 0);
|
234
|
+
} else {
|
235
|
+
info_str = fiobj_str_static(DEFAULT_CLI_INFO, sizeof(DEFAULT_CLI_INFO) - 1);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
/** Clears the memory and resources used by the CLI helper */
|
240
|
+
void fio_cli_end(void) {
|
241
|
+
#define free_and_reset(o) \
|
242
|
+
do { \
|
243
|
+
fiobj_free((o)); \
|
244
|
+
o = NULL; \
|
245
|
+
} while (0);
|
246
|
+
|
247
|
+
free_and_reset(arg_aliases);
|
248
|
+
free_and_reset(arg_type);
|
249
|
+
free_and_reset(help_str);
|
250
|
+
free_and_reset(info_str);
|
251
|
+
if (parsed)
|
252
|
+
free_and_reset(parsed);
|
253
|
+
|
254
|
+
#undef free_and_reset
|
255
|
+
|
256
|
+
ARGC = 0;
|
257
|
+
ARGV = NULL;
|
258
|
+
is_parsed = 0;
|
259
|
+
}
|
260
|
+
|
261
|
+
/**
|
262
|
+
* Sets a CLI acceptable argument of type Number (both `int` and `float`).
|
263
|
+
*
|
264
|
+
* The `aliases` string sets aliases for the same argument. i.e. "string
|
265
|
+
* s".
|
266
|
+
*
|
267
|
+
* The first alias will be the name available for `fio_cli_get_*`
|
268
|
+
* functions.
|
269
|
+
*
|
270
|
+
* The `desc` string will be printed if `-?`, `-h` of `-help` are used.
|
271
|
+
*
|
272
|
+
* The function will crash the application on failure, printing an error
|
273
|
+
* message.
|
274
|
+
*/
|
275
|
+
void fio_cli_accept_num(const char *aliases, const char *desc) {
|
276
|
+
fio_cli_set(aliases, desc, CLI_NUM);
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Sets a CLI acceptable argument of type String.
|
281
|
+
*
|
282
|
+
* The `aliases` string sets aliases for the same argument. i.e. "string s".
|
283
|
+
*
|
284
|
+
* The first alias will be the name used
|
285
|
+
*
|
286
|
+
* The `desc` string will be printed if `-?`, `-h` of `-help` are used.
|
287
|
+
*
|
288
|
+
* The function will crash the application on failure, printing an error
|
289
|
+
* message.
|
290
|
+
*/
|
291
|
+
void fio_cli_accept_str(const char *aliases, const char *desc) {
|
292
|
+
fio_cli_set(aliases, desc, CLI_STR);
|
293
|
+
}
|
294
|
+
|
295
|
+
/**
|
296
|
+
* Sets a CLI acceptable argument of type Bool (true if exists).
|
297
|
+
*
|
298
|
+
* The `aliases` string sets aliases for the same argument. i.e. "string s".
|
299
|
+
*
|
300
|
+
* The first alias will be the name available for `fio_cli_get_*` functions.
|
301
|
+
*
|
302
|
+
* The `desc` string will be printed if `-?`, `-h` of `-help` are used.
|
303
|
+
*
|
304
|
+
* The function will crash the application on failure, printing an error
|
305
|
+
* message.
|
306
|
+
*/
|
307
|
+
void fio_cli_accept_bool(const char *aliases, const char *desc) {
|
308
|
+
fio_cli_set(aliases, desc, CLI_BOOL);
|
309
|
+
}
|
310
|
+
|
311
|
+
/**
|
312
|
+
* Returns a C String containing the value of the received argument, or NULL
|
313
|
+
* if none.
|
314
|
+
*/
|
315
|
+
const char *fio_cli_get_str(const char *opt) {
|
316
|
+
fio_cli_parse();
|
317
|
+
fiobj_s *name = fio_cli_get_name(opt, strlen(opt));
|
318
|
+
if (!name)
|
319
|
+
return NULL;
|
320
|
+
fiobj_s *result = fiobj_hash_get(parsed, name);
|
321
|
+
if (!result)
|
322
|
+
return NULL;
|
323
|
+
return fiobj_obj2cstr(result).data;
|
324
|
+
}
|
325
|
+
|
326
|
+
/**
|
327
|
+
* Returns an Integer containing the parsed value of the argument.
|
328
|
+
*
|
329
|
+
* For boolean values, the value will be 0 for FALSE and 1 for TRUE.
|
330
|
+
*/
|
331
|
+
int fio_cli_get_int(const char *opt) {
|
332
|
+
fio_cli_parse();
|
333
|
+
fiobj_s *name = fio_cli_get_name(opt, strlen(opt));
|
334
|
+
if (!name)
|
335
|
+
return 0;
|
336
|
+
fiobj_s *result = fiobj_hash_get(parsed, name);
|
337
|
+
if (!result)
|
338
|
+
return 0;
|
339
|
+
return (int)fiobj_obj2num(result);
|
340
|
+
}
|
341
|
+
|
342
|
+
/**
|
343
|
+
* Returns a Float containing the parsed value of the argument.
|
344
|
+
*
|
345
|
+
* For boolean values, the value will be 0 for FALSE and 1 for TRUE.
|
346
|
+
*/
|
347
|
+
double fio_cli_get_float(const char *opt) {
|
348
|
+
fio_cli_parse();
|
349
|
+
fiobj_s *name = fio_cli_get_name(opt, strlen(opt));
|
350
|
+
if (!name)
|
351
|
+
return 0;
|
352
|
+
fiobj_s *result = fiobj_hash_get(parsed, name);
|
353
|
+
if (!result)
|
354
|
+
return 0;
|
355
|
+
return fiobj_obj2float(result);
|
356
|
+
}
|
357
|
+
|
358
|
+
/**
|
359
|
+
* Overrides the existing value of the argument with the requested C String.
|
360
|
+
*
|
361
|
+
* Boolean that were set to TRUE have the string "1".
|
362
|
+
*/
|
363
|
+
void fio_cli_set_str(const char *opt, const char *value) {
|
364
|
+
fio_cli_parse();
|
365
|
+
fiobj_s *name = fio_cli_get_name(opt, strlen(opt));
|
366
|
+
if (!name) {
|
367
|
+
fprintf(stderr, "ERROR: facil.io's CLI helper can only override values for "
|
368
|
+
"valid options\n");
|
369
|
+
exit(-1);
|
370
|
+
}
|
371
|
+
fiobj_hash_set(parsed, name, fiobj_str_static(value, strlen(value)));
|
372
|
+
}
|
373
|
+
|
374
|
+
/**
|
375
|
+
* Overrides the existing value of the argument with the requested Integer.
|
376
|
+
*
|
377
|
+
* For boolean values, the value will be 0 for FALSE and 1 for TRUE.
|
378
|
+
*/
|
379
|
+
void fio_cli_set_int(const char *opt, int value) {
|
380
|
+
fio_cli_parse();
|
381
|
+
fiobj_s *name = fio_cli_get_name(opt, strlen(opt));
|
382
|
+
if (!name) {
|
383
|
+
fprintf(stderr, "ERROR: facil.io's CLI helper can only override values for "
|
384
|
+
"valid options\n");
|
385
|
+
exit(-1);
|
386
|
+
}
|
387
|
+
fiobj_hash_set(parsed, name, fiobj_num_new(value));
|
388
|
+
}
|
389
|
+
|
390
|
+
/**
|
391
|
+
* Overrides the existing value of the argument with the requested Float.
|
392
|
+
*
|
393
|
+
* For boolean values, the value will be 0 for FALSE and 1 for TRUE.
|
394
|
+
*/
|
395
|
+
void fio_cli_set_float(const char *opt, double value) {
|
396
|
+
fio_cli_parse();
|
397
|
+
fiobj_s *name = fio_cli_get_name(opt, strlen(opt));
|
398
|
+
if (!name) {
|
399
|
+
fprintf(stderr, "ERROR: facil.io's CLI helper can only override values for "
|
400
|
+
"valid options\n");
|
401
|
+
exit(-1);
|
402
|
+
}
|
403
|
+
fiobj_hash_set(parsed, name, fiobj_float_new(value));
|
404
|
+
}
|