-module(dirent). -export([opendir/1, readdir/1, readdir_type/1, readdir_all/1, readdir_raw/1, controlling_process/2]). -on_load(init/0). -export_type([dirent/0]). -define(APPNAME, dirent). -define(LIBNAME, dirent). %% Public data types. -type dirent() :: reference(). %% Directory reference. -type filename() :: file:name(). %% File name. -type dirname() :: filename(). %% Directory name. %% @type dtype(). Indicates the file type. %% %%
%%
`device'
%%
This is a block or character device.
%%
`directory'
%%
This is a directory.
%%
`symlink'
%%
This is a symbolic link.
%%
`regular'
%%
This is a regular file.
%%
`other'
%%
This is a UNIX domain socket or named pipe (FIFO).
%%
`undefined'
%%
The file type could not be determined.
%%
-type dtype() :: device | directory | symlink | regular | other | undefined. %% @type posix_error(). Common POSIX errors: %% %%
%%
`enoent'
%%
Directory does not exist, or `Path' is an empty string.
%%
`eacces'
%%
Permission denied.
%%
`emfile'
%%
The per-process limit on the number of open file descriptors has %% been reached.
%%
`enfile'
%%
The system-wide limit on the total number of open files has been %% reached.
%%
`enomem'
%%
Insufficient memory to complete the operation.
%%
`enotdir'
%%
`Path' is not a directory.
%%
-type posix_error() :: enoent | eacces | emfile | enfile | enomem | enotdir | atom(). % Public functions %% @doc Opens the given `Path'. Returns `{ok, DirRef}' if successful, otherwise %% `{error, Reason}'. -spec opendir(Path) -> DirRef | {error, Reason} when Path :: dirname(), DirRef :: dirent(), Reason :: posix_error(). opendir(Path) -> try opendir_nif(encode_path(Path)) of {ok, DirRef} -> {ok, DirRef}; {error, Reason} -> {error, Reason} catch error:badarg -> {error, badarg} end. %% @doc Lists all files in `DirRef', except files with raw %% names. Returns `F' while iterating over the directory or `finished' when %% done. %% %% If the filename is a `binary()' with characters coded in ISO Latin-1 and the %% VM was started with parameter `+fnue', the function returns %% `{error, {no_translation, RawName}}'. %% %% If called by any other process than the current controlling process, %% `{error, not_owner}' is returned. %% %% The names are not sorted. -spec readdir(DirRef) -> F | finished | {error, Reason} when DirRef :: dirent(), F :: filename(), Reason :: {no_translation, RawName} | not_owner, RawName :: binary(). readdir(DirRef) -> try case readdir_nif(DirRef, false) of finished -> finished; {error, Reason} -> {error, Reason}; RawName -> case readdir_skip(RawName) of skip -> readdir(DirRef); {error, Reason} -> {error, Reason}; F -> F end end catch error:badarg -> {error, badarg} end. readdir_skip(RawName) -> case decode_path(RawName) of Converted when is_list(Converted) -> Converted; %% If the filename cannot be converted, return error or ignore with %% optional error logger warning depending on +fn{u|a}{i|e|w} emulator %% switches. {error, ignore} -> skip; {error, warning} -> %% This is equal to calling logger:warning/3 which %% we don't want to do from code_server during system boot. %% We don't want to call logger:timestamp() either. catch logger ! {log,warning,"Non-unicode filename ~p ignored\n", [RawName], #{pid=>self(), gl=>group_leader(), time=>os:system_time(microsecond), error_logger=>#{tag=>warning_msg}}}, skip; {error, _} -> {error, {no_translation, RawName}} end. %% @doc Lists all files in `DirRef', except files with raw %% names, returning also the file type. %% %% Returns `{F, Dtype}' or `finished' when done. `Dtype' can be used to %% avoid making a second call to `file:read_file_info/1,2' if the filesystem %% has support to it in the system `readdir'. If the filesystem does not have %% support, `Dtype' will be always `undefined'. %% %% If the filename is a `binary()' with characters coded in ISO Latin-1 and the %% VM was started with parameter `+fnue', the function returns %% `{error, {no_translation, RawName}}'. %% %% If called by any other process than the current controlling process, %% `{error, not_owner}' is returned. %% %% The names are not sorted. -spec readdir_type(DirRef) -> {F, Dtype} | finished | {error, Reason} when DirRef :: dirent(), F :: filename(), Dtype :: dtype(), Reason :: {no_translation, RawName} | not_owner, RawName :: binary(). readdir_type(DirRef) -> try case readdir_nif(DirRef, true) of finished -> finished; {error, Reason} -> {error, Reason}; {RawName, Dtype} -> case readdir_skip(RawName) of skip -> readdir_type(DirRef); {error, Reason} -> {error, Reason}; F -> {F, Dtype} end end catch error:badarg -> {error, badarg} end. %% @doc Lists all files in `DirRef', including files with raw names, returning %% also the file type. %% %% Returns `{F, Dtype}' or `finished' when done. `Dtype' can be used to avoid %% making a second call to `file:read_file_info/1,2' if the filesystem has %% support to it in the system `readdir'. If the filesystem does not have %% support, `Dtype' will be always `undefined'. %% %% If Unicode filename translation is in effect and the file system is %% transparent, filenames that cannot be interpreted as Unicode can be %% encountered, in which case `F' will represent a raw filename (that is, %% binary). %% %% If called by any other process than the current controlling process, %% `{error, not_owner}' is returned. %% %% The names are not sorted. -spec readdir_all(DirRef) -> {F, Dtype} | finished | {error, Reason} when DirRef :: dirent(), F :: filename() | binary(), Dtype :: dtype(), Reason :: not_owner. readdir_all(DirRef) -> try case readdir_nif(DirRef, true) of finished -> finished; {error, Reason} -> {error, Reason}; {RawName, Dtype} -> case decode_path(RawName) of Converted when is_list(Converted) -> {Converted, Dtype}; {error, _} -> {RawName, Dtype} end end catch error:badarg -> {error, badarg} end. %% @doc Lists raw filenames in `DirRef', returning also the file type. %% %% No Unicode translation is made on the filename, and it is returned in raw %% format (binary). %% %% Returns `{F, Dtype}' or `finished' when done. `Dtype' can be used to avoid %% making a second call to `file:read_file_info/1,2' if the filesystem has %% support to it in the system `readdir'. If the filesystem does not have %% support, `Dtype' will be always `undefined'. %% %% The names are not sorted. -spec readdir_raw(DirRef) -> {F, Dtype} | finished | {error, Reason} when DirRef :: dirent(), F :: filename() | binary(), Dtype :: dtype(), Reason :: not_owner. readdir_raw(DirRef) -> try case readdir_nif(DirRef, true) of finished -> finished; {error, Reason} -> {error, Reason}; {RawName, Dtype} -> {RawName, Dtype} end catch error:badarg -> {error, badarg} end. %% @doc Set controlling affinity. %% %% Once created, `DirRef' is associated to the calling process and reading %% functions should be executed by the same process. If passing to another %% process is required, then this function should be called from the process %% owner, delegating control to another process indicated by `Pid'. -spec controlling_process(DirRef, Pid) -> 'ok' when DirRef :: dirent(), Pid :: pid(). controlling_process(DirRef, Pid) -> set_controller_nif(DirRef, Pid). % Private functions encode_path(Path) -> prim_file:internal_name2native(Path). decode_path(NativePath) when is_binary(NativePath) -> prim_file:internal_native2name(NativePath). % NIF loading set_controller_nif(_DirRef, _Pid) -> not_loaded(?LINE). opendir_nif(_Path) -> not_loaded(?LINE). readdir_nif(_DirRef, _ReturnDtype) -> not_loaded(?LINE). init() -> SoName = case code:priv_dir(?APPNAME) of {error, bad_name} -> case filelib:is_dir(filename:join(["..", priv])) of true -> filename:join(["..", priv, ?LIBNAME]); _ -> filename:join([priv, ?LIBNAME]) end; Dir -> filename:join(Dir, ?LIBNAME) end, erlang:load_nif(SoName, 0). not_loaded(Line) -> erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).