Files
snapd/systemd/emulation.go
2022-02-23 17:23:34 +01:00

225 lines
6.3 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019 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 systemd
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/squashfs"
)
type emulation struct {
rootDir string
}
type notImplementedError struct {
op string
}
func (e *notImplementedError) Error() string {
return fmt.Sprintf("%q is not implemented in emulation mode", e.op)
}
func (s *emulation) Backend() Backend {
return EmulationModeBackend
}
func (s *emulation) DaemonReload() error {
return nil
}
func (s *emulation) DaemonReexec() error {
return &notImplementedError{"DaemonReexec"}
}
func (s *emulation) Enable(services []string) error {
_, err := systemctlCmd(append([]string{"--root", s.rootDir, "enable"}, services...)...)
return err
}
func (s *emulation) Disable(services []string) error {
_, err := systemctlCmd(append([]string{"--root", s.rootDir, "disable"}, services...)...)
return err
}
func (s *emulation) Start(services []string) error {
return nil
}
func (s *emulation) StartNoBlock(services []string) error {
return nil
}
func (s *emulation) Stop(services []string, timeout time.Duration) error {
return nil
}
func (s *emulation) Kill(service, signal, who string) error {
return &notImplementedError{"Kill"}
}
func (s *emulation) Restart(services []string, timeout time.Duration) error {
return nil
}
func (s *emulation) ReloadOrRestart(service string) error {
return &notImplementedError{"ReloadOrRestart"}
}
func (s *emulation) RestartAll(service string) error {
return &notImplementedError{"RestartAll"}
}
func (s *emulation) Status(units []string) ([]*UnitStatus, error) {
return nil, &notImplementedError{"Status"}
}
func (s *emulation) InactiveEnterTimestamp(unit string) (time.Time, error) {
return time.Time{}, &notImplementedError{"InactiveEnterTimestamp"}
}
func (s *emulation) CurrentMemoryUsage(unit string) (quantity.Size, error) {
return 0, &notImplementedError{"CurrentMemoryUsage"}
}
func (s *emulation) CurrentTasksCount(unit string) (uint64, error) {
return 0, &notImplementedError{"CurrentTasksCount"}
}
func (s *emulation) IsEnabled(service string) (bool, error) {
return false, &notImplementedError{"IsEnabled"}
}
func (s *emulation) IsActive(service string) (bool, error) {
return false, &notImplementedError{"IsActive"}
}
func (s *emulation) LogReader(services []string, n int, follow bool) (io.ReadCloser, error) {
return nil, fmt.Errorf("LogReader")
}
func (s *emulation) AddMountUnitFile(snapName, revision, what, where, fstype string) (string, error) {
if osutil.IsDirectory(what) {
return "", fmt.Errorf("bind-mounted directory is not supported in emulation mode")
}
// In emulation mode hostFsType is the fs we want to use to manually mount
// the snap below, but fstype is used for the created mount unit.
// This means that when preseeding in a lxd container, the snap will be
// mounted with fuse, but mount unit will use squashfs.
mountUnitOptions := append(fsMountOptions(fstype), squashfs.StandardOptions()...)
mountUnitName, err := writeMountUnitFile(&MountUnitOptions{
Lifetime: Persistent,
SnapName: snapName,
Revision: revision,
What: what,
Where: where,
Fstype: fstype,
Options: mountUnitOptions,
})
if err != nil {
return "", err
}
hostFsType, actualOptions := hostFsTypeAndMountOptions(fstype)
cmd := exec.Command("mount", "-t", hostFsType, what, where, "-o", strings.Join(actualOptions, ","))
if out, err := cmd.CombinedOutput(); err != nil {
return "", fmt.Errorf("cannot mount %s (%s) at %s in preseed mode: %s; %s", what, hostFsType, where, err, string(out))
}
multiUserTargetWantsDir := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants")
if err := os.MkdirAll(multiUserTargetWantsDir, 0755); err != nil {
return "", err
}
// cannot call systemd, so manually enable the unit by symlinking into multi-user.target.wants
mu := MountUnitPath(where)
enableUnitPath := filepath.Join(multiUserTargetWantsDir, mountUnitName)
if err := os.Symlink(mu, enableUnitPath); err != nil {
return "", fmt.Errorf("cannot enable mount unit %s: %v", mountUnitName, err)
}
return mountUnitName, nil
}
func (s *emulation) AddMountUnitFileWithOptions(unitOptions *MountUnitOptions) (string, error) {
return "", &notImplementedError{"AddMountUnitFileWithOptions"}
}
func (s *emulation) RemoveMountUnitFile(mountedDir string) error {
unit := MountUnitPath(dirs.StripRootDir(mountedDir))
if !osutil.FileExists(unit) {
return nil
}
isMounted, err := osutilIsMounted(mountedDir)
if err != nil {
return err
}
if isMounted {
// use detach-loop and lazy unmount
if output, err := exec.Command("umount", "-d", "-l", mountedDir).CombinedOutput(); err != nil {
return osutil.OutputErr(output, err)
}
}
multiUserTargetWantsDir := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants")
enableUnitPathSymlink := filepath.Join(multiUserTargetWantsDir, filepath.Base(unit))
if err := os.Remove(enableUnitPathSymlink); err != nil {
return err
}
if err := os.Remove(unit); err != nil {
return err
}
return nil
}
func (s *emulation) ListMountUnits(snapName, origin string) ([]string, error) {
return nil, &notImplementedError{"ListMountUnits"}
}
func (s *emulation) Mask(service string) error {
_, err := systemctlCmd("--root", s.rootDir, "mask", service)
return err
}
func (s *emulation) Unmask(service string) error {
_, err := systemctlCmd("--root", s.rootDir, "unmask", service)
return err
}
func (s *emulation) Mount(what, where string, options ...string) error {
return &notImplementedError{"Mount"}
}
func (s *emulation) Umount(whatOrWhere string) error {
return &notImplementedError{"Umount"}
}