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 1
3#endif
4
5#include "../include/stdstreams.h"
6
7#include <float.h>
8#include <inttypes.h>
9#include <stdlib.h>
10#include <string.h>
11
12#ifdef _WIN32
13#include <windows.h>
14#else
15#include <termios.h>
16#include <unistd.h>
17#endif
18
19// Read a line from stdin with a prompt
20bool readline(const char* prompt, char* buffer, size_t buffer_len) {
21 if (prompt) {
22 printf("%s", prompt);
23 fflush(stdout);
24 }
25
26 if (fgets(buffer, (int)buffer_len, stdin) == NULL) {
27 return false;
28 }
29
30 buffer[strcspn(buffer, "\n")] = '\0'; // Remove trailing newline
31
32 if (strlen(buffer) >= buffer_len - 1) {
33 int c = EOF;
34 while ((c = getchar()) != EOF) {
35 if (c == '\n') break;
36 }
37 }
38 return true;
39}
40
41// getpassword reads a password from the terminal with echo disabled.
42// If the prompt is not NULL, it is printed before reading the password.
43// uses termios on Linux and Windows API on Windows.
44// The password is stored in the buffer and the function returns the number of
45// characters read or -1 on error. The buffer is null-terminated.
46int getpassword(const char* prompt, char* buffer, size_t buffer_len) {
47#ifdef _WIN32
48 if (prompt) {
49 printf("%s", prompt);
50 fflush(stdout);
51 }
52
53 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
54 DWORD mode;
55 DWORD count;
56 DWORD i;
57
58 if (hStdin == INVALID_HANDLE_VALUE) {
59 return -1;
60 }
61
62 if (!GetConsoleMode(hStdin, &mode)) {
63 return -1;
64 }
65
66 if (!SetConsoleMode(hStdin, mode & ~ENABLE_ECHO_INPUT)) {
67 return -1;
68 }
69
70 for (i = 0; i < buffer_len - 1; i++) {
71 if (!ReadConsoleA(hStdin, &buffer[i], 1, &count, NULL) || count == 0) {
72 break;
73 }
74
75 if (buffer[i] == '\n' || buffer[i] == '\r') {
76 buffer[i] = '\0';
77 break;
78 }
79 }
80
81 buffer[i] = '\0';
82
83 if (!SetConsoleMode(hStdin, mode)) {
84 return -1;
85 }
86 // Add a newline
87 printf("\n");
88 return i;
89#else
90 struct termios old, new;
91 int nread = 0;
92
93 // Turn off echoing
94 if (tcgetattr(fileno(stdin), &old) != 0) {
95 return -1;
96 }
97
98 new = old;
99 new.c_lflag &= (tcflag_t)~ECHO;
100
101 if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) {
102 return -1;
103 }
104
105 // Read the password
106 nread = readline(prompt, buffer, buffer_len);
107
108 // Restore terminal
109 if (tcsetattr(fileno(stdin), TCSAFLUSH, &old) != 0) {
110 return -1;
111 }
112
113 // Add a newline to stdout
114 putc('\n', stdout);
115 return nread;
116#endif
117}
118
119enum stream_type {
120 INVALID_STREAM = -1,
121 FILE_STREAM = 0,
122 STRING_STREAM = 1,
123};
124
125// stream_t type wraps common FILE* operations to be work with
126// strings as a files.
127// Any type can implement
128struct stream {
129 // Function pointer to from the stream
130 unsigned long (*read)(void* handle, size_t size, size_t count, void* ptr);
131
132 // Function pointer for reading a single character from the stream
133 int (*read_char)(void* handle);
134
135 // Function pointer for writing to the stream (if applicable)
136 // similar API as fwrite
137 size_t (*write)(const void* ptr, size_t size, size_t count, void* handle);
138
139 // Function pointer to check if the end of the stream is reached
140 int (*eof)(void* handle);
141
142 // Function pointer for seeking within the stream (if applicable)
143 int (*seek)(void* handle, long offset, int whence);
144
145 // flush the stream
146 int (*flush)(void* handle);
147
148 // The concrete stream implementation
149 void* handle;
150
151 // Store the type of stream
152 enum stream_type type;
153};
154
155static size_t file_write(const void* ptr, size_t size, size_t count, void* handle) {
156 return fwrite(ptr, size, count, (FILE*)handle);
157}
158
159static unsigned long file_read(void* handle, size_t size, size_t count, void* ptr) {
160 return fread(ptr, size, count, (FILE*)handle);
161}
162
163size_t file_stream_read(stream_t s, void* restrict ptr, size_t size, size_t count) {
164 if (s->type == FILE_STREAM) {
165 s->seek(s->handle, 0, SEEK_SET);
166 return fread(ptr, size, count, (FILE*)s->handle);
167 }
168
169 fprintf(stderr, "Reading from a non file stream\n");
170 return EOF;
171}
172
173stream_t create_file_stream(FILE* fp) {
174 stream_t stream = malloc(sizeof(struct stream));
175 if (!stream) {
176 return NULL;
177 }
178
179 stream->read = file_read;
180 stream->write = file_write;
181 stream->flush = (int (*)(void*))fflush;
182 stream->seek = (int (*)(void*, long int, int))fseek;
183 stream->eof = (int (*)(void*))feof;
184 stream->read_char = (int (*)(void*))fgetc;
185 stream->handle = fp;
186 stream->type = FILE_STREAM;
187 return stream;
188}
189
190ssize_t read_until(stream_t stream, int delim, char* buffer, size_t buffer_size) {
191 ssize_t bytes_read = 0;
192 int ch = 0;
193 while ((ch = stream->read_char(stream->handle)) != EOF && ch != delim && bytes_read < (int)buffer_size - 1) {
194 buffer[bytes_read++] = (char)ch;
195 }
196
197 // Handle errors (EOF or read error)
198 if (ch == EOF && stream->eof(stream->handle)) {
199 return -1; // Indicate error
200 }
201
202 // Null-terminate the string if there's space
203 if (bytes_read < (int)(buffer_size - 1)) {
204 buffer[bytes_read] = '\0';
205 } else {
206#ifndef NDEBUG
207 // Handle buffer overflow
208 puts("[read_upto_char]: Buffer overflow");
209 printf("read %zd bytes, buffer size %zu\n", bytes_read, buffer_size);
210#endif
211 buffer[buffer_size - 1] = '\0'; // truncate the string
212 }
213 return bytes_read;
214}
215
216unsigned long io_copy(stream_t writer, stream_t reader) {
217 char buffer[4096];
218 unsigned long nread = 0;
219 unsigned long total_written = 0;
220 reader->seek(reader->handle, 0, SEEK_SET);
221
222 while ((nread = reader->read(reader->handle, 1, sizeof(buffer), buffer)) > 0) {
223 total_written += writer->write(buffer, 1, nread, writer->handle);
224 }
225
226 // Flush the destination stream
227 writer->flush(writer->handle);
228 return total_written;
229}
230
231// Copy contents of one reader into writer, writing up to n bytes into the
232// writer.
233unsigned long io_copy_n(stream_t writer, stream_t reader, size_t n) {
234 char buffer[4096];
235 unsigned long nread = 0;
236 unsigned long total_written = 0;
237 reader->seek(reader->handle, 0, SEEK_SET);
238
239 while (n > 0 && (nread = reader->read(reader->handle, 1, sizeof(buffer), buffer)) > 0) {
240 if ((nread > n)) {
241 nread = n;
242 }
243
244 total_written += writer->write(buffer, 1, nread, writer->handle);
245 n -= nread;
246 }
247
248 // Flush the destination stream
249 writer->flush(writer->handle);
250 return total_written;
251}
252
253// concrete implementation for reading from a string
254static unsigned long inner_string_stream_read(void* handle, size_t size, size_t count, void* ptr) {
255 (void)size; // size is always 1 for a string
256
257 string_stream* ss = (string_stream*)handle;
258 size_t string_len = cstr_len(ss->str);
259
260 if (count == 0 || ss->pos >= string_len) {
261 return EOF;
262 }
263
264 // Check for size_t overflow when calculating bytes_to_read
265 if (count > SIZE_MAX) {
266 // Prevent overflow, return 0 as we can't handle such a large read
267 return EOF;
268 }
269
270 // make sure we don't read past the end of the string
271 if (ss->pos + count > string_len) {
272 count = string_len - ss->pos; // read only the remaining bytes
273 }
274
275 // Perform the read operation
276 char* data = (char*)cstr_data(ss->str);
277 memcpy(ptr, data + ss->pos, count);
278 ss->pos += count;
279
280 return count;
281}
282
283// implementation for reading a single character from a string
284static int string_stream_read_char(void* handle) {
285 string_stream* ss = (string_stream*)handle;
286 if (ss->pos >= cstr_len(ss->str)) {
287 return EOF;
288 }
289 return cstr_data(ss->str)[ss->pos++];
290}
291
292// Returns whether the end of the string has been reached
293static int string_stream_eof(void* handle) {
294 string_stream* ss = (string_stream*)handle;
295 return ss->pos >= cstr_len(ss->str);
296}
297
298// implementation for writing to a string. For a char *, size must be 1.
299static size_t inner__string_stream_write(const void* ptr, size_t size, size_t count, void* handle) {
300 string_stream* ss = (string_stream*)handle;
301 size_t bytes_written = 0;
302 size_t total_bytes = size * count;
303 bool resized = false;
304
305 resized = cstr_resize(ss->str, cstr_len(ss->str) + total_bytes);
306 if (!resized) {
307 return 0;
308 }
309
310 char* data = (char*)cstr_data(ss->str);
311 for (size_t i = 0; i < total_bytes; i++) {
312 data[ss->pos++] = ((char*)ptr)[i];
313 bytes_written++;
314 }
315 return bytes_written / size;
316}
317
318static int string_stream_seek(void* handle, long offset, int whence) {
319 string_stream* ss = (string_stream*)handle;
320 size_t length = cstr_len(ss->str);
321 size_t new_pos = ss->pos;
322
323 switch (whence) {
324 case SEEK_SET:
325 if (offset < 0 || (size_t)offset > length) return EOF;
326 new_pos = (size_t)offset;
327 break;
328 case SEEK_CUR:
329 if ((offset < 0 && (size_t)(-offset) > ss->pos) || (offset > 0 && ss->pos + (size_t)offset > length))
330 return EOF;
331 new_pos += (size_t)offset;
332 break;
333 case SEEK_END:
334 if ((offset < 0 && (size_t)(-offset) > length) || (offset > 0)) return EOF;
335 new_pos = length + (size_t)offset;
336 break;
337 default:
338 return EOF;
339 }
340
341 ss->pos = new_pos;
342 return 0;
343}
344
345static int string_flush(void* handle) {
346 (void)handle;
347 return 0;
348}
349
350// copy the string to the stream. The position is not incremented.
351int string_stream_write(stream_t stream, const char* str) {
352 // copy over the string to the stream.
353 // we don't have to increment the position.
354 string_stream* ss = (string_stream*)stream->handle;
355 if (!cstr_append(ss->str, str)) {
356 return -1;
357 }
358 return (int)strlen(str);
359}
360
361const char* string_stream_data(stream_t stream) {
362 if (stream->type == STRING_STREAM) {
363 string_stream* ss = (string_stream*)stream->handle;
364 return cstr_data(ss->str);
365 } else {
366 return NULL;
367 }
368}
369
370static void inner__free_file_stream(stream_t stream) {
371 if (!stream) return;
372 FILE* fp = (FILE*)stream->handle;
373
374 if (!(fp == stdout || fp == stderr || fp == stdin)) {
375 fclose(fp);
376 }
377
378 free(stream);
379 stream = NULL;
380}
381
382static void inner__free_string_stream(stream_t stream) {
383 if (!stream) return;
384
385 if (stream->handle) {
386 free(stream->handle);
387 stream->handle = NULL;
388 }
389
390 free(stream);
391 stream = NULL;
392}
393
394void stream_destroy(stream_t stream) {
395 switch (stream->type) {
396 case FILE_STREAM:
397 inner__free_file_stream(stream);
398 break;
399 case STRING_STREAM:
400 inner__free_string_stream(stream);
401 break;
402 case INVALID_STREAM:
403 // Should never happen
404 fprintf(stderr, "Invalid stream");
405 exit(EXIT_FAILURE);
406 }
407}
408
409// Create a stream from a string from a char *
410stream_t create_string_stream(size_t initial_capacity) {
411 stream_t stream = malloc(sizeof(struct stream));
412 if (!stream) {
413 return NULL;
414 }
415
416 string_stream* ss = (string_stream*)malloc(sizeof(string_stream));
417 if (ss == NULL) {
418 free(stream);
419 return NULL;
420 }
421
422 ss->str = cstr_init(initial_capacity);
423 if (ss->str == NULL) {
424 free(stream);
425 free(ss);
426 return NULL;
427 }
428
429 ss->pos = 0;
430 stream->read = inner_string_stream_read;
431 stream->flush = (int (*)(void*))string_flush;
432 stream->read_char = string_stream_read_char;
433 stream->eof = string_stream_eof;
434 stream->write = inner__string_stream_write;
435 stream->seek = string_stream_seek;
436 stream->handle = ss;
437 stream->type = STRING_STREAM;
438 return stream;
439}
CSTR_INLINE bool cstr_resize(cstr *s, size_t capacity) CSTR_NONNULL(1) CSTR_WARN_UNUSED
Definition cstr.h:286
bool cstr_append(cstr *s, const char *CSTR_RESTRICT append) CSTR_NONNULL(1
Append a NUL-terminated C string.
CSTR_INLINE size_t cstr_len(const cstr *s) CSTR_PURE
Length of the string, excluding NUL.
Definition cstr.h:153
cstr * cstr_init(size_t initial_capacity) CSTR_WARN_UNUSED
Create a new heap-allocated cstr with a given initial capacity.
Definition cstr.c:127
CSTR_INLINE char * cstr_data(cstr *s) CSTR_PURE
Mutable pointer to the NUL-terminated string data. Never NULL for a valid cstr.
Definition cstr.h:182
size_t file_read(const file_t *file, void *buffer, size_t size, size_t count)
Definition file.c:301
size_t file_write(file_t *file, const void *buffer, size_t size, size_t count)
Definition file.c:308