Add bcrypt_pool for pooling a set of 'worker' port processes
diff --git a/.gitignore b/.gitignore
index 061d5ee..b43c9fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
 priv/bcrypt
 priv/bcrypt_nif.so
 logs/*
+.eunit
 
 ## libtool
 .deps
diff --git a/Makefile b/Makefile
index 8541698..c7371a9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,22 @@
+CC = gcc
+CFLAGS = -g -O2 -Wall
+ERLANG_LIBS = -lerl_interface -lei -lpthread
+
+BCRYPTLIBS = c_src/bcrypt_port.o c_src/bcrypt.o c_src/blowfish.o
+
 all: compile
 
+compile_port: priv/bcrypt
+
+priv/bcrypt: $(BCRYPTLIBS)
+	$(CC) $(CFLAGS) $(BCRYPTLIBS) $(ERLANG_LIBS) -o $@
+
 compile:
 	@ ./rebar compile
 
 tests:
 	@ ./rebar eunit
-	
+
 clean:
-	@ ./rebar clean
\ No newline at end of file
+	@ ./rebar clean
+
diff --git a/rebar.config b/rebar.config
index f7aa888..72a0f86 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,8 +1,8 @@
 %% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*-
-{so_specs,
- [{"priv/bcrypt_nif.so",
-   ["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_nif.c"]},
-  {"priv/bcrypt",
-   ["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_port.c"]}]}.
+{so_name, "bcrypt_nif.so"}.
+{port_sources, ["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_nif.c"]}.
 
 {erl_opts, [debug_info]}.
+
+{pre_hooks, [{clean, "rm -f priv/bcrypt c_src/bcrypt_port.o"},
+             {compile, "make compile_port"}]}.
diff --git a/src/bcrypt.app.src b/src/bcrypt.app.src
index 8620680..ad759e4 100644
--- a/src/bcrypt.app.src
+++ b/src/bcrypt.app.src
@@ -10,7 +10,7 @@
          {default_log_rounds, 12},
 
          % Mechanism to use 'nif' or 'port'
-         {mechanism, nif},
+         {mechanism, port},
 
          % Size of port program pool
          {pool_size, 4}
diff --git a/src/bcrypt_pool.erl b/src/bcrypt_pool.erl
new file mode 100644
index 0000000..3b72be0
--- /dev/null
+++ b/src/bcrypt_pool.erl
@@ -0,0 +1,81 @@
+%% Copyright (c) 2011 Hunter Morris
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_pool).
+-author('Hunter Morris <huntermorris@gmail.com>').
+
+-behaviour(gen_server).
+
+-export([start_link/0, available/1]).
+-export([gen_salt/0, gen_salt/1]).
+-export([hashpw/2]).
+
+%% gen_server
+-export([init/1, code_change/3, terminate/2,
+         handle_call/3, handle_cast/2, handle_info/2]).
+
+-record(state, {
+          size = 0,
+          busy = 0,
+          requests = queue:new(),
+          ports = queue:new()
+         }).
+
+-record(req, {mon, from}).
+
+start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+available(Pid) -> gen_server:cast(?MODULE, {available, Pid}).
+
+gen_salt()             -> do_call(fun bcrypt_port:gen_salt/1, []).
+gen_salt(Rounds)       -> do_call(fun bcrypt_port:gen_salt/2, [Rounds]).
+hashpw(Password, Salt) -> do_call(fun bcrypt_port:hashpw/3, [Password, Salt]).
+
+init([]) ->
+    {ok, Size} = application:get_env(bcrypt, pool_size),
+    {ok, #state{size = Size}}.
+
+terminate(shutdown, _) -> ok.
+
+handle_call(request, {RPid, _} = From, #state{ports = P} = State) ->
+    case queue:out(P) of
+        {empty, P} ->
+            #state{size = Size, busy = B, requests = R} = State,
+            B1 =
+                if Size > B ->
+                        {ok, _} = bcrypt_port_sup:start_child(),
+                        B + 1;
+                   true ->
+                        B
+                end,
+            RRef = erlang:monitor(process, RPid),
+            R1 = queue:in(#req{mon = RRef, from = From}, R),
+            {noreply, State#state{requests = R1,
+                                  busy = B1}};
+        {{value, PPid}, P1} ->
+            #state{busy = B} = State,
+            {reply, {ok, PPid}, State#state{busy = B + 1, ports = P1}}
+    end;
+handle_call(Msg, _, _) -> exit({unknown_call, Msg}).
+
+handle_cast(
+  {available, Pid},
+  #state{requests = R, ports = P, busy = B} = S) ->
+    case queue:out(R) of
+        {empty, R} ->
+            {noreply, S#state{ports = queue:in(Pid, P), busy = B - 1}};
+        {{value, #req{mon = Mon, from = F}}, R1} ->
+            true = erlang:demonitor(Mon, [flush]),
+            gen_server:reply(F, {ok, Pid}),
+            {noreply, S#state{requests = R1}}
+    end;
+handle_cast(Msg, _) -> exit({unknown_cast, Msg}).
+
+handle_info({'DOWN', Ref, process, _Pid, _Reason}, #state{requests = R} = State) ->
+    R1 = queue:from_list(lists:keydelete(Ref, #req.mon, queue:to_list(R))),
+    {noreply, State#state{requests = R1}};
+handle_info(Msg, _) -> exit({unknown_info, Msg}).
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+do_call(F, Args0) ->
+    {ok, Pid} = gen_server:call(?MODULE, request, infinity),
+    Args = [Pid|Args0],
+    apply(F, Args).
diff --git a/src/bcrypt_port.erl b/src/bcrypt_port.erl
new file mode 100644
index 0000000..90ca174
--- /dev/null
+++ b/src/bcrypt_port.erl
@@ -0,0 +1,108 @@
+%% Copyright (c) 2011 Hunter Morris
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_port).
+-author('Hunter Morris <hunter.morris@smarkets.com>').
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0, stop/0]).
+-export([gen_salt/1, gen_salt/2]).
+-export([hashpw/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+         code_change/3]).
+
+-record(state, {port, default_log_rounds}).
+
+-define(CMD_SALT, 0).
+-define(CMD_HASHPW, 1).
+-define(BCRYPT_ERROR(F, D), error_logger:error_msg(F, D)).
+-define(BCRYPT_WARNING(F, D), error_logger:warning_msg(F, D)).
+
+start_link() ->
+    Dir = case code:priv_dir(bcrypt) of
+              {error, bad_name} ->
+                  case code:which(bcrypt) of
+                      Filename when is_list(Filename) ->
+                          filename:join(
+                            [filename:dirname(Filename), "../priv"]);
+                      _ ->
+                          "../priv"
+                  end;
+              Priv -> Priv
+          end,
+    Port = filename:join(Dir, "bcrypt"),
+    gen_server:start_link(?MODULE, [Port], []).
+
+stop() -> gen_server:call(?MODULE, stop).
+
+gen_salt(Pid) ->
+    R = crypto:rand_bytes(16),
+    gen_server:call(Pid, {encode_salt, R}, infinity).
+
+gen_salt(Pid, LogRounds) ->
+    R = crypto:rand_bytes(16),
+    gen_server:call(Pid, {encode_salt, R, LogRounds}, infinity).
+
+hashpw(Pid, Password, Salt) ->
+    gen_server:call(Pid, {hashpw, Password, Salt}, infinity).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+init([Filename]) ->
+    case file:read_file_info(Filename) of
+        {ok, _Info} ->
+            Port = open_port(
+                     {spawn, Filename}, [{packet, 2}, binary, exit_status]),
+            ok = bcrypt_pool:available(self()),
+            {ok, Rounds} = application:get_env(bcrypt, default_log_rounds),
+            {ok, #state{port = Port, default_log_rounds = Rounds}};
+        {error, Reason} ->
+            ?BCRYPT_ERROR("Can't open file ~p: ~p", [Filename, Reason]),
+            {stop, error_opening_bcrypt_file}
+    end.
+
+terminate(_Reason, #state{port=Port}) ->
+    catch port_close(Port),
+    ok.
+
+handle_call({encode_salt, R}, From, #state{default_log_rounds = LogRounds} = State) ->
+    handle_call({encode_salt, R, LogRounds}, From, State);
+handle_call({encode_salt, R, LogRounds}, From, State) ->
+    Port = State#state.port,
+    Data = term_to_binary({?CMD_SALT, From, {R, LogRounds}}),
+    port_command(Port, Data),
+    {noreply, State};
+handle_call({hashpw, Password, Salt}, From, State) ->
+    Port = State#state.port,
+    Data = term_to_binary({?CMD_HASHPW, From, {Password, Salt}}),
+    port_command(Port, Data),
+    {noreply, State};
+handle_call(stop, _From, State) ->
+    {stop, normal, ok, State};
+handle_call(Msg, _, _) -> exit({unknown_call, Msg}).
+
+handle_cast(Msg, _) -> exit({unknown_cast, Msg}).
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+handle_info({Port, {data, Data}}, #state{port=Port}=State) ->
+    {Cmd, To, Reply0} = binary_to_term(Data),
+    Reply =
+        case Reply0 of
+            "Invalid salt" -> {error, invalid_salt};
+            "Invalid salt length" -> {error, invalid_salt_length};
+            "Invalid number of rounds" -> {error, invalid_rounds};
+            _ -> {ok, Reply0}
+        end,
+    gen_server:reply(To, Reply),
+    ok = bcrypt_pool:available(self()),
+    {noreply, State};
+handle_info({Port, {exit_status, Status}}, #state{port=Port}=State) ->
+    %% Rely on whomever is supervising this process to restart.
+    ?BCRYPT_WARNING("Port died: ~p", [Status]),
+    {stop, port_died, State};
+handle_info(Msg, _) -> exit({unknown_info, Msg}).
diff --git a/src/bcrypt_port_sup.erl b/src/bcrypt_port_sup.erl
index 61752e0..451751f 100644
--- a/src/bcrypt_port_sup.erl
+++ b/src/bcrypt_port_sup.erl
@@ -5,9 +5,10 @@
 
 -behaviour(supervisor).
 
--export([start_link/0, init/1]).
+-export([start_link/0, start_child/0, init/1]).
 
 start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+start_child() -> supervisor:start_child(?MODULE, []).
 
 init([]) ->
     {ok, {{simple_one_for_one, 1, 1},