2#define _POSIX_C_SOURCE 200809L
23#if defined(__GNUC__) || defined(__clang__)
24#define LIKELY(x) __builtin_expect(!!(x), 1)
25#define UNLIKELY(x) __builtin_expect(!!(x), 0)
28#define UNLIKELY(x) (x)
35bool readline(
const char* prompt,
char* buffer,
size_t buffer_len) {
37 fputs(prompt, stdout);
41 if (fgets(buffer, (
int)buffer_len, stdin) == NULL)
return false;
43 buffer[strcspn(buffer,
"\n")] =
'\0';
46 if (strlen(buffer) >= buffer_len - 1) {
48 while ((c = getchar()) != EOF && c !=
'\n');
53int getpassword(
const char* prompt,
char* buffer,
size_t buffer_len) {
56 fputs(prompt, stdout);
60 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
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;
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') {
76 if (!SetConsoleMode(hStdin, mode))
return -1;
80 struct termios old_t, new_t;
82 if (tcgetattr(fileno(stdin), &old_t) != 0)
return -1;
85 new_t.c_lflag &= (tcflag_t)~ECHO;
87 if (tcsetattr(fileno(stdin), TCSAFLUSH, &new_t) != 0)
return -1;
89 bool ok =
readline(prompt, buffer, buffer_len);
91 if (tcsetattr(fileno(stdin), TCSAFLUSH, &old_t) != 0)
return -1;
94 return ok ? (int)strlen(buffer) : -1;
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);
118 enum stream_type type;
126 STREAM_ASSERT(stream);
127 return stream->seek(stream->handle, offset, whence);
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;
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;
147 stream_t s = malloc(
sizeof(
struct stream));
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;
157 s->type = FILE_STREAM;
163 STREAM_ASSERT(s->type == FILE_STREAM);
164 fseek((FILE*)s->handle, 0, SEEK_SET);
165 return fread(ptr, size, count, (FILE*)s->handle);
172static bool string_stream_ensure_capacity(
string_stream* ss,
size_t needed) {
173 if (needed <= ss->capacity)
return true;
176 while (new_cap < needed) {
180 char* new_data = (
char*)realloc(ss->
data, new_cap);
181 if (!new_data)
return false;
192static ssize_t string_read_impl(
void* handle,
void* ptr,
size_t n) {
195 if (ss->
pos >= ss->
size)
return 0;
197 size_t avail = ss->
size - ss->
pos;
198 if (n > avail) n = avail;
200 memcpy(ptr, ss->
data + ss->
pos, n);
205static ssize_t string_write_impl(
void* handle,
const void* ptr,
size_t n) {
208 size_t needed_cap = ss->
pos + n + 1;
210 if (!string_stream_ensure_capacity(ss, needed_cap))
return -1;
212 memcpy(ss->
data + ss->
pos, ptr, n);
225static int string_read_char_impl(
void* handle) {
227 if (ss->
pos >= ss->
size)
return EOF;
228 return (
unsigned char)ss->
data[ss->
pos++];
231static int string_eof_impl(
void* handle) {
236static int string_seek_impl(
void* handle,
long offset,
int whence) {
242 if (offset < 0 || (
size_t)offset > ss->
size)
return -1;
243 new_pos = (size_t)offset;
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);
251 if (offset > 0 || (
size_t)(-offset) > ss->
size)
return -1;
252 new_pos = (size_t)((ssize_t)ss->
size + offset);
262static int string_flush_impl(
void* handle) {
272 STREAM_ASSERT(stream && stream->type == STRING_STREAM);
275 size_t len = strlen(str);
276 size_t needed_cap = ss->
size + len + 1;
278 if (!string_stream_ensure_capacity(ss, needed_cap))
return -1;
280 memcpy(ss->
data + ss->
size, str, len);
288 STREAM_ASSERT(stream && stream->type == STRING_STREAM);
291 size_t needed_cap = ss->
size + n + 1;
293 if (!string_stream_ensure_capacity(ss, needed_cap))
return -1;
295 memcpy(ss->
data + ss->
size, str, n);
303 if (!stream || stream->type != STRING_STREAM)
return NULL;
316 size_t cap = initial_capacity > 0 ? initial_capacity : 1;
317 ss->
data = malloc(cap);
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;
336 s->type = STRING_STREAM;
345 STREAM_ASSERT(stream && buffer && buffer_size > 0);
348 if (
LIKELY(stream->type == STRING_STREAM)) {
350 if (UNLIKELY(ss->
pos >= ss->
size))
return -1;
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;
356 const char* p = ss->
data + ss->
pos;
359 const char* match = memchr(p, delim, check_len);
363 copy_len = (size_t)(match - p);
364 memcpy(buffer, p, copy_len);
365 ss->
pos += copy_len + 1;
367 copy_len = check_len;
368 memcpy(buffer, p, copy_len);
372 buffer[copy_len] =
'\0';
373 return (ssize_t)copy_len;
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;
385 if (ch == EOF && stream->eof(stream->handle) && bytes == 0)
return -1;
386 buffer[bytes] =
'\0';
395 STREAM_ASSERT(dst && src);
396 STREAM_ASSERT(dst->type == STRING_STREAM && src->type == STRING_STREAM);
401 if (s->
pos >= s->
size)
return 0;
404 size_t needed_cap = d->
pos + n + 1;
407 if (!string_stream_ensure_capacity(d, needed_cap))
return (
unsigned long)-1;
420 return (
unsigned long)n;
428 STREAM_ASSERT(writer && reader);
431 if (writer->type == STRING_STREAM && reader->type == STRING_STREAM)
return string_stream_copy_fast(writer, reader);
436 unsigned long total = 0;
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;
445 if (nread < 0)
return (
unsigned long)-1;
447 writer->flush(writer->handle);
452 STREAM_ASSERT(writer && reader);
457 unsigned long total = 0;
460 size_t want = n <
sizeof(buf) ? n :
sizeof(buf);
461 nread = reader->read(reader->handle, buf, want);
462 if (nread <= 0)
break;
464 STREAM_ASSERT((
size_t)nread <=
sizeof(buf));
466 ssize_t w = writer->write(writer->handle, buf, (
size_t)nread);
467 if (w < 0)
return (
unsigned long)-1;
469 total += (
unsigned long)w;
473 if (nread < 0)
return (
unsigned long)-1;
475 writer->flush(writer->handle);
483static void free_file_stream(
stream_t s) {
485 FILE* fp = (FILE*)s->handle;
486 if (fp != stdout && fp != stderr && fp != stdin) fclose(fp);
490static void free_string_stream(
stream_t s) {
495 if (ss && ss->
data) {
505 switch (stream->type) {
507 free_file_stream(stream);
510 free_string_stream(stream);
513 fprintf(stderr,
"[stream_destroy]: warning: attempted to destroy invalid stream\n");
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.
void stream_destroy(stream_t stream)
Destroy a stream and release all connected resources.
int stream_seek(stream_t stream, long offset, int whence)
Seek within a stream exactly matching POSIX fseek(3) semantics.
const char * string_stream_data(stream_t stream)
Fetch a read-only view of the underlying strictly NUL-terminated string data.
bool readline(const char *prompt, char *buffer, size_t buffer_len)
Read a line from stdin, optionally printing a prompt first.
struct stream * stream_t
Opaque stream handle.
int string_stream_write(stream_t stream, const char *str)
Append the NUL-terminated string str to stream.
int getpassword(const char *prompt, char *buffer, size_t buffer_len)
Read a password from the terminal with echo disabled.
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...
unsigned long io_copy_n(stream_t writer, stream_t reader, size_t n)
Copy tightly capped chunk up to n bytes.
stream_t create_file_stream(FILE *fp)
Wrap an existing FILE* in a standardized stream context.
unsigned long string_stream_copy_fast(stream_t dst, stream_t src)
Direct memory-bound string to string copy without generic dispatch.
unsigned long io_copy(stream_t writer, stream_t reader)
Copy contents universally from reader into writer.
stream_t create_string_stream(size_t initial_capacity)
Allocate a new in-memory string stream.
ssize_t read_until(stream_t stream, int delim, char *buffer, size_t buffer_size)
Read from stream into buffer until delim is found.
String stream internal state structure.