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 void cxPropertiesReset(CxProperties *prop) { 55 CxPropertiesConfig config = prop->config; 56 cxPropertiesDestroy(prop); 57 cxPropertiesInit(prop, config); 58 } 59 60 int cxPropertiesFilln( 61 CxProperties *prop, 62 const char *buf, 63 size_t len 64 ) { 65 if (cxBufferEof(&prop->input)) { 66 // destroy a possible previously initialized buffer 67 cxBufferDestroy(&prop->input); 68 cxBufferInit(&prop->input, (void*) buf, len, 69 NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); 70 prop->input.size = len; 71 } else { 72 if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; 73 } 74 return 0; 75 } 76 77 void cxPropertiesUseStack( 78 CxProperties *prop, 79 char *buf, 80 size_t capacity 81 ) { 82 cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); 83 } 84 85 CxPropertiesStatus cxPropertiesNext( 86 CxProperties *prop, 87 cxstring *key, 88 cxstring *value 89 ) { 90 // check if we have a text buffer 91 if (prop->input.space == NULL) { 92 return CX_PROPERTIES_NULL_INPUT; 93 } 94 95 // a pointer to the buffer we want to read from 96 CxBuffer *current_buffer = &prop->input; 97 98 // check if we have rescued data 99 if (!cxBufferEof(&prop->buffer)) { 100 // check if we can now get a complete line 101 cxstring input = cx_strn(prop->input.space + prop->input.pos, 102 prop->input.size - prop->input.pos); 103 cxstring nl = cx_strchr(input, '\n'); 104 if (nl.length > 0) { 105 // we add as much data to the rescue buffer as we need 106 // to complete the line 107 size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; 108 109 if (cxBufferAppend(input.ptr, 1, 110 len_until_nl, &prop->buffer) < len_until_nl) { 111 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 112 } 113 114 // advance the position in the input buffer 115 prop->input.pos += len_until_nl; 116 117 // we now want to read from the rescue buffer 118 current_buffer = &prop->buffer; 119 } else { 120 // still not enough data, copy input buffer to internal buffer 121 if (cxBufferAppend(input.ptr, 1, 122 input.length, &prop->buffer) < input.length) { 123 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 124 } 125 // reset the input buffer (make way for a re-fill) 126 cxBufferReset(&prop->input); 127 return CX_PROPERTIES_INCOMPLETE_DATA; 128 } 129 } 130 131 char comment1 = prop->config.comment1; 132 char comment2 = prop->config.comment2; 133 char comment3 = prop->config.comment3; 134 char delimiter = prop->config.delimiter; 135 136 // get one line and parse it 137 while (!cxBufferEof(current_buffer)) { 138 const char *buf = current_buffer->space + current_buffer->pos; 139 size_t len = current_buffer->size - current_buffer->pos; 140 141 /* 142 * First we check if we have at least one line. We also get indices of 143 * delimiter and comment chars 144 */ 145 size_t delimiter_index = 0; 146 size_t comment_index = 0; 147 bool has_comment = false; 148 149 size_t i = 0; 150 char c = 0; 151 for (; i < len; i++) { 152 c = buf[i]; 153 if (c == comment1 || c == comment2 || c == comment3) { 154 if (comment_index == 0) { 155 comment_index = i; 156 has_comment = true; 157 } 158 } else if (c == delimiter) { 159 if (delimiter_index == 0 && !has_comment) { 160 delimiter_index = i; 161 } 162 } else if (c == '\n') { 163 break; 164 } 165 } 166 167 if (c != '\n') { 168 // we don't have enough data for a line, use the rescue buffer 169 assert(current_buffer != &prop->buffer); 170 // make sure that the rescue buffer does not already contain something 171 assert(cxBufferEof(&prop->buffer)); 172 if (prop->buffer.space == NULL) { 173 // initialize a rescue buffer, if the user did not provide one 174 cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); 175 } else { 176 // from a previous rescue there might be already read data 177 // reset the buffer to avoid unnecessary buffer extension 178 cxBufferReset(&prop->buffer); 179 } 180 if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) { 181 return CX_PROPERTIES_BUFFER_ALLOC_FAILED; 182 } 183 // reset the input buffer (make way for a re-fill) 184 cxBufferReset(&prop->input); 185 return CX_PROPERTIES_INCOMPLETE_DATA; 186 } 187 188 cxstring line = has_comment ? 189 cx_strn(buf, comment_index) : 190 cx_strn(buf, i); 191 // check line 192 if (delimiter_index == 0) { 193 // if line is not blank ... 194 line = cx_strtrim(line); 195 // ... either no delimiter found, or key is empty 196 if (line.length > 0) { 197 if (line.ptr[0] == delimiter) { 198 return CX_PROPERTIES_INVALID_EMPTY_KEY; 199 } else { 200 return CX_PROPERTIES_INVALID_MISSING_DELIMITER; 201 } 202 } else { 203 // skip blank line 204 // if it was the rescue buffer, return to the original buffer 205 if (current_buffer == &prop->buffer) { 206 // assert that the rescue buffer really does not contain more data 207 assert(current_buffer->pos + i + 1 == current_buffer->size); 208 // reset the rescue buffer, but don't destroy it! 209 cxBufferReset(&prop->buffer); 210 // continue with the input buffer 211 current_buffer = &prop->input; 212 } else { 213 // if it was the input buffer already, just advance the position 214 current_buffer->pos += i + 1; 215 } 216 continue; 217 } 218 } else { 219 cxstring k = cx_strn(buf, delimiter_index); 220 cxstring val = cx_strn( 221 buf + delimiter_index + 1, 222 line.length - delimiter_index - 1); 223 k = cx_strtrim(k); 224 val = cx_strtrim(val); 225 if (k.length > 0) { 226 *key = k; 227 *value = val; 228 current_buffer->pos += i + 1; 229 assert(current_buffer->pos <= current_buffer->size); 230 return CX_PROPERTIES_NO_ERROR; 231 } else { 232 return CX_PROPERTIES_INVALID_EMPTY_KEY; 233 } 234 } 235 } 236 237 // when we come to this point, all data must have been read 238 assert(cxBufferEof(&prop->buffer)); 239 assert(cxBufferEof(&prop->input)); 240 241 return CX_PROPERTIES_NO_DATA; 242 } 243 244 static int cx_properties_sink_map( 245 cx_attr_unused CxProperties *prop, 246 CxPropertiesSink *sink, 247 cxstring key, 248 cxstring value 249 ) { 250 CxMap *map = sink->sink; 251 CxAllocator *alloc = sink->data; 252 cxmutstr v = cx_strdup_a(alloc, value); 253 int r = cxMapPut(map, key, v.ptr); 254 if (r != 0) cx_strfree_a(alloc, &v); 255 return r; 256 } 257 258 CxPropertiesSink cxPropertiesMapSink(CxMap *map) { 259 CxPropertiesSink sink; 260 sink.sink = map; 261 sink.data = (void*) cxDefaultAllocator; 262 sink.sink_func = cx_properties_sink_map; 263 return sink; 264 } 265 266 static int cx_properties_read_string( 267 CxProperties *prop, 268 CxPropertiesSource *src, 269 cxstring *target 270 ) { 271 if (prop->input.space == src->src) { 272 // when the input buffer already contains the string 273 // we have nothing more to provide 274 target->length = 0; 275 } else { 276 target->ptr = src->src; 277 target->length = src->data_size; 278 } 279 return 0; 280 } 281 282 static int cx_properties_read_file( 283 cx_attr_unused CxProperties *prop, 284 CxPropertiesSource *src, 285 cxstring *target 286 ) { 287 target->ptr = src->data_ptr; 288 target->length = fread(src->data_ptr, 1, src->data_size, src->src); 289 return ferror((FILE*)src->src); 290 } 291 292 static int cx_properties_read_init_file( 293 cx_attr_unused CxProperties *prop, 294 CxPropertiesSource *src 295 ) { 296 src->data_ptr = cxMallocDefault(src->data_size); 297 if (src->data_ptr == NULL) return 1; 298 return 0; 299 } 300 301 static void cx_properties_read_clean_file( 302 cx_attr_unused CxProperties *prop, 303 CxPropertiesSource *src 304 ) { 305 cxFreeDefault(src->data_ptr); 306 } 307 308 CxPropertiesSource cxPropertiesStringSource(cxstring str) { 309 CxPropertiesSource src; 310 src.src = (void*) str.ptr; 311 src.data_size = str.length; 312 src.data_ptr = NULL; 313 src.read_func = cx_properties_read_string; 314 src.read_init_func = NULL; 315 src.read_clean_func = NULL; 316 return src; 317 } 318 319 CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { 320 CxPropertiesSource src; 321 src.src = (void*) str; 322 src.data_size = len; 323 src.data_ptr = NULL; 324 src.read_func = cx_properties_read_string; 325 src.read_init_func = NULL; 326 src.read_clean_func = NULL; 327 return src; 328 } 329 330 CxPropertiesSource cxPropertiesCstrSource(const char *str) { 331 CxPropertiesSource src; 332 src.src = (void*) str; 333 src.data_size = strlen(str); 334 src.data_ptr = NULL; 335 src.read_func = cx_properties_read_string; 336 src.read_init_func = NULL; 337 src.read_clean_func = NULL; 338 return src; 339 } 340 341 CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { 342 CxPropertiesSource src; 343 src.src = file; 344 src.data_size = chunk_size; 345 src.data_ptr = NULL; 346 src.read_func = cx_properties_read_file; 347 src.read_init_func = cx_properties_read_init_file; 348 src.read_clean_func = cx_properties_read_clean_file; 349 return src; 350 } 351 352 CxPropertiesStatus cxPropertiesLoad( 353 CxProperties *prop, 354 CxPropertiesSink sink, 355 CxPropertiesSource source 356 ) { 357 assert(source.read_func != NULL); 358 assert(sink.sink_func != NULL); 359 360 // initialize reader 361 if (source.read_init_func != NULL) { 362 if (source.read_init_func(prop, &source)) { 363 return CX_PROPERTIES_READ_INIT_FAILED; 364 } 365 } 366 367 // transfer the data from the source to the sink 368 CxPropertiesStatus status; 369 CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; 370 bool found = false; 371 while (true) { 372 // read input 373 cxstring input; 374 if (source.read_func(prop, &source, &input)) { 375 status = CX_PROPERTIES_READ_FAILED; 376 break; 377 } 378 379 // no more data - break 380 if (input.length == 0) { 381 if (found) { 382 // something was found, check the last kv_status 383 if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { 384 status = CX_PROPERTIES_INCOMPLETE_DATA; 385 } else { 386 status = CX_PROPERTIES_NO_ERROR; 387 } 388 } else { 389 // nothing found 390 status = CX_PROPERTIES_NO_DATA; 391 } 392 break; 393 } 394 395 // set the input buffer and read the k/v-pairs 396 cxPropertiesFill(prop, input); 397 398 do { 399 cxstring key, value; 400 kv_status = cxPropertiesNext(prop, &key, &value); 401 if (kv_status == CX_PROPERTIES_NO_ERROR) { 402 found = true; 403 if (sink.sink_func(prop, &sink, key, value)) { 404 kv_status = CX_PROPERTIES_SINK_FAILED; 405 } 406 } 407 } while (kv_status == CX_PROPERTIES_NO_ERROR); 408 409 if (kv_status > CX_PROPERTIES_OK) { 410 status = kv_status; 411 break; 412 } 413 } 414 415 if (source.read_clean_func != NULL) { 416 source.read_clean_func(prop, &source); 417 } 418 419 return status; 420 } 421