package udisks import ( "bytes" "fmt" "github.com/godbus/dbus/v5" ) // Client communicates with udisks2 over D-Bus. type Client struct { conn *dbus.Conn } // NewClient connects to the system D-Bus and returns a udisks2 client. func NewClient() (*Client, error) { conn, err := dbus.SystemBus() if err != nil { return nil, fmt.Errorf("cannot connect to udisks2 — is the udisks2 service running? (%w)", err) } return &Client{conn: conn}, nil } // Close closes the D-Bus connection. func (c *Client) Close() { c.conn.Close() } // FindDevice finds a block device by UUID (if non-empty) or device path. func (c *Client) FindDevice(uuid, pathOrName string) (*BlockDevice, error) { devices, err := c.listBlockDevices() if err != nil { return nil, err } if uuid != "" { for i := range devices { if devices[i].UUID == uuid { return &devices[i], nil } } return nil, fmt.Errorf("no device with UUID %s — run 'arca status' to list devices", uuid) } for i := range devices { if devices[i].DevicePath == pathOrName { return &devices[i], nil } } return nil, fmt.Errorf("device %s not found — run 'arca status' to list devices or 'arca init' to generate config", pathOrName) } // ListEncryptedDevices returns all block devices with the Encrypted interface. func (c *Client) ListEncryptedDevices() ([]BlockDevice, error) { devices, err := c.listBlockDevices() if err != nil { return nil, err } var encrypted []BlockDevice for _, d := range devices { if d.HasEncrypted { encrypted = append(encrypted, d) } } return encrypted, nil } // CleartextDevice finds the unlocked cleartext device for a LUKS device. func (c *Client) CleartextDevice(dev *BlockDevice) (*BlockDevice, error) { devices, err := c.listBlockDevices() if err != nil { return nil, err } for i := range devices { if devices[i].CryptoBackingDevice == dev.ObjectPath { return &devices[i], nil } } return nil, fmt.Errorf("no cleartext device for %s (is it unlocked?)", dev.DevicePath) } // MountPoint returns the current mount point of a device, or "" if not mounted. func (c *Client) MountPoint(dev *BlockDevice) (string, error) { obj := c.conn.Object(busName, dev.ObjectPath) variant, err := obj.GetProperty(ifaceFilesystem + ".MountPoints") if err != nil { return "", err } mountPoints, ok := variant.Value().([][]byte) if !ok || len(mountPoints) == 0 { return "", nil } return string(bytes.TrimRight(mountPoints[0], "\x00")), nil } // IsMounted returns the mount point and true if the device is mounted. func (c *Client) IsMounted(dev *BlockDevice) (string, bool) { if !dev.HasFilesystem { return "", false } mp, err := c.MountPoint(dev) if err != nil || mp == "" { return "", false } return mp, true } // DeviceAtPath returns the block device at the given D-Bus object path. func (c *Client) DeviceAtPath(path dbus.ObjectPath) (*BlockDevice, error) { devices, err := c.listBlockDevices() if err != nil { return nil, err } for i := range devices { if devices[i].ObjectPath == path { return &devices[i], nil } } return nil, fmt.Errorf("device at %s not found", path) } // RootBackingDevices returns the object paths of LUKS devices that back // the root filesystem. Used to exclude them from init config generation. func (c *Client) RootBackingDevices() ([]dbus.ObjectPath, error) { devices, err := c.listBlockDevices() if err != nil { return nil, err } var backing []dbus.ObjectPath for i := range devices { if !devices[i].HasFilesystem { continue } mp, err := c.MountPoint(&devices[i]) if err != nil || mp != "/" { continue } if devices[i].CryptoBackingDevice != "" && devices[i].CryptoBackingDevice != "/" { backing = append(backing, devices[i].CryptoBackingDevice) } } return backing, nil } func (c *Client) listBlockDevices() ([]BlockDevice, error) { obj := c.conn.Object(busName, dbus.ObjectPath(objectPrefix)) var managed map[dbus.ObjectPath]map[string]map[string]dbus.Variant err := obj.Call(ifaceObjectManager+".GetManagedObjects", 0).Store(&managed) if err != nil { return nil, fmt.Errorf("listing devices — is the udisks2 service running? (%w)", err) } var devices []BlockDevice for path, ifaces := range managed { blockProps, ok := ifaces[ifaceBlock] if !ok { continue } dev := BlockDevice{ObjectPath: path} if v, ok := blockProps["Device"]; ok { if bs, ok := v.Value().([]byte); ok { dev.DevicePath = string(bytes.TrimRight(bs, "\x00")) } } if v, ok := blockProps["IdUUID"]; ok { if s, ok := v.Value().(string); ok { dev.UUID = s } } if v, ok := blockProps["CryptoBackingDevice"]; ok { if p, ok := v.Value().(dbus.ObjectPath); ok { dev.CryptoBackingDevice = p } } _, dev.HasEncrypted = ifaces[ifaceEncrypted] _, dev.HasFilesystem = ifaces[ifaceFilesystem] devices = append(devices, dev) } return devices, nil }