Implement config parameter max_pread_size

Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/8ea500ef
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/8ea500ef
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/8ea500ef

Branch: refs/heads/master
Commit: 8ea500ef413d09f862609d34bdd8ac6737cd26a3
Parents: 89990e1
Author: Eric Avdey <e...@eiri.ca>
Authored: Mon May 16 13:55:52 2016 -0300
Committer: Eric Avdey <e...@eiri.ca>
Committed: Mon May 16 16:50:50 2016 -0300

----------------------------------------------------------------------
 src/couch_file.erl        | 37 +++++++++++++++++++++++++++++++------
 test/couch_file_tests.erl | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/8ea500ef/src/couch_file.erl
----------------------------------------------------------------------
diff --git a/src/couch_file.erl b/src/couch_file.erl
index 4bb8be8..03c2628 100644
--- a/src/couch_file.erl
+++ b/src/couch_file.erl
@@ -12,7 +12,7 @@
 
 -module(couch_file).
 -behaviour(gen_server).
--vsn(1).
+-vsn(2).
 
 -include_lib("couch/include/couch_db.hrl").
 
@@ -21,13 +21,15 @@
 -define(MONITOR_CHECK, 10000).
 -define(SIZE_BLOCK, 16#1000). % 4 KiB
 -define(READ_AHEAD, 2 * ?SIZE_BLOCK).
+-define(IS_OLD_STATE(S), tuple_size(S) /= tuple_size(#file{})).
 
 
 -record(file, {
     fd,
     is_sys,
     eof = 0,
-    db_pid
+    db_pid,
+    pread_limit = 0
 }).
 
 % public API
@@ -337,6 +339,8 @@ init_status_error(ReturnPid, Ref, Error) ->
 init({Filepath, Options, ReturnPid, Ref}) ->
     process_flag(trap_exit, true),
     OpenOptions = file_open_options(Options),
+    Limit = get_pread_limit(),
+    IsSys = lists:member(sys_db, Options),
     case lists:member(create, Options) of
     true ->
         filelib:ensure_dir(Filepath),
@@ -357,7 +361,7 @@ init({Filepath, Options, ReturnPid, Ref}) ->
                     ok = file:sync(Fd),
                     maybe_track_open_os_files(Options),
                     erlang:send_after(?INITIAL_WAIT, self(), maybe_close),
-                    {ok, #file{fd=Fd, is_sys=lists:member(sys_db, Options)}};
+                    {ok, #file{fd=Fd, is_sys=IsSys, pread_limit=Limit}};
                 false ->
                     ok = file:close(Fd),
                     init_status_error(ReturnPid, Ref, {error, eexist})
@@ -365,7 +369,7 @@ init({Filepath, Options, ReturnPid, Ref}) ->
             false ->
                 maybe_track_open_os_files(Options),
                 erlang:send_after(?INITIAL_WAIT, self(), maybe_close),
-                {ok, #file{fd=Fd, is_sys=lists:member(sys_db, Options)}}
+                {ok, #file{fd=Fd, is_sys=IsSys, pread_limit=Limit}}
             end;
         Error ->
             init_status_error(ReturnPid, Ref, Error)
@@ -381,7 +385,7 @@ init({Filepath, Options, ReturnPid, Ref}) ->
             maybe_track_open_os_files(Options),
             {ok, Eof} = file:position(Fd, eof),
             erlang:send_after(?INITIAL_WAIT, self(), maybe_close),
-            {ok, #file{fd=Fd, eof=Eof, is_sys=lists:member(sys_db, Options)}};
+            {ok, #file{fd=Fd, eof=Eof, is_sys=IsSys, pread_limit=Limit}};
         Error ->
             init_status_error(ReturnPid, Ref, Error)
         end
@@ -408,6 +412,9 @@ terminate(_Reason, #file{fd = nil}) ->
 terminate(_Reason, #file{fd = Fd}) ->
     ok = file:close(Fd).
 
+handle_call(Msg, From, File) when ?IS_OLD_STATE(File) ->
+    handle_call(Msg, From, upgrade_state(File));
+
 handle_call(close, _From, #file{fd=Fd}=File) ->
     {stop, normal, file:close(Fd), File#file{fd = nil}};
 
@@ -418,6 +425,8 @@ handle_call({pread_iolist, Pos}, _From, File) ->
     catch
     throw:read_beyond_eof ->
         throw(read_beyond_eof);
+    throw:{exceed_pread_limit, Limit} ->
+        throw({exceed_pread_limit, Limit});
     _:_ ->
         read_raw_iolist_int(File, Pos, 4)
     end,
@@ -491,6 +500,9 @@ handle_cast(close, Fd) ->
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
 
+handle_info(Msg, File) when ?IS_OLD_STATE(File) ->
+    handle_info(Msg, upgrade_state(File));
+
 handle_info(maybe_close, File) ->
     case is_idle(File) of
         true ->
@@ -553,12 +565,14 @@ maybe_read_more_iolist(Buffer, DataSize, NextPos, File) ->
     {Data::iolist(), CurPos::non_neg_integer()}.
 read_raw_iolist_int(Fd, {Pos, _Size}, Len) -> % 0110 UPGRADE CODE
     read_raw_iolist_int(Fd, Pos, Len);
-read_raw_iolist_int(#file{fd = Fd} = F, Pos, Len) ->
+read_raw_iolist_int(#file{fd = Fd, pread_limit = Limit} = F, Pos, Len) ->
     BlockOffset = Pos rem ?SIZE_BLOCK,
     TotalBytes = calculate_total_read_len(BlockOffset, Len),
     case Pos + TotalBytes of
     Size when Size > F#file.eof + ?READ_AHEAD ->
         throw(read_beyond_eof);
+    Size when Size > Limit ->
+        throw({exceed_pread_limit, Limit});
     Size ->
         {ok, <<RawBin:TotalBytes/binary>>} = file:pread(Fd, Pos, TotalBytes),
         {remove_block_prefixes(BlockOffset, RawBin), Size}
@@ -659,6 +673,17 @@ process_info(Pid) ->
             {Fd, InitialName}
     end.
 
+upgrade_state({file, Fd, IsSys, Eof, DbPid}) ->
+    Limit = get_pread_limit(),
+    #file{fd=Fd, is_sys=IsSys, eof=Eof, db_pid=DbPid, pread_limit=Limit};
+upgrade_state(State) ->
+    State.
+
+get_pread_limit() ->
+    case config:get_integer("couchdb", "max_pread_size", 0) of
+        N when N > 0 -> N;
+        _ -> infinity
+    end.
 
 -ifdef(TEST).
 -include_lib("couch/include/couch_eunit.hrl").

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/8ea500ef/test/couch_file_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_file_tests.erl b/test/couch_file_tests.erl
index 24edf46..497999e 100644
--- a/test/couch_file_tests.erl
+++ b/test/couch_file_tests.erl
@@ -150,6 +150,38 @@ should_truncate(Fd) ->
     ok = couch_file:truncate(Fd, Size),
     ?_assertMatch({ok, foo}, couch_file:pread_term(Fd, 0)).
 
+pread_limit_test_() ->
+    {
+        "Read limit tests",
+        {
+            setup,
+            fun() ->
+                Ctx = test_util:start(?MODULE),
+                config:set("couchdb", "max_pread_size", "50000"),
+                Ctx
+            end,
+            fun(Ctx) ->
+                config:delete("couchdb", "max_pread_size"),
+                test_util:stop(Ctx)
+            end,
+            ?foreach([
+                fun should_increase_file_size_on_write/1,
+                fun should_return_current_file_size_on_write/1,
+                fun should_write_and_read_term/1,
+                fun should_write_and_read_binary/1,
+                fun should_not_read_more_than_pread_limit/1
+            ])
+        }
+    }.
+
+should_not_read_more_than_pread_limit(Fd) ->
+    BigBin = list_to_binary(lists:duplicate(100000, 0)),
+    {ok, Pos, _Size} = couch_file:append_binary(Fd, BigBin),
+    unlink(Fd),
+    ExpectedError = {badmatch, {'EXIT', {bad_return_value,
+        {exceed_pread_limit, 50000}}}},
+    ?_assertError(ExpectedError, couch_file:pread_binary(Fd, Pos)).
+
 
 header_test_() ->
     {

Reply via email to