From 99abd46ac9438cee4bc22bf2f0b6e94d5520a8ef Mon Sep 17 00:00:00 2001 From: Enzo Date: Wed, 10 Mar 2021 17:45:12 +0100 Subject: [PATCH] os: add `(read|write)_raw[_at]` to File (#9171) --- vlib/os/file.c.v | 97 +++++++++++++++++++++++- vlib/os/file_test.v | 174 ++++++++++++++++++++++++++++++++++++-------- vlib/os/os.v | 6 +- vlib/os/os_c.v | 2 +- 4 files changed, 241 insertions(+), 38 deletions(-) diff --git a/vlib/os/file.c.v b/vlib/os/file.c.v index fe08b48788..4ea3af8810 100644 --- a/vlib/os/file.c.v +++ b/vlib/os/file.c.v @@ -358,12 +358,60 @@ pub fn (mut f File) read_struct_at(mut t T, pos int) ? { } } +// read_raw reads and returns a single instance of type `T` +pub fn (mut f File) read_raw() ?T { + if !f.is_opened { + return none + } + tsize := int(sizeof(T)) + if tsize == 0 { + return none + } + C.errno = 0 + mut t := T{} + nbytes := int(C.fread(&t, 1, tsize, f.cfile)) + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if nbytes != tsize { + return error_with_code('incomplete struct read', nbytes) + } + return t +} + +// read_raw_at reads and returns a single instance of type `T` starting at file byte offset `pos` +pub fn (mut f File) read_raw_at(pos int) ?T { + if !f.is_opened { + return none + } + tsize := int(sizeof(T)) + if tsize == 0 { + return none + } + C.errno = 0 + if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + mut t := T{} + nbytes := int(C.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 { + return error(posix_get_error_msg(C.errno)) + } + if nbytes != tsize { + return error_with_code('incomplete struct read', nbytes) + } + return t +} + // write_struct writes a single struct of type `T` pub fn (mut f File) write_struct(t &T) ? { if !f.is_opened { return error('file is not opened') } - tsize := int(sizeof(*t)) + tsize := int(sizeof(T)) if tsize == 0 { return error('struct size is 0') } @@ -382,7 +430,7 @@ pub fn (mut f File) write_struct_at(t &T, pos int) ? { if !f.is_opened { return error('file is not opened') } - tsize := int(sizeof(*t)) + tsize := int(sizeof(T)) if tsize == 0 { return error('struct size is 0') } @@ -397,3 +445,48 @@ pub fn (mut f File) write_struct_at(t &T, pos int) ? { return error_with_code('incomplete struct write', nbytes) } } + +// TODO `write_raw[_at]` implementations are copy-pasted from `write_struct[_at]` + +// write_raw writes a single instance of type `T` +pub fn (mut f File) write_raw(t &T) ? { + if !f.is_opened { + return error('file is not opened') + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error('struct size is 0') + } + C.errno = 0 + nbytes := int(C.fwrite(t, 1, tsize, f.cfile)) + if C.errno != 0 { + return error(posix_get_error_msg(C.errno)) + } + if nbytes != tsize { + return error_with_code('incomplete struct write', nbytes) + } +} + +// write_raw_at writes a single instance of type `T` starting at file byte offset `pos` +pub fn (mut f File) write_raw_at(t &T, pos int) ? { + if !f.is_opened { + return error('file is not opened') + } + tsize := int(sizeof(T)) + if tsize == 0 { + return error('struct size is 0') + } + if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 { + return error(posix_get_error_msg(C.errno)) + } + nbytes := int(C.fwrite(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 { + return error(posix_get_error_msg(C.errno)) + } + if nbytes != tsize { + return error_with_code('incomplete struct write', nbytes) + } +} diff --git a/vlib/os/file_test.v b/vlib/os/file_test.v index 0e7bfb6562..4403f7f603 100644 --- a/vlib/os/file_test.v +++ b/vlib/os/file_test.v @@ -18,36 +18,54 @@ struct Extended_Point { i f64 } -const unit_point = Point{1.0, 1.0, 1.0} +enum Color { + red + green + blue +} -const tfolder = os.join_path(os.temp_dir(), 'os_file_test') +[flag] +enum Permissions { + read + write + execute +} -const tfile = os.join_path(tfolder, 'test_file') +const ( + unit_point = Point{1.0, 1.0, 1.0} + another_point = Point{0.25, 2.25, 6.25} + extended_point = Extended_Point{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} + another_byte = byte(123) + another_color = Color.red + another_permission = Permissions.read | .write +) -const another_point = Point{0.25, 2.25, 6.25} +const ( + tfolder = os.join_path(os.temp_dir(), 'os_file_test') + tfile = os.join_path(tfolder, 'test_file') +) -const extended_point = Extended_Point{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} - -fn testsuite_begin() { +fn testsuite_begin() ? { os.rmdir_all(tfolder) or {} assert !os.is_dir(tfolder) - os.mkdir_all(tfolder) or { panic(err) } + os.mkdir_all(tfolder) ? os.chdir(tfolder) assert os.is_dir(tfolder) } -fn testsuite_end() { +fn testsuite_end() ? { os.chdir(os.wd_at_startup) - os.rmdir_all(tfolder) or { panic(err) } + os.rmdir_all(tfolder) ? assert !os.is_dir(tfolder) } -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' size_of_point := int(sizeof(Point)) - mut f := os.open_file(tfile, 'w') or { panic(err) } - f.write_struct(another_point) or { panic(err) } + mut f := os.open_file(tfile, 'w') ? + f.write_struct(another_point) ? f.close() - x := os.read_file(tfile) or { panic(err) } + x := os.read_file(tfile) ? y := unsafe { byteptr(memdup(&another_point, size_of_point)).vstring_with_len(size_of_point) } assert x == y $if debug { @@ -56,41 +74,133 @@ fn test_write_struct() { } } -fn test_read_struct() { - mut f := os.open_file(tfile, 'w') or { panic(err) } - f.write_struct(another_point) or { panic(err) } +fn test_write_struct_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_struct(extended_point) ? + f.write_struct_at(another_point, 3) ? f.close() - - f = os.open_file(tfile, 'r') or { panic(err) } + f = os.open_file(tfile, 'r') ? mut p := Point{} - f.read_struct(mut p) or { panic(err) } + f.read_struct_at(mut p, 3) ? f.close() assert p == another_point } -fn test_read_struct_at() { - mut f := os.open_file(tfile, 'w') or { panic(err) } - f.write([byte(1), 2, 3]) or { panic(err) } - f.write_struct(another_point) or { panic(err) } +fn test_read_struct() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_struct(another_point) ? f.close() - f = os.open_file(tfile, 'r') or { panic(err) } + + f = os.open_file(tfile, 'r') ? mut p := Point{} - f.read_struct_at(mut p, 3) or { panic(err) } + f.read_struct(mut p) ? f.close() assert p == another_point } -fn test_write_struct_at() { - mut f := os.open_file(tfile, 'w') or { panic(err) } - f.write_struct(extended_point) or { panic(err) } - f.write_struct_at(another_point, 3) or { panic(err) } +fn test_read_struct_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write([byte(1), 2, 3]) ? + f.write_struct(another_point) ? f.close() - f = os.open_file(tfile, 'r') or { panic(err) } + f = os.open_file(tfile, 'r') ? mut p := Point{} - f.read_struct_at(mut p, 3) or { panic(err) } + f.read_struct_at(mut p, 3) ? f.close() assert p == another_point } + +fn test_write_raw() ? { + 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)) + mut f := os.open_file(tfile, 'w') ? + f.write_raw(another_point) ? + f.close() + x := os.read_file(tfile) ? + y := unsafe { byteptr(memdup(&another_point, size_of_point)).vstring_with_len(size_of_point) } + assert x == y + $if debug { + eprintln(x.bytes()) + eprintln(y.bytes()) + } +} + +fn test_write_raw_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_raw(extended_point) ? + f.write_raw_at(another_point, 3) ? + f.close() + f = os.open_file(tfile, 'r') ? + mut p := Point{} + f.read_struct_at(mut p, 3) ? + f.close() + + assert p == another_point +} + +fn test_write_raw_at_negative_pos() ? { + mut f := os.open_file(tfile, 'w') ? + if _ := f.write_raw_at(another_point, -1) { + assert false + } + f.write_raw_at(another_point, -234) or { assert err.msg == 'Invalid argument' } + f.close() +} + +fn test_read_raw() ? { + mut f := os.open_file(tfile, 'w') ? + f.write_raw(another_point) ? + f.write_raw(another_byte) ? + f.write_raw(another_color) ? + f.write_raw(another_permission) ? + f.close() + f = os.open_file(tfile, 'r') ? + p := f.read_raw() ? + b := f.read_raw() ? + c := f.read_raw() ? + x := f.read_raw() ? + f.close() + + assert p == another_point + assert b == another_byte + assert c == another_color + assert x == another_permission +} + +fn test_read_raw_at() ? { + mut f := os.open_file(tfile, 'w') ? + f.write([byte(1), 2, 3]) ? + f.write_raw(another_point) ? + f.write_raw(another_byte) ? + f.write_raw(another_color) ? + f.write_raw(another_permission) ? + f.close() + f = os.open_file(tfile, 'r') ? + mut at := 3 + p := f.read_raw_at(at) ? + at += int(sizeof(Point)) + b := f.read_raw_at(at) ? + at += int(sizeof(byte)) + c := f.read_raw_at(at) ? + at += int(sizeof(Color)) + x := f.read_raw_at(at) ? + at += int(sizeof(Permissions)) + f.close() + + assert p == another_point + assert b == another_byte + assert c == another_color + assert x == another_permission +} + +fn test_read_raw_at_negative_pos() ? { + mut f := os.open_file(tfile, 'r') ? + if _ := f.read_raw_at(-1) { + assert false + } + f.read_raw_at(-234) or { assert err.msg == 'Invalid argument' } + f.close() +} diff --git a/vlib/os/os.v b/vlib/os/os.v index a06b61caa0..d7a1e5ac40 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -138,11 +138,11 @@ pub fn rmdir_all(path string) ? { for item in items { fullpath := join_path(path, item) if is_dir(fullpath) { - rmdir_all(fullpath) or { ret_err = err } + rmdir_all(fullpath) or { ret_err = err.msg } } - rm(fullpath) or { ret_err = err } + rm(fullpath) or { ret_err = err.msg } } - rmdir(path) or { ret_err = err } + rmdir(path) or { ret_err = err.msg } if ret_err.len > 0 { return error(ret_err) } diff --git a/vlib/os/os_c.v b/vlib/os/os_c.v index 250cffdf7b..7cd7eaecb0 100644 --- a/vlib/os/os_c.v +++ b/vlib/os/os_c.v @@ -382,7 +382,7 @@ pub fn rmdir(path string) ? { rc := C.RemoveDirectory(path.to_wide()) if rc == 0 { // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya - 0 is failure - return error('Failed to remove "$path"') + return error('Failed to remove "$path": ' + posix_get_error_msg(C.errno)) } } $else { rc := C.rmdir(charptr(path.str))