1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package hotplug
import (
"bufio"
"bytes"
"fmt"
"os/exec"
"strings"
)
// udevadm export output is divided in per-device blocks, blocks are separated
// with empty lines, each block starts with device path (P:) line, within a block
// there is one attribute per line, example:
//
// P: /devices/virtual/workqueue/nvme-wq
// E: DEVPATH=/devices/virtual/workqueue/nvme-wq
// E: SUBSYSTEM=workqueue
// <empty-line>
// P: /devices/virtual/block/dm-1
// N: dm-1
// S: disk/by-id/dm-name-linux-root
// E: DEVNAME=/dev/dm-1
// E: USEC_INITIALIZED=8899394
// <empty-line>
var udevadmBin = `udevadm`
func parseEnvBlock(block string) (map[string]string, error) {
env := make(map[string]string)
for i, line := range strings.Split(block, "\n") {
if i == 0 && !strings.HasPrefix(line, "P: ") {
return nil, fmt.Errorf("no device block marker found before %q", line)
}
// We are only interested in 'E' properties as they carry all the interesting data,
// including DEVPATH and DEVNAME which seem to mirror the 'P' and 'N' values.
if strings.HasPrefix(line, "E: ") {
if kv := strings.SplitN(line[3:], "=", 2); len(kv) == 2 {
env[kv[0]] = kv[1]
} else {
return nil, fmt.Errorf("cannot parse udevadm output %q", line)
}
}
}
return env, nil
}
func scanDoubleNewline(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.Index(data, []byte("\n\n")); i >= 0 {
// we found data
return i + 2, data[0:i], nil
}
// If we're at EOF, return what is left.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}
func parseUdevadmOutput(cmd *exec.Cmd, rd *bufio.Scanner) (devices []*HotplugDeviceInfo, parseErrors []error) {
for rd.Scan() {
block := rd.Text()
env, err := parseEnvBlock(block)
if err != nil {
parseErrors = append(parseErrors, err)
} else {
dev, err := NewHotplugDeviceInfo(env)
if err != nil {
parseErrors = append(parseErrors, err)
} else {
devices = append(devices, dev)
}
}
}
if err := rd.Err(); err != nil {
parseErrors = append(parseErrors, fmt.Errorf("cannot read udevadm output: %s", err))
}
if err := cmd.Wait(); err != nil {
parseErrors = append(parseErrors, fmt.Errorf("cannot read udevadm output: %s", err))
}
return devices, parseErrors
}
// EnumerateExistingDevices enumerates all devices by parsing 'udevadm info -e' command output.
// Non-fatal parsing errors are reported via parseErrors and they don't stop the parser.
func EnumerateExistingDevices() (devices []*HotplugDeviceInfo, parseErrors []error, fatalError error) {
cmd := exec.Command(udevadmBin, "info", "-e")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
rd := bufio.NewScanner(stdout)
rd.Split(scanDoubleNewline)
if err = cmd.Start(); err != nil {
return nil, nil, err
}
devices, parseErrors = parseUdevadmOutput(cmd, rd)
return devices, parseErrors, nil
}
|