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.

Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +26 -0
  4. data/README.md +18 -12
  5. data/SPEC-Websocket-Draft.md +9 -5
  6. data/bin/ws-echo +3 -0
  7. data/examples/config.ru +0 -1
  8. data/examples/echo.ru +3 -1
  9. data/examples/redis.ru +0 -1
  10. data/ext/iodine/base64.c +97 -105
  11. data/ext/iodine/defer.c +16 -1
  12. data/ext/iodine/defer.h +10 -0
  13. data/ext/iodine/evio.c +35 -13
  14. data/ext/iodine/extconf.rb +1 -1
  15. data/ext/iodine/facil.c +12 -1
  16. data/ext/iodine/facil.h +3 -1
  17. data/ext/iodine/fio2resp.c +71 -0
  18. data/ext/iodine/fio2resp.h +50 -0
  19. data/ext/iodine/fio_cli_helper.c +404 -0
  20. data/ext/iodine/fio_cli_helper.h +152 -0
  21. data/ext/iodine/fiobj.h +631 -0
  22. data/ext/iodine/fiobj_alloc.c +81 -0
  23. data/ext/iodine/fiobj_ary.c +290 -0
  24. data/ext/iodine/fiobj_generic.c +260 -0
  25. data/ext/iodine/fiobj_hash.c +447 -0
  26. data/ext/iodine/fiobj_io.c +58 -0
  27. data/ext/iodine/fiobj_json.c +779 -0
  28. data/ext/iodine/fiobj_misc.c +213 -0
  29. data/ext/iodine/fiobj_numbers.c +113 -0
  30. data/ext/iodine/fiobj_primitives.c +98 -0
  31. data/ext/iodine/fiobj_str.c +261 -0
  32. data/ext/iodine/fiobj_sym.c +213 -0
  33. data/ext/iodine/fiobj_tests.c +474 -0
  34. data/ext/iodine/fiobj_types.h +290 -0
  35. data/ext/iodine/http1.c +54 -36
  36. data/ext/iodine/http1_parser.c +143 -35
  37. data/ext/iodine/http1_parser.h +6 -3
  38. data/ext/iodine/http1_response.c +0 -1
  39. data/ext/iodine/http_response.c +1 -1
  40. data/ext/iodine/iodine.c +20 -4
  41. data/ext/iodine/iodine_protocol.c +5 -4
  42. data/ext/iodine/iodine_pubsub.c +1 -1
  43. data/ext/iodine/random.c +5 -5
  44. data/ext/iodine/sha1.c +5 -8
  45. data/ext/iodine/sha2.c +8 -11
  46. data/ext/iodine/sha2.h +3 -3
  47. data/ext/iodine/sock.c +29 -31
  48. data/ext/iodine/websocket_parser.h +428 -0
  49. data/ext/iodine/websockets.c +112 -377
  50. data/ext/iodine/xor-crypt.c +16 -12
  51. data/lib/iodine/version.rb +1 -1
  52. metadata +21 -3
  53. 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
- /* setup zomie reaping */
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
- #if defined(__linux__) || defined(__CYGWIN__)
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
- return timerfd_create(CLOCK_MONOTONIC, O_NONBLOCK);
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
- chevent.data.ptr = (void *)callback_arg;
118
- chevent.events =
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.
@@ -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
- return -1;
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 2
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
+ }