solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
stdstreams.c
1#ifndef _POSIX_C_SOURCE
2#define _POSIX_C_SOURCE 200809L
3#endif
4
5#include "stdstreams.h"
6
7#include <assert.h>
8#include <float.h>
9#include <inttypes.h>
10#include <stdlib.h>
11#include <string.h>
12
13#ifdef _WIN32
14#include <windows.h>
15#else
16#include <termios.h>
17#include <unistd.h>
18#endif
19
20/* -------------------------------------------------------------------------
21 * Compiler-specific branch prediction macros for hot paths
22 * ---------------------------------------------------------------------- */
23#if defined(__GNUC__) || defined(__clang__)
24#define LIKELY(x) __builtin_expect(!!(x), 1)
25#define UNLIKELY(x) __builtin_expect(!!(x), 0)
26#else
27#define LIKELY(x) (x)
28#define UNLIKELY(x) (x)
29#endif
30
31/* =========================================================================
32 * Terminal IO implementation
33 * ====================================================================== */
34
35bool readline(const char* prompt, char* buffer, size_t buffer_len) {
36 if (prompt) {
37 fputs(prompt, stdout);
38 fflush(stdout);
39 }
40
41 if (fgets(buffer, (int)buffer_len, stdin) == NULL) return false;
42
43 buffer[strcspn(buffer, "\n")] = '\0';
44
45 /* Drain any overflow that didn't fit tightly in the buffer. */
46 if (strlen(buffer) >= buffer_len - 1) {
47 int c;
48 while ((c = getchar()) != EOF && c != '\n');
49 }
50 return true;
51}
52
53int getpassword(const char* prompt, char* buffer, size_t buffer_len) {
54#ifdef _WIN32
55 if (prompt) {
56 fputs(prompt, stdout);
57 fflush(stdout);
58 }
59
60 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
61 DWORD mode, count, i;
62
63 if (hStdin == INVALID_HANDLE_VALUE) return -1;
64 if (!GetConsoleMode(hStdin, &mode)) return -1;
65 if (!SetConsoleMode(hStdin, mode & ~ENABLE_ECHO_INPUT)) return -1;
66
67 for (i = 0; i < (DWORD)(buffer_len - 1); i++) {
68 if (!ReadConsoleA(hStdin, &buffer[i], 1, &count, NULL) || count == 0) break;
69 if (buffer[i] == '\n' || buffer[i] == '\r') {
70 buffer[i] = '\0';
71 break;
72 }
73 }
74 buffer[i] = '\0';
75
76 if (!SetConsoleMode(hStdin, mode)) return -1;
77 putchar('\n');
78 return (int)i;
79#else
80 struct termios old_t, new_t;
81
82 if (tcgetattr(fileno(stdin), &old_t) != 0) return -1;
83
84 new_t = old_t;
85 new_t.c_lflag &= (tcflag_t)~ECHO;
86
87 if (tcsetattr(fileno(stdin), TCSAFLUSH, &new_t) != 0) return -1;
88
89 bool ok = readline(prompt, buffer, buffer_len);
90
91 if (tcsetattr(fileno(stdin), TCSAFLUSH, &old_t) != 0) return -1;
92
93 putchar('\n');
94 return ok ? (int)strlen(buffer) : -1;
95#endif
96}
97
98/* =========================================================================
99 * Stream internals definitions
100 * ====================================================================== */
101
102enum stream_type {
103 INVALID_STREAM = -1, // Internal use only — indicates uninitialized or error state
104 FILE_STREAM = 0, // Standard file stream wrapper around FILE*
105 STRING_STREAM = 1, // In-memory string stream with dynamic resizing and null-termination guarantees
106};
107
108struct stream {
109 /* POSIX-style: returns bytes transferred (>0), 0 for EOF, -1 for error. */
110 ssize_t (*read)(void* handle, void* ptr, size_t n);
111 ssize_t (*write)(void* handle, const void* ptr, size_t n);
112 int (*read_char)(void* handle);
113 int (*eof)(void* handle);
114 int (*seek)(void* handle, long offset, int whence);
115 int (*flush)(void* handle);
116
117 void* handle;
118 enum stream_type type;
119};
120
121/* =========================================================================
122 * Public seek wrapper
123 * ====================================================================== */
124
125int stream_seek(stream_t stream, long offset, int whence) {
126 STREAM_ASSERT(stream);
127 return stream->seek(stream->handle, offset, whence);
128}
129
130/* =========================================================================
131 * FILE stream vtable
132 * ====================================================================== */
133
134static ssize_t file_read_impl(void* handle, void* ptr, size_t n) {
135 size_t r = fread(ptr, 1, n, (FILE*)handle);
136 if (r == 0) return ferror((FILE*)handle) ? -1 : 0;
137 return (ssize_t)r;
138}
139
140static ssize_t file_write_impl(void* handle, const void* ptr, size_t n) {
141 size_t w = fwrite(ptr, 1, n, (FILE*)handle);
142 return (w == 0 && ferror((FILE*)handle)) ? -1 : (ssize_t)w;
143}
144
146 STREAM_ASSERT(fp);
147 stream_t s = malloc(sizeof(struct stream));
148 if (!s) return NULL;
149
150 s->read = file_read_impl;
151 s->write = file_write_impl;
152 s->flush = (int (*)(void*))fflush;
153 s->seek = (int (*)(void*, long, int))fseek;
154 s->eof = (int (*)(void*))feof;
155 s->read_char = (int (*)(void*))fgetc;
156 s->handle = fp;
157 s->type = FILE_STREAM;
158 return s;
159}
160
161size_t file_stream_read(stream_t s, void* restrict ptr, size_t size, size_t count) {
162 STREAM_ASSERT(s);
163 STREAM_ASSERT(s->type == FILE_STREAM);
164 fseek((FILE*)s->handle, 0, SEEK_SET);
165 return fread(ptr, size, count, (FILE*)s->handle);
166}
167
168/* =========================================================================
169 * String stream capacity helper (Layer 1 Fast Math Allocation)
170 * ====================================================================== */
171
172static bool string_stream_ensure_capacity(string_stream* ss, size_t needed) {
173 if (needed <= ss->capacity) return true;
174
175 size_t new_cap = ss->capacity == 0 ? 64 : ss->capacity;
176 while (new_cap < needed) {
177 new_cap *= 2; // Exponential expansion limits malloc overhead calls
178 }
179
180 char* new_data = (char*)realloc(ss->data, new_cap);
181 if (!new_data) return false;
182
183 ss->data = new_data;
184 ss->capacity = new_cap;
185 return true;
186}
187
188/* =========================================================================
189 * String stream vtable
190 * ====================================================================== */
191
192static ssize_t string_read_impl(void* handle, void* ptr, size_t n) {
193 string_stream* ss = (string_stream*)handle;
194
195 if (ss->pos >= ss->size) return 0; /* EOF */
196
197 size_t avail = ss->size - ss->pos;
198 if (n > avail) n = avail;
199
200 memcpy(ptr, ss->data + ss->pos, n);
201 ss->pos += n;
202 return (ssize_t)n;
203}
204
205static ssize_t string_write_impl(void* handle, const void* ptr, size_t n) {
206 string_stream* ss = (string_stream*)handle;
207
208 size_t needed_cap = ss->pos + n + 1; // Factor in the NUL boundary
209
210 if (!string_stream_ensure_capacity(ss, needed_cap)) return -1;
211
212 memcpy(ss->data + ss->pos, ptr, n);
213 ss->pos += n;
214
215 if (ss->pos > ss->size) {
216 ss->size = ss->pos;
217 }
218
219 /* Strictly maintain null termination integrity at boundary */
220 ss->data[ss->size] = '\0';
221
222 return (ssize_t)n;
223}
224
225static int string_read_char_impl(void* handle) {
226 string_stream* ss = (string_stream*)handle;
227 if (ss->pos >= ss->size) return EOF;
228 return (unsigned char)ss->data[ss->pos++];
229}
230
231static int string_eof_impl(void* handle) {
232 string_stream* ss = (string_stream*)handle;
233 return ss->pos >= ss->size;
234}
235
236static int string_seek_impl(void* handle, long offset, int whence) {
237 string_stream* ss = (string_stream*)handle;
238 size_t new_pos;
239
240 switch (whence) {
241 case SEEK_SET:
242 if (offset < 0 || (size_t)offset > ss->size) return -1;
243 new_pos = (size_t)offset;
244 break;
245 case SEEK_CUR:
246 if (offset < 0 && (size_t)(-offset) > ss->pos) return -1;
247 if (offset > 0 && ss->pos + (size_t)offset > ss->size) return -1;
248 new_pos = (size_t)((ssize_t)ss->pos + offset);
249 break;
250 case SEEK_END:
251 if (offset > 0 || (size_t)(-offset) > ss->size) return -1;
252 new_pos = (size_t)((ssize_t)ss->size + offset);
253 break;
254 default:
255 return -1;
256 }
257
258 ss->pos = new_pos;
259 return 0;
260}
261
262static int string_flush_impl(void* handle) {
263 (void)handle;
264 return 0;
265}
266
267/* -------------------------------------------------------------------------
268 * Public string-stream helpers
269 * ---------------------------------------------------------------------- */
270
271int string_stream_write(stream_t stream, const char* str) {
272 STREAM_ASSERT(stream && stream->type == STRING_STREAM);
273
274 string_stream* ss = (string_stream*)stream->handle;
275 size_t len = strlen(str);
276 size_t needed_cap = ss->size + len + 1; // +1 explicitly limits NUL constraint breach
277
278 if (!string_stream_ensure_capacity(ss, needed_cap)) return -1;
279
280 memcpy(ss->data + ss->size, str, len);
281 ss->size += len;
282 ss->data[ss->size] = '\0'; /* Null term is secured */
283
284 return (int)len;
285}
286
287int string_stream_write_len(stream_t stream, const char* str, size_t n) {
288 STREAM_ASSERT(stream && stream->type == STRING_STREAM);
289
290 string_stream* ss = (string_stream*)stream->handle;
291 size_t needed_cap = ss->size + n + 1; // +1 explicitly limits NUL constraint breach
292
293 if (!string_stream_ensure_capacity(ss, needed_cap)) return -1;
294
295 memcpy(ss->data + ss->size, str, n);
296 ss->size += n;
297 ss->data[ss->size] = '\0'; /* Null term is secured */
298
299 return (int)n;
300}
301
302const char* string_stream_data(stream_t stream) {
303 if (!stream || stream->type != STRING_STREAM) return NULL;
304 return ((string_stream*)stream->handle)->data;
305}
306
307stream_t create_string_stream(size_t initial_capacity) {
308 /* Perform combined single-block memory allocation to drastically trim overhead */
309 stream_t s = malloc(sizeof(struct stream) + sizeof(string_stream));
310 if (!s) return NULL;
311
312 // The string_stream struct is allocated directly after the stream struct in the same block
313 // for cache locality and reduced malloc calls.
314 string_stream* ss = (string_stream*)(s + 1);
315
316 size_t cap = initial_capacity > 0 ? initial_capacity : 1;
317 ss->data = malloc(cap);
318 if (!ss->data) {
319 free(s);
320 return NULL;
321 }
322
323 ss->data[0] = '\0';
324 ss->size = 0;
325 ss->capacity = cap;
326 ss->pos = 0;
327
328 s->read = string_read_impl;
329 s->write = string_write_impl;
330 s->flush = string_flush_impl;
331 s->read_char = string_read_char_impl;
332 s->eof = string_eof_impl;
333 s->seek = string_seek_impl;
334
335 s->handle = ss;
336 s->type = STRING_STREAM;
337 return s;
338}
339
340/* =========================================================================
341 * Delimited read
342 * ====================================================================== */
343
344ssize_t read_until(stream_t stream, int delim, char* buffer, size_t buffer_size) {
345 STREAM_ASSERT(stream && buffer && buffer_size > 0);
346
347 /* --- Vectorized SIMD Fast Path for String Streams --- */
348 if (LIKELY(stream->type == STRING_STREAM)) {
349 string_stream* ss = (string_stream*)stream->handle;
350 if (UNLIKELY(ss->pos >= ss->size)) return -1;
351
352 size_t avail = ss->size - ss->pos;
353 size_t max_read = buffer_size - 1;
354 size_t check_len = avail < max_read ? avail : max_read;
355
356 const char* p = ss->data + ss->pos;
357
358 // memchr is often optimized with SIMD instructions for large buffers
359 const char* match = memchr(p, delim, check_len);
360
361 size_t copy_len = 0;
362 if (match) {
363 copy_len = (size_t)(match - p);
364 memcpy(buffer, p, copy_len);
365 ss->pos += copy_len + 1; /* Consume delimiter */
366 } else {
367 copy_len = check_len;
368 memcpy(buffer, p, copy_len);
369 ss->pos += copy_len;
370 }
371
372 buffer[copy_len] = '\0';
373 return (ssize_t)copy_len;
374 }
375
376 /* --- Standard fallback for traditional FILE_STREAM --- */
377 ssize_t bytes = 0;
378 int ch = 0;
379 while (bytes < (ssize_t)(buffer_size - 1)) {
380 ch = stream->read_char(stream->handle);
381 if (ch == EOF || ch == delim) break;
382 buffer[bytes++] = (char)ch;
383 }
384
385 if (ch == EOF && stream->eof(stream->handle) && bytes == 0) return -1;
386 buffer[bytes] = '\0';
387 return bytes;
388}
389
390/* =========================================================================
391 * Layer 1 — fast string→string copy
392 * ====================================================================== */
393
395 STREAM_ASSERT(dst && src);
396 STREAM_ASSERT(dst->type == STRING_STREAM && src->type == STRING_STREAM);
397
398 string_stream* s = (string_stream*)src->handle;
399 string_stream* d = (string_stream*)dst->handle;
400
401 if (s->pos >= s->size) return 0; /* nothing to copy */
402
403 size_t n = s->size - s->pos;
404 size_t needed_cap = d->pos + n + 1; // Protect NUL requirement constraints
405
406 /* Grow destination securely caching exponential capacity checks. */
407 if (!string_stream_ensure_capacity(d, needed_cap)) return (unsigned long)-1;
408
409 memcpy(d->data + d->pos, s->data + s->pos, n);
410
411 if (d->pos + n > d->size) {
412 d->size = d->pos + n;
413 }
414
415 /* Enforce rigid null termination constraint directly */
416 d->data[d->size] = '\0';
417
418 d->pos += n;
419 s->pos += n;
420 return (unsigned long)n;
421}
422
423/* =========================================================================
424 * Layer 2 — generic copy
425 * ====================================================================== */
426
427unsigned long io_copy(stream_t writer, stream_t reader) {
428 STREAM_ASSERT(writer && reader);
429
430 /* ---- FAST PATH: Native String → String bypass ---- */
431 if (writer->type == STRING_STREAM && reader->type == STRING_STREAM) return string_stream_copy_fast(writer, reader);
432
433 /* L1/L2 cache tuned 16 KiB buffer chunking limits vtable dispatch overhead */
434 char buf[16384];
435 ssize_t nread = 0;
436 unsigned long total = 0;
437
438 while ((nread = reader->read(reader->handle, buf, sizeof(buf))) > 0) {
439 STREAM_ASSERT((size_t)nread <= sizeof(buf));
440 ssize_t w = writer->write(writer->handle, buf, (size_t)nread);
441 if (w < 0) return (unsigned long)-1;
442 total += (unsigned long)w;
443 }
444
445 if (nread < 0) return (unsigned long)-1;
446
447 writer->flush(writer->handle);
448 return total;
449}
450
451unsigned long io_copy_n(stream_t writer, stream_t reader, size_t n) {
452 STREAM_ASSERT(writer && reader);
453
454 // L1/L2 cache tuned 16 KiB buffer chunking limits vtable dispatch overhead
455 char buf[16384];
456 ssize_t nread = 0;
457 unsigned long total = 0;
458
459 while (n > 0) {
460 size_t want = n < sizeof(buf) ? n : sizeof(buf);
461 nread = reader->read(reader->handle, buf, want);
462 if (nread <= 0) break;
463
464 STREAM_ASSERT((size_t)nread <= sizeof(buf));
465
466 ssize_t w = writer->write(writer->handle, buf, (size_t)nread);
467 if (w < 0) return (unsigned long)-1;
468
469 total += (unsigned long)w;
470 n -= (size_t)nread;
471 }
472
473 if (nread < 0) return (unsigned long)-1;
474
475 writer->flush(writer->handle);
476 return total;
477}
478
479/* =========================================================================
480 * Destroy
481 * ====================================================================== */
482
483static void free_file_stream(stream_t s) {
484 if (!s) return;
485 FILE* fp = (FILE*)s->handle;
486 if (fp != stdout && fp != stderr && fp != stdin) fclose(fp);
487 free(s);
488}
489
490static void free_string_stream(stream_t s) {
491 if (!s) return;
492
493 /* s->handle effectively points to memory inherently linked beside 's' buffer due to creation step. */
494 string_stream* ss = (string_stream*)s->handle;
495 if (ss && ss->data) {
496 free(ss->data);
497 }
498
499 /* Only requires a single free operation targeting 's' due to contiguous inline setup wrapper mechanism! */
500 free(s);
501}
502
504 if (!stream) return;
505 switch (stream->type) {
506 case FILE_STREAM:
507 free_file_stream(stream);
508 break;
509 case STRING_STREAM:
510 free_string_stream(stream);
511 break;
512 case INVALID_STREAM:
513 fprintf(stderr, "[stream_destroy]: warning: attempted to destroy invalid stream\n");
514 break;
515 }
516}
#define LIKELY(x)
Definition macros.h:66
Standard stream handling utilities.
size_t file_stream_read(stream_t s, void *restrict ptr, size_t size, size_t count)
Read up to count objects of size bytes from a file stream into ptr, rewinding to the beginning first.
Definition stdstreams.c:161
void stream_destroy(stream_t stream)
Destroy a stream and release all connected resources.
Definition stdstreams.c:503
int stream_seek(stream_t stream, long offset, int whence)
Seek within a stream exactly matching POSIX fseek(3) semantics.
Definition stdstreams.c:125
const char * string_stream_data(stream_t stream)
Fetch a read-only view of the underlying strictly NUL-terminated string data.
Definition stdstreams.c:302
bool readline(const char *prompt, char *buffer, size_t buffer_len)
Read a line from stdin, optionally printing a prompt first.
Definition stdstreams.c:35
struct stream * stream_t
Opaque stream handle.
Definition stdstreams.h:81
int string_stream_write(stream_t stream, const char *str)
Append the NUL-terminated string str to stream.
Definition stdstreams.c:271
int getpassword(const char *prompt, char *buffer, size_t buffer_len)
Read a password from the terminal with echo disabled.
Definition stdstreams.c:53
int string_stream_write_len(stream_t stream, const char *str, size_t n)
Append the NUL-terminated string str to stream. This is faster than string_stream_write when the leng...
Definition stdstreams.c:287
unsigned long io_copy_n(stream_t writer, stream_t reader, size_t n)
Copy tightly capped chunk up to n bytes.
Definition stdstreams.c:451
stream_t create_file_stream(FILE *fp)
Wrap an existing FILE* in a standardized stream context.
Definition stdstreams.c:145
unsigned long string_stream_copy_fast(stream_t dst, stream_t src)
Direct memory-bound string to string copy without generic dispatch.
Definition stdstreams.c:394
unsigned long io_copy(stream_t writer, stream_t reader)
Copy contents universally from reader into writer.
Definition stdstreams.c:427
stream_t create_string_stream(size_t initial_capacity)
Allocate a new in-memory string stream.
Definition stdstreams.c:307
ssize_t read_until(stream_t stream, int delim, char *buffer, size_t buffer_size)
Read from stream into buffer until delim is found.
Definition stdstreams.c:344
String stream internal state structure.
Definition stdstreams.h:91
size_t capacity
Definition stdstreams.h:94