github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/systemd/systemd.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2021 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 "encoding/json" 26 "errors" 27 "fmt" 28 "io" 29 "os" 30 "os/exec" 31 "path/filepath" 32 "regexp" 33 "strconv" 34 "strings" 35 "sync" 36 "sync/atomic" 37 "time" 38 39 _ "github.com/snapcore/squashfuse" 40 41 "github.com/snapcore/snapd/dirs" 42 "github.com/snapcore/snapd/gadget/quantity" 43 "github.com/snapcore/snapd/osutil" 44 "github.com/snapcore/snapd/osutil/squashfs" 45 "github.com/snapcore/snapd/sandbox/selinux" 46 ) 47 48 var ( 49 // the output of "show" must match this for Stop to be done: 50 isStopDone = regexp.MustCompile(`(?m)\AActiveState=(?:failed|inactive)$`).Match 51 52 // how much time should Stop wait between calls to show 53 stopCheckDelay = 250 * time.Millisecond 54 55 // how much time should Stop wait between notifying the user of the waiting 56 stopNotifyDelay = 20 * time.Second 57 58 // daemonReloadLock is a package level lock to ensure that we 59 // do not run any `systemd daemon-reload` while a 60 // daemon-reload is in progress or a mount unit is 61 // generated/activated. 62 // 63 // See https://github.com/systemd/systemd/issues/10872 for the 64 // upstream systemd bug 65 daemonReloadLock extMutex 66 67 osutilIsMounted = osutil.IsMounted 68 ) 69 70 // mu is a sync.Mutex that also supports to check if the lock is taken 71 type extMutex struct { 72 lock sync.Mutex 73 muC int32 74 } 75 76 // Lock acquires the mutex 77 func (m *extMutex) Lock() { 78 m.lock.Lock() 79 atomic.AddInt32(&m.muC, 1) 80 } 81 82 // Unlock releases the mutex 83 func (m *extMutex) Unlock() { 84 atomic.AddInt32(&m.muC, -1) 85 m.lock.Unlock() 86 } 87 88 // Taken will panic with the given error message if the lock is not 89 // taken when this code runs. This is useful to internally check if 90 // something is accessed without a valid lock. 91 func (m *extMutex) Taken(errMsg string) { 92 if atomic.LoadInt32(&m.muC) != 1 { 93 panic("internal error: " + errMsg) 94 } 95 } 96 97 // systemctlCmd calls systemctl with the given args, returning its standard output (and wrapped error) 98 var systemctlCmd = func(args ...string) ([]byte, error) { 99 // TODO: including stderr here breaks many things when systemd is in debug 100 // output mode, see LP #1885597 101 bs, err := exec.Command("systemctl", args...).CombinedOutput() 102 if err != nil { 103 exitCode, runErr := osutil.ExitCode(err) 104 return nil, &Error{cmd: args, exitCode: exitCode, runErr: runErr, msg: bs} 105 } 106 107 return bs, nil 108 } 109 110 // MockSystemctl is called from the commands to actually call out to 111 // systemctl. It's exported so it can be overridden by testing. 112 func MockSystemctl(f func(args ...string) ([]byte, error)) func() { 113 oldSystemctlCmd := systemctlCmd 114 systemctlCmd = f 115 return func() { 116 systemctlCmd = oldSystemctlCmd 117 } 118 } 119 120 // MockStopDelays is used from tests so that Stop can be less 121 // forgiving there. 122 func MockStopDelays(checkDelay, notifyDelay time.Duration) func() { 123 oldCheckDelay := stopCheckDelay 124 oldNotifyDelay := stopNotifyDelay 125 stopCheckDelay = checkDelay 126 stopNotifyDelay = notifyDelay 127 return func() { 128 stopCheckDelay = oldCheckDelay 129 stopNotifyDelay = oldNotifyDelay 130 } 131 } 132 133 func Available() error { 134 _, err := systemctlCmd("--version") 135 return err 136 } 137 138 // Version returns systemd version. 139 func Version() (int, error) { 140 out, err := systemctlCmd("--version") 141 if err != nil { 142 return 0, err 143 } 144 145 // systemd version outpus is two lines - actual version and a list 146 // of features, e.g: 147 // systemd 229 148 // +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP ... 149 // 150 // The version string may have extra data (a case on newer ubuntu), e.g: 151 // systemd 245 (245.4-4ubuntu3) 152 r := bufio.NewScanner(bytes.NewReader(out)) 153 r.Split(bufio.ScanWords) 154 var verstr string 155 for i := 0; i < 2; i++ { 156 if !r.Scan() { 157 return 0, fmt.Errorf("cannot read systemd version: %v", r.Err()) 158 } 159 s := r.Text() 160 if i == 0 && s != "systemd" { 161 return 0, fmt.Errorf("cannot parse systemd version: expected \"systemd\", got %q", s) 162 } 163 if i == 1 { 164 verstr = strings.TrimSpace(s) 165 } 166 } 167 168 ver, err := strconv.Atoi(verstr) 169 if err != nil { 170 return 0, fmt.Errorf("cannot convert systemd version to number: %s", verstr) 171 } 172 return ver, nil 173 } 174 175 var osutilStreamCommand = osutil.StreamCommand 176 177 // jctl calls journalctl to get the JSON logs of the given services. 178 var jctl = func(svcs []string, n int, follow bool) (io.ReadCloser, error) { 179 // args will need two entries per service, plus a fixed number (give or take 180 // one) for the initial options. 181 args := make([]string, 0, 2*len(svcs)+6) // the fixed number is 6 182 args = append(args, "-o", "json", "--no-pager") // 3... 183 if n < 0 { 184 args = append(args, "--no-tail") // < 2 185 } else { 186 args = append(args, "-n", strconv.Itoa(n)) // ... + 2 ... 187 } 188 if follow { 189 args = append(args, "-f") // ... + 1 == 6 190 } 191 192 for i := range svcs { 193 args = append(args, "-u", svcs[i]) // this is why 2× 194 } 195 196 return osutilStreamCommand("journalctl", args...) 197 } 198 199 func MockJournalctl(f func(svcs []string, n int, follow bool) (io.ReadCloser, error)) func() { 200 oldJctl := jctl 201 jctl = f 202 return func() { 203 jctl = oldJctl 204 } 205 } 206 207 // Systemd exposes a minimal interface to manage systemd via the systemctl command. 208 type Systemd interface { 209 // DaemonReload reloads systemd's configuration. 210 DaemonReload() error 211 // DaemonRexec reexecutes systemd's system manager, should be 212 // only necessary to apply manager's configuration like 213 // watchdog. 214 DaemonReexec() error 215 // Enable the given service. 216 Enable(service string) error 217 // Disable the given service. 218 Disable(service string) error 219 // Start the given service or services. 220 Start(service ...string) error 221 // StartNoBlock starts the given service or services non-blocking. 222 StartNoBlock(service ...string) error 223 // Stop the given service, and wait until it has stopped. 224 Stop(service string, timeout time.Duration) error 225 // Kill all processes of the unit with the given signal. 226 Kill(service, signal, who string) error 227 // Restart the service, waiting for it to stop before starting it again. 228 Restart(service string, timeout time.Duration) error 229 // Reload or restart the service via 'systemctl reload-or-restart' 230 ReloadOrRestart(service string) error 231 // RestartAll restarts the given service using systemctl restart --all 232 RestartAll(service string) error 233 // Status fetches the status of given units. Statuses are 234 // returned in the same order as unit names passed in 235 // argument. 236 Status(units ...string) ([]*UnitStatus, error) 237 // InactiveEnterTimestamp returns the time that the given unit entered the 238 // inactive state as defined by the systemd docs. Specifically, this time is 239 // the most recent time in which the unit transitioned from deactivating 240 // ("Stopping") to dead ("Stopped"). It may be the zero time if this has 241 // never happened during the current boot, since this property is only 242 // tracked during the current boot. It specifically does not return a time 243 // that is monotonic, so the time returned here may be subject to bugs if 244 // there was a discontinuous time jump on the system before or during the 245 // unit's transition to inactive. 246 // TODO: incorporate this result into Status instead? 247 InactiveEnterTimestamp(unit string) (time.Time, error) 248 // IsEnabled checks whether the given service is enabled. 249 IsEnabled(service string) (bool, error) 250 // IsActive checks whether the given service is Active 251 IsActive(service string) (bool, error) 252 // LogReader returns a reader for the given services' log. 253 LogReader(services []string, n int, follow bool) (io.ReadCloser, error) 254 // AddMountUnitFile adds/enables/starts a mount unit. 255 AddMountUnitFile(name, revision, what, where, fstype string) (string, error) 256 // RemoveMountUnitFile unmounts/stops/disables/removes a mount unit. 257 RemoveMountUnitFile(baseDir string) error 258 // Mask the given service. 259 Mask(service string) error 260 // Unmask the given service. 261 Unmask(service string) error 262 // Mount requests a mount of what under where with options. 263 Mount(what, where string, options ...string) error 264 // Umount requests a mount from what or at where to be unmounted. 265 Umount(whatOrWhere string) error 266 // CurrentMemoryUsage returns the current memory usage for the specified 267 // unit. 268 CurrentMemoryUsage(unit string) (quantity.Size, error) 269 // CurrentTasksCount returns the number of tasks (processes, threads, kernel 270 // threads if enabled, etc) part of the unit, which can be a service or a 271 // slice. 272 CurrentTasksCount(unit string) (uint64, error) 273 } 274 275 // A Log is a single entry in the systemd journal. 276 // In almost all cases, the strings map to a single string value, but as per the 277 // manpage for journalctl, under the json format, 278 // 279 // Journal entries permit non-unique fields within the same log entry. JSON 280 // does not allow non-unique fields within objects. Due to this, if a 281 // non-unique field is encountered a JSON array is used as field value, 282 // listing all field values as elements. 283 // 284 // and this snippet as well, 285 // 286 // Fields containing non-printable or non-UTF8 bytes are 287 // encoded as arrays containing the raw bytes individually 288 // formatted as unsigned numbers. 289 // 290 // as such, we sometimes get array values which need to be handled differently, 291 // so we manually try to decode the json for each message into different types. 292 type Log map[string]*json.RawMessage 293 294 const ( 295 // the default target for systemd units that we generate 296 ServicesTarget = "multi-user.target" 297 298 // the target prerequisite for systemd units we generate 299 PrerequisiteTarget = "network.target" 300 301 // the default target for systemd socket units that we generate 302 SocketsTarget = "sockets.target" 303 304 // the default target for systemd timer units that we generate 305 TimersTarget = "timers.target" 306 307 // the target for systemd user session units that we generate 308 UserServicesTarget = "default.target" 309 ) 310 311 type reporter interface { 312 Notify(string) 313 } 314 315 // New returns a Systemd that uses the default root directory and omits 316 // --root argument when executing systemctl. 317 func New(mode InstanceMode, rep reporter) Systemd { 318 return &systemd{mode: mode, reporter: rep} 319 } 320 321 // NewUnderRoot returns a Systemd that operates on the given rootdir. 322 func NewUnderRoot(rootDir string, mode InstanceMode, rep reporter) Systemd { 323 return &systemd{rootDir: rootDir, mode: mode, reporter: rep} 324 } 325 326 // NewEmulationMode returns a Systemd that runs in emulation mode where 327 // systemd is not really called, but instead its functions are emulated 328 // by other means. 329 func NewEmulationMode(rootDir string) Systemd { 330 if rootDir == "" { 331 rootDir = dirs.GlobalRootDir 332 } 333 return &emulation{ 334 rootDir: rootDir, 335 } 336 } 337 338 // InstanceMode determines which instance of systemd to control. 339 // 340 // SystemMode refers to the system instance (i.e. pid 1). UserMode 341 // refers to the instance launched to manage the user's desktop 342 // session. GlobalUserMode controls configuration respected by all 343 // user instances on the system. 344 // 345 // As GlobalUserMode does not refer to a single instance of systemd, 346 // some operations are not supported such as starting and stopping 347 // daemons. 348 type InstanceMode int 349 350 const ( 351 SystemMode InstanceMode = iota 352 UserMode 353 GlobalUserMode 354 ) 355 356 type systemd struct { 357 rootDir string 358 reporter reporter 359 mode InstanceMode 360 } 361 362 func (s *systemd) systemctl(args ...string) ([]byte, error) { 363 switch s.mode { 364 case SystemMode: 365 case UserMode: 366 args = append([]string{"--user"}, args...) 367 case GlobalUserMode: 368 args = append([]string{"--user", "--global"}, args...) 369 default: 370 panic("unknown InstanceMode") 371 } 372 return systemctlCmd(args...) 373 } 374 375 func (s *systemd) DaemonReload() error { 376 if s.mode == GlobalUserMode { 377 panic("cannot call daemon-reload with GlobalUserMode") 378 } 379 daemonReloadLock.Lock() 380 defer daemonReloadLock.Unlock() 381 382 return s.daemonReloadNoLock() 383 } 384 385 func (s *systemd) daemonReloadNoLock() error { 386 daemonReloadLock.Taken("cannot use daemon-reload without lock") 387 388 _, err := s.systemctl("daemon-reload") 389 return err 390 } 391 392 func (s *systemd) DaemonReexec() error { 393 if s.mode == GlobalUserMode { 394 panic("cannot call daemon-reexec with GlobalUserMode") 395 } 396 daemonReloadLock.Lock() 397 defer daemonReloadLock.Unlock() 398 399 _, err := s.systemctl("daemon-reexec") 400 return err 401 } 402 403 func (s *systemd) Enable(serviceName string) error { 404 var err error 405 if s.rootDir != "" { 406 _, err = s.systemctl("--root", s.rootDir, "enable", serviceName) 407 } else { 408 _, err = s.systemctl("enable", serviceName) 409 } 410 return err 411 } 412 413 func (s *systemd) Unmask(serviceName string) error { 414 var err error 415 if s.rootDir != "" { 416 _, err = s.systemctl("--root", s.rootDir, "unmask", serviceName) 417 } else { 418 _, err = s.systemctl("unmask", serviceName) 419 } 420 return err 421 } 422 423 func (s *systemd) Disable(serviceName string) error { 424 var err error 425 if s.rootDir != "" { 426 _, err = s.systemctl("--root", s.rootDir, "disable", serviceName) 427 } else { 428 _, err = s.systemctl("disable", serviceName) 429 } 430 return err 431 } 432 433 func (s *systemd) Mask(serviceName string) error { 434 var err error 435 if s.rootDir != "" { 436 _, err = s.systemctl("--root", s.rootDir, "mask", serviceName) 437 } else { 438 _, err = s.systemctl("mask", serviceName) 439 } 440 return err 441 } 442 443 func (s *systemd) Start(serviceNames ...string) error { 444 if s.mode == GlobalUserMode { 445 panic("cannot call start with GlobalUserMode") 446 } 447 _, err := s.systemctl(append([]string{"start"}, serviceNames...)...) 448 return err 449 } 450 451 func (s *systemd) StartNoBlock(serviceNames ...string) error { 452 if s.mode == GlobalUserMode { 453 panic("cannot call start with GlobalUserMode") 454 } 455 _, err := s.systemctl(append([]string{"start", "--no-block"}, serviceNames...)...) 456 return err 457 } 458 459 func (*systemd) LogReader(serviceNames []string, n int, follow bool) (io.ReadCloser, error) { 460 return jctl(serviceNames, n, follow) 461 } 462 463 var statusregex = regexp.MustCompile(`(?m)^(?:(.+?)=(.*)|(.*))?$`) 464 465 type UnitStatus struct { 466 Daemon string 467 UnitName string 468 Enabled bool 469 Active bool 470 // Installed is false if the queried unit doesn't exist. 471 Installed bool 472 } 473 474 var baseProperties = []string{"Id", "ActiveState", "UnitFileState"} 475 var extendedProperties = []string{"Id", "ActiveState", "UnitFileState", "Type"} 476 var unitProperties = map[string][]string{ 477 ".timer": baseProperties, 478 ".socket": baseProperties, 479 // in service units, Type is the daemon type 480 ".service": extendedProperties, 481 // in mount units, Type is the fs type 482 ".mount": extendedProperties, 483 } 484 485 func (s *systemd) getUnitStatus(properties []string, unitNames []string) ([]*UnitStatus, error) { 486 cmd := make([]string, len(unitNames)+2) 487 cmd[0] = "show" 488 // ask for all properties, regardless of unit type 489 cmd[1] = "--property=" + strings.Join(properties, ",") 490 copy(cmd[2:], unitNames) 491 bs, err := s.systemctl(cmd...) 492 if err != nil { 493 return nil, err 494 } 495 496 sts := make([]*UnitStatus, 0, len(unitNames)) 497 cur := &UnitStatus{} 498 seen := map[string]bool{} 499 500 for _, bs := range statusregex.FindAllSubmatch(bs, -1) { 501 if len(bs[0]) == 0 { 502 // systemctl separates data pertaining to particular services by an empty line 503 unitType := filepath.Ext(cur.UnitName) 504 expected := unitProperties[unitType] 505 if expected == nil { 506 expected = baseProperties 507 } 508 509 missing := make([]string, 0, len(expected)) 510 for _, k := range expected { 511 if !seen[k] { 512 missing = append(missing, k) 513 } 514 } 515 if len(missing) > 0 { 516 return nil, fmt.Errorf("cannot get unit %q status: missing %s in ‘systemctl show’ output", cur.UnitName, strings.Join(missing, ", ")) 517 } 518 sts = append(sts, cur) 519 if len(sts) > len(unitNames) { 520 break // wut 521 } 522 if cur.UnitName != unitNames[len(sts)-1] { 523 return nil, fmt.Errorf("cannot get unit status: queried status of %q but got status of %q", unitNames[len(sts)-1], cur.UnitName) 524 } 525 526 cur = &UnitStatus{} 527 seen = map[string]bool{} 528 continue 529 } 530 if len(bs[3]) > 0 { 531 return nil, fmt.Errorf("cannot get unit status: bad line %q in ‘systemctl show’ output", bs[3]) 532 } 533 k := string(bs[1]) 534 v := string(bs[2]) 535 536 if v == "" && k != "UnitFileState" && k != "Type" { 537 return nil, fmt.Errorf("cannot get unit status: empty field %q in ‘systemctl show’ output", k) 538 } 539 540 switch k { 541 case "Id": 542 cur.UnitName = v 543 case "Type": 544 cur.Daemon = v 545 case "ActiveState": 546 // made to match “systemctl is-active” behaviour, at least at systemd 229 547 cur.Active = v == "active" || v == "reloading" 548 case "UnitFileState": 549 // "static" means it can't be disabled 550 cur.Enabled = v == "enabled" || v == "static" 551 cur.Installed = v != "" 552 default: 553 return nil, fmt.Errorf("cannot get unit status: unexpected field %q in ‘systemctl show’ output", k) 554 } 555 556 if seen[k] { 557 return nil, fmt.Errorf("cannot get unit status: duplicate field %q in ‘systemctl show’ output", k) 558 } 559 seen[k] = true 560 } 561 562 if len(sts) != len(unitNames) { 563 return nil, fmt.Errorf("cannot get unit status: expected %d results, got %d", len(unitNames), len(sts)) 564 } 565 return sts, nil 566 } 567 568 func (s *systemd) getGlobalUserStatus(unitNames ...string) ([]*UnitStatus, error) { 569 // As there is one instance per user, the active state does 570 // not make sense. We can determine the global "enabled" 571 // state of the services though. 572 cmd := append([]string{"is-enabled"}, unitNames...) 573 if s.rootDir != "" { 574 cmd = append([]string{"--root", s.rootDir}, cmd...) 575 } 576 bs, err := s.systemctl(cmd...) 577 if err != nil { 578 // is-enabled returns non-zero if no units are 579 // enabled. We still need to examine the output to 580 // track the other units. 581 sysdErr := err.(systemctlError) 582 bs = sysdErr.Msg() 583 } 584 585 results := bytes.Split(bytes.Trim(bs, "\n"), []byte("\n")) 586 if len(results) != len(unitNames) { 587 return nil, fmt.Errorf("cannot get enabled status of services: expected %d results, got %d", len(unitNames), len(results)) 588 } 589 590 sts := make([]*UnitStatus, len(unitNames)) 591 for i, line := range results { 592 sts[i] = &UnitStatus{ 593 UnitName: unitNames[i], 594 Enabled: bytes.Equal(line, []byte("enabled")) || bytes.Equal(line, []byte("static")), 595 } 596 } 597 return sts, nil 598 } 599 600 func (s *systemd) getPropertyStringValue(unit, key string) (string, error) { 601 // XXX: ignore stderr of systemctl command to avoid further infractions 602 // around LP #1885597 603 out, err := s.systemctl("show", "--property", key, unit) 604 if err != nil { 605 return "", osutil.OutputErr(out, err) 606 } 607 cleanVal := strings.TrimSpace(string(out)) 608 609 // strip the <property>= from the output 610 splitVal := strings.SplitN(cleanVal, "=", 2) 611 if len(splitVal) != 2 { 612 return "", fmt.Errorf("invalid property format from systemd for %s (got %s)", key, cleanVal) 613 } 614 615 return strings.TrimSpace(splitVal[1]), nil 616 } 617 618 var errNotSet = errors.New("property value is not available") 619 620 func (s *systemd) getPropertyUintValue(unit, key string) (uint64, error) { 621 valStr, err := s.getPropertyStringValue(unit, key) 622 if err != nil { 623 return 0, err 624 } 625 626 // if the unit is inactive or doesn't exist, the value can be reported as 627 // "[not set]" 628 if valStr == "[not set]" { 629 return 0, errNotSet 630 } 631 632 intVal, err := strconv.ParseUint(valStr, 10, 64) 633 if err != nil { 634 return 0, fmt.Errorf("invalid property value from systemd for %s: cannot parse %q as an integer", key, valStr) 635 } 636 637 return intVal, nil 638 } 639 640 func (s *systemd) CurrentTasksCount(unit string) (uint64, error) { 641 tasksCount, err := s.getPropertyUintValue(unit, "TasksCurrent") 642 if err != nil && err != errNotSet { 643 return 0, err 644 } 645 646 if err == errNotSet { 647 return 0, fmt.Errorf("tasks count unavailable") 648 } 649 650 return tasksCount, nil 651 } 652 653 func (s *systemd) CurrentMemoryUsage(unit string) (quantity.Size, error) { 654 memBytes, err := s.getPropertyUintValue(unit, "MemoryCurrent") 655 if err != nil && err != errNotSet { 656 return 0, err 657 } 658 659 if err == errNotSet { 660 return 0, fmt.Errorf("memory usage unavailable") 661 } 662 663 return quantity.Size(memBytes), nil 664 } 665 666 func (s *systemd) InactiveEnterTimestamp(unit string) (time.Time, error) { 667 timeStr, err := s.getPropertyStringValue(unit, "InactiveEnterTimestamp") 668 if err != nil { 669 return time.Time{}, err 670 } 671 672 if timeStr == "" { 673 return time.Time{}, nil 674 } 675 676 // finally parse the time string 677 inactiveEnterTime, err := time.Parse("Mon 2006-01-02 15:04:05 MST", timeStr) 678 if err != nil { 679 return time.Time{}, fmt.Errorf("internal error: systemctl time output (%s) is malformed", timeStr) 680 } 681 return inactiveEnterTime, nil 682 } 683 684 func (s *systemd) Status(unitNames ...string) ([]*UnitStatus, error) { 685 if s.mode == GlobalUserMode { 686 return s.getGlobalUserStatus(unitNames...) 687 } 688 unitToStatus := make(map[string]*UnitStatus, len(unitNames)) 689 690 var limitedUnits []string 691 var extendedUnits []string 692 693 for _, name := range unitNames { 694 if strings.HasSuffix(name, ".timer") || strings.HasSuffix(name, ".socket") { 695 limitedUnits = append(limitedUnits, name) 696 } else { 697 extendedUnits = append(extendedUnits, name) 698 } 699 } 700 701 for _, set := range []struct { 702 units []string 703 properties []string 704 }{ 705 {units: extendedUnits, properties: extendedProperties}, 706 {units: limitedUnits, properties: baseProperties}, 707 } { 708 if len(set.units) == 0 { 709 continue 710 } 711 sts, err := s.getUnitStatus(set.properties, set.units) 712 if err != nil { 713 return nil, err 714 } 715 for _, status := range sts { 716 unitToStatus[status.UnitName] = status 717 } 718 } 719 720 // unpack to preserve the promised order 721 sts := make([]*UnitStatus, len(unitNames)) 722 for idx, name := range unitNames { 723 var ok bool 724 sts[idx], ok = unitToStatus[name] 725 if !ok { 726 return nil, fmt.Errorf("cannot determine status of unit %q", name) 727 } 728 } 729 730 return sts, nil 731 } 732 733 func (s *systemd) IsEnabled(serviceName string) (bool, error) { 734 var err error 735 if s.rootDir != "" { 736 _, err = s.systemctl("--root", s.rootDir, "is-enabled", serviceName) 737 } else { 738 _, err = s.systemctl("is-enabled", serviceName) 739 } 740 if err == nil { 741 return true, nil 742 } 743 // "systemctl is-enabled <name>" prints `disabled\n` to stderr and returns exit code 1 744 // for disabled services 745 sysdErr, ok := err.(systemctlError) 746 if ok && sysdErr.ExitCode() == 1 && strings.TrimSpace(string(sysdErr.Msg())) == "disabled" { 747 return false, nil 748 } 749 return false, err 750 } 751 752 func (s *systemd) IsActive(serviceName string) (bool, error) { 753 if s.mode == GlobalUserMode { 754 panic("cannot call is-active with GlobalUserMode") 755 } 756 var err error 757 if s.rootDir != "" { 758 _, err = s.systemctl("--root", s.rootDir, "is-active", serviceName) 759 } else { 760 _, err = s.systemctl("is-active", serviceName) 761 } 762 if err == nil { 763 return true, nil 764 } 765 // "systemctl is-active <name>" returns exit code 3 for inactive services, 766 // the stderr output may be `unknown\n` for services that were not found, 767 // `inactive\n` for services that are inactive (or not found for some 768 // systemd versions), or `failed\n` for services that are in a failed state; 769 // nevertheless make sure to check any non-0 exit code 770 sysdErr, ok := err.(systemctlError) 771 if ok { 772 switch strings.TrimSpace(string(sysdErr.Msg())) { 773 case "inactive", "failed", "unknown": 774 return false, nil 775 } 776 } 777 return false, err 778 } 779 780 func (s *systemd) Stop(serviceName string, timeout time.Duration) error { 781 if s.mode == GlobalUserMode { 782 panic("cannot call stop with GlobalUserMode") 783 } 784 if _, err := s.systemctl("stop", serviceName); err != nil { 785 return err 786 } 787 788 // and now wait for it to actually stop 789 giveup := time.NewTimer(timeout) 790 notify := time.NewTicker(stopNotifyDelay) 791 defer notify.Stop() 792 check := time.NewTicker(stopCheckDelay) 793 defer check.Stop() 794 795 firstCheck := true 796 loop: 797 for { 798 select { 799 case <-giveup.C: 800 break loop 801 case <-check.C: 802 bs, err := s.systemctl("show", "--property=ActiveState", serviceName) 803 if err != nil { 804 return err 805 } 806 if isStopDone(bs) { 807 return nil 808 } 809 if !firstCheck { 810 continue loop 811 } 812 firstCheck = false 813 case <-notify.C: 814 } 815 // after notify delay or after a failed first check 816 s.reporter.Notify(fmt.Sprintf("Waiting for %s to stop.", serviceName)) 817 } 818 819 return &Timeout{action: "stop", service: serviceName} 820 } 821 822 func (s *systemd) Kill(serviceName, signal, who string) error { 823 if s.mode == GlobalUserMode { 824 panic("cannot call kill with GlobalUserMode") 825 } 826 if who == "" { 827 who = "all" 828 } 829 _, err := s.systemctl("kill", serviceName, "-s", signal, "--kill-who="+who) 830 return err 831 } 832 833 func (s *systemd) Restart(serviceName string, timeout time.Duration) error { 834 if s.mode == GlobalUserMode { 835 panic("cannot call restart with GlobalUserMode") 836 } 837 if err := s.Stop(serviceName, timeout); err != nil { 838 return err 839 } 840 return s.Start(serviceName) 841 } 842 843 func (s *systemd) RestartAll(serviceName string) error { 844 if s.mode == GlobalUserMode { 845 panic("cannot call restart with GlobalUserMode") 846 } 847 _, err := s.systemctl("restart", serviceName, "--all") 848 return err 849 } 850 851 type systemctlError interface { 852 Msg() []byte 853 ExitCode() int 854 Error() string 855 } 856 857 // Error is returned if the systemd action failed 858 type Error struct { 859 cmd []string 860 msg []byte 861 exitCode int 862 runErr error 863 } 864 865 func (e *Error) Msg() []byte { 866 return e.msg 867 } 868 869 func (e *Error) ExitCode() int { 870 return e.exitCode 871 } 872 873 func (e *Error) Error() string { 874 var msg string 875 if len(e.msg) > 0 { 876 msg = fmt.Sprintf(": %s", e.msg) 877 } 878 if e.runErr != nil { 879 return fmt.Sprintf("systemctl command %v failed with: %v%s", e.cmd, e.runErr, msg) 880 } 881 return fmt.Sprintf("systemctl command %v failed with exit status %d%s", e.cmd, e.exitCode, msg) 882 } 883 884 // Timeout is returned if the systemd action failed to reach the 885 // expected state in a reasonable amount of time 886 type Timeout struct { 887 action string 888 service string 889 } 890 891 func (e *Timeout) Error() string { 892 return fmt.Sprintf("%v failed to %v: timeout", e.service, e.action) 893 } 894 895 // IsTimeout checks whether the given error is a Timeout 896 func IsTimeout(err error) bool { 897 _, isTimeout := err.(*Timeout) 898 return isTimeout 899 } 900 901 func (l Log) parseLogRawMessageString(key string, sliceHandler func([]string) (string, error)) (string, error) { 902 valObject, ok := l[key] 903 if !ok { 904 // NOTE: journalctl says that sometimes if a json string would be too 905 // large null is returned, so we may miss a message here 906 return "", fmt.Errorf("key %q missing from message", key) 907 } 908 if valObject == nil { 909 // NOTE: journalctl says that sometimes if a json string would be too 910 // large null is returned, so in this case the message may be truncated 911 return "", fmt.Errorf("message key %q truncated", key) 912 } 913 914 // first try normal string 915 s := "" 916 err := json.Unmarshal(*valObject, &s) 917 if err == nil { 918 return s, nil 919 } 920 921 // next up, try a list of bytes that is utf-8 next, this is the case if 922 // journald thinks the output is not valid utf-8 or is not printable ascii 923 b := []byte{} 924 err = json.Unmarshal(*valObject, &b) 925 if err == nil { 926 // we have an array of bytes here, and there is a chance that it is 927 // not valid utf-8, but since this feature is used in snapd to present 928 // user-facing messages, we simply let Go do its best to turn the bytes 929 // into a string, with the chance that some byte sequences that are 930 // invalid end up getting replaced with Go's hex encoding of the byte 931 // sequence. 932 // Programs that are concerned with reading the exact sequence of 933 // characters or binary data, etc. should probably talk to journald 934 // directly instead of going through snapd using this API. 935 return string(b), nil 936 } 937 938 // next, try slice of slices of bytes 939 bb := [][]byte{} 940 err = json.Unmarshal(*valObject, &bb) 941 if err == nil { 942 // turn the slice of slices of bytes into a slice of strings to call the 943 // handler on it, see above about how invalid utf8 bytes are handled 944 l := make([]string, 0, len(bb)) 945 for _, r := range bb { 946 l = append(l, string(r)) 947 } 948 return sliceHandler(l) 949 } 950 951 // finally try list of strings 952 stringSlice := []string{} 953 err = json.Unmarshal(*valObject, &stringSlice) 954 if err == nil { 955 // if the slice is of length 1, just promote it to a plain scalar string 956 if len(stringSlice) == 1 { 957 return stringSlice[0], nil 958 } 959 // otherwise let the caller handle it 960 return sliceHandler(stringSlice) 961 } 962 963 // some examples of input data that would get here would be a raw scalar 964 // number, or a JSON map object, etc. 965 return "", fmt.Errorf("unsupported JSON encoding format") 966 } 967 968 // Time returns the time the Log was received by the journal. 969 func (l Log) Time() (time.Time, error) { 970 // since the __REALTIME_TIMESTAMP is underscored and thus "trusted" by 971 // systemd, we assume that it will always be a valid string and not try to 972 // handle any possible array cases 973 sus, err := l.parseLogRawMessageString("__REALTIME_TIMESTAMP", func([]string) (string, error) { 974 return "", errors.New("no timestamp") 975 }) 976 if err != nil { 977 return time.Time{}, err 978 } 979 980 // according to systemd.journal-fields(7) it's microseconds as a decimal string 981 us, err := strconv.ParseInt(sus, 10, 64) 982 if err != nil { 983 return time.Time{}, fmt.Errorf("timestamp not a decimal number: %#v", sus) 984 } 985 986 return time.Unix(us/1000000, 1000*(us%1000000)).UTC(), nil 987 } 988 989 // Message of the Log, if any; otherwise, "-". 990 func (l Log) Message() string { 991 // for MESSAGE, if there are multiple strings, just concatenate them with a 992 // newline to keep as much data from journald as possible 993 msg, err := l.parseLogRawMessageString("MESSAGE", func(stringSlice []string) (string, error) { 994 return strings.Join(stringSlice, "\n"), nil 995 }) 996 if err != nil { 997 if _, ok := l["MESSAGE"]; !ok { 998 // if the MESSAGE key is just missing, then return "-" 999 return "-" 1000 } 1001 return fmt.Sprintf("- (error decoding original message: %v)", err) 1002 } 1003 return msg 1004 } 1005 1006 // SID is the syslog identifier of the Log, if any; otherwise, "-". 1007 func (l Log) SID() string { 1008 // if there are multiple SYSLOG_IDENTIFIER values, just act like there was 1009 // not one, making an arbitrary choice here is probably not helpful 1010 sid, err := l.parseLogRawMessageString("SYSLOG_IDENTIFIER", func([]string) (string, error) { 1011 return "", fmt.Errorf("multiple identifiers not supported") 1012 }) 1013 if err != nil || sid == "" { 1014 return "-" 1015 } 1016 return sid 1017 } 1018 1019 // PID is the pid of the client pid, if any; otherwise, "-". 1020 func (l Log) PID() string { 1021 // look for _PID first as that is underscored and thus "trusted" from 1022 // systemd, also don't support multiple arrays if we find then 1023 multiplePIDsErr := fmt.Errorf("multiple pids not supported") 1024 pid, err := l.parseLogRawMessageString("_PID", func([]string) (string, error) { 1025 return "", multiplePIDsErr 1026 }) 1027 if err == nil && pid != "" { 1028 return pid 1029 } 1030 1031 pid, err = l.parseLogRawMessageString("SYSLOG_PID", func([]string) (string, error) { 1032 return "", multiplePIDsErr 1033 }) 1034 if err == nil && pid != "" { 1035 return pid 1036 } 1037 1038 return "-" 1039 } 1040 1041 // MountUnitPath returns the path of a {,auto}mount unit 1042 func MountUnitPath(baseDir string) string { 1043 escapedPath := EscapeUnitNamePath(baseDir) 1044 return filepath.Join(dirs.SnapServicesDir, escapedPath+".mount") 1045 } 1046 1047 var squashfsFsType = squashfs.FsType 1048 1049 // XXX: After=zfs-mount.service is a workaround for LP: #1922293 (a problem 1050 // with order of mounting most likely related to zfs-linux and/or systemd). 1051 var mountUnitTemplate = `[Unit] 1052 Description=Mount unit for %s, revision %s 1053 Before=snapd.service 1054 After=zfs-mount.service 1055 1056 [Mount] 1057 What=%s 1058 Where=%s 1059 Type=%s 1060 Options=%s 1061 LazyUnmount=yes 1062 1063 [Install] 1064 WantedBy=multi-user.target 1065 ` 1066 1067 func writeMountUnitFile(snapName, revision, what, where, fstype string, options []string) (mountUnitName string, err error) { 1068 content := fmt.Sprintf(mountUnitTemplate, snapName, revision, what, where, fstype, strings.Join(options, ",")) 1069 mu := MountUnitPath(where) 1070 mountUnitName, err = filepath.Base(mu), osutil.AtomicWriteFile(mu, []byte(content), 0644, 0) 1071 if err != nil { 1072 return "", err 1073 } 1074 return mountUnitName, nil 1075 } 1076 1077 func fsMountOptions(fstype string) []string { 1078 options := []string{"nodev"} 1079 if fstype == "squashfs" { 1080 if selinux.ProbedLevel() != selinux.Unsupported { 1081 if mountCtx := selinux.SnapMountContext(); mountCtx != "" { 1082 options = append(options, "context="+mountCtx) 1083 } 1084 } 1085 } 1086 return options 1087 } 1088 1089 // hostFsTypeAndMountOptions returns filesystem type and options to actually 1090 // mount the given fstype at runtime, i.e. it determines if fuse should be used 1091 // for squashfs. 1092 func hostFsTypeAndMountOptions(fstype string) (hostFsType string, options []string) { 1093 options = fsMountOptions(fstype) 1094 hostFsType = fstype 1095 if fstype == "squashfs" { 1096 newFsType, newOptions := squashfsFsType() 1097 options = append(options, newOptions...) 1098 hostFsType = newFsType 1099 } 1100 return hostFsType, options 1101 } 1102 1103 func (s *systemd) AddMountUnitFile(snapName, revision, what, where, fstype string) (string, error) { 1104 daemonReloadLock.Lock() 1105 defer daemonReloadLock.Unlock() 1106 1107 hostFsType, options := hostFsTypeAndMountOptions(fstype) 1108 if osutil.IsDirectory(what) { 1109 options = append(options, "bind") 1110 hostFsType = "none" 1111 } 1112 mountUnitName, err := writeMountUnitFile(snapName, revision, what, where, hostFsType, options) 1113 if err != nil { 1114 return "", err 1115 } 1116 1117 // we need to do a daemon-reload here to ensure that systemd really 1118 // knows about this new mount unit file 1119 if err := s.daemonReloadNoLock(); err != nil { 1120 return "", err 1121 } 1122 1123 if err := s.Enable(mountUnitName); err != nil { 1124 return "", err 1125 } 1126 if err := s.Start(mountUnitName); err != nil { 1127 return "", err 1128 } 1129 1130 return mountUnitName, nil 1131 } 1132 1133 func (s *systemd) RemoveMountUnitFile(mountedDir string) error { 1134 daemonReloadLock.Lock() 1135 defer daemonReloadLock.Unlock() 1136 1137 unit := MountUnitPath(dirs.StripRootDir(mountedDir)) 1138 if !osutil.FileExists(unit) { 1139 return nil 1140 } 1141 1142 // use umount -d (cleanup loopback devices) -l (lazy) to ensure that even busy mount points 1143 // can be unmounted. 1144 // note that the long option --lazy is not supported on trusty. 1145 // the explicit -d is only needed on trusty. 1146 isMounted, err := osutilIsMounted(mountedDir) 1147 if err != nil { 1148 return err 1149 } 1150 if isMounted { 1151 if output, err := exec.Command("umount", "-d", "-l", mountedDir).CombinedOutput(); err != nil { 1152 return osutil.OutputErr(output, err) 1153 } 1154 1155 if err := s.Stop(filepath.Base(unit), time.Duration(1*time.Second)); err != nil { 1156 return err 1157 } 1158 } 1159 if err := s.Disable(filepath.Base(unit)); err != nil { 1160 return err 1161 } 1162 if err := os.Remove(unit); err != nil { 1163 return err 1164 } 1165 // daemon-reload to ensure that systemd actually really 1166 // forgets about this mount unit 1167 if err := s.daemonReloadNoLock(); err != nil { 1168 return err 1169 } 1170 1171 return nil 1172 } 1173 1174 func (s *systemd) ReloadOrRestart(serviceName string) error { 1175 if s.mode == GlobalUserMode { 1176 panic("cannot call restart with GlobalUserMode") 1177 } 1178 _, err := s.systemctl("reload-or-restart", serviceName) 1179 return err 1180 } 1181 1182 func (s *systemd) Mount(what, where string, options ...string) error { 1183 args := make([]string, 0, 2+len(options)) 1184 if len(options) > 0 { 1185 args = append(args, options...) 1186 } 1187 args = append(args, what, where) 1188 if output, err := exec.Command("systemd-mount", args...).CombinedOutput(); err != nil { 1189 return osutil.OutputErr(output, err) 1190 } 1191 return nil 1192 } 1193 1194 func (s *systemd) Umount(whatOrWhere string) error { 1195 if output, err := exec.Command("systemd-mount", "--umount", whatOrWhere).CombinedOutput(); err != nil { 1196 return osutil.OutputErr(output, err) 1197 } 1198 return nil 1199 }