%%-------------------------------------------------------------------- %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %%-------------------------------------------------------------------- -module(emqx_utils_fs). -include_lib("kernel/include/file.hrl"). -export([traverse_dir/3]). -export([read_info/1]). -export([canonicalize/1]). -type fileinfo() :: #file_info{}. -type foldfun(Acc) :: fun((_Filepath :: file:name(), fileinfo() | {error, file:posix()}, Acc) -> Acc). %% @doc Traverse a directory recursively and apply a fold function to each file. %% %% This is a safer version of `filelib:fold_files/5` which does not follow symlinks %% and reports errors to the fold function, giving the user more control over the %% traversal. %% It's not an error if `Dirpath` is not a directory, in which case the fold function %% will be called once with the file info of `Dirpath`. -spec traverse_dir(foldfun(Acc), Acc, _Dirpath :: file:name()) -> Acc. traverse_dir(FoldFun, Acc, Dirpath) -> traverse_dir(FoldFun, Acc, Dirpath, read_info(Dirpath)). traverse_dir(FoldFun, AccIn, DirPath, {ok, #file_info{type = directory}}) -> case file:list_dir(DirPath) of {ok, Filenames} -> lists:foldl( fun(Filename, Acc) -> AbsPath = filename:join(DirPath, Filename), traverse_dir(FoldFun, Acc, AbsPath) end, AccIn, Filenames ); {error, Reason} -> FoldFun(DirPath, {error, Reason}, AccIn) end; traverse_dir(FoldFun, Acc, AbsPath, {ok, Info}) -> FoldFun(AbsPath, Info, Acc); traverse_dir(FoldFun, Acc, AbsPath, {error, Reason}) -> FoldFun(AbsPath, {error, Reason}, Acc). -spec read_info(file:name()) -> {ok, fileinfo()} | {error, file:posix() | badarg}. read_info(AbsPath) -> file:read_link_info(AbsPath, [{time, posix}, raw]). %% @doc Canonicalize a file path. %% Removes stray slashes and converts to a string. -spec canonicalize(file:name()) -> string(). canonicalize(Filename) -> case filename:split(str(Filename)) of Components = [_ | _] -> filename:join(Components); [] -> "" end. str(Value) -> case unicode:characters_to_list(Value, unicode) of Str when is_list(Str) -> Str; {error, _, _} -> erlang:error(badarg, [Value]) end.