diff options
-rw-r--r-- | lib/system.nim | 9 | ||||
-rw-r--r-- | lib/system/sysio.nim | 51 | ||||
-rw-r--r-- | tests/stdlib/tio.nim | 40 |
3 files changed, 76 insertions, 24 deletions
diff --git a/lib/system.nim b/lib/system.nim index 5d17f7651..a5a52f7bd 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -435,6 +435,10 @@ type ## Raised if an IO error occurred. ## ## See the full `exception hierarchy`_. + EOFError* = object of IOError ## \ + ## Raised if an IO "end of file" error occurred. + ## + ## See the full `exception hierarchy`_. OSError* = object of SystemError ## \ ## Raised if an operating system service failed. ## @@ -2778,8 +2782,9 @@ when not defined(JS): #and not defined(nimscript): proc endOfFile*(f: File): bool {.tags: [], benign.} ## Returns true iff `f` is at the end. - proc readChar*(f: File): char {.tags: [ReadIOEffect].} - ## Reads a single character from the stream `f`. + proc readChar*(f: File): char {.tags: [ReadIOEffect], deprecated.} + ## Reads a single character from the stream `f`. **Deprecated** since + ## version 0.16.2. Use some variant of ``readBuffer`` instead. proc flushFile*(f: File) {.tags: [WriteIOEffect].} ## Flushes `f`'s buffer. diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 29c5777cc..9a2fb920a 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -32,6 +32,10 @@ proc c_fflush(f: File): cint {. importc: "fflush", header: "<stdio.h>".} proc c_fclose(f: File): cint {. importc: "fclose", header: "<stdio.h>".} +proc c_clearerr(f: File) {. + importc: "clearerr", header: "<stdio.h>".} +proc c_feof(f: File): cint {. + importc: "feof", header: "<stdio.h>".} # C routine that is used here: proc c_fread(buf: pointer, size, n: csize, f: File): csize {. @@ -50,9 +54,18 @@ proc c_fwrite(buf: pointer, size, n: csize, f: File): cint {. proc raiseEIO(msg: string) {.noinline, noreturn.} = sysFatal(IOError, msg) +proc raiseEOF() {.noinline, noreturn.} = + sysFatal(EOFError, "EOF reached") + +proc checkErr(f: File) = + if c_ferror(f) != 0: + c_clearerr(f) + raiseEIO("Unknown IO Error") + {.push stackTrace:off, profiler:off.} proc readBuffer(f: File, buffer: pointer, len: Natural): int = result = c_fread(buffer, 1, len, f) + checkErr(f) proc readBytes(f: File, a: var openArray[int8|uint8], start, len: Natural): int = result = readBuffer(f, addr(a[start]), len) @@ -62,10 +75,13 @@ proc readChars(f: File, a: var openArray[char], start, len: Natural): int = raiseEIO("buffer overflow: (start+len) > length of openarray buffer") result = readBuffer(f, addr(a[start]), len) -proc write(f: File, c: cstring) = discard c_fputs(c, f) +proc write(f: File, c: cstring) = + discard c_fputs(c, f) + checkErr(f) proc writeBuffer(f: File, buffer: pointer, len: Natural): int = result = c_fwrite(buffer, 1, len, f) + checkErr(f) proc writeBytes(f: File, a: openArray[int8|uint8], start, len: Natural): int = var x = cast[ptr array[0..1000_000_000, int8]](a) @@ -98,7 +114,12 @@ const BufSize = 4000 proc close*(f: File) = discard c_fclose(f) -proc readChar*(f: File): char = result = char(c_fgetc(f)) +proc readChar(f: File): char = + let x = c_fgetc(f) + if x == -1: raiseEOF() + checkErr(f) + result = char(x) + proc flushFile*(f: File) = discard c_fflush(f) proc getFileHandle*(f: File): FileHandle = c_fileno(f) @@ -113,19 +134,18 @@ proc readLine(f: File, line: var TaintedString): bool = sp = cint(cast[PGenericSeq](line.string).space) line.string.setLen(sp) while true: - # memset to \l so that we can tell how far fgets wrote, even on EOF, where - # fgets doesn't append an \l - c_memset(addr line.string[pos], '\l'.ord, sp) - if c_fgets(addr line.string[pos], sp, f) == nil: - line.string.setLen(0) - return false - let m = c_memchr(addr line.string[pos], '\l'.ord, sp) + # memset to \L so that we can tell how far fgets wrote, even on EOF, where + # fgets doesn't append an \L + c_memset(addr line.string[pos], '\L'.ord, sp) + var fgetsSuccess = c_fgets(addr line.string[pos], sp, f) != nil + checkErr(f) + let m = c_memchr(addr line.string[pos], '\L'.ord, sp) if m != nil: # \l found: Could be our own or the one by fgets, in any case, we're done var last = cast[ByteAddress](m) - cast[ByteAddress](addr line.string[0]) if last > 0 and line.string[last-1] == '\c': line.string.setLen(last-1) - return true + return fgetsSuccess # We have to distinguish between two possible cases: # \0\l\0 => line ending in a null character. # \0\l\l => last line without newline, null was put there by fgets. @@ -133,7 +153,7 @@ proc readLine(f: File, line: var TaintedString): bool = if last < pos + sp - 1 and line.string[last+1] != '\0': dec last line.string.setLen(last) - return true + return fgetsSuccess else: # fgets will have inserted a null byte at the end of the string. dec sp @@ -144,7 +164,7 @@ proc readLine(f: File, line: var TaintedString): bool = proc readLine(f: File): TaintedString = result = TaintedString(newStringOfCap(80)) - if not readLine(f, result): raiseEIO("EOF reached") + if not readLine(f, result): raiseEOF() proc write(f: File, i: int) = when sizeof(int) == 8: @@ -190,10 +210,7 @@ proc rawFileSize(file: File): int = discard c_fseek(file, clong(oldPos), 0) proc endOfFile(f: File): bool = - # do not blame me; blame the ANSI C standard this is so brain-damaged - var c = c_fgetc(f) - discard c_ungetc(c, f) - return c < 0'i32 + result = c_feof(f) != 0 proc readAllFile(file: File, len: int): string = # We acquire the filesize beforehand and hope it doesn't change. @@ -203,8 +220,6 @@ proc readAllFile(file: File, len: int): string = if endOfFile(file): if bytes < len: result.setLen(bytes) - elif c_ferror(file) != 0: - raiseEIO("error while reading from file") else: # We read all the bytes but did not reach the EOF # Try to read it as a buffer diff --git a/tests/stdlib/tio.nim b/tests/stdlib/tio.nim index ebf2d70f3..93284c1f7 100644 --- a/tests/stdlib/tio.nim +++ b/tests/stdlib/tio.nim @@ -1,7 +1,39 @@ +discard """ + output: '''9 +b = false +123456789 +Second readLine raised an exception +123456789 +''' +""" +# bug #5349 +import os + # test the file-IO -proc main() = - for line in lines("thello.nim"): - writeLine(stdout, line) +const fn = "file9char.txt" + +writeFile(fn, "123456789") + +var f = open(fn) +echo getFileSize(f) + +var line = newString(10) +try: + let b = readLine(f, line) + echo "b = ", b +except: + echo "First readLine raised an exception" + +echo line + +try: + line = readLine(f) + let b = readLine(f, line) + echo "b = ", b +except: + echo "Second readLine raised an exception" + +echo line -main() +removeFile(fn) |