@@ -18,6 +18,7 @@ package trace
1818import (
1919 "context"
2020 "errors"
21+ "fmt"
2122 "io/fs"
2223 "slices"
2324 "sort"
@@ -54,7 +55,7 @@ type locationAndIndex struct {
5455// Note that a precondition of this algorithm is that the chain layers are ordered by order of
5556// creation.
5657func PopulateLayerDetails (ctx context.Context , inventory []* extractor.Inventory , chainLayers []scalibrImage.ChainLayer , config * filesystem.Config ) {
57- chainLayerDetailsList := []* extractor.LayerDetails {}
58+ layerDetailsList := []* extractor.LayerDetails {}
5859
5960 // Create list of layer details struct to be referenced by inventory.
6061 for i , chainLayer := range chainLayers {
@@ -65,7 +66,7 @@ func PopulateLayerDetails(ctx context.Context, inventory []*extractor.Inventory,
6566 diffID = chainLayer .Layer ().DiffID ().Encoded ()
6667 }
6768
68- chainLayerDetailsList = append (chainLayerDetailsList , & extractor.LayerDetails {
69+ layerDetailsList = append (layerDetailsList , & extractor.LayerDetails {
6970 Index : i ,
7071 DiffID : diffID ,
7172 Command : chainLayer .Layer ().Command (),
@@ -90,7 +91,7 @@ func PopulateLayerDetails(ctx context.Context, inventory []*extractor.Inventory,
9091 lastLayerIndex := len (chainLayers ) - 1
9192
9293 for _ , inv := range inventory {
93- layerDetails := chainLayerDetailsList [lastLayerIndex ]
94+ layerDetails := layerDetailsList [lastLayerIndex ]
9495 invExtractor , isFilesystemExtractor := inv .Extractor .(filesystem.Extractor )
9596
9697 // Only filesystem extractors are supported for layer scanning. Also, if the inventory has no
@@ -101,33 +102,39 @@ func PopulateLayerDetails(ctx context.Context, inventory []*extractor.Inventory,
101102 }
102103
103104 var foundOrigin bool
105+ fileLocation := inv .Locations [0 ]
104106
105107 // Go backwards through the chain layers and find the first layer where the inventory is not
106108 // present. Such layer is the layer in which the inventory was introduced. If the inventory is
107109 // present in all layers, then it means it was introduced in the first layer.
108- // TODO: b/381249869 - Optimization: Skip layers if file not found.
109110 for i := len (chainLayers ) - 2 ; i >= 0 ; i -- {
110111 oldChainLayer := chainLayers [i ]
111112
112113 invLocationAndIndex := locationAndIndex {
113- location : inv . Locations [ 0 ] ,
114+ location : fileLocation ,
114115 index : i ,
115116 }
116117
118+ // If the file of interest is not present in the underlying layer, then there will be no
119+ // difference in the extracted inventory from oldChainLayer, so extraction can be skipped in
120+ // the chain layer. This is an optimization to avoid extracting the same inventory multiple
121+ // times.
122+ if ! isFileInLayerDiff (oldChainLayer , fileLocation ) {
123+ continue
124+ }
125+
117126 var oldInventory []* extractor.Inventory
118127 if cachedInventory , ok := locationIndexToInventory [invLocationAndIndex ]; ok {
119128 oldInventory = cachedInventory
120129 } else {
121130 // Check if file still exist in this layer, if not skip extraction.
122131 // This is both an optimization, and avoids polluting the log output with false file not found errors.
123- if _ , err := oldChainLayer .FS ().Stat (inv .Locations [0 ]); errors .Is (err , fs .ErrNotExist ) {
124- oldInventory = []* extractor.Inventory {}
125- } else {
132+ if _ , err := oldChainLayer .FS ().Stat (fileLocation ); err == nil {
126133 // Update the extractor config to use the files from the current layer.
127134 // We only take extract the first location because other locations are derived from the initial
128135 // extraction location. If other locations can no longer be determined from the first location
129136 // they should not be included here, and the trace for those packages stops here.
130- updateExtractorConfig ([]string {inv . Locations [ 0 ] }, invExtractor , oldChainLayer .FS ())
137+ updateExtractorConfig ([]string {fileLocation }, invExtractor , oldChainLayer .FS ())
131138
132139 var err error
133140 oldInventory , _ , err = filesystem .Run (ctx , config )
@@ -150,7 +157,7 @@ func PopulateLayerDetails(ctx context.Context, inventory []*extractor.Inventory,
150157
151158 // If the inventory is not present in the old layer, then it was introduced in layer i+1.
152159 if ! foundPackage {
153- layerDetails = chainLayerDetailsList [i + 1 ]
160+ layerDetails = layerDetailsList [i + 1 ]
154161 foundOrigin = true
155162 break
156163 }
@@ -159,7 +166,7 @@ func PopulateLayerDetails(ctx context.Context, inventory []*extractor.Inventory,
159166 // If the inventory is present in every layer, then it means it was introduced in the first
160167 // layer.
161168 if ! foundOrigin {
162- layerDetails = chainLayerDetailsList [0 ]
169+ layerDetails = layerDetailsList [0 ]
163170 }
164171 inv .LayerDetails = layerDetails
165172 }
@@ -192,3 +199,30 @@ func areInventoriesEqual(inv1 *extractor.Inventory, inv2 *extractor.Inventory) b
192199 }
193200 return true
194201}
202+
203+ // getSingleLayerFSFromChainLayer returns the filesystem of the underlying layer in the chain layer.
204+ func getLayerFSFromChainLayer (chainLayer scalibrImage.ChainLayer ) (scalibrfs.FS , error ) {
205+ layer := chainLayer .Layer ()
206+ if layer == nil {
207+ return nil , fmt .Errorf ("chain layer has no layer" )
208+ }
209+
210+ fs := layer .FS ()
211+ if fs == nil {
212+ return nil , fmt .Errorf ("layer has no filesystem" )
213+ }
214+
215+ return fs , nil
216+ }
217+
218+ // isFileInLayerDiff checks if a file is present in the underlying layer of a chain layer.
219+ func isFileInLayerDiff (chainLayer scalibrImage.ChainLayer , fileLocation string ) bool {
220+ layerFS , err := getLayerFSFromChainLayer (chainLayer )
221+ if err != nil {
222+ return false
223+ }
224+ if _ , err := layerFS .Stat (fileLocation ); errors .Is (err , fs .ErrNotExist ) {
225+ return true
226+ }
227+ return false
228+ }
0 commit comments