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

More detailed StatStruct show #39463

Merged
merged 3 commits into from May 31, 2021
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
16 changes: 16 additions & 0 deletions base/filesystem.jl
Expand Up @@ -4,6 +4,22 @@

module Filesystem

const S_IFDIR = 0o040000 # directory
const S_IFCHR = 0o020000 # character device
const S_IFBLK = 0o060000 # block device
const S_IFREG = 0o100000 # regular file
const S_IFIFO = 0o010000 # fifo (named pipe)
const S_IFLNK = 0o120000 # symbolic link
const S_IFSOCK = 0o140000 # socket file
const S_IFMT = 0o170000

IanButterworth marked this conversation as resolved.
Show resolved Hide resolved
const S_ISUID = 0o4000 # set UID bit
const S_ISGID = 0o2000 # set GID bit
const S_ENFMT = S_ISGID # file locking enforcement
const S_ISVTX = 0o1000 # sticky bit
const S_IRWXU = 0o0700 # mask for owner permissions
const S_IRUSR = 0o0400 # read by owner

const S_IRUSR = 0o400
const S_IWUSR = 0o200
const S_IXUSR = 0o100
Expand Down
73 changes: 73 additions & 0 deletions base/libc.jl
Expand Up @@ -402,6 +402,79 @@ Interface to the C `srand(seed)` function.
"""
srand(seed=floor(Int, time()) % Cuint) = ccall(:srand, Cvoid, (Cuint,), seed)

struct Cpasswd
username::Cstring
uid::Clong
gid::Clong
shell::Cstring
homedir::Cstring
gecos::Cstring
Cpasswd() = new(C_NULL, -1, -1, C_NULL, C_NULL, C_NULL)
end
mutable struct Cgroup
groupname::Cstring # group name
gid::Clong # group ID
mem::Ptr{Cstring} # group members
Cgroup() = new(C_NULL, -1, C_NULL)
end
struct Passwd
username::String
uid::Int
gid::Int
shell::String
homedir::String
gecos::String
end
struct Group
groupname::String
gid::Int
mem::Vector{String}
end

function getpwuid(uid::Unsigned, throw_error::Bool=true)
ref_pd = Ref(Cpasswd())
ret = ccall(:jl_os_get_passwd, Cint, (Ref{Cpasswd}, UInt), ref_pd, uid)
if ret != 0
throw_error && Base.uv_error("getpwuid", ret)
return
end
pd = ref_pd[]
pd = Passwd(
pd.username == C_NULL ? "" : unsafe_string(pd.username),
pd.uid,
pd.gid,
pd.shell == C_NULL ? "" : unsafe_string(pd.shell),
pd.homedir == C_NULL ? "" : unsafe_string(pd.homedir),
pd.gecos == C_NULL ? "" : unsafe_string(pd.gecos),
)
ccall(:uv_os_free_passwd, Cvoid, (Ref{Cpasswd},), ref_pd)
return pd
end
function getgrgid(gid::Unsigned, throw_error::Bool=true)
ref_gp = Ref(Cgroup())
ret = ccall(:jl_os_get_group, Cint, (Ref{Cgroup}, UInt), ref_gp, gid)
if ret != 0
throw_error && Base.uv_error("getgrgid", ret)
return
end
gp = ref_gp[]
members = String[]
if gp.mem != C_NULL
while true
mem = unsafe_load(gp.mem, length(members) + 1)
mem == C_NULL && break
push!(members, unsafe_string(mem))
end
end
gp = Group(
gp.groupname == C_NULL ? "" : unsafe_string(gp.groupname),
gp.gid,
members,
)
ccall(:jl_os_free_group, Cvoid, (Ref{Cgroup},), ref_gp)
return gp
end

# Include dlopen()/dlpath() code
include("libdl.jl")
using .Libdl
Expand Down
27 changes: 14 additions & 13 deletions base/reflection.jl
Expand Up @@ -654,19 +654,20 @@ use it in the following manner to summarize information about a struct:
julia> structinfo(T) = [(fieldoffset(T,i), fieldname(T,i), fieldtype(T,i)) for i = 1:fieldcount(T)];

julia> structinfo(Base.Filesystem.StatStruct)
12-element Vector{Tuple{UInt64, Symbol, DataType}}:
(0x0000000000000000, :device, UInt64)
(0x0000000000000008, :inode, UInt64)
(0x0000000000000010, :mode, UInt64)
(0x0000000000000018, :nlink, Int64)
(0x0000000000000020, :uid, UInt64)
(0x0000000000000028, :gid, UInt64)
(0x0000000000000030, :rdev, UInt64)
(0x0000000000000038, :size, Int64)
(0x0000000000000040, :blksize, Int64)
(0x0000000000000048, :blocks, Int64)
(0x0000000000000050, :mtime, Float64)
(0x0000000000000058, :ctime, Float64)
13-element Vector{Tuple{UInt64, Symbol, Type}}:
(0x0000000000000000, :desc, Union{RawFD, String})
(0x0000000000000008, :device, UInt64)
(0x0000000000000010, :inode, UInt64)
(0x0000000000000018, :mode, UInt64)
(0x0000000000000020, :nlink, Int64)
(0x0000000000000028, :uid, UInt64)
(0x0000000000000030, :gid, UInt64)
(0x0000000000000038, :rdev, UInt64)
(0x0000000000000040, :size, Int64)
(0x0000000000000048, :blksize, Int64)
(0x0000000000000050, :blocks, Int64)
(0x0000000000000058, :mtime, Float64)
(0x0000000000000060, :ctime, Float64)
```
"""
fieldoffset(x::DataType, idx::Integer) = (@_pure_meta; ccall(:jl_get_field_offset, Csize_t, (Any, Cint), x, idx))
Expand Down
154 changes: 149 additions & 5 deletions base/stat.jl
Expand Up @@ -26,6 +26,7 @@ export
uperm

struct StatStruct
desc :: Union{String, OS_HANDLE} # for show method, not included in equality or hash
device :: UInt
inode :: UInt
mode :: UInt
Expand All @@ -39,10 +40,26 @@ struct StatStruct
mtime :: Float64
ctime :: Float64
end
const StatFieldTypes = Union{UInt,Int,Int64,Float64}

StatStruct() = StatStruct(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
function Base.:(==)(x::StatStruct, y::StatStruct) # do not include `desc` in equality or hash
for i = 2:nfields(x)
xi = getfield(x, i)::StatFieldTypes
xi === getfield(y, i) || return false
end
return true
end
function Base.hash(obj::StatStruct, h::UInt)
for i = 2:nfields(obj)
h = hash(getfield(obj, i)::StatFieldTypes, h)
end
return h
end

StatStruct(buf::Union{Vector{UInt8},Ptr{UInt8}}) = StatStruct(
StatStruct() = StatStruct("", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
StatStruct(buf::Union{Vector{UInt8},Ptr{UInt8}}) = StatStruct("", buf)
StatStruct(desc::Union{AbstractString, OS_HANDLE}, buf::Union{Vector{UInt8},Ptr{UInt8}}) = StatStruct(
desc isa OS_HANDLE ? desc : String(desc),
ccall(:jl_stat_dev, UInt32, (Ptr{UInt8},), buf),
ccall(:jl_stat_ino, UInt32, (Ptr{UInt8},), buf),
ccall(:jl_stat_mode, UInt32, (Ptr{UInt8},), buf),
Expand All @@ -57,7 +74,72 @@ StatStruct(buf::Union{Vector{UInt8},Ptr{UInt8}}) = StatStruct(
ccall(:jl_stat_ctime, Float64, (Ptr{UInt8},), buf),
)

show(io::IO, st::StatStruct) = print(io, "StatStruct(mode=0o$(string(filemode(st), base = 8, pad = 6)), size=$(filesize(st)))")
function iso_datetime_with_relative(t, tnow)
str = Libc.strftime("%FT%T%z", t)
secdiff = t - tnow
for (d, name) in ((24*60*60, "day"), (60*60, "hour"), (60, "minute"), (1, "second"))
tdiff = round(Int, div(abs(secdiff), d))
if tdiff != 0 # find first unit difference
plural = tdiff == 1 ? "" : "s"
when = secdiff < 0 ? "ago" : "in the future"
return "$str ($tdiff $name$plural $when)"
end
end
return "$str (just now)"
end


function getusername(uid::Unsigned)
pwd = Libc.getpwuid(uid, false)
pwd === nothing && return
isempty(pwd.username) && return
return pwd.username
end

function getgroupname(gid::Unsigned)
gp = Libc.getgrgid(gid, false)
gp === nothing && return
isempty(gp.groupname) && return
return gp.groupname
end

function show_statstruct(io::IO, st::StatStruct, oneline::Bool)
print(io, oneline ? "StatStruct(" : "StatStruct for ")
show(io, st.desc)
oneline || print("\n ")
print(io, " size: ", st.size, " bytes")
oneline || print("\n")
print(io, " device: ", st.device)
oneline || print("\n ")
print(io, " inode: ", st.inode)
oneline || print("\n ")
print(io, " mode: 0o", string(filemode(st), base = 8, pad = 6), " (", filemode_string(st), ")")
oneline || print("\n ")
print(io, " nlink: ", st.nlink)
oneline || print("\n ")
print(io, " uid: $(st.uid)")
username = getusername(st.uid)
username === nothing || print(io, " (", username, ")")
oneline || print("\n ")
print(io, " gid: ", st.gid)
groupname = getgroupname(st.gid)
groupname === nothing || print(io, " (", groupname, ")")
oneline || print("\n ")
print(io, " rdev: ", st.rdev)
oneline || print("\n ")
print(io, " blksz: ", st.blksize)
oneline || print("\n")
print(io, " blocks: ", st.blocks)
tnow = round(UInt, time())
oneline || print("\n ")
print(io, " mtime: ", iso_datetime_with_relative(st.mtime, tnow))
oneline || print("\n ")
print(io, " ctime: ", iso_datetime_with_relative(st.ctime, tnow))
print(io, oneline ? ")" : "\n")
end

show(io::IO, st::StatStruct) = show_statstruct(io, st, true)
show(io::IO, ::MIME"text/plain", st::StatStruct) = show_statstruct(io, st, false)

# stat & lstat functions

Expand All @@ -66,9 +148,9 @@ macro stat_call(sym, arg1type, arg)
stat_buf = zeros(UInt8, ccall(:jl_sizeof_stat, Int32, ()))
r = ccall($(Expr(:quote, sym)), Int32, ($(esc(arg1type)), Ptr{UInt8}), $(esc(arg)), stat_buf)
if !(r in (0, Base.UV_ENOENT, Base.UV_ENOTDIR, Base.UV_EINVAL))
uv_error(string("stat(",repr($(esc(arg))),")"), r)
uv_error(string("stat(", repr($(esc(arg))), ")"), r)
end
st = StatStruct(stat_buf)
st = StatStruct($(esc(arg)), stat_buf)
if ispath(st) != (r == 0)
error("stat returned zero type for a valid path")
end
Expand All @@ -92,6 +174,7 @@ The fields of the structure are:

| Name | Description |
|:--------|:-------------------------------------------------------------------|
| desc | The path or OS file descriptor |
| size | The size (in bytes) of the file |
| device | ID of the device that contains the file |
| inode | The inode number of the file |
Expand Down Expand Up @@ -120,12 +203,73 @@ lstat(path...) = lstat(joinpath(path...))

# some convenience functions

const filemode_table = (
[
(S_IFLNK, "l"),
(S_IFSOCK, "s"), # Must appear before IFREG and IFDIR as IFSOCK == IFREG | IFDIR
(S_IFREG, "-"),
(S_IFBLK, "b"),
(S_IFDIR, "d"),
(S_IFCHR, "c"),
(S_IFIFO, "p")
],
[
(S_IRUSR, "r"),
],
[
(S_IWUSR, "w"),
],
[
(S_IXUSR|S_ISUID, "s"),
(S_ISUID, "S"),
(S_IXUSR, "x")
],
[
(S_IRGRP, "r"),
],
[
(S_IWGRP, "w"),
],
[
(S_IXGRP|S_ISGID, "s"),
(S_ISGID, "S"),
(S_IXGRP, "x")
],
[
(S_IROTH, "r"),
],
[
(S_IWOTH, "w"),
],
[
(S_IXOTH|S_ISVTX, "t"),
(S_ISVTX, "T"),
(S_IXOTH, "x")
]
)

"""
filemode(file)

Equivalent to `stat(file).mode`.
"""
filemode(st::StatStruct) = st.mode
filemode_string(st::StatStruct) = filemode_string(st.mode)
function filemode_string(mode)
str = IOBuffer()
for table in filemode_table
complete = true
for (bit, char) in table
if mode & bit == bit
write(str, char)
complete = false
break
end
end
complete && write(str, "-")
end
return String(take!(str))
end

"""
filesize(path...)
Expand Down