1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

os: fix os.read_file and os.read_bytes for 0 sized /proc/ files on Linux (fix #15852) (#15853)

This commit is contained in:
Dominik Pytlewski 2022-09-25 21:54:46 +02:00 committed by GitHub
parent 50820105a1
commit 721328ef58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 35 deletions

View File

@ -122,7 +122,7 @@ fn C.chdir(path &char) int
fn C.rewind(stream &C.FILE) int
fn C.ftell(&C.FILE) int
fn C.ftell(&C.FILE) isize
fn C.stat(&char, voidptr) int

View File

@ -1,5 +1,7 @@
module os
import strings
#include <sys/stat.h> // #include <signal.h>
#include <errno.h>
@ -13,8 +15,6 @@ fn C.readlink(pathname &char, buf &char, bufsiz usize) int
fn C.getline(voidptr, voidptr, voidptr) int
fn C.ftell(fp voidptr) i64
fn C.sigaction(int, voidptr, int) int
fn C.open(&char, int, ...int) int
@ -44,52 +44,76 @@ pub fn read_bytes(path string) ?[]u8 {
defer {
C.fclose(fp)
}
cseek := C.fseek(fp, 0, C.SEEK_END)
if cseek != 0 {
return error('fseek failed')
fsize := find_cfile_size(fp)?
if fsize == 0 {
mut sb := slurp_file_in_builder(fp)?
return unsafe { sb.reuse_as_plain_u8_array() }
}
fsize := C.ftell(fp)
if fsize < 0 {
return error('ftell failed')
}
len := int(fsize)
// On some systems C.ftell can return values in the 64-bit range
// that, when cast to `int`, can result in values below 0.
if i64(len) < fsize {
return error('$fsize cast to int results in ${int(fsize)})')
}
C.rewind(fp)
mut res := []u8{len: len}
nr_read_elements := int(C.fread(res.data, len, 1, fp))
mut res := []u8{len: fsize}
nr_read_elements := int(C.fread(res.data, 1, fsize, fp))
if nr_read_elements == 0 && fsize > 0 {
return error('fread failed')
}
res.trim(nr_read_elements * len)
res.trim(nr_read_elements)
return res
}
fn find_cfile_size(fp &C.FILE) ?int {
// NB: Musl's fseek returns -1 for virtual files, while Glibc's fseek returns 0
cseek := C.fseek(fp, 0, C.SEEK_END)
raw_fsize := C.ftell(fp)
if raw_fsize != 0 && cseek != 0 {
return error('fseek failed')
}
if cseek != 0 && raw_fsize < 0 {
return error('ftell failed')
}
len := int(raw_fsize)
// For files > 2GB, C.ftell can return values that, when cast to `int`, can result in values below 0.
if i64(len) < raw_fsize {
return error('int($raw_fsize) cast results in $len')
}
C.rewind(fp)
return len
}
const buf_size = 4096
// slurp_file_in_builder reads an entire file into a strings.Builder chunk by chunk, without relying on its file size.
// It is intended for reading 0 sized files, or a dynamic files in a virtual filesystem like /proc/cpuinfo.
// For these, we can not allocate all memory in advance (since we do not know the final size), and so we have no choice
// but to read the file in `buf_size` chunks.
[manualfree]
fn slurp_file_in_builder(fp &C.FILE) ?strings.Builder {
buf := [os.buf_size]u8{}
mut sb := strings.new_builder(os.buf_size)
for {
mut read_bytes := fread(&buf[0], 1, os.buf_size, fp) or {
if err is none {
break
}
unsafe { sb.free() }
return err
}
unsafe { sb.write_ptr(&buf[0], read_bytes) }
}
return sb
}
// read_file reads the file in `path` and returns the contents.
[manualfree]
pub fn read_file(path string) ?string {
mode := 'rb'
mut fp := vfopen(path, mode)?
defer {
C.fclose(fp)
}
cseek := C.fseek(fp, 0, C.SEEK_END)
if cseek != 0 {
return error('fseek failed')
}
fsize := C.ftell(fp)
if fsize < 0 {
return error('ftell failed')
}
// C.fseek(fp, 0, SEEK_SET) // same as `C.rewind(fp)` below
C.rewind(fp)
allocate := int(fsize)
// On some systems C.ftell can return values in the 64-bit range
// that, when cast to `int`, can result in values below 0.
if i64(allocate) < fsize {
return error('$fsize cast to int results in ${int(fsize)})')
allocate := find_cfile_size(fp)?
if allocate == 0 {
mut sb := slurp_file_in_builder(fp)?
res := sb.str()
unsafe { sb.free() }
return res
}
unsafe {
mut str := malloc_noscan(allocate + 1)

View File

@ -47,6 +47,29 @@ fn test_open_file() {
os.rm(filename) or { panic(err) }
}
fn test_read_file_from_virtual_filesystem() {
$if linux {
mounts := os.read_file('/proc/mounts')?
// it is not empty, contains some mounting such as root filesystem: /dev/x / ext4 rw 0 0
assert mounts.len > 20
assert mounts.contains('/')
assert mounts.contains(' ')
}
}
fn test_read_binary_from_virtual_filesystem() {
$if linux {
mounts_raw := os.read_bytes('/proc/mounts')?
mounts := mounts_raw.bytestr()
// it is not empty, contains some mounting such as root filesystem: /dev/x / ext4 rw 0 0
assert mounts.len > 20
assert mounts.contains('/')
assert mounts.contains(' ')
}
}
fn test_open_file_binary() {
filename := './test1.dat'
hello := 'hello \n world!'
@ -870,3 +893,36 @@ fn test_command() {
// dump( cmd_to_fail )
assert cmd_to_fail.exit_code != 0 // 2 on linux, 1 on macos
}
fn test_reading_from_proc_cpuinfo() {
// This test is only for plain linux systems (they have a /proc virtual filesystem).
$if android {
assert true
return
}
$if !linux {
assert true
return
}
info := os.read_file('/proc/cpuinfo')?
assert info.len > 0
assert info.contains('processor')
assert info.ends_with('\n\n')
info_bytes := os.read_bytes('/proc/cpuinfo')?
assert info_bytes.len > 0
assert info.len == info_bytes.len
}
fn test_reading_from_empty_file() {
empty_file := os.join_path_single(tfolder, 'empty_file.txt')
os.rm(empty_file) or {}
assert !os.exists(empty_file)
os.write_file(empty_file, '')?
assert os.exists(empty_file)
content := os.read_file(empty_file)?
assert content.len == 0
content_bytes := os.read_bytes(empty_file)?
assert content_bytes.len == 0
os.rm(empty_file)?
}

View File

@ -16,6 +16,17 @@ pub fn new_builder(initial_size int) Builder {
return res
}
// reuse_as_plain_u8_array allows using the Builder instance as a plain []u8 return value.
// It is useful, when you have accumulated data in the builder, that you want to
// pass/access as []u8 later, without copying or freeing the buffer.
// NB: you *should NOT use* the string builder instance after calling this method.
// Use only the return value after calling this method.
[unsafe]
pub fn (mut b Builder) reuse_as_plain_u8_array() []u8 {
unsafe { b.flags.clear(.noslices) }
return *b
}
// write_ptr writes `len` bytes provided byteptr to the accumulated buffer
[unsafe]
pub fn (mut b Builder) write_ptr(ptr &u8, len int) {