diff --git a/configure.ac b/configure.ac index 2e98fcd..69b7b9f 100644 --- a/configure.ac +++ b/configure.ac @@ -77,8 +77,8 @@ dnl Checks for library functions. AC_TYPE_GETGROUPS AC_FUNC_MMAP -AC_CHECK_FUNCS(strerror strtol lstat setrlimit sigrelse sighold sigaction \ -sysconf sigsetjmp getrusage gettimeofday mmap mprotect) +AC_CHECK_FUNCS(strerror strtol lseek lstat setrlimit sigrelse sighold \ +sigaction sysconf sigsetjmp getrusage gettimeofday mmap mprotect) AC_CACHE_CHECK(whether getenv can be redefined, es_cv_local_getenv, [if test "$ac_cv_header_stdlib_h" = no || test "$ac_cv_header_stdc" = no; then diff --git a/prim-io.c b/prim-io.c index 57edf20..eb3886e 100644 --- a/prim-io.c +++ b/prim-io.c @@ -413,7 +413,7 @@ PRIM(newfd) { return mklist(mkstr(str("%d", newfd())), NULL); } -/* read1 -- read one byte */ +/* read1 -- read one byte, return the byte */ static int read1(int fd) { int nread; unsigned char buf; @@ -426,6 +426,18 @@ static int read1(int fd) { return nread == 0 ? EOF : buf; } +/* readn -- read up to n bytes, return the number read */ +static int readn(int fd, char *s, size_t n) { + int nread; + do { + nread = read(fd, s, n); + SIGCHK(); + } while (nread == -1 && errno == EINTR); + if (nread == -1) + fail("$&read", "%s", esstrerror(errno)); + return nread; +} + PRIM(read) { int c; int fd = fdmap(0); @@ -435,11 +447,36 @@ PRIM(read) { freebuffer(buffer); buffer = openbuffer(0); - while ((c = read1(fd)) != EOF && c != '\n') - if (c == '\0') - fail("$&read", "%%read: null character encountered"); - else - buffer = bufputc(buffer, c); +#if HAVE_LSEEK + if (lseek(fd, 0, SEEK_CUR) < 0) { +#endif + while ((c = read1(fd)) != EOF && c != '\n') + if (c == '\0') + fail("$&read", "%%read: null character encountered"); + else + buffer = bufputc(buffer, c); +#if HAVE_LSEEK + } else { + int n; + char *np, *zp; + char s[BUFSIZE]; + c = EOF; + while ((n = readn(fd, s, BUFSIZE)) > 0) { + c = 0; + if ((np = strchr(s, '\n')) != NULL) { + lseek(fd, 1 + ((np - s) - n), SEEK_CUR); + n = np - s; + } + if ((zp = memchr(s, '\0', n)) != NULL) { + lseek(fd, 1 + ((zp - s) - n), SEEK_CUR); + fail("$&read", "%%read: null character encountered"); + } + buffer = bufncat(buffer, s, n); + if (np != NULL && *np == '\n') + break; + } + } +#endif if (c == EOF && buffer->current == 0) { freebuffer(buffer); diff --git a/test/tests/read.es b/test/tests/read.es new file mode 100644 index 0000000..e4e1598 --- /dev/null +++ b/test/tests/read.es @@ -0,0 +1,38 @@ +#!/usr/local/bin/es + +test 'null reading' { + let (tmp = `{mktemp test-nul.XXXXXX}) + unwind-protect { + echo first line > $tmp + ./testrun 0 >> $tmp + + let (fl = (); ex = (); remainder = ()) { + catch @ e { + ex = $e + remainder = <=%read + } { + fl = <=%read + %read + } < $tmp + assert {~ $fl 'first line'} 'seeking read reads valid line' + assert {~ $ex(3) *'null character encountered'*} 'seeking read throws exception correctly' + assert {~ $remainder 'sult 6'} 'seeking read leaves file in correct state:' $remainder + } + + let ((fl ex remainder) = `` \n { + let (fl = ()) + cat $tmp | catch @ e { + echo $fl\n$e(3)\n^<=%read + } { + fl = <=%read + %read + } + }) { + assert {~ $fl 'first line'} 'non-seeking read reads valid line' + assert {~ $ex *'null character encountered'*} 'non-seeking read throws exception correctly' + assert {~ $remainder 'sult 6'} 'non-seeking read leaves file in correct state' + } + } { + rm -f $tmp + } +}