Skip to content

Add st_{a,m,c}time to os.stat #962

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

Merged
merged 4 commits into from
May 12, 2019
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
43 changes: 43 additions & 0 deletions tests/snippets/stdlib_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
assert os.read(fd, len(CONTENT3)) == CONTENT3
os.close(fd)

# wait a little bit to ensure that the file times aren't the same
time.sleep(0.1)

fname2 = os.path.join(tmpdir, FILE_NAME2)
with open(fname2, "wb"):
pass
Expand Down Expand Up @@ -143,6 +146,46 @@ def __exit__(self, exc_type, exc_val, exc_tb):
print(stat_res.st_gid)
print(stat_res.st_size)
assert stat_res.st_size == len(CONTENT2) + len(CONTENT3)
print(stat_res.st_atime)
print(stat_res.st_ctime)
print(stat_res.st_mtime)
# test that it all of these times are greater than the 10 May 2019, when this test was written
assert stat_res.st_atime > 1557500000
assert stat_res.st_ctime > 1557500000
assert stat_res.st_mtime > 1557500000

stat_file2 = os.stat(fname2)
print(stat_file2.st_ctime)
assert stat_file2.st_ctime > stat_res.st_ctime

# wait a little bit to ensures that the access/modify time will change
time.sleep(0.1)

old_atime = stat_res.st_atime
old_mtime = stat_res.st_mtime

fd = os.open(fname, os.O_RDWR)
os.write(fd, CONTENT)
os.fsync(fd)

# wait a little bit to ensures that the access/modify time is different
time.sleep(0.1)

os.read(fd, 1)
os.fsync(fd)
os.close(fd)

# retrieve update file stats
stat_res = os.stat(fname)
print(stat_res.st_atime)
print(stat_res.st_ctime)
print(stat_res.st_mtime)
if os.name != "nt":
# access time on windows has a resolution ranging from 1 hour to 1 day
# https://docs.microsoft.com/en-gb/windows/desktop/api/minwinbase/ns-minwinbase-filetime
assert stat_res.st_atime > old_atime, "Access time should be update"
assert stat_res.st_atime > stat_res.st_mtime
assert stat_res.st_mtime > old_mtime, "Modified time should be update"

# stat default is follow_symlink=True
os.stat(fname).st_ino == os.stat(symlink_file).st_ino
Expand Down
158 changes: 117 additions & 41 deletions vm/src/stdlib/os.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::cell::RefCell;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::{ErrorKind, Read, Write};
use std::io::{self, ErrorKind, Read, Write};
use std::time::{Duration, SystemTime};
use std::{env, fs};

use num_traits::cast::ToPrimitive;
Expand Down Expand Up @@ -88,6 +89,7 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
let handle = match objint::get_value(mode).to_u16().unwrap() {
0 => OpenOptions::new().read(true).open(&fname),
1 => OpenOptions::new().write(true).open(&fname),
2 => OpenOptions::new().read(true).write(true).open(&fname),
512 => OpenOptions::new().write(true).create(true).open(&fname),
_ => OpenOptions::new().read(true).open(&fname),
}
Expand Down Expand Up @@ -123,6 +125,15 @@ fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
Err(vm.new_os_error(msg))
}

fn os_fsync(fd: PyIntRef, vm: &VirtualMachine) -> PyResult<()> {
let file = rust_file(fd.as_bigint().to_i64().unwrap());
file.sync_all()
.map_err(|s| vm.new_os_error(s.to_string()))?;
// Avoid closing the fd
raw_file_number(file);
Ok(())
}

fn os_read(fd: PyIntRef, n: PyIntRef, vm: &VirtualMachine) -> PyResult {
let mut buffer = vec![0u8; n.as_bigint().to_usize().unwrap()];
let mut file = rust_file(fd.as_bigint().to_i64().unwrap());
Expand Down Expand Up @@ -261,7 +272,7 @@ impl DirEntryRef {
.is_symlink())
}

fn stat(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult {
fn stat(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult<StatResult> {
os_stat(self.path(vm).try_into_ref(vm)?, follow_symlinks, vm)
}
}
Expand Down Expand Up @@ -317,6 +328,9 @@ struct StatResult {
st_uid: u32,
st_gid: u32,
st_size: u64,
st_atime: f64,
st_ctime: f64,
st_mtime: f64,
}

impl PyValue for StatResult {
Expand Down Expand Up @@ -355,47 +369,94 @@ impl StatResultRef {
fn st_size(self, _vm: &VirtualMachine) -> u64 {
self.st_size
}

fn st_atime(self, _vm: &VirtualMachine) -> f64 {
self.st_atime
}

fn st_ctime(self, _vm: &VirtualMachine) -> f64 {
self.st_ctime
}

fn st_mtime(self, _vm: &VirtualMachine) -> f64 {
self.st_mtime
}
}

// Copied code from Duration::as_secs_f64 as it's still unstable
fn duration_as_secs_f64(duration: Duration) -> f64 {
(duration.as_secs() as f64) + (duration.subsec_nanos() as f64) / (1_000_000_000 as f64)
}

fn to_seconds_from_unix_epoch(sys_time: SystemTime) -> f64 {
match sys_time.duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => duration_as_secs_f64(duration),
Err(err) => -duration_as_secs_f64(err.duration()),
}
}

#[cfg(unix)]
macro_rules! os_unix_stat_inner {
( $path:expr, $follow_symlinks:expr, $vm:expr) => {{
let metadata = match $follow_symlinks.follow_symlinks {
true => fs::metadata($path),
false => fs::symlink_metadata($path),
};
let meta = metadata.map_err(|s| $vm.new_os_error(s.to_string()))?;
fn to_seconds_from_nanos(secs: i64, nanos: i64) -> f64 {
let duration = Duration::new(secs as u64, nanos as u32);
duration_as_secs_f64(duration)
}

Ok(StatResult {
st_mode: meta.st_mode(),
st_ino: meta.st_ino(),
st_dev: meta.st_dev(),
st_nlink: meta.st_nlink(),
st_uid: meta.st_uid(),
st_gid: meta.st_gid(),
st_size: meta.st_size(),
#[cfg(unix)]
macro_rules! os_unix_stat_inner {
( $path:expr, $follow_symlinks:expr, $vm:expr ) => {{
fn get_stats(path: &str, follow_symlinks: bool) -> io::Result<StatResult> {
let meta = match follow_symlinks {
true => fs::metadata(path)?,
false => fs::symlink_metadata(path)?,
};

Ok(StatResult {
st_mode: meta.st_mode(),
st_ino: meta.st_ino(),
st_dev: meta.st_dev(),
st_nlink: meta.st_nlink(),
st_uid: meta.st_uid(),
st_gid: meta.st_gid(),
st_size: meta.st_size(),
st_atime: to_seconds_from_unix_epoch(meta.accessed()?),
st_mtime: to_seconds_from_unix_epoch(meta.modified()?),
st_ctime: to_seconds_from_nanos(meta.st_ctime(), meta.st_ctime_nsec()),
})
}
.into_ref($vm)
.into_object())

get_stats(&$path.value, $follow_symlinks.follow_symlinks)
.map_err(|s| $vm.new_os_error(s.to_string()))
}};
}

#[cfg(target_os = "linux")]
fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult {
fn os_stat(
path: PyStringRef,
follow_symlinks: FollowSymlinks,
vm: &VirtualMachine,
) -> PyResult<StatResult> {
use std::os::linux::fs::MetadataExt;
os_unix_stat_inner!(&path.value, follow_symlinks, vm)
os_unix_stat_inner!(path, follow_symlinks, vm)
}

#[cfg(target_os = "macos")]
fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult {
fn os_stat(
path: PyStringRef,
follow_symlinks: FollowSymlinks,
vm: &VirtualMachine,
) -> PyResult<StatResult> {
use std::os::macos::fs::MetadataExt;
os_unix_stat_inner!(&path.value, follow_symlinks, vm)
os_unix_stat_inner!(path, follow_symlinks, vm)
}

#[cfg(target_os = "android")]
fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult {
fn os_stat(
path: PyStringRef,
follow_symlinks: FollowSymlinks,
vm: &VirtualMachine,
) -> PyResult<StatResult> {
use std::os::android::fs::MetadataExt;
os_unix_stat_inner!(&path.value, follow_symlinks, vm)
os_unix_stat_inner!(path, follow_symlinks, vm)
}

// Copied from CPython fileutils.c
Expand All @@ -420,24 +481,35 @@ fn attributes_to_mode(attr: u32) -> u32 {
}

#[cfg(windows)]
fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult {
fn os_stat(
path: PyStringRef,
follow_symlinks: FollowSymlinks,
vm: &VirtualMachine,
) -> PyResult<StatResult> {
use std::os::windows::fs::MetadataExt;
let metadata = match follow_symlinks.follow_symlinks {
true => fs::metadata(&path.value),
false => fs::symlink_metadata(&path.value),
};
let meta = metadata.map_err(|s| vm.new_os_error(s.to_string()))?;
Ok(StatResult {
st_mode: attributes_to_mode(meta.file_attributes()),
st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt.
st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt.
st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt.
st_uid: 0, // 0 on windows
st_gid: 0, // 0 on windows
st_size: meta.file_size(),

fn get_stats(path: &str, follow_symlinks: bool) -> io::Result<StatResult> {
let meta = match follow_symlinks {
true => fs::metadata(path)?,
false => fs::symlink_metadata(path)?,
};

Ok(StatResult {
st_mode: attributes_to_mode(meta.file_attributes()),
st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt.
st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt.
st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt.
st_uid: 0, // 0 on windows
st_gid: 0, // 0 on windows
st_size: meta.file_size(),
st_atime: to_seconds_from_unix_epoch(meta.accessed()?),
st_mtime: to_seconds_from_unix_epoch(meta.modified()?),
st_ctime: to_seconds_from_unix_epoch(meta.created()?),
})
}
.into_ref(vm)
.into_object())

get_stats(&path.value, follow_symlinks.follow_symlinks)
.map_err(|s| vm.new_os_error(s.to_string()))
}

#[cfg(not(any(
Expand Down Expand Up @@ -510,12 +582,16 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
"st_uid" => ctx.new_property(StatResultRef::st_uid),
"st_gid" => ctx.new_property(StatResultRef::st_gid),
"st_size" => ctx.new_property(StatResultRef::st_size),
"st_atime" => ctx.new_property(StatResultRef::st_atime),
"st_ctime" => ctx.new_property(StatResultRef::st_ctime),
"st_mtime" => ctx.new_property(StatResultRef::st_mtime),
});

py_module!(vm, "_os", {
"open" => ctx.new_rustfunc(os_open),
"close" => ctx.new_rustfunc(os_close),
"error" => ctx.new_rustfunc(os_error),
"fsync" => ctx.new_rustfunc(os_fsync),
"read" => ctx.new_rustfunc(os_read),
"write" => ctx.new_rustfunc(os_write),
"remove" => ctx.new_rustfunc(os_remove),
Expand Down