Skip to content

Commit 030495b

Browse files
committed
Rename and rework File::Stat as File::Info
1 parent d294dd1 commit 030495b

File tree

15 files changed

+261
-303
lines changed

15 files changed

+261
-303
lines changed

spec/std/file_spec.cr

+46-47
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ describe "File" do
314314
begin
315315
File.write(path, "")
316316
File.chmod(path, 0o775)
317-
File.stat(path).perm.should eq(0o775)
317+
File.stat(path).permissions.should eq(0o775)
318318
ensure
319319
File.delete(path) if File.exists?(path)
320320
end
@@ -325,20 +325,31 @@ describe "File" do
325325
begin
326326
Dir.mkdir(path, 0o775)
327327
File.chmod(path, 0o664)
328-
File.stat(path).perm.should eq(0o664)
328+
File.stat(path).permissions.should eq(0o664)
329329
ensure
330330
Dir.rmdir(path) if Dir.exists?(path)
331331
end
332332
end
333333

334+
it "can take File::Permissions" do
335+
path = "#{__DIR__}/data/chmod.txt"
336+
begin
337+
File.write(path, "")
338+
File.chmod(path, File::Permissions.flags(OwnerAll, GroupAll, OtherExecute, OtherRead))
339+
File.stat(path).permissions.should eq(0o775)
340+
ensure
341+
File.delete(path) if File.exists?(path)
342+
end
343+
end
344+
334345
it "follows symlinks" do
335346
path = "#{__DIR__}/data/chmod_destination.txt"
336347
link = "#{__DIR__}/data/chmod.txt"
337348
begin
338349
File.write(path, "")
339350
File.symlink(path, link)
340351
File.chmod(link, 0o775)
341-
File.stat(link).perm.should eq(0o775)
352+
File.stat(link).permissions.should eq(0o775)
342353
ensure
343354
File.delete(path) if File.exists?(path)
344355
File.delete(link) if File.symlink?(link)
@@ -354,61 +365,35 @@ describe "File" do
354365

355366
it "gets stat for this file" do
356367
stat = File.stat(__FILE__)
357-
stat.blockdev?.should be_false
358-
stat.chardev?.should be_false
359-
stat.directory?.should be_false
360-
stat.file?.should be_true
361-
stat.symlink?.should be_false
362-
stat.socket?.should be_false
368+
stat.type.should eq(File::Type::File)
363369
end
364370

365371
it "gets stat for this directory" do
366372
stat = File.stat(__DIR__)
367-
stat.blockdev?.should be_false
368-
stat.chardev?.should be_false
369-
stat.directory?.should be_true
370-
stat.file?.should be_false
371-
stat.symlink?.should be_false
372-
stat.socket?.should be_false
373+
stat.type.should eq(File::Type::Directory)
373374
end
374375

375376
it "gets stat for a character device" do
376377
stat = File.stat("/dev/null")
377-
stat.blockdev?.should be_false
378-
stat.chardev?.should be_true
379-
stat.directory?.should be_false
380-
stat.file?.should be_false
381-
stat.symlink?.should be_false
382-
stat.socket?.should be_false
378+
stat.type.should eq(File::Type::CharacterDevice)
383379
end
384380

385381
it "gets stat for a symlink" do
386382
stat = File.lstat("#{__DIR__}/data/symlink.txt")
387-
stat.blockdev?.should be_false
388-
stat.chardev?.should be_false
389-
stat.directory?.should be_false
390-
stat.file?.should be_false
391-
stat.symlink?.should be_true
392-
stat.socket?.should be_false
383+
stat.type.should eq(File::Type::SymbolicLink)
393384
end
394385

395386
it "gets stat for open file" do
396387
File.open(__FILE__, "r") do |file|
397388
stat = file.stat
398-
stat.blockdev?.should be_false
399-
stat.chardev?.should be_false
400-
stat.directory?.should be_false
401-
stat.file?.should be_true
402-
stat.symlink?.should be_false
403-
stat.socket?.should be_false
404-
stat.pipe?.should be_false
389+
stat.type.should eq(File::Type::File)
405390
end
406391
end
407392

408393
it "gets stat for pipe" do
409394
IO.pipe do |r, w|
410-
r.stat.pipe?.should be_true
411-
w.stat.pipe?.should be_true
395+
r.stat.type.should eq(File::Type::Pipe)
396+
w.stat.type.should eq(File::Type::Pipe)
412397
end
413398
end
414399

@@ -421,9 +406,8 @@ describe "File" do
421406
it "gets stat mtime for new file" do
422407
tmp = Tempfile.new "tmp"
423408
begin
424-
(tmp.stat.atime - Time.utc_now).total_seconds.should be < 5
425-
(tmp.stat.ctime - Time.utc_now).total_seconds.should be < 5
426-
(tmp.stat.mtime - Time.utc_now).total_seconds.should be < 5
409+
tmp.stat.modification_time.should be_close(Time.now, 5.seconds)
410+
File.stat(tmp.path).modification_time.should be_close(Time.now, 5.seconds)
427411
ensure
428412
tmp.delete
429413
end
@@ -680,11 +664,20 @@ describe "File" do
680664
end
681665
end
682666

683-
it "opens with perm" do
667+
it "opens with perm (int)" do
684668
filename = "#{__DIR__}/data/temp_write.txt"
685669
perm = 0o600
686670
File.open(filename, "w", perm) do |file|
687-
file.stat.perm.should eq(perm)
671+
file.stat.permissions.should eq(perm)
672+
end
673+
File.delete filename
674+
end
675+
676+
it "opens with perm (File::Permissions)" do
677+
filename = "#{__DIR__}/data/temp_write.txt"
678+
perm = File::Permissions.flags(OwnerRead, OwnerWrite)
679+
File.open(filename, "w", perm) do |file|
680+
file.stat.permissions.should eq(perm)
688681
end
689682
File.delete filename
690683
end
@@ -1039,8 +1032,7 @@ describe "File" do
10391032
File.utime(atime, mtime, filename)
10401033

10411034
stat = File.stat(filename)
1042-
stat.atime.should eq(atime)
1043-
stat.mtime.should eq(mtime)
1035+
stat.modification_time.should eq(mtime)
10441036

10451037
File.delete filename
10461038
end
@@ -1074,8 +1066,7 @@ describe "File" do
10741066
File.touch(filename, time)
10751067

10761068
stat = File.stat(filename)
1077-
stat.atime.should eq(time)
1078-
stat.mtime.should eq(time)
1069+
stat.modification_time.should eq(time)
10791070
ensure
10801071
File.delete filename
10811072
end
@@ -1088,8 +1079,7 @@ describe "File" do
10881079
File.touch(filename)
10891080

10901081
stat = File.stat(filename)
1091-
stat.atime.should be_close(time, 1.second)
1092-
stat.mtime.should be_close(time, 1.second)
1082+
stat.modification_time.should be_close(time, 1.second)
10931083
ensure
10941084
File.delete filename
10951085
end
@@ -1221,4 +1211,13 @@ describe "File" do
12211211
File.match?("ab{{c,d}ef,}", "abdef").should be_true
12221212
end
12231213
end
1214+
1215+
describe File::Permissions do
1216+
it "does to_s" do
1217+
perm = File::Permissions.flags(OwnerAll, GroupRead, GroupWrite, OtherRead)
1218+
perm.to_s.should eq("rwxrw-r-- (0o764)")
1219+
perm.inspect.should eq("rwxrw-r-- (0o764)")
1220+
perm.pretty_inspect.should eq("rwxrw-r-- (0o764)")
1221+
end
1222+
end
12241223
end

spec/std/file_utils_spec.cr

+17
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,23 @@ describe "FileUtils" do
136136
end
137137
end
138138

139+
it "copies permissions" do
140+
src_path = File.join(__DIR__, "data/new_test_file.txt")
141+
out_path = File.join(__DIR__, "data/test_file_cp.txt")
142+
begin
143+
File.write(src_path, "foo")
144+
File.chmod(src_path, 0o700)
145+
146+
FileUtils.cp(src_path, out_path)
147+
148+
File.stat(out_path).permissions.should eq(0o700)
149+
FileUtils.cmp(src_path, out_path).should be_true
150+
ensure
151+
File.delete(src_path) if File.exists?(out_path)
152+
File.delete(out_path) if File.exists?(out_path)
153+
end
154+
end
155+
139156
it "raises an error if the directory doesn't exists" do
140157
expect_raises(ArgumentError, "No such directory : not_existing_dir") do
141158
FileUtils.cp({File.join(__DIR__, "data/test_file.text")}, "not_existing_dir")

src/compiler/crystal/codegen/cache_dir.cr

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ module Crystal
107107
private def cleanup_dirs(entries)
108108
entries
109109
.select { |dir| Dir.exists?(dir) }
110-
.sort_by! { |dir| File.stat(dir).mtime rescue Time.epoch(0) }
110+
.sort_by! { |dir| File.stat?(dir).try(&.modification_time) || Time.epoch(0) }
111111
.reverse!
112112
.skip(10)
113113
.each { |name| `rm -rf "#{name}"` rescue nil }

src/compiler/crystal/macros/macros.cr

+2-2
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class Crystal::Program
158158
# Together with their timestamp
159159
# (this is the list of all effective files that were required)
160160
requires_with_timestamps = result.program.requires.map do |required_file|
161-
epoch = File.stat(required_file).mtime.epoch
161+
epoch = File.stat(required_file).modification_time.epoch
162162
RequireWithTimestamp.new(required_file, epoch)
163163
end
164164

@@ -203,7 +203,7 @@ class Crystal::Program
203203
end
204204

205205
new_requires_with_timestamps = required_files.map do |required_file|
206-
epoch = File.stat(required_file).mtime.epoch
206+
epoch = File.stat(required_file).modification_time.epoch
207207
RequireWithTimestamp.new(required_file, epoch)
208208
end
209209

src/crystal/system/unix/file.cr

+48-5
Original file line numberDiff line numberDiff line change
@@ -70,26 +70,69 @@ module Crystal::System::File
7070
tmpdir.rchop(::File::SEPARATOR)
7171
end
7272

73-
def self.stat?(path : String) : ::File::Stat?
73+
def self.stat?(path : String) : ::File::Info?
7474
if LibC.stat(path.check_no_null_byte, out stat) != 0
7575
if {Errno::ENOENT, Errno::ENOTDIR}.includes? Errno.value
7676
return nil
7777
else
7878
raise Errno.new("Unable to get stat for '#{path}'")
7979
end
8080
end
81-
::File::Stat.new(stat)
81+
82+
to_file_info(stat)
8283
end
8384

84-
def self.lstat?(path : String) : ::File::Stat?
85+
def self.lstat?(path : String) : ::File::Info?
8586
if LibC.lstat(path.check_no_null_byte, out stat) != 0
8687
if {Errno::ENOENT, Errno::ENOTDIR}.includes? Errno.value
8788
return nil
8889
else
8990
raise Errno.new("Unable to get lstat for '#{path}'")
9091
end
9192
end
92-
::File::Stat.new(stat)
93+
94+
to_file_info(stat)
95+
end
96+
97+
def self.to_file_info(stat)
98+
size = stat.st_size.to_u64
99+
100+
permissions = ::File::Permissions.new((stat.st_mode & 0o777).to_i16)
101+
102+
case stat.st_mode & LibC::S_IFMT
103+
when LibC::S_IFBLK
104+
type = ::File::Type::BlockDevice
105+
when LibC::S_IFCHR
106+
type = ::File::Type::CharacterDevice
107+
when LibC::S_IFDIR
108+
type = ::File::Type::Directory
109+
when LibC::S_IFIFO
110+
type = ::File::Type::Pipe
111+
when LibC::S_IFLNK
112+
type = ::File::Type::SymbolicLink
113+
when LibC::S_IFREG
114+
type = ::File::Type::File
115+
when LibC::S_IFSOCK
116+
type = ::File::Type::Socket
117+
else
118+
raise "BUG: unknown File::Type"
119+
end
120+
121+
flags = ::File::Flags::None
122+
flags |= ::File::Flags::SetUser if (stat.st_mode & LibC::S_ISUID) != 0
123+
flags |= ::File::Flags::SetGroup if (stat.st_mode & LibC::S_ISGID) != 0
124+
flags |= ::File::Flags::Sticky if (stat.st_mode & LibC::S_ISVTX) != 0
125+
126+
{% if flag?(:darwin) %}
127+
modification_time = ::Time.new(stat.st_mtimespec, ::Time::Kind::Utc)
128+
{% else %}
129+
modification_time = ::Time.new(stat.st_mtim, ::Time::Kind::Utc)
130+
{% end %}
131+
132+
owner = stat.st_uid.to_u32
133+
group = stat.st_gid.to_u32
134+
135+
::File::Info.new(size, permissions, type, flags, modification_time, owner, group)
93136
end
94137

95138
def self.exists?(path)
@@ -121,7 +164,7 @@ module Crystal::System::File
121164
raise Errno.new("Error changing owner of '#{path}'") if ret == -1
122165
end
123166

124-
def self.chmod(path, mode : Int)
167+
def self.chmod(path, mode)
125168
if LibC.chmod(path, mode) == -1
126169
raise Errno.new("Error changing permissions of '#{path}'")
127170
end

src/crystal/system/unix/file_descriptor.cr

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ module Crystal::System::FileDescriptor
6161
if LibC.fstat(@fd, out stat) != 0
6262
raise Errno.new("Unable to get stat")
6363
end
64-
::File::Stat.new(stat)
64+
65+
File.to_file_info(stat)
6566
end
6667

6768
private def system_seek(offset, whence : IO::Seek) : Nil

src/crystal/system/unix/getrandom.cr

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module Crystal::System::Random
1515
@@getrandom_available = true
1616
else
1717
urandom = ::File.open("/dev/urandom", "r")
18-
return unless urandom.stat.chardev?
18+
return unless urandom.stat.type.character_device?
1919

2020
urandom.close_on_exec = true
2121
urandom.sync = true # don't buffer bytes

src/crystal/system/unix/urandom.cr

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module Crystal::System::Random
99
@@initialized = true
1010

1111
urandom = ::File.open("/dev/urandom", "r")
12-
return unless urandom.stat.chardev?
12+
return unless urandom.stat.type.character_device?
1313

1414
urandom.close_on_exec = true
1515
urandom.sync = true # don't buffer bytes

src/dir.cr

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class Dir
194194
# Returns `true` if the given path exists and is a directory
195195
def self.exists?(path) : Bool
196196
if stat = File.stat?(path)
197-
stat.directory?
197+
stat.type.directory?
198198
else
199199
false
200200
end

src/dir/glob.cr

+5-5
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,11 @@ class Dir
267267
end
268268

269269
private def self.dir?(path)
270-
return true unless path
271-
stat = File.lstat(path)
272-
stat.directory? && !stat.symlink?
273-
rescue Errno
274-
false
270+
if stat = File.lstat?(path)
271+
stat.type.directory?
272+
else
273+
false
274+
end
275275
end
276276

277277
private def self.join(path, entry)

0 commit comments

Comments
 (0)