diff -r 06091e067bee -r 8a90552bba29 src/properties.c --- a/src/properties.c Fri Dec 20 15:00:05 2024 +0100 +++ b/src/properties.c Fri Dec 20 15:00:31 2024 +0100 @@ -30,8 +30,6 @@ #include -static const int CX_PROPERTIES_FLAG_USE_STACK = 0x01; - const CxPropertiesConfig cx_properties_config_default = { '=', //'\\', @@ -49,60 +47,8 @@ } void cxPropertiesDestroy(CxProperties *prop) { - if (0 == (prop->flags & CX_PROPERTIES_FLAG_USE_STACK)) { - free(prop->buf); - } - prop->buf = NULL; - prop->buf_capacity = prop->buf_size = 0; -} - -static int cx_properties_ensure_buf_capacity(CxProperties *prop, size_t cap) { - if (prop->buf_capacity >= cap) { - return 0; - } - - // not enough capacity - are we on the stack right now? - if ((prop->flags & CX_PROPERTIES_FLAG_USE_STACK) != 0) { - // move to the heap - char *newbuf = malloc(cap); - if (newbuf == NULL) return 1; - memcpy(newbuf, prop->buf, prop->buf_size); - prop->buf = newbuf; - prop->flags &= CX_PROPERTIES_FLAG_USE_STACK; - } else { - // we are on the heap already, reallocate - // this is legit, because realloc() behaves like malloc() when the - // current pointer is NULL - char *newbuf = realloc(prop->buf, cap); - if (newbuf == NULL) return 1; - prop->buf = newbuf; - } - - // store new capacity and return - prop->buf_capacity = cap; - return 0; -} - -static int cx_properties_rescuen_input(CxProperties *prop, size_t len) { - if (cx_properties_ensure_buf_capacity(prop, prop->buf_size + len)) { - return 1; - } - const char *src = prop->text + prop->text_pos; - char *dest = prop->buf + prop->buf_size; - memcpy(dest, src, len); - prop->buf_size += len; - prop->text_pos += len; - return 0; -} - -static int cx_properties_rescue_input(CxProperties *prop) { - // someone fucked around with our integers, exit immediately - if (prop->text_pos > prop->text_size) return 0; - - // determine the bytes needed - size_t len = prop->text_size - prop->text_pos; - - return cx_properties_rescuen_input(prop, len); + cxBufferDestroy(&prop->input); + cxBufferDestroy(&prop->buffer); } int cxPropertiesFilln( @@ -110,10 +56,15 @@ const char *buf, size_t len ) { - if (cx_properties_rescue_input(prop)) return 1; - prop->text = buf; - prop->text_size = len; - prop->text_pos = 0; + if (cxBufferEof(&prop->input)) { + // destroy a possible previously initialized buffer + cxBufferDestroy(&prop->input); + cxBufferInit(&prop->input, (void*) buf, len, + NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); + prop->input.size = len; + } else { + if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; + } return 0; } @@ -122,11 +73,7 @@ char *buf, size_t capacity ) { - assert(prop->buf == NULL); - prop->buf = buf; - prop->buf_capacity = capacity; - prop->buf_size = 0; - prop->flags |= CX_PROPERTIES_FLAG_USE_STACK; + cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); } CxPropertiesStatus cxPropertiesNext( @@ -135,65 +82,42 @@ cxstring *value ) { // check if we have a text buffer - if (prop->text == NULL) { + if (prop->input.space == NULL) { return CX_PROPERTIES_NULL_INPUT; } + + // a pointer to the buffer we want to read from + CxBuffer *current_buffer = &prop->input; + // check if we have rescued data - if (prop->buf_size > 0) { + if (!cxBufferEof(&prop->buffer)) { // check if we can now get a complete line - const char *buf = prop->text + prop->text_pos; - size_t len = prop->text_size - prop->text_pos; - cxstring str = cx_strn(buf, len); - cxstring nl = cx_strchr(str, '\n'); - if(nl.length > 0) { + cxstring input = cx_strn(prop->input.space + prop->input.pos, + prop->input.size - prop->input.pos); + cxstring nl = cx_strchr(input, '\n'); + if (nl.length > 0) { // we add as much data to the rescue buffer as we need // to complete the line - size_t len_until_nl = (size_t)(nl.ptr - buf) + 1; + size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; - if (cx_properties_rescuen_input(prop, len_until_nl)) { + if (cxBufferAppend(input.ptr, 1, + len_until_nl, &prop->buffer) < len_until_nl) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } - // the tmp buffer contains exactly one line now - // we use a trick here: we swap the buffers and recurse - const char *orig_text = prop->text; - size_t orig_size = prop->text_size; - prop->text = prop->buf; - prop->text_size = prop->buf_size; - prop->text_pos = 0; - prop->buf_size = 0; - - CxPropertiesStatus result; - result = cxPropertiesNext(prop, key, value); - - // restore original buffer - prop->text = orig_text; - prop->text_size = orig_size; - - // set the position to after the newline - prop->text_pos = len_until_nl; + // advance the position in the input buffer + prop->input.pos += len_until_nl; - // check the result - if (result == CX_PROPERTIES_NO_ERROR) { - // reset the rescue buffer and return with the result - prop->buf_size = 0; - return result; - } else if (result == CX_PROPERTIES_NO_DATA) { - // rescue buffer contained only blanks or comments - // reset the rescue buffer and retry with text buffer - prop->buf_size = 0; - return cxPropertiesNext(prop, key, value); - } else { - // CX_PROPERTIES_INCOMPLETE_DATA is not possible - // so it must have been another error - // do not reset the rescue buffer and return the error - return result; - } + // we now want to read from the rescue buffer + current_buffer = &prop->buffer; } else { - // still not enough data - if (cx_properties_rescue_input(prop)) { + // still not enough data, copy input buffer to internal buffer + if (cxBufferAppend(input.ptr, 1, + input.length, &prop->buffer) < input.length) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); return CX_PROPERTIES_INCOMPLETE_DATA; } } @@ -204,9 +128,9 @@ char delimiter = prop->config.delimiter; // get one line and parse it - while (prop->text_pos < prop->text_size) { - const char *buf = prop->text + prop->text_pos; - size_t len = prop->text_size - prop->text_pos; + while (!cxBufferEof(current_buffer)) { + const char *buf = current_buffer->space + current_buffer->pos; + size_t len = current_buffer->size - current_buffer->pos; /* * First we check if we have at least one line. We also get indices of @@ -235,10 +159,23 @@ } if (c != '\n') { - // we don't have enough data for a line - if (cx_properties_rescue_input(prop)) { + // we don't have enough data for a line, use the rescue buffer + assert(current_buffer != &prop->buffer); + // make sure that the rescue buffer does not already contain something + assert(cxBufferEof(&prop->buffer)); + if (prop->buffer.space == NULL) { + // initialize a rescue buffer, if the user did not provide one + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } else { + // from a previous rescue there might be already read data + // reset the buffer to avoid unnecessary buffer extension + cxBufferReset(&prop->buffer); + } + if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); return CX_PROPERTIES_INCOMPLETE_DATA; } @@ -256,6 +193,21 @@ } else { return CX_PROPERTIES_INVALID_MISSING_DELIMITER; } + } else { + // skip blank line + // if it was the rescue buffer, return to the original buffer + if (current_buffer == &prop->buffer) { + // assert that the rescue buffer really does not contain more data + assert(current_buffer->pos + i + 1 == current_buffer->size); + // reset the rescue buffer, but don't destroy it! + cxBufferReset(&prop->buffer); + // continue with the input buffer + current_buffer = &prop->input; + } else { + // if it was the input buffer already, just advance the position + current_buffer->pos += i + 1; + } + continue; } } else { cxstring k = cx_strn(buf, delimiter_index); @@ -267,19 +219,21 @@ if (k.length > 0) { *key = k; *value = val; - prop->text_pos += i + 1; - assert(prop->text_pos <= prop->text_size); + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); return CX_PROPERTIES_NO_ERROR; } else { return CX_PROPERTIES_INVALID_EMPTY_KEY; } } - - prop->text_pos += i + 1; + // unreachable - either we returned or skipped a blank line + assert(false); } // when we come to this point, all data must have been read - assert(prop->text_pos == prop->text_size); + assert(cxBufferEof(&prop->buffer)); + assert(cxBufferEof(&prop->input)); + return CX_PROPERTIES_NO_DATA; } @@ -310,7 +264,7 @@ CxPropertiesSource *src, cxstring *target ) { - if (prop->text == src->src) { + if (prop->input.space == src->src) { // when the input buffer already contains the string // we have nothing more to provide target->length = 0; @@ -351,6 +305,7 @@ CxPropertiesSource src; src.src = (void*) str.ptr; src.data_size = str.length; + src.data_ptr = NULL; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; @@ -361,6 +316,7 @@ CxPropertiesSource src; src.src = (void*) str; src.data_size = len; + src.data_ptr = NULL; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; @@ -371,6 +327,7 @@ CxPropertiesSource src; src.src = (void*) str; src.data_size = strlen(str); + src.data_ptr = NULL; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; @@ -381,6 +338,7 @@ CxPropertiesSource src; src.src = file; src.data_size = chunk_size; + src.data_ptr = NULL; src.read_func = cx_properties_read_file; src.read_init_func = cx_properties_read_init_file; src.read_clean_func = cx_properties_read_clean_file; @@ -420,9 +378,7 @@ } // set the input buffer and read the k/v-pairs - prop->text = input.ptr; - prop->text_size = input.length; - prop->text_pos = 0; + cxPropertiesFill(prop, input); CxPropertiesStatus kv_status; do {