Skip to content

Commit

Permalink
[GR-45621] Add Pathname#lutime method
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/4159
  • Loading branch information
andrykonchin committed Feb 7, 2024
2 parents 577c1dd + bbd1e2f commit 3c22d4a
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 104 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Compatibility:
* Handle either positional or keywords arguments by default in `Struct.new` (#3039, @rwstauner).
* Promote `Set` class to core library (#3039, @andrykonchin).
* Support `connect_timeout` keyword argument to `TCPSocket.{new,open}` (#3421, @manefz, @patricklinpl, @nirvdrum, @rwstauner).
* Add `File.lutime` and `Pathname#lutime` methods (#3039, @andrykonchin).

Performance:

Expand Down
4 changes: 4 additions & 0 deletions lib/truffle/pathname.rb
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,10 @@ def truncate(length) File.truncate(@path, length) end
# See <tt>File.utime</tt>. Update the access and modification times.
def utime(atime, mtime) File.utime(atime, mtime, @path) end

# See <tt>File.lutime</tt>. Update the access and modification times.
# Same as Pathname#utime, but does not follow symbolic links.
def lutime(atime, mtime) File.lutime(atime, mtime, @path) end

# See <tt>File.basename</tt>. Returns the last component of the path.
def basename(*args) self.class.new(File.basename(@path, *args)) end

Expand Down
5 changes: 5 additions & 0 deletions spec/ruby/core/file/lutime_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
require_relative '../../spec_helper'
require_relative 'shared/update_time'

describe "File.lutime" do
it_behaves_like :update_time, :lutime
end

describe "File.lutime" do
platform_is_not :windows do
Expand Down
105 changes: 105 additions & 0 deletions spec/ruby/core/file/shared/update_time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
describe :update_time, shared: true do
before :all do
@time_is_float = platform_is :windows
end

before :each do
@atime = Time.now
@mtime = Time.now
@file1 = tmp("specs_file_utime1")
@file2 = tmp("specs_file_utime2")
touch @file1
touch @file2
end

after :each do
rm_r @file1, @file2
end

it "sets the access and modification time of each file" do
File.send(@method, @atime, @mtime, @file1, @file2)

if @time_is_float
File.atime(@file1).should be_close(@atime, 0.0001)
File.mtime(@file1).should be_close(@mtime, 0.0001)
File.atime(@file2).should be_close(@atime, 0.0001)
File.mtime(@file2).should be_close(@mtime, 0.0001)
else
File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
end
end

it "uses the current times if two nil values are passed" do
tn = Time.now
File.send(@method, nil, nil, @file1, @file2)

if @time_is_float
File.atime(@file1).should be_close(tn, 0.050)
File.mtime(@file1).should be_close(tn, 0.050)
File.atime(@file2).should be_close(tn, 0.050)
File.mtime(@file2).should be_close(tn, 0.050)
else
File.atime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
File.mtime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
File.atime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
File.mtime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
end
end

it "accepts an object that has a #to_path method" do
File.send(@method, @atime, @mtime, mock_to_path(@file1), mock_to_path(@file2))
end

it "accepts numeric atime and mtime arguments" do
if @time_is_float
File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2)

File.atime(@file1).should be_close(@atime, 0.0001)
File.mtime(@file1).should be_close(@mtime, 0.0001)
File.atime(@file2).should be_close(@atime, 0.0001)
File.mtime(@file2).should be_close(@mtime, 0.0001)
else
File.send(@method, @atime.to_i, @mtime.to_i, @file1, @file2)

File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
end
end

it "may set nanosecond precision" do
t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r)
File.send(@method, t, t, @file1)

File.atime(@file1).nsec.should.between?(0, 123500000)
File.mtime(@file1).nsec.should.between?(0, 123500000)
end

it "returns the number of filenames in the arguments" do
File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2).should == 2
end

platform_is :linux do
platform_is wordsize: 64 do
it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do
# https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps
# "Therefore, timestamps should not overflow until May 2446."
# https://lwn.net/Articles/804382/
# "On-disk timestamps hitting the y2038 limit..."
# The problem seems to be being improved, but currently it actually fails on XFS on RHEL8
# https://rubyci.org/logs/rubyci.s3.amazonaws.com/rhel8/ruby-master/log/20201112T123004Z.fail.html.gz
# Amazon Linux 2023 returns 2486-07-02 in this example
# http://rubyci.s3.amazonaws.com/amazon2023/ruby-master/log/20230322T063004Z.fail.html.gz
time = Time.at(1<<44)
File.send(@method, time, time, @file1)

[559444, 2486, 2446, 2038].should.include? File.atime(@file1).year
[559444, 2486, 2446, 2038].should.include? File.mtime(@file1).year
end
end
end
end
100 changes: 2 additions & 98 deletions spec/ruby/core/file/utime_spec.rb
Original file line number Diff line number Diff line change
@@ -1,102 +1,6 @@
require_relative '../../spec_helper'
require_relative 'shared/update_time'

describe "File.utime" do

before :all do
@time_is_float = platform_is :windows
end

before :each do
@atime = Time.now
@mtime = Time.now
@file1 = tmp("specs_file_utime1")
@file2 = tmp("specs_file_utime2")
touch @file1
touch @file2
end

after :each do
rm_r @file1, @file2
end

it "sets the access and modification time of each file" do
File.utime(@atime, @mtime, @file1, @file2)
if @time_is_float
File.atime(@file1).should be_close(@atime, 0.0001)
File.mtime(@file1).should be_close(@mtime, 0.0001)
File.atime(@file2).should be_close(@atime, 0.0001)
File.mtime(@file2).should be_close(@mtime, 0.0001)
else
File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
end
end

it "uses the current times if two nil values are passed" do
tn = Time.now
File.utime(nil, nil, @file1, @file2)
if @time_is_float
File.atime(@file1).should be_close(tn, 0.050)
File.mtime(@file1).should be_close(tn, 0.050)
File.atime(@file2).should be_close(tn, 0.050)
File.mtime(@file2).should be_close(tn, 0.050)
else
File.atime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
File.mtime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
File.atime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
File.mtime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE)
end
end

it "accepts an object that has a #to_path method" do
File.utime(@atime, @mtime, mock_to_path(@file1), mock_to_path(@file2))
end

it "accepts numeric atime and mtime arguments" do
if @time_is_float
File.utime(@atime.to_f, @mtime.to_f, @file1, @file2)
File.atime(@file1).should be_close(@atime, 0.0001)
File.mtime(@file1).should be_close(@mtime, 0.0001)
File.atime(@file2).should be_close(@atime, 0.0001)
File.mtime(@file2).should be_close(@mtime, 0.0001)
else
File.utime(@atime.to_i, @mtime.to_i, @file1, @file2)
File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE)
File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE)
end
end

it "may set nanosecond precision" do
t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r)
File.utime(t, t, @file1)
File.atime(@file1).nsec.should.between?(0, 123500000)
File.mtime(@file1).nsec.should.between?(0, 123500000)
end

it "returns the number of filenames in the arguments" do
File.utime(@atime.to_f, @mtime.to_f, @file1, @file2).should == 2
end

platform_is :linux do
platform_is wordsize: 64 do
it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do
# https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps
# "Therefore, timestamps should not overflow until May 2446."
# https://lwn.net/Articles/804382/
# "On-disk timestamps hitting the y2038 limit..."
# The problem seems to be being improved, but currently it actually fails on XFS on RHEL8
# https://rubyci.org/logs/rubyci.s3.amazonaws.com/rhel8/ruby-master/log/20201112T123004Z.fail.html.gz
# Amazon Linux 2023 returns 2486-07-02 in this example
# http://rubyci.s3.amazonaws.com/amazon2023/ruby-master/log/20230322T063004Z.fail.html.gz
time = Time.at(1<<44)
File.utime(time, time, @file1)
[559444, 2486, 2446, 2038].should.include? File.atime(@file1).year
[559444, 2486, 2446, 2038].should.include? File.mtime(@file1).year
end
end
end
it_behaves_like :update_time, :utime
end
2 changes: 0 additions & 2 deletions spec/tags/core/file/lutime_tags.txt

This file was deleted.

12 changes: 12 additions & 0 deletions src/main/c/truffleposix/truffleposix.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ int truffleposix_poll_single_fd(int fd, int events, int timeout_ms) {
return poll(&fds, 1, timeout_ms) >= 0 ? fds.revents : -1;
}

int truffleposix_lutimes(const char *filename, long atime_sec, int atime_nsec,
long mtime_sec, int mtime_nsec) {
struct timespec timespecs[2];

timespecs[0].tv_sec = atime_sec;
timespecs[0].tv_nsec = atime_nsec;
timespecs[1].tv_sec = mtime_sec;
timespecs[1].tv_nsec = mtime_nsec;

return utimensat(AT_FDCWD, filename, timespecs, AT_SYMLINK_NOFOLLOW);
}

int truffleposix_utimes(const char *filename, long atime_sec, int atime_nsec,
long mtime_sec, int mtime_nsec) {
struct timespec timespecs[2];
Expand Down
33 changes: 29 additions & 4 deletions src/main/ruby/truffleruby/core/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,31 @@ def self.lstat(path)
Stat.lstat path
end

##
# Sets the access and modification times of each named file to the first
# two arguments. If a file is a symlink, this method acts upon the link
# itself as opposed to its referent; for the inverse behavior, see
# File.utime. Returns the number of file names in the argument list.
def self.lutime(atime, mtime, *paths)
if !atime || !mtime
now = Time.now
atime ||= now
mtime ||= now
end

atime = Time.at(atime) unless Primitive.is_a?(atime, Time)
mtime = Time.at(mtime) unless Primitive.is_a?(mtime, Time)

paths.each do |path|
path = Truffle::Type.coerce_to_path(path)
n = POSIX.truffleposix_lutimes(path, atime.to_i, atime.nsec,
mtime.to_i, mtime.nsec)
Errno.handle unless n == 0
end

paths.size
end

##
# Returns the modification time for the named file as a Time object.
#
Expand Down Expand Up @@ -1062,10 +1087,10 @@ def self.unlink(*paths)
end

##
# Sets the access and modification times of each named
# file to the first two arguments. Returns the number
# of file names in the argument list.
# #=> Integer
# Sets the access and modification times of each named file to the first
# two arguments. If a file is a symlink, this method acts upon its
# referent rather than the link itself; for the inverse behavior see
# File.lutime. Returns the number of file names in the argument list.
def self.utime(atime, mtime, *paths)
if !atime || !mtime
now = Time.now
Expand Down
1 change: 1 addition & 0 deletions src/main/ruby/truffleruby/core/posix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def self.attach_function_eagerly(native_name, argument_types, return_type,
attach_function :lseek, [:int, :off_t, :int], :off_t
attach_function :truffleposix_lstat, [:string, :pointer], :int, LIBTRUFFLEPOSIX
attach_function :truffleposix_lstat_mode, [:string], :mode_t, LIBTRUFFLEPOSIX
attach_function :truffleposix_lutimes, [:string, :long, :int, :long, :int], :int, LIBTRUFFLEPOSIX
attach_function :truffleposix_major, [:dev_t], :uint, LIBTRUFFLEPOSIX
attach_function :truffleposix_minor, [:dev_t], :uint, LIBTRUFFLEPOSIX
attach_function :mkdir, [:string, :mode_t], :int
Expand Down

0 comments on commit 3c22d4a

Please sign in to comment.