C0 code coverage information

Generated on 2009-08-09 21:22:54 with etap 0.3.4.

Name Total lines Lines of code Total coverage Code coverage
couch_server ?? ?? ??
62% 
....1 % Licensed under the Apache License, Version 2.0 (the "License"); you may not
....2 % use this file except in compliance with the License. You may obtain a copy of
....3 % the License at
....4 %
....5 %   http://www.apache.org/licenses/LICENSE-2.0
....6 %
....7 % Unless required by applicable law or agreed to in writing, software
....8 % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
....9 % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
...10 % License for the specific language governing permissions and limitations under
...11 % the License.
...12 
...13 -module(couch_server).
...14 -behaviour(gen_server).
...15 -behaviour(application).
...16 
...17 -export([start/0,start/1,start/2,stop/0,stop/1,restart/0]).
...18 -export([open/2,create/2,delete/2,all_databases/0,get_version/0]).
...19 -export([init/1, handle_call/3,sup_start_link/0]).
...20 -export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
...21 -export([dev_start/0,is_admin/2,has_admins/0,get_stats/0]).
...22 
...23 -include("couch_db.hrl").
...24 
...25 -record(server,{
...26     root_dir = [],
...27     dbname_regexp,
...28     max_dbs_open=100,
...29     dbs_open=0,
...30     start_time=""
...31     }).
...32 
...33 start() ->
...34     start(["default.ini"]).
...35 
...36 start(IniFiles) ->
...37     couch_server_sup:start_link(IniFiles).
...38 
...39 start(_Type, _Args) ->
...40     start().
...41 
...42 restart() ->
...43     stop(),
...44     start().
...45 
...46 stop() ->
...47     couch_server_sup:stop().
...48 
...49 stop(_Reason) ->
...50     stop().
...51 
...52 dev_start() ->
...53     stop(),
...54     up_to_date = make:all([load, debug_info]),
...55     start().
...56 
...57 get_version() ->
...58     Apps = application:loaded_applications(),
...59     case lists:keysearch(couch, 1, Apps) of
...60     {value, {_, _, Vsn}} ->
...61         Vsn;
...62     false ->
...63         "0.0.0"
...64     end.
...65 
...66 get_stats() ->
...67     {ok, #server{start_time=Time,dbs_open=Open}} =
...68             gen_server:call(couch_server, get_server),
...69     [{start_time, ?l2b(Time)}, {dbs_open, Open}].
...70 
...71 sup_start_link() ->
...72     gen_server:start_link({local, couch_server}, couch_server, [], []).
...73 
...74 open(DbName, Options) ->
...75     case gen_server:call(couch_server, {open, DbName, Options}) of
...76     {ok, MainPid} ->
...77         Ctx = proplists:get_value(user_ctx, Options, #user_ctx{}),
...78         couch_db:open_ref_counted(MainPid, Ctx);
...79     Error ->
...80         Error
...81     end.
...82 
...83 create(DbName, Options) ->
...84     case gen_server:call(couch_server, {create, DbName, Options}) of
...85     {ok, MainPid} ->
...86         Ctx = proplists:get_value(user_ctx, Options, #user_ctx{}),
...87         couch_db:open_ref_counted(MainPid, Ctx);
...88     Error ->
...89         Error
...90     end.
...91 
...92 delete(DbName, Options) ->
...93     gen_server:call(couch_server, {delete, DbName, Options}).
...94 
...95 check_dbname(#server{dbname_regexp=RegExp}, DbName) ->
...96     case regexp:match(DbName, RegExp) of
...97     nomatch ->
...98         {error, illegal_database_name};
...99     _Match ->
..100         ok
..101     end.
..102 
..103 is_admin(User, ClearPwd) ->
..104     case couch_config:get("admins", User) of
..105     "-hashed-" ++ HashedPwdAndSalt ->
..106         [HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","),
..107         couch_util:to_hex(crypto:sha(ClearPwd ++ Salt)) == HashedPwd;
..108     _Else ->
..109         false
..110     end.
..111 
..112 has_admins() ->
..113     couch_config:get("admins") /= [].
..114 
..115 get_full_filename(Server, DbName) ->
..116     filename:join([Server#server.root_dir, "./" ++ DbName ++ ".couch"]).
..117 
..118 hash_admin_passwords() ->
..119     lists:foreach(
..120         fun({_User, "-hashed-" ++ _}) ->
..121             ok; % already hashed
..122         ({User, ClearPassword}) ->
..123             Salt = ?b2l(couch_util:new_uuid()),
..124             Hashed = couch_util:to_hex(crypto:sha(ClearPassword ++ Salt)),
..125             couch_config:set("admins", User, "-hashed-" ++ Hashed ++ "," ++ Salt)
..126         end, couch_config:get("admins")).
..127 
..128 init([]) ->
..129     % read config and register for configuration changes
..130 
..131     % just stop if one of the config settings change. couch_server_sup
..132     % will restart us and then we will pick up the new settings.
..133 
..134     RootDir = couch_config:get("couchdb", "database_dir", "."),
..135     MaxDbsOpen = list_to_integer(
..136             couch_config:get("couchdb", "max_dbs_open")),
..137     Self = self(),
..138     ok = couch_config:register(
..139         fun("couchdb", "database_dir") ->
..140             exit(Self, config_change)
..141         end),
..142     ok = couch_config:register(
..143         fun("couchdb", "max_dbs_open", Max) ->
..144             gen_server:call(couch_server,
..145                     {set_max_dbs_open, list_to_integer(Max)})
..146         end),
..147     hash_admin_passwords(),
..148     ok = couch_config:register(
..149         fun("admins") ->
..150             % spawn here so couch_config doesn't try to call itself
..151             spawn(fun() -> hash_admin_passwords() end)
..152         end),
..153     {ok, RegExp} = regexp:parse("^[a-z][a-z0-9\\_\\$()\\+\\-\\/]*$"),
..154     ets:new(couch_dbs_by_name, [set, private, named_table]),
..155     ets:new(couch_dbs_by_pid, [set, private, named_table]),
..156     ets:new(couch_dbs_by_lru, [ordered_set, private, named_table]),
..157     process_flag(trap_exit, true),
..158     {ok, #server{root_dir=RootDir,
..159                 dbname_regexp=RegExp,
..160                 max_dbs_open=MaxDbsOpen,
..161                 start_time=httpd_util:rfc1123_date()}}.
..162 
..163 terminate(Reason, _Srv) ->
..164     couch_util:terminate_linked(Reason),
..165     ok.
..166 
..167 all_databases() ->
..168     {ok, #server{root_dir=Root}} = gen_server:call(couch_server, get_server),
..169     Filenames =
..170     filelib:fold_files(Root, "^[a-z0-9\\_\\$()\\+\\-]*[\\.]couch$", true,
..171         fun(Filename, AccIn) ->
..172             case Filename -- Root of
..173             [$/ | RelativeFilename] -> ok;
..174             RelativeFilename -> ok
..175             end,
..176             [list_to_binary(filename:rootname(RelativeFilename, ".couch")) | AccIn]
..177         end, []),
..178     {ok, Filenames}.
..179 
..180 
..181 maybe_close_lru_db(#server{dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server)
..182         when NumOpen < MaxOpen ->
..183     {ok, Server};
..184 maybe_close_lru_db(#server{dbs_open=NumOpen}=Server) ->
..185     % must free up the lru db.
..186     case try_close_lru(now()) of
..187     ok ->
..188         {ok, Server#server{dbs_open=NumOpen - 1}};
..189     Error -> Error
..190     end.
..191 
..192 try_close_lru(StartTime) ->
..193     LruTime = ets:first(couch_dbs_by_lru),
..194     if LruTime > StartTime ->
..195         % this means we've looped through all our opened dbs and found them
..196         % all in use.
..197         {error, all_dbs_active};
..198     true ->
..199         [{_, DbName}] = ets:lookup(couch_dbs_by_lru, LruTime),
..200         [{_, {MainPid, LruTime}}] = ets:lookup(couch_dbs_by_name, DbName),
..201         case couch_db:is_idle(MainPid) of
..202         true ->
..203             exit(MainPid, kill),
..204             receive {'EXIT', MainPid, _Reason} -> ok end,
..205             true = ets:delete(couch_dbs_by_lru, LruTime),
..206             true = ets:delete(couch_dbs_by_name, DbName),
..207             true = ets:delete(couch_dbs_by_pid, MainPid),
..208             ok;
..209         false ->
..210             % this still has referrers. Go ahead and give it a current lru time
..211             % and try the next one in the table.
..212             NewLruTime = now(),
..213             true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, NewLruTime}}),
..214             true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
..215             true = ets:delete(couch_dbs_by_lru, LruTime),
..216             true = ets:insert(couch_dbs_by_lru, {NewLruTime, DbName}),
..217             try_close_lru(StartTime)
..218         end
..219     end.
..220 
..221 handle_call({set_max_dbs_open, Max}, _From, Server) ->
..222     {reply, ok, Server#server{max_dbs_open=Max}};
..223 handle_call(get_server, _From, Server) ->
..224     {reply, {ok, Server}, Server};
..225 handle_call({open, DbName, Options}, _From, Server) ->
..226     DbNameList = binary_to_list(DbName),
..227     case check_dbname(Server, DbNameList) of
..228     ok ->
..229         Filepath = get_full_filename(Server, DbNameList),
..230         LruTime = now(),
..231         case ets:lookup(couch_dbs_by_name, DbName) of
..232         [] ->
..233             case maybe_close_lru_db(Server) of
..234             {ok, Server2} ->
..235                 case couch_db:start_link(DbName, Filepath, Options) of
..236                 {ok, MainPid} ->
..237                     true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, LruTime}}),
..238                     true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
..239                     true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
..240                     DbsOpen = Server2#server.dbs_open + 1,
..241                     {reply, {ok, MainPid},
..242                             Server2#server{dbs_open=DbsOpen}};
..243                 Error ->
..244                     {reply, Error, Server2}
..245                 end;
..246             CloseError ->
..247                 {reply, CloseError, Server}
..248             end;
..249         [{_, {MainPid, PrevLruTime}}] ->
..250             true = ets:insert(couch_dbs_by_name, {DbName, {MainPid, LruTime}}),
..251             true = ets:delete(couch_dbs_by_lru, PrevLruTime),
..252             true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
..253             {reply, {ok, MainPid}, Server}
..254         end;
..255     Error ->
..256         {reply, Error, Server}
..257     end;
..258 handle_call({create, DbName, Options}, _From, Server) ->
..259     DbNameList = binary_to_list(DbName),
..260     case check_dbname(Server, DbNameList) of
..261     ok ->
..262         Filepath = get_full_filename(Server, DbNameList),
..263 
..264         case ets:lookup(couch_dbs_by_name, DbName) of
..265         [] ->
..266             case maybe_close_lru_db(Server) of
..267             {ok, Server2} ->
..268                 case couch_db:start_link(DbName, Filepath, [create|Options]) of
..269                 {ok, MainPid} ->
..270                     LruTime = now(),
..271                     true = ets:insert(couch_dbs_by_name,
..272                             {DbName, {MainPid, LruTime}}),
..273                     true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}),
..274                     true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
..275                     DbsOpen = Server2#server.dbs_open + 1,
..276                     couch_db_update_notifier:notify({created, DbName}),
..277                     {reply, {ok, MainPid},
..278                             Server2#server{dbs_open=DbsOpen}};
..279                 Error ->
..280                     {reply, Error, Server2}
..281                 end;
..282             CloseError ->
..283                 {reply, CloseError, Server}
..284             end;
..285         [_AlreadyRunningDb] ->
..286             {reply, file_exists, Server}
..287         end;
..288     Error ->
..289         {reply, Error, Server}
..290     end;
..291 handle_call({delete, DbName, _Options}, _From, Server) ->
..292     DbNameList = binary_to_list(DbName),
..293     case check_dbname(Server, DbNameList) of
..294     ok ->
..295         FullFilepath = get_full_filename(Server, DbNameList),
..296         Server2 =
..297         case ets:lookup(couch_dbs_by_name, DbName) of
..298         [] -> Server;
..299         [{_, {Pid, LruTime}}] ->
..300             exit(Pid, kill),
..301             receive {'EXIT', Pid, _Reason} -> ok end,
..302             true = ets:delete(couch_dbs_by_name, DbName),
..303             true = ets:delete(couch_dbs_by_pid, Pid),
..304             true = ets:delete(couch_dbs_by_lru, LruTime),
..305             Server#server{dbs_open=Server#server.dbs_open - 1}
..306         end,
..307 
..308         %% Delete any leftover .compact files.  If we don't do this a subsequent
..309         %% request for this DB will try to open the .compact file and use it.
..310         file:delete(FullFilepath ++ ".compact"),
..311 
..312         case file:delete(FullFilepath) of
..313         ok ->
..314             couch_db_update_notifier:notify({deleted, DbName}),
..315             {reply, ok, Server2};
..316         {error, enoent} ->
..317             {reply, not_found, Server2};
..318         Else ->
..319             {reply, Else, Server2}
..320         end;
..321     Error ->
..322         {reply, Error, Server}
..323     end.
..324 
..325 handle_cast(Msg, _Server) ->
..326     exit({unknown_cast_message, Msg}).
..327 
..328 code_change(_OldVsn, State, _Extra) ->
..329     {ok, State}.
..330 
..331 handle_info({'EXIT', _Pid, config_change}, _Server) ->
..332     exit(kill);
..333 handle_info({'EXIT', Pid, _Reason}, #server{dbs_open=DbsOpen}=Server) ->
..334     [{Pid, DbName}] = ets:lookup(couch_dbs_by_pid, Pid),
..335     [{DbName, {Pid, LruTime}}] = ets:lookup(couch_dbs_by_name, DbName),
..336     true = ets:delete(couch_dbs_by_pid, Pid),
..337     true = ets:delete(couch_dbs_by_name, DbName),
..338     true = ets:delete(couch_dbs_by_lru, LruTime),
..339     {noreply, Server#server{dbs_open=DbsOpen - 1}};
..340 handle_info(Info, _Server) ->
..341     exit({unknown_message, Info}).

Generated using etap 0.3.4.