EI wip
This commit is contained in:
parent
0bc39d9ad1
commit
443ce0501f
7 changed files with 327 additions and 118 deletions
6
Makefile
6
Makefile
|
@ -1,8 +1,10 @@
|
||||||
# Apprentice binary
|
# Apprentice binary
|
||||||
|
|
||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -std=c99 -g -Wall -Wextra -Werror
|
ERL_EI_INCLUDE:=$(shell erl -eval 'io:format("~s", [code:lib_dir(erl_interface, include)])' -s init stop -noshell | head -1)
|
||||||
LDFLAGS = -lm -lmagic
|
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
|
HEADER_FILES = src
|
||||||
C_SOURCE_FILES = src/apprentice.c
|
C_SOURCE_FILES = src/apprentice.c
|
||||||
OBJECT_FILES = $(C_SOURCE_FILES:.c=.o)
|
OBJECT_FILES = $(C_SOURCE_FILES:.c=.o)
|
||||||
|
|
|
@ -12,7 +12,7 @@ defmodule GenMagic.Config do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_port_options(options) do
|
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
|
case get_executable_arguments(options) do
|
||||||
[] -> arguments
|
[] -> arguments
|
||||||
|
|
|
@ -168,7 +168,7 @@ defmodule GenMagic.Server do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def starting(:enter, _, %{request: nil, port: nil} = data) do
|
def starting(:enter, x, %{request: nil, port: nil} = data) do
|
||||||
port = Port.open(data.port_name, data.port_options)
|
port = Port.open(data.port_name, data.port_options)
|
||||||
{:keep_state, %{data | port: port}, data.startup_timeout}
|
{:keep_state, %{data | port: port}, data.startup_timeout}
|
||||||
end
|
end
|
||||||
|
@ -184,8 +184,24 @@ defmodule GenMagic.Server do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def starting(:info, {port, {:data, "ok\n"}}, %{port: port} = data) do
|
def starting(:info, {port, {:data, binary}}, %{port: port} = data) do
|
||||||
{:next_state, :available, data}
|
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
|
||||||
|
4 -> :term_error
|
||||||
|
5 -> :ei_error
|
||||||
|
end
|
||||||
|
|
||||||
|
{:stop, {:error, error}, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -196,7 +212,8 @@ defmodule GenMagic.Server do
|
||||||
@doc false
|
@doc false
|
||||||
def available({:call, from}, {:perform, path}, data) do
|
def available({:call, from}, {:perform, path}, data) do
|
||||||
data = %{data | cycles: data.cycles + 1, request: {path, from, :erlang.now()}}
|
data = %{data | cycles: data.cycles + 1, request: {path, from, :erlang.now()}}
|
||||||
_ = send(data.port, {self(), {:command, "file; " <> path <> "\n"}})
|
command = :erlang.term_to_binary({:file, path})
|
||||||
|
_ = send(data.port, {self(), {:command, command}})
|
||||||
{:next_state, :processing, data}
|
{:next_state, :processing, data}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -231,7 +248,7 @@ defmodule GenMagic.Server do
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def recycling(:enter, _, %{request: nil, port: port} = data) when is_port(port) do
|
def recycling(:enter, _, %{request: nil, port: port} = data) when is_port(port) do
|
||||||
_ = send(data.port, {self(), :close})
|
_ = send(data.port, {self(), {:command, :erlang.term_to_binary({:stop, :recycle})}})
|
||||||
{:keep_state_and_data, data.startup_timeout}
|
{:keep_state_and_data, data.startup_timeout}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -246,19 +263,26 @@ defmodule GenMagic.Server do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@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}}
|
{:next_state, :starting, %{data | port: nil, cycles: 0}}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_response("ok; " <> message) do
|
@errnos %{
|
||||||
case message |> String.trim() |> String.split("\t") do
|
2 => :enoent,
|
||||||
[mime_type, encoding, content] -> {:ok, Result.build(mime_type, encoding, content)}
|
13 => :eaccess,
|
||||||
_ -> {:error, :malformed_response}
|
21 => :eisdir,
|
||||||
end
|
20 => :enotdir,
|
||||||
end
|
12 => :enomem,
|
||||||
|
24 => :emfile
|
||||||
|
}
|
||||||
|
@errno Map.keys(@errnos)
|
||||||
|
|
||||||
defp handle_response("error; " <> message) do
|
defp handle_response(data) do
|
||||||
{:error, String.trim(message)}
|
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
|
end
|
||||||
|
|
||||||
defp handle_status_call(from, state, data) do
|
defp handle_status_call(from, state, data) do
|
||||||
|
|
276
src/apprentice.c
276
src/apprentice.c
|
@ -13,16 +13,18 @@
|
||||||
// Where each argument either refers to a compiled or uncompiled magic database, or the default
|
// Where each argument either refers to a compiled or uncompiled magic database, or the default
|
||||||
// database. They will be loaded in the sequence that they were specified. Note that you must
|
// database. They will be loaded in the sequence that they were specified. Note that you must
|
||||||
// specify at least one database.
|
// specify at least one database.
|
||||||
|
// Erlang Term
|
||||||
//
|
//
|
||||||
// Once the program starts, it will print info statements if run from a terminal, then it will
|
// -- main: send atom ready
|
||||||
// print `ok`. From this point onwards, additional commands can be passed:
|
// enter loop
|
||||||
//
|
|
||||||
// file; <path>
|
|
||||||
//
|
//
|
||||||
// Results will be printed tab-separated, e.g.:
|
// -- while
|
||||||
|
// get {:file, path} -> process_file -> ok | error
|
||||||
|
// {:bytes, path} -> process_bytes -> ok | error
|
||||||
|
// ok: {:ok, {type, encoding, name}}
|
||||||
|
// error: {:error, :badarg} | {:error, {errno, String.t()}}
|
||||||
|
// {:stop, _} -> exit(ERROR_OK) -> exit 0
|
||||||
//
|
//
|
||||||
// ok; application/zip binary Zip archive data, at least v1.0 to extract
|
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
|
@ -33,8 +35,8 @@
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <ei.h>
|
||||||
#include <magic.h>
|
#include <magic.h>
|
||||||
|
|
||||||
#define USAGE "[--database-file <path/to/magic.mgc> | --database-default, ...]"
|
#define USAGE "[--database-file <path/to/magic.mgc> | --database-default, ...]"
|
||||||
#define DELIMITER "\t"
|
#define DELIMITER "\t"
|
||||||
|
|
||||||
|
@ -42,6 +44,8 @@
|
||||||
#define ERROR_NO_DATABASE 1
|
#define ERROR_NO_DATABASE 1
|
||||||
#define ERROR_NO_ARGUMENT 2
|
#define ERROR_NO_ARGUMENT 2
|
||||||
#define ERROR_MISSING_DATABASE 3
|
#define ERROR_MISSING_DATABASE 3
|
||||||
|
#define ERROR_BAD_TERM 4
|
||||||
|
#define ERROR_EI 5
|
||||||
|
|
||||||
#define ANSI_INFO "\x1b[37m" // gray
|
#define ANSI_INFO "\x1b[37m" // gray
|
||||||
#define ANSI_OK "\x1b[32m" // green
|
#define ANSI_OK "\x1b[32m" // green
|
||||||
|
@ -52,16 +56,20 @@
|
||||||
#define MAGIC_FLAGS_COMMON (MAGIC_CHECK|MAGIC_ERROR)
|
#define MAGIC_FLAGS_COMMON (MAGIC_CHECK|MAGIC_ERROR)
|
||||||
magic_t magic_setup(int flags);
|
magic_t magic_setup(int flags);
|
||||||
|
|
||||||
|
typedef char byte;
|
||||||
|
|
||||||
|
int read_cmd(byte *buf);
|
||||||
|
int write_cmd(byte *buf, int len);
|
||||||
|
|
||||||
void setup_environment();
|
void setup_environment();
|
||||||
void setup_options(int argc, char **argv);
|
void setup_options(int argc, char **argv);
|
||||||
void setup_options_file(char *optarg);
|
void setup_options_file(char *optarg);
|
||||||
void setup_options_default();
|
void setup_options_default();
|
||||||
void setup_system();
|
void setup_system();
|
||||||
|
int process_command(byte *buf);
|
||||||
void process_line(char *line);
|
void process_line(char *line);
|
||||||
void process_file(char *path);
|
void process_file(char *path, ei_x_buff *result);
|
||||||
void print_info(const char *format, ...);
|
void error(ei_x_buff *result, const char *error);
|
||||||
void print_ok(const char *format, ...);
|
|
||||||
void print_error(const char *format, ...);
|
|
||||||
|
|
||||||
struct magic_file {
|
struct magic_file {
|
||||||
struct magic_file *prev;
|
struct magic_file *prev;
|
||||||
|
@ -75,20 +83,84 @@ static magic_t magic_mime_encoding; // MAGIC_MIME_ENCODING
|
||||||
static magic_t magic_type_name; // MAGIC_NONE
|
static magic_t magic_type_name; // MAGIC_NONE
|
||||||
|
|
||||||
int main (int argc, char **argv) {
|
int main (int argc, char **argv) {
|
||||||
|
ei_init();
|
||||||
setup_environment();
|
setup_environment();
|
||||||
setup_options(argc, argv);
|
setup_options(argc, argv);
|
||||||
setup_system();
|
setup_system();
|
||||||
printf("ok\n");
|
|
||||||
fflush(stdout);
|
|
||||||
|
|
||||||
char line[4096];
|
ei_x_buff ok_buf;
|
||||||
while (fgets(line, 4096, stdin)) {
|
if (ei_x_new_with_version(&ok_buf) || ei_x_encode_atom(&ok_buf, "ready")) return 5;
|
||||||
process_line(line);
|
write_cmd(ok_buf.buff, ok_buf.index);
|
||||||
|
if (ei_x_free(&ok_buf) != 0)
|
||||||
|
exit(ERROR_EI);
|
||||||
|
|
||||||
|
byte buf[5000];
|
||||||
|
while (read_cmd(buf) > 0) {
|
||||||
|
process_command(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int process_command(byte *buf) {
|
||||||
|
ei_x_buff result;
|
||||||
|
char atom[128];
|
||||||
|
int index, version, arity;
|
||||||
|
index = 0;
|
||||||
|
|
||||||
|
if (ei_decode_version(buf, &index, &version) != 0)
|
||||||
|
exit(ERROR_BAD_TERM);
|
||||||
|
|
||||||
|
// Initialize result
|
||||||
|
if (ei_x_new_with_version(&result) || ei_x_encode_tuple_header(&result, 2)) exit(ERROR_EI);
|
||||||
|
|
||||||
|
if (ei_decode_tuple_header(buf, &index, &arity) != 0) {
|
||||||
|
error(&result, "badarg");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arity != 2) {
|
||||||
|
error(&result, "badarg");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ei_decode_atom(buf, &index, atom) != 0) {
|
||||||
|
error(&result, "badarg");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(atom, "file", 3) == 0) {
|
||||||
|
int pathtype;
|
||||||
|
int pathsize;
|
||||||
|
char path[4097];
|
||||||
|
ei_get_type(buf, &index, &pathtype, &pathsize);
|
||||||
|
|
||||||
|
if (pathtype == ERL_BINARY_EXT && pathsize < 4096) {
|
||||||
|
long bin_length;
|
||||||
|
ei_decode_binary(buf, &index, path, &bin_length);
|
||||||
|
path[pathsize] = '\0';
|
||||||
|
process_file(path, &result);
|
||||||
|
} else {
|
||||||
|
error(&result, "badarg");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if (strncmp(atom, "bytes", 3) == 0) {
|
||||||
|
ei_x_encode_atom(&result, "ok");
|
||||||
|
ei_x_encode_atom(&result, "bytes_not_implemented");
|
||||||
|
} else if (strncmp(atom, "stop", 3) == 0) {
|
||||||
|
exit(ERROR_OK);
|
||||||
|
} else {
|
||||||
|
error(&result, "badarg");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_cmd(result.buff, result.index);
|
||||||
|
|
||||||
|
if (ei_x_free(&result) != 0)
|
||||||
|
exit(ERROR_EI);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void setup_environment() {
|
void setup_environment() {
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +191,6 @@ void setup_options(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
case '?':
|
case '?':
|
||||||
default: {
|
default: {
|
||||||
print_info("%s %s\n", basename(argv[0]), USAGE);
|
|
||||||
exit(ERROR_NO_ARGUMENT);
|
exit(ERROR_NO_ARGUMENT);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -128,9 +199,7 @@ void setup_options(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_options_file(char *optarg) {
|
void setup_options_file(char *optarg) {
|
||||||
print_info("Requested database %s", optarg);
|
|
||||||
if (0 != access(optarg, R_OK)) {
|
if (0 != access(optarg, R_OK)) {
|
||||||
print_error("Missing Database");
|
|
||||||
exit(ERROR_MISSING_DATABASE);
|
exit(ERROR_MISSING_DATABASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +216,6 @@ void setup_options_file(char *optarg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_options_default() {
|
void setup_options_default() {
|
||||||
print_info("requested default database");
|
|
||||||
|
|
||||||
struct magic_file *next = malloc(sizeof(struct magic_file));
|
struct magic_file *next = malloc(sizeof(struct magic_file));
|
||||||
next->path = NULL;
|
next->path = NULL;
|
||||||
|
@ -165,12 +233,10 @@ void setup_system() {
|
||||||
}
|
}
|
||||||
|
|
||||||
magic_t magic_setup(int flags) {
|
magic_t magic_setup(int flags) {
|
||||||
print_info("starting libmagic instance for flags %i", flags);
|
|
||||||
|
|
||||||
magic_t magic = magic_open(flags);
|
magic_t magic = magic_open(flags);
|
||||||
struct magic_file *current_database = magic_database;
|
struct magic_file *current_database = magic_database;
|
||||||
if (!current_database) {
|
if (!current_database) {
|
||||||
print_error("no database configured");
|
|
||||||
exit(ERROR_NO_DATABASE);
|
exit(ERROR_NO_DATABASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,14 +244,6 @@ magic_t magic_setup(int flags) {
|
||||||
current_database = current_database->prev;
|
current_database = current_database->prev;
|
||||||
}
|
}
|
||||||
while (current_database) {
|
while (current_database) {
|
||||||
if (isatty(STDERR_FILENO)) {
|
|
||||||
fprintf(stderr, ANSI_IGNORE);
|
|
||||||
}
|
|
||||||
if (!current_database->path) {
|
|
||||||
print_info("loading default database");
|
|
||||||
} else {
|
|
||||||
print_info("loading database %s", current_database->path);
|
|
||||||
}
|
|
||||||
magic_load(magic, current_database->path);
|
magic_load(magic, current_database->path);
|
||||||
if (isatty(STDERR_FILENO)) {
|
if (isatty(STDERR_FILENO)) {
|
||||||
fprintf(stderr, ANSI_RESET);
|
fprintf(stderr, ANSI_RESET);
|
||||||
|
@ -195,90 +253,110 @@ magic_t magic_setup(int flags) {
|
||||||
return magic;
|
return magic;
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_line(char *line) {
|
void process_file(char *path, ei_x_buff *result) {
|
||||||
char path[4096];
|
|
||||||
|
|
||||||
if (0 == strcmp(line, "exit\n")) {
|
|
||||||
exit(ERROR_OK);
|
|
||||||
}
|
|
||||||
if (1 != sscanf(line, "file; %[^\n]s", path)) {
|
|
||||||
print_error("invalid commmand");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 != access(path, R_OK)) {
|
|
||||||
print_error("unable to access file");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_file(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_file(char *path) {
|
|
||||||
const char *mime_type_result = magic_file(magic_mime_type, path);
|
const char *mime_type_result = magic_file(magic_mime_type, path);
|
||||||
const char *mime_type_error = magic_error(magic_mime_type);
|
const char *mime_type_error = magic_error(magic_mime_type);
|
||||||
const char *mine_encoding_result = magic_file(magic_mime_encoding, path);
|
int mime_type_errno = magic_errno(magic_mime_type);
|
||||||
const char *mine_encoding_error = magic_error(magic_mime_encoding);
|
|
||||||
|
if (mime_type_errno > 0) {
|
||||||
|
ei_x_encode_atom(result, "error");
|
||||||
|
ei_x_encode_tuple_header(result, 2);
|
||||||
|
long errlon = (long)mime_type_errno;
|
||||||
|
ei_x_encode_long(result, errlon);
|
||||||
|
ei_x_encode_binary(result, mime_type_error, strlen(mime_type_error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *mime_encoding_result = magic_file(magic_mime_encoding, path);
|
||||||
|
const char *mime_encoding_error = magic_error(magic_mime_encoding);
|
||||||
|
int mime_encoding_errno = magic_errno(magic_mime_encoding);
|
||||||
|
|
||||||
|
if (mime_encoding_error) {
|
||||||
|
ei_x_encode_atom(result, "error");
|
||||||
|
ei_x_encode_tuple_header(result, 2);
|
||||||
|
long errlon = (long)mime_encoding_errno;
|
||||||
|
ei_x_encode_long(result, errlon);
|
||||||
|
ei_x_encode_binary(result, mime_encoding_error, strlen(mime_encoding_error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const char *type_name_result = magic_file(magic_type_name, path);
|
const char *type_name_result = magic_file(magic_type_name, path);
|
||||||
const char *type_name_error = magic_error(magic_type_name);
|
const char *type_name_error = magic_error(magic_type_name);
|
||||||
|
int type_name_errno = magic_errno(magic_type_name);
|
||||||
if (mime_type_error) {
|
|
||||||
print_error(mime_type_error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mine_encoding_error) {
|
|
||||||
print_error(mine_encoding_error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type_name_error) {
|
if (type_name_error) {
|
||||||
print_error(type_name_error);
|
ei_x_encode_atom(result, "error");
|
||||||
|
ei_x_encode_tuple_header(result, 2);
|
||||||
|
long errlon = (long)type_name_errno;
|
||||||
|
ei_x_encode_long(result, errlon);
|
||||||
|
ei_x_encode_binary(result, type_name_error, strlen(type_name_error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print_ok("%s%s%s%s%s", mime_type_result, DELIMITER, mine_encoding_result, DELIMITER, type_name_result);
|
ei_x_encode_atom(result, "ok");
|
||||||
|
ei_x_encode_tuple_header(result, 3);
|
||||||
|
ei_x_encode_binary(result, mime_type_result, strlen(mime_type_result));
|
||||||
|
ei_x_encode_binary(result, mime_encoding_result, strlen(mime_encoding_result));
|
||||||
|
ei_x_encode_binary(result, type_name_result, strlen(type_name_result));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_info(const char *format, ...) {
|
// From https://erlang.org/doc/tutorial/erl_interface.html
|
||||||
if (!isatty(STDOUT_FILENO)) {
|
int read_exact(byte *buf, int len)
|
||||||
return;
|
{
|
||||||
}
|
int i, got=0;
|
||||||
|
|
||||||
printf(ANSI_INFO "info; " ANSI_RESET);
|
do {
|
||||||
va_list arguments;
|
if ((i = read(0, buf+got, len-got)) <= 0){
|
||||||
va_start(arguments, format);
|
return(i);
|
||||||
vprintf(format, arguments);
|
}
|
||||||
va_end(arguments);
|
got += i;
|
||||||
printf("\n");
|
} while (got<len);
|
||||||
|
|
||||||
|
return(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_ok(const char *format, ...) {
|
int write_exact(byte *buf, int len)
|
||||||
if (isatty(STDOUT_FILENO)) {
|
{
|
||||||
printf(ANSI_OK "ok; " ANSI_RESET);
|
int i, wrote = 0;
|
||||||
} else {
|
|
||||||
printf("ok; ");
|
|
||||||
}
|
|
||||||
|
|
||||||
va_list arguments;
|
do {
|
||||||
va_start(arguments, format);
|
if ((i = write(1, buf+wrote, len-wrote)) <= 0)
|
||||||
vprintf(format, arguments);
|
return (i);
|
||||||
va_end(arguments);
|
wrote += i;
|
||||||
printf("\n");
|
} while (wrote<len);
|
||||||
fflush(stdout);
|
|
||||||
|
return (len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_error(const char *format, ...) {
|
int read_cmd(byte *buf)
|
||||||
if (isatty(STDERR_FILENO)) {
|
{
|
||||||
fprintf(stderr, ANSI_ERROR "error; " ANSI_RESET);
|
int len;
|
||||||
} else {
|
|
||||||
fprintf(stderr, "error; ");
|
|
||||||
}
|
|
||||||
|
|
||||||
va_list arguments;
|
if (read_exact(buf, 2) != 2)
|
||||||
va_start(arguments, format);
|
return(-1);
|
||||||
vfprintf(stderr, format, arguments);
|
len = (buf[0] << 8) | buf[1];
|
||||||
va_end(arguments);
|
return read_exact(buf, len);
|
||||||
fprintf(stderr, "\n");
|
}
|
||||||
fflush(stderr);
|
|
||||||
|
int write_cmd(byte *buf, int len)
|
||||||
|
{
|
||||||
|
byte li;
|
||||||
|
|
||||||
|
li = (len >> 8) & 0xff;
|
||||||
|
write_exact(&li, 1);
|
||||||
|
|
||||||
|
li = len & 0xff;
|
||||||
|
write_exact(&li, 1);
|
||||||
|
|
||||||
|
return write_exact(buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error(ei_x_buff *result, const char *error) {
|
||||||
|
ei_x_encode_atom(result, "error");
|
||||||
|
ei_x_encode_atom(result, error);
|
||||||
|
write_cmd(result->buff, result->index);
|
||||||
|
|
||||||
|
if (ei_x_free(result) != 0)
|
||||||
|
exit(ERROR_EI);
|
||||||
}
|
}
|
||||||
|
|
101
test/gen_magic/apprentice_test.exs
Normal file
101
test/gen_magic/apprentice_test.exs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
defmodule GenMagic.ApprenticeTest do
|
||||||
|
use GenMagic.MagicCase
|
||||||
|
|
||||||
|
test "sends ready" do
|
||||||
|
port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([]))
|
||||||
|
assert_ready(port)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "stops" do
|
||||||
|
port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([]))
|
||||||
|
assert_ready(port)
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary({:stop, :stop})}})
|
||||||
|
assert_receive {^port, {:exit_status, 0}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "exits with no database" do
|
||||||
|
opts = [:use_stdio, :binary, :exit_status, {:packet, 2}, {:args, []}]
|
||||||
|
port = Port.open(GenMagic.Config.get_port_name(), opts)
|
||||||
|
assert_receive {^port, {:exit_status, 1}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "exits with a non existent database" do
|
||||||
|
opts = [
|
||||||
|
{:args, ["--database-file", "/no/such/database"]},
|
||||||
|
:use_stdio,
|
||||||
|
:binary,
|
||||||
|
:exit_status,
|
||||||
|
{:packet, 2}
|
||||||
|
]
|
||||||
|
|
||||||
|
port = Port.open(GenMagic.Config.get_port_name(), opts)
|
||||||
|
assert_receive {^port, {:exit_status, 3}}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "port" do
|
||||||
|
setup do
|
||||||
|
port = Port.open(GenMagic.Config.get_port_name(), GenMagic.Config.get_port_options([]))
|
||||||
|
assert_ready(port)
|
||||||
|
%{port: port}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "exits with badly formatted erlang terms", %{port: port} do
|
||||||
|
send(port, {self(), {:command, "i forgot to term_to_binary!!"}})
|
||||||
|
assert_receive {^port, {:exit_status, 4}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "errors with wrong command", %{port: port} do
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary(:wrong)}})
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert {:error, :badarg} = :erlang.binary_to_term(data)
|
||||||
|
refute_receive _
|
||||||
|
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary({:file, 42})}})
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert {:error, :badarg} = :erlang.binary_to_term(data)
|
||||||
|
refute_receive _
|
||||||
|
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary("more wrong")}})
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert {:error, :badarg} = :erlang.binary_to_term(data)
|
||||||
|
refute_receive _
|
||||||
|
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary({"no", "no"})}})
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert {:error, :badarg} = :erlang.binary_to_term(data)
|
||||||
|
refute_receive _
|
||||||
|
end
|
||||||
|
|
||||||
|
test "works", %{port: port} do
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary({:file, Path.expand("Makefile")})}})
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert {:ok, _} = :erlang.binary_to_term(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails with non existent file", %{port: port} do
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary({:file, "/path/to/nowhere"})}})
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert {:error, _} = :erlang.binary_to_term(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "works with a 4096 path", %{port: port} do
|
||||||
|
file = too_big() <> "/a"
|
||||||
|
File.mkdir_p!(too_big())
|
||||||
|
File.touch!(file)
|
||||||
|
on_exit(fn -> File.rm_rf!("/tmp/testmagicex/") end)
|
||||||
|
send(port, {self(), {:command, :erlang.term_to_binary({:file, file})}})
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert {:ok, _} = :erlang.binary_to_term(data)
|
||||||
|
refute_receive _
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_ready(port) do
|
||||||
|
assert_receive {^port, {:data, data}}
|
||||||
|
assert :ready == :erlang.binary_to_term(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def too_big() do
|
||||||
|
"/tmp/testmagicex/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,6 +12,7 @@ defmodule GenMagic.ServerTest do
|
||||||
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
||||||
assert {:ok, %{cycles: 2}} = GenMagic.Server.status(pid)
|
assert {:ok, %{cycles: 2}} = GenMagic.Server.status(pid)
|
||||||
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
||||||
|
Process.sleep(100)
|
||||||
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -20,10 +21,13 @@ defmodule GenMagic.ServerTest do
|
||||||
path = absolute_path("Makefile")
|
path = absolute_path("Makefile")
|
||||||
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
||||||
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
||||||
|
Process.sleep(100)
|
||||||
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
||||||
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
||||||
|
Process.sleep(100)
|
||||||
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
||||||
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
assert {:ok, _} = GenMagic.Server.perform(pid, path)
|
||||||
|
Process.sleep(100)
|
||||||
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
assert {:ok, %{cycles: 0}} = GenMagic.Server.status(pid)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,8 +20,8 @@ defmodule GenMagic.MagicCase do
|
||||||
|> Stream.flat_map(&Enum.shuffle/1)
|
|> Stream.flat_map(&Enum.shuffle/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_no_file({:error, message}) do
|
def assert_no_file(message) do
|
||||||
assert "unable to access file" = message
|
assert {:error, :enoent} = message
|
||||||
end
|
end
|
||||||
|
|
||||||
def absolute_path(path) do
|
def absolute_path(path) do
|
||||||
|
|
Loading…
Reference in a new issue