UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "cx/properties.h" 30 31 #include <assert.h> 32 33 const CxPropertiesConfig cx_properties_config_default = { 34 '=', 35 '#', 36 '\0', 37 '\0', 38 '\\', 39 }; 40 41 void cxPropertiesInit( 42 CxProperties *prop, 43 CxPropertiesConfig config 44 ) { 45 memset(prop, 0, sizeof(CxProperties)); 46 prop->config = config; 47 } 48 49 void cxPropertiesDestroy(CxProperties *prop) { 50 cxBufferDestroy(&prop->input); 51 cxBufferDestroy(&prop->buffer); 52 } 53 54 int cxPropertiesFilln( 55 CxProperties *prop, 56 const char *buf, 57 size_t len 58 ) { 59 if (cxBufferEof(&prop->input)) { 60 // destroy a possible previously initialized buffer 61 cxBufferDestroy(&prop->input); 62 cxBufferInit(&prop->input, (void*) buf, len, 63 NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); 64 prop->input.size = len; 65 } else { 66 if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; 67 } 68 return 0; 69 } 70 71 void cxPropertiesUseStack( 72 CxProperties *prop, 73 char *buf, 74 size_t capacity 75 ) { 76 cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); 77 } 78 79 CxPropertiesStatus cxPropertiesNext( 80 CxProperties *prop, 81 cxstring *key, 82 cxstring *value 83 ) { 84 // check if we have a text buffer 85 if (prop->input.space == NULL) { 86 return CX_PROPERTIES_NULL_INPUT; 87 } 88 89 // a pointer to the buffer we want to read from 90 CxBuffer *current_buffer = &prop->input; 91 92 // check if we have rescued data 93 if (!cxBufferEof(&prop->buffer)) { 94 // check if we can now get a complete line 95 cxstring input = cx_strn(prop->input.space + prop->input.pos, 96 prop->input.size - prop->input.pos); 97 cxstring nl = cx_strchr(input, '\n'); 98 if (nl.length > 0) { 99 // we add as much data to the rescue buffer as we need 100 // to complete the line 101 size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; 102 103 if (cxBufferAppend(input.ptr, 1, 104 len_until_nl, &prop->buffer) < len_until_nl) { 105 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 106 } 107 108 // advance the position in the input buffer 109 prop->input.pos += len_until_nl; 110 111 // we now want to read from the rescue buffer 112 current_buffer = &prop->buffer; 113 } else { 114 // still not enough data, copy input buffer to internal buffer 115 if (cxBufferAppend(input.ptr, 1, 116 input.length, &prop->buffer) < input.length) { 117 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 118 } 119 // reset the input buffer (make way for a re-fill) 120 cxBufferReset(&prop->input); 121 return CX_PROPERTIES_INCOMPLETE_DATA; 122 } 123 } 124 125 char comment1 = prop->config.comment1; 126 char comment2 = prop->config.comment2; 127 char comment3 = prop->config.comment3; 128 char delimiter = prop->config.delimiter; 129 130 // get one line and parse it 131 while (!cxBufferEof(current_buffer)) { 132 const char *buf = current_buffer->space + current_buffer->pos; 133 size_t len = current_buffer->size - current_buffer->pos; 134 135 /* 136 * First we check if we have at least one line. We also get indices of 137 * delimiter and comment chars 138 */ 139 size_t delimiter_index = 0; 140 size_t comment_index = 0; 141 bool has_comment = false; 142 143 size_t i = 0; 144 char c = 0; 145 for (; i < len; i++) { 146 c = buf[i]; 147 if (c == comment1 || c == comment2 || c == comment3) { 148 if (comment_index == 0) { 149 comment_index = i; 150 has_comment = true; 151 } 152 } else if (c == delimiter) { 153 if (delimiter_index == 0 && !has_comment) { 154 delimiter_index = i; 155 } 156 } else if (c == '\n') { 157 break; 158 } 159 } 160 161 if (c != '\n') { 162 // we don't have enough data for a line, use the rescue buffer 163 assert(current_buffer != &prop->buffer); 164 // make sure that the rescue buffer does not already contain something 165 assert(cxBufferEof(&prop->buffer)); 166 if (prop->buffer.space == NULL) { 167 // initialize a rescue buffer, if the user did not provide one 168 cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); 169 } else { 170 // from a previous rescue there might be already read data 171 // reset the buffer to avoid unnecessary buffer extension 172 cxBufferReset(&prop->buffer); 173 } 174 if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) { 175 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 176 } 177 // reset the input buffer (make way for a re-fill) 178 cxBufferReset(&prop->input); 179 return CX_PROPERTIES_INCOMPLETE_DATA; 180 } 181 182 cxstring line = has_comment ? 183 cx_strn(buf, comment_index) : 184 cx_strn(buf, i); 185 // check line 186 if (delimiter_index == 0) { 187 // if line is not blank ... 188 line = cx_strtrim(line); 189 // ... either no delimiter found, or key is empty 190 if (line.length > 0) { 191 if (line.ptr[0] == delimiter) { 192 return CX_PROPERTIES_INVALID_EMPTY_KEY; 193 } else { 194 return CX_PROPERTIES_INVALID_MISSING_DELIMITER; 195 } 196 } else { 197 // skip blank line 198 // if it was the rescue buffer, return to the original buffer 199 if (current_buffer == &prop->buffer) { 200 // assert that the rescue buffer really does not contain more data 201 assert(current_buffer->pos + i + 1 == current_buffer->size); 202 // reset the rescue buffer, but don't destroy it! 203 cxBufferReset(&prop->buffer); 204 // continue with the input buffer 205 current_buffer = &prop->input; 206 } else { 207 // if it was the input buffer already, just advance the position 208 current_buffer->pos += i + 1; 209 } 210 continue; 211 } 212 } else { 213 cxstring k = cx_strn(buf, delimiter_index); 214 cxstring val = cx_strn( 215 buf + delimiter_index + 1, 216 line.length - delimiter_index - 1); 217 k = cx_strtrim(k); 218 val = cx_strtrim(val); 219 if (k.length > 0) { 220 *key = k; 221 *value = val; 222 current_buffer->pos += i + 1; 223 assert(current_buffer->pos <= current_buffer->size); 224 return CX_PROPERTIES_NO_ERROR; 225 } else { 226 return CX_PROPERTIES_INVALID_EMPTY_KEY; 227 } 228 } 229 } 230 231 // when we come to this point, all data must have been read 232 assert(cxBufferEof(&prop->buffer)); 233 assert(cxBufferEof(&prop->input)); 234 235 return CX_PROPERTIES_NO_DATA; 236 } 237 238 static int cx_properties_sink_map( 239 cx_attr_unused CxProperties *prop, 240 CxPropertiesSink *sink, 241 cxstring key, 242 cxstring value 243 ) { 244 CxMap *map = sink->sink; 245 CxAllocator *alloc = sink->data; 246 cxmutstr v = cx_strdup_a(alloc, value); 247 int r = cx_map_put_cxstr(map, key, v.ptr); 248 if (r != 0) cx_strfree_a(alloc, &v); 249 return r; 250 } 251 252 CxPropertiesSink cxPropertiesMapSink(CxMap *map) { 253 CxPropertiesSink sink; 254 sink.sink = map; 255 sink.data = (void*) cxDefaultAllocator; 256 sink.sink_func = cx_properties_sink_map; 257 return sink; 258 } 259 260 static int cx_properties_read_string( 261 CxProperties *prop, 262 CxPropertiesSource *src, 263 cxstring *target 264 ) { 265 if (prop->input.space == src->src) { 266 // when the input buffer already contains the string 267 // we have nothing more to provide 268 target->length = 0; 269 } else { 270 target->ptr = src->src; 271 target->length = src->data_size; 272 } 273 return 0; 274 } 275 276 static int cx_properties_read_file( 277 cx_attr_unused CxProperties *prop, 278 CxPropertiesSource *src, 279 cxstring *target 280 ) { 281 target->ptr = src->data_ptr; 282 target->length = fread(src->data_ptr, 1, src->data_size, src->src); 283 return ferror((FILE*)src->src); 284 } 285 286 static int cx_properties_read_init_file( 287 cx_attr_unused CxProperties *prop, 288 CxPropertiesSource *src 289 ) { 290 src->data_ptr = malloc(src->data_size); 291 if (src->data_ptr == NULL) return 1; 292 return 0; 293 } 294 295 static void cx_properties_read_clean_file( 296 cx_attr_unused CxProperties *prop, 297 CxPropertiesSource *src 298 ) { 299 free(src->data_ptr); 300 } 301 302 CxPropertiesSource cxPropertiesStringSource(cxstring str) { 303 CxPropertiesSource src; 304 src.src = (void*) str.ptr; 305 src.data_size = str.length; 306 src.data_ptr = NULL; 307 src.read_func = cx_properties_read_string; 308 src.read_init_func = NULL; 309 src.read_clean_func = NULL; 310 return src; 311 } 312 313 CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { 314 CxPropertiesSource src; 315 src.src = (void*) str; 316 src.data_size = len; 317 src.data_ptr = NULL; 318 src.read_func = cx_properties_read_string; 319 src.read_init_func = NULL; 320 src.read_clean_func = NULL; 321 return src; 322 } 323 324 CxPropertiesSource cxPropertiesCstrSource(const char *str) { 325 CxPropertiesSource src; 326 src.src = (void*) str; 327 src.data_size = strlen(str); 328 src.data_ptr = NULL; 329 src.read_func = cx_properties_read_string; 330 src.read_init_func = NULL; 331 src.read_clean_func = NULL; 332 return src; 333 } 334 335 CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { 336 CxPropertiesSource src; 337 src.src = file; 338 src.data_size = chunk_size; 339 src.data_ptr = NULL; 340 src.read_func = cx_properties_read_file; 341 src.read_init_func = cx_properties_read_init_file; 342 src.read_clean_func = cx_properties_read_clean_file; 343 return src; 344 } 345 346 CxPropertiesStatus cxPropertiesLoad( 347 CxProperties *prop, 348 CxPropertiesSink sink, 349 CxPropertiesSource source 350 ) { 351 assert(source.read_func != NULL); 352 assert(sink.sink_func != NULL); 353 354 // initialize reader 355 if (source.read_init_func != NULL) { 356 if (source.read_init_func(prop, &source)) { 357 return CX_PROPERTIES_READ_INIT_FAILED; 358 } 359 } 360 361 // transfer the data from the source to the sink 362 CxPropertiesStatus status; 363 CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; 364 bool found = false; 365 while (true) { 366 // read input 367 cxstring input; 368 if (source.read_func(prop, &source, &input)) { 369 status = CX_PROPERTIES_READ_FAILED; 370 break; 371 } 372 373 // no more data - break 374 if (input.length == 0) { 375 if (found) { 376 // something was found, check the last kv_status 377 if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { 378 status = CX_PROPERTIES_INCOMPLETE_DATA; 379 } else { 380 status = CX_PROPERTIES_NO_ERROR; 381 } 382 } else { 383 // nothing found 384 status = CX_PROPERTIES_NO_DATA; 385 } 386 break; 387 } 388 389 // set the input buffer and read the k/v-pairs 390 cxPropertiesFill(prop, input); 391 392 do { 393 cxstring key, value; 394 kv_status = cxPropertiesNext(prop, &key, &value); 395 if (kv_status == CX_PROPERTIES_NO_ERROR) { 396 found = true; 397 if (sink.sink_func(prop, &sink, key, value)) { 398 kv_status = CX_PROPERTIES_SINK_FAILED; 399 } 400 } 401 } while (kv_status == CX_PROPERTIES_NO_ERROR); 402 403 if (kv_status > CX_PROPERTIES_OK) { 404 status = kv_status; 405 break; 406 } 407 } 408 409 if (source.read_clean_func != NULL) { 410 source.read_clean_func(prop, &source); 411 } 412 413 return status; 414 } 415