-module(fcgi_ffi). -include_lib("kernel/include/file.hrl"). -export([open_and_size/1, sendfile/4, listen_tcp/2, listen_unix/1, close_socket/1, send/2, controlling_process/2, recv/3, delete_path/1, close_file/1, socket_port/1]). -define(LISTEN_OPTS, [binary, {active, false}, {packet, raw}, {backlog, 1024}]). open_and_size(Path) -> case file:open(Path, [read, raw, binary]) of {ok, IoDevice} -> size_and_rewind(IoDevice, Path); {error, Reason} -> {error, translate_error(Path, Reason)} end. size_and_rewind(IoDevice, Path) -> case file:position(IoDevice, eof) of {ok, Size} -> rewind_with_size(IoDevice, Path, Size); {error, Reason} -> close_and_translate(IoDevice, Path, Reason) end. rewind_with_size(IoDevice, Path, Size) -> case file:position(IoDevice, 0) of {ok, 0} -> {ok, {IoDevice, Size}}; {error, Reason} -> close_and_translate(IoDevice, Path, Reason) end. close_and_translate(IoDevice, Path, Reason) -> _ = file:close(IoDevice), {error, translate_error(Path, Reason)}. sendfile(IoDevice, Socket, Offset, Bytes) -> case file:sendfile(IoDevice, Socket, Offset, Bytes, []) of {ok, Sent} -> {ok, Sent}; {error, _} -> {error, nil} end. listen_tcp(Host, Port) -> case inet:parse_address(binary_to_list(Host)) of {ok, Ip} -> Family = case tuple_size(Ip) of 4 -> inet; 8 -> inet6 end, map_posix(gen_tcp:listen(Port, [Family, {ip, Ip} | ?LISTEN_OPTS])); {error, _} -> {error, {invalid_host, Host}} end. listen_unix(PathBin) -> case file:read_file_info(PathBin) of {ok, #file_info{type = other}} -> case is_stale_socket(PathBin) of true -> _ = file:delete(PathBin), map_posix(gen_tcp:listen(0, [{ifaddr, {local, PathBin}} | ?LISTEN_OPTS])); false -> {error, {path_exists, PathBin}} end; {ok, _} -> {error, {path_exists, PathBin}}; {error, enoent} -> map_posix(gen_tcp:listen(0, [{ifaddr, {local, PathBin}} | ?LISTEN_OPTS])); {error, R} -> {error, {posix, R}} end. is_stale_socket(PathBin) -> case gen_tcp:connect({local, PathBin}, 0, [binary, {active, false}], 100) of {ok, Sock} -> _ = gen_tcp:close(Sock), false; {error, econnrefused} -> true; {error, _} -> false end. close_socket(Socket) -> _ = gen_tcp:close(Socket), nil. socket_port(Socket) -> case inet:port(Socket) of {ok, Port} -> {ok, Port}; {error, R} -> {error, {posix, R}} end. send(Socket, Data) -> case gen_tcp:send(Socket, Data) of ok -> {ok, nil}; {error, _} -> {error, nil} end. controlling_process(Socket, Pid) -> map_posix(gen_tcp:controlling_process(Socket, Pid)). recv(Socket, Size, TimeoutMs) -> map_posix(gen_tcp:recv(Socket, Size, TimeoutMs)). delete_path(PathBin) -> _ = file:delete(PathBin), nil. close_file(Handle) -> _ = file:close(Handle), nil. map_posix(ok) -> {ok, nil}; map_posix({ok, _} = Reply) -> Reply; map_posix({error, R}) -> {error, {posix, R}}. translate_error(Path, enoent) -> {file_not_found, Path}; translate_error(Path, eacces) -> {file_access_denied, Path}; translate_error(Path, eperm) -> {file_access_denied, Path}; translate_error(Path, eisdir) -> {file_is_directory, Path}; translate_error(Path, Reason) -> {file_other, Path, list_to_binary(io_lib:format("~p", [Reason]))}.