diff --git a/.gitignore b/.gitignore index 805f409..9b12c53 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ c_src/alcove_call.h c_src/alcove_calls.h c_src/alcove_version.h src/alcove.erl +src/alcove_proto.erl diff --git a/bin/alcove.escript b/bin/alcove.escript index fb72578..11200eb 100755 --- a/bin/alcove.escript +++ b/bin/alcove.escript @@ -32,16 +32,6 @@ license() -> erl_syntax:comment(License). -api(Proto) -> - Calls = calls(Proto), - - - % Generate the function - Pattern = [], - Body = erl_syntax:tuple([ erl_syntax:atom(N) || {N,_} <- Calls ]), - Clause = erl_syntax:clause(Pattern, [], [Body]), - [erl_syntax:function(erl_syntax:atom("api"), [Clause])]. - mkerl(File, Proto) -> Module = erl_syntax:attribute( erl_syntax:atom(module), @@ -120,7 +110,6 @@ mkerl(File, Proto) -> Exports_gen1, Static, - api(Proto), Functions ]))), @@ -163,8 +152,6 @@ static_exports() -> {stderr,1}, {stderr,2}, {stderr,3}, {eof,2}, {eof,3}, {event,1}, {event,2}, {event,3}, - {encode,3}, - {command,1}, {call,2}, {call,3}, {call,4}, {call,5}]. static() -> @@ -315,27 +302,6 @@ event(Drv, Pids, Timeout) -> alcove_drv:event(Drv, Pids, Timeout). "; -static({encode,3}) -> -" -encode(Call, Pids, Arg) when is_atom(Call), is_list(Pids), is_list(Arg) -> - Bin = alcove_drv:encode(command(Call), Arg), - alcove_drv:msg(Pids, Bin). -"; - -static({command,1}) -> -" -command(Cmd) when is_atom(Cmd) -> - lookup(Cmd, api()). - -lookup(Cmd, Cmds) -> - lookup(Cmd, 1, Cmds, tuple_size(Cmds)). -lookup(Cmd, N, Cmds, _Max) when Cmd =:= element(N, Cmds) -> - % Convert to 0 offset - N-1; -lookup(Cmd, N, Cmds, Max) when N =< Max -> - lookup(Cmd, N+1, Cmds, Max). -"; - static({call,2}) -> " call(Drv, Command) -> @@ -354,8 +320,7 @@ call(Drv, Pids, Command, Argv) -> static({call,5}) -> " call(Drv, Pids, Command, Argv, Timeout) when is_pid(Drv), is_list(Argv) -> - case alcove_drv:call(Drv, Pids, encode(Command, Pids, Argv), - call_returns(Command), Timeout) of + case alcove_drv:call(Drv, Pids, Command, Argv, Timeout) of badarg -> erlang:error(badarg, [Drv, Command, Argv]); undef -> @@ -363,11 +328,6 @@ call(Drv, Pids, Command, Argv, Timeout) when is_pid(Drv), is_list(Argv) -> Reply -> Reply end. - -call_returns(execve) -> false; -call_returns(execvp) -> false; -call_returns(exit) -> false; -call_returns(_) -> true. ". includes(Header) -> diff --git a/bin/alcove_proto.escript b/bin/alcove_proto.escript new file mode 100755 index 0000000..a75122c --- /dev/null +++ b/bin/alcove_proto.escript @@ -0,0 +1,140 @@ +#!/usr/bin/env escript + +%%% +%%% Generate the alcove.erl file +%%% +main([]) -> + File = "alcove_proto.erl", + Proto = "c_src/alcove_call.proto", + main([File, Proto]); + +main([File, Proto]) -> + mkerl(File, Proto). + +license() -> + {{Year,_,_},{_,_,_}} = calendar:universal_time(), + + Date = integer_to_list(Year), + + License = [ +" Copyright (c) " ++ Date ++ ", Michael Santos ", +" Permission to use, copy, modify, and/or distribute this software for any", +" purpose with or without fee is hereby granted, provided that the above", +" copyright notice and this permission notice appear in all copies.", +"", +" THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES", +" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF", +" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR", +" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES", +" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN", +" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF", +" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE."], + + erl_syntax:comment(License). + +mkerl(File, Proto) -> + Module = erl_syntax:attribute( + erl_syntax:atom(module), + [erl_syntax:atom(filename:basename(File, ".erl"))] + ), + Includes = includes(["alcove.hrl"]), + + % Type specs + Specs = erl_syntax:comment(["%__SPECS__%%"]), + + % Any hardcoded functions will be included here + Static = erl_syntax:comment(["%__STATIC__%%"]), + + % Generate the list of exports + Comment_static = erl_syntax:comment([" Static functions"]), + Exports_static = erl_syntax:attribute(erl_syntax:atom(export), [ + erl_syntax:list([ + erl_syntax:arity_qualifier(erl_syntax:atom(Fun), erl_syntax:integer(Arity)) + || {Fun, Arity} <- static_exports() ]) + ]), + + Comment_gen = erl_syntax:comment([" Generated functions"]), + Exports_gen = erl_syntax:attribute(erl_syntax:atom(export), [ + erl_syntax:list([ + erl_syntax:arity_qualifier(erl_syntax:atom(calls), erl_syntax:integer(0)) + ])]), + + Body = erl_syntax:list([ erl_syntax:atom(N) || {N,_} <- proto(Proto) ]), + Clause = erl_syntax:clause([], [], [Body]), + Fun = [erl_syntax:function(erl_syntax:atom("calls"), [Clause])], + + Code0 = erl_prettypr:format(erl_syntax:form_list(lists:flatten([ + license(), + Module, + Includes, + + Specs, + + Comment_static, + Exports_static, + + Comment_gen, + Exports_gen, + + Static, + Fun + ]))), + + Code = lists:foldl(fun({Marker, Generated}, Text) -> + re:replace(Text, Marker, Generated) + end, + Code0, + [ + {"%%__STATIC__%%", static()}, + {"%%__SPECS__%%", specs()} + ]), + +% io:format("~s~n", [Code]). + file:write_file(File, [Code]). + +% List the supported alcove API functions +proto(Proto) -> + {ok, Bin} = file:read_file(Proto), + Fun = binary:split(Bin, <<"\n">>, [trim,global]), + call_to_fun(Fun, []). + +call_to_fun([], Acc) -> + lists:reverse(Acc); +call_to_fun([H|T], Acc) -> + [Fun, Arity] = binary:split(H, <<"/">>), + call_to_fun(T, [{binary_to_list(Fun), b2i(Arity)}|Acc]). + +b2i(N) when is_binary(N) -> + list_to_integer(binary_to_list(N)). + +static_exports() -> + [{call,1},{returns,1}]. + +static() -> + [ static({Fun, Arity}) || {Fun, Arity} <- static_exports() ]. + +static({call,1}) -> +" +call(Call) when is_atom(Call) -> + lookup(Call, calls(), 0). + +lookup(Call, [Call|_], N) -> + N; +lookup(Call, [_|Calls], N) -> + lookup(Call, Calls, N+1). +"; + +static({returns,1}) -> +" +returns(execve) -> false; +returns(execvp) -> false; +returns(exit) -> false; +returns(_) -> true. +". + +includes(Header) -> + [ erl_syntax:attribute(erl_syntax:atom(include), [erl_syntax:string(N)]) || N <- Header ]. + +% FIXME hack for hard coding typespecs +specs() -> +"". diff --git a/rebar.config b/rebar.config index b4c4cbb..236118f 100644 --- a/rebar.config +++ b/rebar.config @@ -33,6 +33,7 @@ {compile, "bin/alcove_version.escript c_src/alcove_version.h"}, {compile, "bin/alcove_calls.sh c_src/alcove_call.proto > c_src/alcove_calls.h"}, {compile, "bin/alcove_call.sh c_src/alcove_call.proto > c_src/alcove_call.h"}, + {compile, "bin/alcove_proto.escript src/alcove_proto.erl c_src/alcove_call.proto"}, {compile, "bin/alcove.escript src/alcove.erl c_src/alcove_call.proto"} ]}. diff --git a/src/alcove_codec.erl b/src/alcove_codec.erl new file mode 100644 index 0000000..61bd9dd --- /dev/null +++ b/src/alcove_codec.erl @@ -0,0 +1,97 @@ +%%% Copyright (c) 2014, Michael Santos +%%% +%%% Permission to use, copy, modify, and/or distribute this software for any +%%% purpose with or without fee is hereby granted, provided that the above +%%% copyright notice and this permission notice appear in all copies. +%%% +%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-module(alcove_codec). +-include_lib("alcove/include/alcove.hrl"). + +-export([call/3, stdin/2]). +-export([encode/2, encode/3, decode/1]). + +call(Call, Pids, Arg) -> + Bin = encode(alcove_proto:call(Call), Arg), + Payload = case Pids of + [] -> + Bin; + _ -> + Size = iolist_size(Bin), + [<>, Bin] + end, + stdin(Pids, Payload). + +stdin([], Data) -> + Data; +stdin(Pids, Data) -> + hdr(Pids, Data). + +hdr(Pids, Data) -> + hdr_1(lists:reverse(Pids), Data). + +hdr_1([], [_Length|Acc]) -> + Acc; +hdr_1([Pid|Pids], Acc) -> + Size = iolist_size(Acc) + 2 + 4, + hdr_1(Pids, [<>, <>, <>|Acc]). + +encode(Command, Arg) when is_integer(Command), is_list(Arg) -> + encode(?ALCOVE_MSG_CALL, Command, Arg). +encode(Type, Command, Arg) when is_integer(Type), is_integer(Command), is_list(Arg) -> + << + ?UINT16(Type), + ?UINT16(Command), + (term_to_binary(list_to_tuple(Arg)))/binary + >>. + +decode(Msg) -> + % Re-add the message length stripped off by open_port + Len = byte_size(Msg), + decode(<>, [], [], []). + +decode(<<>>, _Pids, Msg, Acc) -> + lists:flatten(lists:reverse([Msg|Acc])); + +decode(<>, Pids, [], Acc) when Len =:= 2 + 4 + byte_size(Rest) -> + decode(Rest, [Pid|Pids], [], Acc); +decode(<>, _Pids, Msg, Acc) when Len =:= 2 + 4 + byte_size(Rest) -> + decode(Rest, [Pid], [], [lists:reverse(Msg)|Acc]); + +decode(<>, Pids, Msg, Acc) -> + Bytes = Len - (2 + 4), + <> = Bin, + decode(Rest, Pids, + [{alcove_stdout, lists:reverse([Pid|Pids]), Reply}|Msg], Acc); + +decode(<>, Pids, Msg, Acc) -> + Bytes = Len - (2 + 4), + <> = Bin, + decode(Rest, Pids, + [{alcove_stderr, lists:reverse([Pid|Pids]), Reply}|Msg], Acc); + +decode(<>, Pids, Msg, Acc) -> + Bytes = Len - 2, + <> = Bin, + decode(Rest, Pids, + [{alcove_call, lists:reverse(Pids), binary_to_term(Reply)}|Msg], + Acc); + +decode(<>, Pids, Msg, Acc) -> + Bytes = Len - 2, + <> = Bin, + decode(Rest, Pids, + [{alcove_event, lists:reverse(Pids), binary_to_term(Reply)}|Msg], + Acc). diff --git a/src/alcove_drv.erl b/src/alcove_drv.erl index e7d6161..884ab74 100644 --- a/src/alcove_drv.erl +++ b/src/alcove_drv.erl @@ -18,10 +18,8 @@ %% API -export([start/0, start/1, stop/1]). -export([start_link/2]). --export([call/5, encode/2, encode/3]). +-export([call/5]). -export([stdin/3, stdout/3, stderr/3, event/3, send/2]). --export([atom_to_type/1, type_to_atom/1]). --export([msg/2, decode/1]). -export([getopts/1]). %% gen_server callbacks @@ -52,11 +50,12 @@ start_link(Owner, Options) -> stop(Drv) -> gen_server:call(Drv, stop). --spec call(ref(),[integer()],iodata(),boolean(),timeout()) -> term(). -call(Drv, Pids, Data, Returns, Timeout) -> +-spec call(ref(),[integer()],atom(),list(),timeout()) -> term(). +call(Drv, Pids, Command, Argv, Timeout) -> + Data = alcove_codec:call(Command, Pids, Argv), case send(Drv, Data) of true -> - call_reply(Drv, Pids, Returns, Timeout); + call_reply(Drv, Pids, alcove_proto:returns(Command), Timeout); Error -> Error end. @@ -74,88 +73,20 @@ send(Drv, Data) -> stdin(Drv, [], Data) -> send(Drv, Data); stdin(Drv, Pids, Data) -> - Stdin = hdr(lists:reverse(Pids), [Data]), + Stdin = alcove_codec:stdin(Pids, Data), send(Drv, Stdin). --spec stdout(ref(),[integer()],timeout()) -> - 'false' | binary(). +-spec stdout(ref(),[integer()],timeout()) -> 'false' | binary(). stdout(Drv, Pids, Timeout) -> - reply(Drv, Pids, ?ALCOVE_MSG_STDOUT, Timeout). + reply(Drv, Pids, alcove_stdout, Timeout). --spec stderr(ref(),[integer()],timeout()) -> - 'false' | binary(). +-spec stderr(ref(),[integer()],timeout()) -> 'false' | binary(). stderr(Drv, Pids, Timeout) -> - reply(Drv, Pids, ?ALCOVE_MSG_STDERR, Timeout). + reply(Drv, Pids, alcove_stderr, Timeout). -spec event(ref(),[integer()],timeout()) -> term(). event(Drv, Pids, Timeout) -> - reply(Drv, Pids, ?ALCOVE_MSG_EVENT, Timeout). - -msg([], Data) -> - Data; -msg(Pids, Data) -> - Size = iolist_size(Data), - hdr(lists:reverse(Pids), [<>, Data]). - -hdr([], [_Length|Acc]) -> - Acc; -hdr([Pid|Pids], Acc) -> - Size = iolist_size(Acc) + 2 + 4, - hdr(Pids, [<>, <>, <>|Acc]). - -encode(Command, Arg) when is_integer(Command), is_list(Arg) -> - encode(?ALCOVE_MSG_CALL, Command, Arg). -encode(Type, Command, Arg) when is_integer(Type), is_integer(Command), is_list(Arg) -> - << - ?UINT16(Type), - ?UINT16(Command), - (term_to_binary(list_to_tuple(Arg)))/binary - >>. - -decode(Msg) -> - % Re-add the message length stripped off by open_port - Len = byte_size(Msg), - decode(<>, [], [], []). - -decode(<<>>, _Pids, Msg, Acc) -> - lists:flatten(lists:reverse([Msg|Acc])); - -decode(<>, Pids, [], Acc) when Len =:= 2 + 4 + byte_size(Rest) -> - decode(Rest, [Pid|Pids], [], Acc); -decode(<>, _Pids, Msg, Acc) when Len =:= 2 + 4 + byte_size(Rest) -> - decode(Rest, [Pid], [], [lists:reverse(Msg)|Acc]); - -decode(<>, Pids, Msg, Acc) -> - Bytes = Len - (2 + 4), - <> = Bin, - decode(Rest, Pids, - [{alcove_stdout, lists:reverse([Pid|Pids]), Reply}|Msg], Acc); - -decode(<>, Pids, Msg, Acc) -> - Bytes = Len - (2 + 4), - <> = Bin, - decode(Rest, Pids, - [{alcove_stderr, lists:reverse([Pid|Pids]), Reply}|Msg], Acc); - -decode(<>, Pids, Msg, Acc) -> - Bytes = Len - 2, - <> = Bin, - decode(Rest, Pids, - [{alcove_call, lists:reverse(Pids), binary_to_term(Reply)}|Msg], - Acc); - -decode(<>, Pids, Msg, Acc) -> - Bytes = Len - 2, - <> = Bin, - decode(Rest, Pids, - [{alcove_event, lists:reverse(Pids), binary_to_term(Reply)}|Msg], - Acc). + reply(Drv, Pids, alcove_event, Timeout). %%-------------------------------------------------------------------- %%% Callbacks @@ -211,7 +142,7 @@ code_change(_OldVsn, State, _Extra) -> % the parent. handle_info({Port, {data, Data}}, #state{port = Port, pid = Pid} = State) -> [ Pid ! {Tag, self(), Pids, Term} || - {Tag, Pids, Term} <- decode(Data) ], + {Tag, Pids, Term} <- alcove_codec:decode(Data) ], {noreply, State}; handle_info({'EXIT', Port, Reason}, #state{port = Port} = State) -> @@ -252,9 +183,8 @@ call_reply(Drv, Pids, true, Timeout) -> end. reply(Drv, Pids, Type, Timeout) -> - Tag = type_to_atom(Type), receive - {Tag, Drv, Pids, Event} -> + {Type, Drv, Pids, Event} -> Event after Timeout -> @@ -300,20 +230,6 @@ find_executable(Exe) -> N end. -atom_to_type(alcove_call) -> ?ALCOVE_MSG_CALL; -atom_to_type(alcove_event) -> ?ALCOVE_MSG_EVENT; -atom_to_type(alcove_stdin) -> ?ALCOVE_MSG_STDIN; -atom_to_type(alcove_stdout) -> ?ALCOVE_MSG_STDOUT; -atom_to_type(alcove_stderr) -> ?ALCOVE_MSG_STDERR; -atom_to_type(alcove_proxy) -> ?ALCOVE_MSG_PROXY. - -type_to_atom(?ALCOVE_MSG_CALL) -> alcove_call; -type_to_atom(?ALCOVE_MSG_EVENT) -> alcove_event; -type_to_atom(?ALCOVE_MSG_STDIN) -> alcove_stdin; -type_to_atom(?ALCOVE_MSG_STDOUT) -> alcove_stdout; -type_to_atom(?ALCOVE_MSG_STDERR) -> alcove_stderr; -type_to_atom(?ALCOVE_MSG_PROXY) -> alcove_proxy. - basedir(Module) -> case code:priv_dir(Module) of {error, bad_name} -> diff --git a/test/alcove_tests.erl b/test/alcove_tests.erl index 5b246cb..15be442 100644 --- a/test/alcove_tests.erl +++ b/test/alcove_tests.erl @@ -122,7 +122,7 @@ msg(_) -> 0,8, 0,4, 131,100,0,2,111,107 >>, - Reply = alcove_drv:decode(Msg), + Reply = alcove_codec:decode(Msg), ?_assertEqual( [{alcove_call,[295,551,807],<<"0.2.0">>},