Append Message into a Dynamic Log Buffer with Max Capacity (grow/compact)
Title: Append C-String Message into a Growing Log with Hard Cap and Compaction
Level: Difficult
Concepts: malloc/realloc growth, maximum capacity, compaction vs. growth policy, NUL‑termination, pointer arithmetic, error isolation, consistent state
Scenario
You maintain a single dynamic char buffer that stores a log of messages separated by a delimiter (e.g., '\n'). The buffer should grow to accommodate new messages but must not exceed a configured max capacity (bytes). When there’s not enough space, your API should try to compact (drop the oldest messages from the beginning) to make room; if still insufficient and you’re below max, it should grow (up to max). If the message can’t fit even after compaction and growth (or the message itself is larger than max), the append should fail without partial writes.
Problem Statement
Implement a function that appends a null‑terminated message (without its trailing '\0') plus a delimiter into a dynamic buffer, while:
- Preserving existing log data order,
- Ensuring the resulting buffer is NUL‑terminated for convenient printing,
- Never exceeding
max_capacity, - Performing compaction (remove oldest prefix) before growth,
- Growing via
reallocusing a temporary pointer to avoid leaks, - Leaving the buffer unchanged if the operation fails.
Requirements
- Allowed types only:
int,long,double,char,bool,enum, plus pointers. - Inputs:
char **buf— pointer to dynamic buffer (may beNULLif empty).int *used— number of valid bytes currently used excluding final NUL (≥ 0).int *capacity— current allocation size in bytes (≥ 0).int max_capacity— hard cap in bytes (> 0).const char *msg— NUL‑terminated message to append.char delim— delimiter to append after the message.
- Behavior:
- Compute
msg_lenby scanningmsguntil'\0'. - Required additional bytes =
msg_len + 1(for delimiter). Buffer must also have space for a final NUL (1 more byte not counted in*used). - If
msg_len + 1 > max_capacity, fail (message too large). - Compaction step (if
*used > 0): remove oldest prefix to free space only if needed (e.g., drop from the start until enough room exists or log becomes empty). This is done by shifting remaining bytes down with a manual copy (nomemmoveassumed), then updating*used. - If still insufficient and
*capacity < max_capacity, grow capacity (e.g., double up tomax_capacity), guarding against overflow; use tempreallocand commit only on success. - After enough space is available: write
msgbytes, thendelim, update*used, and ensure(*buf)[*used] = '\0'.
- Compute
- Error handling:
- Invalid pointers/values →
-1(no changes). - Couldn’t make room (even after compaction/growth) →
-2(no changes).
- Invalid pointers/values →
- Return codes:
0success;-1invalid args;-2insufficient space (and message not appended).
Function Details
- Name:
logbuf_append - Arguments:
char **bufint *used// bytes in use (excludes trailing NUL)int *capacity// total allocated bytesint max_capacity// hard upper boundconst char *msg// NUL-terminatedchar delim
- Return Value:
int—0on success;-1invalid input;-2cannot fit (after compaction/growth). - Description:
Appendsmsganddelimto a dynamic log, ensuring there is always one trailing'\0'. Attempts compaction first by dropping oldest bytes; then tries to grow (not exceedingmax_capacity). Uses a temporary result fromreallocto avoid leaks. On failure, nothing changes.
Solution Approach
- Validate: pointers non‑NULL;
*used ≥ 0,*capacity ≥ 0,max_capacity > 0;*used ≤ *capacity. - Measure
msg_lenby scanningmsguntil'\0'. - Reject if
msg_len + 1 > max_capacity(too large). - Ensure space: needed total free =
(msg_len + 1) + 1(data + delim + final NUL) minus current free(capacity - used). - Compact if needed:
- Compute
need = msg_len + 1(without the final NUL; NUL sits atused). - If
*used > 0and not enough room, drop a prefix:- Decide minimal prefix length to drop so that
new_used + need ≤ max_capacity - 1(keeping space for final NUL). - Shift remaining bytes down with a manual copy loop; update
*used.
- Decide minimal prefix length to drop so that
- Compute
- Grow if needed:
- If still not enough and
*capacity < max_capacity:- New capacity = min(max_capacity, max( (*capacity==0? 8 : *capacity * 2), *used + need + 1 )) with overflow checks using
long. tmp = realloc(*buf, new_capacity), commit on success.
- New capacity = min(max_capacity, max( (*capacity==0? 8 : *capacity * 2), *used + need + 1 )) with overflow checks using
- If still not enough and
- Append: copy
msgbytes, thendelim, update*used, write trailing'\0'. - Return with
0on success;-2if space couldn’t be made.
Tasks to Perform
- Validate arguments and invariants (
*used ≤ *capacity,max_capacity > 0). - Measure
msg_len. - If
msg_len + 1 > max_capacity, return-2. - Calculate required free (include space for trailing
'\0'). - If insufficient:
- Compact by removing minimal prefix; update
*used. - If still insufficient, grow via
reallocup tomax_capacityusing a temporary pointer; commit on success.
- Compact by removing minimal prefix; update
- If space is now sufficient:
- Copy
msg, thendelim;*used += msg_len + 1; set(*buf)[*used] = '\0'.
- Copy
- On any failure to make room, return
-2without modifying the buffer or metadata.
Test Cases
| # | Inputs / Precondition | Expected Output | Notes |
|---|---|---|---|
| 1 | *buf=NULL,*used=0,*capacity=0,max=32; msg="A", delim='\n' |
ret=0; capacity grows (e.g., 8); used=2; buf="A\n\0" |
First append allocates |
| 2 | Append "BC" then "DEF" under max=32 |
ret=0; buf="A\nBC\nDEF\n\0"; used=8 |
Normal growth path |
| 3 | Small capacity near full; append small msg; compaction frees space | ret=0; oldest bytes dropped; buf ends with only newest messages; NUL at end |
Compaction exercised |
| 4 | msg length = max_capacity (no delimiter space) |
ret=-2; unchanged |
Too large: needs delimiter + NUL |
| 5 | msg="" (empty) |
ret=0; only delimiter appended; used += 1 |
Handles empty messages |
| 6 | *used > *capacity or negative values |
ret=-1 |
Invalid invariants |
| 7 | Growth hits max_capacity and still can’t fit after compaction |
ret=-2; unchanged |
Hard cap respected |
| 8 | Simulated realloc failure (conceptual) |
ret=-2; unchanged; no leaks |
Commit-after-success pattern |