Skip to content

Commit 2ba06cc

Browse files
authored
Make preserving dir permissions opt-in, closes #15308 (#15312)
1 parent 68aae43 commit 2ba06cc

2 files changed

Lines changed: 28 additions & 9 deletions

File tree

lib/elixir/lib/file.ex

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,11 @@ defmodule File do
11511151
dereferenced and have their contents copied instead when set to `true`. If the dereferenced
11521152
files do not exist, than the operation fails. The default is `false`.
11531153
1154+
* `:preserve_directory_permissions` - (since v1.20.0) when `true`, the permissions of
1155+
source directories are copied to the destination directories after their contents are
1156+
written. This is useful when source directories are read-only or have restricted
1157+
permissions that must be preserved. The default is `false`.
1158+
11541159
## Examples
11551160
11561161
# Copies file "a.txt" to "b.txt"
@@ -1176,7 +1181,8 @@ defmodule File do
11761181
"""
11771182
@spec cp_r(Path.t(), Path.t(),
11781183
on_conflict: on_conflict_callback,
1179-
dereference_symlinks: boolean()
1184+
dereference_symlinks: boolean(),
1185+
preserve_directory_permissions: boolean()
11801186
) ::
11811187
{:ok, [binary]} | {:error, posix | :badarg | :terminated, binary}
11821188

@@ -1198,6 +1204,7 @@ defmodule File do
11981204
def cp_r(source, destination, options) when is_list(options) do
11991205
on_conflict = Keyword.get(options, :on_conflict, fn _, _ -> true end)
12001206
dereference? = Keyword.get(options, :dereference_symlinks, false)
1207+
preserve_directory_permissions? = Keyword.get(options, :preserve_directory_permissions, false)
12011208

12021209
source =
12031210
source
@@ -1217,7 +1224,14 @@ defmodule File do
12171224
else
12181225
dereference = if dereference?, do: MapSet.new(), else: nil
12191226

1220-
case do_cp_r(source, destination, on_conflict, dereference, []) do
1227+
case do_cp_r(
1228+
source,
1229+
destination,
1230+
on_conflict,
1231+
dereference,
1232+
preserve_directory_permissions?,
1233+
[]
1234+
) do
12211235
{:error, _, _} = error -> error
12221236
res -> {:ok, res}
12231237
end
@@ -1241,7 +1255,8 @@ defmodule File do
12411255
"""
12421256
@spec cp_r!(Path.t(), Path.t(),
12431257
on_conflict: on_conflict_callback,
1244-
dereference_symlinks: boolean()
1258+
dereference_symlinks: boolean(),
1259+
preserve_directory_permissions: boolean()
12451260
) :: [binary]
12461261
def cp_r!(source, destination, options \\ []) do
12471262
case cp_r(source, destination, options) do
@@ -1258,7 +1273,7 @@ defmodule File do
12581273
end
12591274
end
12601275

1261-
defp do_cp_r(src, dest, on_conflict, dereference, acc) when is_list(acc) do
1276+
defp do_cp_r(src, dest, on_conflict, dereference, preserve_dir_perms?, acc) when is_list(acc) do
12621277
case :elixir_utils.read_link_type(src) do
12631278
{:ok, :regular} ->
12641279
case do_cp_file(src, dest, on_conflict, acc) do
@@ -1278,7 +1293,7 @@ defmodule File do
12781293
{:error, :eloop, src}
12791294
else
12801295
dereference = MapSet.put(dereference, resolved)
1281-
do_cp_r(resolved, dest, on_conflict, dereference, acc)
1296+
do_cp_r(resolved, dest, on_conflict, dereference, preserve_dir_perms?, acc)
12821297
end
12831298

12841299
{:ok, link} ->
@@ -1300,6 +1315,7 @@ defmodule File do
13001315
Path.join(dest, x),
13011316
on_conflict,
13021317
dereference,
1318+
preserve_dir_perms?,
13031319
acc
13041320
) do
13051321
{:error, _, _} = error -> {:halt, error}
@@ -1310,13 +1326,16 @@ defmodule File do
13101326
{:error, _, _} = error ->
13111327
error
13121328

1313-
files ->
1329+
files when preserve_dir_perms? ->
13141330
# Change the directory after writing files in case
13151331
# it was originally read only
13161332
case copy_file_mode(src, dest) do
13171333
:ok -> files
13181334
{:error, reason} -> {:error, reason, src}
13191335
end
1336+
1337+
files ->
1338+
files
13201339
end
13211340

13221341
{:error, reason} ->
@@ -1336,7 +1355,7 @@ defmodule File do
13361355
end
13371356

13381357
# If we reach this clause, there was an error while processing a file.
1339-
defp do_cp_r(_, _, _, _, acc) do
1358+
defp do_cp_r(_, _, _, _, _, acc) do
13401359
acc
13411360
end
13421361

lib/elixir/test/elixir/file_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ defmodule FileTest do
869869
assert src_mode == dest_mode
870870
end
871871

872-
test "cp_r preserves directory mode" do
872+
test "cp_r preserves directory mode with preserve_directory_permissions: true" do
873873
src = tmp_path("tmp/src_dir")
874874
dest = tmp_path("tmp/dest_dir")
875875
inner = Path.join(src, "inner")
@@ -879,7 +879,7 @@ defmodule FileTest do
879879
File.chmod!(inner, 0o500)
880880

881881
try do
882-
File.cp_r!(src, dest)
882+
File.cp_r!(src, dest, preserve_directory_permissions: true)
883883

884884
%File.Stat{mode: src_mode} = File.stat!(inner)
885885
%File.Stat{mode: dest_mode} = File.stat!(Path.join(dest, "inner"))

0 commit comments

Comments
 (0)