os: fix file read end-of-file detection (#10070)
parent
ebe58dcafa
commit
49deeac71e
100
vlib/os/file.c.v
100
vlib/os/file.c.v
|
@ -321,6 +321,31 @@ pub fn (mut f File) write_ptr_at(data voidptr, size int, pos u64) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// **************************** Read ops ***************************
|
// **************************** Read ops ***************************
|
||||||
|
|
||||||
|
// fread wraps C.fread and handles error and end-of-file detection.
|
||||||
|
fn fread(ptr voidptr, item_size int, items int, stream &C.FILE) ?int {
|
||||||
|
nbytes := int(C.fread(ptr, item_size, items, stream))
|
||||||
|
// If no bytes were read, check for errors and end-of-file.
|
||||||
|
if nbytes <= 0 {
|
||||||
|
// If fread encountered end-of-file return the none error. Note that fread
|
||||||
|
// may read data and encounter the end-of-file, but we shouldn't return none
|
||||||
|
// in that case which is why we only check for end-of-file if no data was
|
||||||
|
// read. The caller will get none on their next call because there will be
|
||||||
|
// no data available and the end-of-file will be encountered again.
|
||||||
|
if C.feof(stream) != 0 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
// If fread encountered an error, return it. Note that fread and ferror do
|
||||||
|
// not tell us what the error was, so we can't return anything more specific
|
||||||
|
// than there was an error. This is because fread and ferror do not set
|
||||||
|
// errno.
|
||||||
|
if C.ferror(stream) != 0 {
|
||||||
|
return error('file read error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nbytes
|
||||||
|
}
|
||||||
|
|
||||||
// read_bytes reads bytes from the beginning of the file.
|
// read_bytes reads bytes from the beginning of the file.
|
||||||
// Utility method, same as .read_bytes_at(size, 0).
|
// Utility method, same as .read_bytes_at(size, 0).
|
||||||
pub fn (f &File) read_bytes(size int) []byte {
|
pub fn (f &File) read_bytes(size int) []byte {
|
||||||
|
@ -348,23 +373,14 @@ pub fn (f &File) read_bytes_into(pos u64, mut buf []byte) ?int {
|
||||||
$if windows {
|
$if windows {
|
||||||
// Note: fseek errors if pos == os.file_size, which we accept
|
// Note: fseek errors if pos == os.file_size, which we accept
|
||||||
C._fseeki64(f.cfile, pos, C.SEEK_SET)
|
C._fseeki64(f.cfile, pos, C.SEEK_SET)
|
||||||
// errno is only set if fread fails, so clear it first to tell
|
nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
|
||||||
C.errno = 0
|
|
||||||
nbytes := int(C.fread(buf.data, 1, buf.len, f.cfile))
|
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
$if debug {
|
$if debug {
|
||||||
C._fseeki64(f.cfile, 0, C.SEEK_SET)
|
C._fseeki64(f.cfile, 0, C.SEEK_SET)
|
||||||
}
|
}
|
||||||
return nbytes
|
return nbytes
|
||||||
} $else {
|
} $else {
|
||||||
C.fseeko(f.cfile, pos, C.SEEK_SET)
|
C.fseeko(f.cfile, pos, C.SEEK_SET)
|
||||||
C.errno = 0
|
nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
|
||||||
nbytes := int(C.fread(buf.data, 1, buf.len, f.cfile))
|
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
$if debug {
|
$if debug {
|
||||||
C.fseeko(f.cfile, 0, C.SEEK_SET)
|
C.fseeko(f.cfile, 0, C.SEEK_SET)
|
||||||
}
|
}
|
||||||
|
@ -373,11 +389,7 @@ pub fn (f &File) read_bytes_into(pos u64, mut buf []byte) ?int {
|
||||||
}
|
}
|
||||||
$if x32 {
|
$if x32 {
|
||||||
C.fseek(f.cfile, pos, C.SEEK_SET)
|
C.fseek(f.cfile, pos, C.SEEK_SET)
|
||||||
C.errno = 0
|
nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
|
||||||
nbytes := int(C.fread(buf.data, 1, buf.len, f.cfile))
|
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
$if debug {
|
$if debug {
|
||||||
C.fseek(f.cfile, 0, C.SEEK_SET)
|
C.fseek(f.cfile, 0, C.SEEK_SET)
|
||||||
}
|
}
|
||||||
|
@ -391,11 +403,7 @@ pub fn (f &File) read(mut buf []byte) ?int {
|
||||||
if buf.len == 0 {
|
if buf.len == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
C.errno = 0
|
nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
|
||||||
nbytes := int(C.fread(buf.data, 1, buf.len, f.cfile))
|
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
return nbytes
|
return nbytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,20 +425,12 @@ pub fn (f &File) read_from(pos u64, mut buf []byte) ?int {
|
||||||
C.fseeko(f.cfile, pos, C.SEEK_SET)
|
C.fseeko(f.cfile, pos, C.SEEK_SET)
|
||||||
}
|
}
|
||||||
|
|
||||||
C.errno = 0
|
nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
|
||||||
nbytes := int(C.fread(buf.data, 1, buf.len, f.cfile))
|
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
return nbytes
|
return nbytes
|
||||||
}
|
}
|
||||||
$if x32 {
|
$if x32 {
|
||||||
C.fseek(f.cfile, pos, C.SEEK_SET)
|
C.fseek(f.cfile, pos, C.SEEK_SET)
|
||||||
C.errno = 0
|
nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
|
||||||
nbytes := int(C.fread(buf.data, 1, buf.len, f.cfile))
|
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
return nbytes
|
return nbytes
|
||||||
}
|
}
|
||||||
return error('Could not read file')
|
return error('Could not read file')
|
||||||
|
@ -461,11 +461,7 @@ pub fn (mut f File) read_struct<T>(mut t T) ? {
|
||||||
if tsize == 0 {
|
if tsize == 0 {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
C.errno = 0
|
nbytes := fread(t, 1, tsize, f.cfile) ?
|
||||||
nbytes := int(C.fread(t, 1, tsize, f.cfile))
|
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
if nbytes != tsize {
|
if nbytes != tsize {
|
||||||
return error_with_code('incomplete struct read', nbytes)
|
return error_with_code('incomplete struct read', nbytes)
|
||||||
}
|
}
|
||||||
|
@ -480,27 +476,23 @@ pub fn (mut f File) read_struct_at<T>(mut t T, pos u64) ? {
|
||||||
if tsize == 0 {
|
if tsize == 0 {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
C.errno = 0
|
|
||||||
mut nbytes := 0
|
mut nbytes := 0
|
||||||
$if x64 {
|
$if x64 {
|
||||||
$if windows {
|
$if windows {
|
||||||
C._fseeki64(f.cfile, pos, C.SEEK_SET)
|
C._fseeki64(f.cfile, pos, C.SEEK_SET)
|
||||||
nbytes = int(C.fread(t, 1, tsize, f.cfile))
|
nbytes = fread(t, 1, tsize, f.cfile) ?
|
||||||
C._fseeki64(f.cfile, 0, C.SEEK_END)
|
C._fseeki64(f.cfile, 0, C.SEEK_END)
|
||||||
} $else {
|
} $else {
|
||||||
C.fseeko(f.cfile, pos, C.SEEK_SET)
|
C.fseeko(f.cfile, pos, C.SEEK_SET)
|
||||||
nbytes = int(C.fread(t, 1, tsize, f.cfile))
|
nbytes = fread(t, 1, tsize, f.cfile) ?
|
||||||
C.fseeko(f.cfile, 0, C.SEEK_END)
|
C.fseeko(f.cfile, 0, C.SEEK_END)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$if x32 {
|
$if x32 {
|
||||||
C.fseek(f.cfile, pos, C.SEEK_SET)
|
C.fseek(f.cfile, pos, C.SEEK_SET)
|
||||||
nbytes = int(C.fread(t, 1, tsize, f.cfile))
|
nbytes = fread(t, 1, tsize, f.cfile) ?
|
||||||
C.fseek(f.cfile, 0, C.SEEK_END)
|
C.fseek(f.cfile, 0, C.SEEK_END)
|
||||||
}
|
}
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
if nbytes != tsize {
|
if nbytes != tsize {
|
||||||
return error_with_code('incomplete struct read', nbytes)
|
return error_with_code('incomplete struct read', nbytes)
|
||||||
}
|
}
|
||||||
|
@ -515,12 +507,8 @@ pub fn (mut f File) read_raw<T>() ?T {
|
||||||
if tsize == 0 {
|
if tsize == 0 {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
C.errno = 0
|
|
||||||
mut t := T{}
|
mut t := T{}
|
||||||
nbytes := int(C.fread(&t, 1, tsize, f.cfile))
|
nbytes := fread(&t, 1, tsize, f.cfile) ?
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
if nbytes != tsize {
|
if nbytes != tsize {
|
||||||
return error_with_code('incomplete struct read', nbytes)
|
return error_with_code('incomplete struct read', nbytes)
|
||||||
}
|
}
|
||||||
|
@ -536,7 +524,6 @@ pub fn (mut f File) read_raw_at<T>(pos u64) ?T {
|
||||||
if tsize == 0 {
|
if tsize == 0 {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
C.errno = 0
|
|
||||||
mut nbytes := 0
|
mut nbytes := 0
|
||||||
mut t := T{}
|
mut t := T{}
|
||||||
$if x64 {
|
$if x64 {
|
||||||
|
@ -544,10 +531,7 @@ pub fn (mut f File) read_raw_at<T>(pos u64) ?T {
|
||||||
if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 {
|
if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 {
|
||||||
return error(posix_get_error_msg(C.errno))
|
return error(posix_get_error_msg(C.errno))
|
||||||
}
|
}
|
||||||
nbytes = int(C.fread(&t, 1, tsize, f.cfile))
|
nbytes = fread(&t, 1, tsize, f.cfile) ?
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 {
|
if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 {
|
||||||
return error(posix_get_error_msg(C.errno))
|
return error(posix_get_error_msg(C.errno))
|
||||||
}
|
}
|
||||||
|
@ -555,10 +539,7 @@ pub fn (mut f File) read_raw_at<T>(pos u64) ?T {
|
||||||
if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 {
|
if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 {
|
||||||
return error(posix_get_error_msg(C.errno))
|
return error(posix_get_error_msg(C.errno))
|
||||||
}
|
}
|
||||||
nbytes = int(C.fread(&t, 1, tsize, f.cfile))
|
nbytes = fread(&t, 1, tsize, f.cfile) ?
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 {
|
if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 {
|
||||||
return error(posix_get_error_msg(C.errno))
|
return error(posix_get_error_msg(C.errno))
|
||||||
}
|
}
|
||||||
|
@ -568,10 +549,7 @@ pub fn (mut f File) read_raw_at<T>(pos u64) ?T {
|
||||||
if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 {
|
if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 {
|
||||||
return error(posix_get_error_msg(C.errno))
|
return error(posix_get_error_msg(C.errno))
|
||||||
}
|
}
|
||||||
nbytes = int(C.fread(&t, 1, tsize, f.cfile))
|
nbytes = fread(&t, 1, tsize, f.cfile) ?
|
||||||
if C.errno != 0 {
|
|
||||||
return error(posix_get_error_msg(C.errno))
|
|
||||||
}
|
|
||||||
if C.fseek(f.cfile, 0, C.SEEK_END) != 0 {
|
if C.fseek(f.cfile, 0, C.SEEK_END) != 0 {
|
||||||
return error(posix_get_error_msg(C.errno))
|
return error(posix_get_error_msg(C.errno))
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,70 @@ fn testsuite_end() ? {
|
||||||
assert !os.is_dir(tfolder)
|
assert !os.is_dir(tfolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test_read_eof_last_read_partial_buffer_fill tests that when reading a file
|
||||||
|
// the end-of-file is detected and results in a none error being returned. This
|
||||||
|
// test simulates file reading where the end-of-file is reached inside an fread
|
||||||
|
// containing data.
|
||||||
|
fn test_read_eof_last_read_partial_buffer_fill() ? {
|
||||||
|
mut f := os.open_file(tfile, 'w') ?
|
||||||
|
bw := []byte{len: 199, init: 5}
|
||||||
|
f.write(bw) ?
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = os.open_file(tfile, 'r') ?
|
||||||
|
mut br := []byte{len: 100}
|
||||||
|
// Read first 100 bytes of 199 byte file, should fill buffer with no error.
|
||||||
|
n0 := f.read(mut br) ?
|
||||||
|
assert n0 == 100
|
||||||
|
// Read remaining 99 bytes of 199 byte file, should fill buffer with no
|
||||||
|
// error, even though end-of-file was reached.
|
||||||
|
n1 := f.read(mut br) ?
|
||||||
|
assert n1 == 99
|
||||||
|
// Read again, end-of-file was previously reached so should return none
|
||||||
|
// error.
|
||||||
|
if _ := f.read(mut br) {
|
||||||
|
// This is not intended behavior because the read function should
|
||||||
|
// not return a number of bytes read when end-of-file is reached.
|
||||||
|
assert false
|
||||||
|
} else {
|
||||||
|
// Expect none to have been returned when end-of-file.
|
||||||
|
assert err is none
|
||||||
|
}
|
||||||
|
f.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// test_read_eof_last_read_full_buffer_fill tests that when reading a file the
|
||||||
|
// end-of-file is detected and results in a none error being returned. This test
|
||||||
|
// simulates file reading where the end-of-file is reached at the beinning of an
|
||||||
|
// fread that returns no data.
|
||||||
|
fn test_read_eof_last_read_full_buffer_fill() ? {
|
||||||
|
mut f := os.open_file(tfile, 'w') ?
|
||||||
|
bw := []byte{len: 200, init: 5}
|
||||||
|
f.write(bw) ?
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
f = os.open_file(tfile, 'r') ?
|
||||||
|
mut br := []byte{len: 100}
|
||||||
|
// Read first 100 bytes of 200 byte file, should fill buffer with no error.
|
||||||
|
n0 := f.read(mut br) ?
|
||||||
|
assert n0 == 100
|
||||||
|
// Read remaining 100 bytes of 200 byte file, should fill buffer with no
|
||||||
|
// error. The end-of-file isn't reached yet, but there is no more data.
|
||||||
|
n1 := f.read(mut br) ?
|
||||||
|
assert n1 == 100
|
||||||
|
// Read again, end-of-file was previously reached so should return none
|
||||||
|
// error.
|
||||||
|
if _ := f.read(mut br) {
|
||||||
|
// This is not intended behavior because the read function should
|
||||||
|
// not return a number of bytes read when end-of-file is reached.
|
||||||
|
assert false
|
||||||
|
} else {
|
||||||
|
// Expect none to have been returned when end-of-file.
|
||||||
|
assert err is none
|
||||||
|
}
|
||||||
|
f.close()
|
||||||
|
}
|
||||||
|
|
||||||
fn test_write_struct() ? {
|
fn test_write_struct() ? {
|
||||||
os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w'
|
os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w'
|
||||||
size_of_point := int(sizeof(Point))
|
size_of_point := int(sizeof(Point))
|
||||||
|
|
|
@ -160,8 +160,13 @@ fn test_write_and_read_bytes() {
|
||||||
// check that trying to read data from EOF doesn't error and returns 0
|
// check that trying to read data from EOF doesn't error and returns 0
|
||||||
mut a := []byte{len: 5}
|
mut a := []byte{len: 5}
|
||||||
nread := file_read.read_bytes_into(5, mut a) or {
|
nread := file_read.read_bytes_into(5, mut a) or {
|
||||||
eprintln(err)
|
n := if err is none {
|
||||||
int(-1)
|
int(0)
|
||||||
|
} else {
|
||||||
|
eprintln(err)
|
||||||
|
int(-1)
|
||||||
|
}
|
||||||
|
n
|
||||||
}
|
}
|
||||||
assert nread == 0
|
assert nread == 0
|
||||||
file_read.close()
|
file_read.close()
|
||||||
|
|
Loading…
Reference in New Issue