Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions internal/deb/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ type ExtractOptions struct {
}

type ExtractInfo struct {
Path string
Mode uint
Optional bool
Context any
Path string
Mode uint
Optional bool
Context any
SpecialGlobs map[rune]string
}

func getValidOptions(options *ExtractOptions) (*ExtractOptions, error) {
Expand Down Expand Up @@ -152,7 +153,12 @@ func extractData(pkgReader io.ReadSeeker, options *ExtractOptions) error {
continue
}
if strings.ContainsAny(extractPath, "*?") {
if strdist.GlobPath(extractPath, sourcePath) {
// Get special globs from the first extractInfo if available
var extractSpecial map[rune]string
if len(extractInfos) > 0 {
extractSpecial = extractInfos[0].SpecialGlobs
}
if strdist.GlobPathWithSpecial(extractPath, sourcePath, extractSpecial, nil) {
targetPaths[sourcePath] = append(targetPaths[sourcePath], extractInfos...)
delete(pendingPaths, extractPath)
}
Expand Down
28 changes: 22 additions & 6 deletions internal/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ type PathInfo struct {
Info string
Mode uint

Mutable bool
Until PathUntil
Arch []string
Generate GenerateKind
Prefer string
Mutable bool
Until PathUntil
Arch []string
Generate GenerateKind
Prefer string
SpecialGlobs map[rune]string
}

// SameContent returns whether the path has the same content properties as some
Expand Down Expand Up @@ -206,6 +207,21 @@ func (r *Release) validate() error {

keys := []SliceKey(nil)

// Validate special-globs regex patterns can compile
for _, pkg := range r.Packages {
for _, slice := range pkg.Slices {
for path, info := range slice.Contents {
if len(info.SpecialGlobs) > 0 {
for char, pattern := range info.SpecialGlobs {
if err := strdist.ValidateSpecialGlob(char, pattern); err != nil {
return fmt.Errorf("slice %s path %s has invalid special-glob: %w", slice, path, err)
}
}
}
}
}
}

// Check for info conflicts and prepare for following checks. A conflict
// means that two slices attempt to extract different files or directories
// to the same location.
Expand Down Expand Up @@ -288,7 +304,7 @@ func (r *Release) validate() error {
continue
}
}
if strdist.GlobPath(newPath, oldPath) {
if strdist.GlobPathWithSpecial(newPath, oldPath, newInfo.SpecialGlobs, oldInfo.SpecialGlobs) {
if (old.Package > new.Package) || (old.Package == new.Package && old.Name > new.Name) ||
(old.Package == new.Package && old.Name == new.Name && oldPath > newPath) {
old, new = new, old
Expand Down
123 changes: 123 additions & 0 deletions internal/setup/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,73 @@ var setupTests = []setupTest{{
`,
},
relerror: `slices mypkg1_myslice and mypkg2_myslice conflict on /file/f\*obar and /file/foob\*r`,
}, {
summary: "Special globs",
input: map[string]string{
"slices/mydir/mypkg1.yaml": `
package: mypkg1
slices:
myslice:
contents:
/file/f🎄obar:
special-globs:
🎄: '[^o]'
`,
"slices/mydir/mypkg2.yaml": `
package: mypkg2
slices:
myslice:
contents:
/file/foob*r:
`,
},
release: &setup.Release{
Archives: map[string]*setup.Archive{
"ubuntu": {
Name: "ubuntu",
Version: "22.04",
Suites: []string{"jammy"},
Components: []string{"main", "universe"},
PubKeys: []*packet.PublicKey{testKey.PubKey},
Maintained: true,
},
},
Packages: map[string]*setup.Package{
"mypkg1": {
Name: "mypkg1",
Path: "slices/mydir/mypkg1.yaml",
Slices: map[string]*setup.Slice{
"myslice": {
Package: "mypkg1",
Name: "myslice",
Contents: map[string]setup.PathInfo{
"/file/f🎄obar": {
Kind: "glob",
SpecialGlobs: map[rune]string{'🎄': "[^o]"},
},
},
},
},
},
"mypkg2": {
Name: "mypkg2",
Path: "slices/mydir/mypkg2.yaml",
Slices: map[string]*setup.Slice{
"myslice": {
Package: "mypkg2",
Name: "myslice",
Contents: map[string]setup.PathInfo{
"/file/foob*r": {Kind: "glob"},
},
},
},
},
},
Maintenance: &setup.Maintenance{
Standard: time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
EndOfLife: time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC),
},
},
}, {
summary: "Conflicting globs and plain copies",
input: map[string]string{
Expand Down Expand Up @@ -584,6 +651,62 @@ var setupTests = []setupTest{{
/file/foob*r: {until: mutate}
`,
},
}, {
summary: "Special globs with multi-character key is invalid",
input: map[string]string{
"slices/mydir/mypkg.yaml": `
package: mypkg
slices:
myslice:
contents:
/file/fXYobar:
special-globs:
XY: '[a-z]'
`,
},
relerror: `slice mypkg_myslice path /file/fXYobar special-globs key must be a single character: "XY"`,
}, {
summary: "Special globs cannot use standard glob characters",
input: map[string]string{
"slices/mydir/mypkg.yaml": `
package: mypkg
slices:
myslice:
contents:
/file/f*obar:
special-globs:
'*': '[a-z]'
`,
},
relerror: `slice mypkg_myslice path /file/f\*obar special-globs key cannot be a standard glob character: "\*"`,
}, {
summary: "Special globs cannot use slash",
input: map[string]string{
"slices/mydir/mypkg.yaml": `
package: mypkg
slices:
myslice:
contents:
/file/f/obar:
special-globs:
'/': '[a-z]'
`,
},
relerror: `slice mypkg_myslice path /file/f/obar special-globs key cannot be a standard glob character: "/"`,
}, {
summary: "Special globs with invalid regex pattern",
input: map[string]string{
"slices/mydir/mypkg.yaml": `
package: mypkg
slices:
myslice:
contents:
/file/fXobar:
special-globs:
X: '[invalid'
`,
},
relerror: `slice mypkg_myslice path /file/fXobar has invalid special-glob: special-glob "X" has invalid regex pattern "\[invalid": error parsing regexp: missing closing \]: ` + "`" + `\[invalid\$` + "`",
}, {
summary: "Mutable does not work for directories extractions",
input: map[string]string{
Expand Down
80 changes: 61 additions & 19 deletions internal/setup/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,17 @@ type yamlPackage struct {
}

type yamlPath struct {
Dir bool `yaml:"make,omitempty"`
Mode yamlMode `yaml:"mode,omitempty"`
Copy string `yaml:"copy,omitempty"`
Text *string `yaml:"text,omitempty"`
Symlink string `yaml:"symlink,omitempty"`
Mutable bool `yaml:"mutable,omitempty"`
Until PathUntil `yaml:"until,omitempty"`
Arch yamlArch `yaml:"arch,omitempty"`
Generate GenerateKind `yaml:"generate,omitempty"`
Prefer string `yaml:"prefer,omitempty"`
Dir bool `yaml:"make,omitempty"`
Mode yamlMode `yaml:"mode,omitempty"`
Copy string `yaml:"copy,omitempty"`
Text *string `yaml:"text,omitempty"`
Symlink string `yaml:"symlink,omitempty"`
Mutable bool `yaml:"mutable,omitempty"`
Until PathUntil `yaml:"until,omitempty"`
Arch yamlArch `yaml:"arch,omitempty"`
Generate GenerateKind `yaml:"generate,omitempty"`
Prefer string `yaml:"prefer,omitempty"`
SpecialGlobs map[string]string `yaml:"special-globs,omitempty"`
}

func (yp *yamlPath) MarshalYAML() (any, error) {
Expand Down Expand Up @@ -490,6 +491,26 @@ func parsePackage(baseDir, pkgName, pkgPath string, data []byte) (*Package, erro
var arch []string
var generate GenerateKind
var prefer string
var specialGlobs map[rune]string

// Parse special-globs first if present
if yamlPath != nil && len(yamlPath.SpecialGlobs) > 0 {
specialGlobs = make(map[rune]string)
for key, pattern := range yamlPath.SpecialGlobs {
// Ensure key is a single rune
runes := []rune(key)
if len(runes) != 1 {
return nil, fmt.Errorf("slice %s_%s path %s special-globs key must be a single character: %q", pkgName, sliceName, contPath, key)
}
r := runes[0]
// Disallow conflicts with basic glob tokens
if r == '*' || r == '?' || r == '/' {
return nil, fmt.Errorf("slice %s_%s path %s special-globs key cannot be a standard glob character: %q", pkgName, sliceName, contPath, key)
}
specialGlobs[r] = pattern
}
}

if yamlPath != nil && yamlPath.Generate != "" {
zeroPathGenerate := zeroPath
zeroPathGenerate.Generate = yamlPath.Generate
Expand All @@ -501,7 +522,7 @@ func parsePackage(baseDir, pkgName, pkgPath string, data []byte) (*Package, erro
return nil, fmt.Errorf("slice %s_%s has invalid generate path: %s", pkgName, sliceName, err)
}
kinds = append(kinds, GeneratePath)
} else if strings.ContainsAny(contPath, "*?") {
} else if strings.ContainsAny(contPath, "*?") || hasSpecialGlobChar(contPath, specialGlobs) {
if yamlPath != nil {
if !yamlPath.SameContent(&zeroPath) || yamlPath.Prefer != "" {
return nil, fmt.Errorf("slice %s_%s path %s has invalid wildcard options",
Expand Down Expand Up @@ -567,14 +588,15 @@ func parsePackage(baseDir, pkgName, pkgPath string, data []byte) (*Package, erro
return nil, fmt.Errorf("slice %s_%s mutable is not a regular file: %s", pkgName, sliceName, contPath)
}
slice.Contents[contPath] = PathInfo{
Kind: kinds[0],
Info: info,
Mode: mode,
Mutable: mutable,
Until: until,
Arch: arch,
Generate: generate,
Prefer: prefer,
Kind: kinds[0],
Info: info,
Mode: mode,
Mutable: mutable,
Until: until,
Arch: arch,
Generate: generate,
Prefer: prefer,
SpecialGlobs: specialGlobs,
}
}

Expand All @@ -600,6 +622,19 @@ func validateGeneratePath(path string) (string, error) {
return dirPath, nil
}

// hasSpecialGlobChar checks if path contains any runes defined in specialGlobs
func hasSpecialGlobChar(path string, specialGlobs map[rune]string) bool {
if len(specialGlobs) == 0 {
return false
}
for _, r := range path {
if _, ok := specialGlobs[r]; ok {
return true
}
}
return false
}

// pathInfoToYAML converts a PathInfo object to a yamlPath object.
// The returned object takes pointers to the given PathInfo object.
func pathInfoToYAML(pi *PathInfo) (*yamlPath, error) {
Expand All @@ -611,6 +646,13 @@ func pathInfoToYAML(pi *PathInfo) (*yamlPath, error) {
Generate: pi.Generate,
Prefer: pi.Prefer,
}
// Convert special globs back to yaml format
if len(pi.SpecialGlobs) > 0 {
path.SpecialGlobs = make(map[string]string)
for r, pattern := range pi.SpecialGlobs {
path.SpecialGlobs[string(r)] = pattern
}
}
switch pi.Kind {
case DirPath:
path.Dir = true
Expand Down
5 changes: 3 additions & 2 deletions internal/slicer/slicer.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ func Run(options *RunOptions) error {
sourcePath = targetPath
}
extractPackage[sourcePath] = append(extractPackage[sourcePath], deb.ExtractInfo{
Path: targetPath,
Context: slice,
Path: targetPath,
Context: slice,
SpecialGlobs: pathInfo.SpecialGlobs,
})
} else {
// When the content is not extracted from the package (i.e. path is
Expand Down
Loading
Loading