github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/systemd/systemd.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2015 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 systemd 21 22 import ( 23 "bufio" 24 "bytes" 25 "errors" 26 "fmt" 27 "io" 28 "os" 29 "os/exec" 30 "path/filepath" 31 "regexp" 32 "strconv" 33 "strings" 34 "sync" 35 "sync/atomic" 36 "time" 37 38 _ "github.com/snapcore/squashfuse" 39 40 "github.com/snapcore/snapd/dirs" 41 "github.com/snapcore/snapd/osutil" 42 "github.com/snapcore/snapd/osutil/squashfs" 43 "github.com/snapcore/snapd/sandbox/selinux" 44 ) 45 46 var ( 47 // the output of "show" must match this for Stop to be done: 48 isStopDone = regexp.MustCompile(`(?m)\AActiveState=(?:failed|inactive)$`).Match 49 50 // how much time should Stop wait between calls to show 51 stopCheckDelay = 250 * time.Millisecond 52 53 // how much time should Stop wait between notifying the user of the waiting 54 stopNotifyDelay = 20 * time.Second 55 56 // daemonReloadLock is a package level lock to ensure that we 57 // do not run any `systemd daemon-reload` while a 58 // daemon-reload is in progress or a mount unit is 59 // generated/activated. 60 // 61 // See https://github.com/systemd/systemd/issues/10872 for the 62 // upstream systemd bug 63 daemonReloadLock extMutex 64 65 osutilIsMounted = osutil.IsMounted 66 ) 67 68 // mu is a sync.Mutex that also supports to check if the lock is taken 69 type extMutex struct { 70 lock sync.Mutex 71 muC int32 72 } 73 74 // Lock acquires the mutex 75 func (m *extMutex) Lock() { 76 m.lock.Lock() 77 atomic.AddInt32(&m.muC, 1) 78 } 79 80 // Unlock releases the mutex 81 func (m *extMutex) Unlock() { 82 atomic.AddInt32(&m.muC, -1) 83 m.lock.Unlock() 84 } 85 86 // Taken will panic with the given error message if the lock is not 87 // taken when this code runs. This is useful to internally check if 88 // something is accessed without a valid lock. 89 func (m *extMutex) Taken(errMsg string) { 90 if atomic.LoadInt32(&m.muC) != 1 { 91 panic("internal error: " + errMsg) 92 } 93 } 94 95 // systemctlCmd calls systemctl with the given args, returning its standard output (and wrapped error) 96 var systemctlCmd = func(args ...string) ([]byte, error) { 97 bs, err := exec.Command("systemctl", args...).CombinedOutput() 98 if err != nil { 99 exitCode, _ := osutil.ExitCode(err) 100 return nil, &Error{cmd: args, exitCode: exitCode, msg: bs} 101 } 102 103 return bs, nil 104 } 105 106 // MockSystemctl is called from the commands to actually call out to 107 // systemctl. It's exported so it can be overridden by testing. 108 func MockSystemctl(f func(args ...string) ([]byte, error)) func() { 109 oldSystemctlCmd := systemctlCmd 110 systemctlCmd = f 111 return func() { 112 systemctlCmd = oldSystemctlCmd 113 } 114 } 115 116 // MockStopDelays is used from tests so that Stop can be less 117 // forgiving there. 118 func MockStopDelays(checkDelay, notifyDelay time.Duration) func() { 119 oldCheckDelay := stopCheckDelay 120 oldNotifyDelay := stopNotifyDelay 121 stopCheckDelay = checkDelay 122 stopNotifyDelay = notifyDelay 123 return func() { 124 stopCheckDelay = oldCheckDelay 125 stopNotifyDelay = oldNotifyDelay 126 } 127 } 128 129 func Available() error { 130 _, err := systemctlCmd("--version") 131 return err 132 } 133 134 // Version returns systemd version. 135 func Version() (int, error) { 136 out, err := systemctlCmd("--version") 137 if err != nil { 138 return 0, err 139 } 140 141 // systemd version outpus is two lines - actual version and a list 142 // of features, e.g: 143 // systemd 229 144 // +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP ... 145 // 146 // The version string may have extra data (a case on newer ubuntu), e.g: 147 // systemd 245 (245.4-4ubuntu3) 148 r := bufio.NewScanner(bytes.NewReader(out)) 149 r.Split(bufio.ScanWords) 150 var verstr string 151 for i := 0; i < 2; i++ { 152 if !r.Scan() { 153 return 0, fmt.Errorf("cannot read systemd version: %v", r.Err()) 154 } 155 s := r.Text() 156 if i == 0 && s != "systemd" { 157 return 0, fmt.Errorf("cannot parse systemd version: expected \"systemd\", got %q", s) 158 } 159 if i == 1 { 160 verstr = strings.TrimSpace(s) 161 } 162 } 163 164 ver, err := strconv.Atoi(verstr) 165 if err != nil { 166 return 0, fmt.Errorf("cannot convert systemd version to number: %s", verstr) 167 } 168 return ver, nil 169 } 170 171 var osutilStreamCommand = osutil.StreamCommand 172 173 // jctl calls journalctl to get the JSON logs of the given services. 174 var jctl = func(svcs []string, n int, follow bool) (io.ReadCloser, error) { 175 // args will need two entries per service, plus a fixed number (give or take 176 // one) for the initial options. 177 args := make([]string, 0, 2*len(svcs)+6) // the fixed number is 6 178 args = append(args, "-o", "json", "--no-pager") // 3... 179 if n < 0 { 180 args = append(args, "--no-tail") // < 2 181 } else { 182 args = append(args, "-n", strconv.Itoa(n)) // ... + 2 ... 183 } 184 if follow { 185 args = append(args, "-f") // ... + 1 == 6 186 } 187 188 for i := range svcs { 189 args = append(args, "-u", svcs[i]) // this is why 2× 190 } 191 192 return osutilStreamCommand("journalctl", args...) 193 } 194 195 func MockJournalctl(f func(svcs []string, n int, follow bool) (io.ReadCloser, error)) func() { 196 oldJctl := jctl 197 jctl = f 198 return func() { 199 jctl = oldJctl 200 } 201 } 202 203 // Systemd exposes a minimal interface to manage systemd via the systemctl command. 204 type Systemd interface { 205 // DaemonReload reloads systemd's configuration. 206 DaemonReload() error 207 // DaemonRexec reexecutes systemd's system manager, should be 208 // only necessary to apply manager's configuration like 209 // watchdog. 210 DaemonReexec() error 211 // Enable the given service. 212 Enable(service string) error 213 // Disable the given service. 214 Disable(service string) error 215 // Start the given service or services. 216 Start(service ...string) error 217 // StartNoBlock starts the given service or services non-blocking. 218 StartNoBlock(service ...string) error 219 // Stop the given service, and wait until it has stopped. 220 Stop(service string, timeout time.Duration) error 221 // Kill all processes of the unit with the given signal. 222 Kill(service, signal, who string) error 223 // Restart the service, waiting for it to stop before starting it again. 224 Restart(service string, timeout time.Duration) error 225 // Reload or restart the service via 'systemctl reload-or-restart' 226 ReloadOrRestart(service string) error 227 // RestartAll restarts the given service using systemctl restart --all 228 RestartAll(service string) error 229 // Status fetches the status of given units. Statuses are 230 // returned in the same order as unit names passed in 231 // argument. 232 Status(units ...string) ([]*UnitStatus, error) 233 // IsEnabled checks whether the given service is enabled. 234 IsEnabled(service string) (bool, error) 235 // IsActive checks whether the given service is Active 236 IsActive(service string) (bool, error) 237 // LogReader returns a reader for the given services' log. 238 LogReader(services []string, n int, follow bool) (io.ReadCloser, error) 239 // AddMountUnitFile adds/enables/starts a mount unit. 240 AddMountUnitFile(name, revision, what, where, fstype string) (string, error) 241 // RemoveMountUnitFile unmounts/stops/disables/removes a mount unit. 242 RemoveMountUnitFile(baseDir string) error 243 // Mask the given service. 244 Mask(service string) error 245 // Unmask the given service. 246 Unmask(service string) error 247 } 248 249 // A Log is a single entry in the systemd journal 250 type Log map[string]string 251 252 const ( 253 // the default target for systemd units that we generate 254 ServicesTarget = "multi-user.target" 255 256 // the target prerequisite for systemd units we generate 257 PrerequisiteTarget = "network.target" 258 259 // the default target for systemd socket units that we generate 260 SocketsTarget = "sockets.target" 261 262 // the default target for systemd timer units that we generate 263 TimersTarget = "timers.target" 264 265 // the target for systemd user session units that we generate 266 UserServicesTarget = "default.target" 267 ) 268 269 type reporter interface { 270 Notify(string) 271 } 272 273 // New returns a Systemd that uses the default root directory and omits 274 // --root argument when executing systemctl. 275 func New(mode InstanceMode, rep reporter) Systemd { 276 return &systemd{mode: mode, reporter: rep} 277 } 278 279 // NewUnderRoot returns a Systemd that operates on the given rootdir. 280 func NewUnderRoot(rootDir string, mode InstanceMode, rep reporter) Systemd { 281 return &systemd{rootDir: rootDir, mode: mode, reporter: rep} 282 } 283 284 // NewEmulationMode returns a Systemd that runs in emulation mode where 285 // systemd is not really called, but instead its functions are emulated 286 // by other means. 287 func NewEmulationMode(rootDir string) Systemd { 288 if rootDir == "" { 289 rootDir = dirs.GlobalRootDir 290 } 291 return &emulation{ 292 rootDir: rootDir, 293 } 294 } 295 296 // InstanceMode determines which instance of systemd to control. 297 // 298 // SystemMode refers to the system instance (i.e. pid 1). UserMode 299 // refers to the instance launched to manage the user's desktop 300 // session. GlobalUserMode controls configuration respected by all 301 // user instances on the system. 302 // 303 // As GlobalUserMode does not refer to a single instance of systemd, 304 // some operations are not supported such as starting and stopping 305 // daemons. 306 type InstanceMode int 307 308 const ( 309 SystemMode InstanceMode = iota 310 UserMode 311 GlobalUserMode 312 ) 313 314 type systemd struct { 315 rootDir string 316 reporter reporter 317 mode InstanceMode 318 } 319 320 func (s *systemd) systemctl(args ...string) ([]byte, error) { 321 switch s.mode { 322 case SystemMode: 323 case UserMode: 324 args = append([]string{"--user"}, args...) 325 case GlobalUserMode: 326 args = append([]string{"--user", "--global"}, args...) 327 default: 328 panic("unknown InstanceMode") 329 } 330 return systemctlCmd(args...) 331 } 332 333 func (s *systemd) DaemonReload() error { 334 if s.mode == GlobalUserMode { 335 panic("cannot call daemon-reload with GlobalUserMode") 336 } 337 daemonReloadLock.Lock() 338 defer daemonReloadLock.Unlock() 339 340 return s.daemonReloadNoLock() 341 } 342 343 func (s *systemd) daemonReloadNoLock() error { 344 daemonReloadLock.Taken("cannot use daemon-reload without lock") 345 346 _, err := s.systemctl("daemon-reload") 347 return err 348 } 349 350 func (s *systemd) DaemonReexec() error { 351 if s.mode == GlobalUserMode { 352 panic("cannot call daemon-reexec with GlobalUserMode") 353 } 354 daemonReloadLock.Lock() 355 defer daemonReloadLock.Unlock() 356 357 _, err := s.systemctl("daemon-reexec") 358 return err 359 } 360 361 func (s *systemd) Enable(serviceName string) error { 362 var err error 363 if s.rootDir != "" { 364 _, err = s.systemctl("--root", s.rootDir, "enable", serviceName) 365 } else { 366 _, err = s.systemctl("enable", serviceName) 367 } 368 return err 369 } 370 371 func (s *systemd) Unmask(serviceName string) error { 372 var err error 373 if s.rootDir != "" { 374 _, err = s.systemctl("--root", s.rootDir, "unmask", serviceName) 375 } else { 376 _, err = s.systemctl("unmask", serviceName) 377 } 378 return err 379 } 380 381 func (s *systemd) Disable(serviceName string) error { 382 var err error 383 if s.rootDir != "" { 384 _, err = s.systemctl("--root", s.rootDir, "disable", serviceName) 385 } else { 386 _, err = s.systemctl("disable", serviceName) 387 } 388 return err 389 } 390 391 func (s *systemd) Mask(serviceName string) error { 392 var err error 393 if s.rootDir != "" { 394 _, err = s.systemctl("--root", s.rootDir, "mask", serviceName) 395 } else { 396 _, err = s.systemctl("mask", serviceName) 397 } 398 return err 399 } 400 401 func (s *systemd) Start(serviceNames ...string) error { 402 if s.mode == GlobalUserMode { 403 panic("cannot call start with GlobalUserMode") 404 } 405 _, err := s.systemctl(append([]string{"start"}, serviceNames...)...) 406 return err 407 } 408 409 func (s *systemd) StartNoBlock(serviceNames ...string) error { 410 if s.mode == GlobalUserMode { 411 panic("cannot call start with GlobalUserMode") 412 } 413 _, err := s.systemctl(append([]string{"start", "--no-block"}, serviceNames...)...) 414 return err 415 } 416 417 func (*systemd) LogReader(serviceNames []string, n int, follow bool) (io.ReadCloser, error) { 418 return jctl(serviceNames, n, follow) 419 } 420 421 var statusregex = regexp.MustCompile(`(?m)^(?:(.+?)=(.*)|(.*))?$`) 422 423 type UnitStatus struct { 424 Daemon string 425 UnitName string 426 Enabled bool 427 Active bool 428 } 429 430 var baseProperties = []string{"Id", "ActiveState", "UnitFileState"} 431 var extendedProperties = []string{"Id", "ActiveState", "UnitFileState", "Type"} 432 var unitProperties = map[string][]string{ 433 ".timer": baseProperties, 434 ".socket": baseProperties, 435 // in service units, Type is the daemon type 436 ".service": extendedProperties, 437 // in mount units, Type is the fs type 438 ".mount": extendedProperties, 439 } 440 441 func (s *systemd) getUnitStatus(properties []string, unitNames []string) ([]*UnitStatus, error) { 442 cmd := make([]string, len(unitNames)+2) 443 cmd[0] = "show" 444 // ask for all properties, regardless of unit type 445 cmd[1] = "--property=" + strings.Join(properties, ",") 446 copy(cmd[2:], unitNames) 447 bs, err := s.systemctl(cmd...) 448 if err != nil { 449 return nil, err 450 } 451 452 sts := make([]*UnitStatus, 0, len(unitNames)) 453 cur := &UnitStatus{} 454 seen := map[string]bool{} 455 456 for _, bs := range statusregex.FindAllSubmatch(bs, -1) { 457 if len(bs[0]) == 0 { 458 // systemctl separates data pertaining to particular services by an empty line 459 unitType := filepath.Ext(cur.UnitName) 460 expected := unitProperties[unitType] 461 if expected == nil { 462 expected = baseProperties 463 } 464 465 missing := make([]string, 0, len(expected)) 466 for _, k := range expected { 467 if !seen[k] { 468 missing = append(missing, k) 469 } 470 } 471 if len(missing) > 0 { 472 return nil, fmt.Errorf("cannot get unit %q status: missing %s in ‘systemctl show’ output", cur.UnitName, strings.Join(missing, ", ")) 473 } 474 sts = append(sts, cur) 475 if len(sts) > len(unitNames) { 476 break // wut 477 } 478 if cur.UnitName != unitNames[len(sts)-1] { 479 return nil, fmt.Errorf("cannot get unit status: queried status of %q but got status of %q", unitNames[len(sts)-1], cur.UnitName) 480 } 481 482 cur = &UnitStatus{} 483 seen = map[string]bool{} 484 continue 485 } 486 if len(bs[3]) > 0 { 487 return nil, fmt.Errorf("cannot get unit status: bad line %q in ‘systemctl show’ output", bs[3]) 488 } 489 k := string(bs[1]) 490 v := string(bs[2]) 491 492 if v == "" { 493 return nil, fmt.Errorf("cannot get unit status: empty field %q in ‘systemctl show’ output", k) 494 } 495 496 switch k { 497 case "Id": 498 cur.UnitName = v 499 case "Type": 500 cur.Daemon = v 501 case "ActiveState": 502 // made to match “systemctl is-active” behaviour, at least at systemd 229 503 cur.Active = v == "active" || v == "reloading" 504 case "UnitFileState": 505 // "static" means it can't be disabled 506 cur.Enabled = v == "enabled" || v == "static" 507 default: 508 return nil, fmt.Errorf("cannot get unit status: unexpected field %q in ‘systemctl show’ output", k) 509 } 510 511 if seen[k] { 512 return nil, fmt.Errorf("cannot get unit status: duplicate field %q in ‘systemctl show’ output", k) 513 } 514 seen[k] = true 515 } 516 517 if len(sts) != len(unitNames) { 518 return nil, fmt.Errorf("cannot get unit status: expected %d results, got %d", len(unitNames), len(sts)) 519 } 520 return sts, nil 521 } 522 523 func (s *systemd) Status(unitNames ...string) ([]*UnitStatus, error) { 524 if s.mode == GlobalUserMode { 525 panic("cannot call status with GlobalUserMode") 526 } 527 unitToStatus := make(map[string]*UnitStatus, len(unitNames)) 528 529 var limitedUnits []string 530 var extendedUnits []string 531 532 for _, name := range unitNames { 533 if strings.HasSuffix(name, ".timer") || strings.HasSuffix(name, ".socket") { 534 limitedUnits = append(limitedUnits, name) 535 } else { 536 extendedUnits = append(extendedUnits, name) 537 } 538 } 539 540 for _, set := range []struct { 541 units []string 542 properties []string 543 }{ 544 {units: extendedUnits, properties: extendedProperties}, 545 {units: limitedUnits, properties: baseProperties}, 546 } { 547 if len(set.units) == 0 { 548 continue 549 } 550 sts, err := s.getUnitStatus(set.properties, set.units) 551 if err != nil { 552 return nil, err 553 } 554 for _, status := range sts { 555 unitToStatus[status.UnitName] = status 556 } 557 } 558 559 // unpack to preserve the promised order 560 sts := make([]*UnitStatus, len(unitNames)) 561 for idx, name := range unitNames { 562 var ok bool 563 sts[idx], ok = unitToStatus[name] 564 if !ok { 565 return nil, fmt.Errorf("cannot determine status of unit %q", name) 566 } 567 } 568 569 return sts, nil 570 } 571 572 func (s *systemd) IsEnabled(serviceName string) (bool, error) { 573 var err error 574 if s.rootDir != "" { 575 _, err = s.systemctl("--root", s.rootDir, "is-enabled", serviceName) 576 } else { 577 _, err = s.systemctl("is-enabled", serviceName) 578 } 579 if err == nil { 580 return true, nil 581 } 582 // "systemctl is-enabled <name>" prints `disabled\n` to stderr and returns exit code 1 583 // for disabled services 584 sysdErr, ok := err.(systemctlError) 585 if ok && sysdErr.ExitCode() == 1 && strings.TrimSpace(string(sysdErr.Msg())) == "disabled" { 586 return false, nil 587 } 588 return false, err 589 } 590 591 func (s *systemd) IsActive(serviceName string) (bool, error) { 592 if s.mode == GlobalUserMode { 593 panic("cannot call is-active with GlobalUserMode") 594 } 595 var err error 596 if s.rootDir != "" { 597 _, err = s.systemctl("--root", s.rootDir, "is-active", serviceName) 598 } else { 599 _, err = s.systemctl("is-active", serviceName) 600 } 601 if err == nil { 602 return true, nil 603 } 604 // "systemctl is-active <name>" returns exit code 3 for inactive 605 // services, the stderr output may be `inactive\n` for services that are 606 // inactive (or not found), or `failed\n` for services that are in a 607 // failed state; nevertheless make sure to check any non-0 exit code 608 sysdErr, ok := err.(*Error) 609 if ok { 610 switch strings.TrimSpace(string(sysdErr.msg)) { 611 case "inactive", "failed": 612 return false, nil 613 } 614 } 615 return false, err 616 } 617 618 func (s *systemd) Stop(serviceName string, timeout time.Duration) error { 619 if s.mode == GlobalUserMode { 620 panic("cannot call stop with GlobalUserMode") 621 } 622 if _, err := s.systemctl("stop", serviceName); err != nil { 623 return err 624 } 625 626 // and now wait for it to actually stop 627 giveup := time.NewTimer(timeout) 628 notify := time.NewTicker(stopNotifyDelay) 629 defer notify.Stop() 630 check := time.NewTicker(stopCheckDelay) 631 defer check.Stop() 632 633 firstCheck := true 634 loop: 635 for { 636 select { 637 case <-giveup.C: 638 break loop 639 case <-check.C: 640 bs, err := s.systemctl("show", "--property=ActiveState", serviceName) 641 if err != nil { 642 return err 643 } 644 if isStopDone(bs) { 645 return nil 646 } 647 if !firstCheck { 648 continue loop 649 } 650 firstCheck = false 651 case <-notify.C: 652 } 653 // after notify delay or after a failed first check 654 s.reporter.Notify(fmt.Sprintf("Waiting for %s to stop.", serviceName)) 655 } 656 657 return &Timeout{action: "stop", service: serviceName} 658 } 659 660 func (s *systemd) Kill(serviceName, signal, who string) error { 661 if s.mode == GlobalUserMode { 662 panic("cannot call kill with GlobalUserMode") 663 } 664 if who == "" { 665 who = "all" 666 } 667 _, err := s.systemctl("kill", serviceName, "-s", signal, "--kill-who="+who) 668 return err 669 } 670 671 func (s *systemd) Restart(serviceName string, timeout time.Duration) error { 672 if s.mode == GlobalUserMode { 673 panic("cannot call restart with GlobalUserMode") 674 } 675 if err := s.Stop(serviceName, timeout); err != nil { 676 return err 677 } 678 return s.Start(serviceName) 679 } 680 681 func (s *systemd) RestartAll(serviceName string) error { 682 if s.mode == GlobalUserMode { 683 panic("cannot call restart with GlobalUserMode") 684 } 685 _, err := s.systemctl("restart", serviceName, "--all") 686 return err 687 } 688 689 type systemctlError interface { 690 Msg() []byte 691 ExitCode() int 692 Error() string 693 } 694 695 // Error is returned if the systemd action failed 696 type Error struct { 697 cmd []string 698 msg []byte 699 exitCode int 700 } 701 702 func (e *Error) Msg() []byte { 703 return e.msg 704 } 705 706 func (e *Error) ExitCode() int { 707 return e.exitCode 708 } 709 710 func (e *Error) Error() string { 711 return fmt.Sprintf("%v failed with exit status %d: %s", e.cmd, e.exitCode, e.msg) 712 } 713 714 // Timeout is returned if the systemd action failed to reach the 715 // expected state in a reasonable amount of time 716 type Timeout struct { 717 action string 718 service string 719 } 720 721 func (e *Timeout) Error() string { 722 return fmt.Sprintf("%v failed to %v: timeout", e.service, e.action) 723 } 724 725 // IsTimeout checks whether the given error is a Timeout 726 func IsTimeout(err error) bool { 727 _, isTimeout := err.(*Timeout) 728 return isTimeout 729 } 730 731 // Time returns the time the Log was received by the journal. 732 func (l Log) Time() (time.Time, error) { 733 sus, ok := l["__REALTIME_TIMESTAMP"] 734 if !ok { 735 return time.Time{}, errors.New("no timestamp") 736 } 737 // according to systemd.journal-fields(7) it's microseconds as a decimal string 738 us, err := strconv.ParseInt(sus, 10, 64) 739 if err != nil { 740 return time.Time{}, fmt.Errorf("timestamp not a decimal number: %#v", sus) 741 } 742 743 return time.Unix(us/1000000, 1000*(us%1000000)).UTC(), nil 744 } 745 746 // Message of the Log, if any; otherwise, "-". 747 func (l Log) Message() string { 748 if msg, ok := l["MESSAGE"]; ok { 749 return msg 750 } 751 752 return "-" 753 } 754 755 // SID is the syslog identifier of the Log, if any; otherwise, "-". 756 func (l Log) SID() string { 757 if sid, ok := l["SYSLOG_IDENTIFIER"]; ok { 758 return sid 759 } 760 761 return "-" 762 } 763 764 // PID is the pid of the client pid, if any; otherwise, "-". 765 func (l Log) PID() string { 766 if pid, ok := l["_PID"]; ok { 767 return pid 768 } 769 if pid, ok := l["SYSLOG_PID"]; ok { 770 return pid 771 } 772 773 return "-" 774 } 775 776 // MountUnitPath returns the path of a {,auto}mount unit 777 func MountUnitPath(baseDir string) string { 778 escapedPath := EscapeUnitNamePath(baseDir) 779 return filepath.Join(dirs.SnapServicesDir, escapedPath+".mount") 780 } 781 782 var squashfsFsType = squashfs.FsType 783 784 var mountUnitTemplate = `[Unit] 785 Description=Mount unit for %s, revision %s 786 Before=snapd.service 787 788 [Mount] 789 What=%s 790 Where=%s 791 Type=%s 792 Options=%s 793 LazyUnmount=yes 794 795 [Install] 796 WantedBy=multi-user.target 797 ` 798 799 func writeMountUnitFile(snapName, revision, what, where, fstype string, options []string) (mountUnitName string, err error) { 800 content := fmt.Sprintf(mountUnitTemplate, snapName, revision, what, where, fstype, strings.Join(options, ",")) 801 mu := MountUnitPath(where) 802 mountUnitName, err = filepath.Base(mu), osutil.AtomicWriteFile(mu, []byte(content), 0644, 0) 803 if err != nil { 804 return "", err 805 } 806 return mountUnitName, nil 807 } 808 809 func fsMountOptions(fstype string) []string { 810 options := []string{"nodev"} 811 if fstype == "squashfs" { 812 if selinux.ProbedLevel() != selinux.Unsupported { 813 if mountCtx := selinux.SnapMountContext(); mountCtx != "" { 814 options = append(options, "context="+mountCtx) 815 } 816 } 817 } 818 return options 819 } 820 821 // hostFsTypeAndMountOptions returns filesystem type and options to actually 822 // mount the given fstype at runtime, i.e. it determines if fuse should be used 823 // for squashfs. 824 func hostFsTypeAndMountOptions(fstype string) (hostFsType string, options []string) { 825 options = fsMountOptions(fstype) 826 hostFsType = fstype 827 if fstype == "squashfs" { 828 newFsType, newOptions := squashfsFsType() 829 options = append(options, newOptions...) 830 hostFsType = newFsType 831 } 832 return hostFsType, options 833 } 834 835 func (s *systemd) AddMountUnitFile(snapName, revision, what, where, fstype string) (string, error) { 836 daemonReloadLock.Lock() 837 defer daemonReloadLock.Unlock() 838 839 hostFsType, options := hostFsTypeAndMountOptions(fstype) 840 if osutil.IsDirectory(what) { 841 options = append(options, "bind") 842 hostFsType = "none" 843 } 844 mountUnitName, err := writeMountUnitFile(snapName, revision, what, where, hostFsType, options) 845 if err != nil { 846 return "", err 847 } 848 849 // we need to do a daemon-reload here to ensure that systemd really 850 // knows about this new mount unit file 851 if err := s.daemonReloadNoLock(); err != nil { 852 return "", err 853 } 854 855 if err := s.Enable(mountUnitName); err != nil { 856 return "", err 857 } 858 if err := s.Start(mountUnitName); err != nil { 859 return "", err 860 } 861 862 return mountUnitName, nil 863 } 864 865 func (s *systemd) RemoveMountUnitFile(mountedDir string) error { 866 daemonReloadLock.Lock() 867 defer daemonReloadLock.Unlock() 868 869 unit := MountUnitPath(dirs.StripRootDir(mountedDir)) 870 if !osutil.FileExists(unit) { 871 return nil 872 } 873 874 // use umount -d (cleanup loopback devices) -l (lazy) to ensure that even busy mount points 875 // can be unmounted. 876 // note that the long option --lazy is not supported on trusty. 877 // the explicit -d is only needed on trusty. 878 isMounted, err := osutilIsMounted(mountedDir) 879 if err != nil { 880 return err 881 } 882 if isMounted { 883 if output, err := exec.Command("umount", "-d", "-l", mountedDir).CombinedOutput(); err != nil { 884 return osutil.OutputErr(output, err) 885 } 886 887 if err := s.Stop(filepath.Base(unit), time.Duration(1*time.Second)); err != nil { 888 return err 889 } 890 } 891 if err := s.Disable(filepath.Base(unit)); err != nil { 892 return err 893 } 894 if err := os.Remove(unit); err != nil { 895 return err 896 } 897 // daemon-reload to ensure that systemd actually really 898 // forgets about this mount unit 899 if err := s.daemonReloadNoLock(); err != nil { 900 return err 901 } 902 903 return nil 904 } 905 906 func (s *systemd) ReloadOrRestart(serviceName string) error { 907 if s.mode == GlobalUserMode { 908 panic("cannot call restart with GlobalUserMode") 909 } 910 _, err := s.systemctl("reload-or-restart", serviceName) 911 return err 912 }