@@ -198,6 +198,15 @@ func (req *request) Excludes() []string {
198198 }
199199}
200200
201+ func (req * request ) Includes () []string {
202+ switch req .Request {
203+ case requestGet :
204+ return req .GetOptions .Includes
205+ default :
206+ return nil
207+ }
208+ }
209+
201210func (req * request ) UIDMap () []idtools.IDMap {
202211 switch req .Request {
203212 case requestEval :
@@ -405,6 +414,7 @@ type GetOptions struct {
405414 Timestamp * time.Time // timestamp to force on all contents
406415 DisallowWildcard bool // reject glob patterns in source paths
407416 AllowEmptyWildcard bool // don't error when glob patterns match nothing
417+ Includes []string // contents to include, using the OS-specific path separator.
408418}
409419
410420// Get produces an archive containing items that match the specified glob
@@ -1024,11 +1034,19 @@ func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*re
10241034 // os.PathSeparator, implying that it expects OS-specific naming
10251035 // conventions.
10261036 excludes := req .Excludes ()
1027- pm , err := fileutils .NewPatternMatcher (excludes )
1037+ pmExcludes , err := fileutils .NewPatternMatcher (excludes )
10281038 if err != nil {
10291039 return nil , nil , fmt .Errorf ("processing excludes list %v: %w" , excludes , err )
10301040 }
10311041
1042+ var pmIncludes * fileutils.PatternMatcher
1043+ if includes := req .Includes (); len (includes ) > 0 {
1044+ pmIncludes , err = fileutils .NewPatternMatcher (includes )
1045+ if err != nil {
1046+ return nil , nil , fmt .Errorf ("processing includes list %v: %w" , includes , err )
1047+ }
1048+ }
1049+
10321050 var idMappings * idtools.IDMappings
10331051 uidMap , gidMap := req .UIDMap (), req .GIDMap ()
10341052 if len (uidMap ) > 0 && len (gidMap ) > 0 {
@@ -1042,10 +1060,10 @@ func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*re
10421060 resp := copierHandlerEval (req )
10431061 return resp , nil , nil
10441062 case requestStat :
1045- resp := copierHandlerStat (req , pm , idMappings )
1063+ resp := copierHandlerStat (req , pmExcludes , idMappings )
10461064 return resp , nil , nil
10471065 case requestGet :
1048- return copierHandlerGet (bulkWriter , req , pm , idMappings )
1066+ return copierHandlerGet (bulkWriter , req , pmExcludes , pmIncludes , idMappings )
10491067 case requestPut :
10501068 return copierHandlerPut (bulkReader , req , idMappings )
10511069 case requestMkdir :
@@ -1092,6 +1110,28 @@ func pathIsExcluded(root, path string, pm *fileutils.PatternMatcher) (string, bo
10921110 return rel , false , nil
10931111}
10941112
1113+ func pathIsIncluded (root , path string , pm * fileutils.PatternMatcher ) (bool , error ) {
1114+ rel , err := convertToRelSubdirectory (root , path )
1115+ if err != nil {
1116+ return false , fmt .Errorf ("copier: error computing path of %q relative to root %q: %w" , path , root , err )
1117+ }
1118+ if pm == nil {
1119+ return true , nil
1120+ }
1121+ if rel == "." {
1122+ // special case
1123+ return true , nil
1124+ }
1125+ // Matches uses filepath.FromSlash() to convert candidates before
1126+ // checking if they match the patterns it's been given, implying that
1127+ // it expects Unix-style paths.
1128+ matches , err := pm .Matches (filepath .ToSlash (rel )) //nolint:staticcheck
1129+ if err != nil {
1130+ return false , fmt .Errorf ("copier: error checking if %q is included: %w" , rel , err )
1131+ }
1132+ return matches , nil
1133+ }
1134+
10951135// resolvePath resolves symbolic links in paths, treating the specified
10961136// directory as the root.
10971137// Resolving the path this way, and using the result, is in no way secure
@@ -1173,7 +1213,7 @@ func containsWildcards(path string) bool {
11731213 return strings .ContainsAny (path , "*?[" )
11741214}
11751215
1176- func copierHandlerStat (req request , pm * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) * response {
1216+ func copierHandlerStat (req request , pmExcludes * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) * response {
11771217 errorResponse := func (fmtspec string , args ... any ) * response {
11781218 return & response {Error : fmt .Sprintf (fmtspec , args ... ), Stat : statResponse {}}
11791219 }
@@ -1202,7 +1242,7 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *id
12021242 s .Globbed = make ([]string , 0 , len (globMatched ))
12031243 s .Results = make (map [string ]* StatForItem )
12041244 for _ , globbed := range globMatched {
1205- rel , excluded , err := pathIsExcluded (req .Root , globbed , pm )
1245+ rel , excluded , err := pathIsExcluded (req .Root , globbed , pmExcludes )
12061246 if err != nil {
12071247 return errorResponse ("copier: stat: %v" , err )
12081248 }
@@ -1262,7 +1302,7 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *id
12621302 // could be a relative link) and in the context
12631303 // of the chroot
12641304 result .ImmediateTarget = immediateTarget
1265- resolvedTarget , err := resolvePath (req .Root , globbed , true , pm )
1305+ resolvedTarget , err := resolvePath (req .Root , globbed , true , pmExcludes )
12661306 if err != nil {
12671307 return errorResponse ("copier: stat: error resolving %q: %v" , globbed , err )
12681308 }
@@ -1355,8 +1395,8 @@ func checkLinks(item string, req request, info os.FileInfo) (string, os.FileInfo
13551395 return item , info , nil
13561396}
13571397
1358- func copierHandlerGet (bulkWriter io.Writer , req request , pm * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) (* response , func () error , error ) {
1359- statResponse := copierHandlerStat (req , pm , idMappings )
1398+ func copierHandlerGet (bulkWriter io.Writer , req request , pmExcludes , pmIncludes * fileutils.PatternMatcher , idMappings * idtools.IDMappings ) (* response , func () error , error ) {
1399+ statResponse := copierHandlerStat (req , pmExcludes , idMappings )
13601400 errorResponse := func (fmtspec string , args ... any ) (* response , func () error , error ) {
13611401 return & response {Error : fmt .Sprintf (fmtspec , args ... ), Stat : statResponse .Stat , Get : getResponse {}}, nil , nil
13621402 }
@@ -1522,7 +1562,7 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
15221562 // skip the "." entry
15231563 return nil
15241564 }
1525- skippedPath , skip , err := pathIsExcluded (req .Root , path , pm )
1565+ skippedPath , skip , err := pathIsExcluded (req .Root , path , pmExcludes )
15261566 if err != nil {
15271567 return err
15281568 }
@@ -1533,14 +1573,14 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
15331573 // all, we don't need to
15341574 // descend into this particular
15351575 // directory if it's a directory
1536- if ! pm .Exclusions () {
1576+ if ! pmExcludes .Exclusions () {
15371577 return filepath .SkipDir
15381578 }
15391579 // if there are exclusion
15401580 // patterns for which this
15411581 // path is a prefix, we
15421582 // need to keep descending
1543- for _ , pattern := range pm .Patterns () {
1583+ for _ , pattern := range pmExcludes .Patterns () {
15441584 if ! pattern .Exclusion () {
15451585 continue
15461586 }
@@ -1563,6 +1603,16 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
15631603 // also be in the excludes list
15641604 return nil
15651605 }
1606+ if pmIncludes != nil && ! d .IsDir () {
1607+ included , err := pathIsIncluded (item , path , pmIncludes )
1608+ if err != nil {
1609+ return err
1610+ }
1611+
1612+ if ! included {
1613+ return nil
1614+ }
1615+ }
15661616 // if it's a symlink, read its target
15671617 symlinkTarget := ""
15681618 if d .Type () == os .ModeSymlink {
@@ -1608,14 +1658,24 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
16081658 }
16091659 itemsCopied ++
16101660 } else {
1611- _ , skip , err := pathIsExcluded (req .Root , item , pm )
1661+ _ , skip , err := pathIsExcluded (req .Root , item , pmExcludes )
16121662 if err != nil {
16131663 return err
16141664 }
16151665 if skip {
16161666 continue
16171667 }
16181668
1669+ if pmIncludes != nil {
1670+ included , err := pathIsIncluded (req .Root , item , pmIncludes )
1671+ if err != nil {
1672+ return err
1673+ }
1674+ if ! included {
1675+ continue
1676+ }
1677+ }
1678+
16191679 name := filepath .Base (queue [i ].glob )
16201680 if req .GetOptions .Parents {
16211681 name , err = convertToRelSubdirectory (req .Directory , queue [i ].glob )
0 commit comments