263 lines
5.2 KiB
C
263 lines
5.2 KiB
C
#include "fileutil.h"
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
int __fp_open(FILE *fp, int fd, bool use_buffering)
|
|
{
|
|
/* We initialize fdpos to 0 even in append mode (7.19.3§1) */
|
|
fp->fd = fd;
|
|
fp->fdpos = 0;
|
|
|
|
/* We assume all files in the filesystem are non-interactive in order
|
|
to conform to (7.19.5.3§7)
|
|
TODO: Vhex might want something more elaborate here */
|
|
if(use_buffering && setvbuf(fp, NULL, _IOFBF, BUFSIZ))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __fp_close(FILE *fp, bool free_fp)
|
|
{
|
|
if(!fp)
|
|
return;
|
|
|
|
if(fp->fd >= 0) {
|
|
fflush(fp);
|
|
close(fp->fd);
|
|
}
|
|
if(fp->bufowned)
|
|
free(fp->buf);
|
|
if(free_fp)
|
|
free(fp);
|
|
}
|
|
|
|
void __fp_remove_buffer(FILE *fp)
|
|
{
|
|
if(fp->bufowned)
|
|
free(fp->buf);
|
|
|
|
fp->buf = NULL;
|
|
fp->bufowned = false;
|
|
fp->bufsize = 0;
|
|
}
|
|
|
|
bool __fp_set_buffer(FILE *fp, void *buf, size_t size)
|
|
{
|
|
bool owned = false;
|
|
|
|
if(!buf) {
|
|
owned = true;
|
|
buf = malloc(size);
|
|
if(!buf)
|
|
return false;
|
|
}
|
|
|
|
fp->buf = buf;
|
|
fp->bufowned = owned;
|
|
fp->bufsize = size;
|
|
return true;
|
|
}
|
|
|
|
void __fp_buffer_mode_read(FILE *fp)
|
|
{
|
|
if(__fp_hasbuf_write(fp)) {
|
|
fflush(fp);
|
|
}
|
|
if(fp->buf)
|
|
fp->bufdir = __FILE_BUF_READ;
|
|
}
|
|
|
|
void __fp_buffer_mode_write(FILE *fp)
|
|
{
|
|
if(__fp_hasbuf_read(fp))
|
|
fflush(fp);
|
|
if(fp->buf)
|
|
fp->bufdir = __FILE_BUF_WRITE;
|
|
}
|
|
|
|
ssize_t __fp_fread2(FILE *fp, void *data, size_t request_size, int stop_char)
|
|
{
|
|
if(!fp->readable) {
|
|
fp->error = 1;
|
|
return -1;
|
|
}
|
|
|
|
/* If the stream if unbuffered, we might have no buffer for the reads.
|
|
If it's buffered, we always have one. It's also possible that fp is
|
|
unbuffered (_IONBF) but has a buffer temporarily because ungetc()
|
|
has been used, in which case we have to transition from buffered
|
|
reads into direct reads midway. We use __fp_buffered_read() to
|
|
handle this. */
|
|
|
|
size_t read_size = 0;
|
|
__fp_buffer_mode_read(fp);
|
|
|
|
while(read_size < request_size) {
|
|
int remaining = request_size - read_size;
|
|
int chunk = __fp_buffered_read(fp, data+read_size, remaining,
|
|
stop_char);
|
|
|
|
/* Stream is not/no longer buffered, finish unbuffered */
|
|
if(chunk < 0 && stop_char >= 0) {
|
|
unsigned char c;
|
|
|
|
/* When there is a stop char, we can only read one
|
|
byte at a time, which is reaaaally slow */
|
|
for(int i = 0; i < remaining; i++) {
|
|
ssize_t rc = __fp_read(fp, &c, 1);
|
|
if(rc != 1)
|
|
break;
|
|
((char *)data)[read_size++] = c;
|
|
if(c == stop_char)
|
|
break;
|
|
}
|
|
return read_size;
|
|
}
|
|
else if(chunk < 0) {
|
|
ssize_t rc = __fp_read(fp, data+read_size, remaining);
|
|
return read_size + (rc == EOF ? 0 : rc);
|
|
}
|
|
|
|
read_size += chunk;
|
|
if(read_size >= request_size)
|
|
break;
|
|
|
|
/* If a stop char has been read, stop */
|
|
if(stop_char >= 0 && read_size > 0 &&
|
|
((char *)data)[read_size-1] == stop_char)
|
|
break;
|
|
|
|
/* Get more data from the file descriptor into the buffer */
|
|
if(fp->buf) {
|
|
ssize_t rc = __fp_read(fp, fp->buf, fp->bufsize);
|
|
if(rc <= 0) /* EOF or error */
|
|
break;
|
|
fp->bufread = rc;
|
|
}
|
|
}
|
|
|
|
return read_size;
|
|
}
|
|
|
|
ssize_t __fp_buffered_read(FILE *fp, void *data, size_t request_size,
|
|
int stop_char)
|
|
{
|
|
if(!fp->buf || __fp_hasbuf_write(fp))
|
|
return -1;
|
|
|
|
int read_size = request_size;
|
|
if(read_size > (int)(fp->bufread - fp->bufpos))
|
|
read_size = fp->bufread - fp->bufpos;
|
|
if(read_size <= 0)
|
|
return 0;
|
|
|
|
if(stop_char >= 0) {
|
|
char *end = memchr(fp->buf+fp->bufpos, stop_char, read_size);
|
|
if(end != NULL)
|
|
read_size = end - (fp->buf + fp->bufpos) + 1;
|
|
}
|
|
|
|
memcpy(data, fp->buf + fp->bufpos, read_size);
|
|
fp->bufpos += read_size;
|
|
fp->bufungetc = fp->bufungetc - read_size;
|
|
if(fp->bufungetc < 0)
|
|
fp->bufungetc = 0;
|
|
|
|
/* Rewind the buffer if we read it fully */
|
|
if(fp->bufpos >= fp->bufread) {
|
|
fp->bufread = 0;
|
|
fp->bufpos = 0;
|
|
|
|
/* Clear temporary ungetc() buffers of _IONBF streams */
|
|
if(fp->bufmode == _IONBF)
|
|
__fp_remove_buffer(fp);
|
|
}
|
|
|
|
return read_size;
|
|
}
|
|
|
|
ssize_t __fp_read(FILE *fp, void *data, size_t size)
|
|
{
|
|
size_t read_ = 0;
|
|
|
|
while(read_ < size) {
|
|
ssize_t rc = read(fp->fd, data + read_, size - read_);
|
|
|
|
if(rc < 0) {
|
|
fp->error = 1;
|
|
return EOF;
|
|
}
|
|
if(rc == 0) {
|
|
fp->eof = 1;
|
|
break;
|
|
}
|
|
|
|
fp->fdpos += rc;
|
|
read_ += rc;
|
|
}
|
|
|
|
return read_;
|
|
}
|
|
|
|
ssize_t __fp_write(FILE *fp, void const *data, size_t size)
|
|
{
|
|
size_t written = 0;
|
|
|
|
while(written < size) {
|
|
ssize_t rc = write(fp->fd, data + written, size - written);
|
|
|
|
if(rc < 0) {
|
|
fp->error = 1;
|
|
return EOF;
|
|
}
|
|
if(rc == 0)
|
|
break;
|
|
|
|
fp->fdpos += rc;
|
|
written += rc;
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
int __fp_parse_mode(char const *mode, FILE *fp)
|
|
{
|
|
int base = 0;
|
|
bool binary = false;
|
|
bool update = false;
|
|
|
|
for(int i = 0; mode[i]; i++) {
|
|
if(mode[i] == 'r' || mode[i] == 'w' || mode[i] == 'a') {
|
|
if(base) goto err;
|
|
base = mode[i];
|
|
}
|
|
else if(mode[i] == 'b')
|
|
binary = true;
|
|
else if(mode[i] == '+')
|
|
update = true;
|
|
}
|
|
if(!base) goto err;
|
|
|
|
if(fp) {
|
|
fp->readable = (base == 'r' || update);
|
|
fp->writable = (base == 'w' || base == 'a' || update);
|
|
fp->append = (base == 'a');
|
|
fp->text = !binary;
|
|
}
|
|
|
|
if(base == 'r')
|
|
return (update ? O_RDWR : O_RDONLY);
|
|
if(base == 'w')
|
|
return (update ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC;
|
|
if(base == 'a')
|
|
return (update ? O_RDWR : O_WRONLY) | O_CREAT;
|
|
/* Fallthrough */
|
|
|
|
err:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|