| #if defined(__unix__) || defined(__APPLE__) || defined(__linux__) || \ |
| defined(__CYGWIN__) /* require POSIX */ |
| /* |
| Copyright: Boaz Segev, 2017-2018 |
| License: MIT |
| */ |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| /** |
| * A dynamic type for reading / writing to a local file, a temporary file or an |
| * in-memory string. |
| * |
| * Supports basic reak, write, seek, puts and gets operations. |
| * |
| * Writing is always performed at the end of the stream / memory buffer, |
| * ignoring the current seek position. |
| */ |
| #include <fio_tmpfile.h> |
| #include <fiobj_data.h> |
| #include <fiobj_str.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <fio.h> |
| |
| /* ***************************************************************************** |
| Numbers Type |
| ***************************************************************************** */ |
| |
| typedef struct { |
| fiobj_object_header_s head; |
| uint8_t *buffer; /* reader buffer */ |
| union { |
| FIOBJ parent; |
| void (*dealloc)(void *); /* buffer deallocation function */ |
| size_t fpos; /* the file reader's position */ |
| } source; |
| size_t capa; /* total buffer capacity / slice offset */ |
| size_t len; /* length of valid data in buffer */ |
| size_t pos; /* position of reader */ |
| int fd; /* file descriptor (-1 if invalid). */ |
| } fiobj_data_s; |
| |
| #define obj2io(o) ((fiobj_data_s *)(o)) |
| |
| /* ***************************************************************************** |
| Object required VTable and functions |
| ***************************************************************************** */ |
| |
| #define REQUIRE_MEM(mem) \ |
| do { \ |
| if ((mem) == NULL) { \ |
| perror("FATAL ERROR: fiobj IO couldn't allocate memory"); \ |
| exit(errno); \ |
| } \ |
| } while (0) |
| |
| static void fiobj_data_copy_buffer(FIOBJ o) { |
| obj2io(o)->capa = (((obj2io(o)->len) >> 12) + 1) << 12; |
| void *tmp = fio_malloc(obj2io(o)->capa); |
| REQUIRE_MEM(tmp); |
| memcpy(tmp, obj2io(o)->buffer, obj2io(o)->len); |
| if (obj2io(o)->source.dealloc) |
| obj2io(o)->source.dealloc(obj2io(o)->buffer); |
| obj2io(o)->source.dealloc = fio_free; |
| obj2io(o)->buffer = tmp; |
| } |
| |
| static void fiobj_data_copy_parent(FIOBJ o) { |
| switch (obj2io(obj2io(o)->source.parent)->fd) { |
| case -1: |
| obj2io(o)->buffer = fio_malloc(obj2io(o)->len + 1); |
| memcpy(obj2io(o)->buffer, |
| obj2io(obj2io(o)->source.parent)->buffer + obj2io(o)->capa, |
| obj2io(o)->len); |
| obj2io(o)->buffer[obj2io(o)->len] = 0; |
| obj2io(o)->capa = obj2io(o)->len; |
| obj2io(o)->fd = -1; |
| fiobj_free(obj2io(o)->source.parent); |
| obj2io(o)->source.dealloc = fio_free; |
| return; |
| default: |
| obj2io(o)->fd = fio_tmpfile(); |
| if (obj2io(o)->fd < 0) { |
| perror("FATAL ERROR: (fiobj_data) can't create temporary file"); |
| exit(errno); |
| } |
| fio_str_info_s data; |
| size_t pos = 0; |
| do { |
| ssize_t written; |
| data = fiobj_data_pread(obj2io(o)->source.parent, pos + obj2io(o)->capa, |
| 4096); |
| if (data.len + pos > obj2io(o)->len) |
| data.len = obj2io(o)->len - pos; |
| retry_int: |
| written = write(obj2io(o)->fd, data.data, data.len); |
| if (written < 0) { |
| if (errno == EINTR) |
| goto retry_int; |
| perror("FATAL ERROR: (fiobj_data) can't write to temporary file"); |
| exit(errno); |
| } |
| pos += written; |
| } while (data.len == 4096); |
| fiobj_free(obj2io(o)->source.parent); |
| obj2io(o)->capa = 0; |
| obj2io(o)->len = pos; |
| obj2io(o)->source.fpos = obj2io(o)->pos; |
| obj2io(o)->pos = 0; |
| obj2io(o)->buffer = NULL; |
| break; |
| } |
| } |
| |
| static inline void fiobj_data_pre_write(FIOBJ o, uintptr_t length) { |
| switch (obj2io(o)->fd) { |
| case -1: |
| if (obj2io(o)->source.dealloc != fio_free) { |
| fiobj_data_copy_buffer(o); |
| } |
| break; |
| case -2: |
| fiobj_data_copy_parent(o); |
| break; |
| } |
| if (obj2io(o)->capa >= obj2io(o)->len + length) |
| return; |
| /* add rounded pages (4096) to capacity */ |
| obj2io(o)->capa = (((obj2io(o)->len + length) >> 12) + 1) << 12; |
| obj2io(o)->buffer = fio_realloc(obj2io(o)->buffer, obj2io(o)->capa); |
| REQUIRE_MEM(obj2io(o)->buffer); |
| } |
| |
| static inline int64_t fiobj_data_get_fd_size(const FIOBJ o) { |
| struct stat stat; |
| retry: |
| if (fstat(obj2io(o)->fd, &stat)) { |
| if (errno == EINTR) |
| goto retry; |
| return -1; |
| } |
| return stat.st_size; |
| } |
| |
| static FIOBJ fiobj_data_alloc(void *buffer, int fd) { |
| fiobj_data_s *io = fio_malloc(sizeof(*io)); |
| REQUIRE_MEM(io); |
| *io = (fiobj_data_s){ |
| .head = {.ref = 1, .type = FIOBJ_T_DATA}, |
| .buffer = buffer, |
| .fd = fd, |
| }; |
| return (FIOBJ)io; |
| } |
| |
| static void fiobj_data_dealloc(FIOBJ o, void (*task)(FIOBJ, void *), |
| void *arg) { |
| switch (obj2io(o)->fd) { |
| case -1: |
| if (obj2io(o)->source.dealloc && obj2io(o)->buffer) |
| obj2io(o)->source.dealloc(obj2io(o)->buffer); |
| break; |
| case -2: |
| fiobj_free(obj2io(o)->source.parent); |
| break; |
| default: |
| close(obj2io(o)->fd); |
| fio_free(obj2io(o)->buffer); |
| break; |
| } |
| fio_free((void *)o); |
| (void)task; |
| (void)arg; |
| } |
| |
| static intptr_t fiobj_data_i(const FIOBJ o) { |
| switch (obj2io(o)->fd) { |
| case -1: |
| case -2: |
| return obj2io(o)->len; |
| break; |
| default: |
| return fiobj_data_get_fd_size(o); |
| } |
| } |
| |
| static size_t fiobj_data_is_true(const FIOBJ o) { return fiobj_data_i(o) > 0; } |
| |
| static fio_str_info_s fio_io2str(const FIOBJ o) { |
| switch (obj2io(o)->fd) { |
| case -1: |
| return (fio_str_info_s){.data = (char *)obj2io(o)->buffer, |
| .len = obj2io(o)->len}; |
| break; |
| case -2: |
| return fiobj_data_pread(obj2io(o)->source.parent, obj2io(o)->capa, |
| obj2io(o)->len); |
| break; |
| } |
| int64_t i = fiobj_data_get_fd_size(o); |
| if (i <= 0) |
| return (fio_str_info_s){.data = (char *)obj2io(o)->buffer, |
| .len = obj2io(o)->len}; |
| obj2io(o)->len = 0; |
| obj2io(o)->pos = 0; |
| fiobj_data_pre_write((FIOBJ)o, i + 1); |
| if (pread(obj2io(o)->fd, obj2io(o)->buffer, i, 0) != i) |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| obj2io(o)->buffer[i] = 0; |
| return (fio_str_info_s){.data = (char *)obj2io(o)->buffer, .len = i}; |
| } |
| |
| static size_t fiobj_data_iseq(const FIOBJ self, const FIOBJ other) { |
| int64_t len; |
| return ((len = fiobj_data_i(self)) == fiobj_data_i(other) && |
| !memcmp(fio_io2str(self).data, fio_io2str(other).data, (size_t)len)); |
| } |
| |
| uintptr_t fiobject___noop_count(FIOBJ o); |
| double fiobject___noop_to_f(FIOBJ o); |
| |
| const fiobj_object_vtable_s FIOBJECT_VTABLE_DATA = { |
| .class_name = "IO", |
| .dealloc = fiobj_data_dealloc, |
| .to_i = fiobj_data_i, |
| .to_str = fio_io2str, |
| .is_eq = fiobj_data_iseq, |
| .is_true = fiobj_data_is_true, |
| .to_f = fiobject___noop_to_f, |
| .count = fiobject___noop_count, |
| }; |
| |
| /* ***************************************************************************** |
| Seeking for characters in a string |
| ***************************************************************************** */ |
| |
| #if FIO_MEMCHAR |
| |
| /** |
| * This seems to be faster on some systems, especially for smaller distances. |
| * |
| * On newer systems, `memchr` should be faster. |
| */ |
| static inline int swallow_ch(uint8_t **buffer, register uint8_t *const limit, |
| const uint8_t c) { |
| if (**buffer == c) |
| return 1; |
| |
| #if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__) |
| /* too short for this mess */ |
| if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7))) |
| goto finish; |
| |
| /* align memory */ |
| { |
| const uint8_t *alignment = |
| (uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8); |
| if (limit >= alignment) { |
| while (*buffer < alignment) { |
| if (**buffer == c) { |
| (*buffer)++; |
| return 1; |
| } |
| *buffer += 1; |
| } |
| } |
| } |
| const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7)); |
| #else |
| const uint8_t *limit64 = (uint8_t *)limit - 7; |
| #endif |
| uint64_t wanted1 = 0x0101010101010101ULL * c; |
| for (; *buffer < limit64; *buffer += 8) { |
| const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1); |
| const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu; |
| const uint64_t t1 = (eq1 & 0x8080808080808080llu); |
| if ((t0 & t1)) { |
| break; |
| } |
| } |
| #if !ALLOW_UNALIGNED_MEMORY_ACCESS || !defined(__x86_64__) |
| finish: |
| #endif |
| while (*buffer < limit) { |
| if (**buffer == c) { |
| (*buffer)++; |
| return 1; |
| } |
| (*buffer)++; |
| } |
| |
| return 0; |
| } |
| #else |
| |
| static inline int swallow_ch(uint8_t **buffer, uint8_t *const limit, |
| const uint8_t c) { |
| if (limit - *buffer == 0) |
| return 0; |
| void *tmp = memchr(*buffer, c, limit - (*buffer)); |
| if (tmp) { |
| *buffer = tmp; |
| (*buffer)++; |
| return 1; |
| } |
| *buffer = (uint8_t *)limit; |
| return 0; |
| } |
| |
| #endif |
| |
| /* ***************************************************************************** |
| Creating the IO object |
| ***************************************************************************** */ |
| |
| /** Creates a new local in-memory IO object */ |
| FIOBJ fiobj_data_newstr(void) { |
| FIOBJ o = fiobj_data_alloc(fio_malloc(4096), -1); |
| REQUIRE_MEM(obj2io(o)->buffer); |
| obj2io(o)->capa = 4096; |
| obj2io(o)->source.dealloc = fio_free; |
| return o; |
| } |
| |
| /** |
| * Creates a IO object from an existing buffer. The buffer will be deallocated |
| * using the provided `dealloc` function pointer. Use a NULL `dealloc` function |
| * pointer if the buffer is static and shouldn't be freed. |
| */ |
| FIOBJ fiobj_data_newstr2(void *buffer, uintptr_t length, |
| void (*dealloc)(void *)) { |
| FIOBJ o = fiobj_data_alloc(buffer, -1); |
| obj2io(o)->capa = length; |
| obj2io(o)->len = length; |
| obj2io(o)->source.dealloc = dealloc; |
| return o; |
| } |
| |
| /** Creates a new local file IO object */ |
| FIOBJ fiobj_data_newfd(int fd) { |
| FIOBJ o = fiobj_data_alloc(fio_malloc(4096), fd); |
| REQUIRE_MEM(obj2io(o)->buffer); |
| obj2io(o)->source.fpos = 0; |
| return o; |
| } |
| |
| /** Creates a new local tempfile IO object */ |
| FIOBJ fiobj_data_newtmpfile(void) { |
| // create a temporary file to contain the data. |
| int fd = fio_tmpfile(); |
| if (fd == -1) |
| return 0; |
| return fiobj_data_newfd(fd); |
| } |
| |
| /** Creates a slice from an existing Data object. */ |
| FIOBJ fiobj_data_slice(FIOBJ parent, intptr_t offset, uintptr_t length) { |
| /* cut from the end */ |
| if (offset < 0) { |
| size_t parent_len = fiobj_data_len(parent); |
| offset = parent_len + 1 + offset; |
| } |
| if (offset < 0) |
| offset = 0; |
| while (obj2io(parent)->fd == -2) { |
| /* don't slice a slice... climb the parent chain. */ |
| offset += obj2io(parent)->capa; |
| parent = obj2io(parent)->source.parent; |
| } |
| size_t parent_len = fiobj_data_len(parent); |
| if (parent_len <= (size_t)offset) { |
| length = 0; |
| offset = parent_len; |
| } else if (parent_len < offset + length) { |
| length = parent_len - offset; |
| } |
| /* make the object */ |
| FIOBJ o = fiobj_data_alloc(NULL, -2); |
| obj2io(o)->capa = offset; |
| obj2io(o)->len = length; |
| obj2io(o)->source.parent = fiobj_dup(parent); |
| return o; |
| } |
| |
| /* ***************************************************************************** |
| Saving the IO object |
| ***************************************************************************** */ |
| |
| static int fiobj_data_save_str(FIOBJ o, const char *filename) { |
| int target = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0777); |
| if (target == -1) |
| return -1; |
| errno = 0; |
| size_t total = 0; |
| do { |
| ssize_t act = |
| write(target, obj2io(o)->buffer + total, obj2io(o)->len - total); |
| if (act < 0) |
| goto error; |
| total += act; |
| } while (total < obj2io(o)->len); |
| close(target); |
| return 0; |
| error: |
| close(target); |
| unlink(filename); |
| return -1; |
| } |
| |
| static int fiobj_data_save_file(FIOBJ o, const char *filename) { |
| int target = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0777); |
| if (target == -1) |
| return -1; |
| errno = 0; |
| char buf[1024]; |
| size_t total = 0; |
| do { |
| ssize_t act = pread(obj2io(o)->fd, buf, 1024, total); |
| if (act == 0) |
| break; |
| if (act < 0) |
| goto error; |
| ssize_t act2 = write(target, buf, act); |
| if (act2 < act) |
| goto error; |
| total += act2; |
| } while (1); |
| close(target); |
| return 0; |
| error: |
| close(target); |
| unlink(filename); |
| return -1; |
| } |
| |
| static int fiobj_data_save_slice(FIOBJ o, const char *filename) { |
| int target = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0777); |
| if (target == -1) |
| return -1; |
| errno = 0; |
| fio_str_info_s tmp; |
| size_t total = 0; |
| do { |
| tmp = fiobj_data_pread(obj2io(o)->source.parent, obj2io(o)->capa + total, |
| 4096); |
| if (tmp.len == 0) |
| break; |
| if (total + tmp.len > obj2io(o)->len) |
| tmp.len = obj2io(o)->len - total; |
| if (tmp.len) { |
| ssize_t act2 = write(target, tmp.data, tmp.len); |
| if (act2 < 0 || (size_t)act2 < tmp.len) |
| goto error; |
| total += act2; |
| } |
| } while (tmp.len == 4096); |
| close(target); |
| return 0; |
| error: |
| close(target); |
| unlink(filename); |
| return -1; |
| } |
| |
| /** Creates a new local file IO object */ |
| int fiobj_data_save(FIOBJ o, const char *filename) { |
| switch (obj2io(o)->fd) { |
| case -1: |
| return fiobj_data_save_str(o, filename); |
| break; |
| case -2: |
| return fiobj_data_save_slice(o, filename); |
| break; |
| default: |
| return fiobj_data_save_file(o, filename); |
| } |
| } |
| |
| /* ***************************************************************************** |
| Reading API |
| ***************************************************************************** */ |
| |
| /** Reads up to `length` bytes */ |
| static fio_str_info_s fiobj_data_read_str(FIOBJ io, intptr_t length) { |
| if (obj2io(io)->pos == obj2io(io)->len) { |
| /* EOF */ |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| |
| if (length <= 0) { |
| /* read to EOF - length */ |
| length = (obj2io(io)->len - obj2io(io)->pos) + length; |
| } |
| |
| if (length <= 0) { |
| /* We are at EOF - length or beyond */ |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| |
| /* reading length bytes */ |
| register size_t pos = obj2io(io)->pos; |
| obj2io(io)->pos = pos + length; |
| if (obj2io(io)->pos > obj2io(io)->len) |
| obj2io(io)->pos = obj2io(io)->len; |
| return (fio_str_info_s){ |
| .data = (char *)(obj2io(io)->buffer + pos), |
| .len = (obj2io(io)->pos - pos), |
| }; |
| } |
| |
| /** Reads up to `length` bytes */ |
| static fio_str_info_s fiobj_data_read_slice(FIOBJ io, intptr_t length) { |
| if (obj2io(io)->pos == obj2io(io)->len) { |
| /* EOF */ |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| if (length <= 0) { |
| /* read to EOF - length */ |
| length = (obj2io(io)->len - obj2io(io)->pos) + length; |
| } |
| |
| if (length <= 0) { |
| /* We are at EOF - length or beyond */ |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| register size_t pos = obj2io(io)->pos; |
| obj2io(io)->pos = pos + length; |
| if (obj2io(io)->pos > obj2io(io)->len) |
| obj2io(io)->pos = obj2io(io)->len; |
| return fiobj_data_pread(obj2io(io)->source.parent, pos + obj2io(io)->capa, |
| (obj2io(io)->pos - pos)); |
| } |
| |
| /** Reads up to `length` bytes */ |
| static fio_str_info_s fiobj_data_read_file(FIOBJ io, intptr_t length) { |
| uintptr_t fsize = fiobj_data_get_fd_size(io); |
| |
| if (length <= 0) { |
| /* read to EOF - length */ |
| length = (fsize - obj2io(io)->source.fpos) + length; |
| } |
| |
| if (length <= 0) { |
| /* We are at EOF - length or beyond */ |
| errno = 0; |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| |
| /* reading length bytes */ |
| if (length + obj2io(io)->pos <= obj2io(io)->len) { |
| /* the data already exists in the buffer */ |
| // fprintf(stderr, "in_buffer...\n"); |
| fio_str_info_s data = {.data = |
| (char *)(obj2io(io)->buffer + obj2io(io)->pos), |
| .len = (uintptr_t)length}; |
| obj2io(io)->pos += length; |
| obj2io(io)->source.fpos += length; |
| return data; |
| } else { |
| /* read the data into the buffer - internal counting gets invalidated */ |
| // fprintf(stderr, "populate buffer...\n"); |
| obj2io(io)->len = 0; |
| obj2io(io)->pos = 0; |
| fiobj_data_pre_write(io, length); |
| ssize_t l; |
| retry_int: |
| l = pread(obj2io(io)->fd, obj2io(io)->buffer, length, |
| obj2io(io)->source.fpos); |
| if (l == -1 && errno == EINTR) |
| goto retry_int; |
| if (l == -1 || l == 0) |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| obj2io(io)->source.fpos += l; |
| return (fio_str_info_s){.data = (char *)obj2io(io)->buffer, .len = l}; |
| } |
| } |
| |
| /** |
| * Reads up to `length` bytes and returns a temporary(!) C string object. |
| * |
| * The C string object will be invalidate the next time a function call to the |
| * IO object is made. |
| */ |
| fio_str_info_s fiobj_data_read(FIOBJ io, intptr_t length) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) { |
| errno = EFAULT; |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| errno = 0; |
| switch (obj2io(io)->fd) { |
| case -1: |
| return fiobj_data_read_str(io, length); |
| break; |
| case -2: |
| return fiobj_data_read_slice(io, length); |
| break; |
| default: |
| return fiobj_data_read_file(io, length); |
| } |
| } |
| |
| /* ***************************************************************************** |
| Tokenize (read2ch) |
| ***************************************************************************** */ |
| |
| static fio_str_info_s fiobj_data_read2ch_str(FIOBJ io, uint8_t token) { |
| if (obj2io(io)->pos == obj2io(io)->len) /* EOF */ |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| |
| uint8_t *pos = obj2io(io)->buffer + obj2io(io)->pos; |
| uint8_t *lim = obj2io(io)->buffer + obj2io(io)->len; |
| swallow_ch(&pos, lim, token); |
| fio_str_info_s ret = (fio_str_info_s){ |
| .data = (char *)obj2io(io)->buffer + obj2io(io)->pos, |
| .len = (uintptr_t)(pos - obj2io(io)->buffer) - obj2io(io)->pos, |
| }; |
| obj2io(io)->pos = (uintptr_t)(pos - obj2io(io)->buffer); |
| return ret; |
| } |
| |
| static fio_str_info_s fiobj_data_read2ch_slice(FIOBJ io, uint8_t token) { |
| if (obj2io(io)->pos == obj2io(io)->len) /* EOF */ |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| size_t old_pos = obj2io(obj2io(io)->source.parent)->pos; |
| obj2io(obj2io(io)->source.parent)->pos = obj2io(io)->capa + obj2io(io)->pos; |
| fio_str_info_s tmp = fiobj_data_read2ch(obj2io(io)->source.parent, token); |
| obj2io(obj2io(io)->source.parent)->pos = old_pos; |
| if (tmp.len + obj2io(io)->pos > obj2io(io)->len) { |
| /* EOF */ |
| tmp.len = obj2io(io)->len - obj2io(io)->pos; |
| obj2io(io)->pos = obj2io(io)->len; |
| return tmp; |
| } |
| return tmp; |
| } |
| |
| static fio_str_info_s fiobj_data_read2ch_file(FIOBJ io, uint8_t token) { |
| uint8_t *pos = obj2io(io)->buffer + obj2io(io)->pos; |
| uint8_t *lim = obj2io(io)->buffer + obj2io(io)->len; |
| if (pos != lim && swallow_ch(&pos, lim, token)) { |
| /* newline found in existing buffer */ |
| const uintptr_t delta = |
| (uintptr_t)(pos - (obj2io(io)->buffer + obj2io(io)->pos)); |
| obj2io(io)->pos += delta; |
| obj2io(io)->source.fpos += delta; |
| return (fio_str_info_s){ |
| .data = |
| (char *)(delta ? ((obj2io(io)->buffer + obj2io(io)->pos) - delta) |
| : NULL), |
| .len = delta, |
| }; |
| } |
| |
| obj2io(io)->pos = 0; |
| obj2io(io)->len = 0; |
| |
| while (1) { |
| ssize_t tmp; |
| fiobj_data_pre_write(io, 4096); /* read a page at a time */ |
| retry_int: |
| tmp = pread(obj2io(io)->fd, obj2io(io)->buffer + obj2io(io)->len, 4096, |
| obj2io(io)->source.fpos + obj2io(io)->len); |
| if (tmp < 0 && errno == EINTR) |
| goto retry_int; |
| if (tmp < 0 || (tmp == 0 && obj2io(io)->len == 0)) { |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| if (tmp == 0) { |
| obj2io(io)->source.fpos += obj2io(io)->len; |
| return (fio_str_info_s){.data = (char *)obj2io(io)->buffer, |
| .len = obj2io(io)->len}; |
| } |
| obj2io(io)->len += tmp; |
| pos = obj2io(io)->buffer; |
| lim = obj2io(io)->buffer + obj2io(io)->len; |
| if (swallow_ch(&pos, lim, token)) { |
| const uintptr_t delta = |
| (uintptr_t)(pos - (obj2io(io)->buffer + obj2io(io)->pos)); |
| obj2io(io)->pos = delta; |
| obj2io(io)->source.fpos += delta; |
| return (fio_str_info_s){ |
| .data = (char *)obj2io(io)->buffer, |
| .len = delta, |
| }; |
| } |
| } |
| } |
| |
| /** |
| * Reads until the `token` byte is encountered or until the end of the stream. |
| * |
| * Returns a temporary(!) C string including the end of line marker. |
| * |
| * Careful when using this call on large file streams, as the whole file |
| * stream might be loaded into the memory. |
| * |
| * The C string object will be invalidate the next time a function call to the |
| * IO object is made. |
| */ |
| fio_str_info_s fiobj_data_read2ch(FIOBJ io, uint8_t token) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) { |
| errno = EFAULT; |
| return (fio_str_info_s){.data = NULL, .len = 0}; |
| } |
| switch (obj2io(io)->fd) { |
| case -1: |
| return fiobj_data_read2ch_str(io, token); |
| break; |
| case -2: |
| return fiobj_data_read2ch_slice(io, token); |
| break; |
| default: |
| return fiobj_data_read2ch_file(io, token); |
| } |
| } |
| |
| /* ***************************************************************************** |
| Position / Seeking |
| ***************************************************************************** */ |
| |
| /** |
| * Returns the current reading position. |
| */ |
| intptr_t fiobj_data_pos(FIOBJ io) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) |
| return -1; |
| switch (obj2io(io)->fd) { |
| case -1: /* fallthrough */ |
| case -2: |
| return obj2io(io)->pos; |
| break; |
| default: |
| return obj2io(io)->source.fpos; |
| } |
| } |
| |
| /** |
| * Returns the length of the stream. |
| */ |
| intptr_t fiobj_data_len(FIOBJ io) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) |
| return -1; |
| return fiobj_data_i(io); |
| } |
| |
| /** |
| * Moves the reading position to the requested position. |
| */ |
| void fiobj_data_seek(FIOBJ io, intptr_t position) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) |
| return; |
| switch (obj2io(io)->fd) { |
| case -1: /* fallthrough */ |
| case -2: |
| /* String / Slice code */ |
| if (position == 0) { |
| obj2io(io)->pos = 0; |
| return; |
| } |
| if (position > 0) { |
| if ((uintptr_t)position > obj2io(io)->len) |
| position = obj2io(io)->len; |
| obj2io(io)->pos = position; |
| return; |
| } |
| position = (0 - position); |
| if ((uintptr_t)position > obj2io(io)->len) |
| position = 0; |
| else |
| position = obj2io(io)->len - position; |
| obj2io(io)->pos = position; |
| return; |
| break; |
| default: |
| /* File code */ |
| obj2io(io)->pos = 0; |
| obj2io(io)->len = 0; |
| |
| if (position == 0) { |
| obj2io(io)->source.fpos = 0; |
| return; |
| } |
| int64_t len = fiobj_data_get_fd_size(io); |
| if (len < 0) |
| len = 0; |
| if (position > 0) { |
| if (position > len) |
| position = len; |
| |
| obj2io(io)->source.fpos = position; |
| return; |
| } |
| position = (0 - position); |
| if (position > len) |
| position = 0; |
| else |
| position = len - position; |
| obj2io(io)->source.fpos = position; |
| return; |
| } |
| } |
| |
| /* ***************************************************************************** |
| `fiobj_data_pread` |
| ***************************************************************************** */ |
| // switch(obj2io(o)->fd) { |
| // case -1: |
| // break; |
| // case -2: |
| // break; |
| // default: |
| // } |
| |
| static fio_str_info_s fiobj_data_pread_str(FIOBJ io, intptr_t start_at, |
| uintptr_t length) { |
| if (start_at < 0) |
| start_at = obj2io(io)->len + start_at; |
| if (start_at < 0) |
| start_at = 0; |
| if ((size_t)start_at > obj2io(io)->len) |
| start_at = obj2io(io)->len; |
| if (length + start_at > obj2io(io)->len) |
| length = obj2io(io)->len - start_at; |
| if (length == 0) |
| return (fio_str_info_s){ |
| .data = NULL, |
| .len = 0, |
| }; |
| return (fio_str_info_s){ |
| .data = (char *)obj2io(io)->buffer + start_at, |
| .len = length, |
| }; |
| } |
| static fio_str_info_s fiobj_data_pread_slice(FIOBJ io, intptr_t start_at, |
| uintptr_t length) { |
| if (start_at < 0) |
| start_at = obj2io(io)->len + start_at; |
| if (start_at < 0) |
| start_at = 0; |
| if ((size_t)start_at > obj2io(io)->len) |
| start_at = obj2io(io)->len; |
| if (length + start_at > obj2io(io)->len) |
| length = obj2io(io)->len - start_at; |
| if (length == 0) |
| return (fio_str_info_s){ |
| .data = NULL, |
| .len = 0, |
| }; |
| return fiobj_data_pread(obj2io(io)->source.parent, start_at, length); |
| } |
| |
| static fio_str_info_s fiobj_data_pread_file(FIOBJ io, intptr_t start_at, |
| uintptr_t length) { |
| const int64_t size = fiobj_data_get_fd_size(io); |
| if (start_at < 0) |
| start_at = size + start_at; |
| if (start_at < 0) |
| start_at = 0; |
| if (length + start_at > (uint64_t)size) |
| length = size - start_at; |
| if (length == 0) { |
| /* free memory once there's no more data to read */ |
| obj2io(io)->capa = 0; |
| fio_free(obj2io(io)->buffer); |
| obj2io(io)->buffer = NULL; |
| return (fio_str_info_s){ |
| .data = NULL, |
| .len = 0, |
| }; |
| } |
| obj2io(io)->len = 0; |
| obj2io(io)->pos = 0; |
| fiobj_data_pre_write(io, length + 1); |
| ssize_t tmp = pread(obj2io(io)->fd, obj2io(io)->buffer, length, start_at); |
| if (tmp <= 0) { |
| return (fio_str_info_s){ |
| .data = NULL, |
| .len = 0, |
| }; |
| } |
| obj2io(io)->buffer[tmp] = 0; |
| return (fio_str_info_s){ |
| .data = (char *)obj2io(io)->buffer, |
| .len = tmp, |
| }; |
| } |
| /** |
| * Reads up to `length` bytes starting at `start_at` position and returns a |
| * temporary(!) C string object. The reading position is ignored and |
| * unchanged. |
| * |
| * The C string object will be invalidate the next time a function call to the |
| * IO object is made. |
| */ |
| fio_str_info_s fiobj_data_pread(FIOBJ io, intptr_t start_at, uintptr_t length) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA)) { |
| errno = EFAULT; |
| return (fio_str_info_s){ |
| .data = NULL, |
| .len = 0, |
| }; |
| } |
| |
| errno = 0; |
| switch (obj2io(io)->fd) { |
| case -1: |
| return fiobj_data_pread_str(io, start_at, length); |
| break; |
| case -2: |
| return fiobj_data_pread_slice(io, start_at, length); |
| break; |
| default: |
| return fiobj_data_pread_file(io, start_at, length); |
| } |
| } |
| |
| /* ***************************************************************************** |
| Writing API |
| ***************************************************************************** */ |
| |
| /** |
| * Makes sure the IO object isn't attached to a static or external string. |
| * |
| * If the IO object is attached to a static or external string, the data will be |
| * copied to a new memory block. |
| */ |
| void fiobj_data_assert_dynamic(FIOBJ io) { |
| if (!io) { |
| errno = ENFILE; |
| return; |
| } |
| assert(FIOBJ_TYPE(io) == FIOBJ_T_DATA); |
| fiobj_data_pre_write(io, 0); |
| return; |
| } |
| |
| /** |
| * Writes `length` bytes at the end of the IO stream, ignoring the reading |
| * position. |
| * |
| * Behaves and returns the same value as the system call `write`. |
| */ |
| intptr_t fiobj_data_write(FIOBJ io, void *buffer, uintptr_t length) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA) || (!buffer && length)) { |
| errno = EFAULT; |
| return -1; |
| } |
| errno = 0; |
| /* Unslice slices */ |
| if (obj2io(io)->fd == -2) |
| fiobj_data_assert_dynamic(io); |
| |
| if (obj2io(io)->fd == -1) { |
| /* String Code */ |
| fiobj_data_pre_write(io, length + 1); |
| memcpy(obj2io(io)->buffer + obj2io(io)->len, buffer, length); |
| obj2io(io)->len = obj2io(io)->len + length; |
| obj2io(io)->buffer[obj2io(io)->len] = 0; |
| return length; |
| } |
| |
| /* File Code */ |
| return pwrite(obj2io(io)->fd, buffer, length, fiobj_data_get_fd_size(io)); |
| } |
| |
| /** |
| * Writes `length` bytes at the end of the IO stream, ignoring the reading |
| * position, adding an EOL marker ("\r\n") to the end of the stream. |
| * |
| * Behaves and returns the same value as the system call `write`. |
| */ |
| intptr_t fiobj_data_puts(FIOBJ io, void *buffer, uintptr_t length) { |
| if (!io || !FIOBJ_TYPE_IS(io, FIOBJ_T_DATA) || (!buffer && length)) { |
| errno = EFAULT; |
| return -1; |
| } |
| /* Unslice slices */ |
| if (obj2io(io)->fd == -2) |
| fiobj_data_assert_dynamic(io); |
| |
| if (obj2io(io)->fd == -1) { |
| /* String Code */ |
| fiobj_data_pre_write(io, length + 2); |
| if (length) { |
| memcpy(obj2io(io)->buffer + obj2io(io)->len, buffer, length); |
| } |
| obj2io(io)->len = obj2io(io)->len + length + 2; |
| obj2io(io)->buffer[obj2io(io)->len - 2] = '\r'; |
| obj2io(io)->buffer[obj2io(io)->len - 1] = '\n'; |
| return length + 2; |
| } |
| /* File Code */ |
| uintptr_t end = fiobj_data_get_fd_size(io); |
| ssize_t t1 = 0, t2 = 0; |
| |
| if (length) { |
| t1 = pwrite(obj2io(io)->fd, buffer, length, end); |
| if (t1 < 0) |
| return t1; |
| end += t1; |
| } |
| t2 = pwrite(obj2io(io)->fd, buffer, length, end); |
| if (t2 < 0) |
| return t1; |
| return t1 + t2; |
| } |
| |
| #if DEBUG |
| |
| void fiobj_data_test(void) { |
| char *filename = NULL; |
| FIOBJ text; |
| fio_str_info_s s1, s2; |
| fprintf(stderr, "=== testing fiobj_data\n"); |
| if (filename) { |
| text = fiobj_str_buf(0); |
| fiobj_str_readfile(text, filename, 0, 0); |
| } else |
| text = fiobj_str_new("Line 1\r\nLine 2\nLine 3 unended", 29); |
| FIOBJ strio = fiobj_data_newstr(); |
| fprintf(stderr, "* `newstr` passed.\n"); |
| FIOBJ fdio = fiobj_data_newtmpfile(); |
| fprintf(stderr, "* `newtmpfile` passed.\n"); |
| fiobj_data_write(fdio, fiobj_obj2cstr(text).data, fiobj_obj2cstr(text).len); |
| fiobj_data_write(strio, fiobj_obj2cstr(text).data, fiobj_obj2cstr(text).len); |
| FIOBJ sliceio = fiobj_data_slice(fdio, 8, 7); |
| |
| s1 = fiobj_data_read(sliceio, 4096); |
| if (s1.len != 7 || memcmp(s1.data, fiobj_data_pread(strio, 8, 7).data, 7)) { |
| fprintf(stderr, "* `fiobj_data_slice` operation FAILED!\n"); |
| fprintf(stderr, "* `fiobj_data_slice` s1.len = %zu s1.data = %s!\n", s1.len, |
| s1.data); |
| exit(-1); |
| } |
| s1 = fiobj_data_read(sliceio, 4096); |
| if (s1.len || s1.data) { |
| fprintf(stderr, "* `fiobj_data_read` operation overflow - FAILED!\n"); |
| exit(-1); |
| } |
| |
| if (fiobj_obj2cstr(strio).len != fiobj_obj2cstr(text).len || |
| fiobj_obj2cstr(fdio).len != fiobj_obj2cstr(text).len) { |
| fprintf(stderr, "* `write` operation FAILED!\n"); |
| exit(-1); |
| } |
| s1 = fiobj_data_gets(strio); |
| s2 = fiobj_data_gets(fdio); |
| fprintf(stderr, "str(%d): %.*s", (int)s1.len, (int)s1.len, s1.data); |
| fprintf(stderr, "fd(%d): %.*s", (int)s2.len, (int)s2.len, s2.data); |
| if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { |
| fprintf(stderr, |
| "* `gets` operation FAILED! (non equal data):\n" |
| "%d bytes vs. %d bytes\n" |
| "%.*s vs %.*s\n", |
| (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, |
| s2.data); |
| exit(-1); |
| } else |
| fprintf(stderr, "* `gets` operation passed (equal data).\n"); |
| |
| if (!filename) { |
| intptr_t last_pos = fiobj_data_pos(fdio); |
| fiobj_data_seek(sliceio, 0); |
| s1 = fiobj_data_gets(sliceio); |
| s2 = fiobj_data_gets(fdio); |
| fiobj_data_seek(fdio, last_pos); |
| if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { |
| fprintf(stderr, |
| "* slice `gets` operation FAILED! (non equal data):\n" |
| "%d bytes vs. %d bytes\n" |
| "%.*s vs %.*s\n", |
| (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, |
| s2.data); |
| exit(-1); |
| } |
| } |
| |
| s1 = fiobj_data_read(strio, 3); |
| s2 = fiobj_data_read(fdio, 3); |
| if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { |
| fprintf(stderr, |
| "* `read` operation FAILED! (non equal data):\n" |
| "%d bytes vs. %d bytes\n" |
| "%.*s vs %.*s\n", |
| (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, |
| s2.data); |
| exit(-1); |
| } else |
| fprintf(stderr, "* `read` operation passed (equal data).\n"); |
| if (!filename) { |
| s1 = fiobj_data_gets(strio); |
| s2 = fiobj_data_gets(fdio); |
| s1 = fiobj_data_gets(strio); |
| s2 = fiobj_data_gets(fdio); |
| if (s1.len != s2.len || memcmp(s1.data, s2.data, s1.len)) { |
| fprintf(stderr, |
| "* EOF `gets` operation FAILED! (non equal data):\n" |
| "%d bytes vs. %d bytes\n" |
| "%.*s vs %.*s\n", |
| (int)s1.len, (int)s2.len, (int)s1.len, s1.data, (int)s2.len, |
| s2.data); |
| exit(-1); |
| } else |
| fprintf(stderr, "* EOF `gets` operation passed (equal data).\n"); |
| s1 = fiobj_data_gets(strio); |
| s2 = fiobj_data_gets(fdio); |
| if (s1.data || s2.data) { |
| fprintf(stderr, |
| "* EOF `gets` was not EOF?!\n" |
| "str(%d): %.*s\n" |
| "fd(%d): %.*s\n", |
| (int)s1.len, (int)s1.len, s1.data, (int)s2.len, (int)s2.len, |
| s2.data); |
| exit(-1); |
| } |
| } |
| fiobj_free(text); |
| fiobj_free(strio); |
| fiobj_free(fdio); |
| |
| { |
| fiobj_data_seek(sliceio, 0); |
| s1 = fiobj_data_read(sliceio, 4096); |
| if (s1.len != (size_t)fiobj_data_len(sliceio) || !s1.data) { |
| fprintf(stderr, "* `fiobj_data_slice` data lost? FAILED!\n"); |
| fprintf(stderr, |
| "* `fiobj_data_slice` s1.len = %zu (out of %zu) s1.data = %s!\n", |
| s1.len, (size_t)fiobj_data_len(sliceio), s1.data); |
| exit(-1); |
| } |
| size_t old_len = fiobj_data_len(sliceio); |
| fiobj_data_write(sliceio, "hi", 2); |
| fiobj_data_seek(sliceio, 0); |
| s1 = fiobj_data_read(sliceio, 4096); |
| if (s1.len != old_len + 2 || !s1.data || s1.data[s1.len - 1] != 'i') { |
| fprintf(stderr, "* `fiobj_data_write` for Slice data lost? FAILED!\n"); |
| fprintf(stderr, |
| "* `fiobj_data_slice` s1.len = %zu (out of %zu) s1.data = %s!\n", |
| s1.len, (size_t)fiobj_data_len(sliceio), s1.data); |
| exit(-1); |
| } |
| } |
| fiobj_free(sliceio); |
| |
| fprintf(stderr, "* passed.\n"); |
| } |
| |
| #endif |
| |
| #else /* require POSIX */ |
| #include <fiobj_data.h> |
| |
| /** Creates a new local in-memory IO object */ |
| FIOBJ fiobj_data_newstr(void) { return FIOBJ_INVALID; } |
| |
| /** |
| * Creates a IO object from an existing buffer. The buffer will be deallocated |
| * using the provided `dealloc` function pointer. Use a NULL `dealloc` function |
| * pointer if the buffer is static and shouldn't be freed. |
| */ |
| FIOBJ fiobj_data_newstr2(void *buffer, uintptr_t length, |
| void (*dealloc)(void *)) { |
| return FIOBJ_INVALID; |
| } |
| |
| /** Creates a new local tempfile IO object */ |
| FIOBJ fiobj_data_newtmpfile(void) { return FIOBJ_INVALID; } |
| |
| /** Creates a new local file IO object */ |
| FIOBJ fiobj_data_newfd(int fd) { return FIOBJ_INVALID; } |
| |
| int fiobj_data_save(FIOBJ io, const char *filename) { return -1; } |
| |
| #endif /* require POSIX */ |