github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/udev/backend.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-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 udev implements integration between snapd, udev and
    21  // snap-confine around tagging character and block devices so that they
    22  // can be accessed by applications.
    23  //
    24  // TODO: Document this better
    25  package udev
    26  
    27  import (
    28  	"bytes"
    29  	"fmt"
    30  	"os"
    31  	"path/filepath"
    32  	"strings"
    33  
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/interfaces"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/snap"
    38  	"github.com/snapcore/snapd/timings"
    39  )
    40  
    41  // Backend is responsible for maintaining udev rules.
    42  type Backend struct{}
    43  
    44  // Initialize does nothing.
    45  func (b *Backend) Initialize() error {
    46  	return nil
    47  }
    48  
    49  // Name returns the name of the backend.
    50  func (b *Backend) Name() interfaces.SecuritySystem {
    51  	return interfaces.SecurityUDev
    52  }
    53  
    54  // snapRulesFileName returns the path of the snap udev rules file.
    55  func snapRulesFilePath(snapName string) string {
    56  	rulesFileName := fmt.Sprintf("70-%s.rules", snap.SecurityTag(snapName))
    57  	return filepath.Join(dirs.SnapUdevRulesDir, rulesFileName)
    58  }
    59  
    60  // Setup creates udev rules specific to a given snap.
    61  // If any of the rules are changed or removed then udev database is reloaded.
    62  //
    63  // UDev has no concept of a complain mode so confinment options are ignored.
    64  //
    65  // If the method fails it should be re-tried (with a sensible strategy) by the caller.
    66  func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
    67  	snapName := snapInfo.InstanceName()
    68  	spec, err := repo.SnapSpecification(b.Name(), snapName)
    69  	if err != nil {
    70  		return fmt.Errorf("cannot obtain udev specification for snap %q: %s", snapName, err)
    71  	}
    72  	content := b.deriveContent(spec.(*Specification), snapInfo)
    73  	subsystemTriggers := spec.(*Specification).TriggeredSubsystems()
    74  
    75  	dir := dirs.SnapUdevRulesDir
    76  	if err := os.MkdirAll(dir, 0755); err != nil {
    77  		return fmt.Errorf("cannot create directory for udev rules %q: %s", dir, err)
    78  	}
    79  
    80  	rulesFilePath := snapRulesFilePath(snapInfo.InstanceName())
    81  
    82  	if len(content) == 0 {
    83  		// Make sure that the rules file gets removed when we don't have any
    84  		// content and exists.
    85  		err = os.Remove(rulesFilePath)
    86  		if err != nil && !os.IsNotExist(err) {
    87  			return err
    88  		} else if err == nil {
    89  			// FIXME: somehow detect the interfaces that were
    90  			// disconnected and set subsystemTriggers appropriately.
    91  			// ATM, it is always going to be empty on disconnect.
    92  			return ReloadRules(subsystemTriggers)
    93  		}
    94  		return nil
    95  	}
    96  
    97  	var buffer bytes.Buffer
    98  	buffer.WriteString("# This file is automatically generated.\n")
    99  	if (opts.DevMode || opts.Classic) && !opts.JailMode {
   100  		buffer.WriteString("# udev tagging/device cgroups disabled with non-strict mode snaps\n")
   101  	}
   102  	for _, snippet := range content {
   103  		if (opts.DevMode || opts.Classic) && !opts.JailMode {
   104  			buffer.WriteRune('#')
   105  			snippet = strings.Replace(snippet, "\n", "\n#", -1)
   106  		}
   107  		buffer.WriteString(snippet)
   108  		buffer.WriteByte('\n')
   109  	}
   110  
   111  	rulesFileState := &osutil.FileState{
   112  		Content: buffer.Bytes(),
   113  		Mode:    0644,
   114  	}
   115  
   116  	// EnsureFileState will make sure the file will be only updated when its content
   117  	// has changed and will otherwise return an error which prevents us from reloading
   118  	// udev rules when not needed.
   119  	err = osutil.EnsureFileState(rulesFilePath, rulesFileState)
   120  	if err == osutil.ErrSameState {
   121  		return nil
   122  	} else if err != nil {
   123  		return err
   124  	}
   125  
   126  	// FIXME: somehow detect the interfaces that were disconnected and set
   127  	// subsystemTriggers appropriately. ATM, it is always going to be empty
   128  	// on disconnect.
   129  	return ReloadRules(subsystemTriggers)
   130  }
   131  
   132  // Remove removes udev rules specific to a given snap.
   133  // If any of the rules are removed then udev database is reloaded.
   134  //
   135  // This method should be called after removing a snap.
   136  //
   137  // If the method fails it should be re-tried (with a sensible strategy) by the caller.
   138  func (b *Backend) Remove(snapName string) error {
   139  	rulesFilePath := snapRulesFilePath(snapName)
   140  	err := os.Remove(rulesFilePath)
   141  	if os.IsNotExist(err) {
   142  		// If file doesn't exist we avoid reloading the udev rules when we return here
   143  		return nil
   144  	} else if err != nil {
   145  		return err
   146  	}
   147  
   148  	// FIXME: somehow detect the interfaces that were disconnected and set
   149  	// subsystemTriggers appropriately. ATM, it is always going to be empty
   150  	// on disconnect.
   151  	return ReloadRules(nil)
   152  }
   153  
   154  func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info) (content []string) {
   155  	for _, snippet := range spec.Snippets() {
   156  		content = append(content, snippet)
   157  	}
   158  
   159  	return content
   160  }
   161  
   162  func (b *Backend) NewSpecification() interfaces.Specification {
   163  	return &Specification{}
   164  }
   165  
   166  // SandboxFeatures returns the list of features supported by snapd for mediating access to kernel devices.
   167  func (b *Backend) SandboxFeatures() []string {
   168  	return []string{
   169  		"device-cgroup-v1", /* Snapd creates a device group (v1) for each snap */
   170  		"tagging",          /* Tagging dynamically associates new devices with specific snaps */
   171  	}
   172  }