Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions godd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
6 changes: 1 addition & 5 deletions snapcraft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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*
148 changes: 87 additions & 61 deletions udev/udev.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,112 @@
package udev

/*
#cgo pkg-config: gudev-1.0

#include <gudev/gudev.h>
*/
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
}