github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/snapstate/aliasesv2.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package snapstate 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "strings" 26 27 "github.com/snapcore/snapd/i18n" 28 "github.com/snapcore/snapd/logger" 29 "github.com/snapcore/snapd/overlord/snapstate/backend" 30 "github.com/snapcore/snapd/overlord/state" 31 "github.com/snapcore/snapd/snap" 32 "github.com/snapcore/snapd/strutil" 33 ) 34 35 // AliasTarget carries the targets of an alias in the context of snap. 36 // If Manual is set it is the target of an enabled manual alias. 37 // Auto is set to the target for an automatic alias, enabled or 38 // disabled depending on the automatic aliases flag state. 39 type AliasTarget struct { 40 Manual string `json:"manual,omitempty"` 41 Auto string `json:"auto,omitempty"` 42 } 43 44 // Effective returns the target to use considering whether automatic 45 // aliases are disabled for the whole snap (autoDisabled), returns "" 46 // if the alias is disabled. 47 func (at *AliasTarget) Effective(autoDisabled bool) string { 48 if at == nil { 49 return "" 50 } 51 if at.Manual != "" { 52 return at.Manual 53 } 54 if !autoDisabled { 55 return at.Auto 56 } 57 return "" 58 } 59 60 /* 61 State for aliases for a snap is tracked in SnapState with: 62 63 type SnapState struct { 64 ... 65 Aliases map[string]*AliasTarget 66 AutoAliasesDisabled bool 67 } 68 69 There are two kinds of aliases: 70 71 * automatic aliases listed with their target application in the 72 snap-declaration of the snap (using AliasTarget.Auto) 73 74 * manual aliases setup with "snap alias SNAP.APP ALIAS" (tracked 75 using AliasTarget.Manual) 76 77 Further 78 79 * all automatic aliases of a snap are either enabled 80 or disabled together (tracked with AutoAliasesDisabled) 81 82 * disabling a manual alias removes it from disk and state (for 83 simplicity there is no disabled state for manual aliases) 84 85 * an AliasTarget with both Auto and Manual set is a manual alias 86 that has the same name as an automatic one, the manual target 87 is what wins 88 89 */ 90 91 // autoDisabled options and doApply 92 const ( 93 autoDis = true 94 autoEn = false 95 96 doApply = false 97 ) 98 99 // applyAliasesChange applies the necessary changes to aliases on disk 100 // to go from prevAliases considering the automatic aliases flag 101 // (prevAutoDisabled) to newAliases considering newAutoDisabled for 102 // snapName. It assumes that conflicts have already been checked. 103 func applyAliasesChange(snapName string, prevAutoDisabled bool, prevAliases map[string]*AliasTarget, newAutoDisabled bool, newAliases map[string]*AliasTarget, be managerBackend, dryRun bool) (add, remove []*backend.Alias, err error) { 104 for alias, prevTargets := range prevAliases { 105 if _, ok := newAliases[alias]; ok { 106 continue 107 } 108 // gone 109 if effTgt := prevTargets.Effective(prevAutoDisabled); effTgt != "" { 110 remove = append(remove, &backend.Alias{ 111 Name: alias, 112 Target: snap.JoinSnapApp(snapName, effTgt), 113 }) 114 } 115 } 116 for alias, newTargets := range newAliases { 117 prevTgt := prevAliases[alias].Effective(prevAutoDisabled) 118 newTgt := newTargets.Effective(newAutoDisabled) 119 if prevTgt == newTgt { 120 // nothing to do 121 continue 122 } 123 if prevTgt != "" { 124 remove = append(remove, &backend.Alias{ 125 Name: alias, 126 Target: snap.JoinSnapApp(snapName, prevTgt), 127 }) 128 } 129 if newTgt != "" { 130 add = append(add, &backend.Alias{ 131 Name: alias, 132 Target: snap.JoinSnapApp(snapName, newTgt), 133 }) 134 } 135 } 136 if !dryRun { 137 if err := be.UpdateAliases(add, remove); err != nil { 138 return nil, nil, err 139 } 140 } 141 return add, remove, nil 142 } 143 144 // AutoAliases allows to hook support for retrieving the automatic aliases of a snap. 145 var AutoAliases func(st *state.State, info *snap.Info) (map[string]string, error) 146 147 // autoAliasesDelta compares the automatic aliases with the current snap 148 // declaration for the installed snaps with the given names (or all if 149 // names is empty) and returns changed and dropped auto-aliases by 150 // snap name. 151 func autoAliasesDelta(st *state.State, names []string) (changed map[string][]string, dropped map[string][]string, err error) { 152 var snapStates map[string]*SnapState 153 if len(names) == 0 { 154 var err error 155 snapStates, err = All(st) 156 if err != nil { 157 return nil, nil, err 158 } 159 } else { 160 snapStates = make(map[string]*SnapState, len(names)) 161 for _, name := range names { 162 var snapst SnapState 163 err := Get(st, name, &snapst) 164 if err != nil { 165 return nil, nil, err 166 } 167 snapStates[name] = &snapst 168 } 169 } 170 var firstErr error 171 changed = make(map[string][]string) 172 dropped = make(map[string][]string) 173 for instanceName, snapst := range snapStates { 174 aliases := snapst.Aliases 175 info, err := snapst.CurrentInfo() 176 if err != nil { 177 if firstErr == nil { 178 firstErr = err 179 } 180 continue 181 } 182 autoAliases, err := AutoAliases(st, info) 183 if err != nil { 184 if firstErr == nil { 185 firstErr = err 186 } 187 continue 188 } 189 for alias, target := range autoAliases { 190 curTarget := aliases[alias] 191 if curTarget == nil || curTarget.Auto != target { 192 changed[instanceName] = append(changed[instanceName], alias) 193 } 194 } 195 for alias, target := range aliases { 196 if target.Auto != "" && autoAliases[alias] == "" { 197 dropped[instanceName] = append(dropped[instanceName], alias) 198 } 199 } 200 } 201 return changed, dropped, firstErr 202 } 203 204 // refreshAliases applies the current snap-declaration aliases 205 // considering which applications exist in info and produces new aliases 206 // for the snap. 207 func refreshAliases(st *state.State, info *snap.Info, curAliases map[string]*AliasTarget) (newAliases map[string]*AliasTarget, err error) { 208 autoAliases, err := AutoAliases(st, info) 209 if err != nil { 210 return nil, err 211 } 212 213 newAliases = make(map[string]*AliasTarget, len(autoAliases)) 214 // apply the current auto-aliases 215 for alias, target := range autoAliases { 216 if app := info.Apps[target]; app == nil || app.IsService() { 217 // non-existing app or a daemon 218 continue 219 } 220 newAliases[alias] = &AliasTarget{Auto: target} 221 } 222 223 // carry over the current manual ones 224 for alias, curTarget := range curAliases { 225 if curTarget.Manual == "" { 226 continue 227 } 228 if app := info.Apps[curTarget.Manual]; app == nil || app.IsService() { 229 // non-existing app or daemon 230 continue 231 } 232 newTarget := newAliases[alias] 233 if newTarget == nil { 234 newAliases[alias] = &AliasTarget{Manual: curTarget.Manual} 235 } else { 236 // alias is both manually setup but has an underlying auto-alias 237 newAliases[alias].Manual = curTarget.Manual 238 } 239 } 240 return newAliases, nil 241 } 242 243 type AliasConflictError struct { 244 Snap string 245 Alias string 246 Reason string 247 Conflicts map[string][]string 248 } 249 250 func (e *AliasConflictError) Error() string { 251 if len(e.Conflicts) != 0 { 252 errParts := []string{"cannot enable"} 253 first := true 254 for instanceName, aliases := range e.Conflicts { 255 if !first { 256 errParts = append(errParts, "nor") 257 } 258 if len(aliases) == 1 { 259 errParts = append(errParts, fmt.Sprintf("alias %q", aliases[0])) 260 } else { 261 errParts = append(errParts, fmt.Sprintf("aliases %s", strutil.Quoted(aliases))) 262 } 263 if first { 264 errParts = append(errParts, fmt.Sprintf("for %q,", e.Snap)) 265 first = false 266 } 267 errParts = append(errParts, fmt.Sprintf("already enabled for %q", instanceName)) 268 } 269 // TODO: add recommendation about what to do next 270 return strings.Join(errParts, " ") 271 } 272 return fmt.Sprintf("cannot enable alias %q for %q, %s", e.Alias, e.Snap, e.Reason) 273 } 274 275 func addAliasConflicts(st *state.State, skipSnap string, testAliases map[string]bool, aliasConflicts map[string][]string, changing map[string]*SnapState) error { 276 snapStates, err := All(st) 277 if err != nil { 278 return err 279 } 280 for otherSnap, snapst := range snapStates { 281 if otherSnap == skipSnap { 282 // skip 283 continue 284 } 285 if nextSt, ok := changing[otherSnap]; ok { 286 snapst = nextSt 287 } 288 autoDisabled := snapst.AutoAliasesDisabled 289 var confls []string 290 if len(snapst.Aliases) < len(testAliases) { 291 for alias, target := range snapst.Aliases { 292 if testAliases[alias] && target.Effective(autoDisabled) != "" { 293 confls = append(confls, alias) 294 } 295 } 296 } else { 297 for alias := range testAliases { 298 target := snapst.Aliases[alias] 299 if target != nil && target.Effective(autoDisabled) != "" { 300 confls = append(confls, alias) 301 } 302 } 303 } 304 if len(confls) > 0 { 305 aliasConflicts[otherSnap] = confls 306 } 307 } 308 return nil 309 } 310 311 // checkAliasesStatConflicts checks candAliases considering 312 // candAutoDisabled for conflicts against other snap aliases returning 313 // conflicting snaps and aliases for alias conflicts. 314 // changing can specify about to be set states for some snaps that will 315 // then be considered. 316 func checkAliasesConflicts(st *state.State, snapName string, candAutoDisabled bool, candAliases map[string]*AliasTarget, changing map[string]*SnapState) (conflicts map[string][]string, err error) { 317 var snapNames map[string]*json.RawMessage 318 err = st.Get("snaps", &snapNames) 319 if err != nil && err != state.ErrNoState { 320 return nil, err 321 } 322 323 enabled := make(map[string]bool, len(candAliases)) 324 for alias, candTarget := range candAliases { 325 if candTarget.Effective(candAutoDisabled) != "" { 326 enabled[alias] = true 327 } else { 328 continue 329 } 330 namespace := alias 331 if i := strings.IndexRune(alias, '.'); i != -1 { 332 namespace = alias[:i] 333 } 334 // check against snap namespaces 335 if snapNames[namespace] != nil { 336 return nil, &AliasConflictError{ 337 Alias: alias, 338 Snap: snapName, 339 Reason: fmt.Sprintf("it conflicts with the command namespace of installed snap %q", namespace), 340 } 341 } 342 } 343 344 // check against enabled aliases 345 conflicts = make(map[string][]string) 346 if err := addAliasConflicts(st, snapName, enabled, conflicts, changing); err != nil { 347 return nil, err 348 } 349 if len(conflicts) != 0 { 350 return conflicts, &AliasConflictError{Snap: snapName, Conflicts: conflicts} 351 } 352 return nil, nil 353 } 354 355 // checkSnapAliasConflict checks whether instanceName and its command 356 // namepsace conflicts against installed snap aliases. 357 func checkSnapAliasConflict(st *state.State, instanceName string) error { 358 prefix := fmt.Sprintf("%s.", instanceName) 359 snapStates, err := All(st) 360 if err != nil { 361 return err 362 } 363 for otherSnap, snapst := range snapStates { 364 autoDisabled := snapst.AutoAliasesDisabled 365 for alias, target := range snapst.Aliases { 366 if alias == instanceName || strings.HasPrefix(alias, prefix) { 367 if target.Effective(autoDisabled) != "" { 368 return fmt.Errorf("snap %q command namespace conflicts with alias %q for %q snap", instanceName, alias, otherSnap) 369 } 370 } 371 } 372 } 373 return nil 374 } 375 376 // disableAliases returns newAliases corresponding to the disabling of 377 // curAliases, for manual aliases that means removed. 378 func disableAliases(curAliases map[string]*AliasTarget) (newAliases map[string]*AliasTarget, disabledManual map[string]string) { 379 newAliases = make(map[string]*AliasTarget, len(curAliases)) 380 disabledManual = make(map[string]string, len(curAliases)) 381 for alias, curTarget := range curAliases { 382 if curTarget.Manual != "" { 383 disabledManual[alias] = curTarget.Manual 384 } 385 if curTarget.Auto != "" { 386 newAliases[alias] = &AliasTarget{Auto: curTarget.Auto} 387 } 388 } 389 if len(disabledManual) == 0 { 390 disabledManual = nil 391 } 392 return newAliases, disabledManual 393 } 394 395 // reenableAliases returns newAliases corresponding to the re-enabling over 396 // curAliases of disabledManual manual aliases. 397 func reenableAliases(info *snap.Info, curAliases map[string]*AliasTarget, disabledManual map[string]string) (newAliases map[string]*AliasTarget) { 398 newAliases = make(map[string]*AliasTarget, len(curAliases)) 399 for alias, aliasTarget := range curAliases { 400 newAliases[alias] = aliasTarget 401 } 402 403 for alias, manual := range disabledManual { 404 if app := info.Apps[manual]; app == nil || app.IsService() { 405 // not a non-daemon app presently 406 continue 407 } 408 409 newTarget := newAliases[alias] 410 if newTarget == nil { 411 newAliases[alias] = &AliasTarget{Manual: manual} 412 } else { 413 manualTarget := *newTarget 414 manualTarget.Manual = manual 415 newAliases[alias] = &manualTarget 416 } 417 } 418 419 return newAliases 420 } 421 422 // pruneAutoAliases returns newAliases by dropping the automatic 423 // aliases autoAliases from curAliases, used as the task 424 // prune-auto-aliases to handle transfers of automatic aliases in a 425 // refresh. 426 func pruneAutoAliases(curAliases map[string]*AliasTarget, autoAliases []string) (newAliases map[string]*AliasTarget) { 427 newAliases = make(map[string]*AliasTarget, len(curAliases)) 428 for alias, aliasTarget := range curAliases { 429 newAliases[alias] = aliasTarget 430 } 431 for _, alias := range autoAliases { 432 curTarget := curAliases[alias] 433 if curTarget == nil { 434 // nothing to do 435 continue 436 } 437 if curTarget.Manual == "" { 438 delete(newAliases, alias) 439 } else { 440 newAliases[alias] = &AliasTarget{Manual: curTarget.Manual} 441 } 442 } 443 return newAliases 444 } 445 446 // transition to aliases v2 447 func (m *SnapManager) ensureAliasesV2() error { 448 m.state.Lock() 449 defer m.state.Unlock() 450 451 var aliasesV1 map[string]interface{} 452 err := m.state.Get("aliases", &aliasesV1) 453 if err != nil && err != state.ErrNoState { 454 return err 455 } 456 if len(aliasesV1) == 0 { 457 if err == nil { // something empty was there, delete it 458 m.state.Set("aliases", nil) 459 } 460 // nothing to do 461 return nil 462 } 463 464 snapStates, err := All(m.state) 465 if err != nil { 466 return err 467 } 468 469 // mark pending "alias" tasks as errored 470 // they were never parts of lanes but either standalone or at 471 // the start of wait chains 472 for _, t := range m.state.Tasks() { 473 if t.Kind() == "alias" && !t.Status().Ready() { 474 var param interface{} 475 err := t.Get("aliases", ¶m) 476 if err == state.ErrNoState { 477 // not the old variant, leave alone 478 continue 479 } 480 t.Errorf("internal representation for aliases has changed, please retry") 481 t.SetStatus(state.ErrorStatus) 482 } 483 } 484 485 withAliases := make(map[string]*SnapState, len(snapStates)) 486 for instanceName, snapst := range snapStates { 487 err := m.backend.RemoveSnapAliases(instanceName) 488 if err != nil { 489 logger.Noticef("cannot cleanup aliases for %q: %v", instanceName, err) 490 continue 491 } 492 493 info, err := snapst.CurrentInfo() 494 if err != nil { 495 logger.Noticef("cannot get info for %q: %v", instanceName, err) 496 continue 497 } 498 newAliases, err := refreshAliases(m.state, info, nil) 499 if err != nil { 500 logger.Noticef("cannot get automatic aliases for %q: %v", instanceName, err) 501 continue 502 } 503 // TODO: check for conflicts 504 if len(newAliases) != 0 { 505 snapst.Aliases = newAliases 506 withAliases[instanceName] = snapst 507 } 508 snapst.AutoAliasesDisabled = false 509 if !snapst.Active { 510 snapst.AliasesPending = true 511 } 512 } 513 514 for instanceName, snapst := range withAliases { 515 if !snapst.AliasesPending { 516 _, _, err := applyAliasesChange(instanceName, autoDis, nil, autoEn, snapst.Aliases, m.backend, doApply) 517 if err != nil { 518 // try to clean up and disable 519 logger.Noticef("cannot create automatic aliases for %q: %v", instanceName, err) 520 m.backend.RemoveSnapAliases(instanceName) 521 snapst.AutoAliasesDisabled = true 522 } 523 } 524 Set(m.state, instanceName, snapst) 525 } 526 527 m.state.Set("aliases", nil) 528 return nil 529 } 530 531 // Alias sets up a manual alias from alias to app in snapName. 532 func Alias(st *state.State, instanceName, app, alias string) (*state.TaskSet, error) { 533 if err := snap.ValidateAlias(alias); err != nil { 534 return nil, err 535 } 536 537 var snapst SnapState 538 err := Get(st, instanceName, &snapst) 539 if err == state.ErrNoState { 540 return nil, &snap.NotInstalledError{Snap: instanceName} 541 } 542 if err != nil { 543 return nil, err 544 } 545 if err := CheckChangeConflict(st, instanceName, nil); err != nil { 546 return nil, err 547 } 548 549 snapName, instanceKey := snap.SplitInstanceName(instanceName) 550 snapsup := &SnapSetup{ 551 SideInfo: &snap.SideInfo{RealName: snapName}, 552 InstanceKey: instanceKey, 553 } 554 555 manualAlias := st.NewTask("alias", fmt.Sprintf(i18n.G("Setup manual alias %q => %q for snap %q"), alias, app, snapsup.InstanceName())) 556 manualAlias.Set("alias", alias) 557 manualAlias.Set("target", app) 558 manualAlias.Set("snap-setup", &snapsup) 559 560 return state.NewTaskSet(manualAlias), nil 561 } 562 563 // manualAliases returns newAliases with a manual alias to target setup over 564 // curAliases. 565 func manualAlias(info *snap.Info, curAliases map[string]*AliasTarget, target, alias string) (newAliases map[string]*AliasTarget, err error) { 566 if app := info.Apps[target]; app == nil || app.IsService() { 567 var reason string 568 if app == nil { 569 reason = fmt.Sprintf("target application %q does not exist", target) 570 } else { 571 reason = fmt.Sprintf("target application %q is a daemon", target) 572 } 573 return nil, fmt.Errorf("cannot enable alias %q for %q, %s", alias, info.InstanceName(), reason) 574 } 575 newAliases = make(map[string]*AliasTarget, len(curAliases)) 576 for alias, aliasTarget := range curAliases { 577 newAliases[alias] = aliasTarget 578 } 579 580 newTarget := newAliases[alias] 581 if newTarget == nil { 582 newAliases[alias] = &AliasTarget{Manual: target} 583 } else { 584 manualTarget := *newTarget 585 manualTarget.Manual = target 586 newAliases[alias] = &manualTarget 587 } 588 589 return newAliases, nil 590 } 591 592 // DisableAllAliases disables all aliases of a snap, removing all manual ones. 593 func DisableAllAliases(st *state.State, instanceName string) (*state.TaskSet, error) { 594 var snapst SnapState 595 err := Get(st, instanceName, &snapst) 596 if err == state.ErrNoState { 597 return nil, &snap.NotInstalledError{Snap: instanceName} 598 } 599 if err != nil { 600 return nil, err 601 } 602 603 if err := CheckChangeConflict(st, instanceName, nil); err != nil { 604 return nil, err 605 } 606 607 snapName, instanceKey := snap.SplitInstanceName(instanceName) 608 snapsup := &SnapSetup{ 609 SideInfo: &snap.SideInfo{RealName: snapName}, 610 InstanceKey: instanceKey, 611 } 612 613 disableAll := st.NewTask("disable-aliases", fmt.Sprintf(i18n.G("Disable aliases for snap %q"), instanceName)) 614 disableAll.Set("snap-setup", &snapsup) 615 616 return state.NewTaskSet(disableAll), nil 617 } 618 619 // RemoveManualAlias removes a manual alias. 620 func RemoveManualAlias(st *state.State, alias string) (ts *state.TaskSet, instanceName string, err error) { 621 instanceName, err = findSnapOfManualAlias(st, alias) 622 if err != nil { 623 return nil, "", err 624 } 625 626 if err := CheckChangeConflict(st, instanceName, nil); err != nil { 627 return nil, "", err 628 } 629 630 snapName, instanceKey := snap.SplitInstanceName(instanceName) 631 snapsup := &SnapSetup{ 632 SideInfo: &snap.SideInfo{RealName: snapName}, 633 InstanceKey: instanceKey, 634 } 635 636 unalias := st.NewTask("unalias", fmt.Sprintf(i18n.G("Remove manual alias %q for snap %q"), alias, instanceName)) 637 unalias.Set("alias", alias) 638 unalias.Set("snap-setup", &snapsup) 639 640 return state.NewTaskSet(unalias), instanceName, nil 641 } 642 643 func findSnapOfManualAlias(st *state.State, alias string) (snapName string, err error) { 644 snapStates, err := All(st) 645 if err != nil { 646 return "", err 647 } 648 for instanceName, snapst := range snapStates { 649 target := snapst.Aliases[alias] 650 if target != nil && target.Manual != "" { 651 return instanceName, nil 652 } 653 } 654 return "", fmt.Errorf("cannot find manual alias %q in any snap", alias) 655 } 656 657 // manualUnalias returns newAliases with the manual alias removed from 658 // curAliases. 659 func manualUnalias(curAliases map[string]*AliasTarget, alias string) (newAliases map[string]*AliasTarget, err error) { 660 newTarget := curAliases[alias] 661 if newTarget == nil { 662 return nil, fmt.Errorf("no alias %q", alias) 663 } 664 newAliases = make(map[string]*AliasTarget, len(curAliases)) 665 for alias, aliasTarget := range curAliases { 666 newAliases[alias] = aliasTarget 667 } 668 669 if newTarget.Auto == "" { 670 delete(newAliases, alias) 671 } else { 672 newAliases[alias] = &AliasTarget{Auto: newTarget.Auto} 673 } 674 675 return newAliases, nil 676 } 677 678 // Prefer enables all aliases of a snap in preference to conflicting aliases 679 // of other snaps whose aliases will be disabled (removed for manual ones). 680 func Prefer(st *state.State, name string) (*state.TaskSet, error) { 681 var snapst SnapState 682 err := Get(st, name, &snapst) 683 if err == state.ErrNoState { 684 return nil, &snap.NotInstalledError{Snap: name} 685 } 686 if err != nil { 687 return nil, err 688 } 689 690 if err := CheckChangeConflict(st, name, nil); err != nil { 691 return nil, err 692 } 693 694 snapName, instanceKey := snap.SplitInstanceName(name) 695 snapsup := &SnapSetup{ 696 SideInfo: &snap.SideInfo{RealName: snapName}, 697 InstanceKey: instanceKey, 698 } 699 700 prefer := st.NewTask("prefer-aliases", fmt.Sprintf(i18n.G("Prefer aliases for snap %q"), name)) 701 prefer.Set("snap-setup", &snapsup) 702 703 return state.NewTaskSet(prefer), nil 704 }