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 }