Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

os: refactor to use os.stat and os.lstat instead of unsafe C calls #20759

Merged
merged 6 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 10 additions & 69 deletions vlib/os/inode.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -60,75 +60,16 @@ pub fn (m FileMode) bitmask() u32 {

// inode returns the metadata of the file/inode, containing inode type, permission information, size and modification time.
// it supports windows for regular files, but it doesn't matter if you use owner, group or others when checking permissions on windows.
// if a symlink is targetted, it returns info on the link, not the target
pub fn inode(path string) FileInfo {
mut attr := C.stat{}
$if windows {
// TODO: replace this with a C.GetFileAttributesW call instead.
// Use stat, lstat is not available on windows
unsafe { C.stat(&char(path.str), &attr) }
mut typ := FileType.regular
if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFDIR) {
typ = .directory
}
return FileInfo{
typ: typ
size: attr.st_size
mtime: attr.st_mtime
owner: FilePermission{
read: (attr.st_mode & u32(C.S_IREAD)) != 0
write: (attr.st_mode & u32(C.S_IWRITE)) != 0
execute: (attr.st_mode & u32(C.S_IEXEC)) != 0
}
group: FilePermission{
read: (attr.st_mode & u32(C.S_IREAD)) != 0
write: (attr.st_mode & u32(C.S_IWRITE)) != 0
execute: (attr.st_mode & u32(C.S_IEXEC)) != 0
}
others: FilePermission{
read: (attr.st_mode & u32(C.S_IREAD)) != 0
write: (attr.st_mode & u32(C.S_IWRITE)) != 0
execute: (attr.st_mode & u32(C.S_IEXEC)) != 0
}
}
} $else {
// note, that we use lstat here on purpose, to know the information about
// the potential symlinks themselves, not about the entities they point at
unsafe { C.lstat(&char(path.str), &attr) }
mut typ := FileType.unknown
if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFREG) {
typ = .regular
} else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFDIR) {
typ = .directory
} else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFCHR) {
typ = .character_device
} else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFBLK) {
typ = .block_device
} else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFIFO) {
typ = .fifo
} else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFLNK) {
typ = .symbolic_link
} else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFSOCK) {
typ = .socket
}
return FileInfo{
typ: typ
size: attr.st_size
mtime: attr.st_mtime
owner: FilePermission{
read: (attr.st_mode & u32(C.S_IRUSR)) != 0
write: (attr.st_mode & u32(C.S_IWUSR)) != 0
execute: (attr.st_mode & u32(C.S_IXUSR)) != 0
}
group: FilePermission{
read: (attr.st_mode & u32(C.S_IRGRP)) != 0
write: (attr.st_mode & u32(C.S_IWGRP)) != 0
execute: (attr.st_mode & u32(C.S_IXGRP)) != 0
}
others: FilePermission{
read: (attr.st_mode & u32(C.S_IROTH)) != 0
write: (attr.st_mode & u32(C.S_IWOTH)) != 0
execute: (attr.st_mode & u32(C.S_IXOTH)) != 0
}
}
attr := lstat(path) or { Stat{} }
fm := attr.get_mode()
return FileInfo{
typ: fm.typ
owner: fm.owner
group: fm.group
others: fm.others
size: attr.size
mtime: attr.mtime
}
}
146 changes: 12 additions & 134 deletions vlib/os/os.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -169,52 +169,15 @@ pub fn truncate(path string, len u64) ! {
}
}

fn eprintln_unknown_file_size() {
eprintln('os.file_size() Cannot determine file-size: ' + posix_get_error_msg(C.errno))
}

// file_size returns the size of the file located in `path`.
// If an error occurs it returns 0.
// Note that use of this on symbolic links on Windows returns always 0.
pub fn file_size(path string) u64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check, if that works with files larger than 4GB on windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed; works on 32- and 64-bit Windows

mut s := C.stat{}
unsafe {
$if x64 {
$if windows {
mut swin := C.__stat64{}
if C._wstat64(path.to_wide(), voidptr(&swin)) != 0 {
eprintln_unknown_file_size()
return 0
}
return swin.st_size
} $else {
if C.stat(&char(path.str), &s) != 0 {
eprintln_unknown_file_size()
return 0
}
return u64(s.st_size)
}
}
$if x32 {
$if debug {
eprintln('Using os.file_size() on 32bit systems may not work on big files.')
}
$if windows {
if C._wstat(path.to_wide(), voidptr(&s)) != 0 {
eprintln_unknown_file_size()
return 0
}
return u64(s.st_size)
} $else {
if C.stat(&char(path.str), &s) != 0 {
eprintln_unknown_file_size()
return 0
}
return u64(s.st_size)
}
}
attr := stat(path) or {
eprintln('os.file_size() Cannot determine file-size: ' + posix_get_error_msg(C.errno))
return 0
}
return 0
return attr.size
}

// rename_dir renames the folder from `src` to `dst`.
Expand Down Expand Up @@ -291,11 +254,8 @@ pub fn cp(src string, dst string) ! {
return error_with_code('cp: failed to write to ${dst}', int(-1))
}
}
from_attr := C.stat{}
unsafe {
C.stat(&char(src.str), &from_attr)
}
if C.chmod(&char(dst.str), from_attr.st_mode) < 0 {
from_attr := stat(src)!
if C.chmod(&char(dst.str), from_attr.mode) < 0 {
C.close(fp_to)
C.close(fp_from)
return error_with_code('failed to set permissions for ${dst}', int(-1))
Expand Down Expand Up @@ -460,13 +420,8 @@ pub fn is_executable(path string) bool {
return exists(p) && (p.ends_with('.exe') || p.ends_with('.bat') || p.ends_with('.cmd'))
}
$if solaris {
statbuf := C.stat{}
unsafe {
if C.stat(&char(path.str), &statbuf) != 0 {
return false
}
}
return (int(statbuf.st_mode) & (s_ixusr | s_ixgrp | s_ixoth)) != 0
attr := stat(path) or { return false }
return (int(attr.mode) & (s_ixusr | s_ixgrp | s_ixoth)) != 0
}
return C.access(&char(path.str), x_ok) != -1
}
Expand Down Expand Up @@ -783,88 +738,13 @@ pub fn executable() string {
return executable_fallback()
}

// is_dir returns a `bool` indicating whether the given `path` is a directory.
pub fn is_dir(path string) bool {
$if windows {
w_path := path.replace('/', '\\')
attr := C.GetFileAttributesW(w_path.to_wide())
if attr == u32(C.INVALID_FILE_ATTRIBUTES) {
return false
}
if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 {
return true
}
return false
} $else {
statbuf := C.stat{}
if unsafe { C.stat(&char(path.str), &statbuf) } != 0 {
return false
}
// ref: https://code.woboq.org/gcc/include/sys/stat.h.html
val := int(statbuf.st_mode) & s_ifmt
return val == s_ifdir
}
}

// is_link returns a boolean indicating whether `path` is a link.
// Warning: `is_link()` is known to cause a TOCTOU vulnerability when used incorrectly
// (for more information: https://github.com/vlang/v/blob/master/vlib/os/README.md)
pub fn is_link(path string) bool {
$if windows {
path_ := path.replace('/', '\\')
attr := C.GetFileAttributesW(path_.to_wide())
return int(attr) != int(C.INVALID_FILE_ATTRIBUTES) && (attr & 0x400) != 0
} $else {
statbuf := C.stat{}
if C.lstat(&char(path.str), &statbuf) != 0 {
return false
}
return int(statbuf.st_mode) & s_ifmt == s_iflnk
}
}

struct PathKind {
mut:
is_file bool
is_dir bool
is_link bool
}

fn kind_of_existing_path(path string) PathKind {
mut res := PathKind{}
$if windows {
attr := C.GetFileAttributesW(path.to_wide())
if attr != u32(C.INVALID_FILE_ATTRIBUTES) {
if (int(attr) & C.FILE_ATTRIBUTE_NORMAL) != 0 {
res.is_file = true
}
if (int(attr) & C.FILE_ATTRIBUTE_DIRECTORY) != 0 {
res.is_dir = true
}
if (int(attr) & 0x400) != 0 {
res.is_link = true
}
}
} $else {
statbuf := C.stat{}
// ref: https://code.woboq.org/gcc/include/sys/stat.h.html
res_stat := unsafe { C.lstat(&char(path.str), &statbuf) }
if res_stat == 0 {
kind := (int(statbuf.st_mode) & s_ifmt)
if kind == s_ifreg {
res.is_file = true
}
if kind == s_ifdir {
res.is_dir = true
}
if kind == s_iflnk {
res.is_link = true
}
}
}
return res
}

// chdir changes the current working directory to the new directory in `path`.
pub fn chdir(path string) ! {
ret := $if windows { C._wchdir(path.to_wide()) } $else { C.chdir(&char(path.str)) }
Expand Down Expand Up @@ -1005,12 +885,10 @@ pub fn wait() int {

// file_last_mod_unix returns the "last modified" time stamp of file in `path`.
pub fn file_last_mod_unix(path string) i64 {
attr := C.stat{}
// # struct stat attr;
unsafe { C.stat(&char(path.str), &attr) }
// # stat(path.str, &attr);
return i64(attr.st_mtime)
// # return attr.st_mtime ;
if attr := stat(path) {
return attr.mtime
}
return 0
}

// flush will flush the stdout buffer.
Expand Down
23 changes: 12 additions & 11 deletions vlib/os/os.v
Original file line number Diff line number Diff line change
Expand Up @@ -978,17 +978,18 @@ pub fn config_dir() !string {
return error('Cannot find config directory')
}

// Stat struct modeled on POSIX
pub struct Stat {
pub:
dev u64
inode u64
mode u32
nlink u64
uid u32
gid u32
rdev u64
size u64
atime i64
mtime i64
ctime i64
dev u64 // ID of device containing file
inode u64 // Inode number
mode u32 // File type and user/group/world permission bits
nlink u64 // Number of hard links to file
uid u32 // Owner user ID
gid u32 // Owner group ID
rdev u64 // Device ID (if special file)
size u64 // Total size in bytes
atime i64 // Last access (seconds since UNIX epoch)
mtime i64 // Last modified (seconds since UNIX epoch)
ctime i64 // Last status change (seconds since UNIX epoch)
}
9 changes: 3 additions & 6 deletions vlib/os/os_nix.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -519,18 +519,15 @@ pub fn getegid() int {

// Turns the given bit on or off, depending on the `enable` parameter
pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) {
mut s := C.stat{}
mut new_mode := u32(0)
path := &char(path_s.str)
unsafe {
C.stat(path, &s)
new_mode = s.st_mode
if s := stat(path_s) {
new_mode = s.mode
}
match enable {
true { new_mode |= mode }
false { new_mode &= (0o7777 - mode) }
}
C.chmod(path, int(new_mode))
C.chmod(&char(path_s.str), int(new_mode))
}

// get_long_path has no meaning for *nix, but has for windows, where `c:\folder\some~1` for example
Expand Down
53 changes: 52 additions & 1 deletion vlib/os/os_stat_default.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,36 @@ module os

// stat returns a platform-agnostic Stat struct comparable to what is
// available in other programming languages and fails with the POSIX
// error if the stat call fails. Symlinks are followed and the resulting
// Stat provided. (If this is not desired, use lstat().)
pub fn stat(path string) !Stat {
mut s := C.stat{}
unsafe {
res := C.stat(&char(path.str), &s)
if res != 0 {
return error_posix()
}
return Stat{
dev: s.st_dev
inode: s.st_ino
nlink: s.st_nlink
mode: s.st_mode
uid: s.st_uid
gid: s.st_gid
rdev: s.st_rdev
size: s.st_size
atime: s.st_atime
mtime: s.st_mtime
ctime: s.st_ctime
}
}
}

// lstat returns a platform-agnostic Stat struct comparable to what is
// available in other programming languages and fails with the POSIX
// error if the stat call fails. If a link is stat'd, the stat info
// for the link is provided.
pub fn stat(path string) !Stat {
pub fn lstat(path string) !Stat {
mut s := C.stat{}
unsafe {
res := C.lstat(&char(path.str), &s)
Expand Down Expand Up @@ -79,3 +106,27 @@ pub fn (st Stat) get_mode() FileMode {
}
}
}

// is_dir returns a `bool` indicating whether the given `path` is a directory.
pub fn is_dir(path string) bool {
attr := stat(path) or { return false }
return attr.get_filetype() == .directory
}

// is_link returns a boolean indicating whether `path` is a link.
// Warning: `is_link()` is known to cause a TOCTOU vulnerability when used incorrectly
// (for more information: https://github.com/vlang/v/blob/master/vlib/os/README.md)
pub fn is_link(path string) bool {
attr := lstat(path) or { return false }
return attr.get_filetype() == .symbolic_link
}

// kind_of_existing_path identifies whether path is a file, directory, or link
fn kind_of_existing_path(path string) PathKind {
mut res := PathKind{}
attr := lstat(path) or { return res }
res.is_file = attr.get_filetype() == .regular
res.is_dir = attr.get_filetype() == .directory
res.is_link = attr.get_filetype() == .symbolic_link
return res
}
Loading
Loading