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: add os.stat() and helpers #20739

Merged
merged 6 commits into from Feb 7, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions vlib/os/os.v
Expand Up @@ -977,3 +977,18 @@ pub fn config_dir() !string {
}
return error('Cannot find config directory')
}

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
}
81 changes: 81 additions & 0 deletions vlib/os/os_stat_default.c.v
@@ -0,0 +1,81 @@
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. If a link is stat'd, the stat info
// for the link is provided.
pub fn stat(path string) !Stat {
mut s := C.stat{}
unsafe {
res := C.lstat(&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
}
}
}

// get_filetype returns the FileType from the Stat struct
pub fn (st Stat) get_filetype() FileType {
match st.mode & u32(C.S_IFMT) {
u32(C.S_IFREG) {
return .regular
}
u32(C.S_IFDIR) {
return .directory
}
u32(C.S_IFCHR) {
return .character_device
}
u32(C.S_IFBLK) {
return .block_device
}
u32(C.S_IFIFO) {
return .fifo
}
u32(C.S_IFLNK) {
return .symbolic_link
}
u32(C.S_IFSOCK) {
return .socket
}
else {
return .unknown
}
}
}

// get_mode returns the file type and permissions (readable, writable, executable)
// in owner/group/others format
pub fn (st Stat) get_mode() FileMode {
return FileMode{
typ: st.get_filetype()
owner: FilePermission{
read: (st.mode & u32(C.S_IRUSR)) != 0
write: (st.mode & u32(C.S_IWUSR)) != 0
execute: (st.mode & u32(C.S_IXUSR)) != 0
}
group: FilePermission{
read: (st.mode & u32(C.S_IRGRP)) != 0
write: (st.mode & u32(C.S_IWGRP)) != 0
execute: (st.mode & u32(C.S_IXGRP)) != 0
}
others: FilePermission{
read: (st.mode & u32(C.S_IROTH)) != 0
write: (st.mode & u32(C.S_IWOTH)) != 0
execute: (st.mode & u32(C.S_IXOTH)) != 0
}
}
}
57 changes: 57 additions & 0 deletions vlib/os/os_stat_test.v
@@ -0,0 +1,57 @@
import os
import rand
import time

fn test_stat() {
start_time := time.utc()

temp_dir := os.join_path(os.temp_dir(), rand.ulid())
os.mkdir(temp_dir)!
defer {
os.rmdir(temp_dir) or {}
}

test_file := os.join_path(temp_dir, rand.ulid())
test_content := rand.ulid()
os.write_file(test_file, test_content)!
defer {
os.rm(test_file) or {}
}

end_time := time.utc()

mut fstat := os.stat(test_file)!
assert fstat.get_filetype() == .regular
assert fstat.size == u64(test_content.len)
assert fstat.ctime >= start_time.unix
assert fstat.ctime <= end_time.unix
assert fstat.mtime >= start_time.unix
assert fstat.mtime <= end_time.unix

$if !windows {
os.chmod(test_file, 0o600)!
fstat = os.stat(test_file)!

mut fmode := fstat.get_mode()
assert fmode.typ == .regular
assert fmode.owner.read && fmode.owner.write && !fmode.owner.execute
assert !fmode.group.read && !fmode.group.write && !fmode.group.execute
assert !fmode.others.read && !fmode.others.write && !fmode.others.execute

os.chmod(test_file, 0o421)!
fstat = os.stat(test_file)!
fmode = fstat.get_mode()
assert fmode.owner.read && !fmode.owner.write && !fmode.owner.execute
assert !fmode.group.read && fmode.group.write && !fmode.group.execute
assert !fmode.others.read && !fmode.others.write && fmode.others.execute

os.chmod(test_file, 0o600)!
}

// When using the Time struct, allow for up to 1 second difference due to nanoseconds
// which are not captured in the timestamp
dstat := os.stat(temp_dir)!
assert dstat.get_filetype() == .directory
assert fstat.dev == dstat.dev, 'File and directory should be created on same device'
assert fstat.rdev == dstat.rdev, 'File and directory should have same device ID'
}
63 changes: 63 additions & 0 deletions vlib/os/os_stat_windows.c.v
@@ -0,0 +1,63 @@
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. If a link is stat'd, the stat info
// for the link is provided.
pub fn stat(path string) !Stat {
mut s := C.__stat64{}
unsafe {
res := C._wstat64(path.to_wide(), &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
}
}
}

// get_filetype returns the FileType from the Stat struct
pub fn (st Stat) get_filetype() FileType {
match st.mode & u32(C.S_IFMT) {
u32(C.S_IFDIR) {
return .directory
}
else {
return .regular
}
}
}

// get_mode returns the file type and permissions (readable, writable, executable)
// in owner/group/others format, however, they will all be the same for Windows
pub fn (st Stat) get_mode() FileMode {
return FileMode{
typ: st.get_filetype()
owner: FilePermission{
read: (st.mode & u32(C.S_IREAD)) != 0
write: (st.mode & u32(C.S_IWRITE)) != 0
execute: (st.mode & u32(C.S_IEXEC)) != 0
}
group: FilePermission{
read: (st.mode & u32(C.S_IREAD)) != 0
write: (st.mode & u32(C.S_IWRITE)) != 0
execute: (st.mode & u32(C.S_IEXEC)) != 0
}
others: FilePermission{
read: (st.mode & u32(C.S_IREAD)) != 0
write: (st.mode & u32(C.S_IWRITE)) != 0
execute: (st.mode & u32(C.S_IEXEC)) != 0
}
}
}
12 changes: 11 additions & 1 deletion vlib/os/os_structs_stat_default.c.v
@@ -1,9 +1,19 @@
module os

// Minimal stat struct as specified in
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html
pub struct C.stat {
st_size u64
st_dev u64
st_ino u64
st_mode u32
st_nlink u64
st_uid u32
st_gid u32
st_rdev u64
st_size u64
st_atime int
st_mtime int
st_ctime int
}

pub struct C.__stat64 {
Expand Down
15 changes: 15 additions & 0 deletions vlib/os/os_structs_stat_windows.v
@@ -0,0 +1,15 @@
module os

pub struct C.__stat64 {
st_dev u32 // 4
st_ino u16 // 2
st_mode u16 // 2
st_nlink u16 // 2
st_uid u16 // 2
st_gid u16 // 2
st_rdev u32 // 4
st_size u64 // 8
st_atime i64 // 8
st_mtime i64 // 8
st_ctime i64 // 8
}