Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Apprentice binary

CC = gcc
CFLAGS = -std=c99 -g -Wall -Wextra -Werror
LDFLAGS = -lm -lmagic
ERL_EI_INCLUDE:=$(shell erl -eval 'io:format("~s", [code:lib_dir(erl_interface, include)])' -s init stop -noshell | head -1)
ERL_EI_LIB:=$(shell erl -eval 'io:format("~s", [code:lib_dir(erl_interface, lib)])' -s init stop -noshell | head -1)
CFLAGS = -std=c99 -g -Wall -Wextra -Werror -I$(ERL_EI_INCLUDE)
LDFLAGS = -L/usr/include/linux/ -L$(ERL_EI_LIB) -lm -lmagic -lei -lpthread
HEADER_FILES = src
C_SOURCE_FILES = src/apprentice.c
OBJECT_FILES = $(C_SOURCE_FILES:.c=.o)
Expand Down
2 changes: 1 addition & 1 deletion lib/gen_magic/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule GenMagic.Config do
end

def get_port_options(options) do
arguments = [:use_stdio, :stderr_to_stdout, :binary, :exit_status]
arguments = [:use_stdio, :binary, :exit_status, {:packet, 2}]

case get_executable_arguments(options) do
[] -> arguments
Expand Down
54 changes: 40 additions & 14 deletions lib/gen_magic/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule GenMagic.Server do
alias GenMagic.Result
alias GenMagic.Server.Data
alias GenMagic.Server.Status
import Kernel, except: [send: 2]

@typedoc """
Represents the reference to the underlying server, as returned by `:gen_statem`.
Expand Down Expand Up @@ -72,15 +73,15 @@ defmodule GenMagic.Server do
- `:recycling`: This is the state the Server will be in, if its underlying C program needs to be
recycled. This state is triggered whenever the cycle count reaches the defined value as per
`:recycle_threshold`.

In this state, the Server is able to accept requests, but they will not be processed until the
underlying C server program has been started again.
"""
@type state :: :starting | :processing | :available | :recycling

@spec child_spec([option()]) :: Supervisor.child_spec()
@spec start_link([option()]) :: :gen_statem.start_ret()
@spec perform(t(), Path.t(), timeout()) :: {:ok, Result.t()} | {:error, term()}
@spec perform(t(), Path.t(), timeout()) :: {:ok, Result.t()} | {:error, term() | String.t()}
@spec status(t(), timeout()) :: {:ok, Status.t()} | {:error, term()}
@spec stop(t(), term(), timeout()) :: :ok

Expand Down Expand Up @@ -184,8 +185,22 @@ defmodule GenMagic.Server do
end

@doc false
def starting(:info, {port, {:data, "ok\n"}}, %{port: port} = data) do
{:next_state, :available, data}
def starting(:info, {port, {:data, binary}}, %{port: port} = data) do
case :erlang.binary_to_term(binary) do
:ready ->
{:next_state, :available, data}
end
end

def starting(:info, {port, {:exit_status, code}}, %{port: port} = data) do
error =
case code do
1 -> :no_database
2 -> :no_argument
3 -> :missing_database
end

{:stop, {:error, error}, data}
end

@doc false
Expand All @@ -196,7 +211,7 @@ defmodule GenMagic.Server do
@doc false
def available({:call, from}, {:perform, path}, data) do
data = %{data | cycles: data.cycles + 1, request: {path, from, :erlang.now()}}
_ = send(data.port, {self(), {:command, "file; " <> path <> "\n"}})
send(data.port, {:file, path})
{:next_state, :processing, data}
end

Expand Down Expand Up @@ -231,7 +246,7 @@ defmodule GenMagic.Server do

@doc false
def recycling(:enter, _, %{request: nil, port: port} = data) when is_port(port) do
_ = send(data.port, {self(), :close})
send(data.port, {:stop, :recycle})
{:keep_state_and_data, data.startup_timeout}
end

Expand All @@ -246,19 +261,30 @@ defmodule GenMagic.Server do
end

@doc false
def recycling(:info, {port, :closed}, %{port: port} = data) do
def recycling(:info, {port, {:exit_status, 0}}, %{port: port} = data) do
{:next_state, :starting, %{data | port: nil, cycles: 0}}
end

defp handle_response("ok; " <> message) do
case message |> String.trim() |> String.split("\t") do
[mime_type, encoding, content] -> {:ok, Result.build(mime_type, encoding, content)}
_ -> {:error, :malformed_response}
end
defp send(port, command) do
Kernel.send(port, {self(), {:command, :erlang.term_to_binary(command)}})
end

defp handle_response("error; " <> message) do
{:error, String.trim(message)}
@errnos %{
2 => :enoent,
13 => :eaccess,
21 => :eisdir,
20 => :enotdir,
12 => :enomem,
24 => :emfile
}
@errno Map.keys(@errnos)

defp handle_response(data) do
case :erlang.binary_to_term(data) do
{:ok, {mime_type, encoding, content}} -> {:ok, Result.build(mime_type, encoding, content)}
{:error, {errno, _}} when errno in @errno -> {:error, @errnos[errno]}
{:error, {errno, string}} -> {:error, "#{errno}: #{string}"}
end
end

defp handle_status_call(from, state, data) do
Expand Down
Loading