forked from snapcore/snapd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
repo.go
772 lines (702 loc) · 23.1 KB
/
repo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package interfaces
import (
"bytes"
"fmt"
"sort"
"strings"
"sync"
"github.com/snapcore/snapd/snap"
)
// Repository stores all known snappy plugs and slots and ifaces.
type Repository struct {
// Protects the internals from concurrent access.
m sync.Mutex
ifaces map[string]Interface
// Indexed by [snapName][plugName]
plugs map[string]map[string]*Plug
slots map[string]map[string]*Slot
slotPlugs map[*Slot]map[*Plug]bool
plugSlots map[*Plug]map[*Slot]bool
}
// NewRepository creates an empty plug repository.
func NewRepository() *Repository {
return &Repository{
ifaces: make(map[string]Interface),
plugs: make(map[string]map[string]*Plug),
slots: make(map[string]map[string]*Slot),
slotPlugs: make(map[*Slot]map[*Plug]bool),
plugSlots: make(map[*Plug]map[*Slot]bool),
}
}
// Interface returns an interface with a given name.
func (r *Repository) Interface(interfaceName string) Interface {
r.m.Lock()
defer r.m.Unlock()
return r.ifaces[interfaceName]
}
// AddInterface adds the provided interface to the repository.
func (r *Repository) AddInterface(i Interface) error {
r.m.Lock()
defer r.m.Unlock()
interfaceName := i.Name()
if err := ValidateName(interfaceName); err != nil {
return err
}
if _, ok := r.ifaces[interfaceName]; ok {
return fmt.Errorf("cannot add interface: %q, interface name is in use", interfaceName)
}
r.ifaces[interfaceName] = i
return nil
}
// AllPlugs returns all plugs of the given interface.
// If interfaceName is the empty string, all plugs are returned.
func (r *Repository) AllPlugs(interfaceName string) []*Plug {
r.m.Lock()
defer r.m.Unlock()
var result []*Plug
for _, plugsForSnap := range r.plugs {
for _, plug := range plugsForSnap {
if interfaceName == "" || plug.Interface == interfaceName {
result = append(result, plug)
}
}
}
sort.Sort(byPlugSnapAndName(result))
return result
}
// Plugs returns the plugs offered by the named snap.
func (r *Repository) Plugs(snapName string) []*Plug {
r.m.Lock()
defer r.m.Unlock()
var result []*Plug
for _, plug := range r.plugs[snapName] {
result = append(result, plug)
}
sort.Sort(byPlugSnapAndName(result))
return result
}
// Plug returns the specified plug from the named snap.
func (r *Repository) Plug(snapName, plugName string) *Plug {
r.m.Lock()
defer r.m.Unlock()
return r.plugs[snapName][plugName]
}
// AddPlug adds a plug to the repository.
// Plug names must be valid snap names, as defined by ValidateName.
// Plug name must be unique within a particular snap.
func (r *Repository) AddPlug(plug *Plug) error {
r.m.Lock()
defer r.m.Unlock()
// Reject snaps with invalid names
if err := snap.ValidateName(plug.Snap.Name()); err != nil {
return err
}
// Reject plug with invalid names
if err := ValidateName(plug.Name); err != nil {
return err
}
i := r.ifaces[plug.Interface]
if i == nil {
return fmt.Errorf("cannot add plug, interface %q is not known", plug.Interface)
}
// Reject plug that don't pass interface-specific sanitization
if err := i.SanitizePlug(plug); err != nil {
return fmt.Errorf("cannot add plug: %v", err)
}
if _, ok := r.plugs[plug.Snap.Name()][plug.Name]; ok {
return fmt.Errorf("cannot add plug, snap %q already has plug %q", plug.Snap.Name(), plug.Name)
}
if r.plugs[plug.Snap.Name()] == nil {
r.plugs[plug.Snap.Name()] = make(map[string]*Plug)
}
r.plugs[plug.Snap.Name()][plug.Name] = plug
return nil
}
// RemovePlug removes the named plug provided by a given snap.
// The removed plug must exist and must not be used anywhere.
func (r *Repository) RemovePlug(snapName, plugName string) error {
r.m.Lock()
defer r.m.Unlock()
// Ensure that such plug exists
plug := r.plugs[snapName][plugName]
if plug == nil {
return fmt.Errorf("cannot remove plug %q from snap %q, no such plug", plugName, snapName)
}
// Ensure that the plug is not used by any slot
if len(r.plugSlots[plug]) > 0 {
return fmt.Errorf("cannot remove plug %q from snap %q, it is still connected", plugName, snapName)
}
delete(r.plugs[snapName], plugName)
if len(r.plugs[snapName]) == 0 {
delete(r.plugs, snapName)
}
return nil
}
// AllSlots returns all slots of the given interface.
// If interfaceName is the empty string, all slots are returned.
func (r *Repository) AllSlots(interfaceName string) []*Slot {
r.m.Lock()
defer r.m.Unlock()
var result []*Slot
for _, slotsForSnap := range r.slots {
for _, slot := range slotsForSnap {
if interfaceName == "" || slot.Interface == interfaceName {
result = append(result, slot)
}
}
}
sort.Sort(bySlotSnapAndName(result))
return result
}
// Slots returns the slots offered by the named snap.
func (r *Repository) Slots(snapName string) []*Slot {
r.m.Lock()
defer r.m.Unlock()
var result []*Slot
for _, slot := range r.slots[snapName] {
result = append(result, slot)
}
sort.Sort(bySlotSnapAndName(result))
return result
}
// Slot returns the specified slot from the named snap.
func (r *Repository) Slot(snapName, slotName string) *Slot {
r.m.Lock()
defer r.m.Unlock()
return r.slots[snapName][slotName]
}
// AddSlot adds a new slot to the repository.
// Adding a slot with invalid name returns an error.
// Adding a slot that has the same name and snap name as another slot returns an error.
func (r *Repository) AddSlot(slot *Slot) error {
r.m.Lock()
defer r.m.Unlock()
// Reject snaps with invalid names
if err := snap.ValidateName(slot.Snap.Name()); err != nil {
return err
}
// Reject plug with invalid names
if err := ValidateName(slot.Name); err != nil {
return err
}
// TODO: ensure that apps are correct
i := r.ifaces[slot.Interface]
if i == nil {
return fmt.Errorf("cannot add slot, interface %q is not known", slot.Interface)
}
if err := i.SanitizeSlot(slot); err != nil {
return fmt.Errorf("cannot add slot: %v", err)
}
if _, ok := r.slots[slot.Snap.Name()][slot.Name]; ok {
return fmt.Errorf("cannot add slot, snap %q already has slot %q", slot.Snap.Name(), slot.Name)
}
if r.slots[slot.Snap.Name()] == nil {
r.slots[slot.Snap.Name()] = make(map[string]*Slot)
}
r.slots[slot.Snap.Name()][slot.Name] = slot
return nil
}
// RemoveSlot removes a named slot from the given snap.
// Removing a slot that doesn't exist returns an error.
// Removing a slot that is connected to a plug returns an error.
func (r *Repository) RemoveSlot(snapName, slotName string) error {
r.m.Lock()
defer r.m.Unlock()
// Ensure that such slot exists
slot := r.slots[snapName][slotName]
if slot == nil {
return fmt.Errorf("cannot remove slot %q from snap %q, no such slot", slotName, snapName)
}
// Ensure that the slot is not using any plugs
if len(r.slotPlugs[slot]) > 0 {
return fmt.Errorf("cannot remove slot %q from snap %q, it is still connected", slotName, snapName)
}
delete(r.slots[snapName], slotName)
if len(r.slots[snapName]) == 0 {
delete(r.slots, snapName)
}
return nil
}
// Connect establishes a connection between a plug and a slot.
// The plug and the slot must have the same interface.
func (r *Repository) Connect(plugSnapName, plugName, slotSnapName, slotName string) error {
r.m.Lock()
defer r.m.Unlock()
// Ensure that such plug exists
plug := r.plugs[plugSnapName][plugName]
if plug == nil {
return fmt.Errorf("cannot connect plug %q from snap %q, no such plug", plugName, plugSnapName)
}
// Ensure that such slot exists
slot := r.slots[slotSnapName][slotName]
if slot == nil {
return fmt.Errorf("cannot connect plug to slot %q from snap %q, no such slot", slotName, slotSnapName)
}
// Ensure that plug and slot are compatible
if slot.Interface != plug.Interface {
return fmt.Errorf(`cannot connect plug "%s:%s" (interface %q) to "%s:%s" (interface %q)`,
plugSnapName, plugName, plug.Interface, slotSnapName, slotName, slot.Interface)
}
// Ensure that slot and plug are not connected yet
if r.slotPlugs[slot][plug] {
// But if they are don't treat this as an error.
return nil
}
// Connect the plug
if r.slotPlugs[slot] == nil {
r.slotPlugs[slot] = make(map[*Plug]bool)
}
if r.plugSlots[plug] == nil {
r.plugSlots[plug] = make(map[*Slot]bool)
}
r.slotPlugs[slot][plug] = true
r.plugSlots[plug][slot] = true
slot.Connections = append(slot.Connections, PlugRef{plug.Snap.Name(), plug.Name})
plug.Connections = append(plug.Connections, SlotRef{slot.Snap.Name(), slot.Name})
return nil
}
// Disconnect disconnects the named plug from the slot of the given snap.
//
// Disconnect has three modes of operation that depend on the passed arguments:
//
// - If all the arguments are specified then Disconnect() finds a specific slot
// and a specific plug and disconnects that plug from that slot. It is
// an error if plug or slot cannot be found or if the connect does not
// exist.
// - If plugSnapName and plugName are empty then Disconnect() finds the specified
// slot and disconnects all the plugs connected there. It is not an error if
// there are no such plugs but it is still an error if the slot does
// not exist.
// - If plugSnapName, plugName and slotName are all empty then Disconnect finds
// the specified snap (designated by slotSnapName) and disconnects all the plugs
// from all the slots found therein. It is not an error if there are no
// such plugs but it is still an error if the snap does not exist or has no
// slots at all.
func (r *Repository) Disconnect(plugSnapName, plugName, slotSnapName, slotName string) error {
r.m.Lock()
defer r.m.Unlock()
switch {
case plugSnapName == "" && plugName == "" && slotName == "":
// Disconnect everything from slotSnapName
return r.disconnectEverythingFromSnap(slotSnapName)
case plugSnapName == "" && plugName == "":
// Disconnect everything from slotSnapName:slotName
return r.disconnectEverythingFromSlot(slotSnapName, slotName)
default:
return r.disconnectPlugFromSlot(plugSnapName, plugName, slotSnapName, slotName)
}
}
// disconnectEverythingFromSnap finds a specific snap and disconnects all the plugs connected to all the slots therein.
func (r *Repository) disconnectEverythingFromSnap(slotSnapName string) error {
if _, ok := r.slots[slotSnapName]; !ok {
return fmt.Errorf("cannot disconnect plug from snap %q, no such snap", slotSnapName)
}
for _, slot := range r.slots[slotSnapName] {
for plug := range r.slotPlugs[slot] {
r.disconnect(plug, slot)
}
}
return nil
}
// disconnectEverythingFromSlot finds a specific slot and disconnects all the plugs connected there.
func (r *Repository) disconnectEverythingFromSlot(slotSnapName, slotName string) error {
// Ensure that such slot exists
slot := r.slots[slotSnapName][slotName]
if slot == nil {
return fmt.Errorf("cannot disconnect plug from slot %q from snap %q, no such slot", slotName, slotSnapName)
}
for plug := range r.slotPlugs[slot] {
r.disconnect(plug, slot)
}
return nil
}
// disconnectPlugFromSlot finds a specific slot and plug and disconnects it.
func (r *Repository) disconnectPlugFromSlot(plugSnapName, plugName, slotSnapName, slotName string) error {
// Ensure that such plug exists
plug := r.plugs[plugSnapName][plugName]
if plug == nil {
return fmt.Errorf("cannot disconnect plug %q from snap %q, no such plug", plugName, plugSnapName)
}
// Ensure that such slot exists
slot := r.slots[slotSnapName][slotName]
if slot == nil {
return fmt.Errorf("cannot disconnect plug from slot %q from snap %q, no such slot", slotName, slotSnapName)
}
// Ensure that slot and plug are connected
if !r.slotPlugs[slot][plug] {
return fmt.Errorf("cannot disconnect plug %q from snap %q from slot %q from snap %q, it is not connected",
plugName, plugSnapName, slotName, slotSnapName)
}
r.disconnect(plug, slot)
return nil
}
// disconnect disconnects a plug from a slot.
func (r *Repository) disconnect(plug *Plug, slot *Slot) {
delete(r.slotPlugs[slot], plug)
if len(r.slotPlugs[slot]) == 0 {
delete(r.slotPlugs, slot)
}
delete(r.plugSlots[plug], slot)
if len(r.plugSlots[plug]) == 0 {
delete(r.plugSlots, plug)
}
for i, plugRef := range slot.Connections {
if plugRef.Snap == plug.Snap.Name() && plugRef.Name == plug.Name {
slot.Connections[i] = slot.Connections[len(slot.Connections)-1]
slot.Connections = slot.Connections[:len(slot.Connections)-1]
if len(slot.Connections) == 0 {
slot.Connections = nil
}
break
}
}
for i, slotRef := range plug.Connections {
if slotRef.Snap == slot.Snap.Name() && slotRef.Name == slot.Name {
plug.Connections[i] = plug.Connections[len(plug.Connections)-1]
plug.Connections = plug.Connections[:len(plug.Connections)-1]
if len(plug.Connections) == 0 {
plug.Connections = nil
}
break
}
}
}
// Interfaces returns object holding a lists of all the plugs and slots and their connections.
func (r *Repository) Interfaces() *Interfaces {
r.m.Lock()
defer r.m.Unlock()
ifaces := &Interfaces{}
// Copy and flatten plugs and slots
for _, plugs := range r.plugs {
for _, plug := range plugs {
p := &Plug{
PlugInfo: plug.PlugInfo,
Connections: append([]SlotRef(nil), plug.Connections...),
}
sort.Sort(bySlotRef(p.Connections))
ifaces.Plugs = append(ifaces.Plugs, p)
}
}
for _, slots := range r.slots {
for _, slot := range slots {
s := &Slot{
SlotInfo: slot.SlotInfo,
Connections: append([]PlugRef(nil), slot.Connections...),
}
sort.Sort(byPlugRef(s.Connections))
ifaces.Slots = append(ifaces.Slots, s)
}
}
sort.Sort(byPlugSnapAndName(ifaces.Plugs))
sort.Sort(bySlotSnapAndName(ifaces.Slots))
return ifaces
}
// SecuritySnippetsForSnap collects all of the snippets of a given security
// system that affect a given snap. The return value is indexed by app/hook
// security tag within that snap.
func (r *Repository) SecuritySnippetsForSnap(snapName string, securitySystem SecuritySystem) (map[string][][]byte, error) {
r.m.Lock()
defer r.m.Unlock()
return r.securitySnippetsForSnap(snapName, securitySystem)
}
func (r *Repository) securitySnippetsForSnap(snapName string, securitySystem SecuritySystem) (map[string][][]byte, error) {
var snippets = make(map[string][][]byte)
// Find all of the slots that affect this snap because of plug connection.
for _, slot := range r.slots[snapName] {
iface := r.ifaces[slot.Interface]
// Add the static snippet for the slot
snippet, err := iface.PermanentSlotSnippet(slot, securitySystem)
if err != nil {
return nil, err
}
if snippet != nil {
for appName := range slot.Apps {
securityTag := snap.AppSecurityTag(snapName, appName)
snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
// Add connection-specific snippet specific to each plug
for plug := range r.slotPlugs[slot] {
snippet, err := iface.ConnectedSlotSnippet(plug, slot, securitySystem)
if err != nil {
return nil, err
}
if snippet == nil {
continue
}
for appName := range slot.Apps {
securityTag := snap.AppSecurityTag(snapName, appName)
snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
}
// Find all of the plugs that affect this snap because of slot connection
for _, plug := range r.plugs[snapName] {
iface := r.ifaces[plug.Interface]
// Add the static snippet for the plug
snippet, err := iface.PermanentPlugSnippet(plug, securitySystem)
if err != nil {
return nil, err
}
if snippet != nil {
for appName := range plug.Apps {
securityTag := snap.AppSecurityTag(snapName, appName)
snippets[securityTag] = append(snippets[securityTag], snippet)
}
for hookName := range plug.Hooks {
securityTag := snap.HookSecurityTag(snapName, hookName)
snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
// Add connection-specific snippet specific to each slot
for slot := range r.plugSlots[plug] {
snippet, err := iface.ConnectedPlugSnippet(plug, slot, securitySystem)
if err != nil {
return nil, err
}
if snippet == nil {
continue
}
for appName := range plug.Apps {
securityTag := snap.AppSecurityTag(snapName, appName)
snippets[securityTag] = append(snippets[securityTag], snippet)
}
for hookName := range plug.Hooks {
securityTag := snap.HookSecurityTag(snapName, hookName)
snippets[securityTag] = append(snippets[securityTag], snippet)
}
}
}
return snippets, nil
}
// BadInterfacesError is returned when some snap interfaces could not be registered.
// Those interfaces not mentioned in the error were successfully registered.
type BadInterfacesError struct {
snap string
issues map[string]string // slot or plug name => message
}
func (e *BadInterfacesError) Error() string {
inverted := make(map[string][]string)
for name, reason := range e.issues {
inverted[reason] = append(inverted[reason], name)
}
var buf bytes.Buffer
fmt.Fprintf(&buf, "snap %q has bad plugs or slots: ", e.snap)
reasons := make([]string, 0, len(inverted))
for reason := range inverted {
reasons = append(reasons, reason)
}
sort.Strings(reasons)
for _, reason := range reasons {
names := inverted[reason]
sort.Strings(names)
for i, name := range names {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(name)
}
fmt.Fprintf(&buf, " (%s); ", reason)
}
return strings.TrimSuffix(buf.String(), "; ")
}
// AddSnap adds plugs and slots declared by the given snap to the repository.
//
// This function can be used to implement snap install or, when used along with
// RemoveSnap, snap upgrade.
//
// AddSnap doesn't change existing plugs/slots. The caller is responsible for
// ensuring that the snap is not present in the repository in any way prior to
// calling this function. If this constraint is violated then no changes are
// made and an error is returned.
//
// Each added plug/slot is validated according to the corresponding interface.
// Unknown interfaces and plugs/slots that don't validate are not added.
// Information about those failures are returned to the caller.
func (r *Repository) AddSnap(snapInfo *snap.Info) error {
r.m.Lock()
defer r.m.Unlock()
snapName := snapInfo.Name()
if r.plugs[snapName] != nil || r.slots[snapName] != nil {
return fmt.Errorf("cannot register interfaces for snap %q more than once", snapName)
}
bad := BadInterfacesError{
snap: snapName,
issues: make(map[string]string),
}
for plugName, plugInfo := range snapInfo.Plugs {
iface, ok := r.ifaces[plugInfo.Interface]
if !ok {
bad.issues[plugName] = "unknown interface"
continue
}
plug := &Plug{PlugInfo: plugInfo}
if err := iface.SanitizePlug(plug); err != nil {
bad.issues[plugName] = err.Error()
continue
}
if r.plugs[snapName] == nil {
r.plugs[snapName] = make(map[string]*Plug)
}
r.plugs[snapName][plugName] = plug
}
for slotName, slotInfo := range snapInfo.Slots {
iface, ok := r.ifaces[slotInfo.Interface]
if !ok {
bad.issues[slotName] = "unknown interface"
continue
}
slot := &Slot{SlotInfo: slotInfo}
if err := iface.SanitizeSlot(slot); err != nil {
bad.issues[slotName] = err.Error()
continue
}
if r.slots[snapName] == nil {
r.slots[snapName] = make(map[string]*Slot)
}
r.slots[snapName][slotName] = slot
}
if len(bad.issues) > 0 {
return &bad
}
return nil
}
// RemoveSnap removes all the plugs and slots associated with a given snap.
//
// This function can be used to implement snap removal or, when used along with
// AddSnap, snap upgrade.
//
// RemoveSnap does not remove connections. The caller is responsible for
// ensuring that connections are broken before calling this method. If this
// constraint is violated then no changes are made and an error is returned.
func (r *Repository) RemoveSnap(snapName string) error {
r.m.Lock()
defer r.m.Unlock()
for plugName, plug := range r.plugs[snapName] {
if len(plug.Connections) > 0 {
return fmt.Errorf("cannot remove connected plug %s.%s", snapName, plugName)
}
}
for slotName, slot := range r.slots[snapName] {
if len(slot.Connections) > 0 {
return fmt.Errorf("cannot remove connected slot %s.%s", snapName, slotName)
}
}
for _, plug := range r.plugs[snapName] {
delete(r.plugSlots, plug)
}
delete(r.plugs, snapName)
for _, slot := range r.slots[snapName] {
delete(r.slotPlugs, slot)
}
delete(r.slots, snapName)
return nil
}
// AutoConnectBlacklist returns plug names that should not be auto-connected.
//
// Plug is blacklisted if it has no connections despite using an auto-connected
// interface. That implies it was manually disconnected.
func (r *Repository) AutoConnectBlacklist(snapName string) map[string]bool {
r.m.Lock()
defer r.m.Unlock()
var blacklist map[string]bool
for plugName, plug := range r.plugs[snapName] {
iface := r.ifaces[plug.Interface]
if !iface.AutoConnect() {
continue
}
if len(r.plugSlots[plug]) != 0 {
continue
}
if blacklist == nil {
blacklist = make(map[string]bool)
}
blacklist[plugName] = true
}
return blacklist
}
// DisconnectSnap disconnects all the connections to and from a given snap.
//
// The return value is a list of names that were affected.
func (r *Repository) DisconnectSnap(snapName string) ([]string, error) {
r.m.Lock()
defer r.m.Unlock()
seen := make(map[*snap.Info]bool)
for _, plug := range r.plugs[snapName] {
for slot := range r.plugSlots[plug] {
r.disconnect(plug, slot)
seen[plug.Snap] = true
seen[slot.Snap] = true
}
}
for _, slot := range r.slots[snapName] {
for plug := range r.slotPlugs[slot] {
r.disconnect(plug, slot)
seen[plug.Snap] = true
seen[slot.Snap] = true
}
}
result := make([]string, 0, len(seen))
for info := range seen {
result = append(result, info.Name())
}
sort.Strings(result)
return result, nil
}
// AutoConnectCandidates finds and returns viable auto-connection candidates
// for a given plug.
func (r *Repository) AutoConnectCandidates(plugSnapName, plugName string) []*Slot {
r.m.Lock()
defer r.m.Unlock()
plug := r.plugs[plugSnapName][plugName]
if plug == nil {
return nil
}
if r.ifaces[plug.Interface].AutoConnect() == false {
return nil
}
var candidates []*Slot
for _, slotsForSnap := range r.slots {
for _, slot := range slotsForSnap {
if isAutoConnectCandidate(plug, slot) {
candidates = append(candidates, slot)
}
}
}
return candidates
}
// isAutoConnectCandidate returns true if the plug is a candidate to
// automatically connect to the given slot.
func isAutoConnectCandidate(plug *Plug, slot *Slot) bool {
// content sharing auto connect candidates
if slot.Interface == "content" {
if slot.Attrs["content"] == plug.Attrs["content"] && slot.Snap.Developer == plug.Snap.Developer {
return true
}
// we need to stop here to avoid the OS snap autoconnecting
// any content later
return false
}
// OS snap auto connect candidates
if slot.Snap.Type == snap.TypeOS && slot.Interface == plug.Interface {
return true
}
return false
}