%% The contents of this file are subject to the Mozilla Public License %% Version 1.1 (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License %% at http://www.mozilla.org/MPL/ %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and %% limitations under the License. %% %% The Original Code is RabbitMQ. %% %% The Initial Developer of the Original Code is GoPivotal, Inc. %% Copyright (c) 2011-2014 GoPivotal, Inc. All rights reserved. %% -module(rabbit_file). -include_lib("kernel/include/file.hrl"). -export([is_file/1, is_dir/1, file_size/1, ensure_dir/1, wildcard/2, list_dir/1]). -export([read_term_file/1, write_term_file/2, write_file/2, write_file/3]). -export([append_file/2, ensure_parent_dirs_exist/1]). -export([rename/2, delete/1, recursive_delete/1, recursive_copy/2]). -export([lock_file/1]). -import(file_handle_cache, [with_handle/1, with_handle/2]). -define(TMP_EXT, ".tmp"). %%---------------------------------------------------------------------------- -ifdef(use_specs). -type(ok_or_error() :: rabbit_types:ok_or_error(any())). -spec(is_file/1 :: ((file:filename())) -> boolean()). -spec(is_dir/1 :: ((file:filename())) -> boolean()). -spec(file_size/1 :: ((file:filename())) -> non_neg_integer()). -spec(ensure_dir/1 :: ((file:filename())) -> ok_or_error()). -spec(wildcard/2 :: (string(), file:filename()) -> [file:filename()]). -spec(list_dir/1 :: (file:filename()) -> rabbit_types:ok_or_error2( [file:filename()], any())). -spec(read_term_file/1 :: (file:filename()) -> {'ok', [any()]} | rabbit_types:error(any())). -spec(write_term_file/2 :: (file:filename(), [any()]) -> ok_or_error()). -spec(write_file/2 :: (file:filename(), iodata()) -> ok_or_error()). -spec(write_file/3 :: (file:filename(), iodata(), [any()]) -> ok_or_error()). -spec(append_file/2 :: (file:filename(), string()) -> ok_or_error()). -spec(ensure_parent_dirs_exist/1 :: (string()) -> 'ok'). -spec(rename/2 :: (file:filename(), file:filename()) -> ok_or_error()). -spec(delete/1 :: ([file:filename()]) -> ok_or_error()). -spec(recursive_delete/1 :: ([file:filename()]) -> rabbit_types:ok_or_error({file:filename(), any()})). -spec(recursive_copy/2 :: (file:filename(), file:filename()) -> rabbit_types:ok_or_error({file:filename(), file:filename(), any()})). -spec(lock_file/1 :: (file:filename()) -> rabbit_types:ok_or_error('eexist')). -endif. %%---------------------------------------------------------------------------- is_file(File) -> case read_file_info(File) of {ok, #file_info{type=regular}} -> true; {ok, #file_info{type=directory}} -> true; _ -> false end. is_dir(Dir) -> is_dir_internal(read_file_info(Dir)). is_dir_no_handle(Dir) -> is_dir_internal(prim_file:read_file_info(Dir)). is_dir_internal({ok, #file_info{type=directory}}) -> true; is_dir_internal(_) -> false. file_size(File) -> case read_file_info(File) of {ok, #file_info{size=Size}} -> Size; _ -> 0 end. ensure_dir(File) -> with_handle(fun () -> ensure_dir_internal(File) end). ensure_dir_internal("/") -> ok; ensure_dir_internal(File) -> Dir = filename:dirname(File), case is_dir_no_handle(Dir) of true -> ok; false -> ensure_dir_internal(Dir), prim_file:make_dir(Dir) end. wildcard(Pattern, Dir) -> case list_dir(Dir) of {ok, Files} -> {ok, RE} = re:compile(Pattern, [anchored]), [File || File <- Files, match =:= re:run(File, RE, [{capture, none}])]; {error, _} -> [] end. list_dir(Dir) -> with_handle(fun () -> prim_file:list_dir(Dir) end). read_file_info(File) -> with_handle(fun () -> prim_file:read_file_info(File) end). read_term_file(File) -> try {ok, Data} = with_handle(fun () -> prim_file:read_file(File) end), {ok, Tokens, _} = erl_scan:string(binary_to_list(Data)), TokenGroups = group_tokens(Tokens), {ok, [begin {ok, Term} = erl_parse:parse_term(Tokens1), Term end || Tokens1 <- TokenGroups]} catch error:{badmatch, Error} -> Error end. group_tokens(Ts) -> [lists:reverse(G) || G <- group_tokens([], Ts)]. group_tokens([], []) -> []; group_tokens(Cur, []) -> [Cur]; group_tokens(Cur, [T = {dot, _} | Ts]) -> [[T | Cur] | group_tokens([], Ts)]; group_tokens(Cur, [T | Ts]) -> group_tokens([T | Cur], Ts). write_term_file(File, Terms) -> write_file(File, list_to_binary([io_lib:format("~w.~n", [Term]) || Term <- Terms])). write_file(Path, Data) -> write_file(Path, Data, []). write_file(Path, Data, Modes) -> Modes1 = [binary, write | (Modes -- [binary, write])], case make_binary(Data) of Bin when is_binary(Bin) -> write_file1(Path, Bin, Modes1); {error, _} = E -> E end. %% make_binary/1 is based on the corresponding function in the %% kernel/file.erl module of the Erlang R14B02 release, which is %% licensed under the EPL. make_binary(Bin) when is_binary(Bin) -> Bin; make_binary(List) -> try iolist_to_binary(List) catch error:Reason -> {error, Reason} end. write_file1(Path, Bin, Modes) -> try with_synced_copy(Path, Modes, fun (Hdl) -> ok = prim_file:write(Hdl, Bin) end) catch error:{badmatch, Error} -> Error; _:{error, Error} -> {error, Error} end. with_synced_copy(Path, Modes, Fun) -> case lists:member(append, Modes) of true -> {error, append_not_supported, Path}; false -> with_handle( fun () -> Bak = Path ++ ?TMP_EXT, case prim_file:open(Bak, Modes) of {ok, Hdl} -> try Result = Fun(Hdl), ok = prim_file:sync(Hdl), ok = prim_file:rename(Bak, Path), Result after prim_file:close(Hdl) end; {error, _} = E -> E end end) end. %% TODO the semantics of this function are rather odd. But see bug 25021. append_file(File, Suffix) -> case read_file_info(File) of {ok, FInfo} -> append_file(File, FInfo#file_info.size, Suffix); {error, enoent} -> append_file(File, 0, Suffix); Error -> Error end. append_file(_, _, "") -> ok; append_file(File, 0, Suffix) -> with_handle(fun () -> case prim_file:open([File, Suffix], [append]) of {ok, Fd} -> prim_file:close(Fd); Error -> Error end end); append_file(File, _, Suffix) -> case with_handle(2, fun () -> file:copy(File, {[File, Suffix], [append]}) end) of {ok, _BytesCopied} -> ok; Error -> Error end. ensure_parent_dirs_exist(Filename) -> case ensure_dir(Filename) of ok -> ok; {error, Reason} -> throw({error, {cannot_create_parent_dirs, Filename, Reason}}) end. rename(Old, New) -> with_handle(fun () -> prim_file:rename(Old, New) end). delete(File) -> with_handle(fun () -> prim_file:delete(File) end). recursive_delete(Files) -> with_handle( fun () -> lists:foldl(fun (Path, ok) -> recursive_delete1(Path); (_Path, {error, _Err} = Error) -> Error end, ok, Files) end). recursive_delete1(Path) -> case is_dir_no_handle(Path) and not(is_symlink_no_handle(Path)) of false -> case prim_file:delete(Path) of ok -> ok; {error, enoent} -> ok; %% Path doesn't exist anyway {error, Err} -> {error, {Path, Err}} end; true -> case prim_file:list_dir(Path) of {ok, FileNames} -> case lists:foldl( fun (FileName, ok) -> recursive_delete1( filename:join(Path, FileName)); (_FileName, Error) -> Error end, ok, FileNames) of ok -> case prim_file:del_dir(Path) of ok -> ok; {error, Err} -> {error, {Path, Err}} end; {error, _Err} = Error -> Error end; {error, Err} -> {error, {Path, Err}} end end. is_symlink_no_handle(File) -> case prim_file:read_link(File) of {ok, _} -> true; _ -> false end. recursive_copy(Src, Dest) -> %% Note that this uses the 'file' module and, hence, shouldn't be %% run on many processes at once. case is_dir(Src) of false -> case file:copy(Src, Dest) of {ok, _Bytes} -> ok; {error, enoent} -> ok; %% Path doesn't exist anyway {error, Err} -> {error, {Src, Dest, Err}} end; true -> case file:list_dir(Src) of {ok, FileNames} -> case file:make_dir(Dest) of ok -> lists:foldl( fun (FileName, ok) -> recursive_copy( filename:join(Src, FileName), filename:join(Dest, FileName)); (_FileName, Error) -> Error end, ok, FileNames); {error, Err} -> {error, {Src, Dest, Err}} end; {error, Err} -> {error, {Src, Dest, Err}} end end. %% TODO: When we stop supporting Erlang prior to R14, this should be %% replaced with file:open [write, exclusive] lock_file(Path) -> case is_file(Path) of true -> {error, eexist}; false -> with_handle( fun () -> {ok, Lock} = prim_file:open(Path, [write]), ok = prim_file:close(Lock) end) end.