@@ -791,6 +791,8 @@ type digestLookupResult struct {
791791 Path string
792792 // Offset is the offset within the file where the content starts.
793793 Offset int64
794+ // Size is the size of the content.
795+ Size int64
794796}
795797
796798func (c * layersCache ) findDigestInternal (digest string ) (* digestLookupResult , error ) {
@@ -817,7 +819,7 @@ func (c *layersCache) findDigestInternal(digest string) (*digestLookupResult, er
817819 }
818820 fileLocationData := layer .cacheFile .vdata [off : off + tagLen ]
819821
820- fnamePosition , offFile , _ , err := parseFileLocation (fileLocationData )
822+ fnamePosition , offFile , lenFile , err := parseFileLocation (fileLocationData )
821823 if err != nil {
822824 return nil , fmt .Errorf ("corrupted cache file for layer %q" , layer .id )
823825 }
@@ -836,6 +838,7 @@ func (c *layersCache) findDigestInternal(digest string) (*digestLookupResult, er
836838 Target : layer .target ,
837839 Path : path ,
838840 Offset : int64 (offFile ),
841+ Size : int64 (lenFile ),
839842 }, nil
840843 }
841844 }
@@ -979,3 +982,68 @@ func unmarshalToc(manifest []byte) (*minimal.TOC, error) {
979982
980983 return & toc , nil
981984}
985+
986+ // FileByDigestResult contains the result of a file lookup by digest.
987+ type FileByDigestResult struct {
988+ File * os.File
989+ Offset int64
990+ Size int64
991+ }
992+
993+ // FindFileByDigest looks up a file or chunk by its digest in the layer cache.
994+ // It returns an open file descriptor, the offset within the file, and the size.
995+ // The caller is responsible for closing the returned file descriptor.
996+ func FindFileByDigest (store storage.Store , digest string ) (* FileByDigestResult , error ) {
997+ cache , err := getLayersCache (store )
998+ if err != nil {
999+ return nil , fmt .Errorf ("failed to get layers cache: %w" , err )
1000+ }
1001+
1002+ result , err := cache .findDigestInternal (digest )
1003+ if err != nil {
1004+ return nil , err
1005+ }
1006+ if result == nil {
1007+ return nil , nil // Not found
1008+ }
1009+
1010+ // Construct the full path to the file
1011+ driver , err := store .GraphDriver ()
1012+ if err != nil {
1013+ return nil , fmt .Errorf ("failed to get graph driver: %w" , err )
1014+ }
1015+
1016+ layerPath , err := driver .Get (result .Target , graphdriver.MountOpts {})
1017+ if err != nil {
1018+ return nil , fmt .Errorf ("failed to get layer path: %w" , err )
1019+ }
1020+ defer driver .Put (result .Target )
1021+
1022+ // Open the layer directory first
1023+ layerDirFd , err := unix .Open (layerPath , unix .O_RDONLY | unix .O_DIRECTORY | unix .O_CLOEXEC , 0 )
1024+ if err != nil {
1025+ return nil , fmt .Errorf ("failed to open layer directory: %w" , err )
1026+ }
1027+ defer unix .Close (layerDirFd )
1028+
1029+ // Use Openat2 with RESOLVE_BENEATH to safely open the file within the layer directory
1030+ fd , err := unix .Openat2 (layerDirFd , result .Path , & unix.OpenHow {
1031+ Flags : unix .O_RDONLY | unix .O_CLOEXEC ,
1032+ Resolve : unix .RESOLVE_BENEATH | unix .RESOLVE_NO_SYMLINKS ,
1033+ })
1034+ if err != nil {
1035+ return nil , fmt .Errorf ("failed to open file: %w" , err )
1036+ }
1037+
1038+ file := os .NewFile (uintptr (fd ), result .Path )
1039+ if file == nil {
1040+ unix .Close (fd )
1041+ return nil , fmt .Errorf ("failed to create File from fd" )
1042+ }
1043+
1044+ return & FileByDigestResult {
1045+ File : file ,
1046+ Offset : result .Offset ,
1047+ Size : result .Size ,
1048+ }, nil
1049+ }
0 commit comments