Skip to content

Commit

Permalink
os: add os.stat() and helpers (#20739)
Browse files Browse the repository at this point in the history
  • Loading branch information
syrmel committed Feb 7, 2024
1 parent ee55e9b commit 205f2dd
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 1 deletion.
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
}

0 comments on commit 205f2dd

Please sign in to comment.