-
Notifications
You must be signed in to change notification settings - Fork 2.2k
libct/specconv: reduce init allocations, use global maps, refactor #3281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8fe1e8b
81586e1
2c3792b
37c5fd5
6907bec
029b73c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,8 @@ import ( | |
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "regexp" | ||
| "strings" | ||
| "sync" | ||
| "time" | ||
|
|
||
| systemdDbus "github.com/coreos/go-systemd/v22/dbus" | ||
|
|
@@ -24,26 +24,85 @@ import ( | |
| "golang.org/x/sys/unix" | ||
| ) | ||
|
|
||
| var namespaceMapping = map[specs.LinuxNamespaceType]configs.NamespaceType{ | ||
| specs.PIDNamespace: configs.NEWPID, | ||
| specs.NetworkNamespace: configs.NEWNET, | ||
| specs.MountNamespace: configs.NEWNS, | ||
| specs.UserNamespace: configs.NEWUSER, | ||
| specs.IPCNamespace: configs.NEWIPC, | ||
| specs.UTSNamespace: configs.NEWUTS, | ||
| specs.CgroupNamespace: configs.NEWCGROUP, | ||
| } | ||
| var ( | ||
| initMapsOnce sync.Once | ||
| namespaceMapping map[specs.LinuxNamespaceType]configs.NamespaceType | ||
| mountPropagationMapping map[string]int | ||
| mountFlags, extensionFlags map[string]struct { | ||
| clear bool | ||
| flag int | ||
| } | ||
| ) | ||
|
|
||
| var mountPropagationMapping = map[string]int{ | ||
| "rprivate": unix.MS_PRIVATE | unix.MS_REC, | ||
| "private": unix.MS_PRIVATE, | ||
| "rslave": unix.MS_SLAVE | unix.MS_REC, | ||
| "slave": unix.MS_SLAVE, | ||
| "rshared": unix.MS_SHARED | unix.MS_REC, | ||
| "shared": unix.MS_SHARED, | ||
| "runbindable": unix.MS_UNBINDABLE | unix.MS_REC, | ||
| "unbindable": unix.MS_UNBINDABLE, | ||
| "": 0, | ||
| func initMaps() { | ||
| initMapsOnce.Do(func() { | ||
| namespaceMapping = map[specs.LinuxNamespaceType]configs.NamespaceType{ | ||
| specs.PIDNamespace: configs.NEWPID, | ||
| specs.NetworkNamespace: configs.NEWNET, | ||
| specs.MountNamespace: configs.NEWNS, | ||
| specs.UserNamespace: configs.NEWUSER, | ||
| specs.IPCNamespace: configs.NEWIPC, | ||
| specs.UTSNamespace: configs.NEWUTS, | ||
| specs.CgroupNamespace: configs.NEWCGROUP, | ||
| } | ||
|
|
||
| mountPropagationMapping = map[string]int{ | ||
| "rprivate": unix.MS_PRIVATE | unix.MS_REC, | ||
| "private": unix.MS_PRIVATE, | ||
| "rslave": unix.MS_SLAVE | unix.MS_REC, | ||
| "slave": unix.MS_SLAVE, | ||
| "rshared": unix.MS_SHARED | unix.MS_REC, | ||
| "shared": unix.MS_SHARED, | ||
| "runbindable": unix.MS_UNBINDABLE | unix.MS_REC, | ||
| "unbindable": unix.MS_UNBINDABLE, | ||
| "": 0, | ||
| } | ||
|
|
||
| mountFlags = map[string]struct { | ||
| clear bool | ||
| flag int | ||
| }{ | ||
| "acl": {false, unix.MS_POSIXACL}, | ||
| "async": {true, unix.MS_SYNCHRONOUS}, | ||
| "atime": {true, unix.MS_NOATIME}, | ||
| "bind": {false, unix.MS_BIND}, | ||
| "defaults": {false, 0}, | ||
| "dev": {true, unix.MS_NODEV}, | ||
| "diratime": {true, unix.MS_NODIRATIME}, | ||
| "dirsync": {false, unix.MS_DIRSYNC}, | ||
| "exec": {true, unix.MS_NOEXEC}, | ||
| "iversion": {false, unix.MS_I_VERSION}, | ||
| "lazytime": {false, unix.MS_LAZYTIME}, | ||
| "loud": {true, unix.MS_SILENT}, | ||
| "mand": {false, unix.MS_MANDLOCK}, | ||
| "noacl": {true, unix.MS_POSIXACL}, | ||
| "noatime": {false, unix.MS_NOATIME}, | ||
| "nodev": {false, unix.MS_NODEV}, | ||
| "nodiratime": {false, unix.MS_NODIRATIME}, | ||
| "noexec": {false, unix.MS_NOEXEC}, | ||
| "noiversion": {true, unix.MS_I_VERSION}, | ||
| "nolazytime": {true, unix.MS_LAZYTIME}, | ||
| "nomand": {true, unix.MS_MANDLOCK}, | ||
| "norelatime": {true, unix.MS_RELATIME}, | ||
| "nostrictatime": {true, unix.MS_STRICTATIME}, | ||
| "nosuid": {false, unix.MS_NOSUID}, | ||
| "rbind": {false, unix.MS_BIND | unix.MS_REC}, | ||
| "relatime": {false, unix.MS_RELATIME}, | ||
| "remount": {false, unix.MS_REMOUNT}, | ||
| "ro": {false, unix.MS_RDONLY}, | ||
| "rw": {true, unix.MS_RDONLY}, | ||
| "silent": {false, unix.MS_SILENT}, | ||
| "strictatime": {false, unix.MS_STRICTATIME}, | ||
| "suid": {true, unix.MS_NOSUID}, | ||
| "sync": {false, unix.MS_SYNCHRONOUS}, | ||
| } | ||
| extensionFlags = map[string]struct { | ||
| clear bool | ||
| flag int | ||
| }{ | ||
| "tmpcopyup": {false, configs.EXT_COPYUP}, | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| // AllowedDevices is the set of devices which are automatically included for | ||
|
|
@@ -256,6 +315,8 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) { | |
| config.Cgroups = c | ||
| // set linux-specific config | ||
| if spec.Linux != nil { | ||
| initMaps() | ||
|
|
||
| var exists bool | ||
| if config.RootPropagation, exists = mountPropagationMapping[spec.Linux.RootfsPropagation]; !exists { | ||
| return nil, fmt.Errorf("rootfsPropagation=%v is not supported", spec.Linux.RootfsPropagation) | ||
|
|
@@ -332,33 +393,38 @@ func createLibcontainerMount(cwd string, m specs.Mount) (*configs.Mount, error) | |
| // return nil, fmt.Errorf("mount destination %s is not absolute", m.Destination) | ||
| logrus.Warnf("mount destination %s is not absolute. Support for non-absolute mount destinations will be removed in a future release.", m.Destination) | ||
| } | ||
| flags, pgflags, data, ext := parseMountOptions(m.Options) | ||
| source := m.Source | ||
| device := m.Type | ||
| if flags&unix.MS_BIND != 0 { | ||
| mnt := parseMountOptions(m.Options) | ||
|
|
||
| mnt.Destination = m.Destination | ||
| mnt.Source = m.Source | ||
| mnt.Device = m.Type | ||
| if mnt.Flags&unix.MS_BIND != 0 { | ||
| // Any "type" the user specified is meaningless (and ignored) for | ||
| // bind-mounts -- so we set it to "bind" because rootfs_linux.go | ||
| // (incorrectly) relies on this for some checks. | ||
| device = "bind" | ||
| if !filepath.IsAbs(source) { | ||
| source = filepath.Join(cwd, m.Source) | ||
| } | ||
| } | ||
| return &configs.Mount{ | ||
| Device: device, | ||
| Source: source, | ||
| Destination: m.Destination, | ||
| Data: data, | ||
| Flags: flags, | ||
| PropagationFlags: pgflags, | ||
| Extensions: ext, | ||
| }, nil | ||
| mnt.Device = "bind" | ||
| if !filepath.IsAbs(mnt.Source) { | ||
| mnt.Source = filepath.Join(cwd, m.Source) | ||
| } | ||
| } | ||
| return mnt, nil | ||
| } | ||
|
|
||
| // systemd property name check: latin letters only, at least 3 of them | ||
| var isValidName = regexp.MustCompile(`^[a-zA-Z]{3,}$`).MatchString | ||
|
|
||
| var isSecSuffix = regexp.MustCompile(`[a-z]Sec$`).MatchString | ||
| // isValidName checks if systemd property name is valid. It should consists | ||
| // of latin letters only, and have least 3 of them. | ||
| func isValidName(s string) bool { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to my other comment (no need to fix in this PR); perhaps would be nice to have this return an error with the reason for it being invalid (too short or invalid character)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense; opened #3285 |
||
| if len(s) < 3 { | ||
| return false | ||
| } | ||
| // Check ASCII characters rather than Unicode runes. | ||
| for _, ch := range s { | ||
| if (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') { | ||
| continue | ||
| } | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| // Some systemd properties are documented as having "Sec" suffix | ||
| // (e.g. TimeoutStopSec) but are expected to have "USec" suffix | ||
|
|
@@ -406,11 +472,16 @@ func initSystemdProps(spec *specs.Spec) ([]systemdDbus.Property, error) { | |
| if err != nil { | ||
| return nil, fmt.Errorf("Annotation %s=%s value parse error: %w", k, v, err) | ||
| } | ||
| if isSecSuffix(name) { | ||
| name = strings.TrimSuffix(name, "Sec") + "USec" | ||
| value, err = convertSecToUSec(value) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("Annotation %s=%s value parse error: %w", k, v, err) | ||
| // Check for Sec suffix. | ||
| if trimName := strings.TrimSuffix(name, "Sec"); len(trimName) < len(name) { | ||
| // Check for a lowercase ascii a-z just before Sec. | ||
| if ch := trimName[len(trimName)-1]; ch >= 'a' && ch <= 'z' { | ||
| // Convert from Sec to USec. | ||
| name = trimName + "USec" | ||
| value, err = convertSecToUSec(value) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("Annotation %s=%s value parse error: %w", k, v, err) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not something new, so probably better to fix separately, but this should've been lowercase
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you aware of any linter for such things? Would be nice to have
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I expect the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (that's a commit from #2998) |
||
| } | ||
| } | ||
| } | ||
| sp = append(sp, systemdDbus.Property{Name: name, Value: value}) | ||
|
|
@@ -759,92 +830,38 @@ func setupUserNamespace(spec *specs.Spec, config *configs.Config) error { | |
| return nil | ||
| } | ||
|
|
||
| // parseMountOptions parses the string and returns the flags, propagation | ||
| // flags and any mount data that it contains. | ||
| func parseMountOptions(options []string) (int, []int, string, int) { | ||
| // parseMountOptions parses options and returns a configs.Mount | ||
| // structure with fields that depends on options set accordingly. | ||
| func parseMountOptions(options []string) *configs.Mount { | ||
| var ( | ||
| flag int | ||
| pgflag []int | ||
| data []string | ||
| extFlags int | ||
| data []string | ||
| m configs.Mount | ||
| ) | ||
| flags := map[string]struct { | ||
| clear bool | ||
| flag int | ||
| }{ | ||
| "acl": {false, unix.MS_POSIXACL}, | ||
| "async": {true, unix.MS_SYNCHRONOUS}, | ||
| "atime": {true, unix.MS_NOATIME}, | ||
| "bind": {false, unix.MS_BIND}, | ||
| "defaults": {false, 0}, | ||
| "dev": {true, unix.MS_NODEV}, | ||
| "diratime": {true, unix.MS_NODIRATIME}, | ||
| "dirsync": {false, unix.MS_DIRSYNC}, | ||
| "exec": {true, unix.MS_NOEXEC}, | ||
| "iversion": {false, unix.MS_I_VERSION}, | ||
| "lazytime": {false, unix.MS_LAZYTIME}, | ||
| "loud": {true, unix.MS_SILENT}, | ||
| "mand": {false, unix.MS_MANDLOCK}, | ||
| "noacl": {true, unix.MS_POSIXACL}, | ||
| "noatime": {false, unix.MS_NOATIME}, | ||
| "nodev": {false, unix.MS_NODEV}, | ||
| "nodiratime": {false, unix.MS_NODIRATIME}, | ||
| "noexec": {false, unix.MS_NOEXEC}, | ||
| "noiversion": {true, unix.MS_I_VERSION}, | ||
| "nolazytime": {true, unix.MS_LAZYTIME}, | ||
| "nomand": {true, unix.MS_MANDLOCK}, | ||
| "norelatime": {true, unix.MS_RELATIME}, | ||
| "nostrictatime": {true, unix.MS_STRICTATIME}, | ||
| "nosuid": {false, unix.MS_NOSUID}, | ||
| "rbind": {false, unix.MS_BIND | unix.MS_REC}, | ||
| "relatime": {false, unix.MS_RELATIME}, | ||
| "remount": {false, unix.MS_REMOUNT}, | ||
| "ro": {false, unix.MS_RDONLY}, | ||
| "rw": {true, unix.MS_RDONLY}, | ||
| "silent": {false, unix.MS_SILENT}, | ||
| "strictatime": {false, unix.MS_STRICTATIME}, | ||
| "suid": {true, unix.MS_NOSUID}, | ||
| "sync": {false, unix.MS_SYNCHRONOUS}, | ||
| } | ||
| propagationFlags := map[string]int{ | ||
| "private": unix.MS_PRIVATE, | ||
| "shared": unix.MS_SHARED, | ||
| "slave": unix.MS_SLAVE, | ||
| "unbindable": unix.MS_UNBINDABLE, | ||
| "rprivate": unix.MS_PRIVATE | unix.MS_REC, | ||
| "rshared": unix.MS_SHARED | unix.MS_REC, | ||
| "rslave": unix.MS_SLAVE | unix.MS_REC, | ||
| "runbindable": unix.MS_UNBINDABLE | unix.MS_REC, | ||
| } | ||
| extensionFlags := map[string]struct { | ||
| clear bool | ||
| flag int | ||
| }{ | ||
| "tmpcopyup": {false, configs.EXT_COPYUP}, | ||
| } | ||
| initMaps() | ||
| for _, o := range options { | ||
| // If the option does not exist in the flags table or the flag | ||
| // is not supported on the platform, | ||
| // then it is a data value for a specific fs type | ||
| if f, exists := flags[o]; exists && f.flag != 0 { | ||
| // If the option does not exist in the mountFlags table, | ||
| // or the flag is not supported on the platform, | ||
| // then it is a data value for a specific fs type. | ||
| if f, exists := mountFlags[o]; exists && f.flag != 0 { | ||
| if f.clear { | ||
| flag &= ^f.flag | ||
| m.Flags &= ^f.flag | ||
| } else { | ||
| flag |= f.flag | ||
| m.Flags |= f.flag | ||
| } | ||
| } else if f, exists := propagationFlags[o]; exists && f != 0 { | ||
| pgflag = append(pgflag, f) | ||
| } else if f, exists := mountPropagationMapping[o]; exists && f != 0 { | ||
| m.PropagationFlags = append(m.PropagationFlags, f) | ||
| } else if f, exists := extensionFlags[o]; exists && f.flag != 0 { | ||
| if f.clear { | ||
| extFlags &= ^f.flag | ||
| m.Extensions &= ^f.flag | ||
| } else { | ||
| extFlags |= f.flag | ||
| m.Extensions |= f.flag | ||
| } | ||
| } else { | ||
| data = append(data, o) | ||
| } | ||
| } | ||
| return flag, pgflag, strings.Join(data, ","), extFlags | ||
| m.Data = strings.Join(data, ",") | ||
| return &m | ||
| } | ||
|
|
||
| func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the improvement that's brought by this, but (as a follow-up) perhaps we should consider having utilities / accessor functions for these variables, so that the initialisation happens more 'organically' (reducing the cognitive overload of having to thing about first calling
initMap()whenever we need to use one of the variables.In this case it could be a function to check if
fooexists inmountPropagationMappingThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean something like
To me it looks like a bloat, since the
nsMap := getNamespaceMapping()is just a way to sugar-coatinitMaps().A runtime assertion would be nice. If only accessing a nil map would panic....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of anything better than
but it's still inferior to
initMaps()There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some kind of a linter to which we can add a rule (in the source code) like "X should be called before using Y" would also be nice to have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, a lazy way to initialize variables. IOW same as this PR does, but automatically (perhaps via an annotation).
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Someone worked on something like this -- alas it does not look good without generics. https://github.com/zetamatta/go-lazy
I guess we have to postpone it till Go 2.0 ))))