11defmodule Castle.Peer do
22 @ moduledoc false
33
4+ alias Castle.FileReason
5+
46 # Materialises the configuration of the release being upgraded *to*, by
57 # running that release's own `Config.Provider` pipeline in a temporary VM
68 # booted from that release's own boot script and code.
@@ -468,7 +470,7 @@ defmodule Castle.Peer do
468470 { :error , "Cannot read #{ path } as a release file. It holds #{ inspect ( terms ) } ." }
469471
470472 { :error , reason } ->
471- { :error , "Cannot read #{ path } . #{ format_error ( reason ) } " }
473+ { :error , "Cannot read #{ path } . #{ FileReason . format ( reason ) } " }
472474 end
473475 end
474476
@@ -516,7 +518,7 @@ defmodule Castle.Peer do
516518 release_file ( rel_vsn_dir , Enum . filter ( entries , & String . ends_with? ( & 1 , ".rel" ) ) )
517519
518520 { :error , reason } ->
519- { :error , "Cannot list #{ rel_vsn_dir } . #{ format_error ( reason ) } " }
521+ { :error , "Cannot list #{ rel_vsn_dir } . #{ FileReason . format ( reason ) } " }
520522 end
521523 end
522524
@@ -529,16 +531,17 @@ defmodule Castle.Peer do
529531 end
530532
531533 defp release_file ( rel_vsn_dir , names ) do
532- case unpacked_copy ( names , Path . basename ( rel_vsn_dir ) ) do
534+ vsn = Path . basename ( rel_vsn_dir )
535+
536+ case unpacked_copy ( names , vsn ) do
533537 { :ok , name } ->
534538 { :ok , Path . join ( rel_vsn_dir , name ) }
535539
536540 :none ->
537541 { :error ,
538- "Found more than one release file in #{ rel_vsn_dir } - #{ Enum . join ( names , ", " ) } . An " <>
539- "unpacked version directory holds two, a release file and the copy unpacking " <>
540- "leaves beside it, and these are not that pair - so the emulator to evaluate its " <>
541- "configuration with is ambiguous." }
542+ "Cannot determine the release file in #{ rel_vsn_dir } : found " <>
543+ "#{ Enum . join ( names , ", " ) } . Expected one file or an unpacked " <>
544+ "<name>.rel/<name>-#{ vsn } .rel pair." }
542545 end
543546 end
544547
@@ -640,13 +643,14 @@ defmodule Castle.Peer do
640643 end
641644 end
642645
643- ## Writing a file that holds configuration
646+ ## Private staging primitives
644647 #
645- # Every file this module creates holds a release's configuration - the base,
646- # and the scratch copy the providers are resolved into - so none of them may be
647- # readable by anyone the `sys.config` it came from or is about to become would
648- # not let read it. An operator who restricts that file has said something, and
649- # it has to hold for the copies too.
648+ # The configuration path uses these for the base and scratch files, and the
649+ # restart protocol uses them for its marker. The primitives enforce filesystem
650+ # facts only; their callers say which lifecycle operation was interrupted and
651+ # what had already changed. Configuration files must not become readable by
652+ # anyone `sys.config` excludes, and the same owner-only staging also protects a
653+ # marker from being replaced before publication.
650654 #
651655 # What protects them is the *directory* they are made in, and it has to be,
652656 # because OTP cannot create a file with a mode. `:file.open/2`'s modes say how
@@ -822,18 +826,14 @@ defmodule Castle.Peer do
822826 case File . ls ( path ) do
823827 { :ok , [ ] } -> :ok
824828 { :ok , entries } -> { :error , occupied ( path , entries ) }
825- { :error , reason } -> { :error , "Cannot list #{ path } . #{ format_error ( reason ) } " }
829+ { :error , reason } -> { :error , "Cannot list #{ path } . #{ FileReason . format ( reason ) } " }
826830 end
827831 end
828832
829833 defp occupied ( path , entries ) do
830- "Cannot assemble configuration in #{ path } . Castle had just created that " <>
831- "directory and written nothing to it, and it already holds " <>
832- "#{ Enum . join ( entries , ", " ) } - so something else can write where this " <>
833- "release's configuration is about to be, and a name planted there is a name " <>
834- "the configuration could be written through. Nothing has been written and " <>
835- "the directory has been removed. Check the umask the release runs under: a " <>
836- "new directory has to be private to the account doing the install."
834+ "Cannot use #{ path } : newly created directory contains #{ Enum . join ( entries , ", " ) } . " <>
835+ "Castle removed it without writing to it. " <>
836+ "Check that the release umask creates owner-only directories."
837837 end
838838
839839 @ doc false
@@ -866,7 +866,7 @@ defmodule Castle.Peer do
866866 def create_exclusive ( path ) do
867867 case File . open ( path , [ :write , :exclusive , :raw ] ) do
868868 { :ok , handle } -> { :ok , handle }
869- { :error , reason } -> { :error , "Cannot create #{ path } . #{ format_error ( reason ) } " }
869+ { :error , reason } -> { :error , "Cannot create #{ path } . #{ FileReason . format ( reason ) } " }
870870 end
871871 end
872872
@@ -898,14 +898,14 @@ defmodule Castle.Peer do
898898 defp written ( handle , path , bytes ) do
899899 case :file . write ( handle , bytes ) do
900900 :ok -> :ok
901- { :error , reason } -> { :error , "Cannot write #{ path } . #{ format_error ( reason ) } " }
901+ { :error , reason } -> { :error , "Cannot write #{ path } . #{ FileReason . format ( reason ) } " }
902902 end
903903 end
904904
905905 defp closed ( handle , path ) do
906906 case File . close ( handle ) do
907907 :ok -> :ok
908- { :error , reason } -> { :error , "Cannot write #{ path } . #{ format_error ( reason ) } " }
908+ { :error , reason } -> { :error , "Cannot write #{ path } . #{ FileReason . format ( reason ) } " }
909909 end
910910 end
911911
@@ -915,14 +915,14 @@ defmodule Castle.Peer do
915915 case File . ln ( staging , path ) do
916916 :ok -> :ok
917917 { :error , :eexist } -> :taken
918- { :error , reason } -> { :error , "Cannot write #{ path } . #{ format_error ( reason ) } " }
918+ { :error , reason } -> { :error , "Cannot write #{ path } . #{ FileReason . format ( reason ) } " }
919919 end
920920 end
921921
922922 defp carry_mode ( from , to ) do
923923 case File . stat ( from ) do
924924 { :ok , % File.Stat { mode: mode } } -> chmod ( to , Bitwise . band ( mode , 0o7777 ) )
925- { :error , reason } -> { :error , "Cannot read #{ from } . #{ format_error ( reason ) } " }
925+ { :error , reason } -> { :error , "Cannot read #{ from } . #{ FileReason . format ( reason ) } " }
926926 end
927927 end
928928
@@ -1082,15 +1082,15 @@ defmodule Castle.Peer do
10821082
10831083 ## Files
10841084
1085- # Whether a file holding configuration may be created here at all: nothing
1086- # granted to group or other, so there is no path through this directory for
1087- # anyone else to open what is inside it by. Checked on every creation rather
1088- # than assumed of the working directory, because assuming it at the call sites
1089- # is the mistake this whole arrangement exists to make impossible.
1085+ # Whether a private staging file may be created here at all: nothing granted to
1086+ # group or other, so there is no path through this directory for anyone else to
1087+ # open what is inside it by. Checked on every creation rather than assumed of
1088+ # the working directory, because assuming it at the call sites is the mistake
1089+ # this whole arrangement exists to make impossible.
10901090 defp private_dir ( path ) do
10911091 case File . stat ( path ) do
10921092 { :ok , % File.Stat { mode: mode } } -> private_mode ( path , Bitwise . band ( mode , 0o7777 ) )
1093- { :error , reason } -> { :error , "Cannot read #{ path } . #{ format_error ( reason ) } " }
1093+ { :error , reason } -> { :error , "Cannot read #{ path } . #{ FileReason . format ( reason ) } " }
10941094 end
10951095 end
10961096
@@ -1099,18 +1099,16 @@ defmodule Castle.Peer do
10991099 :ok
11001100 else
11011101 { :error ,
1102- "Cannot write in #{ path } , whose mode is 0#{ Integer . to_string ( mode , 8 ) } . A file holding " <>
1103- "a release's configuration is only ever created in a directory Castle has made " <>
1104- "owner-only, so that nothing can open it while it is being written. Castle chmods " <>
1105- "that directory to 0700 as it creates it, and a wider mode than that means the " <>
1106- "filesystem holding the release did not take it." }
1102+ "Cannot write in #{ path } : directory mode " <>
1103+ "0#{ Integer . to_string ( mode , 8 ) } allows access beyond its owner. Castle requires " <>
1104+ "owner-only mode 0700; check the release filesystem and permissions." }
11071105 end
11081106 end
11091107
11101108 defp mkdir ( path ) do
11111109 case File . mkdir ( path ) do
11121110 :ok -> :ok
1113- { :error , reason } -> { :error , "Cannot create #{ path } . #{ format_error ( reason ) } " }
1111+ { :error , reason } -> { :error , "Cannot create #{ path } . #{ FileReason . format ( reason ) } " }
11141112 end
11151113 end
11161114
@@ -1124,7 +1122,7 @@ defmodule Castle.Peer do
11241122 defp read ( path ) do
11251123 case File . read ( path ) do
11261124 { :ok , contents } -> { :ok , contents }
1127- { :error , reason } -> { :error , "Cannot read #{ path } . #{ format_error ( reason ) } " }
1125+ { :error , reason } -> { :error , "Cannot read #{ path } . #{ FileReason . format ( reason ) } " }
11281126 end
11291127 end
11301128
@@ -1137,31 +1135,31 @@ defmodule Castle.Peer do
11371135 { :error , "Cannot read #{ path } : expected one configuration term, found #{ length ( terms ) } ." }
11381136
11391137 { :error , reason } ->
1140- { :error , "Cannot read #{ path } . #{ format_error ( reason ) } " }
1138+ { :error , "Cannot read #{ path } . #{ FileReason . format ( reason ) } " }
11411139 end
11421140 end
11431141
11441142 defp write ( path , contents ) do
11451143 case File . write ( path , contents ) do
11461144 :ok -> :ok
1147- { :error , reason } -> { :error , "Cannot write #{ path } . #{ format_error ( reason ) } " }
1145+ { :error , reason } -> { :error , "Cannot write #{ path } . #{ FileReason . format ( reason ) } " }
11481146 end
11491147 end
11501148
11511149 defp chmod ( path , mode ) do
11521150 case File . chmod ( path , mode ) do
1153- :ok -> :ok
1154- { :error , reason } -> { :error , "Cannot set the mode of #{ path } . #{ format_error ( reason ) } " }
1151+ :ok ->
1152+ :ok
1153+
1154+ { :error , reason } ->
1155+ { :error , "Cannot set the mode of #{ path } . #{ FileReason . format ( reason ) } " }
11551156 end
11561157 end
11571158
11581159 defp rename ( source , destination ) do
11591160 case File . rename ( source , destination ) do
11601161 :ok -> { :ok , [ ] }
1161- { :error , reason } -> { :error , "Cannot write #{ destination } . #{ format_error ( reason ) } " }
1162+ { :error , reason } -> { :error , "Cannot write #{ destination } . #{ FileReason . format ( reason ) } " }
11621163 end
11631164 end
1164-
1165- defp format_error ( reason ) when is_atom ( reason ) , do: :file . format_error ( reason )
1166- defp format_error ( reason ) , do: inspect ( reason )
11671165end
0 commit comments