-
Notifications
You must be signed in to change notification settings - Fork 594
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
interfaces,dirs: add ldconfig backend #14926
interfaces,dirs: add ldconfig backend #14926
Conversation
Mon Feb 3 14:25:56 UTC 2025 Failures:Preparing:
Executing:
Restoring:
|
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.
This looks sensible to me, and clean. Thank you
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.
One main question.
Further usually interfaces decide to do different things depending if the slot is implicit (see the usage of the implicitSystemPermanent/ConnectedSlot helpers) but maybe here we want to do something at the backend level?
dirs/dirs.go
Outdated
@@ -465,6 +466,7 @@ func SetRootDir(rootdir string) { | |||
|
|||
SnapDataDir = filepath.Join(rootdir, "/var/snap") | |||
SnapAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "profiles") | |||
SnapLdconfigDir = filepath.Join(rootdir, "etc/ld.so.conf.d") |
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.
nitpick: the style here seems to be /etc...
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.
Changed
interfaces/ldconfig/backend.go
Outdated
// the rootfs. For snaps, we will create files in | ||
// /var/lib/snapd/ldconfig/ that will be used to generate a cache | ||
// specific to each snap. | ||
ldconfigPath := filepath.Join(dir, "snapd.conf") |
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'm confused, specification are per snap app set but here we reuse the same file? won't multiple snap stomp on each ther?
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 have changed this. However I started to re-think about this and I am not sure anymore, maybe I'm missing some piece. I created snapd.conf
thinking of it as the only config file for the "system" (rootfs) snap (the implementation is not considering exporting libraries to snap yet). That is, the "snap app set" here is the rootfs. Afaiu any change in interfaces would trigger the call to Setup(), and having multiple files will help with knowing where libraries come from (this could still be done by adding comments in snapd.conf
), but there is no other practical effect as the call to ldconfig
will process all files in here.
My plan for the implementation for the snaps case, is to have also only one config file per snap, that will be seen in ld.so.conf.d/ but only for the mount namespace of the snap, that would be a mix of the contents of the app base and of these files generated by snapd (that will be found also in the rootfs in, maybe, /var/lib/snapd/ldconfig/).
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.
@pedronis as discussed, I've looked with a bit more of detail into this and how it works now is
- Tasks like "connect" and "setup-profiles" connect the interfaces. In a refresh case, the later does a set of operations (
snapd/overlord/ifacestate/handlers.go
Lines 244 to 253 in b85fde1
// The snap may have been updated so perform the following operation to // ensure that we are always working on the correct state: // // - disconnect all connections to/from the given snap // - remembering the snaps that were affected by this operation // - remove the (old) snap from the interfaces repository // - add the (new) snap to the interfaces repository // - restore connections based on what is kept in the state // - if a connection cannot be restored then remove it from the state // - setup the security of all the affected snaps repo.Connect
is called for all connections in the state, filling the interfaces repo appropriately. setupSecurityByBackend
is then called in all these tasks, which callsinterfaces.SetupMany
for all backends of the affected snaps- When the backend's
Setup
is called it callsrepo.SnapSpecification
, which adds the connected plug for all connected slots to a plug, which covers the greedy plug case (Lines 941 to 952 in b85fde1
// plug side for _, plugInfo := range r.plugs[snapName] { iface := r.ifaces[plugInfo.Interface] if err := spec.AddPermanentPlug(iface, plugInfo); err != nil { return nil, err } for _, conn := range r.plugSlots[plugInfo] { if err := spec.AddConnectedPlug(iface, conn.Plug, conn.Slot); err != nil { return nil, err } } }
I think then that greedy plugs in principle work as expected, and the unit test that I have implemented for the ldconfig backend does similar things to what is done by the taks. I lean now more towards the option of having just one configuration file in /etc/ld.conf.so.d/
for hybrid, to avoid polluting too much that user-exposed folder. Inside it we could add comments to specify what parts come from a given snap/slot. Thoughts?
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.
So if I follow if we have a greedy plug on connected to many slot snaps <slot-snap1,2...>, when e.g. slot-snap2 refreshes:
-
- will add to affected snaps
- When we construct SnapSpecification for 3. will call AddConnectedPlug for all the slots is connected to and not just the one for slot-snap2?
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.
r.plugSlots
is filled by calls to Repository.Connect
. Afaiu in a refresh all connections are restored in the repository by InterfaceManager.reloadConnections()
(
snapd/overlord/ifacestate/helpers.go
Line 472 in b85fde1
if _, err := m.repo.Connect(connRef, staticPlugAttrs, connState.DynamicPlugAttrs, staticSlotAttrs, connState.DynamicSlotAttrs, nil); err != nil { |
So unless I got something wrong, the specification should contain information for all the connected slots, that is, for slot-snap1 and 2.
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.
Afaiu in a refresh all connections are restored in the repository by InterfaceManager.reloadConnections()
all connections for the given snap
I created the tests we were discussing:
the conclusion from there is that we should probably create a file per consumer side
df1088b
to
c5cad2c
Compare
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.
comment
interfaces/ldconfig/spec.go
Outdated
// Methods called by interfaces | ||
|
||
// AddLibDirs add dirs with libraries to the specification. | ||
func (spec *Specification) AddLibDirs(snapName, slotName string, dirs []string) { |
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.
we can capture the contextual info on the spec in AddConnectedPlug, I would expect that's the place we want this to work from? then we should error or panic if it's called with the contextual info unse
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.
see something like setScope in the apparmor backend, though what we need here is simpler
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.
Done now
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.
@pedronis thanks, comments addressed now
interfaces/ldconfig/spec.go
Outdated
// Methods called by interfaces | ||
|
||
// AddLibDirs add dirs with libraries to the specification. | ||
func (spec *Specification) AddLibDirs(snapName, slotName string, dirs []string) { |
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.
Done now
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #14926 +/- ##
=========================================
Coverage ? 78.22%
=========================================
Files ? 1167
Lines ? 154243
Branches ? 0
=========================================
Hits ? 120656
Misses ? 26155
Partials ? 7432
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
interfaces/ldconfig/backend.go
Outdated
content += "\n" | ||
|
||
// File name is snap.<snap_name>.<slot_name>.conf | ||
ldconfigPath := fmt.Sprintf("snap.%s.conf", key) |
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.
based on what I found we want this to be based on the plug snap name I think. OTOH if the plug snap is snapd we could use a special name like snapd.conf or snap.system.conf ?
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.
Changed as discussed, this is ready for review now
264f7be
to
01acd1d
Compare
@Meulengracht I've re-asked you for review as there have been a few changes |
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.
suggestion
interfaces/ldconfig/spec.go
Outdated
type Specification struct { | ||
// libDirs is the list of directories with libraries coming from | ||
// different slots. The key has the form "<snap_name>.<slot>". | ||
libDirs map[string][]string |
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.
maybe we should use a struct:
type sourceSlot {
Snap string
Slot string
}
for the key? to avoid the string mangling?
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.
Good point, done now
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.
thank you
interfaces/system.go
Outdated
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { | ||
switch snapName { | ||
case "", "snapd", "core", "ubuntu-core": |
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.
do we still care about "ubuntu-core" snap?
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've removed, thinking about this we already branched for UC16, so we do not need to care (we probably would not care even for UC16 nowadays).
interfaces/ldconfig/backend_test.go
Outdated
libcConfPath := filepath.Join(confDir, "libc.conf") | ||
c.Assert(os.WriteFile(libcConfPath, []byte{}, 0644), IsNil) | ||
|
||
// Add cakkback and register the interface |
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.
// Add cakkback and register the interface | |
// Add callback and register the interface |
interfaces/system.go
Outdated
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { | ||
switch snapName { | ||
case "", "snapd", "core", "ubuntu-core": |
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.
we have some code in repo.go in guessSystemSnapName()
which maybe could reuse the list.
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.
Changed so at least both share the list of the names of the system snap
|
||
// IsTheSystemSnap returns true if snapName is one of the possible | ||
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { |
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.
func IsTheSystemSnap(snapName string) bool { | |
func IsSystemSnap(snapName string) bool { |
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 prefer to keep this, it makes clear that there can be only one system snap
interfaces/system.go
Outdated
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { | ||
switch snapName { | ||
case "", "snapd", "core", "ubuntu-core": |
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.
is ""
for implicit plugs/slots? Aren't those always attached to either core or snapd?
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 think so, yes, afaiu IsTheSystemSnap
could be called with ""
.
57e771f
to
532b0d9
Compare
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.
@bboozzoo thanks for the review, I've addressed your comments now
|
||
// IsTheSystemSnap returns true if snapName is one of the possible | ||
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { |
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 prefer to keep this, it makes clear that there can be only one system snap
interfaces/system.go
Outdated
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { | ||
switch snapName { | ||
case "", "snapd", "core", "ubuntu-core": |
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've removed, thinking about this we already branched for UC16, so we do not need to care (we probably would not care even for UC16 nowadays).
interfaces/system.go
Outdated
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { | ||
switch snapName { | ||
case "", "snapd", "core", "ubuntu-core": |
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.
Changed so at least both share the list of the names of the system snap
interfaces/system.go
Outdated
// names for the snap representing the system. | ||
func IsTheSystemSnap(snapName string) bool { | ||
switch snapName { | ||
case "", "snapd", "core", "ubuntu-core": |
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 think so, yes, afaiu IsTheSystemSnap
could be called with ""
.
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.
LGTM
532b0d9
to
877cc4f
Compare
@pedronis @bboozzoo I had to do some changes here as spread tests were failing - I was being naive on when |
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 think we want to be a bit more careful with the checks instead
interfaces/ldconfig/spec.go
Outdated
@@ -87,7 +87,7 @@ func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *in | |||
|
|||
// AddConnectedSlot records ldconfig-specific side-effects of having a connected slot. | |||
func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { | |||
if !interfaces.IsTheSystemSnap(plug.Snap().InstanceName()) { | |||
if !interfaces.IsTheSystemSnap(slot.Snap().InstanceName()) { |
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.
this change seems definitely wrong we probably need to do something else and move the check only if the interface has the definer no?
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.
Also in either case this definitely warrants a comment
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.
Right, the check really needs to happen only for interfaces supporting ldconfig, I've changed this now and reverted to check for the plug here too.
interfaces/ldconfig/backend.go
Outdated
@@ -52,6 +53,12 @@ func (b *Backend) Name() interfaces.SecuritySystem { | |||
// If the method fails it should be re-tried (with a sensible strategy) by the caller. | |||
func (b *Backend) Setup(appSet *interfaces.SnapAppSet, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error { | |||
snapName := appSet.InstanceName() | |||
if !interfaces.IsTheSystemSnap(snapName) { |
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.
this might be ok for now but we probably want to instead change the rest of the code to work even if this is not here
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.
With the change in the checks in the specification, this check is not needed anymore. But that is sort of by chance as the spec is empty, if it wasn't it would try to write in /etc/ld.so.conf.d/snap.system.conf
for a snap that is not the system snap. So I think it is better to leave for the moment just to make clear we do not support normal snaps for the moment.
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.
but there two sides to things, you need to be abel to update things both when the system snap is update, but also when the providing snap with the slot which a kernel or something is updated, so I don't think we can really have this check
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.
changed now
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.
Also this is a +1 from me if comments are resolved, but not approving since it's awaitings Samuele's approval and you already have maciejs
@pedronis @Meulengracht this is ready for another round now |
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.
sorry, another question
// This method should be called after removing a snap. | ||
// | ||
// If the method fails it should be re-tried (with a sensible strategy) by the caller. | ||
func (b *Backend) Remove(snapName string) 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.
don't we need to do something here to remove the entries for a snap that had ldconfig providing slot?
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.
@alfonsosanchezbeato remarked that a slot side going away is taken care by this logic:
snapd/overlord/ifacestate/handlers.go
Lines 385 to 396 in 81fd929
func (m *InterfaceManager) removeProfilesForSnap(task *state.Task, _ *tomb.Tomb, snapName string, tm timings.Measurer) error { | |
// Disconnect the snap entirely. | |
// This is required to remove the snap from the interface repository. | |
// The returned list of affected snaps will need to have its security setup | |
// to reflect the change. | |
affectedSnaps, err := m.repo.DisconnectSnap(snapName) | |
if err != nil { | |
return err | |
} | |
if err := m.setupAffectedSnaps(task, snapName, affectedSnaps, tm); err != nil { | |
return err | |
} |
as the plug side will be Setup
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.
thanks
This backend will expose libraries coming from snaps to either the rootfs or to other snaps (currently supporting only the former).
8692ae6
to
e67495c
Compare
This backend will expose libraries coming from snaps to either the
rootfs or to other snaps (currently supporting only the former).