Hello Elixir Forum,
I have a block of pseudo-code saying this:
if !File.exists?(private_key) do
generate_new_private_key(private_key)
...
end
The problem is, the implementation of File.exists?/1
is as follows:
def exists?(path) do
match?({:ok, _}, :file.read_file_info(IO.chardata_to_string(path)))
end
Erlang’s :file.read_file_info/1
can for example return {:error, :emfile}
, which is linux-speak for “Too many files open”. If that happens, File.exists?/1
returns false, and my code concludes there is no private key. If another process frees a file descriptor, my code will destroy my private key.
What is the proper way to handle these kinds of situations, besides implementing File.exists?/1
myself and having it return a format which handles this (nil, tuple return format, etc…)
I’m not sure on the best way to handle it, but you can use File.stat/2
to get an elixir-friendly version of :file.read_file_info
. This lets you do something like:
case File.stat(key_file) do
{:ok, _} -> handle_file_exists(key_file)
{:error, :enoent} -> handle_file_doesnt_exist(key_file)
{:error, :emfile} -> handle_too_many_descriptors(key_file)
{:error, :enomem} -> handle_oom(key_file)
{:error, reason} -> handle_generic(reason, keyfile)
end
The error reasons from File.stat/2
are the same as File.read/1
, so if you are trying to either read it if it exists or create it if it doesn’t then you can skip stat and just use read instead.
There is probably a potential race condition where when you use File.stat
there are enough free descriptors, but when you go to write the new key file the descriptors are all used up. Though I think if you are teetering on the edge of resource consumption probably a more holistic approach is in order.