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(config_listener).
16-vsn(3).
17
18-export([open/2,create/2,delete/2,get_version/0,get_version/1,get_git_sha/0,get_uuid/0]).
19-export([all_databases/0, all_databases/2]).
20-export([init/1, handle_call/3,sup_start_link/1]).
21-export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
22-export([dev_start/0,is_admin/2,has_admins/0,get_stats/0]).
23-export([close_db_if_idle/1]).
24-export([delete_compaction_files/1]).
25-export([exists/1]).
26-export([get_engine_extensions/0]).
27-export([get_engine_path/2]).
28-export([lock/2, unlock/1]).
29-export([db_updated/1]).
30-export([num_servers/0, couch_server/1, couch_dbs_pid_to_name/1, couch_dbs/1]).
31-export([aggregate_queue_len/0,get_spidermonkey_version/0]).
32
33% config_listener api
34-export([handle_config_change/5, handle_config_terminate/3]).
35
36-include_lib("couch/include/couch_db.hrl").
37-include("couch_server_int.hrl").
38
39-define(MAX_DBS_OPEN, 500).
40-define(RELISTEN_DELAY, 5000).
41-define(DEFAULT_ENGINE, "couch").
42
43-record(server,{
44    root_dir = [],
45    engines = [],
46    max_dbs_open=?MAX_DBS_OPEN,
47    dbs_open=0,
48    start_time="",
49    update_lru_on_read=true,
50    lru = couch_lru:new(),
51    couch_dbs,
52    couch_dbs_pid_to_name,
53    couch_dbs_locks
54    }).
55
56dev_start() ->
57    couch:stop(),
58    up_to_date = make:all([load, debug_info]),
59    couch:start().
60
61get_version() ->
62    ?COUCHDB_VERSION. %% Defined in rebar.config.script
63get_version(short) ->
64  %% strip git hash from version string
65  [Version|_Rest] = string:tokens(get_version(), "+"),
66  Version.
67
68get_git_sha() -> ?COUCHDB_GIT_SHA.
69
70get_uuid() ->
71    case config:get("couchdb", "uuid", undefined) of
72        undefined ->
73            UUID = couch_uuids:random(),
74            config:set("couchdb", "uuid", ?b2l(UUID)),
75            UUID;
76        UUID -> ?l2b(UUID)
77    end.
78
79get_stats() ->
80    Fun = fun(N, {TimeAcc, OpenAcc}) ->
81        {ok, #server{start_time=Time,dbs_open=Open}} =
82            gen_server:call(couch_server(N), get_server),
83        {max(Time, TimeAcc), Open + OpenAcc} end,
84    {Time, Open} =
85    lists:foldl(Fun, {0, 0}, lists:seq(1, num_servers())),
86    [{start_time, ?l2b(Time)}, {dbs_open, Open}].
87
88get_spidermonkey_version() -> list_to_binary(?COUCHDB_SPIDERMONKEY_VERSION).
89
90sup_start_link(N) ->
91    gen_server:start_link({local, couch_server(N)}, couch_server, [N], []).
92
93open(DbName, Options) ->
94    try
95        validate_open_or_create(DbName, Options),
96        open_int(DbName, Options)
97    catch throw:{?MODULE, Error} ->
98        Error
99    end.
100
101open_int(DbName, Options0) ->
102    Ctx = couch_util:get_value(user_ctx, Options0, #user_ctx{}),
103    case ets:lookup(couch_dbs(DbName), DbName) of
104    [#entry{db = Db0, lock = Lock} = Entry] when Lock =/= locked ->
105        update_lru(DbName, Entry#entry.db_options),
106        {ok, Db1} = couch_db:incref(Db0),
107        couch_db:set_user_ctx(Db1, Ctx);
108    _ ->
109        Options = maybe_add_sys_db_callbacks(DbName, Options0),
110        Timeout = couch_util:get_value(timeout, Options, infinity),
111        Create = couch_util:get_value(create_if_missing, Options, false),
112        case gen_server:call(couch_server(DbName), {open, DbName, Options}, Timeout) of
113        {ok, Db0} ->
114            {ok, Db1} = couch_db:incref(Db0),
115            couch_db:set_user_ctx(Db1, Ctx);
116        {not_found, no_db_file} when Create ->
117            couch_log:warning("creating missing database: ~s", [DbName]),
118            couch_server:create(DbName, Options);
119        Error ->
120            Error
121        end
122    end.
123
124update_lru(DbName, Options) ->
125    case config:get_boolean("couchdb", "update_lru_on_read", false) of
126        true ->
127            case lists:member(sys_db, Options) of
128                false -> gen_server:cast(couch_server(DbName), {update_lru, DbName});
129                true -> ok
130            end;
131        false ->
132            ok
133    end.
134
135
136create(DbName, Options) ->
137    try
138        validate_open_or_create(DbName, Options),
139        create_int(DbName, Options)
140    catch throw:{?MODULE, Error} ->
141        Error
142    end.
143
144create_int(DbName, Options0) ->
145    Options = maybe_add_sys_db_callbacks(DbName, Options0),
146    couch_partition:validate_dbname(DbName, Options),
147    case gen_server:call(couch_server(DbName), {create, DbName, Options}, infinity) of
148    {ok, Db0} ->
149        Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
150        {ok, Db1} = couch_db:incref(Db0),
151        couch_db:set_user_ctx(Db1, Ctx);
152    Error ->
153        Error
154    end.
155
156delete(DbName, Options) ->
157    gen_server:call(couch_server(DbName), {delete, DbName, Options}, infinity).
158
159
160exists(DbName) ->
161    RootDir = config:get("couchdb", "database_dir", "."),
162    Engines = get_configured_engines(),
163    Possible = get_possible_engines(DbName, RootDir, Engines),
164    Possible /= [].
165
166
167delete_compaction_files(DbName) ->
168    delete_compaction_files(DbName, []).
169
170delete_compaction_files(DbName, DelOpts) when is_list(DbName) ->
171    RootDir = config:get("couchdb", "database_dir", "."),
172    lists:foreach(fun({Ext, Engine}) ->
173        FPath = make_filepath(RootDir, DbName, Ext),
174        couch_db_engine:delete_compaction_files(Engine, RootDir, FPath, DelOpts)
175    end, get_configured_engines()),
176    ok;
177delete_compaction_files(DbName, DelOpts) when is_binary(DbName) ->
178    delete_compaction_files(?b2l(DbName), DelOpts).
179
180maybe_add_sys_db_callbacks(DbName, Options) when is_binary(DbName) ->
181    maybe_add_sys_db_callbacks(?b2l(DbName), Options);
182maybe_add_sys_db_callbacks(DbName, Options) ->
183    DbsDbName = config:get("mem3", "shards_db", "_dbs"),
184    NodesDbName = config:get("mem3", "nodes_db", "_nodes"),
185
186    IsReplicatorDb = path_ends_with(DbName, "_replicator"),
187    UsersDbSuffix = config:get("couchdb", "users_db_suffix", "_users"),
188    IsUsersDb = path_ends_with(DbName, "_users")
189        orelse path_ends_with(DbName, UsersDbSuffix),
190    if
191        DbName == DbsDbName ->
192            [{before_doc_update, fun mem3_bdu:before_doc_update/3},
193             sys_db | Options];
194        DbName == NodesDbName ->
195            [sys_db | Options];
196        IsReplicatorDb ->
197            [{before_doc_update, fun couch_replicator_docs:before_doc_update/3},
198             {after_doc_read, fun couch_replicator_docs:after_doc_read/2},
199             sys_db | Options];
200        IsUsersDb ->
201            [{before_doc_update, fun couch_users_db:before_doc_update/3},
202             {after_doc_read, fun couch_users_db:after_doc_read/2},
203             sys_db | Options];
204        true ->
205            Options
206    end.
207
208path_ends_with(Path, Suffix) when is_binary(Suffix) ->
209    Suffix =:= couch_db:dbname_suffix(Path);
210path_ends_with(Path, Suffix) when is_list(Suffix) ->
211    path_ends_with(Path, ?l2b(Suffix)).
212
213check_dbname(DbName) ->
214    couch_db:validate_dbname(DbName).
215
216is_admin(User, ClearPwd) ->
217    case config:get("admins", User) of
218    "-hashed-" ++ HashedPwdAndSalt ->
219        [HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","),
220        couch_util:to_hex(crypto:hash(sha, ClearPwd ++ Salt)) == HashedPwd;
221    _Else ->
222        false
223    end.
224
225has_admins() ->
226    config:get("admins") /= [].
227
228hash_admin_passwords() ->
229    hash_admin_passwords(true).
230
231hash_admin_passwords(Persist) ->
232    lists:foreach(
233        fun({User, ClearPassword}) ->
234            HashedPassword = couch_passwords:hash_admin_password(ClearPassword),
235            config:set("admins", User, ?b2l(HashedPassword), Persist)
236        end, couch_passwords:get_unhashed_admins()).
237
238close_db_if_idle(DbName) ->
239    case ets:lookup(couch_dbs(DbName), DbName) of
240        [#entry{}] ->
241            gen_server:cast(couch_server(DbName), {close_db_if_idle, DbName});
242        [] ->
243            ok
244    end.
245
246
247init([N]) ->
248    couch_util:set_mqd_off_heap(?MODULE),
249    couch_util:set_process_priority(?MODULE, high),
250
251    % Mark pluggable storage engines as a supported feature
252    config:enable_feature('pluggable-storage-engines'),
253
254    % Mark partitioned databases as a supported feature
255    config:enable_feature(partitioned),
256
257    % Mark being able to receive documents with an _access property as a supported feature
258    config:enable_feature('access-ready'),
259
260    % Mark if fips is enabled
261    case
262        erlang:function_exported(crypto, info_fips, 0) andalso
263          crypto:info_fips() == enabled of
264        true ->
265            config:enable_feature('fips');
266        false ->
267            ok
268    end,
269
270    % read config and register for configuration changes
271
272    % just stop if one of the config settings change. couch_server_sup
273    % will restart us and then we will pick up the new settings.
274
275    RootDir = config:get("couchdb", "database_dir", "."),
276    Engines = get_configured_engines(),
277    MaxDbsOpen = config:get_integer("couchdb", "max_dbs_open", ?MAX_DBS_OPEN),
278    UpdateLruOnRead = config:get_boolean(
279        "couchdb", "update_lru_on_read", false),
280    ok = config:listen_for_changes(?MODULE, N),
281    ok = couch_file:init_delete_dir(RootDir),
282    hash_admin_passwords(),
283    ets:new(couch_dbs(N), [
284        set,
285        protected,
286        named_table,
287        {keypos, #entry.name},
288        {read_concurrency, true}
289    ]),
290    ets:new(couch_dbs_pid_to_name(N), [set, protected, named_table]),
291    ets:new(couch_dbs_locks(N), [
292        set,
293        public,
294        named_table,
295        {read_concurrency, true}
296    ]),
297    process_flag(trap_exit, true),
298    {ok, #server{root_dir=RootDir,
299                engines = Engines,
300                max_dbs_open=per_couch_server(MaxDbsOpen),
301                update_lru_on_read=UpdateLruOnRead,
302                start_time=couch_util:rfc1123_date(),
303                couch_dbs=couch_dbs(N),
304                couch_dbs_pid_to_name=couch_dbs_pid_to_name(N),
305                couch_dbs_locks=couch_dbs_locks(N)}}.
306
307terminate(Reason, Srv) ->
308    couch_log:error("couch_server terminating with ~p, state ~2048p",
309                    [Reason,
310                     Srv#server{lru = redacted}]),
311    ets:foldl(fun(#entry{db = Db}, _) ->
312        % Filter out any entry records for open_async
313        % processes that haven't finished.
314        if Db == undefined -> ok; true ->
315            couch_util:shutdown_sync(couch_db:get_pid(Db))
316        end
317    end, nil, couch_dbs(Srv)),
318    ok.
319
320handle_config_change("couchdb", "database_dir", _, _, _) ->
321    exit(whereis(couch_server), config_change),
322    remove_handler;
323handle_config_change("couchdb", "update_lru_on_read", "true", _, N) ->
324    gen_server:call(couch_server(N),{set_update_lru_on_read,true}),
325    {ok, N};
326handle_config_change("couchdb", "update_lru_on_read", _, _, N) ->
327    gen_server:call(couch_server(N),{set_update_lru_on_read,false}),
328    {ok, N};
329handle_config_change("couchdb", "max_dbs_open", Max0, _, N) when is_list(Max0) ->
330    Max1 = per_couch_server(list_to_integer(Max0)),
331    gen_server:call(couch_server(N),{set_max_dbs_open,Max1}),
332    {ok, N};
333handle_config_change("couchdb", "max_dbs_open", _, _, N) ->
334    Max = per_couch_server(?MAX_DBS_OPEN),
335    gen_server:call(couch_server(N),{set_max_dbs_open,Max}),
336    {ok, N};
337handle_config_change("couchdb_engines", _, _, _, N) ->
338    gen_server:call(couch_server(N), reload_engines),
339    {ok, N};
340handle_config_change("admins", _, _, Persist, N) ->
341    % spawn here so couch event manager doesn't deadlock
342    spawn(fun() -> hash_admin_passwords(Persist) end),
343    {ok, N};
344handle_config_change("httpd", "authentication_handlers", _, _, N) ->
345    couch_httpd:stop(),
346    {ok, N};
347handle_config_change("httpd", "bind_address", _, _, N) ->
348    couch_httpd:stop(),
349    {ok, N};
350handle_config_change("httpd", "port", _, _, N) ->
351    couch_httpd:stop(),
352    {ok, N};
353handle_config_change("httpd", "max_connections", _, _, N) ->
354    couch_httpd:stop(),
355    {ok, N};
356handle_config_change(_, _, _, _, N) ->
357    {ok, N}.
358
359handle_config_terminate(_, stop, _) ->
360    ok;
361handle_config_terminate(_Server, _Reason, N) ->
362    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), {restart_config_listener, N}).
363
364
365per_couch_server(X) ->
366    erlang:max(1, X div num_servers()).
367
368
369all_databases() ->
370    {ok, DbList} = all_databases(
371        fun(DbName, Acc) -> {ok, [DbName | Acc]} end, []),
372    {ok, lists:usort(DbList)}.
373
374all_databases(Fun, Acc0) ->
375    {ok, #server{root_dir=Root}} = gen_server:call(couch_server_1, get_server),
376    NormRoot = couch_util:normpath(Root),
377    Extensions = get_engine_extensions(),
378    ExtRegExp = "(" ++ string:join(Extensions, "|") ++ ")",
379    RegExp =
380        "^[a-z0-9\\_\\$()\\+\\-]*" % stock CouchDB name regex
381        "(\\.[0-9]{10,})?"         % optional shard timestamp
382        "\\." ++ ExtRegExp ++ "$", % filename extension
383    FinalAcc = try
384    couch_util:fold_files(Root,
385        RegExp,
386        true,
387            fun(Filename, AccIn) ->
388                NormFilename = couch_util:normpath(Filename),
389                case NormFilename -- NormRoot of
390                [$/ | RelativeFilename] -> ok;
391                RelativeFilename -> ok
392                end,
393                Ext = filename:extension(RelativeFilename),
394                case Fun(?l2b(filename:rootname(RelativeFilename, Ext)), AccIn) of
395                {ok, NewAcc} -> NewAcc;
396                {stop, NewAcc} -> throw({stop, Fun, NewAcc})
397                end
398            end, Acc0)
399    catch throw:{stop, Fun, Acc1} ->
400         Acc1
401    end,
402    {ok, FinalAcc}.
403
404
405make_room(Server, Options) ->
406    case lists:member(sys_db, Options) of
407        false -> maybe_close_lru_db(Server);
408        true -> {ok, Server}
409    end.
410
411maybe_close_lru_db(#server{dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server)
412        when NumOpen < MaxOpen ->
413    {ok, Server};
414maybe_close_lru_db(#server{lru=Lru}=Server) ->
415    case couch_lru:close(Lru) of
416        {true, NewLru} ->
417            {ok, db_closed(Server#server{lru = NewLru}, [])};
418        false ->
419            {error, all_dbs_active}
420    end.
421
422open_async(Server, From, DbName, Options) ->
423    NoLRUServer = Server#server{
424        lru = redacted
425    },
426    Parent = self(),
427    T0 = os:timestamp(),
428    Opener = spawn_link(fun() ->
429        Res = open_async_int(NoLRUServer, DbName, Options),
430        IsSuccess = case Res of
431            {ok, _} -> true;
432            _ -> false
433        end,
434        case IsSuccess andalso lists:member(create, Options) of
435            true ->
436                couch_event:notify(DbName, created);
437            false ->
438                ok
439        end,
440        gen_server:call(Parent, {open_result, DbName, Res}, infinity),
441        unlink(Parent),
442        case IsSuccess of
443            true ->
444                % Track latency times for successful opens
445                Diff = timer:now_diff(os:timestamp(), T0) / 1000,
446                couch_stats:update_histogram([couchdb, db_open_time], Diff);
447            false ->
448                % Log unsuccessful open results
449                couch_log:info("open_result error ~p for ~s", [Res, DbName])
450        end
451    end),
452    ReqType = case lists:member(create, Options) of
453        true -> create;
454        false -> open
455    end,
456    true = ets:insert(couch_dbs(Server), #entry{
457        name = DbName,
458        pid = Opener,
459        lock = locked,
460        waiters = [From],
461        req_type = ReqType,
462        db_options = Options
463    }),
464    true = ets:insert(couch_dbs_pid_to_name(Server), {Opener, DbName}),
465    db_opened(Server, Options).
466
467open_async_int(Server, DbName, Options) ->
468    DbNameList = binary_to_list(DbName),
469    case check_dbname(DbNameList) of
470        ok ->
471            case get_engine(Server, DbNameList, Options) of
472                {ok, {Module, FilePath}} ->
473                    couch_db:start_link(Module, DbName, FilePath, Options);
474                Error2 ->
475                    Error2
476            end;
477        Error1 ->
478            Error1
479    end.
480
481handle_call(close_lru, _From, #server{lru=Lru} = Server) ->
482    case couch_lru:close(Lru) of
483        {true, NewLru} ->
484            {reply, ok, db_closed(Server#server{lru = NewLru}, [])};
485        false ->
486            {reply, {error, all_dbs_active}, Server}
487    end;
488handle_call(open_dbs_count, _From, Server) ->
489    {reply, Server#server.dbs_open, Server};
490handle_call({set_update_lru_on_read, UpdateOnRead}, _From, Server) ->
491    {reply, ok, Server#server{update_lru_on_read=UpdateOnRead}};
492handle_call({set_max_dbs_open, Max}, _From, Server) ->
493    {reply, ok, Server#server{max_dbs_open=Max}};
494handle_call(reload_engines, _From, Server) ->
495    {reply, ok, Server#server{engines = get_configured_engines()}};
496handle_call(get_server, _From, Server) ->
497    {reply, {ok, Server}, Server};
498handle_call({open_result, DbName, {ok, Db}}, {Opener, _}, Server) ->
499    true = ets:delete(couch_dbs_pid_to_name(Server), Opener),
500    DbPid = couch_db:get_pid(Db),
501    case ets:lookup(couch_dbs(Server), DbName) of
502        [] ->
503            % db was deleted during async open
504            exit(DbPid, kill),
505            {reply, ok, Server};
506        [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] ->
507            link(DbPid),
508            [gen_server:reply(Waiter, {ok, Db}) || Waiter <- Waiters],
509            % Cancel the creation request if it exists.
510            case ReqType of
511                {create, DbName, _Options, CrFrom} ->
512                    gen_server:reply(CrFrom, file_exists);
513                _ ->
514                    ok
515            end,
516            true = ets:insert(couch_dbs(Server), #entry{
517                name = DbName,
518                db = Db,
519                pid = DbPid,
520                lock = unlocked,
521                db_options = Entry#entry.db_options,
522                start_time = couch_db:get_instance_start_time(Db)
523            }),
524            true = ets:insert(couch_dbs_pid_to_name(Server), {DbPid, DbName}),
525            Lru = case couch_db:is_system_db(Db) of
526                false ->
527                    couch_lru:insert(DbName, Server#server.lru);
528                true ->
529                    Server#server.lru
530            end,
531            {reply, ok, Server#server{lru = Lru}};
532        [#entry{}] ->
533            % A mismatched opener pid means that this open_result message
534            % was in our mailbox but is now stale. Mostly ignore
535            % it except to ensure that the db pid is super dead.
536            exit(couch_db:get_pid(Db), kill),
537            {reply, ok, Server}
538    end;
539handle_call({open_result, DbName, {error, eexist}}, From, Server) ->
540    handle_call({open_result, DbName, file_exists}, From, Server);
541handle_call({open_result, DbName, Error}, {Opener, _}, Server) ->
542    case ets:lookup(couch_dbs(Server), DbName) of
543        [] ->
544            % db was deleted during async open
545            {reply, ok, Server};
546        [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] ->
547            [gen_server:reply(Waiter, Error) || Waiter <- Waiters],
548            true = ets:delete(couch_dbs(Server), DbName),
549            true = ets:delete(couch_dbs_pid_to_name(Server), Opener),
550            NewServer = case ReqType of
551                {create, DbName, Options, CrFrom} ->
552                    open_async(Server, CrFrom, DbName, Options);
553                _ ->
554                    Server
555            end,
556            {reply, ok, db_closed(NewServer, Entry#entry.db_options)};
557        [#entry{}] ->
558            % A mismatched pid means that this open_result message
559            % was in our mailbox and is now stale. Ignore it.
560            {reply, ok, Server}
561    end;
562handle_call({open, DbName, Options}, From, Server) ->
563    case ets:lookup(couch_dbs(Server), DbName) of
564    [] ->
565        case make_room(Server, Options) of
566        {ok, Server2} ->
567            {noreply, open_async(Server2, From, DbName, Options)};
568        CloseError ->
569            {reply, CloseError, Server}
570        end;
571    [#entry{waiters = Waiters} = Entry] when is_list(Waiters) ->
572        true = ets:insert(couch_dbs(Server), Entry#entry{waiters = [From | Waiters]}),
573        NumWaiters = length(Waiters),
574        if NumWaiters =< 10 orelse NumWaiters rem 10 /= 0 -> ok; true ->
575            Fmt = "~b clients waiting to open db ~s",
576            couch_log:info(Fmt, [length(Waiters), DbName])
577        end,
578        {noreply, Server};
579    [#entry{db = Db}] ->
580        {reply, {ok, Db}, Server}
581    end;
582handle_call({create, DbName, Options}, From, Server) ->
583    case ets:lookup(couch_dbs(Server), DbName) of
584    [] ->
585        case make_room(Server, Options) of
586        {ok, Server2} ->
587            CrOptions = [create | Options],
588            {noreply, open_async(Server2, From, DbName, CrOptions)};
589        CloseError ->
590            {reply, CloseError, Server}
591        end;
592    [#entry{req_type = open} = Entry] ->
593        % We're trying to create a database while someone is in
594        % the middle of trying to open it. We allow one creator
595        % to wait while we figure out if it'll succeed.
596        CrOptions = [create | Options],
597        Req = {create, DbName, CrOptions, From},
598        true = ets:insert(couch_dbs(Server), Entry#entry{req_type = Req}),
599        {noreply, Server};
600    [_AlreadyRunningDb] ->
601        {reply, file_exists, Server}
602    end;
603handle_call({delete, DbName, Options}, _From, Server) ->
604    DbNameList = binary_to_list(DbName),
605    case check_dbname(DbNameList) of
606    ok ->
607        Server2 =
608        case ets:lookup(couch_dbs(Server), DbName) of
609        [] -> Server;
610        [#entry{pid = Pid, waiters = Waiters} = Entry] when is_list(Waiters) ->
611            true = ets:delete(couch_dbs(Server), DbName),
612            true = ets:delete(couch_dbs_pid_to_name(Server), Pid),
613            exit(Pid, kill),
614            [gen_server:reply(Waiter, not_found) || Waiter <- Waiters],
615            db_closed(Server, Entry#entry.db_options);
616        [#entry{pid = Pid} = Entry] ->
617            true = ets:delete(couch_dbs(Server), DbName),
618            true = ets:delete(couch_dbs_pid_to_name(Server), Pid),
619            exit(Pid, kill),
620            db_closed(Server, Entry#entry.db_options)
621        end,
622
623        couch_db_plugin:on_delete(DbName, Options),
624
625        DelOpt = [{context, delete} | Options],
626
627        % Make sure and remove all compaction data
628        delete_compaction_files(DbNameList, Options),
629
630        {ok, {Engine, FilePath}} = get_engine(Server, DbNameList),
631        RootDir = Server#server.root_dir,
632        case couch_db_engine:delete(Engine, RootDir, FilePath, DelOpt) of
633        ok ->
634            couch_event:notify(DbName, deleted),
635            {reply, ok, Server2};
636        {error, enoent} ->
637            {reply, not_found, Server2};
638        Else ->
639            {reply, Else, Server2}
640        end;
641    Error ->
642        {reply, Error, Server}
643    end;
644handle_call({db_updated, Db}, _From, Server0) ->
645    DbName = couch_db:name(Db),
646    StartTime = couch_db:get_instance_start_time(Db),
647    Server = try ets:lookup_element(couch_dbs(Server0), DbName, #entry.start_time) of
648        StartTime ->
649            true = ets:update_element(couch_dbs(Server0), DbName, {#entry.db, Db}),
650            Lru = case couch_db:is_system_db(Db) of
651                false -> couch_lru:update(DbName, Server0#server.lru);
652                true -> Server0#server.lru
653            end,
654            Server0#server{lru = Lru};
655        _ ->
656            Server0
657    catch _:_ ->
658        Server0
659    end,
660    {reply, ok, Server}.
661
662handle_cast({update_lru, DbName}, #server{lru = Lru, update_lru_on_read=true} = Server) ->
663    {noreply, Server#server{lru = couch_lru:update(DbName, Lru)}};
664handle_cast({update_lru, _DbName}, Server) ->
665    {noreply, Server};
666handle_cast({close_db_if_idle, DbName}, Server) ->
667    case ets:update_element(couch_dbs(Server), DbName, {#entry.lock, locked}) of
668    true ->
669        [#entry{db = Db, db_options = DbOpts}] = ets:lookup(couch_dbs(Server), DbName),
670        case couch_db:is_idle(Db) of
671        true ->
672            DbPid = couch_db:get_pid(Db),
673            true = ets:delete(couch_dbs(Server), DbName),
674            true = ets:delete(couch_dbs_pid_to_name(Server), DbPid),
675            exit(DbPid, kill),
676            {noreply, db_closed(Server, DbOpts)};
677        false ->
678            true = ets:update_element(
679                couch_dbs(Server), DbName, {#entry.lock, unlocked}),
680            {noreply, Server}
681        end;
682    false ->
683        {noreply, Server}
684    end;
685
686handle_cast(Msg, Server) ->
687    {stop, {unknown_cast_message, Msg}, Server}.
688
689code_change(_OldVsn, #server{}=State, _Extra) ->
690    {ok, State}.
691
692handle_info({'EXIT', _Pid, config_change}, Server) ->
693    {stop, config_change, Server};
694handle_info({'EXIT', Pid, Reason}, Server) ->
695    case ets:lookup(couch_dbs_pid_to_name(Server), Pid) of
696    [{Pid, DbName}] ->
697        [#entry{waiters = Waiters} = Entry] = ets:lookup(couch_dbs(Server), DbName),
698        if Reason /= snappy_nif_not_loaded -> ok; true ->
699            Msg = io_lib:format("To open the database `~s`, Apache CouchDB "
700                "must be built with Erlang OTP R13B04 or higher.", [DbName]),
701            couch_log:error(Msg, [])
702        end,
703        % We kill databases on purpose so there's no reason
704        % to log that fact. So we restrict logging to "interesting"
705        % reasons.
706        if Reason == normal orelse Reason == killed -> ok; true ->
707            couch_log:info("db ~s died with reason ~p", [DbName, Reason])
708        end,
709        if not is_list(Waiters) -> ok; true ->
710            [gen_server:reply(Waiter, Reason) || Waiter <- Waiters]
711        end,
712        true = ets:delete(couch_dbs(Server), DbName),
713        true = ets:delete(couch_dbs_pid_to_name(Server), Pid),
714        {noreply, db_closed(Server, Entry#entry.db_options)};
715    [] ->
716        {noreply, Server}
717    end;
718handle_info({restart_config_listener, N}, State) ->
719    ok = config:listen_for_changes(?MODULE, N),
720    {noreply, State};
721handle_info(Info, Server) ->
722    {stop, {unknown_message, Info}, Server}.
723
724db_opened(Server, Options) ->
725    case lists:member(sys_db, Options) of
726        false -> Server#server{dbs_open=Server#server.dbs_open + 1};
727        true -> Server
728    end.
729
730db_closed(Server, Options) ->
731    case lists:member(sys_db, Options) of
732        false -> Server#server{dbs_open=Server#server.dbs_open - 1};
733        true -> Server
734    end.
735
736validate_open_or_create(DbName, Options) ->
737    case check_dbname(DbName) of
738        ok ->
739            ok;
740        DbNameError ->
741            throw({?MODULE, DbNameError})
742    end,
743
744    case check_engine(Options) of
745        ok ->
746            ok;
747        EngineError ->
748            throw({?MODULE, EngineError})
749    end,
750
751    case ets:lookup(couch_dbs_locks(DbName), DbName) of
752        [] ->
753            ok;
754        [{DbName, Reason}] ->
755            throw({?MODULE, {error, {locked, Reason}}})
756    end.
757
758get_configured_engines() ->
759    ConfigEntries = config:get("couchdb_engines"),
760    Engines = lists:flatmap(fun({Extension, ModuleStr}) ->
761        try
762            [{Extension, list_to_atom(ModuleStr)}]
763        catch _T:_R ->
764            []
765        end
766    end, ConfigEntries),
767    case Engines of
768        [] ->
769            [{"couch", couch_bt_engine}];
770        Else ->
771            Else
772    end.
773
774
775get_engine(Server, DbName, Options) ->
776    #server{
777        root_dir = RootDir,
778        engines = Engines
779    } = Server,
780    case couch_util:get_value(engine, Options) of
781        Ext when is_binary(Ext) ->
782            ExtStr = binary_to_list(Ext),
783            case lists:keyfind(ExtStr, 1, Engines) of
784                {ExtStr, Engine} ->
785                    Path = make_filepath(RootDir, DbName, ExtStr),
786                    {ok, {Engine, Path}};
787                false ->
788                    {error, {invalid_engine_extension, Ext}}
789            end;
790        _ ->
791            get_engine(Server, DbName)
792    end.
793
794
795get_engine(Server, DbName) ->
796    #server{
797        root_dir = RootDir,
798        engines = Engines
799    } = Server,
800    Possible = get_possible_engines(DbName, RootDir, Engines),
801    case Possible of
802        [] ->
803            get_default_engine(Server, DbName);
804        [Engine] ->
805            {ok, Engine};
806        _ ->
807            erlang:error(engine_conflict)
808    end.
809
810
811get_possible_engines(DbName, RootDir, Engines) ->
812    lists:foldl(fun({Extension, Engine}, Acc) ->
813        Path = make_filepath(RootDir, DbName, Extension),
814        case couch_db_engine:exists(Engine, Path) of
815            true ->
816                [{Engine, Path} | Acc];
817            false ->
818                Acc
819        end
820    end, [], Engines).
821
822
823get_default_engine(Server, DbName) ->
824    #server{
825        root_dir = RootDir,
826        engines = Engines
827    } = Server,
828    Default = {couch_bt_engine, make_filepath(RootDir, DbName, "couch")},
829    Extension = config:get("couchdb", "default_engine", ?DEFAULT_ENGINE),
830    case lists:keyfind(Extension, 1, Engines) of
831        {Extension, Module} ->
832            {ok, {Module, make_filepath(RootDir, DbName, Extension)}};
833        false ->
834            Fmt = "Invalid storage engine extension ~s,"
835                    " configured engine extensions are: ~s",
836            Exts = [E || {E, _} <- Engines],
837            Args = [Extension, string:join(Exts, ", ")],
838            couch_log:error(Fmt, Args),
839            {ok, Default}
840    end.
841
842
843make_filepath(RootDir, DbName, Extension) when is_binary(RootDir) ->
844    make_filepath(binary_to_list(RootDir), DbName, Extension);
845make_filepath(RootDir, DbName, Extension) when is_binary(DbName) ->
846    make_filepath(RootDir, binary_to_list(DbName), Extension);
847make_filepath(RootDir, DbName, Extension) when is_binary(Extension) ->
848    make_filepath(RootDir, DbName, binary_to_list(Extension));
849make_filepath(RootDir, DbName, Extension) ->
850    filename:join([RootDir, "./" ++ DbName ++ "." ++ Extension]).
851
852
853get_engine_extensions() ->
854    case config:get("couchdb_engines") of
855        [] ->
856            ["couch"];
857        Entries ->
858            [Ext || {Ext, _Mod} <- Entries]
859    end.
860
861
862check_engine(Options) ->
863    case couch_util:get_value(engine, Options) of
864        Ext when is_binary(Ext) ->
865            ExtStr = binary_to_list(Ext),
866            Extensions = get_engine_extensions(),
867            case lists:member(ExtStr, Extensions) of
868                true ->
869                    ok;
870                false ->
871                    {error, {invalid_engine_extension, Ext}}
872            end;
873        _ ->
874            ok
875    end.
876
877
878get_engine_path(DbName, Engine) when is_binary(DbName), is_atom(Engine) ->
879    RootDir = config:get("couchdb", "database_dir", "."),
880    case lists:keyfind(Engine, 2, get_configured_engines()) of
881        {Ext, Engine} ->
882            {ok, make_filepath(RootDir, DbName, Ext)};
883        false ->
884            {error, {invalid_engine, Engine}}
885    end.
886
887lock(DbName, Reason) when is_binary(DbName), is_binary(Reason) ->
888    case ets:lookup(couch_dbs(DbName), DbName) of
889        [] ->
890            true = ets:insert(couch_dbs_locks(DbName), {DbName, Reason}),
891            ok;
892        [#entry{}] ->
893            {error, already_opened}
894    end.
895
896unlock(DbName) when is_binary(DbName) ->
897    true = ets:delete(couch_dbs_locks(DbName), DbName),
898    ok.
899
900
901db_updated(Db) ->
902    DbName = couch_db:name(Db),
903    gen_server:call(couch_server(DbName), {db_updated, Db}, infinity).
904
905
906couch_server(Arg) ->
907    name("couch_server", Arg).
908
909
910couch_dbs(Arg) ->
911    name("couch_dbs", Arg).
912
913
914couch_dbs_pid_to_name(Arg) ->
915    name("couch_dbs_pid_to_name", Arg).
916
917
918couch_dbs_locks(Arg) ->
919    name("couch_dbs_locks", Arg).
920
921
922name("couch_dbs", #server{} = Server) ->
923    Server#server.couch_dbs;
924
925name("couch_dbs_pid_to_name", #server{} = Server) ->
926    Server#server.couch_dbs_pid_to_name;
927
928name("couch_dbs_locks", #server{} = Server) ->
929    Server#server.couch_dbs_locks;
930
931name(BaseName, DbName) when is_list(DbName) ->
932    name(BaseName, ?l2b(DbName));
933
934name(BaseName, DbName) when is_binary(DbName) ->
935    N = 1 + erlang:phash2(DbName, num_servers()),
936    name(BaseName, N);
937
938name(BaseName, N) when is_integer(N), N > 0 ->
939    list_to_atom(BaseName ++ "_" ++ integer_to_list(N)).
940
941
942num_servers() ->
943    erlang:system_info(schedulers).
944
945
946aggregate_queue_len() ->
947    N = num_servers(),
948    Names = [couch_server(I) || I <- lists:seq(1, N)],
949    MQs = [process_info(whereis(Name), message_queue_len) ||
950        Name <- Names],
951    lists:sum([X || {_, X} <- MQs]).
952
953
954-ifdef(TEST).
955-include_lib("eunit/include/eunit.hrl").
956
957setup_all() ->
958    ok = meck:new(config, [passthrough]),
959    ok = meck:expect(config, get, fun config_get/3),
960    ok.
961
962teardown_all(_) ->
963    meck:unload().
964
965config_get("couchdb", "users_db_suffix", _) -> "users_db";
966config_get(_, _, _) -> undefined.
967
968maybe_add_sys_db_callbacks_pass_test_() ->
969    {
970        setup,
971        fun setup_all/0,
972        fun teardown_all/1,
973        [
974            fun should_add_sys_db_callbacks/0,
975            fun should_not_add_sys_db_callbacks/0
976        ]
977    }.
978
979should_add_sys_db_callbacks() ->
980    Cases = [
981        "shards/00000000-3fffffff/foo/users_db.1415960794.couch",
982        "shards/00000000-3fffffff/foo/users_db.1415960794",
983        "shards/00000000-3fffffff/foo/users_db",
984        "shards/00000000-3fffffff/users_db.1415960794.couch",
985        "shards/00000000-3fffffff/users_db.1415960794",
986        "shards/00000000-3fffffff/users_db",
987
988        "shards/00000000-3fffffff/_users.1415960794.couch",
989        "shards/00000000-3fffffff/_users.1415960794",
990        "shards/00000000-3fffffff/_users",
991
992        "foo/users_db.couch",
993        "foo/users_db",
994        "users_db.couch",
995        "users_db",
996        "foo/_users.couch",
997        "foo/_users",
998        "_users.couch",
999        "_users",
1000
1001        "shards/00000000-3fffffff/foo/_replicator.1415960794.couch",
1002        "shards/00000000-3fffffff/foo/_replicator.1415960794",
1003        "shards/00000000-3fffffff/_replicator",
1004        "foo/_replicator.couch",
1005        "foo/_replicator",
1006        "_replicator.couch",
1007        "_replicator"
1008    ],
1009    lists:foreach(fun(DbName) ->
1010        check_case(DbName, true),
1011        check_case(?l2b(DbName), true)
1012    end, Cases).
1013
1014should_not_add_sys_db_callbacks() ->
1015    Cases = [
1016        "shards/00000000-3fffffff/foo/mydb.1415960794.couch",
1017        "shards/00000000-3fffffff/foo/mydb.1415960794",
1018        "shards/00000000-3fffffff/mydb",
1019        "foo/mydb.couch",
1020        "foo/mydb",
1021        "mydb.couch",
1022        "mydb"
1023    ],
1024    lists:foreach(fun(DbName) ->
1025        check_case(DbName, false),
1026        check_case(?l2b(DbName), false)
1027    end, Cases).
1028
1029check_case(DbName, IsAdded) ->
1030    Options = maybe_add_sys_db_callbacks(DbName, [other_options]),
1031    ?assertEqual(IsAdded, lists:member(sys_db, Options)).
1032
1033-endif.
1034