Packages
nerves
1.11.0
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.0
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.3
1.9.2
retired
1.9.1
1.9.0
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
retired
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
retired
1.7.1
retired
1.7.0
retired
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.11.0
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-rc.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
Craft and deploy bulletproof embedded software
Current section
Files
Jump to
Current section
Files
lib/nerves/package/utils/squashfs.ex
defmodule Nerves.Package.Utils.Squashfs do
@moduledoc false
use GenServer
require Logger
@file_types ["c", "b", "l", "d", "-"]
@device_types ["c", "b"]
@posix [r: 4, w: 2, x: 1, s: 1, t: 1]
@sticky ["s", "t", "S", "T"]
@spec start_link(String.t()) :: GenServer.on_start()
def start_link(rootfs) do
params = unsquashfs(rootfs)
dir =
Path.dirname(rootfs)
|> Path.join("squashfs")
case Nerves.Port.cmd("unsquashfs", [rootfs, "-d", dir]) do
{_result, 0} ->
GenServer.start_link(__MODULE__, [rootfs, dir, params])
{error, _} ->
{:error, error}
end
end
@spec stop(GenServer.server()) :: :ok
def stop(pid) do
GenServer.call(pid, :stop)
GenServer.stop(pid)
end
@spec pseudofile(GenServer.server()) :: String.t()
def pseudofile(pid) do
GenServer.call(pid, {:pseudofile})
end
@spec pseudofile_fragment(GenServer.server(), String.t()) :: String.t()
def pseudofile_fragment(pid, fragment) do
GenServer.call(pid, {:pseudofile_fragment, fragment})
end
@spec fragment(GenServer.server(), String.t(), Path.t(), Keyword.t()) :: Path.t()
def fragment(pid, fragment, path, opts \\ []) do
GenServer.call(pid, {:fragment, fragment, path, opts})
end
@spec files(GenServer.server()) :: [Path.t()]
def files(pid) do
GenServer.call(pid, {:files})
end
# def merge(pid, file_systems, pseudofiles, path) do
# GenServer.call(pid, {:mergefs, file_systems, pseudofiles, path})
# end
defp unsquashfs(rootfs) do
case Nerves.Port.cmd("unsquashfs", ["-n", "-ll", rootfs]) do
{result, 0} ->
String.split(result, "\n")
|> parse
{error, _} ->
raise "Error parsing Rootfs: #{inspect(error)}"
end
end
@impl GenServer
def init([rootfs, stage, params]) do
{:ok,
%{
rootfs: rootfs,
params: params,
stage: stage
}}
end
@impl GenServer
def handle_call(:stop, _from, s) do
_ = File.rm_rf!(s.stage)
{:reply, :ok, s}
end
def handle_call({:files}, _from, s) do
files =
Enum.reduce(s.params, [], fn
{"d", _, _, _, _}, acc -> acc
{_, file, _, _, _}, acc -> [file | acc]
end)
{:reply, files, s}
end
def handle_call({:pseudofile}, _from, s) do
{:reply, params_to_pseudofile(s.params), s}
end
def handle_call({:pseudofile_fragment, fragment}, _from, s) do
fragment =
Enum.filter(s.params, fn {_, file, _, _, _} ->
file in fragment
end)
{:reply, params_to_pseudofile(fragment), s}
end
def handle_call({:fragment, fragment, path, opts}, _from, s) do
pseudo_fragment =
fragment
|> Enum.map(&path_to_paths/1)
|> List.flatten()
|> Enum.uniq()
pseudo_fragment = Enum.filter(s.params, fn {_, file, _, _, _} -> file in pseudo_fragment end)
fragment = Enum.filter(s.params, fn {_, file, _, _, _} -> file in fragment end)
pseudofile = params_to_pseudofile(pseudo_fragment)
tmp_dir =
Path.dirname(path)
|> Path.join("tmp")
File.mkdir_p!(tmp_dir)
pseudofile_name = opts[:name] || "pseudofile"
pseudofile_path =
Path.dirname(path)
|> Path.join(pseudofile_name)
File.write!(pseudofile_path, pseudofile)
Enum.each(fragment, fn {_, file, _, _, _} ->
src = Path.join(s.stage, file)
dest = Path.join(tmp_dir, file)
Path.dirname(dest)
|> File.mkdir_p!()
File.cp!(src, dest)
end)
IO.puts(path)
_ =
Nerves.Port.cmd("mksquashfs", [
tmp_dir,
path,
"-pf",
pseudofile_path,
"-noappend",
"-no-recovery",
"-no-progress"
])
_ = File.rm_rf!(tmp_dir)
# File.rm!(pseudofile_path)
{:reply, {:ok, path}, s}
end
# def handle_call({:mergefs, file_systems, pseudofiles, path}, from, s) do
#
# stage_path =
# s.stage
# |> Path.dirname
#
# unionfs = Path.join(stage_path, "union")
# Enum.each(fs, fn() ->
# Nerves.Port.cmd("unsquashfs", ["-d", s.stage, "-f", fs])
# end)
#
# pseudofile = Enum.reduce(pseudofiles, "", fn(file, acc) ->
# File.read!(file) <> acc <> "\n"
# end)
# pseudofile <> "\n" <> params_to_pseudofile(s.params)
#
# pseudofile_path = Path.join(stage_path, "pseudofile")
# File.write!(pseudofile_path, pseudofile)
#
# Nerves.Port.cmd("mksquashfs", [s.stage, path, "-pf", pseudofile_path, "-noappend", "-no-recovery", "-no-progress"])
#
# #File.rm!(pseudofile_path)
#
# {:reply, {:ok, path}, s}
# end
defp params_to_pseudofile(fragment) do
Enum.map(fragment, fn
{type, file, {major, minor}, {p0, p1, p2, p3}, {o, g}} when type in @device_types ->
"#{file} #{type} #{p0}#{p1}#{p2}#{p3} #{o} #{g} #{major} #{minor}"
{_type, file, _attr, {p0, p1, p2, p3}, {o, g}} ->
file = if file == "", do: "/", else: file
"#{file} m #{p0}#{p1}#{p2}#{p3} #{o} #{g}"
end)
|> Enum.reverse()
|> Enum.join("\n")
end
defp parse(_, _ \\ [])
defp parse([], collect), do: collect
defp parse([line | tail], collect) do
collect =
case parse_line(line) do
nil -> collect
value -> [value | collect]
end
parse(tail, collect)
end
defp parse_line(""), do: nil
defp parse_line(<<type::binary-size(1), permissions::binary-size(9), _::utf8, tail::binary>>)
when type in @file_types do
permissions = parse_permissions(permissions)
[own, tail] = String.split(tail, " ", parts: 2)
own = parse_own(own)
tail = String.trim(tail)
{attr, tail} =
if type in @device_types do
[major, tail] = String.split(tail, ",", parts: 2)
tail = String.trim(tail)
[minor, tail] = String.split(tail, " ", parts: 2)
{{major, minor}, tail}
else
[_, tail] = String.split(tail, " ", parts: 2)
{nil, tail}
end
<<_modified::binary-size(16), tail::binary>> = tail
<<"squashfs-root", file::binary>> = String.trim(tail)
file =
if type == "l" do
[file, _] = String.split(file, "->")
String.trim(file)
else
file
end
{type, file, attr, permissions, own}
end
defp parse_line(_), do: nil
defp parse_permissions(<<owner::binary-size(3), group::binary-size(3), other::binary-size(3)>>) do
sticky = 0
sticky = sticky + sticky_to_int(owner, 4) + sticky_to_int(group, 2) + sticky_to_int(other, 1)
{sticky, posix_to_int(owner), posix_to_int(group), posix_to_int(other)}
end
defp parse_own(own) do
[owner, group] = String.split(own, "/")
{owner, group}
end
defp sticky_to_int(<<_::binary-size(1), _::binary-size(1), bit::binary-size(1)>>, weight)
when bit in @sticky,
do: weight
defp sticky_to_int(_, _), do: 0
defp posix_to_int(<<r::binary-size(1), w::binary-size(1), x::binary-size(1)>>) do
Enum.reduce([r, w, x], 0, fn p, a ->
Keyword.get(@posix, String.to_atom(p), 0) + a
end)
end
defp path_to_paths(path) do
path
|> Path.split()
|> Enum.reduce(["/"], fn p, acc ->
[h | _t] = acc
[Path.join(h, p) | acc]
end)
|> Enum.uniq()
end
end