emqx_utils_fs.erl 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_utils_fs).
  17. -include_lib("kernel/include/file.hrl").
  18. -export([traverse_dir/3]).
  19. -export([read_info/1]).
  20. -export([canonicalize/1]).
  21. -type fileinfo() :: #file_info{}.
  22. -type foldfun(Acc) ::
  23. fun((_Filepath :: file:name(), fileinfo() | {error, file:posix()}, Acc) -> Acc).
  24. %% @doc Traverse a directory recursively and apply a fold function to each file.
  25. %%
  26. %% This is a safer version of `filelib:fold_files/5` which does not follow symlinks
  27. %% and reports errors to the fold function, giving the user more control over the
  28. %% traversal.
  29. %% It's not an error if `Dirpath` is not a directory, in which case the fold function
  30. %% will be called once with the file info of `Dirpath`.
  31. -spec traverse_dir(foldfun(Acc), Acc, _Dirpath :: file:name()) ->
  32. Acc.
  33. traverse_dir(FoldFun, Acc, Dirpath) ->
  34. traverse_dir(FoldFun, Acc, Dirpath, read_info(Dirpath)).
  35. traverse_dir(FoldFun, AccIn, DirPath, {ok, #file_info{type = directory}}) ->
  36. case file:list_dir(DirPath) of
  37. {ok, Filenames} ->
  38. lists:foldl(
  39. fun(Filename, Acc) ->
  40. AbsPath = filename:join(DirPath, Filename),
  41. traverse_dir(FoldFun, Acc, AbsPath)
  42. end,
  43. AccIn,
  44. Filenames
  45. );
  46. {error, Reason} ->
  47. FoldFun(DirPath, {error, Reason}, AccIn)
  48. end;
  49. traverse_dir(FoldFun, Acc, AbsPath, {ok, Info}) ->
  50. FoldFun(AbsPath, Info, Acc);
  51. traverse_dir(FoldFun, Acc, AbsPath, {error, Reason}) ->
  52. FoldFun(AbsPath, {error, Reason}, Acc).
  53. -spec read_info(file:name()) ->
  54. {ok, fileinfo()} | {error, file:posix() | badarg}.
  55. read_info(AbsPath) ->
  56. file:read_link_info(AbsPath, [{time, posix}, raw]).
  57. %% @doc Canonicalize a file path.
  58. %% Removes stray slashes and converts to a string.
  59. -spec canonicalize(file:name()) ->
  60. string().
  61. canonicalize(Filename) ->
  62. case filename:split(str(Filename)) of
  63. Components = [_ | _] ->
  64. filename:join(Components);
  65. [] ->
  66. ""
  67. end.
  68. str(Value) ->
  69. case unicode:characters_to_list(Value, unicode) of
  70. Str when is_list(Str) ->
  71. Str;
  72. {error, _, _} ->
  73. erlang:error(badarg, [Value])
  74. end.