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 '#', 37 '\0', 38 '\0' 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 // unreachable - either we returned or skipped a blank line 230 assert(false); 231 } 232 233 // when we come to this point, all data must have been read 234 assert(cxBufferEof(&prop->buffer)); 235 assert(cxBufferEof(&prop->input)); 236 237 return CX_PROPERTIES_NO_DATA; 238 } 239 240 static int cx_properties_sink_map( 241 cx_attr_unused CxProperties *prop, 242 CxPropertiesSink *sink, 243 cxstring key, 244 cxstring value 245 ) { 246 CxMap *map = sink->sink; 247 CxAllocator *alloc = sink->data; 248 cxmutstr v = cx_strdup_a(alloc, value); 249 int r = cx_map_put_cxstr(map, key, v.ptr); 250 if (r != 0) cx_strfree_a(alloc, &v); 251 return r; 252 } 253 254 CxPropertiesSink cxPropertiesMapSink(CxMap *map) { 255 CxPropertiesSink sink; 256 sink.sink = map; 257 sink.data = cxDefaultAllocator; 258 sink.sink_func = cx_properties_sink_map; 259 return sink; 260 } 261 262 static int cx_properties_read_string( 263 CxProperties *prop, 264 CxPropertiesSource *src, 265 cxstring *target 266 ) { 267 if (prop->input.space == src->src) { 268 // when the input buffer already contains the string 269 // we have nothing more to provide 270 target->length = 0; 271 } else { 272 target->ptr = src->src; 273 target->length = src->data_size; 274 } 275 return 0; 276 } 277 278 static int cx_properties_read_file( 279 cx_attr_unused CxProperties *prop, 280 CxPropertiesSource *src, 281 cxstring *target 282 ) { 283 target->ptr = src->data_ptr; 284 target->length = fread(src->data_ptr, 1, src->data_size, src->src); 285 return ferror(src->src); 286 } 287 288 static int cx_properties_read_init_file( 289 cx_attr_unused CxProperties *prop, 290 CxPropertiesSource *src 291 ) { 292 src->data_ptr = malloc(src->data_size); 293 if (src->data_ptr == NULL) return 1; 294 return 0; 295 } 296 297 static void cx_properties_read_clean_file( 298 cx_attr_unused CxProperties *prop, 299 CxPropertiesSource *src 300 ) { 301 free(src->data_ptr); 302 } 303 304 CxPropertiesSource cxPropertiesStringSource(cxstring str) { 305 CxPropertiesSource src; 306 src.src = (void*) str.ptr; 307 src.data_size = str.length; 308 src.data_ptr = NULL; 309 src.read_func = cx_properties_read_string; 310 src.read_init_func = NULL; 311 src.read_clean_func = NULL; 312 return src; 313 } 314 315 CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { 316 CxPropertiesSource src; 317 src.src = (void*) str; 318 src.data_size = len; 319 src.data_ptr = NULL; 320 src.read_func = cx_properties_read_string; 321 src.read_init_func = NULL; 322 src.read_clean_func = NULL; 323 return src; 324 } 325 326 CxPropertiesSource cxPropertiesCstrSource(const char *str) { 327 CxPropertiesSource src; 328 src.src = (void*) str; 329 src.data_size = strlen(str); 330 src.data_ptr = NULL; 331 src.read_func = cx_properties_read_string; 332 src.read_init_func = NULL; 333 src.read_clean_func = NULL; 334 return src; 335 } 336 337 CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { 338 CxPropertiesSource src; 339 src.src = file; 340 src.data_size = chunk_size; 341 src.data_ptr = NULL; 342 src.read_func = cx_properties_read_file; 343 src.read_init_func = cx_properties_read_init_file; 344 src.read_clean_func = cx_properties_read_clean_file; 345 return src; 346 } 347 348 CxPropertiesStatus cxPropertiesLoad( 349 CxProperties *prop, 350 CxPropertiesSink sink, 351 CxPropertiesSource source 352 ) { 353 assert(source.read_func != NULL); 354 assert(sink.sink_func != NULL); 355 356 // initialize reader 357 if (source.read_init_func != NULL) { 358 if (source.read_init_func(prop, &source)) { 359 return CX_PROPERTIES_READ_INIT_FAILED; 360 } 361 } 362 363 // transfer the data from the source to the sink 364 CxPropertiesStatus status; 365 bool found = false; 366 while (true) { 367 // read input 368 cxstring input; 369 if (source.read_func(prop, &source, &input)) { 370 status = CX_PROPERTIES_READ_FAILED; 371 break; 372 } 373 374 // no more data - break 375 if (input.length == 0) { 376 status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; 377 break; 378 } 379 380 // set the input buffer and read the k/v-pairs 381 cxPropertiesFill(prop, input); 382 383 CxPropertiesStatus kv_status; 384 do { 385 cxstring key, value; 386 kv_status = cxPropertiesNext(prop, &key, &value); 387 if (kv_status == CX_PROPERTIES_NO_ERROR) { 388 found = true; 389 if (sink.sink_func(prop, &sink, key, value)) { 390 kv_status = CX_PROPERTIES_SINK_FAILED; 391 } 392 } 393 } while (kv_status == CX_PROPERTIES_NO_ERROR); 394 395 if (kv_status > CX_PROPERTIES_OK) { 396 status = kv_status; 397 break; 398 } 399 } 400 401 if (source.read_clean_func != NULL) { 402 source.read_clean_func(prop, &source); 403 } 404 405 return status; 406 } 407