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