github.com/rigado/snapd@v2.42.5-go-mod+incompatible/wrappers/core18.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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 wrappers
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"regexp"
    29  	"time"
    30  
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/logger"
    33  	"github.com/snapcore/snapd/osutil"
    34  	"github.com/snapcore/snapd/release"
    35  	"github.com/snapcore/snapd/snap"
    36  	"github.com/snapcore/snapd/systemd"
    37  )
    38  
    39  // catches units that run /usr/bin/snap (with args), or things in /usr/lib/snapd/
    40  var execStartRe = regexp.MustCompile(`(?m)^ExecStart=(/usr/bin/snap\s+.*|/usr/lib/snapd/.*)$`)
    41  
    42  func writeSnapdToolingMountUnit(sysd systemd.Systemd, prefix string) error {
    43  	// Not using AddMountUnitFile() because we need
    44  	// "RequiredBy=snapd.service"
    45  	content := []byte(fmt.Sprintf(`[Unit]
    46  Description=Make the snapd snap tooling available for the system
    47  Before=snapd.service
    48  
    49  [Mount]
    50  What=%s/usr/lib/snapd
    51  Where=/usr/lib/snapd
    52  Type=none
    53  Options=bind
    54  
    55  [Install]
    56  WantedBy=snapd.service
    57  `, prefix))
    58  	unit := "usr-lib-snapd.mount"
    59  	fullPath := filepath.Join(dirs.SnapServicesDir, unit)
    60  
    61  	err := osutil.EnsureFileState(fullPath,
    62  		&osutil.FileState{
    63  			Content: content,
    64  			Mode:    0644,
    65  		},
    66  	)
    67  	if err == osutil.ErrSameState {
    68  		return nil
    69  	}
    70  	if err != nil {
    71  		return err
    72  	}
    73  	if err := sysd.DaemonReload(); err != nil {
    74  		return err
    75  	}
    76  	if err := sysd.Enable(unit); err != nil {
    77  		return err
    78  	}
    79  
    80  	if err := sysd.Restart(unit, 5*time.Second); err != nil {
    81  		return err
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  func writeSnapdServicesOnCore(s *snap.Info, inter interacter) error {
    88  	// we never write
    89  	if release.OnClassic {
    90  		return nil
    91  	}
    92  	sysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter)
    93  
    94  	if err := writeSnapdToolingMountUnit(sysd, s.MountDir()); err != nil {
    95  		return err
    96  	}
    97  
    98  	serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.service"))
    99  	if err != nil {
   100  		return err
   101  	}
   102  	socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.socket"))
   103  	if err != nil {
   104  		return err
   105  	}
   106  	timerUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "lib/systemd/system/*.timer"))
   107  	if err != nil {
   108  		return err
   109  	}
   110  	units := append(socketUnits, serviceUnits...)
   111  	units = append(units, timerUnits...)
   112  
   113  	snapdUnits := make(map[string]*osutil.FileState, len(units)+1)
   114  	for _, unit := range units {
   115  		st, err := os.Stat(unit)
   116  		if err != nil {
   117  			return err
   118  		}
   119  		content, err := ioutil.ReadFile(unit)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		content = execStartRe.ReplaceAll(content, []byte(fmt.Sprintf(`ExecStart=%s$1`, s.MountDir())))
   124  
   125  		snapdUnits[filepath.Base(unit)] = &osutil.FileState{
   126  			Content: content,
   127  			Mode:    st.Mode(),
   128  		}
   129  	}
   130  	changed, removed, err := osutil.EnsureDirStateGlobs(dirs.SnapServicesDir, []string{"snapd.service", "snapd.socket", "snapd.*.service", "snapd.*.timer"}, snapdUnits)
   131  	if err != nil {
   132  		// TODO: uhhhh, what do we do in this case?
   133  		return err
   134  	}
   135  	if (len(changed) + len(removed)) == 0 {
   136  		// nothing to do
   137  		return nil
   138  	}
   139  	// stop all removed units first
   140  	for _, unit := range removed {
   141  		if err := sysd.Stop(unit, 5*time.Second); err != nil {
   142  			logger.Noticef("failed to stop %q: %v", unit, err)
   143  		}
   144  		if err := sysd.Disable(unit); err != nil {
   145  			logger.Noticef("failed to disable %q: %v", unit, err)
   146  		}
   147  	}
   148  
   149  	// daemon-reload so that we get the new services
   150  	if len(changed) > 0 {
   151  		if err := sysd.DaemonReload(); err != nil {
   152  			return err
   153  		}
   154  	}
   155  
   156  	// enable/start all the new services
   157  	for _, unit := range changed {
   158  		if err := sysd.Enable(unit); err != nil {
   159  			return err
   160  		}
   161  	}
   162  
   163  	for _, unit := range changed {
   164  		// Some units (like the snapd.system-shutdown.service) cannot
   165  		// be started. Others like "snapd.seeded.service" are started
   166  		// as dependencies of snapd.service.
   167  		if bytes.Contains(snapdUnits[unit].Content, []byte("X-Snapd-Snap: do-not-start")) {
   168  			continue
   169  		}
   170  		// Ensure to only restart if the unit was previously
   171  		// active. This ensures we DTRT on firstboot and do
   172  		// not stop e.g. snapd.socket because doing that
   173  		// would mean that the snapd.seeded.service is also
   174  		// stopped (independently of snapd.socket being
   175  		// active) which confuses the boot order (the unit
   176  		// exists before we are fully seeded).
   177  		isActive, err := sysd.IsActive(unit)
   178  		if err != nil {
   179  			return err
   180  		}
   181  		if isActive {
   182  			if err := sysd.Restart(unit, 5*time.Second); err != nil {
   183  				return err
   184  			}
   185  		} else {
   186  			if err := sysd.Start(unit); err != nil {
   187  				return err
   188  			}
   189  		}
   190  	}
   191  	// and finally start snapd.service (it will stop by itself and gets
   192  	// started by systemd then)
   193  	if err := sysd.Start("snapd.service"); err != nil {
   194  		return err
   195  	}
   196  	if err := sysd.StartNoBlock("snapd.seeded.service"); err != nil {
   197  		return err
   198  	}
   199  	// we cannot start snapd.autoimport in blocking mode because
   200  	// it has a "After=snapd.seeded.service" which means that on
   201  	// seeding a "systemctl start" that blocks would hang forever
   202  	// and we deadlock.
   203  	if err := sysd.StartNoBlock("snapd.autoimport.service"); err != nil {
   204  		return err
   205  	}
   206  
   207  	// Handle the user services
   208  	if err := writeSnapdUserServicesOnCore(s, inter); err != nil {
   209  		return err
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  func writeSnapdUserServicesOnCore(s *snap.Info, inter interacter) error {
   216  	// Ensure /etc/systemd/user exists
   217  	if err := os.MkdirAll(dirs.SnapUserServicesDir, 0755); err != nil {
   218  		return err
   219  	}
   220  
   221  	sysd := systemd.New(dirs.GlobalRootDir, systemd.GlobalUserMode, inter)
   222  
   223  	serviceUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.service"))
   224  	if err != nil {
   225  		return err
   226  	}
   227  	socketUnits, err := filepath.Glob(filepath.Join(s.MountDir(), "usr/lib/systemd/user/*.socket"))
   228  	if err != nil {
   229  		return err
   230  	}
   231  	units := append(serviceUnits, socketUnits...)
   232  
   233  	snapdUnits := make(map[string]*osutil.FileState, len(units)+1)
   234  	for _, unit := range units {
   235  		st, err := os.Stat(unit)
   236  		if err != nil {
   237  			return err
   238  		}
   239  		content, err := ioutil.ReadFile(unit)
   240  		if err != nil {
   241  			return err
   242  		}
   243  		content = execStartRe.ReplaceAll(content, []byte(fmt.Sprintf(`ExecStart=%s$1`, s.MountDir())))
   244  
   245  		snapdUnits[filepath.Base(unit)] = &osutil.FileState{
   246  			Content: content,
   247  			Mode:    st.Mode(),
   248  		}
   249  	}
   250  	changed, removed, err := osutil.EnsureDirStateGlobs(dirs.SnapUserServicesDir, []string{"snapd.*.service", "snapd.*.socket"}, snapdUnits)
   251  	if err != nil {
   252  		// TODO: uhhhh, what do we do in this case?
   253  		return err
   254  	}
   255  	if (len(changed) + len(removed)) == 0 {
   256  		// nothing to do
   257  		return nil
   258  	}
   259  	// disable all removed units first
   260  	for _, unit := range removed {
   261  		if err := sysd.Disable(unit); err != nil {
   262  			logger.Noticef("failed to disable %q: %v", unit, err)
   263  		}
   264  	}
   265  
   266  	// enable/start all the new services
   267  	for _, unit := range changed {
   268  		if err := sysd.Enable(unit); err != nil {
   269  			return err
   270  		}
   271  	}
   272  
   273  	return nil
   274  }