github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/interfaces/systemd/backend.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-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 implements integration between snappy interfaces and
    21  // arbitrary systemd units that may be required for "oneshot" style tasks.
    22  package systemd
    23  
    24  import (
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/interfaces"
    32  	"github.com/snapcore/snapd/logger"
    33  	"github.com/snapcore/snapd/osutil"
    34  	"github.com/snapcore/snapd/snap"
    35  	sysd "github.com/snapcore/snapd/systemd"
    36  	"github.com/snapcore/snapd/timings"
    37  )
    38  
    39  func serviceName(snapName, distinctServiceSuffix string) string {
    40  	return snap.ScopedSecurityTag(snapName, "interface", distinctServiceSuffix) + ".service"
    41  }
    42  
    43  // Backend is responsible for maintaining apparmor profiles for ubuntu-core-launcher.
    44  type Backend struct {
    45  	preseed bool
    46  }
    47  
    48  // Initialize does nothing.
    49  func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error {
    50  	if opts != nil && opts.Preseed {
    51  		b.preseed = true
    52  	}
    53  	return nil
    54  }
    55  
    56  // Name returns the name of the backend.
    57  func (b *Backend) Name() interfaces.SecuritySystem {
    58  	return interfaces.SecuritySystemd
    59  }
    60  
    61  // Setup creates and starts systemd services specific to a given snap.
    62  //
    63  // This method should be called after changing plug, slots, connections between
    64  // them or application present in the snap.
    65  func (b *Backend) Setup(snapInfo *snap.Info, confinement interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
    66  	// Record all the extra systemd services for this snap.
    67  	snapName := snapInfo.InstanceName()
    68  	// Get the services that apply to this snap
    69  	spec, err := repo.SnapSpecification(b.Name(), snapName)
    70  	if err != nil {
    71  		return fmt.Errorf("cannot obtain systemd services for snap %q: %s", snapName, err)
    72  	}
    73  	content := deriveContent(spec.(*Specification), snapInfo)
    74  	// synchronize the content with the filesystem
    75  	dir := dirs.SnapServicesDir
    76  	if err := os.MkdirAll(dir, 0755); err != nil {
    77  		return fmt.Errorf("cannot create directory for systemd services %q: %s", dir, err)
    78  	}
    79  	glob := serviceName(snapName, "*")
    80  
    81  	var systemd sysd.Systemd
    82  	if b.preseed {
    83  		systemd = sysd.NewEmulationMode(dirs.GlobalRootDir)
    84  	} else {
    85  		systemd = sysd.New(sysd.SystemMode, &dummyReporter{})
    86  	}
    87  
    88  	// We need to be carefully here and stop all removed service units before
    89  	// we remove their files as otherwise systemd is not able to disable/stop
    90  	// them anymore.
    91  	if err := b.disableRemovedServices(systemd, dir, glob, content); err != nil {
    92  		logger.Noticef("cannot stop removed services: %s", err)
    93  	}
    94  	changed, removed, errEnsure := osutil.EnsureDirState(dir, glob, content)
    95  	// Reload systemd whenever something is added or removed
    96  	if !b.preseed && (len(changed) > 0 || len(removed) > 0) {
    97  		err := systemd.DaemonReload()
    98  		if err != nil {
    99  			logger.Noticef("cannot reload systemd state: %s", err)
   100  		}
   101  	}
   102  	// Ensure the service is running right now and on reboots
   103  	for _, service := range changed {
   104  		if err := systemd.Enable(service); err != nil {
   105  			logger.Noticef("cannot enable service %q: %s", service, err)
   106  		}
   107  		if !b.preseed {
   108  			// If we have a new service here which isn't started yet the restart
   109  			// operation will start it.
   110  			if err := systemd.Restart(service, 10*time.Second); err != nil {
   111  				logger.Noticef("cannot restart service %q: %s", service, err)
   112  			}
   113  		}
   114  	}
   115  	return errEnsure
   116  }
   117  
   118  // Remove disables, stops and removes systemd services of a given snap.
   119  func (b *Backend) Remove(snapName string) error {
   120  	var systemd sysd.Systemd
   121  	if b.preseed {
   122  		// removing while preseeding is not a viable scenario, but implemented
   123  		// for completness.
   124  		systemd = sysd.NewEmulationMode(dirs.GlobalRootDir)
   125  	} else {
   126  		systemd = sysd.New(sysd.SystemMode, &dummyReporter{})
   127  	}
   128  	// Remove all the files matching snap glob
   129  	glob := serviceName(snapName, "*")
   130  	_, removed, errEnsure := osutil.EnsureDirState(dirs.SnapServicesDir, glob, nil)
   131  	for _, service := range removed {
   132  		if err := systemd.Disable(service); err != nil {
   133  			logger.Noticef("cannot disable service %q: %s", service, err)
   134  		}
   135  		if !b.preseed {
   136  			if err := systemd.Stop(service, 5*time.Second); err != nil {
   137  				logger.Noticef("cannot stop service %q: %s", service, err)
   138  			}
   139  		}
   140  	}
   141  	// Reload systemd whenever something is removed
   142  	if !b.preseed && len(removed) > 0 {
   143  		err := systemd.DaemonReload()
   144  		if err != nil {
   145  			logger.Noticef("cannot reload systemd state: %s", err)
   146  		}
   147  	}
   148  	return errEnsure
   149  }
   150  
   151  // NewSpecification returns a new systemd specification.
   152  func (b *Backend) NewSpecification() interfaces.Specification {
   153  	return &Specification{}
   154  }
   155  
   156  // SandboxFeatures returns nil
   157  func (b *Backend) SandboxFeatures() []string {
   158  	return nil
   159  }
   160  
   161  // deriveContent computes .service files based on requests made to the specification.
   162  func deriveContent(spec *Specification, snapInfo *snap.Info) map[string]osutil.FileState {
   163  	services := spec.Services()
   164  	if len(services) == 0 {
   165  		return nil
   166  	}
   167  	content := make(map[string]osutil.FileState)
   168  	for suffix, service := range services {
   169  		filename := serviceName(snapInfo.InstanceName(), suffix)
   170  		content[filename] = &osutil.MemoryFileState{
   171  			Content: []byte(service.String()),
   172  			Mode:    0644,
   173  		}
   174  	}
   175  	return content
   176  }
   177  
   178  func (b *Backend) disableRemovedServices(systemd sysd.Systemd, dir, glob string, content map[string]osutil.FileState) error {
   179  	paths, err := filepath.Glob(filepath.Join(dir, glob))
   180  	if err != nil {
   181  		return err
   182  	}
   183  	for _, path := range paths {
   184  		service := filepath.Base(path)
   185  		if content[service] == nil {
   186  			if err := systemd.Disable(service); err != nil {
   187  				logger.Noticef("cannot disable service %q: %s", service, err)
   188  			}
   189  			if !b.preseed {
   190  				if err := systemd.Stop(service, 5*time.Second); err != nil {
   191  					logger.Noticef("cannot stop service %q: %s", service, err)
   192  				}
   193  			}
   194  		}
   195  	}
   196  	return nil
   197  }
   198  
   199  type dummyReporter struct{}
   200  
   201  func (dr *dummyReporter) Notify(msg string) {
   202  }