Page MenuHomePhorge

No OneTemporary

Size
17 KB
Referenced Files
None
Subscribers
None
diff --git a/Makefile b/Makefile
index deacaca..55d2e01 100644
--- a/Makefile
+++ b/Makefile
@@ -1,22 +1,32 @@
-CFLAGS += -std=c99 -g -Wall -Werror
-CPPFLAGS += -I$(ERL_EI_INCLUDE_DIR) -I/usr/local/include
-LDFLAGS += -L$(ERL_EI_LIBDIR) -L/usr/local/lib
+CFLAGS += -std=c99 -g -Wall
+MAGIC_INCLUDE_DIR = $(MAGIC_DEP_DIR)/src
+MAGIC_LIBDIR = $(MAGIC_DEP_DIR)/src/.libs
+MAGIC_HEADER = $(MAGIC_INCLUDE_DIR)/magic.h
+MAGIC_LIB = $(MAGIC_LIBDIR)/libmagic.so
+CPPFLAGS += -I$(ERL_EI_INCLUDE_DIR) -I$(MAGIC_INCLUDE_DIR)
+LDFLAGS += -L$(ERL_EI_LIBDIR) -L$(MAGIC_LIBDIR)
LDLIBS = -lpthread -lei -lm -lmagic
PRIV = priv/
RM = rm -Rf
ifeq ($(EI_INCOMPLETE),YES)
LDLIBS += -lerl_interface
CFLAGS += -DEI_INCOMPLETE
endif
all: priv/libmagic_port
-priv/libmagic_port: src/libmagic_port.c
+priv/libmagic_port: src/libmagic_port.c $(MAGIC_HEADER) $(MAGIC_LIB)
mkdir -p priv
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(LDLIBS) -o $@
clean:
$(RM) $(PRIV)
+$(MAGIC_HEADER):
+ $(NOECHO) $(NOOP)
+
+$(MAGIC_LIB):
+ $(NOECHO) $(NOOP)
+
.PHONY: clean
diff --git a/lib/majic.ex b/lib/majic.ex
index b637c0c..7129e6c 100644
--- a/lib/majic.ex
+++ b/lib/majic.ex
@@ -1,119 +1,113 @@
defmodule Majic do
alias Majic.{Once, Pool, Result, Server}
@moduledoc """
Robust libmagic integration for Elixir.
"""
@doc """
Perform on `path`.
An option of `server: ServerName`, `pool: PoolName` or `once: true` must be passed.
"""
@type target :: Path.t() | {:bytes, binary()}
@type result :: {:ok, Result.t()} | {:error, term() | String.t()}
@type name :: {:pool, atom()} | {:server, Server.t()} | {:once, true}
@type option :: name | Server.start_option() | Pool.option()
@spec perform(target(), [option()]) :: result()
def perform(path, opts) do
mod =
cond do
Keyword.has_key?(opts, :pool) -> {Pool, Keyword.get(opts, :pool)}
Keyword.has_key?(opts, :server) -> {Server, Keyword.get(opts, :server)}
Keyword.has_key?(opts, :once) -> {Once, nil}
true -> nil
end
opts =
opts
|> Keyword.drop([:pool, :server, :once])
if mod do
do_perform(mod, path, opts)
else
{:error, :no_method}
end
end
@doc "Compiles a `magic` file or a `Magdir` directory to a magic-compiled database (`.mgc`)"
def compile(path, timeout \\ 5_000) do
compile = fn port ->
send(port, {self(), {:command, :erlang.term_to_binary({:compile_database, path})}})
receive do
{^port, {:data, data}} ->
:erlang.binary_to_term(data)
after
timeout ->
{:error, :timeout}
end
end
with_port(compile, timeout)
end
@doc "Checks whenever a database is valid and can be loaded on the current system."
def valid_database?(path, timeout \\ 5_000) do
valid = fn port ->
send(port, {self(), {:command, :erlang.term_to_binary({:add_database, path})}})
receive do
{^port, {:data, data}} ->
:erlang.binary_to_term(data)
after
timeout ->
{:error, :timeout}
end
end
case with_port(valid, timeout) do
{:ok, _} ->
true
_ ->
false
end
end
defp do_perform({Server = mod, name}, path, opts) do
timeout = Keyword.get(opts, :timeout, Majic.Config.default_process_timeout())
mod.perform(name, path, timeout)
end
defp do_perform({Once = mod, _}, path, opts) do
mod.perform(path, opts)
end
defp do_perform({Pool = mod, name}, path, opts) do
mod.perform(name, path, opts)
end
defp with_port(fun, timeout) do
port =
- Port.open(Majic.Config.get_port_name(), [
- :use_stdio,
- :binary,
- :exit_status,
- {:packet, 2},
- {:args, []}
- ])
+ Port.open(Majic.Config.get_port_name(), Majic.Config.get_port_options(nil))
with_port(port, fun, timeout)
end
defp with_port(port, fun, timeout) do
receive do
{^port, {:data, data}} ->
case :erlang.binary_to_term(data) do
:ready ->
fun.(port)
end
after
timeout ->
{:error, :startup_timeout}
end
after
Port.close(port)
end
end
diff --git a/lib/majic/config.ex b/lib/majic/config.ex
index dabdabd..a9bfc61 100644
--- a/lib/majic/config.ex
+++ b/lib/majic/config.ex
@@ -1,46 +1,46 @@
defmodule Majic.Config do
@moduledoc false
@otp_app Mix.Project.config()[:app]
@executable_name "libmagic_port"
@startup_timeout 1_000
@process_timeout 30_000
@recycle_threshold :infinity
def default_process_timeout, do: @process_timeout
def get_port_name do
{:spawn_executable, to_charlist(get_executable_name())}
end
def get_port_options(_options) do
- [:use_stdio, :binary, :exit_status, {:packet, 2}]
+ [:use_stdio, :binary, :exit_status, {:packet, 2}, {:env, [{'LD_LIBRARY_PATH', :code.priv_dir(:file)}]}]
end
def get_startup_timeout(options) do
get_value(options, :startup_timeout, @startup_timeout)
end
def get_process_timeout(options) do
get_value(options, :process_timeout, @process_timeout)
end
def get_recycle_threshold(options) do
get_value(options, :recycle_threshold, @recycle_threshold)
end
defp get_executable_name do
Path.join(:code.priv_dir(@otp_app), @executable_name)
end
defp get(options, key, default) do
Keyword.get(options, key, default)
end
defp get_value(options, key, default) do
case get(options, key, default) do
value when is_integer(value) and value > 0 -> value
:infinity -> :infinity
_ -> raise ArgumentError, message: "Invalid #{key}"
end
end
end
diff --git a/lib/mix/tasks/compile/majic.ex b/lib/mix/tasks/compile/majic.ex
index 5669754..302cc9b 100644
--- a/lib/mix/tasks/compile/majic.ex
+++ b/lib/mix/tasks/compile/majic.ex
@@ -1,146 +1,151 @@
defmodule Mix.Tasks.Compile.Majic do
use Mix.Task.Compiler
@repo_path Path.join(Mix.Project.deps_path(), "libfile")
@build_path Path.join(Mix.Project.build_path(), "/majic")
@manifest Path.join(Mix.Project.manifest_path(), "/compile.majic")
@patch_path "src/magic_patches"
@built_mgc_path Path.join(to_string(:code.priv_dir(:majic)), "/magic.mgc")
@built_magic_path Path.join(to_string(:code.priv_dir(:majic)), "/magic")
@magic_path Path.join(@repo_path, "/magic")
@magdir Path.join(@magic_path, "/Magdir")
@build_magdir Path.join(@build_path, "/magic")
@shortdoc "Updates and compiles majic's embedded magic database."
@moduledoc """
Uses `libfile` dependency Magdir, applies patches from `#{@patch_path}`, and builds to `#{
@built_mgc_path
}`.
Unlike most Mix compile tasks, this task can be called at runtime from `Majic.Application` to ensure that the built-in
compiled magic database is working on the current system.
"""
defmodule Manifest do
@moduledoc false
defstruct [:hash, {:patches, []}]
end
@impl Mix.Task.Compiler
def clean do
File.rm(@built_mgc_path)
File.rm(@built_magic_path)
File.rm_rf(@build_path)
end
@impl Mix.Task.Compiler
def manifests do
[@manifest]
end
@impl Mix.Task.Compiler
def run(_) do
{:ok, manifest} = read_manifest()
{:ok, sha} = get_dep_revision()
patches = list_patches()
File.mkdir_p!(@build_magdir)
if !File.exists?(@built_mgc_path) || !File.exists?(@built_magic_path) || sha == nil ||
sha != manifest.hash || patches != manifest.patches do
:ok = assemble_magdir()
{:ok, patches, _err} = apply_patches()
:ok = assemble_magic(@built_magic_path)
- {:ok, _} = Majic.compile(@build_magdir)
- File.cp!("magic.mgc", @built_mgc_path)
- File.rm!("magic.mgc")
- manifest = %Manifest{hash: sha, patches: Enum.sort(patches)}
- File.write!(@manifest, :erlang.term_to_binary(manifest))
- Mix.shell().info("Majic: Generated magic database")
+ case Majic.compile(@build_magdir) do
+ {:ok, _} ->
+ File.cp!("magic.mgc", @built_mgc_path)
+ File.rm!("magic.mgc")
+ manifest = %Manifest{hash: sha, patches: Enum.sort(patches)}
+ File.write!(@manifest, :erlang.term_to_binary(manifest))
+ Mix.shell().info("Majic: Generated magic database")
+ :ok
+ {:error, _} ->
+ Mix.shell().error("Majic: Compilation of database failed.")
+ end
end
:ok
end
defp read_manifest do
with {:ok, binary} <- File.read(@manifest),
{:term, %Manifest{} = manifest} <- {:term, :erlang.binary_to_term(binary)} do
{:ok, manifest}
else
{:term, _} -> {:ok, %Manifest{}}
{:error, :enoent} -> {:ok, %Manifest{}}
error -> error
end
end
defp get_dep_revision do
git_dir = Path.join(@repo_path, "/.git")
case System.cmd("git", ["--git-dir=#{git_dir}", "rev-parse", "HEAD"]) do
{rev, 0} -> {:ok, String.trim(rev)}
_ -> {:ok, nil}
end
end
defp list_patches do
@patch_path
|> File.ls!()
|> Enum.filter(fn file ->
Path.extname(file) == ".patch"
end)
|> Enum.sort()
end
defp apply_patches do
patched =
list_patches()
|> Enum.map(fn patch ->
path = Path.expand(Path.join(@patch_path, patch))
# We rewrite paths in patch because we're not applying the patch from repository root, but in a temporary
# build folder that is just the Magdir.
case System.cmd("git", ["apply", "-p3", "--directory=magic/", path], cd: @build_path) do
{_, 0} ->
Mix.shell().info("Majic: Patched magic database: #{patch}")
{patch, :ok}
{error, code} ->
Mix.shell().error("Majic: Failed to apply patch #{patch} (#{code})")
Mix.shell().error(error)
{patch, {:error, error}}
end
end)
ok =
Enum.filter(patched, fn {_, result} -> result == :ok end)
|> Enum.map(fn {name, _} -> name end)
err =
Enum.filter(patched, fn {_, result} -> result != :ok end)
|> Enum.map(fn {name, {:error, error}} -> {name, error} end)
{:ok, ok, err}
end
defp assemble_magdir do
File.rm_rf!(@build_magdir)
File.mkdir!(@build_magdir)
File.ls!(@magdir)
|> Enum.each(fn file ->
File.cp!(Path.join(@magdir, file), Path.join(@build_magdir, file))
end)
:ok
end
defp assemble_magic(destination) do
contents =
File.ls!(@build_magdir)
|> Enum.reduce(<<>>, fn file, acc ->
content = File.read!(Path.join(@build_magdir, file))
<<acc::binary, content::binary>>
end)
File.write!(destination, contents)
:ok
end
end
diff --git a/mix.exs b/mix.exs
index 3658747..27cb395 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,86 +1,89 @@
defmodule Majic.MixProject do
use Mix.Project
if :erlang.system_info(:otp_release) < '21' do
raise "Majic requires Erlang/OTP 21 or newer"
end
def project do
[
app: :majic,
version: "1.0.0",
elixir: "~> 1.7",
elixirc_paths: elixirc_paths(Mix.env()),
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
start_permanent: Mix.env() == :prod,
compilers: [:elixir_make] ++ Mix.compilers() ++ [:majic],
make_env: make_env(),
package: package(),
deps: deps(),
dialyzer: dialyzer(),
name: "Majic",
description: "File introspection with libmagic",
source_url: "https://github.com/hrefhref/majic",
docs: docs()
]
end
def application do
[mod: {Majic.Application, []}, extra_applications: [:logger]]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp dialyzer do
[
plt_add_apps: [:mix, :iex, :ex_unit, :plug, :mime],
flags: ~w(error_handling no_opaque race_conditions underspecs unmatched_returns)a,
ignore_warnings: "dialyzer-ignore-warnings.exs",
list_unused_filters: true
]
end
defp deps do
[
{:nimble_pool, "~> 0.1"},
{:mime, "~> 1.0"},
+ {:file, github: "hrefhref/file", branch: "master"},
{:plug, "~> 1.0", optional: true},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0.0-rc.6", only: :dev, runtime: false},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:elixir_make, "~> 0.6.1", runtime: false},
- {:libfile,
- github: "file/file", branch: "master", app: false, compile: false, runtime: false, sparse: "magic/"}
]
end
defp package do
[
files: ~w(lib/* src/* Makefile),
licenses: ["Apache 2.0"],
links: %{"GitHub" => "https://github.com/hrefhref/majic"},
source_url: "https://github.com/hrefhref/majic"
]
end
defp docs do
[
main: "readme",
extras: ["README.md", "CHANGELOG.md"]
]
end
defp warnings_as_errors(:dev), do: false
defp warnings_as_errors(_), do: true
defp make_env() do
otp =
:erlang.system_info(:otp_release)
|> to_string()
|> String.to_integer()
ei_incomplete = if(otp < 21.3, do: "YES", else: "NO")
- %{"EI_INCOMPLETE" => ei_incomplete}
+
+ magic_dir = Path.join(Mix.Project.deps_path(), "/file")
+
+ %{"EI_INCOMPLETE" => ei_incomplete,
+ "MAGIC_DEP_DIR" => magic_dir}
end
end
diff --git a/mix.lock b/mix.lock
index 09be50c..8d7b401 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,21 +1,22 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
"dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
"earmark": {:hex, :earmark, "1.4.5", "62ffd3bd7722fb7a7b1ecd2419ea0b458c356e7168c1f5d65caf09b4fbdd13c8", [:mix], [], "hexpm", "b7d0e6263d83dc27141a523467799a685965bf8b13b6743413f19a7079843f4f"},
"elixir_make": {:hex, :elixir_make, "0.6.1", "8faa29a5597faba999aeeb72bbb9c91694ef8068f0131192fb199f98d32994ef", [:mix], [], "hexpm", "35d33270680f8d839a4003c3e9f43afb595310a592405a00afc12de4c7f55a18"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlexec": {:hex, :erlexec, "1.10.0", "cba7924cf526097d2082ceb0ec34e7db6bca2624b8f3867fb3fa89c4cf25d227", [:rebar3], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"},
"exexec": {:hex, :exexec, "0.2.0", "a6ffc48cba3ac9420891b847e4dc7120692fb8c08c9e82220ebddc0bb8d96103", [:mix], [{:erlexec, "~> 1.10", [hex: :erlexec, repo: "hexpm", optional: false]}], "hexpm"},
+ "file": {:git, "https://github.com/hrefhref/file.git", "5303e5dd02c6424f1431f5502a9770b2ce40c3a0", [branch: "master"]},
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
"libfile": {:git, "https://github.com/file/file.git", "3810fb8881131bae5b4c4c2acb0b5464b35ff1ea", [branch: "master", sparse: "magic/"]},
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
"nimble_pool": {:hex, :nimble_pool, "0.2.3", "4b84df87cf8b40c7363782a99faad6aa2bb0811bcd3d275b5402ae4bab1f1251", [:mix], [], "hexpm", "a6bf677d3499ef1639c42bf16b8a72bf490f5fed70206d5851d43dd750c7eaca"},
"plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 12:09 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39782
Default Alt Text
(17 KB)

Event Timeline