iodine 0.6.5 → 0.7.0

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +4 -4
  4. data/SPEC-Websocket-Draft.md +3 -6
  5. data/bin/mustache.rb +128 -0
  6. data/examples/test_template.mustache +16 -0
  7. data/ext/iodine/fio.c +9397 -0
  8. data/ext/iodine/fio.h +4723 -0
  9. data/ext/iodine/fio_ary.h +353 -54
  10. data/ext/iodine/fio_cli.c +351 -361
  11. data/ext/iodine/fio_cli.h +84 -105
  12. data/ext/iodine/fio_hashmap.h +70 -16
  13. data/ext/iodine/fio_json_parser.h +35 -24
  14. data/ext/iodine/fio_siphash.c +104 -4
  15. data/ext/iodine/fio_siphash.h +18 -2
  16. data/ext/iodine/fio_str.h +1218 -0
  17. data/ext/iodine/fio_tmpfile.h +1 -1
  18. data/ext/iodine/fiobj.h +13 -8
  19. data/ext/iodine/fiobj4sock.h +6 -8
  20. data/ext/iodine/fiobj_ary.c +107 -17
  21. data/ext/iodine/fiobj_ary.h +36 -4
  22. data/ext/iodine/fiobj_data.c +146 -127
  23. data/ext/iodine/fiobj_data.h +25 -23
  24. data/ext/iodine/fiobj_hash.c +7 -7
  25. data/ext/iodine/fiobj_hash.h +6 -5
  26. data/ext/iodine/fiobj_json.c +20 -17
  27. data/ext/iodine/fiobj_json.h +5 -5
  28. data/ext/iodine/fiobj_mem.h +71 -0
  29. data/ext/iodine/fiobj_mustache.c +310 -0
  30. data/ext/iodine/fiobj_mustache.h +40 -0
  31. data/ext/iodine/fiobj_numbers.c +199 -94
  32. data/ext/iodine/fiobj_numbers.h +7 -7
  33. data/ext/iodine/fiobj_str.c +142 -333
  34. data/ext/iodine/fiobj_str.h +65 -55
  35. data/ext/iodine/fiobject.c +49 -11
  36. data/ext/iodine/fiobject.h +40 -39
  37. data/ext/iodine/http.c +382 -190
  38. data/ext/iodine/http.h +124 -80
  39. data/ext/iodine/http1.c +99 -127
  40. data/ext/iodine/http1.h +5 -5
  41. data/ext/iodine/http1_parser.c +3 -2
  42. data/ext/iodine/http1_parser.h +2 -2
  43. data/ext/iodine/http_internal.c +14 -12
  44. data/ext/iodine/http_internal.h +25 -19
  45. data/ext/iodine/iodine.c +37 -18
  46. data/ext/iodine/iodine.h +4 -0
  47. data/ext/iodine/iodine_caller.c +9 -2
  48. data/ext/iodine/iodine_caller.h +2 -0
  49. data/ext/iodine/iodine_connection.c +82 -117
  50. data/ext/iodine/iodine_defer.c +57 -50
  51. data/ext/iodine/iodine_defer.h +0 -1
  52. data/ext/iodine/iodine_fiobj2rb.h +4 -2
  53. data/ext/iodine/iodine_helpers.c +4 -4
  54. data/ext/iodine/iodine_http.c +25 -32
  55. data/ext/iodine/iodine_json.c +2 -1
  56. data/ext/iodine/iodine_mustache.c +423 -0
  57. data/ext/iodine/iodine_mustache.h +6 -0
  58. data/ext/iodine/iodine_pubsub.c +48 -153
  59. data/ext/iodine/iodine_pubsub.h +5 -4
  60. data/ext/iodine/iodine_rack_io.c +7 -5
  61. data/ext/iodine/iodine_store.c +16 -13
  62. data/ext/iodine/iodine_tcp.c +26 -34
  63. data/ext/iodine/mustache_parser.h +1085 -0
  64. data/ext/iodine/redis_engine.c +740 -646
  65. data/ext/iodine/redis_engine.h +13 -15
  66. data/ext/iodine/resp_parser.h +11 -5
  67. data/ext/iodine/websocket_parser.h +13 -13
  68. data/ext/iodine/websockets.c +240 -393
  69. data/ext/iodine/websockets.h +52 -113
  70. data/lib/iodine.rb +1 -1
  71. data/lib/iodine/mustache.rb +140 -0
  72. data/lib/iodine/version.rb +1 -1
  73. metadata +15 -28
  74. data/ext/iodine/defer.c +0 -566
  75. data/ext/iodine/defer.h +0 -148
  76. data/ext/iodine/evio.c +0 -26
  77. data/ext/iodine/evio.h +0 -161
  78. data/ext/iodine/evio_callbacks.c +0 -26
  79. data/ext/iodine/evio_epoll.c +0 -251
  80. data/ext/iodine/evio_kqueue.c +0 -194
  81. data/ext/iodine/facil.c +0 -2325
  82. data/ext/iodine/facil.h +0 -616
  83. data/ext/iodine/fio_base64.c +0 -277
  84. data/ext/iodine/fio_base64.h +0 -71
  85. data/ext/iodine/fio_llist.h +0 -257
  86. data/ext/iodine/fio_mem.c +0 -675
  87. data/ext/iodine/fio_mem.h +0 -143
  88. data/ext/iodine/fio_random.c +0 -248
  89. data/ext/iodine/fio_random.h +0 -45
  90. data/ext/iodine/fio_sha1.c +0 -362
  91. data/ext/iodine/fio_sha1.h +0 -107
  92. data/ext/iodine/fio_sha2.c +0 -842
  93. data/ext/iodine/fio_sha2.h +0 -169
  94. data/ext/iodine/pubsub.c +0 -867
  95. data/ext/iodine/pubsub.h +0 -221
  96. data/ext/iodine/sock.c +0 -1366
  97. data/ext/iodine/sock.h +0 -566
  98. data/ext/iodine/spnlock.inc +0 -111
@@ -2,7 +2,18 @@
2
2
  Copyright: Boaz Segev, 2017
3
3
  License: MIT
4
4
  */
5
- #include "fio_siphash.h"
5
+ #include <fio_siphash.h>
6
+
7
+ /* *****************************************************************************
8
+
9
+ NOTICE
10
+
11
+ This code won't be linked to the final application when using fio.h and fio.c.
12
+
13
+ The code is here only to allow the FIOBJ library to be extracted from the
14
+ facil.io framework library.
15
+
16
+ ***************************************************************************** */
6
17
 
7
18
  /* *****************************************************************************
8
19
  Hashing (SipHash implementation)
@@ -25,7 +36,8 @@ Hashing (SipHash implementation)
25
36
  #define lrot64(i, bits) \
26
37
  (((uint64_t)(i) << (bits)) | ((uint64_t)(i) >> (64 - (bits))))
27
38
 
28
- uint64_t fio_siphash(const void *data, size_t len) {
39
+ static inline uint64_t fio_siphash_xy(const void *data, size_t len, size_t x,
40
+ size_t y) {
29
41
  /* initialize the 4 words */
30
42
  uint64_t v0 = (0x0706050403020100ULL ^ 0x736f6d6570736575ULL);
31
43
  uint64_t v1 = (0x0f0e0d0c0b0a0908ULL ^ 0x646f72616e646f6dULL);
@@ -56,8 +68,9 @@ uint64_t fio_siphash(const void *data, size_t len) {
56
68
  word.i = sip_local64(*w64);
57
69
  v3 ^= word.i;
58
70
  /* Sip Rounds */
59
- hash_map_SipRound;
60
- hash_map_SipRound;
71
+ for (size_t i = 0; i < x; ++i) {
72
+ hash_map_SipRound;
73
+ }
61
74
  v0 ^= word.i;
62
75
  w64 += 1;
63
76
  len -= 8;
@@ -97,6 +110,9 @@ uint64_t fio_siphash(const void *data, size_t len) {
97
110
  /* Finalization */
98
111
  v2 ^= 0xff;
99
112
  /* d iterations of SipRound */
113
+ for (size_t i = 0; i < y; ++i) {
114
+ hash_map_SipRound;
115
+ }
100
116
  hash_map_SipRound;
101
117
  hash_map_SipRound;
102
118
  hash_map_SipRound;
@@ -106,3 +122,87 @@ uint64_t fio_siphash(const void *data, size_t len) {
106
122
  #undef hash_map_SipRound
107
123
  return v0;
108
124
  }
125
+
126
+ #pragma weak fio_siphash24
127
+ uint64_t __attribute__((weak)) fio_siphash24(const void *data, size_t len) {
128
+ return fio_siphash_xy(data, len, 2, 4);
129
+ }
130
+
131
+ #pragma weak fio_siphash13
132
+ uint64_t __attribute__((weak)) fio_siphash13(const void *data, size_t len) {
133
+ return fio_siphash_xy(data, len, 1, 3);
134
+ }
135
+
136
+ #if defined(DEBUG) && DEBUG == 1
137
+ #include <stdio.h>
138
+ #include <string.h>
139
+ #include <time.h>
140
+
141
+ #if 0
142
+ static void fio_siphash_speed_test(void) {
143
+ /* test based on code from BearSSL with credit to Thomas Pornin */
144
+ uint8_t buffer[8192];
145
+ memset(buffer, 'T', sizeof(buffer));
146
+ /* warmup */
147
+ uint64_t hash = 0;
148
+ for (size_t i = 0; i < 4; i++) {
149
+ hash += fio_siphash(buffer, sizeof(buffer));
150
+ memcpy(buffer, &hash, sizeof(hash));
151
+ }
152
+ /* loop until test runs for more than 2 seconds */
153
+ for (uint64_t cycles = 8192;;) {
154
+ clock_t start, end;
155
+ start = clock();
156
+ for (size_t i = cycles; i > 0; i--) {
157
+ hash += fio_siphash(buffer, sizeof(buffer));
158
+ __asm__ volatile("" ::: "memory");
159
+ }
160
+ end = clock();
161
+ memcpy(buffer, &hash, sizeof(hash));
162
+ if ((end - start) >= (2 * CLOCKS_PER_SEC) ||
163
+ cycles >= ((uint64_t)1 << 62)) {
164
+ fprintf(stderr, "%-20s %8.2f MB/s\n", "fio SipHash24",
165
+ (double)(sizeof(buffer) * cycles) /
166
+ (((end - start) * 1000000.0 / CLOCKS_PER_SEC)));
167
+ break;
168
+ }
169
+ cycles <<= 2;
170
+ }
171
+ /* loop until test runs for more than 2 seconds */
172
+ for (uint64_t cycles = 8192;;) {
173
+ clock_t start, end;
174
+ start = clock();
175
+ for (size_t i = cycles; i > 0; i--) {
176
+ hash += fio_siphash13(buffer, sizeof(buffer));
177
+ __asm__ volatile("" ::: "memory");
178
+ }
179
+ end = clock();
180
+ memcpy(buffer, &hash, sizeof(hash));
181
+ if ((end - start) >= (2 * CLOCKS_PER_SEC) ||
182
+ cycles >= ((uint64_t)1 << 62)) {
183
+ fprintf(stderr, "%-20s %8.2f MB/s\n", "fio SipHash13",
184
+ (double)(sizeof(buffer) * cycles) /
185
+ (((end - start) * 1000000.0 / CLOCKS_PER_SEC)));
186
+ break;
187
+ }
188
+ cycles <<= 2;
189
+ }
190
+ }
191
+
192
+ #endif
193
+
194
+ void fiobj_siphash_test(void) {
195
+ fprintf(stderr, "===================================\n");
196
+ // fio_siphash_speed_test();
197
+ uint64_t result = 0;
198
+ clock_t start;
199
+ start = clock();
200
+ for (size_t i = 0; i < 100000; i++) {
201
+ char *data = "The quick brown fox jumps over the lazy dog ";
202
+ __asm__ volatile("" ::: "memory");
203
+ result += fio_siphash_xy(data, 43, 1, 3);
204
+ }
205
+ fprintf(stderr, "fio 100K SipHash: %lf\n",
206
+ (double)(clock() - start) / CLOCKS_PER_SEC);
207
+ }
208
+ #endif
@@ -8,11 +8,27 @@
8
8
  #include <stdint.h>
9
9
  #include <sys/types.h>
10
10
 
11
+ /**
12
+ * A SipHash variation (2-4).
13
+ */
14
+ uint64_t fio_siphash24(const void *data, size_t len);
15
+
16
+ /**
17
+ * A SipHash 1-3 variation.
18
+ */
19
+ uint64_t fio_siphash13(const void *data, size_t len);
20
+
11
21
  /**
12
22
  * The Hashing function used by dynamic facil.io objects.
13
23
  *
14
- * Currently implemented using SipHash.
24
+ * Currently implemented using SipHash 1-3.
15
25
  */
16
- uint64_t fio_siphash(const void *data, size_t len);
26
+ #define fio_siphash(data, length) fio_siphash13((data), (length))
17
27
 
28
+ #if defined(DEBUG) && DEBUG
29
+ void fiobj_siphash_test(void);
30
+ #else
31
+ #define fiobj_siphash_test()
18
32
  #endif
33
+
34
+ #endif /* H_FIO_SIPHASH_H */
@@ -0,0 +1,1218 @@
1
+ #ifndef H_FIO_STRING_H
2
+ /*
3
+ Copyright: Boaz Segev, 2018
4
+ License: MIT
5
+ */
6
+
7
+ /**
8
+ * A Dynamic String C library for ease of use and binary strings.
9
+ *
10
+ * This is different from the fio.h library in the sense that it does NOT
11
+ * include a built-in reference counter.
12
+ *
13
+ * The string is a simple byte string which is compatible with binary data (NUL
14
+ * is a valid byte).
15
+ *
16
+ * Example use:
17
+ *
18
+ * fio_str_s str = FIO_STR_INIT; // container on the stack.
19
+ * fio_str_write(&str, "hello", 5); // add / remove / read data...
20
+ * printf("String: %s", fio_str_data(&str)); // print data
21
+ * fio_str_free(&str) // free the data - NOT the container.
22
+ *
23
+ * Should work with both 32bit and 64bit architectures.
24
+ */
25
+ #define H_FIO_STRING_H
26
+
27
+ #ifndef _GNU_SOURCE
28
+ #define _GNU_SOURCE
29
+ #endif
30
+
31
+ #if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
32
+ #include <fcntl.h>
33
+ #include <sys/stat.h>
34
+ #include <unistd.h>
35
+ #endif
36
+
37
+ #include <errno.h>
38
+ #include <stdarg.h>
39
+ #include <stdint.h>
40
+ #include <stdio.h>
41
+ #include <stdlib.h>
42
+ #include <string.h>
43
+ #include <strings.h>
44
+
45
+ #ifndef FIO_FUNC
46
+ #define FIO_FUNC static __attribute__((unused))
47
+ #endif
48
+
49
+ #ifndef FIO_ASSERT_ALLOC
50
+ /** Tests for an allocation failure. The behavior can be overridden. */
51
+ #define FIO_ASSERT_ALLOC(ptr) \
52
+ if (!(ptr)) { \
53
+ perror("FATAL ERROR: no memory (for string allocation)"); \
54
+ exit(errno); \
55
+ }
56
+ #endif
57
+
58
+ /* *****************************************************************************
59
+ String API - Initialization and Destruction
60
+ ***************************************************************************** */
61
+
62
+ /**
63
+ * The `fio_str_s` type should be considered opaque.
64
+ *
65
+ * The type's attributes should be accessed ONLY through the accessor functions:
66
+ * `fio_str_state`, `fio_str_len`, `fio_str_data`, `fio_str_capa`, etc'.
67
+ *
68
+ * Note: when the `small` flag is present, the structure is ignored and used as
69
+ * raw memory for a small String (no additional allocation). This changes the
70
+ * String's behavior drastically and requires that the accessor functions be
71
+ * used.
72
+ */
73
+ typedef struct {
74
+ uint8_t small; /* Flag indicating the String is small and self-contained */
75
+ uint8_t frozen; /* Flag indicating the String is frozen (don't edit) */
76
+ uint8_t reserved[sizeof(size_t) - (sizeof(uint8_t) * 2)]; /* padding */
77
+ size_t capa; /* Known capacity for longer Strings */
78
+ size_t len; /* String length for longer Strings */
79
+ char *data; /* Data for longer Strings */
80
+ } fio_str_s;
81
+
82
+ /**
83
+ * This value should be used for initialization. For example:
84
+ *
85
+ * // on the stack
86
+ * fio_str_s str = FIO_STR_INIT;
87
+ *
88
+ * // or on the heap
89
+ * fio_str_s *str = malloc(sizeof(*str);
90
+ * *str = FIO_STR_INIT;
91
+ *
92
+ * Remember to cleanup:
93
+ *
94
+ * // on the stack
95
+ * fio_str_free(&str);
96
+ *
97
+ * // or on the heap
98
+ * fio_str_free(str);
99
+ * free(str);
100
+ */
101
+ #define FIO_STR_INIT ((fio_str_s){.data = NULL, .small = 1})
102
+
103
+ /**
104
+ * This macro allows the container to be initialized with existing data, as long
105
+ * as it's memory was allocated using `malloc`.
106
+ *
107
+ * The `capacity` value should exclude the NUL character (if exists).
108
+ */
109
+ #define FIO_STR_INIT_EXISTING(buffer, length, capacity) \
110
+ ((fio_str_s){.data = (buffer), .len = (length), .capa = (capacity)})
111
+
112
+ /**
113
+ * Frees the String's resources and _reinitializes the container_.
114
+ *
115
+ * Note: if the container isn't allocated on the stack, it should be freed
116
+ * separately using `free(s)`.
117
+ */
118
+ inline FIO_FUNC void fio_str_free(fio_str_s *s);
119
+
120
+ /* *****************************************************************************
121
+ String API - String state (data pointers, length, capacity, etc')
122
+ ***************************************************************************** */
123
+
124
+ #ifndef FIO_STR_INFO_TYPE
125
+ /** A string information type, reports information about a C string. */
126
+ typedef struct fio_str_info_s {
127
+ size_t capa; /* Buffer capacity, if the string is writable. */
128
+ size_t len; /* String length. */
129
+ char *data; /* String's first byte. */
130
+ } fio_str_info_s;
131
+ #define FIO_STR_INFO_TYPE
132
+ #endif
133
+
134
+ /** Returns the String's complete state (capacity, length and pointer). */
135
+ inline FIO_FUNC fio_str_info_s fio_str_state(const fio_str_s *s);
136
+
137
+ /** Returns the String's length in bytes. */
138
+ inline FIO_FUNC size_t fio_str_len(fio_str_s *s);
139
+
140
+ /** Returns a pointer (`char *`) to the String's content. */
141
+ inline FIO_FUNC char *fio_str_data(fio_str_s *s);
142
+
143
+ /** Returns a byte pointer (`uint8_t *`) to the String's unsigned content. */
144
+ #define fio_str_bytes(s) ((uint8_t *)fio_str_data((s)))
145
+
146
+ /** Returns the String's existing capacity (total used & available memory). */
147
+ inline FIO_FUNC size_t fio_str_capa(fio_str_s *s);
148
+
149
+ /**
150
+ * Sets the new String size without reallocating any memory (limited by
151
+ * existing capacity).
152
+ *
153
+ * Returns the updated state of the String.
154
+ *
155
+ * Note: When shrinking, any existing data beyond the new size may be corrupted.
156
+ */
157
+ inline FIO_FUNC fio_str_info_s fio_str_resize(fio_str_s *s, size_t size);
158
+
159
+ /**
160
+ * Clears the string (retaining the existing capacity).
161
+ */
162
+ #define fio_str_clear(s) fio_str_resize((s), 0)
163
+
164
+ /* *****************************************************************************
165
+ String API - Memory management
166
+ ***************************************************************************** */
167
+
168
+ /**
169
+ * Performs a best attempt at minimizing memory consumption.
170
+ *
171
+ * Actual effects depend on the underlying memory allocator and it's
172
+ * implementation. Not all allocators will free any memory.
173
+ */
174
+ inline FIO_FUNC void fio_str_compact(fio_str_s *s);
175
+
176
+ /**
177
+ * Requires the String to have at least `needed` capacity. Returns the current
178
+ * state of the String.
179
+ */
180
+ FIO_FUNC fio_str_info_s fio_str_capa_assert(fio_str_s *s, size_t needed);
181
+
182
+ /* *****************************************************************************
183
+ String API - UTF-8 State
184
+ ***************************************************************************** */
185
+
186
+ /** Returns 1 if the String is UTF-8 valid and 0 if not. */
187
+ inline FIO_FUNC size_t fio_str_utf8_valid(fio_str_s *s);
188
+
189
+ /** Returns the String's length in UTF-8 characters. */
190
+ FIO_FUNC size_t fio_str_utf8_len(fio_str_s *s);
191
+
192
+ /**
193
+ * Takes a UTF-8 character selection information (UTF-8 position and length) and
194
+ * updates the same variables so they reference the raw byte slice information.
195
+ *
196
+ * If the String isn't UTF-8 valid up to the requested selection, than `pos`
197
+ * will be updated to `-1` otherwise values are always positive.
198
+ *
199
+ * The returned `len` value may be shorter than the original if there wasn't
200
+ * enough data left to accomodate the requested length. When a `len` value of
201
+ * `0` is returned, this means that `pos` marks the end of the String.
202
+ *
203
+ * Returns -1 on error and 0 on success.
204
+ */
205
+ FIO_FUNC int fio_str_utf8_select(fio_str_s *s, intptr_t *pos, size_t *len);
206
+
207
+ /**
208
+ * Advances the `ptr` by one utf-8 character, placing the value of the UTF-8
209
+ * character into the i32 variable (which must be a signed integer with 32bits
210
+ * or more). On error, `i32` will be equal to `-1` and `ptr` will not step
211
+ * forwards.
212
+ *
213
+ * The `end` value is only used for overflow protection.
214
+ *
215
+ * This helper macro is used internally but left exposed for external use.
216
+ */
217
+ #define FIO_STR_UTF8_CODE_POINT(ptr, end, i32)
218
+
219
+ /* *****************************************************************************
220
+ String API - Content Manipulation and Review
221
+ ***************************************************************************** */
222
+
223
+ /**
224
+ * Writes data at the end of the String (similar to `fio_str_insert` with the
225
+ * argument `pos == -1`).
226
+ */
227
+ inline FIO_FUNC fio_str_info_s fio_str_write(fio_str_s *s, const void *src,
228
+ size_t src_len);
229
+
230
+ /**
231
+ * Writes a number at the end of the String using normal base 10 notation.
232
+ */
233
+ inline FIO_FUNC fio_str_info_s fio_str_write_i(fio_str_s *s, int64_t num);
234
+
235
+ /**
236
+ * Appens the `src` String to the end of the `dest` String.
237
+ *
238
+ * If `src` is empty, the resulting Strings will be equal.
239
+ */
240
+ inline FIO_FUNC fio_str_info_s fio_str_concat(fio_str_s *dest,
241
+ fio_str_s const *src);
242
+ /** Alias for fio_str_concat */
243
+ #define fio_str_join(dest, src) fio_str_concat((dest), (src))
244
+
245
+ /**
246
+ * Replaces the data in the String - replacing `old_len` bytes starting at
247
+ * `start_pos`, with the data at `src` (`src_len` bytes long).
248
+ *
249
+ * Negative `start_pos` values are calculated backwards, `-1` == end of String.
250
+ *
251
+ * When `old_len` is zero, the function will insert the data at `start_pos`.
252
+ *
253
+ * If `src_len == 0` than `src` will be ignored and the data marked for
254
+ * replacement will be erased.
255
+ */
256
+ inline FIO_FUNC fio_str_info_s fio_str_replace(fio_str_s *s, intptr_t start_pos,
257
+ size_t old_len, const void *src,
258
+ size_t src_len);
259
+
260
+ /**
261
+ * Writes to the String using a vprintf like interface.
262
+ *
263
+ * Data is written to the end of the String.
264
+ */
265
+ FIO_FUNC fio_str_info_s fio_str_vprintf(fio_str_s *s, const char *format,
266
+ va_list argv);
267
+
268
+ /**
269
+ * Writes to the String using a printf like interface.
270
+ *
271
+ * Data is written to the end of the String.
272
+ */
273
+ FIO_FUNC fio_str_info_s fio_str_printf(fio_str_s *s, const char *format, ...);
274
+
275
+ /**
276
+ * Opens the file `filename` and pastes it's contents (or a slice ot it) at the
277
+ * end of the String. If `limit == 0`, than the data will be read until EOF.
278
+ *
279
+ * If the file can't be located, opened or read, or if `start_at` is beyond
280
+ * the EOF position, NULL is returned in the state's `data` field.
281
+ *
282
+ * Works on POSIX only.
283
+ */
284
+ inline FIO_FUNC fio_str_info_s fio_str_readfile(fio_str_s *s,
285
+ const char *filename,
286
+ intptr_t start_at,
287
+ intptr_t limit);
288
+
289
+ /**
290
+ * Prevents further manipulations to the String's content.
291
+ */
292
+ inline FIO_FUNC void fio_str_freeze(fio_str_s *s);
293
+
294
+ /**
295
+ * Binary comparison returns `1` if both strings are equal and `0` if not.
296
+ */
297
+ inline FIO_FUNC int fio_str_iseq(const fio_str_s *str1, const fio_str_s *str2);
298
+
299
+ /* *****************************************************************************
300
+
301
+
302
+ IMPLEMENTATION
303
+
304
+
305
+ ***************************************************************************** */
306
+
307
+ /* *****************************************************************************
308
+ Implementation - String state (data pointers, length, capacity, etc')
309
+ ***************************************************************************** */
310
+
311
+ /* the capacity when the string is stored in the container itself */
312
+ #define FIO_STR_SMALL_CAPA \
313
+ (sizeof(fio_str_s) - (size_t)(&((fio_str_s *)0)->reserved))
314
+
315
+ typedef struct {
316
+ uint8_t small;
317
+ uint8_t frozen;
318
+ char data[1];
319
+ } fio_str__small_s;
320
+
321
+ /** Returns the String's state (capacity, length and pointer). */
322
+ inline FIO_FUNC fio_str_info_s fio_str_state(const fio_str_s *s) {
323
+ if (!s)
324
+ return (fio_str_info_s){.capa = 0};
325
+ return (s->small || !s->data)
326
+ ? (fio_str_info_s){.capa =
327
+ (s->frozen ? 0 : (FIO_STR_SMALL_CAPA - 1)),
328
+ .len = (size_t)(s->small >> 1),
329
+ .data = ((fio_str__small_s *)s)->data}
330
+ : (fio_str_info_s){.capa = (s->frozen ? 0 : s->capa),
331
+ .len = s->len,
332
+ .data = s->data};
333
+ }
334
+
335
+ /**
336
+ * Frees the String's resources and reinitializes the container.
337
+ *
338
+ * Note: if the container isn't allocated on the stack, it should be freed
339
+ * separately using `free(s)`.
340
+ */
341
+ inline FIO_FUNC void fio_str_free(fio_str_s *s) {
342
+ if (!s->small)
343
+ free(s->data);
344
+ *s = FIO_STR_INIT;
345
+ }
346
+
347
+ /** Returns the String's length in bytes. */
348
+ inline FIO_FUNC size_t fio_str_len(fio_str_s *s) {
349
+ return (s->small || !s->data) ? (s->small >> 1) : s->len;
350
+ }
351
+
352
+ /** Returns a pointer (`char *`) to the String's content. */
353
+ inline FIO_FUNC char *fio_str_data(fio_str_s *s) {
354
+ return (s->small || !s->data) ? (((fio_str__small_s *)s)->data) : s->data;
355
+ }
356
+
357
+ /** Returns the String's existing capacity (allocated memory). */
358
+ inline FIO_FUNC size_t fio_str_capa(fio_str_s *s) {
359
+ return (s->small || !s->data) ? (FIO_STR_SMALL_CAPA - 1) : s->capa;
360
+ }
361
+
362
+ /**
363
+ * Sets the new String size without reallocating any memory (limited by
364
+ * existing capacity).
365
+ *
366
+ * Returns the updated state of the String.
367
+ *
368
+ * Note: When shrinking, any existing data beyond the new size may be corrupted.
369
+ */
370
+ inline FIO_FUNC fio_str_info_s fio_str_resize(fio_str_s *s, size_t size) {
371
+ if (!s || s->frozen) {
372
+ return fio_str_state(s);
373
+ }
374
+ fio_str_capa_assert(s, size);
375
+ if (s->small || !s->data) {
376
+ s->small = (uint8_t)(((size << 1) | 1) & 0xFF);
377
+ ((fio_str__small_s *)s)->data[size] = 0;
378
+ return (fio_str_info_s){.capa = (FIO_STR_SMALL_CAPA - 1),
379
+ .len = size,
380
+ .data = ((fio_str__small_s *)s)->data};
381
+ }
382
+ s->len = size;
383
+ s->data[size] = 0;
384
+ return (fio_str_info_s){.capa = s->capa, .len = size, .data = s->data};
385
+ }
386
+
387
+ /* *****************************************************************************
388
+ Implementation - Memory management
389
+ ***************************************************************************** */
390
+
391
+ /**
392
+ * Rounds up allocated capacity to the closest 2 words byte boundary (leaving 1
393
+ * byte space for the NUL byte).
394
+ *
395
+ * This shouldn't effect actual allocation size and should only minimize the
396
+ * effects of the memory allocator's alignment rounding scheme.
397
+ *
398
+ * To clarify:
399
+ *
400
+ * Memory allocators are required to allocate memory on the minimal alignment
401
+ * required by the largest type (`long double`), which usually results in memory
402
+ * allocations using this alignment as a minimal spacing.
403
+ *
404
+ * For example, on 64 bit architectures, it's likely that `malloc(18)` will
405
+ * allocate the same amount of memory as `malloc(32)` due to alignment.
406
+ *
407
+ * In fact, on some allocators (i.e., jemalloc), spacing increases for larger
408
+ * allocations - meaning the allocator will round up to more than 16 bytes, as
409
+ * noted here: http://jemalloc.net/jemalloc.3.html#size_classes
410
+ *
411
+ * Note that this increased spacing, doesn't occure with facil.io's `fio_mem.h`
412
+ * allocator, since it uses 16 byte alignment right up until allocations are
413
+ * routed directly to `mmap` (due to their size, usually over 12KB).
414
+ */
415
+ #define ROUND_UP_CAPA_2WORDS(num) \
416
+ (((num + 1) & (sizeof(long double) - 1)) \
417
+ ? ((num + 1) | (sizeof(long double) - 1)) \
418
+ : (num))
419
+ /**
420
+ * Requires the String to have at least `needed` capacity. Returns the current
421
+ * state of the String.
422
+ */
423
+ FIO_FUNC fio_str_info_s fio_str_capa_assert(fio_str_s *s, size_t needed) {
424
+ if (!s)
425
+ return (fio_str_info_s){.capa = 0};
426
+ char *tmp;
427
+ if (s->small || !s->data) {
428
+ goto is_small;
429
+ }
430
+ if (needed > s->capa) {
431
+ needed = ROUND_UP_CAPA_2WORDS(needed);
432
+ tmp = (char *)realloc(s->data, needed + 1);
433
+ FIO_ASSERT_ALLOC(tmp);
434
+ s->capa = needed;
435
+ s->data = tmp;
436
+ s->data[needed] = 0;
437
+ }
438
+ return (fio_str_info_s){
439
+ .capa = (s->frozen ? 0 : s->capa), .len = s->len, .data = s->data};
440
+
441
+ is_small:
442
+ /* small string (string data is within the container) */
443
+ if (needed < FIO_STR_SMALL_CAPA) {
444
+ return (fio_str_info_s){.capa = (s->frozen ? 0 : (FIO_STR_SMALL_CAPA - 1)),
445
+ .len = (size_t)(s->small >> 1),
446
+ .data = ((fio_str__small_s *)s)->data};
447
+ }
448
+ needed = ROUND_UP_CAPA_2WORDS(needed);
449
+ tmp = (char *)malloc(needed + 1);
450
+ FIO_ASSERT_ALLOC(tmp);
451
+ const size_t existing_len = (size_t)((s->small >> 1) & 0xFF);
452
+ if (existing_len) {
453
+ memcpy(tmp, ((fio_str__small_s *)s)->data, existing_len + 1);
454
+ } else {
455
+ tmp[0] = 0;
456
+ }
457
+ *s = (fio_str_s){
458
+ .small = 0,
459
+ .capa = needed,
460
+ .len = existing_len,
461
+ .data = tmp,
462
+ };
463
+ return (fio_str_info_s){
464
+ .capa = (s->frozen ? 0 : needed), .len = existing_len, .data = s->data};
465
+ }
466
+
467
+ /** Performs a best attempt at minimizing memory consumption. */
468
+ inline FIO_FUNC void fio_str_compact(fio_str_s *s) {
469
+ if (!s || (s->small || !s->data))
470
+ return;
471
+ char *tmp;
472
+ if (s->len < FIO_STR_SMALL_CAPA)
473
+ goto shrink2small;
474
+ tmp = realloc(s->data, s->len + 1);
475
+ FIO_ASSERT_ALLOC(tmp);
476
+ s->data = tmp;
477
+ s->capa = s->len;
478
+ return;
479
+
480
+ shrink2small:
481
+ /* move the string into the container */
482
+ tmp = s->data;
483
+ size_t len = s->len;
484
+ *s = (fio_str_s){.small = (uint8_t)(((len << 1) | 1) & 0xFF),
485
+ .frozen = s->frozen};
486
+ if (len) {
487
+ memcpy(((fio_str__small_s *)s)->data, tmp, len + 1);
488
+ }
489
+ free(tmp);
490
+ }
491
+
492
+ /* *****************************************************************************
493
+ Implementation - UTF-8 State
494
+ ***************************************************************************** */
495
+
496
+ /**
497
+ * Maps the last 5 bits in a byte (0b11111xxx) to a UTF-8 codepoint length.
498
+ *
499
+ * Codepoint length 0 == error.
500
+ *
501
+ * The first valid length can be any value between 1 to 4.
502
+ *
503
+ * An intermidiate (second, third or forth) valid length must be 5.
504
+ *
505
+ * To map was populated using the following Ruby script:
506
+ *
507
+ * map = []; 32.times { map << 0 }; (0..0b1111).each {|i| map[i] = 1} ;
508
+ * (0b10000..0b10111).each {|i| map[i] = 5} ;
509
+ * (0b11000..0b11011).each {|i| map[i] = 2} ;
510
+ * (0b11100..0b11101).each {|i| map[i] = 3} ;
511
+ * map[0b11110] = 4; map;
512
+ */
513
+ static uint8_t fio_str_utf8_map[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
514
+ 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5,
515
+ 5, 5, 2, 2, 2, 2, 3, 3, 4, 0};
516
+
517
+ #undef FIO_STR_UTF8_CODE_POINT
518
+ /**
519
+ * Advances the `ptr` by one utf-8 character, placing the value of the UTF-8
520
+ * character into the i32 variable (which must be a signed integer with 32bits
521
+ * or more). On error, `i32` will be equal to `-1` and `ptr` will not step
522
+ * forwards.
523
+ *
524
+ * The `end` value is only used for overflow protection.
525
+ */
526
+ #define FIO_STR_UTF8_CODE_POINT(ptr, end, i32) \
527
+ do { \
528
+ switch (fio_str_utf8_map[((uint8_t *)(ptr))[0] >> 3]) { \
529
+ case 1: \
530
+ (i32) = ((uint8_t *)(ptr))[0]; \
531
+ ++(ptr); \
532
+ break; \
533
+ case 2: \
534
+ if (((ptr) + 2 > (end)) || \
535
+ fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5) { \
536
+ (i32) = -1; \
537
+ break; \
538
+ } \
539
+ (i32) = \
540
+ ((((uint8_t *)(ptr))[0] & 31) << 6) | (((uint8_t *)(ptr))[1] & 63); \
541
+ (ptr) += 2; \
542
+ break; \
543
+ case 3: \
544
+ if (((ptr) + 3 > (end)) || \
545
+ fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 || \
546
+ fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5) { \
547
+ (i32) = -1; \
548
+ break; \
549
+ } \
550
+ (i32) = ((((uint8_t *)(ptr))[0] & 15) << 12) | \
551
+ ((((uint8_t *)(ptr))[1] & 63) << 6) | \
552
+ (((uint8_t *)(ptr))[2] & 63); \
553
+ (ptr) += 3; \
554
+ break; \
555
+ case 4: \
556
+ if (((ptr) + 4 > (end)) || \
557
+ fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 || \
558
+ fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5 || \
559
+ fio_str_utf8_map[((uint8_t *)(ptr))[3] >> 3] != 5) { \
560
+ (i32) = -1; \
561
+ break; \
562
+ } \
563
+ (i32) = ((((uint8_t *)(ptr))[0] & 7) << 18) | \
564
+ ((((uint8_t *)(ptr))[1] & 63) << 12) | \
565
+ ((((uint8_t *)(ptr))[2] & 63) << 6) | \
566
+ (((uint8_t *)(ptr))[3] & 63); \
567
+ (ptr) += 4; \
568
+ break; \
569
+ default: \
570
+ (i32) = -1; \
571
+ break; \
572
+ } \
573
+ } while (0);
574
+
575
+ /** Returns 1 if the String is UTF-8 valid and 0 if not. */
576
+ inline FIO_FUNC size_t fio_str_utf8_valid(fio_str_s *s) {
577
+ if (!s)
578
+ return 0;
579
+ fio_str_info_s state = fio_str_state(s);
580
+ if (!state.len)
581
+ return 1;
582
+ char *const end = state.data + state.len;
583
+ int32_t c = 0;
584
+ do {
585
+ FIO_STR_UTF8_CODE_POINT(state.data, end, c);
586
+ } while (c > 0 && state.data < end);
587
+ return state.data == end && c >= 0;
588
+ }
589
+
590
+ /** Returns the String's length in UTF-8 characters. */
591
+ FIO_FUNC size_t fio_str_utf8_len(fio_str_s *s) {
592
+ fio_str_info_s state = fio_str_state(s);
593
+ if (!state.len)
594
+ return 0;
595
+ char *end = state.data + state.len;
596
+ size_t utf8len = 0;
597
+ int32_t c = 0;
598
+ do {
599
+ ++utf8len;
600
+ FIO_STR_UTF8_CODE_POINT(state.data, end, c);
601
+ } while (c > 0 && state.data < end);
602
+ if (state.data != end || c == -1) {
603
+ /* invalid */
604
+ return 0;
605
+ }
606
+ return utf8len;
607
+ }
608
+
609
+ /**
610
+ * Takes a UTF-8 character selection information (UTF-8 position and length) and
611
+ * updates the same variables so they reference the raw byte slice information.
612
+ *
613
+ * If the String isn't UTF-8 valid up to the requested selection, than `pos`
614
+ * will be updated to `-1` otherwise values are always positive.
615
+ *
616
+ * The returned `len` value may be shorter than the original if there wasn't
617
+ * enough data left to accomodate the requested length. When a `len` value of
618
+ * `0` is returned, this means that `pos` marks the end of the String.
619
+ *
620
+ * Returns -1 on error and 0 on success.
621
+ */
622
+ FIO_FUNC int fio_str_utf8_select(fio_str_s *s, intptr_t *pos, size_t *len) {
623
+ fio_str_info_s state = fio_str_state(s);
624
+ if (!state.data)
625
+ goto error;
626
+ if (!state.len || *pos == -1)
627
+ goto at_end;
628
+
629
+ int32_t c = 0;
630
+ char *p = state.data;
631
+ char *const end = state.data + state.len;
632
+ size_t start;
633
+
634
+ if (*pos) {
635
+ if ((*pos) > 0) {
636
+ start = *pos;
637
+ while (start && p < end && c >= 0) {
638
+ FIO_STR_UTF8_CODE_POINT(p, end, c);
639
+ --start;
640
+ }
641
+ if (c == -1)
642
+ goto error;
643
+ if (start || p >= end)
644
+ goto at_end;
645
+ *pos = p - state.data;
646
+ } else {
647
+ /* walk backwards */
648
+ p = state.data + state.len - 1;
649
+ c = 0;
650
+ ++*pos;
651
+ do {
652
+ switch (fio_str_utf8_map[((uint8_t *)p)[0] >> 3]) {
653
+ case 5:
654
+ ++c;
655
+ break;
656
+ case 4:
657
+ if (c != 3)
658
+ goto error;
659
+ c = 0;
660
+ ++(*pos);
661
+ break;
662
+ case 3:
663
+ if (c != 2)
664
+ goto error;
665
+ c = 0;
666
+ ++(*pos);
667
+ break;
668
+ case 2:
669
+ if (c != 1)
670
+ goto error;
671
+ c = 0;
672
+ ++(*pos);
673
+ break;
674
+ case 1:
675
+ if (c)
676
+ goto error;
677
+ ++(*pos);
678
+ break;
679
+ default:
680
+ goto error;
681
+ }
682
+ --p;
683
+ } while (p > state.data && *pos);
684
+ if (c)
685
+ goto error;
686
+ ++p; /* There's always an extra back-step */
687
+ *pos = (p - state.data);
688
+ }
689
+ }
690
+
691
+ /* find end */
692
+ start = *len;
693
+ while (start && p < end && c >= 0) {
694
+ FIO_STR_UTF8_CODE_POINT(p, end, c);
695
+ --start;
696
+ }
697
+ if (c == -1 || p > end)
698
+ goto error;
699
+ *len = p - (state.data + (*pos));
700
+ return 0;
701
+
702
+ at_end:
703
+ *pos = state.len;
704
+ *len = 0;
705
+ return 0;
706
+ error:
707
+ *pos = -1;
708
+ *len = 0;
709
+ return -1;
710
+ }
711
+
712
+ /* *****************************************************************************
713
+ Implementation - Content Manipulation and Review
714
+ ***************************************************************************** */
715
+
716
+ /**
717
+ * Writes data at the end of the String (similar to `fio_str_insert` with the
718
+ * argument `pos == -1`).
719
+ */
720
+ inline FIO_FUNC fio_str_info_s fio_str_write(fio_str_s *s, const void *src,
721
+ size_t src_len) {
722
+ if (!s || !src_len || !src || s->frozen)
723
+ return fio_str_state(s);
724
+ fio_str_info_s state = fio_str_resize(s, src_len + fio_str_len(s));
725
+ memcpy(state.data + (state.len - src_len), src, src_len);
726
+ return state;
727
+ }
728
+
729
+ /**
730
+ * Writes a number at the end of the String using normal base 10 notation.
731
+ */
732
+ inline FIO_FUNC fio_str_info_s fio_str_write_i(fio_str_s *s, int64_t num) {
733
+ if (!s || s->frozen)
734
+ return fio_str_state(s);
735
+ char buf[22];
736
+ fio_str_info_s i;
737
+ if (!num)
738
+ goto zero;
739
+ uint64_t l = 0;
740
+ uint8_t neg;
741
+ if ((neg = (num < 0))) {
742
+ num = 0 - num;
743
+ neg = 1;
744
+ }
745
+ while (num) {
746
+ uint64_t t = num / 10;
747
+ buf[l++] = '0' + (num - (t * 10));
748
+ num = t;
749
+ }
750
+ if (neg) {
751
+ buf[l++] = '-';
752
+ }
753
+ i = fio_str_resize(s, fio_str_len(s) + l);
754
+
755
+ while (l) {
756
+ --l;
757
+ i.data[i.len - (l + 1)] = buf[l];
758
+ }
759
+ return i;
760
+
761
+ zero:
762
+ i = fio_str_resize(s, fio_str_len(s) + 1);
763
+ i.data[i.len - 1] = '0';
764
+ return i;
765
+ }
766
+ /**
767
+ * Appens the `src` String to the end of the `dest` String.
768
+ */
769
+ inline FIO_FUNC fio_str_info_s fio_str_concat(fio_str_s *dest,
770
+ fio_str_s const *src) {
771
+ if (!dest || !src || dest->frozen)
772
+ return fio_str_state(dest);
773
+ fio_str_info_s src_state = fio_str_state(src);
774
+ if (!src_state.len)
775
+ return fio_str_state(dest);
776
+ fio_str_info_s state =
777
+ fio_str_resize(dest, src_state.len + fio_str_len(dest));
778
+ memcpy(state.data + state.len - src_state.len, src_state.data, src_state.len);
779
+ return state;
780
+ }
781
+
782
+ /**
783
+ * Replaces the data in the String - replacing `old_len` bytes starting at
784
+ * `start_pos`, with the data at `src` (`src_len` bytes long).
785
+ *
786
+ * Negative `start_pos` values are calculated backwards, `-1` == end of String.
787
+ *
788
+ * When `old_len` is zero, the function will insert the data at `start_pos`.
789
+ *
790
+ * If `src_len == 0` than `src` will be ignored and the data marked for
791
+ * replacement will be erased.
792
+ */
793
+ inline FIO_FUNC fio_str_info_s fio_str_replace(fio_str_s *s, intptr_t start_pos,
794
+ size_t old_len, const void *src,
795
+ size_t src_len) {
796
+ fio_str_info_s state = fio_str_state(s);
797
+ if (!s || s->frozen || (!old_len && !src_len))
798
+ return state;
799
+
800
+ if (start_pos < 0) {
801
+ /* backwards position indexing */
802
+ start_pos += s->len + 1;
803
+ if (start_pos < 0)
804
+ start_pos = 0;
805
+ }
806
+
807
+ if (start_pos + old_len >= state.len) {
808
+ /* old_len overflows the end of the String */
809
+ if (s->small || !s->data) {
810
+ s->small = 1 | ((size_t)((start_pos << 1) & 0xFF));
811
+ } else {
812
+ s->len = start_pos;
813
+ }
814
+ return fio_str_write(s, src, src_len);
815
+ }
816
+
817
+ /* data replacement is now always in the middle (or start) of the String */
818
+ const size_t new_size = state.len + (src_len - old_len);
819
+
820
+ if (old_len != src_len) {
821
+ /* there's an offset requiring an adjustment */
822
+ if (old_len < src_len) {
823
+ /* make room for new data */
824
+ const size_t offset = src_len - old_len;
825
+ state = fio_str_resize(s, state.len + offset);
826
+ }
827
+ memmove(state.data + start_pos + src_len, state.data + start_pos + old_len,
828
+ (state.len - start_pos) - old_len);
829
+ }
830
+ if (src_len) {
831
+ memcpy(state.data + start_pos, src, src_len);
832
+ }
833
+
834
+ return fio_str_resize(s, new_size);
835
+ }
836
+
837
+ /** Writes to the String using a vprintf like interface. */
838
+ FIO_FUNC __attribute__((format(printf, 2, 0))) fio_str_info_s
839
+ fio_str_vprintf(fio_str_s *s, const char *format, va_list argv) {
840
+ va_list argv_cpy;
841
+ va_copy(argv_cpy, argv);
842
+ int len = vsnprintf(NULL, 0, format, argv_cpy);
843
+ va_end(argv_cpy);
844
+ if (len <= 0)
845
+ return fio_str_state(s);
846
+ fio_str_info_s state = fio_str_resize(s, len + fio_str_len(s));
847
+ vsnprintf(state.data + (state.len - len), len + 1, format, argv);
848
+ return state;
849
+ }
850
+
851
+ /** Writes to the String using a printf like interface. */
852
+ FIO_FUNC __attribute__((format(printf, 2, 3))) fio_str_info_s
853
+ fio_str_printf(fio_str_s *s, const char *format, ...) {
854
+ va_list argv;
855
+ va_start(argv, format);
856
+ fio_str_info_s state = fio_str_vprintf(s, format, argv);
857
+ va_end(argv);
858
+ return state;
859
+ }
860
+
861
+ /**
862
+ * Opens the file `filename` and pastes it's contents (or a slice ot it) at the
863
+ * end of the String. If `limit == 0`, than the data will be read until EOF.
864
+ *
865
+ * If the file can't be located, opened or read, or if `start_at` is beyond
866
+ * the EOF position, NULL is returned in the state's `data` field.
867
+ */
868
+ inline FIO_FUNC fio_str_info_s fio_str_readfile(fio_str_s *s,
869
+ const char *filename,
870
+ intptr_t start_at,
871
+ intptr_t limit) {
872
+ fio_str_info_s state = {.data = NULL};
873
+ #if defined(__unix__) || defined(__linux__) || defined(__APPLE__)
874
+ /* POSIX implementations. */
875
+ if (filename == NULL)
876
+ return state;
877
+ struct stat f_data;
878
+ int file = -1;
879
+ char *path = NULL;
880
+ size_t path_len = 0;
881
+
882
+ if (filename[0] == '~' && (filename[1] == '/' || filename[1] == '\\')) {
883
+ char *home = getenv("HOME");
884
+ if (home) {
885
+ size_t filename_len = strlen(filename);
886
+ size_t home_len = strlen(home);
887
+ if ((home_len + filename_len) >= (1 << 16)) {
888
+ /* too long */
889
+ return state;
890
+ }
891
+ if (home[home_len - 1] == '/' || home[home_len - 1] == '\\')
892
+ --home_len;
893
+ path_len = home_len + filename_len - 1;
894
+ path = malloc(path_len + 1);
895
+ FIO_ASSERT_ALLOC(path);
896
+ memcpy(path, home, home_len);
897
+ memcpy(path + home_len, filename + 1, filename_len);
898
+ path[path_len] = 0;
899
+ filename = path;
900
+ }
901
+ }
902
+
903
+ if (stat(filename, &f_data)) {
904
+ goto finish;
905
+ }
906
+
907
+ if (f_data.st_size <= 0 || start_at >= f_data.st_size) {
908
+ state = fio_str_state(s);
909
+ goto finish;
910
+ }
911
+
912
+ file = open(filename, O_RDONLY);
913
+ if (-1 == file)
914
+ goto finish;
915
+
916
+ if (start_at < 0) {
917
+ start_at = f_data.st_size + start_at;
918
+ if (start_at < 0)
919
+ start_at = 0;
920
+ }
921
+
922
+ if (limit <= 0 || f_data.st_size < (limit + start_at))
923
+ limit = f_data.st_size - start_at;
924
+
925
+ const size_t org_len = fio_str_len(s);
926
+ state = fio_str_resize(s, org_len + limit);
927
+ if (pread(file, state.data + org_len, limit, start_at) != (ssize_t)limit) {
928
+ close(file);
929
+ fio_str_resize(s, org_len);
930
+ state.data = NULL;
931
+ state.len = state.capa = 0;
932
+ goto finish;
933
+ }
934
+ close(file);
935
+ finish:
936
+ free(path);
937
+ return state;
938
+ #else
939
+ /* TODO: consider adding non POSIX implementations. */
940
+ return state;
941
+ #endif
942
+ }
943
+
944
+ /**
945
+ * Prevents further manipulations to the String's content.
946
+ */
947
+ inline FIO_FUNC void fio_str_freeze(fio_str_s *s) {
948
+ if (!s)
949
+ return;
950
+ s->frozen = 1;
951
+ }
952
+
953
+ /**
954
+ * Binary comparison returns `1` if both strings are equal and `0` if not.
955
+ */
956
+ inline FIO_FUNC int fio_str_iseq(const fio_str_s *str1, const fio_str_s *str2) {
957
+ if (str1 == str2)
958
+ return 1;
959
+ if (!str1 || !str2)
960
+ return 0;
961
+ fio_str_info_s s1 = fio_str_state(str1);
962
+ fio_str_info_s s2 = fio_str_state(str2);
963
+ return (s1.len == s2.len && !memcmp(s1.data, s2.data, s1.len));
964
+ }
965
+
966
+ /* *****************************************************************************
967
+ Testing
968
+ ***************************************************************************** */
969
+
970
+ #if DEBUG
971
+ #include <stdio.h>
972
+ #define TEST_ASSERT(cond, ...) \
973
+ if (!(cond)) { \
974
+ fprintf(stderr, "* " __VA_ARGS__); \
975
+ fprintf(stderr, "\n !!! Testing failed !!!\n"); \
976
+ exit(-1); \
977
+ }
978
+ /**
979
+ * Removes any FIO_ARY_TYPE_INVALID *pointers* from an Array, keeping all other
980
+ * data in the array.
981
+ *
982
+ * This action is O(n) where n in the length of the array.
983
+ * It could get expensive.
984
+ */
985
+ FIO_FUNC inline void fio_str_test(void) {
986
+ fprintf(stderr, "=== Testing Core String features (fio_str.h)\n");
987
+ fprintf(stderr, "* String container size: %zu\n", sizeof(fio_str_s));
988
+ fprintf(stderr,
989
+ "* Self-Contained String Capacity (FIO_STR_SMALL_CAPA): %zu\n",
990
+ FIO_STR_SMALL_CAPA);
991
+ fio_str_s str = {.small = 0}; /* test zeroed out memory */
992
+ TEST_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1,
993
+ "Small String capacity reporting error!");
994
+ TEST_ASSERT(fio_str_len(&str) == 0, "Small String length reporting error!");
995
+ TEST_ASSERT(fio_str_data(&str) ==
996
+ (char *)((uintptr_t)(&str + 1) - FIO_STR_SMALL_CAPA),
997
+ "Small String pointer reporting error!");
998
+ fio_str_write(&str, "World", 4);
999
+ TEST_ASSERT(str.small,
1000
+ "Small String writing error - not small on small write!");
1001
+ TEST_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1,
1002
+ "Small String capacity reporting error after write!");
1003
+ TEST_ASSERT(fio_str_len(&str) == 4,
1004
+ "Small String length reporting error after write!");
1005
+ TEST_ASSERT(fio_str_data(&str) ==
1006
+ (char *)((uintptr_t)(&str + 1) - FIO_STR_SMALL_CAPA),
1007
+ "Small String pointer reporting error after write!");
1008
+ TEST_ASSERT(strlen(fio_str_data(&str)) == 4,
1009
+ "Small String NUL missing after write (%zu)!",
1010
+ strlen(fio_str_data(&str)));
1011
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Worl"),
1012
+ "Small String write error (%s)!", fio_str_data(&str));
1013
+
1014
+ fio_str_capa_assert(&str, sizeof(fio_str_s) - 1);
1015
+ TEST_ASSERT(!str.small,
1016
+ "Long String reporting as small after capacity update!");
1017
+ TEST_ASSERT(fio_str_capa(&str) == sizeof(fio_str_s) - 1,
1018
+ "Long String capacity update error (%zu != %zu)!",
1019
+ fio_str_capa(&str), sizeof(fio_str_s));
1020
+ TEST_ASSERT(
1021
+ fio_str_len(&str) == 4,
1022
+ "Long String length changed during conversion from small string (%zu)!",
1023
+ fio_str_len(&str));
1024
+ TEST_ASSERT(fio_str_data(&str) == str.data,
1025
+ "Long String pointer reporting error after capacity update!");
1026
+ TEST_ASSERT(strlen(fio_str_data(&str)) == 4,
1027
+ "Long String NUL missing after capacity update (%zu)!",
1028
+ strlen(fio_str_data(&str)));
1029
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Worl"),
1030
+ "Long String value changed after capacity update (%s)!",
1031
+ fio_str_data(&str));
1032
+
1033
+ fio_str_write(&str, "d!", 2);
1034
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "World!"),
1035
+ "Long String `write` error (%s)!", fio_str_data(&str));
1036
+
1037
+ fio_str_replace(&str, 0, 0, "Hello ", 6);
1038
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello World!"),
1039
+ "Long String `insert` error (%s)!", fio_str_data(&str));
1040
+
1041
+ fio_str_resize(&str, 6);
1042
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello "),
1043
+ "Long String `resize` clipping error (%s)!", fio_str_data(&str));
1044
+
1045
+ fio_str_replace(&str, 6, 0, "My World!", 9);
1046
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello My World!"),
1047
+ "Long String `replace` error when testing overflow (%s)!",
1048
+ fio_str_data(&str));
1049
+
1050
+ str.capa = str.len;
1051
+ fio_str_replace(&str, -10, 2, "Big", 3);
1052
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World!"),
1053
+ "Long String `replace` error when testing splicing (%s)!",
1054
+ fio_str_data(&str));
1055
+
1056
+ TEST_ASSERT(
1057
+ fio_str_capa(&str) == ROUND_UP_CAPA_2WORDS(strlen("Hello Big World!")),
1058
+ "Long String `fio_str_replace` capacity update error (%zu != %zu)!",
1059
+ fio_str_capa(&str), ROUND_UP_CAPA_2WORDS(strlen("Hello Big World!")));
1060
+
1061
+ if (str.len < FIO_STR_SMALL_CAPA) {
1062
+ fio_str_compact(&str);
1063
+ TEST_ASSERT(str.small, "Compacting didn't change String to small!");
1064
+ TEST_ASSERT(fio_str_len(&str) == strlen("Hello Big World!"),
1065
+ "Compacting altered String length! (%zu != %zu)!",
1066
+ fio_str_len(&str), strlen("Hello Big World!"));
1067
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World!"),
1068
+ "Compact data error (%s)!", fio_str_data(&str));
1069
+ TEST_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1,
1070
+ "Compacted String capacity reporting error!");
1071
+ } else {
1072
+ fprintf(stderr, "* skipped `compact` test!\n");
1073
+ }
1074
+
1075
+ {
1076
+ fio_str_freeze(&str);
1077
+ fio_str_info_s old_state = fio_str_state(&str);
1078
+ fio_str_write(&str, "more data to be written here", 28);
1079
+ fio_str_replace(&str, 2, 1, "more data to be written here", 28);
1080
+ fio_str_info_s new_state = fio_str_state(&str);
1081
+ TEST_ASSERT(old_state.len == new_state.len,
1082
+ "Frozen String length changed!");
1083
+ TEST_ASSERT(old_state.data == new_state.data,
1084
+ "Frozen String pointer changed!");
1085
+ TEST_ASSERT(
1086
+ old_state.capa == new_state.capa,
1087
+ "Frozen String capacity changed (allowed, but shouldn't happen)!");
1088
+ str.frozen = 0;
1089
+ }
1090
+ fio_str_printf(&str, " %u", 42);
1091
+ TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World! 42"),
1092
+ "`fio_str_printf` data error (%s)!", fio_str_data(&str));
1093
+
1094
+ {
1095
+ fio_str_s str2 = FIO_STR_INIT;
1096
+ fio_str_concat(&str2, &str);
1097
+ TEST_ASSERT(fio_str_iseq(&str, &str2),
1098
+ "`fio_str_concat` error, strings not equal (%s != %s)!",
1099
+ fio_str_data(&str), fio_str_data(&str2));
1100
+ fio_str_write(&str2, ":extra data", 11);
1101
+ TEST_ASSERT(
1102
+ !fio_str_iseq(&str, &str2),
1103
+ "`fio_str_write` error after copy, strings equal ((%zu)%s == (%zu)%s)!",
1104
+ fio_str_len(&str), fio_str_data(&str), fio_str_len(&str2),
1105
+ fio_str_data(&str2));
1106
+
1107
+ fio_str_free(&str2);
1108
+ }
1109
+
1110
+ fio_str_free(&str);
1111
+
1112
+ {
1113
+ fio_str_info_s state = fio_str_readfile(&str, __FILE__, 0, 0);
1114
+ TEST_ASSERT(state.data,
1115
+ "`fio_str_readfile` error, no data was read for file %s!",
1116
+ __FILE__);
1117
+ TEST_ASSERT(!memcmp(state.data, "#ifndef H_FIO_STRING_H", 22),
1118
+ "`fio_str_readfile` content error, header mismatch!\n %s",
1119
+ state.data);
1120
+ TEST_ASSERT(
1121
+ fio_str_utf8_valid(&str),
1122
+ "`fio_str_utf8_valid` error, code in this file should be valid!");
1123
+ TEST_ASSERT(fio_str_utf8_len(&str) &&
1124
+ (fio_str_utf8_len(&str) <= fio_str_len(&str)) &&
1125
+ (fio_str_utf8_len(&str) >= (fio_str_len(&str)) >> 1),
1126
+ "`fio_str_utf8_len` error, invalid value (%zu / %zu!",
1127
+ fio_str_utf8_len(&str), fio_str_len(&str));
1128
+ {
1129
+ /* String content == whole file (this file) */
1130
+ intptr_t pos = -11;
1131
+ size_t len = 20;
1132
+
1133
+ TEST_ASSERT(
1134
+ fio_str_utf8_select(&str, &pos, &len) == 0,
1135
+ "`fio_str_utf8_select` returned error for negative pos! (%zd, %zu)",
1136
+ (ssize_t)pos, len);
1137
+ TEST_ASSERT(
1138
+ pos == (intptr_t)state.len - 10, /* no UTF-8 bytes in this file */
1139
+ "`fio_str_utf8_select` error, negative position invalid! (%zd)",
1140
+ (ssize_t)pos);
1141
+ TEST_ASSERT(
1142
+ len == 10,
1143
+ "`fio_str_utf8_select` error, trancated length invalid! (%zd)",
1144
+ (ssize_t)len);
1145
+ pos = 10;
1146
+ len = 20;
1147
+ TEST_ASSERT(fio_str_utf8_select(&str, &pos, &len) == 0,
1148
+ "`fio_str_utf8_select` returned error! (%zd, %zu)",
1149
+ (ssize_t)pos, len);
1150
+ TEST_ASSERT(pos == 10,
1151
+ "`fio_str_utf8_select` error, position invalid! (%zd)",
1152
+ (ssize_t)pos);
1153
+ TEST_ASSERT(len == 20,
1154
+ "`fio_str_utf8_select` error, length invalid! (%zd)",
1155
+ (ssize_t)len);
1156
+ }
1157
+ }
1158
+ fio_str_free(&str);
1159
+ {
1160
+
1161
+ const char *utf8_sample = /* three hearts, small-big-small*/
1162
+ "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95";
1163
+ fio_str_write(&str, utf8_sample, strlen(utf8_sample));
1164
+ intptr_t pos = -2;
1165
+ size_t len = 2;
1166
+ TEST_ASSERT(fio_str_utf8_select(&str, &pos, &len) == 0,
1167
+ "`fio_str_utf8_select` returned error for negative pos on "
1168
+ "UTF-8 data! (%zd, %zu)",
1169
+ (ssize_t)pos, len);
1170
+ TEST_ASSERT(pos == (intptr_t)fio_str_len(&str) - 4, /* 4 byte emoji */
1171
+ "`fio_str_utf8_select` error, negative position invalid on "
1172
+ "UTF-8 data! (%zd)",
1173
+ (ssize_t)pos);
1174
+ TEST_ASSERT(len == 4, /* last utf-8 char is 4 byte long */
1175
+ "`fio_str_utf8_select` error, trancated length invalid on "
1176
+ "UTF-8 data! (%zd)",
1177
+ (ssize_t)len);
1178
+ pos = 1;
1179
+ len = 20;
1180
+ TEST_ASSERT(
1181
+ fio_str_utf8_select(&str, &pos, &len) == 0,
1182
+ "`fio_str_utf8_select` returned error on UTF-8 data! (%zd, %zu)",
1183
+ (ssize_t)pos, len);
1184
+ TEST_ASSERT(
1185
+ pos == 4,
1186
+ "`fio_str_utf8_select` error, position invalid on UTF-8 data! (%zd)",
1187
+ (ssize_t)pos);
1188
+ TEST_ASSERT(
1189
+ len == 10,
1190
+ "`fio_str_utf8_select` error, length invalid on UTF-8 data! (%zd)",
1191
+ (ssize_t)len);
1192
+ pos = 1;
1193
+ len = 3;
1194
+ TEST_ASSERT(
1195
+ fio_str_utf8_select(&str, &pos, &len) == 0,
1196
+ "`fio_str_utf8_select` returned error on UTF-8 data (2)! (%zd, %zu)",
1197
+ (ssize_t)pos, len);
1198
+ TEST_ASSERT(
1199
+ len == 10, /* 3 UTF-8 chars: 4 byte + 4 byte + 2 byte codes == 10 */
1200
+ "`fio_str_utf8_select` error, length invalid on UTF-8 data! (%zd)",
1201
+ (ssize_t)len);
1202
+ }
1203
+ fio_str_free(&str);
1204
+ fprintf(stderr, "* passed.\n");
1205
+ }
1206
+ #undef TEST_ASSERT
1207
+ #else
1208
+ #define fio_str_test()
1209
+ #endif
1210
+
1211
+ /* *****************************************************************************
1212
+ Done
1213
+ ***************************************************************************** */
1214
+
1215
+ #undef FIO_FUNC
1216
+ #undef FIO_ASSERT_ALLOC
1217
+ #undef ROUND_UP_CAPA_2WORDS
1218
+ #endif