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 }