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 | ?? | ?? | ?? |
|
....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.