Current section

Files

Jump to
archive lib archive stat.ex
Raw

lib/archive/stat.ex

defmodule Archive.Stat do
require Archive.Nif
import Bitwise
@file_kinds Archive.Nif.get_file_kinds() |> Map.drop([:mask])
@mask Archive.Nif.get_file_kinds() |> Map.get(:mask)
@lookup Enum.into(@file_kinds, %{}, fn {k, v} -> {v, k} end)
def file_kinds(), do: @file_kinds
def file_stat_to_zig_map(%File.Stat{} = stat) do
%{
ino: convert_integer(stat.inode),
size: convert_integer(stat.size),
mode: convert_integer(stat.mode),
nlink: convert_integer(stat.links),
gid: convert_integer(stat.gid),
uid: convert_integer(stat.uid)
}
|> Map.merge(to_zig_timespec(stat))
|> Map.merge(to_zig_device(stat))
end
defp to_zig_device(%{major_device: devmajor, minor_device: devminor}) do
%{dev: combine_major_minor(devmajor, devminor)}
end
defp to_zig_timespec(%File.Stat{atime: atime, mtime: mtime, ctime: ctime}) do
case :os.type() do
{:unix, :darwin} ->
%{
atimespec: %{tv_sec: extract_seconds(atime), tv_nsec: extract_nanoseconds(atime)},
mtimespec: %{tv_sec: extract_seconds(mtime), tv_nsec: extract_nanoseconds(mtime)},
ctimespec: %{tv_sec: extract_seconds(ctime), tv_nsec: extract_nanoseconds(ctime)},
# These are required since it checks for type on the parameter
# these correspond to std.c.darwin's stat
birthtimespec: %{tv_sec: 0, tv_nsec: 0},
rdev: 0,
blocks: 0,
blksize: 0,
flags: 0,
gen: 0,
lspare: 0,
qspare: [0, 0]
}
{:unix, _} ->
%{
atim: %{sec: extract_seconds(atime), nsec: extract_nanoseconds(atime)},
mtim: %{sec: extract_seconds(mtime), nsec: extract_nanoseconds(mtime)},
ctim: %{sec: extract_seconds(ctime), nsec: extract_nanoseconds(ctime)},
rdev: 0,
blksize: 0,
blocks: 0
}
end
end
defp convert_integer(:undefined), do: 0
defp convert_integer(value) when is_integer(value), do: value
defp convert_integer(_), do: 0
defp extract_seconds(:undefined), do: 0
defp extract_seconds({{year, month, day}, {hour, minute, second}}) do
:calendar.datetime_to_gregorian_seconds({{year, month, day}, {hour, minute, second}}) -
62_167_219_200
end
defp extract_seconds(unix_timestamp) when is_integer(unix_timestamp), do: unix_timestamp
defp extract_seconds(_), do: 0
defp extract_nanoseconds(:undefined), do: 0
# Calendar format doesn't include nanoseconds
defp extract_nanoseconds({{_, _, _}, {_, _, _}}), do: 0
# For integer timestamps, we don't have nanosecond precision
defp extract_nanoseconds(_), do: 0
@doc false
def to_file_stat(zig_stat) do
%File.Stat{
access: get_access(zig_stat.mode),
gid: zig_stat.gid,
inode: zig_stat.ino,
links: zig_stat.nlink,
mode: zig_stat.mode,
size: zig_stat.size,
uid: zig_stat.uid
}
|> struct!(convert_time(zig_stat))
|> struct!(major_minor(zig_stat))
|> struct!(convert_type(zig_stat))
end
def major_minor(%{dev: dev}) when is_integer(dev) do
# On most Unix-like systems:
# - major = dev >> 8
# - minor = dev & 0xFF
# However, this can vary by OS. Here's a more portable approach:
major = div(dev, 256)
minor = rem(dev, 256)
%{major_device: major, minor_device: minor}
end
def combine_major_minor(major, minor) when is_integer(major) and is_integer(minor) do
# Ensure that major and minor are within valid ranges
major = max(0, min(major, 255))
minor = max(0, min(minor, 255))
# Combine major and minor
major * 256 + minor
end
defp get_access(mode) do
cond do
(mode &&& 0o600) == 0o600 -> :read_write
(mode &&& 0o400) == 0o400 -> :read
(mode &&& 0o200) == 0o200 -> :write
true -> :none
end
end
defp convert_time(%{atimespec: atime, mtimespec: mtime, ctimespec: ctime}) do
%{
atime: convert_time(atime),
mtime: convert_time(mtime),
ctime: convert_time(ctime)
}
end
defp convert_time(%{atim: atime, mtim: mtime, ctim: ctime}) do
%{
atime: convert_time(atime),
mtime: convert_time(mtime),
ctime: convert_time(ctime)
}
end
defp convert_time(%{tv_sec: sec, tv_nsec: nsec}), do: convert_time(sec, nsec)
defp convert_time(%{sec: sec, nsec: nsec}), do: convert_time(sec, nsec)
defp convert_time(sec, nsec) do
DateTime.from_unix!(sec, :second)
|> DateTime.add(nsec, :nanosecond)
|> DateTime.to_naive()
|> NaiveDateTime.to_erl()
end
defp convert_type(%{mode: mode}) when is_integer(mode) do
%{type: Map.get(@lookup, Bitwise.band(mode, @mask), :other)}
end
end