@@ -30,9 +30,17 @@ defmodule Hyper.Img.OciLoader do
3030
3131 alias Hyper.Config
3232 alias Hyper.Img.Db . { Blob , Image , ImageLayer , Repo }
33- alias Hyper.Img.OciLoader.Params
3433
35- @ hash_chunk 2 * 1024 * 1024
34+ @ mib 1024 * 1024
35+ @ hash_chunk 2 * @ mib
36+
37+ # ext4 metadata (inode tables, journal, reserved blocks) plus slack so the
38+ # rootfs always fits. Overhead scales with content -- a flat constant is far
39+ # too small for large images -- as 25% of content plus an 8 MiB base, never
40+ # below a 16 MiB floor. The base is a read-only dm-snapshot origin (guest
41+ # writes land in the COW layer, never here), so generous slack is cheap.
42+ @ base_overhead_bytes 8 * @ mib
43+ @ floor_bytes 16 * @ mib
3644
3745 @ doc "Load `ref` into the store and DB. See the module doc. Label defaults to `ref`."
3846 @ spec load ( String . t ( ) ) :: { :ok , Hyper.Img . id ( ) } | { :error , term ( ) }
@@ -46,12 +54,12 @@ defmodule Hyper.Img.OciLoader do
4654 def load ( ref , opts ) when is_binary ( ref ) and is_list ( opts ) do
4755 label = Keyword . get ( opts , :label , ref )
4856
49- with { :ok , source } <- Params . source ( ref ) ,
57+ with { :ok , source } <- source ( ref ) ,
5058 { :ok , arch } <- Sys.Arch . current ( ) do
5159 Sys.Tmp . with_tempdir ( "hyper-oci" , fn tmp ->
52- with { :ok , rootfs } <- pull_and_unpack ( source , Params . goarch ( arch ) , tmp ) ,
60+ with { :ok , rootfs } <- pull_and_unpack ( source , goarch ( arch ) , tmp ) ,
5361 { :ok , content } <- dir_bytes ( rootfs ) ,
54- bytes = Params . ext4_bytes ( content ) ,
62+ bytes = ext4_bytes ( content ) ,
5563 { :ok , staged } <- build_ext4 ( rootfs , bytes ) ,
5664 { :ok , id } <- finalize ( staged , bytes , label ) do
5765 { :ok , id }
@@ -74,6 +82,36 @@ defmodule Hyper.Img.OciLoader do
7482 if missing == [ ] , do: :ok , else: { :error , { :missing_tools , missing } }
7583 end
7684
85+ # --- pure derivations (no I/O; the unit-tested core) ----------------------
86+
87+ # Validate `ref` and return the `skopeo` source `"docker://" <> ref`. A ref must
88+ # be non-empty and contain no whitespace (refs never do; rejecting whitespace
89+ # also closes the door on accidental arg-splitting surprises).
90+ @ doc false
91+ @ spec source ( String . t ( ) ) :: { :ok , String . t ( ) } | { :error , :invalid_ref }
92+ def source ( ref ) when is_binary ( ref ) do
93+ if ref != "" and not String . match? ( ref , ~r/ \s / ) ,
94+ do: { :ok , "docker://" <> ref } ,
95+ else: { :error , :invalid_ref }
96+ end
97+
98+ # Map a Hyper architecture to the Go/OCI arch name `skopeo --override-arch` wants.
99+ @ doc false
100+ @ spec goarch ( Sys.Arch . t ( ) ) :: String . t ( )
101+ def goarch ( :x86_64 ) , do: "amd64"
102+ def goarch ( :aarch64 ) , do: "arm64"
103+
104+ # ext4 image size (bytes) for a rootfs whose contents total `content_bytes`:
105+ # content + scaled overhead (25% of content + 8 MiB base), rounded up to a whole
106+ # MiB, never below 16 MiB.
107+ @ doc false
108+ @ spec ext4_bytes ( non_neg_integer ( ) ) :: pos_integer ( )
109+ def ext4_bytes ( content_bytes ) when is_integer ( content_bytes ) and content_bytes >= 0 do
110+ raw = content_bytes + div ( content_bytes , 4 ) + @ base_overhead_bytes
111+ rounded = div ( raw + @ mib - 1 , @ mib ) * @ mib
112+ max ( rounded , @ floor_bytes )
113+ end
114+
77115 # --- pull + flatten -------------------------------------------------------
78116
79117 # `skopeo copy` into a local OCI layout, then `umoci unpack` into a bundle.
0 commit comments