github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/interfaces/hotplug/udevadm.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 hotplug
    21  
    22  import (
    23  	"bufio"
    24  	"bytes"
    25  	"fmt"
    26  	"os/exec"
    27  	"strings"
    28  )
    29  
    30  // udevadm export output is divided in per-device blocks, blocks are separated
    31  // with empty lines, each block starts with device path (P:) line, within a block
    32  // there is one attribute per line, example:
    33  //
    34  // P: /devices/virtual/workqueue/nvme-wq
    35  // E: DEVPATH=/devices/virtual/workqueue/nvme-wq
    36  // E: SUBSYSTEM=workqueue
    37  // <empty-line>
    38  // P: /devices/virtual/block/dm-1
    39  // N: dm-1
    40  // S: disk/by-id/dm-name-linux-root
    41  // E: DEVNAME=/dev/dm-1
    42  // E: USEC_INITIALIZED=8899394
    43  // <empty-line>
    44  
    45  var udevadmBin = `udevadm`
    46  
    47  func parseEnvBlock(block string) (map[string]string, error) {
    48  	env := make(map[string]string)
    49  	for i, line := range strings.Split(block, "\n") {
    50  		if i == 0 && !strings.HasPrefix(line, "P: ") {
    51  			return nil, fmt.Errorf("no device block marker found before %q", line)
    52  		}
    53  		// We are only interested in 'E' properties as they carry all the interesting data,
    54  		// including DEVPATH and DEVNAME which seem to mirror the 'P' and 'N' values.
    55  		if strings.HasPrefix(line, "E: ") {
    56  			if kv := strings.SplitN(line[3:], "=", 2); len(kv) == 2 {
    57  				env[kv[0]] = kv[1]
    58  			} else {
    59  				return nil, fmt.Errorf("cannot parse udevadm output %q", line)
    60  			}
    61  		}
    62  	}
    63  	return env, nil
    64  }
    65  
    66  func scanDoubleNewline(data []byte, atEOF bool) (advance int, token []byte, err error) {
    67  	if atEOF && len(data) == 0 {
    68  		return 0, nil, nil
    69  	}
    70  
    71  	if i := bytes.Index(data, []byte("\n\n")); i >= 0 {
    72  		// we found data
    73  		return i + 2, data[0:i], nil
    74  	}
    75  
    76  	// If we're at EOF, return what is left.
    77  	if atEOF {
    78  		return len(data), data, nil
    79  	}
    80  	// Request more data.
    81  	return 0, nil, nil
    82  }
    83  
    84  func parseUdevadmOutput(cmd *exec.Cmd, rd *bufio.Scanner) (devices []*HotplugDeviceInfo, parseErrors []error) {
    85  	for rd.Scan() {
    86  		block := rd.Text()
    87  		env, err := parseEnvBlock(block)
    88  		if err != nil {
    89  			parseErrors = append(parseErrors, err)
    90  		} else {
    91  			dev, err := NewHotplugDeviceInfo(env)
    92  			if err != nil {
    93  				parseErrors = append(parseErrors, err)
    94  			} else {
    95  				devices = append(devices, dev)
    96  			}
    97  		}
    98  	}
    99  
   100  	if err := rd.Err(); err != nil {
   101  		parseErrors = append(parseErrors, fmt.Errorf("cannot read udevadm output: %s", err))
   102  	}
   103  	if err := cmd.Wait(); err != nil {
   104  		parseErrors = append(parseErrors, fmt.Errorf("cannot read udevadm output: %s", err))
   105  	}
   106  	return devices, parseErrors
   107  }
   108  
   109  // EnumerateExistingDevices enumerates all devices by parsing 'udevadm info -e' command output.
   110  // Non-fatal parsing errors are reported via parseErrors and they don't stop the parser.
   111  func EnumerateExistingDevices() (devices []*HotplugDeviceInfo, parseErrors []error, fatalError error) {
   112  	cmd := exec.Command(udevadmBin, "info", "-e")
   113  	stdout, err := cmd.StdoutPipe()
   114  	if err != nil {
   115  		return nil, nil, err
   116  	}
   117  
   118  	rd := bufio.NewScanner(stdout)
   119  	rd.Split(scanDoubleNewline)
   120  	if err = cmd.Start(); err != nil {
   121  		return nil, nil, err
   122  	}
   123  
   124  	devices, parseErrors = parseUdevadmOutput(cmd, rd)
   125  	return devices, parseErrors, nil
   126  }