diff --git a/lib/elixir/lib/file.ex b/lib/elixir/lib/file.ex index 9e334dbb631..d6d7388142b 100644 --- a/lib/elixir/lib/file.ex +++ b/lib/elixir/lib/file.ex @@ -1277,18 +1277,24 @@ defmodule File do {:ok, files} -> case mkdir(dest) do success when success in [:ok, {:error, :eexist}] -> - Enum.reduce_while(files, [dest | acc], fn x, acc -> - case do_cp_r( - Path.join(src, x), - Path.join(dest, x), - on_conflict, - dereference, - acc - ) do - {:error, _, _} = error -> {:halt, error} - acc -> {:cont, acc} - end - end) + case copy_file_mode(src, dest) do + :ok -> + Enum.reduce_while(files, [dest | acc], fn x, acc -> + case do_cp_r( + Path.join(src, x), + Path.join(dest, x), + on_conflict, + dereference, + acc + ) do + {:error, _, _} = error -> {:halt, error} + acc -> {:cont, acc} + end + end) + + {:error, reason} -> + {:error, reason, src} + end {:error, reason} -> {:error, reason, dest} diff --git a/lib/elixir/test/elixir/file_test.exs b/lib/elixir/test/elixir/file_test.exs index 9411a928e6c..846789db58e 100644 --- a/lib/elixir/test/elixir/file_test.exs +++ b/lib/elixir/test/elixir/file_test.exs @@ -869,6 +869,26 @@ defmodule FileTest do assert src_mode == dest_mode end + test "cp_r preserves directory mode" do + src = tmp_path("tmp/src_dir") + dest = tmp_path("tmp/dest_dir") + inner = Path.join(src, "inner") + + File.mkdir_p!(inner) + File.chmod!(inner, 0o700) + + try do + File.cp_r!(src, dest) + + %File.Stat{mode: src_mode} = File.stat!(inner) + %File.Stat{mode: dest_mode} = File.stat!(Path.join(dest, "inner")) + assert src_mode == dest_mode + after + File.rm_rf!(src) + File.rm_rf!(dest) + end + end + @tag :unix test "cp_r skips sockets and other special files" do # We use tmpdir because macOS has a limit on socket paths