Browse Source
This is a simple helper for dealing with buffered I/O. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>ppa-0.6.1
10 changed files with 597 additions and 0 deletions
@ -0,0 +1 @@ |
|||
../../licenses/BSD-MIT |
@ -0,0 +1,53 @@ |
|||
#include "config.h" |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
/** |
|||
* rbuf - buffered I/O input primitive. |
|||
* |
|||
* This code is like stdio, only simpler and more transparent to the user. |
|||
* |
|||
* Author: Rusty Russell <rusty@rustcorp.com.au> |
|||
* License: BSD-MIT |
|||
* |
|||
* Example: |
|||
* #include <ccan/rbuf/rbuf.h> |
|||
* #include <ccan/err/err.h> |
|||
* #include <stdlib.h> |
|||
* #include <unistd.h> |
|||
* |
|||
* // Dumb demo program to replace ' ' with '*'. |
|||
* int main(int argc, char *argv[]) |
|||
* { |
|||
* struct rbuf in; |
|||
* char *word; |
|||
* |
|||
* if (argv[1]) { |
|||
* if (!rbuf_open(&in, argv[1], NULL, 0)) |
|||
* err(1, "Failed opening %s", argv[1]); |
|||
* } else |
|||
* rbuf_init(&in, STDIN_FILENO, NULL, 0); |
|||
* |
|||
* while ((word = rbuf_read_str(&in, ' ', realloc)) != NULL) |
|||
* printf("%s*", word); |
|||
* |
|||
* if (errno) |
|||
* err(1, "Reading %s", argv[1] ? argv[1] : "<stdin>"); |
|||
* |
|||
* // Free the buffer, just because we can. |
|||
* free(in.buf); |
|||
* return 0; |
|||
* } |
|||
*/ |
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
/* Expect exactly one argument */ |
|||
if (argc != 2) |
|||
return 1; |
|||
|
|||
if (strcmp(argv[1], "depends") == 0) { |
|||
return 0; |
|||
} |
|||
|
|||
return 1; |
|||
} |
@ -0,0 +1,130 @@ |
|||
/* Licensed under BSD-MIT - see LICENSE file for details */ |
|||
#include <ccan/rbuf/rbuf.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <unistd.h> |
|||
#include <errno.h> |
|||
#include <string.h> |
|||
#include <fcntl.h> |
|||
|
|||
bool rbuf_open(struct rbuf *rbuf, const char *name, char *buf, size_t buf_max) |
|||
{ |
|||
int fd = open(name, O_RDONLY); |
|||
if (fd >= 0) { |
|||
rbuf_init(rbuf, fd, buf, buf_max); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
static size_t rem(const struct rbuf *buf) |
|||
{ |
|||
return buf->buf_end - (buf->start + buf->len); |
|||
} |
|||
|
|||
size_t rbuf_good_size(int fd) |
|||
{ |
|||
struct stat st; |
|||
|
|||
if (fstat(fd, &st) == 0 && st.st_blksize >= 4096) |
|||
return st.st_blksize; |
|||
return 4096; |
|||
} |
|||
|
|||
static bool enlarge_buf(struct rbuf *buf, size_t len, |
|||
void *(*resize)(void *buf, size_t len)) |
|||
{ |
|||
char *new; |
|||
if (!resize) { |
|||
errno = ENOMEM; |
|||
return false; |
|||
} |
|||
if (!len) |
|||
len = rbuf_good_size(buf->fd); |
|||
new = resize(buf->buf, len); |
|||
if (!new) |
|||
return false; |
|||
buf->start += (new - buf->buf); |
|||
buf->buf = new; |
|||
buf->buf_end = new + len; |
|||
return true; |
|||
} |
|||
|
|||
static ssize_t get_more(struct rbuf *rbuf, |
|||
void *(*resize)(void *buf, size_t len)) |
|||
{ |
|||
size_t r; |
|||
|
|||
if (rbuf->start + rbuf->len == rbuf->buf_end) { |
|||
if (!enlarge_buf(rbuf, (rbuf->buf_end - rbuf->buf) * 2, resize)) |
|||
return -1; |
|||
} |
|||
|
|||
r = read(rbuf->fd, rbuf->start + rbuf->len, rem(rbuf)); |
|||
if (r <= 0) |
|||
return r; |
|||
|
|||
rbuf->len += r; |
|||
return r; |
|||
} |
|||
|
|||
void *rbuf_fill_all(struct rbuf *rbuf, void *(*resize)(void *buf, size_t len)) |
|||
{ |
|||
ssize_t r; |
|||
|
|||
/* Move back to start of buffer if we're empty. */ |
|||
if (!rbuf->len) |
|||
rbuf->start = rbuf->buf; |
|||
|
|||
while ((r = get_more(rbuf, resize)) != 0) |
|||
if (r < 0) |
|||
return NULL; |
|||
return rbuf->start; |
|||
} |
|||
|
|||
void *rbuf_fill(struct rbuf *rbuf, void *(*resize)(void *buf, size_t len)) |
|||
{ |
|||
if (!rbuf->len) { |
|||
rbuf->start = rbuf->buf; |
|||
if (get_more(rbuf, resize) < 0) |
|||
return NULL; |
|||
} |
|||
return rbuf->start; |
|||
} |
|||
|
|||
char *rbuf_read_str(struct rbuf *rbuf, char term, |
|||
void *(*resize)(void *buf, size_t len)) |
|||
{ |
|||
char *p, *ret; |
|||
ssize_t r = 0; |
|||
size_t prev = 0; |
|||
|
|||
/* Move back to start of buffer if we're empty. */ |
|||
if (!rbuf->len) |
|||
rbuf->start = rbuf->buf; |
|||
|
|||
while (!(p = memchr(rbuf->start + prev, term, rbuf->len - prev))) { |
|||
prev += r; |
|||
r = get_more(rbuf, resize); |
|||
if (r < 0) |
|||
return NULL; |
|||
/* EOF with no term. */ |
|||
if (r == 0) { |
|||
/* Nothing read at all? */ |
|||
if (!rbuf->len && term) { |
|||
errno = 0; |
|||
return NULL; |
|||
} |
|||
/* Put term after input (get_more made room). */ |
|||
assert(rbuf->start + rbuf->len < rbuf->buf_end); |
|||
rbuf->start[rbuf->len] = '\0'; |
|||
ret = rbuf->start; |
|||
rbuf_consume(rbuf, rbuf->len); |
|||
return ret; |
|||
} |
|||
} |
|||
*p = '\0'; |
|||
ret = rbuf->start; |
|||
rbuf_consume(rbuf, p + 1 - ret); |
|||
return ret; |
|||
} |
@ -0,0 +1,156 @@ |
|||
/* Licensed under BSD-MIT - see LICENSE file for details */ |
|||
#ifndef CCAN_RBUF_H |
|||
#define CCAN_RBUF_H |
|||
#include <stdio.h> // For size_t |
|||
#include <limits.h> // For UCHAR_MAX |
|||
#include <assert.h> |
|||
#include <stdbool.h> |
|||
|
|||
struct rbuf { |
|||
int fd; |
|||
|
|||
/* Where to read next. */ |
|||
char *start; |
|||
/* How much of what is there is valid. */ |
|||
size_t len; |
|||
|
|||
/* The entire buffer memory we have to work with. */ |
|||
char *buf, *buf_end; |
|||
}; |
|||
|
|||
/**
|
|||
* rbuf_init - set up a buffer. |
|||
* @buf: the struct rbuf. |
|||
* @fd: the file descriptor. |
|||
* @buf: the buffer to use. |
|||
* @buf_max: the size of the buffer. |
|||
*/ |
|||
static inline void rbuf_init(struct rbuf *buf, |
|||
int fd, char *buffer, size_t buf_max) |
|||
{ |
|||
buf->fd = fd; |
|||
buf->start = buf->buf = buffer; |
|||
buf->len = 0; |
|||
buf->buf_end = buffer + buf_max; |
|||
} |
|||
|
|||
/**
|
|||
* rbuf_open - set up a buffer by opening a file. |
|||
* @buf: the struct rbuf. |
|||
* @filename: the filename |
|||
* @buf: the buffer to use. |
|||
* @buf_max: the size of the buffer. |
|||
* |
|||
* Returns false if the open fails. If @buf_max is 0, then the buffer |
|||
* will be resized to rbuf_good_size() on first rbuf_fill. |
|||
* |
|||
* Example: |
|||
* struct rbuf in; |
|||
* |
|||
* if (!rbuf_open(&in, "foo", NULL, 0)) |
|||
* err(1, "Could not open foo"); |
|||
*/ |
|||
bool rbuf_open(struct rbuf *rbuf, const char *name, char *buf, size_t buf_max); |
|||
|
|||
/**
|
|||
* rbuf_good_size - get a good buffer size for this fd. |
|||
* @fd: the file descriptor. |
|||
* |
|||
* If you don't know what size you want, try this. |
|||
*/ |
|||
size_t rbuf_good_size(int fd); |
|||
|
|||
/**
|
|||
* rbuf_fill - read into a buffer if it's empty. |
|||
* @buf: the struct rbuf |
|||
* @resize: the call to resize the buffer. |
|||
* |
|||
* If @resize is needed and is NULL, or returns false, rbuf_read will |
|||
* return NULL (with errno set to ENOMEM). If a read fails, then NULL |
|||
* is also returned. If there is nothing more to read, it will return |
|||
* NULL with errno set to 0. Otherwise, returns @buf->start; @buf->len |
|||
* is the valid length of the buffer. |
|||
* |
|||
* You need to call rbuf_consume() to mark data in the buffer as |
|||
* consumed. |
|||
* |
|||
* Example: |
|||
* while (rbuf_fill(&in, realloc)) { |
|||
* printf("%.*s\n", (int)in.len, in.start); |
|||
* rbuf_consume(&in, in.len); |
|||
* } |
|||
* if (errno) |
|||
* err(1, "reading foo"); |
|||
*/ |
|||
void *rbuf_fill(struct rbuf *rbuf, void *(*resize)(void *buf, size_t len)); |
|||
|
|||
/**
|
|||
* rbuf_consume - helper to use up data in a buffer. |
|||
* @buf: the struct rbuf |
|||
* @len: the length (from @buf->start) you used. |
|||
* |
|||
* After rbuf_fill() you should indicate the data you've used with |
|||
* rbuf_consume(). That way rbuf_fill() will know if it has anything |
|||
* to do. |
|||
*/ |
|||
static inline void rbuf_consume(struct rbuf *buf, size_t len) |
|||
{ |
|||
buf->len -= len; |
|||
buf->start += len; |
|||
} |
|||
|
|||
/**
|
|||
* rbuf_fill_all - read rest of file into a buffer. |
|||
* @buf: the struct rbuf |
|||
* @resize: the call to resize the buffer. |
|||
* |
|||
* If @resize is needed and is NULL, or returns false, rbuf_read_all |
|||
* will return NULL (with errno set to ENOMEM). If a read fails, |
|||
* then NULL is also returned, otherwise returns @buf->start. |
|||
* |
|||
* Example: |
|||
* if (!rbuf_fill_all(&in, realloc)) { |
|||
* if (errno) |
|||
* err(1, "reading foo"); |
|||
* } |
|||
*/ |
|||
void *rbuf_fill_all(struct rbuf *rbuf, void *(*resize)(void *buf, size_t len)); |
|||
|
|||
/**
|
|||
* rbuf_read_str - fill into a buffer up to a terminator, and consume string. |
|||
* @buf: the struct rbuf |
|||
* @term: the character to terminate the read. |
|||
* @resize: the call to resize the buffer. |
|||
* |
|||
* If @resize is needed and is NULL, or returns false, rbuf_read_str |
|||
* will return NULL (with errno set to ENOMEM). If a read fails, |
|||
* then NULL is also returned, otherwise the next string. It |
|||
* replaces the terminator @term (if any) with NUL, otherwise NUL |
|||
* is placed after EOF. If you need to, you can tell this has happened |
|||
* because the nul terminator will be at @buf->start (normally it will |
|||
* be at @buf->start - 1). |
|||
* |
|||
* If there is nothing remaining to be read, NULL is returned with |
|||
* errno set to 0, unless @term is NUL, in which case it returns the |
|||
* empty string. |
|||
* |
|||
* Note: using @term set to NUL is a cheap way of getting an entire |
|||
* file into a C string, as long as the file doesn't contain NUL. |
|||
* |
|||
* Example: |
|||
* char *line; |
|||
* |
|||
* line = rbuf_read_str(&in, '\n', realloc); |
|||
* if (!line) { |
|||
* if (errno) |
|||
* err(1, "reading foo"); |
|||
* else |
|||
* printf("Empty file\n"); |
|||
* } else |
|||
* printf("First line is %s\n", line); |
|||
* |
|||
*/ |
|||
char *rbuf_read_str(struct rbuf *rbuf, char term, |
|||
void *(*resize)(void *buf, size_t len)); |
|||
|
|||
#endif /* CCAN_RBUF_H */ |
@ -0,0 +1,49 @@ |
|||
#include <ccan/rbuf/rbuf.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/rbuf/rbuf.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
#include <stdlib.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct rbuf in; |
|||
char buf[4096]; |
|||
int i, size, fd = open("run-all-file", O_WRONLY|O_CREAT, 0600); |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(8); |
|||
|
|||
/* Make sure we're bigger than a single buffer! */ |
|||
size = rbuf_good_size(fd)*2; |
|||
for (i = 0; i * sizeof(buf) < size; i++) { |
|||
memset(buf, 0x42 + i, sizeof(buf)); |
|||
write(fd, buf, sizeof(buf)); |
|||
} |
|||
close(fd); |
|||
|
|||
ok1(rbuf_open(&in, "run-all-file", NULL, 0)); |
|||
/* Can't fill without realloc. */ |
|||
ok1(!rbuf_fill(&in, NULL)); |
|||
ok1(errno == ENOMEM); |
|||
ok1(rbuf_fill(&in, realloc)); |
|||
/* But can't load in whole file. */ |
|||
ok1(!rbuf_fill_all(&in, NULL)); |
|||
ok1(errno == ENOMEM); |
|||
ok1(rbuf_fill_all(&in, realloc)); |
|||
ok1(in.len == size); |
|||
for (i = 0; i * sizeof(buf) < size; i++) { |
|||
memset(buf, 0x42 + i, sizeof(buf)); |
|||
if (memcmp(buf, in.start, sizeof(buf)) != 0) { |
|||
fail("Bad buffer contents"); |
|||
break; |
|||
} |
|||
rbuf_consume(&in, sizeof(buf)); |
|||
} |
|||
free(in.buf); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -0,0 +1,25 @@ |
|||
#include <ccan/rbuf/rbuf.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/rbuf/rbuf.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
#include <stdlib.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct rbuf in; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(5); |
|||
|
|||
ok1(!rbuf_open(&in, "nonexistent-file", NULL, 0)); |
|||
ok1(errno == ENOENT); |
|||
ok1(rbuf_open(&in, "test/run-open.c", NULL, 0)); |
|||
ok1(close(in.fd) == 0); |
|||
/* If this fails to stat, it should fall back */ |
|||
ok1(rbuf_good_size(in.fd) == 4096); |
|||
|
|||
return exit_status(); |
|||
} |
@ -0,0 +1,67 @@ |
|||
#include <unistd.h> |
|||
|
|||
static ssize_t partial_read(int fd, void *buf, size_t count) |
|||
{ |
|||
return read(fd, buf, 1); |
|||
} |
|||
static ssize_t full_read(int fd, void *buf, size_t count) |
|||
{ |
|||
return read(fd, buf, count); |
|||
} |
|||
#define read partial_read |
|||
|
|||
#include <ccan/rbuf/rbuf.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/rbuf/rbuf.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
#include <stdlib.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct rbuf in; |
|||
char buf[4096]; |
|||
char *lines[100], *p; |
|||
int i, fd = open("test/run.c", O_RDONLY); |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(140); |
|||
|
|||
/* Grab ourselves for comparison. */ |
|||
buf[full_read(fd, buf, sizeof(buf))] = '\0'; |
|||
lseek(fd, SEEK_SET, 0); |
|||
|
|||
for (i = 0, p = buf; *p; i++) { |
|||
lines[i] = p; |
|||
p = strchr(p, '\n'); |
|||
*p = '\0'; |
|||
p++; |
|||
} |
|||
lines[i] = NULL; |
|||
|
|||
rbuf_init(&in, fd, malloc(31), 31); |
|||
ok1(in.fd == fd); |
|||
ok1(in.buf_end - in.buf == 31); |
|||
p = rbuf_read_str(&in, '\n', NULL); |
|||
ok1(p); |
|||
ok1(strcmp(p, lines[0]) == 0); |
|||
|
|||
p = rbuf_read_str(&in, '\n', realloc); |
|||
ok1(p); |
|||
ok1(strcmp(p, lines[1]) == 0); |
|||
|
|||
for (i = 2; lines[i]; i++) { |
|||
ok1(p = rbuf_read_str(&in, '\n', realloc)); |
|||
ok1(strcmp(p, lines[i]) == 0); |
|||
} |
|||
|
|||
p = rbuf_read_str(&in, '\n', realloc); |
|||
ok1(errno == 0); |
|||
ok1(p == NULL); |
|||
free(in.buf); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -0,0 +1,44 @@ |
|||
#include <ccan/rbuf/rbuf.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/rbuf/rbuf.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
#include <stdlib.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct rbuf in; |
|||
char buf[4096], *p; |
|||
int fd = open("test/run-term-eof.c", O_RDONLY), len; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(6); |
|||
|
|||
/* Grab ourselves for comparison. */ |
|||
len = read(fd, buf, sizeof(buf)); |
|||
buf[len] = '\0'; |
|||
lseek(fd, SEEK_SET, 0); |
|||
|
|||
/* We have exact-size buffer, which causes problems adding term. */ |
|||
rbuf_init(&in, fd, malloc(len), len); |
|||
p = rbuf_read_str(&in, 64, NULL); /* At symbol does not appear. */ |
|||
ok1(errno == ENOMEM); |
|||
ok1(!p); |
|||
/* This should succeed... */ |
|||
p = rbuf_read_str(&in, 64, realloc); |
|||
ok1(p); |
|||
ok1(strcmp(p, buf) == 0); |
|||
free(in.buf); |
|||
|
|||
/* Try again. */ |
|||
lseek(fd, SEEK_SET, 0); |
|||
rbuf_init(&in, fd, malloc(len), len); |
|||
p = rbuf_read_str(&in, 64, realloc); |
|||
ok1(p); |
|||
ok1(strcmp(p, buf) == 0); |
|||
free(in.buf); |
|||
|
|||
return exit_status(); |
|||
} |
@ -0,0 +1,68 @@ |
|||
#include <ccan/rbuf/rbuf.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/rbuf/rbuf.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
#include <stdlib.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct rbuf in; |
|||
char buf[4096]; |
|||
char *lines[100], *p; |
|||
int i, fd = open("test/run.c", O_RDONLY), len; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(144); |
|||
|
|||
/* Grab ourselves for comparison. */ |
|||
len = read(fd, buf, sizeof(buf)); |
|||
buf[len] = '\0'; |
|||
lseek(fd, SEEK_SET, 0); |
|||
|
|||
for (i = 0, p = buf; *p; i++) { |
|||
lines[i] = p; |
|||
p = strchr(p, '\n'); |
|||
*p = '\0'; |
|||
p++; |
|||
} |
|||
lines[i] = NULL; |
|||
|
|||
rbuf_init(&in, fd, malloc(31), 31); |
|||
ok1(in.fd == fd); |
|||
ok1(in.buf_end - in.buf == 31); |
|||
p = rbuf_read_str(&in, '\n', NULL); |
|||
ok1(p); |
|||
ok1(strcmp(p, lines[0]) == 0); |
|||
|
|||
p = rbuf_read_str(&in, '\n', realloc); |
|||
ok1(p); |
|||
ok1(strcmp(p, lines[1]) == 0); |
|||
|
|||
for (i = 2; lines[i]; i++) { |
|||
ok1(p = rbuf_read_str(&in, '\n', realloc)); |
|||
ok1(strcmp(p, lines[i]) == 0); |
|||
} |
|||
|
|||
p = rbuf_read_str(&in, '\n', realloc); |
|||
ok1(errno == 0); |
|||
ok1(p == NULL); |
|||
free(in.buf); |
|||
|
|||
/* Another way of reading the entire (text) file. */ |
|||
lseek(fd, SEEK_SET, 0); |
|||
rbuf_init(&in, fd, NULL, 0); |
|||
p = rbuf_read_str(&in, 0, realloc); |
|||
ok1(p); |
|||
ok1(strlen(p) == len); |
|||
|
|||
close(fd); |
|||
p = rbuf_read_str(&in, 0, realloc); |
|||
ok1(errno == EBADF); |
|||
ok1(!p); |
|||
free(in.buf); |
|||
|
|||
return exit_status(); |
|||
} |
Loading…
Reference in new issue