PageRenderTime 206ms CodeModel.GetById 99ms app.highlight 43ms RepoModel.GetById 38ms app.codeStats 10ms

/src/mochitemp.erl

http://github.com/basho/mochiweb
Erlang | 329 lines | 249 code | 37 blank | 43 comment | 1 complexity | 7e587bafa9204c1a4d9eed3985f0ef21 MD5 | raw file
  1%% @author Bob Ippolito <bob@mochimedia.com>
  2%% @copyright 2010 Mochi Media, Inc.
  3%%
  4%% Permission is hereby granted, free of charge, to any person obtaining a
  5%% copy of this software and associated documentation files (the "Software"),
  6%% to deal in the Software without restriction, including without limitation
  7%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8%% and/or sell copies of the Software, and to permit persons to whom the
  9%% Software is furnished to do so, subject to the following conditions:
 10%%
 11%% The above copyright notice and this permission notice shall be included in
 12%% all copies or substantial portions of the Software.
 13%%
 14%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 17%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 19%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 20%% DEALINGS IN THE SOFTWARE.
 21
 22%% @doc Create temporary files and directories. Requires crypto to be started.
 23
 24-module(mochitemp).
 25-export([gettempdir/0]).
 26-export([mkdtemp/0, mkdtemp/3]).
 27-export([rmtempdir/1]).
 28%% -export([mkstemp/4]).
 29-define(SAFE_CHARS, {$a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k, $l, $m,
 30                     $n, $o, $p, $q, $r, $s, $t, $u, $v, $w, $x, $y, $z,
 31                     $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, $M,
 32                     $N, $O, $P, $Q, $R, $S, $T, $U, $V, $W, $X, $Y, $Z,
 33                     $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $_}).
 34-define(TMP_MAX, 10000).
 35
 36-include_lib("kernel/include/file.hrl").
 37
 38%% TODO: An ugly wrapper over the mktemp tool with open_port and sadness?
 39%%       We can't implement this race-free in Erlang without the ability
 40%%       to issue O_CREAT|O_EXCL. I suppose we could hack something with
 41%%       mkdtemp, del_dir, open.
 42%% mkstemp(Suffix, Prefix, Dir, Options) ->
 43%%    ok.
 44
 45rmtempdir(Dir) ->
 46    case file:del_dir(Dir) of
 47        {error, eexist} ->
 48            ok = rmtempdirfiles(Dir),
 49            ok = file:del_dir(Dir);
 50        ok ->
 51            ok
 52    end.
 53
 54rmtempdirfiles(Dir) ->
 55    {ok, Files} = file:list_dir(Dir),
 56    ok = rmtempdirfiles(Dir, Files).
 57
 58rmtempdirfiles(_Dir, []) ->
 59    ok;
 60rmtempdirfiles(Dir, [Basename | Rest]) ->
 61    Path = filename:join([Dir, Basename]),
 62    case filelib:is_dir(Path) of
 63        true ->
 64            ok = rmtempdir(Path);
 65        false ->
 66            ok = file:delete(Path)
 67    end,
 68    rmtempdirfiles(Dir, Rest).
 69
 70mkdtemp() ->
 71    mkdtemp("", "tmp", gettempdir()).
 72
 73mkdtemp(Suffix, Prefix, Dir) ->
 74    mkdtemp_n(rngpath_fun(Suffix, Prefix, Dir), ?TMP_MAX).
 75
 76
 77
 78mkdtemp_n(RngPath, 1) ->
 79    make_dir(RngPath());
 80mkdtemp_n(RngPath, N) ->
 81    try make_dir(RngPath())
 82    catch throw:{error, eexist} ->
 83            mkdtemp_n(RngPath, N - 1)
 84    end.
 85
 86make_dir(Path) ->
 87    case file:make_dir(Path) of
 88        ok ->
 89            ok;
 90        E={error, eexist} ->
 91            throw(E)
 92    end,
 93    %% Small window for a race condition here because dir is created 777
 94    ok = file:write_file_info(Path, #file_info{mode=8#0700}),
 95    Path.
 96
 97rngpath_fun(Prefix, Suffix, Dir) ->
 98    fun () ->
 99            filename:join([Dir, Prefix ++ rngchars(6) ++ Suffix])
100    end.
101
102rngchars(0) ->
103    "";
104rngchars(N) ->
105    [rngchar() | rngchars(N - 1)].
106
107rngchar() ->
108    rngchar(crypto:rand_uniform(0, tuple_size(?SAFE_CHARS))).
109
110rngchar(C) ->
111    element(1 + C, ?SAFE_CHARS).
112
113%% @spec gettempdir() -> string()
114%% @doc Get a usable temporary directory using the first of these that is a directory:
115%%      $TMPDIR, $TMP, $TEMP, "/tmp", "/var/tmp", "/usr/tmp", ".".
116gettempdir() ->
117    gettempdir(gettempdir_checks(), fun normalize_dir/1).
118
119gettempdir_checks() ->
120    [{fun os:getenv/1, ["TMPDIR", "TMP", "TEMP"]},
121     {fun gettempdir_identity/1, ["/tmp", "/var/tmp", "/usr/tmp"]},
122     {fun gettempdir_cwd/1, [cwd]}].
123
124gettempdir_identity(L) ->
125    L.
126
127gettempdir_cwd(cwd) ->
128    {ok, L} = file:get_cwd(),
129    L.
130
131gettempdir([{_F, []} | RestF], Normalize) ->
132    gettempdir(RestF, Normalize);
133gettempdir([{F, [L | RestL]} | RestF], Normalize) ->
134    case Normalize(F(L)) of
135        false ->
136            gettempdir([{F, RestL} | RestF], Normalize);
137        Dir ->
138            Dir
139    end.
140
141normalize_dir(False) when False =:= false orelse False =:= "" ->
142    %% Erlang doesn't have an unsetenv, wtf.
143    false;
144normalize_dir(L) ->
145    Dir = filename:absname(L),
146    case filelib:is_dir(Dir) of
147        false ->
148            false;
149        true ->
150            Dir
151    end.
152
153%%
154%% Tests
155%%
156-ifdef(TEST).
157-include_lib("eunit/include/eunit.hrl").
158
159pushenv(L) ->
160    [{K, os:getenv(K)} || K <- L].
161popenv(L) ->
162    F = fun ({K, false}) ->
163                %% Erlang doesn't have an unsetenv, wtf.
164                os:putenv(K, "");
165            ({K, V}) ->
166                os:putenv(K, V)
167        end,
168    lists:foreach(F, L).
169
170gettempdir_fallback_test() ->
171    ?assertEqual(
172       "/",
173       gettempdir([{fun gettempdir_identity/1, ["/--not-here--/"]},
174                   {fun gettempdir_identity/1, ["/"]}],
175                  fun normalize_dir/1)),
176    ?assertEqual(
177       "/",
178       %% simulate a true os:getenv unset env
179       gettempdir([{fun gettempdir_identity/1, [false]},
180                   {fun gettempdir_identity/1, ["/"]}],
181                  fun normalize_dir/1)),
182    ok.
183
184gettempdir_identity_test() ->
185    ?assertEqual(
186       "/",
187       gettempdir([{fun gettempdir_identity/1, ["/"]}], fun normalize_dir/1)),
188    ok.
189
190gettempdir_cwd_test() ->
191    {ok, Cwd} = file:get_cwd(),
192    ?assertEqual(
193       normalize_dir(Cwd),
194       gettempdir([{fun gettempdir_cwd/1, [cwd]}], fun normalize_dir/1)),
195    ok.
196
197rngchars_test() ->
198    crypto:start(),
199    ?assertEqual(
200       "",
201       rngchars(0)),
202    ?assertEqual(
203       10,
204       length(rngchars(10))),
205    ok.
206
207rngchar_test() ->
208    ?assertEqual(
209       $a,
210       rngchar(0)),
211    ?assertEqual(
212       $A,
213       rngchar(26)),
214    ?assertEqual(
215       $_,
216       rngchar(62)),
217    ok.
218
219mkdtemp_n_failonce_test() ->
220    crypto:start(),
221    D = mkdtemp(),
222    Path = filename:join([D, "testdir"]),
223    %% Toggle the existence of a dir so that it fails
224    %% the first time and succeeds the second.
225    F = fun () ->
226                case filelib:is_dir(Path) of
227                    true ->
228                        file:del_dir(Path);
229                    false ->
230                        file:make_dir(Path)
231                end,
232                Path
233        end,
234    try
235        %% Fails the first time
236        ?assertThrow(
237           {error, eexist},
238           mkdtemp_n(F, 1)),
239        %% Reset state
240        file:del_dir(Path),
241        %% Succeeds the second time
242        ?assertEqual(
243           Path,
244           mkdtemp_n(F, 2))
245    after rmtempdir(D)
246    end,
247    ok.
248
249mkdtemp_n_fail_test() ->
250    {ok, Cwd} = file:get_cwd(),
251    ?assertThrow(
252       {error, eexist},
253       mkdtemp_n(fun () -> Cwd end, 1)),
254    ?assertThrow(
255       {error, eexist},
256       mkdtemp_n(fun () -> Cwd end, 2)),
257    ok.
258
259make_dir_fail_test() ->
260    {ok, Cwd} = file:get_cwd(),
261    ?assertThrow(
262      {error, eexist},
263      make_dir(Cwd)),
264    ok.
265
266mkdtemp_test() ->
267    crypto:start(),
268    D = mkdtemp(),
269    ?assertEqual(
270       true,
271       filelib:is_dir(D)),
272    ?assertEqual(
273       ok,
274       file:del_dir(D)),
275    ok.
276
277rmtempdir_test() ->
278    crypto:start(),
279    D1 = mkdtemp(),
280    ?assertEqual(
281       true,
282       filelib:is_dir(D1)),
283    ?assertEqual(
284       ok,
285       rmtempdir(D1)),
286    D2 = mkdtemp(),
287    ?assertEqual(
288       true,
289       filelib:is_dir(D2)),
290    ok = file:write_file(filename:join([D2, "foo"]), <<"bytes">>),
291    D3 = mkdtemp("suffix", "prefix", D2),
292    ?assertEqual(
293       true,
294       filelib:is_dir(D3)),
295    ok = file:write_file(filename:join([D3, "foo"]), <<"bytes">>),
296    ?assertEqual(
297       ok,
298       rmtempdir(D2)),
299    ?assertEqual(
300       {error, enoent},
301       file:consult(D3)),
302    ?assertEqual(
303       {error, enoent},
304       file:consult(D2)),
305    ok.
306
307gettempdir_env_test() ->
308    Env = pushenv(["TMPDIR", "TEMP", "TMP"]),
309    FalseEnv = [{"TMPDIR", false}, {"TEMP", false}, {"TMP", false}],
310    try
311        popenv(FalseEnv),
312        popenv([{"TMPDIR", "/"}]),
313        ?assertEqual(
314           "/",
315           os:getenv("TMPDIR")),
316        ?assertEqual(
317           "/",
318           gettempdir()),
319        {ok, Cwd} = file:get_cwd(),
320        popenv(FalseEnv),
321        popenv([{"TMP", Cwd}]),
322        ?assertEqual(
323           normalize_dir(Cwd),
324           gettempdir())
325    after popenv(Env)
326    end,
327    ok.
328
329-endif.