1%%% File : yaws_sendfile.erl 2%%% Author : Steve Vinoski <vinoski@ieee.org> 3%%% Description : interface to sendfile linked-in driver for Yaws 4%%% Created : 9 Nov 2008 by Steve Vinoski <vinoski@ieee.org> 5 6-module(yaws_sendfile). 7-author('vinoski@ieee.org'). 8 9-include("../include/yaws.hrl"). 10-include_lib("kernel/include/file.hrl"). 11 12-export([send/2, send/3, send/4]). 13-export([have_sendfile/0, have_erlang_sendfile/0, check_gc_flags/1]). 14 15%% export bytes_to_transfer to avoid warning when sendfile is disabled (or not 16%% supported) 17-export([bytes_to_transfer/3]). 18 19 20-ifdef(HAVE_SENDFILE). 21 22-behavior(gen_server). 23-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 24 code_change/3]). 25 26-export([start_link/0, stop/0]). 27 28have_sendfile() -> true. 29 30-else. 31 32have_sendfile() -> false. 33 34-endif. 35 36have_erlang_sendfile() -> yaws_dynopts:have_erlang_sendfile(). 37 38check_gc_flags(GC) -> 39 %% below, ignore dialyzer warning: 40 %% The pattern depends on the macro HAVE_ERLANG_SENDFILE 41 case have_erlang_sendfile() of 42 false when ?gc_use_erlang_sendfile(GC) -> 43 error_logger:error_msg("Cannot use file:sendfile/5: not supported, " 44 "gen_tcp:send/2 will be used instead.~n", 45 []); 46 _ -> 47 ok 48 end, 49 50 %% below, ignore dialyzer warning: 51 %% The pattern depends on the macro HAVE_SENDFILE 52 case have_sendfile() of 53 false when ?gc_use_yaws_sendfile(GC) -> 54 error_logger:error_msg("Cannot use Yaws sendfile linked-in driver:" 55 " not supported, gen_tcp:send/2 will be used" 56 " instead.~n", 57 []); 58 _ -> 59 ok 60 end. 61 62 63send(Out, Filename) -> 64 send(Out, Filename, 0, all). 65send(Out, Filename, Offset) -> 66 send(Out, Filename, Offset, all). 67 68send(Out, Filename, Offset, Count) -> 69 GC = get(gc), 70 ChunkSize = GC#gconf.large_file_chunk_size, 71 ErlangSendFile = ?gc_use_erlang_sendfile(GC), 72 YawsSendFile = ?gc_use_yaws_sendfile(GC), 73 if 74 ErlangSendFile -> 75 erlang_sendfile(Out, Filename, Offset, Count, ChunkSize); 76 YawsSendFile -> 77 yaws_sendfile(Out, Filename, Offset, Count, ChunkSize); 78 true -> 79 compat_send(Out, Filename, Offset, Count, ChunkSize) 80 end. 81 82 83bytes_to_transfer(Filename, Offset, Count) -> 84 case Count of 85 all -> 86 case file:read_file_info(Filename) of 87 {ok, #file_info{size = Size}} -> Size - Offset; 88 Error -> Error 89 end; 90 Count when is_integer(Count) -> 91 Count; 92 _ -> 93 {error, badarg} 94 end. 95 96 97erlang_sendfile(Out, Filename, Offset, Count, ChunkSize) -> 98 case have_erlang_sendfile() of 99 true -> 100 Count1 = bytes_to_transfer(Filename, Offset, Count), 101 case Count1 of 102 {error, _}=Error1 -> 103 Error1; 104 _ -> 105 case file:open(Filename, [raw, read, binary]) of 106 {ok, RawFile} -> 107 Res = file:sendfile(RawFile, Out, Offset, Count1, 108 [{chunk_size, ChunkSize}]), 109 ok = file:close(RawFile), 110 Res; 111 Error2 -> 112 Error2 113 end 114 end; 115 false -> 116 compat_send(Out, Filename, Offset, Count, ChunkSize) 117 end. 118 119 120-ifdef(HAVE_SENDFILE). 121 122yaws_sendfile(Out, Filename, Offset, Count, ChunkSize) -> 123 Count1 = bytes_to_transfer(Filename, Offset, Count), 124 case Count1 of 125 {error, _}=Error -> 126 Error; 127 _ -> 128 case prim_inet:getfd(Out) of 129 {ok, SocketFd} -> 130 do_send(Out, SocketFd, Filename, Offset, Count1, ChunkSize); 131 Error2 -> 132 Error2 133 end 134 end. 135 136 137-record(state, { 138 port, % driver port 139 caller_tbl % table mapping socket fd to caller 140 }). 141 142start_link() -> 143 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 144 145stop() -> 146 gen_server:cast(?MODULE, stop). 147 148init([]) -> 149 process_flag(trap_exit, true), 150 Shlib = "yaws_sendfile_drv", 151 Dir = filename:join(yaws:get_priv_dir(), "lib"), 152 case erl_ddll:load_driver(Dir, Shlib) of 153 ok -> ok; 154 {error, already_loaded} -> ok; 155 {error, Reason} -> 156 exit({error, 157 "could not load driver " ++ Shlib ++ ": " ++ 158 erl_ddll:format_error(Reason)}) 159 end, 160 Port = open_port({spawn, Shlib}, [binary]), 161 CallerTable = ets:new(yaws_sendfile, []), 162 {ok, #state{port = Port, caller_tbl = CallerTable}}. 163 164handle_call({send, SocketFd, Msg}, From, State) -> 165 true = erlang:port_command(State#state.port, Msg), 166 true = ets:insert(State#state.caller_tbl, {SocketFd, From}), 167 {noreply, State}; 168handle_call(_Req, _From, State) -> 169 {reply, ok, State}. 170 171handle_info({_, {data, <<Cnt:64, SocketFd:32, Res:8, Err/binary>>}}, State) -> 172 Reply = case Res of 173 1 -> 174 {ok, Cnt}; 175 0 -> 176 {error, 177 list_to_atom( 178 lists:takewhile(fun(El) -> El =/= 0 end, 179 binary_to_list(Err)))} 180 end, 181 CallerTable = State#state.caller_tbl, 182 [{SocketFd, From}] = ets:lookup(CallerTable, SocketFd), 183 gen_server:reply(From, Reply), 184 ets:delete(CallerTable, SocketFd), 185 {noreply, State}; 186handle_info(_Info, State) -> 187 {noreply, State}. 188 189handle_cast(stop, State) -> 190 {stop, State}; 191handle_cast(_, State) -> 192 {noreply, State}. 193 194terminate(_Reason, #state{port = Port, caller_tbl = CallerTable}) -> 195 erlang:port_close(Port), 196 receive {'EXIT', Port, _Reason} -> ok 197 after 0 -> ok 198 end, 199 ets:delete(CallerTable), 200 ok. 201 202code_change(_OldVsn, Data, _Extra) -> 203 {ok, Data}. 204 205do_send(_Out, _SocketFd, _Filename, _Offset, Count, _) when Count =< 0 -> 206 {ok, 0}; 207do_send(Out, SocketFd, Filename0, Offset, Count, ChunkSize) -> 208 Filename = case file:native_name_encoding() of 209 latin1 -> Filename0; 210 utf8 -> unicode:characters_to_binary(Filename0) 211 end, 212 Call = list_to_binary([<<Offset:64, Count:64, SocketFd:32>>, 213 Filename, <<0:8>>]), 214 case gen_server:call(?MODULE, {send, SocketFd, Call}, infinity) of 215 {error, eoverflow} -> 216 compat_send(Out, Filename, Offset, Count, ChunkSize); 217 Else -> 218 Else 219 end. 220 221-else. 222 223yaws_sendfile(Out, Filename, Offset, Count, ChunkSize) -> 224 compat_send(Out, Filename, Offset, Count, ChunkSize). 225 226-endif. 227 228 229 230compat_send(Out, Filename, Offset, Count0, ChunkSize) -> 231 Count = case Count0 of 232 0 -> all; 233 _ -> Count0 234 end, 235 case file:open(Filename, [read, binary, raw]) of 236 {ok, Fd} -> 237 file:position(Fd, {bof, Offset}), 238 Ret = loop_send(Fd, ChunkSize, file:read(Fd, ChunkSize), Out, 239 Count, 0), 240 file:close(Fd), 241 Ret; 242 Err -> 243 Err 244 end. 245 246loop_send(Fd, ChunkSize, {ok, Bin}, Out, all, BytesSent) -> 247 case gen_tcp:send(Out, Bin) of 248 ok -> 249 loop_send(Fd, ChunkSize, file:read(Fd, ChunkSize), Out, all, 250 BytesSent+size(Bin)); 251 Err -> 252 Err 253 end; 254loop_send(_Fd, _ChunkSize, eof, _Out, _, BytesSent) -> 255 {ok, BytesSent}; 256loop_send(Fd, ChunkSize, {ok, Bin}, Out, Count, BytesSent) -> 257 Sz = size(Bin), 258 if Sz < Count -> 259 case gen_tcp:send(Out, Bin) of 260 ok -> 261 loop_send(Fd, ChunkSize, file:read(Fd, ChunkSize), 262 Out, Count-Sz, BytesSent+Sz); 263 Err -> 264 Err 265 end; 266 Sz == Count -> 267 case gen_tcp:send(Out, Bin) of 268 ok -> {ok, BytesSent+Sz}; 269 Err -> Err 270 end; 271 Sz > Count -> 272 <<Deliver:Count/binary , _/binary>> = Bin, 273 case gen_tcp:send(Out, Deliver) of 274 ok -> {ok, BytesSent+Count}; 275 Err -> Err 276 end 277 end; 278loop_send(_Fd, _, Err, _, _, _) -> 279 Err. 280