gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/udev/udev.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
    21  
    22  import (
    23  	"fmt"
    24  	"os/exec"
    25  )
    26  
    27  // udevadmTrigger runs "udevadm trigger" but ignores an non-zero exit codes.
    28  // udevadm only started reporting errors in systemd 248 and in order to
    29  // work correctly in LXD these errors need to be ignored. See
    30  // https://github.com/systemd/systemd/pull/18684 for some more background
    31  // (and https://github.com/lxc/lxd/issues/9526)
    32  func udevadmTrigger(args ...string) error {
    33  	args = append([]string{"trigger"}, args...)
    34  	output, err := exec.Command("udevadm", args...).CombinedOutput()
    35  	// can happen when events for some of the devices or all of
    36  	// them could not be triggered, but we cannot distinguish which of
    37  	// those happened, in any case snapd invoked udevadm and tried its
    38  	// best
    39  	if exitErr, ok := err.(*exec.ExitError); ok {
    40  		// ignore "normal" exit codes but report e.g. segfaults
    41  		// that are reported as -1
    42  		if exitErr.ExitCode() > 0 {
    43  			return nil
    44  		}
    45  	}
    46  	if err != nil {
    47  		return fmt.Errorf("%s\nudev output:\n%s", err, string(output))
    48  	}
    49  	return nil
    50  }
    51  
    52  // reloadRules runs three commands that reload udev rule database.
    53  //
    54  // The commands are: udevadm control --reload-rules
    55  //                   udevadm trigger --subsystem-nomatch=input
    56  //                   udevadm settle --timeout=3
    57  // and optionally trigger other subsystems as defined in the interfaces. Eg:
    58  //                   udevadm trigger --subsystem-match=input
    59  //                   udevadm trigger --property-match=ID_INPUT_JOYSTICK=1
    60  func (b *Backend) reloadRules(subsystemTriggers []string) error {
    61  	if b.preseed {
    62  		return nil
    63  	}
    64  
    65  	output, err := exec.Command("udevadm", "control", "--reload-rules").CombinedOutput()
    66  	if err != nil {
    67  		return fmt.Errorf("cannot reload udev rules: %s\nudev output:\n%s", err, string(output))
    68  	}
    69  
    70  	// By default, trigger for all events except the input subsystem since
    71  	// it can cause noticeable blocked input on, for example, classic
    72  	// desktop.
    73  	if err = udevadmTrigger("--subsystem-nomatch=input"); err != nil {
    74  		return fmt.Errorf("cannot run udev triggers: %s", err)
    75  	}
    76  
    77  	mustTriggerForInputSubsystem := false
    78  	mustTriggerForInputKeys := false
    79  
    80  	for _, subsystem := range subsystemTriggers {
    81  		if subsystem == "input/key" {
    82  			mustTriggerForInputKeys = true
    83  		} else if subsystem == "input" {
    84  			mustTriggerForInputSubsystem = true
    85  		}
    86  		// no `else` branch: we already triggered udevadm for all other
    87  		// subsystems before by running it with the `--subsystem-nomatch=input`
    88  		// option, so there's no need to do anything here.
    89  	}
    90  
    91  	if mustTriggerForInputSubsystem {
    92  		// Trigger for the whole input subsystem
    93  		if err := udevadmTrigger("--subsystem-match=input"); err != nil {
    94  			return fmt.Errorf("cannot run udev triggers for input subsystem: %s", err)
    95  		}
    96  	} else {
    97  		// More specific triggers, to avoid blocking keyboards and mice
    98  
    99  		if mustTriggerForInputKeys {
   100  			// If one of the interfaces said it uses the input
   101  			// subsystem for input keys, then trigger the keys
   102  			// events in a way that is specific to input keys
   103  			// to not block other inputs.
   104  			if err = udevadmTrigger("--property-match=ID_INPUT_KEY=1", "--property-match=ID_INPUT_KEYBOARD!=1"); err != nil {
   105  				return fmt.Errorf("cannot run udev triggers for keys: %s", err)
   106  			}
   107  		}
   108  		// FIXME: if not already triggered, trigger the joystick property if it
   109  		// wasn't already since we are not able to detect interfaces that are
   110  		// removed and set subsystemTriggers correctly. When we can, remove
   111  		// this. Allows joysticks to be removed from the device cgroup on
   112  		// interface disconnect.
   113  		if err := udevadmTrigger("--property-match=ID_INPUT_JOYSTICK=1"); err != nil {
   114  			return fmt.Errorf("cannot run udev triggers for joysticks: %s", err)
   115  		}
   116  	}
   117  
   118  	// give our triggered events a chance to be handled before exiting.
   119  	// Ignore errors since we don't want to error on still pending events.
   120  	_ = exec.Command("udevadm", "settle", "--timeout=10").Run()
   121  
   122  	return nil
   123  }