From 7c39a1bd21bd359a4b5a3b12cef94d1142842f15 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 10 Aug 2018 16:56:45 +0200 Subject: [PATCH] drop use of libudev in favour of udevadm output parsing Using libudev makes the packaging more complicated (now we need to ship libudev in the snap) and is also not really needed because we do not use any of the dynamic niceness of libudev. We just use it to detect removable devices which we can equally well do with the output of udevadm. So instead of using a cool and small cgo based libgudev wrapper we go back to good old text parsing. --- godd.go | 17 ++++-- snapcraft.yaml | 6 +- udev/udev.go | 148 +++++++++++++++++++++++++++++-------------------- 3 files changed, 100 insertions(+), 71 deletions(-) diff --git a/godd.go b/godd.go index 3d5e3b2..d5b2664 100644 --- a/godd.go +++ b/godd.go @@ -160,26 +160,33 @@ func ddAtoi(s string) (int64, error) { return n, err } -func findNonCdromRemovableDeviceFiles() (res []string) { - c := udev.New(nil) - for _, d := range c.QueryBySubsystem("block") { +func findNonCdromRemovableDeviceFiles() (res []string, err error) { + devices, err := udev.QueryBySubsystem("block") + if err != nil { + return nil, err + } + for _, d := range devices { if d.GetSysfsAttr("removable") == "1" && d.GetProperty("ID_CDROM") != "1" { res = append(res, d.GetDeviceFile()) } } - return res + return res, nil } func parseArgs(args []string) (*ddOpts, error) { // support: auto-detect removable devices if len(args) == 1 { + devices, err := findNonCdromRemovableDeviceFiles() + if err != nil { + return nil, err + } fmt.Printf(` No target selected, detected the following removable device: %s -`, strings.Join(findNonCdromRemovableDeviceFiles(), "\n ")) +`, strings.Join(devices, "\n ")) return nil, fmt.Errorf("please select target device") } diff --git a/snapcraft.yaml b/snapcraft.yaml index ce15852..7834bb4 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -7,7 +7,7 @@ description: | This means it is possible to e.g. install a Ubuntu Core image via godd http://cdimage.../ubuntu-core-16-amd64.img.xz /dev/sdc -version: 0.6 +version: 0.7 confinement: devmode apps: @@ -19,7 +19,6 @@ parts: plugin: go source: . go-importpath: github.com/mvo5/godd - stage-packages: [libgudev-1.0-dev] override-build: | # the horror the horror sudo mv /usr/bin/go /usr/bin/go.system @@ -30,7 +29,4 @@ parts: build-snaps: - go prime: - - usr/lib/*/libgudev-1.0.so* - - usr/lib/*/libobject-2.0.so* - - usr/lib/*/libglib-2.0.so* - bin/godd* diff --git a/udev/udev.go b/udev/udev.go index 4118997..03186fa 100644 --- a/udev/udev.go +++ b/udev/udev.go @@ -1,86 +1,112 @@ package udev -/* -#cgo pkg-config: gudev-1.0 - -#include -*/ -import "C" - import ( - "runtime" - "unsafe" + "bufio" + "bytes" + "fmt" + "io/ioutil" + "os/exec" + "path/filepath" + "strings" ) -type Client struct { - p *C.struct__GUdevClient -} +// This code used to use a tiny C wrapper around libudev. +// However - this makes the packaging more complicated (now we need +// to ship libudev in the snap) and is also not really needed because +// we do not use any of the dynamic niceness of libudev. We just use +// it to detect removable devices which we can equally well do with +// the output of udevadm. type Device struct { - p *C.struct__GUdevDevice + properties map[string]string } -func New(subsystems []string) *Client { - // convert go to char ** - cs := make([]*C.gchar, len(subsystems)+1) - for i := range subsystems { - cs[i] = (*C.gchar)(C.CString(subsystems[i])) +func (e *Device) GetSysfsAttr(attr string) string { + p := filepath.Join("/sys", e.properties["DEVPATH"], attr) + content, err := ioutil.ReadFile(p) + if err != nil { + return "" } + return strings.TrimSpace(string(content)) +} - p := C.g_udev_client_new((**C.gchar)(unsafe.Pointer(&cs[0]))) - client := &Client{ - p: p, - } - runtime.SetFinalizer(client, func(p *Client) { - C.g_object_unref((C.gpointer)(client.p)) - }) - return client +func (e *Device) GetProperty(name string) string { + return e.properties[name] +} + +func (e *Device) GetDeviceFile() string { + return e.properties["DEVNAME"] } -func (c *Client) QueryBySubsystem(subsystem string) []Device { - l := C.g_udev_client_query_by_subsystem(c.p, (*C.gchar)(C.CString(subsystem))) - result := make([]Device, C.g_list_length(l)) - for i := range result { - p := (*C.struct__GUdevDevice)(l.data) - device := Device{ - p: p, +func parseDevice(block string) (*Device, error) { + props := 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) + } + if strings.HasPrefix(line, "E: ") { + if kv := strings.SplitN(line[3:], "=", 2); len(kv) == 2 { + props[kv[0]] = kv[1] + } else { + return nil, fmt.Errorf("failed to parse udevadm output %q", line) + } } - runtime.SetFinalizer(&device, func(device *Device) { - C.g_object_unref((C.gpointer)(device.p)) - }) - result[i] = device - l = l.next } - C.g_list_free(l) - - return result + return &Device{properties: props}, nil } -func (d *Device) GetSysfsAttr(name string) string { - res := C.g_udev_device_get_sysfs_attr(d.p, (*C.gchar)(C.CString(name))) - return C.GoString((*C.char)(res)) -} +func QueryBySubsystem(sub string) ([]*Device, error) { + cmd := exec.Command("udevadm", "info", "-e") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } -func (d *Device) GetProperty(name string) string { - res := C.g_udev_device_get_property(d.p, (*C.gchar)(C.CString(name))) - return C.GoString((*C.char)(res)) -} + scanner := bufio.NewScanner(stdout) + scanner.Split(scanDoubleNewline) + if err := cmd.Start(); err != nil { + return nil, err + } -func (d *Device) GetName() string { - res := C.g_udev_device_get_name(d.p) - return C.GoString((*C.char)(res)) -} + var res []*Device + for scanner.Scan() { + block := scanner.Text() + env, err := parseDevice(block) + if err != nil { + return nil, err + } + if sub != "" && env.GetProperty("SUBSYSTEM") != sub { + continue + } + res = append(res, env) + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("cannot read udevadm output: %s", err) + } + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("cannot run udevadm command: %s", err) + } -func (d *Device) GetDeviceFile() string { - res := C.g_udev_device_get_device_file(d.p) - return C.GoString((*C.char)(res)) + return res, nil } -func (d *Device) GetParent() *Device { - res := C.g_udev_device_get_parent(d.p) - if res == nil { - return nil +// helpers + +// udevadm output scanner (all devices are separated via \n\n) +func scanDoubleNewline(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil } - return &Device{p: res} + 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 }