| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Package crosdisks provides an interface to talk to cros_disks service |
| // via D-Bus. |
| package crosdisks |
| |
| import ( |
| "context" |
| |
| "github.com/godbus/dbus" |
| |
| "chromiumos/tast/errors" |
| "chromiumos/tast/local/dbusutil" |
| ) |
| |
| const ( |
| dbusName = "org.chromium.CrosDisks" |
| dbusPath = "/org/chromium/CrosDisks" |
| dbusInterface = "org.chromium.CrosDisks" |
| ) |
| |
| // CrosDisks is used to interact with the CrosDisks process over D-Bus. |
| // For detailed spec of each D-Bus method, please find |
| // src/platform2/cros-disks/dbus_bindings/org.chromium.CrosDisks.xml. |
| type CrosDisks struct { |
| conn *dbus.Conn |
| obj dbus.BusObject |
| } |
| |
| // New connects to CrosDisks via D-Bus and returns a crosDisks object. |
| func New(ctx context.Context) (*CrosDisks, error) { |
| conn, obj, err := dbusutil.Connect(ctx, dbusName, dbusPath) |
| if err != nil { |
| return nil, err |
| } |
| return &CrosDisks{conn, obj}, nil |
| } |
| |
| // Close connection to CrosDisks D-Bus service. |
| func (c *CrosDisks) Close() error { |
| // Do not close the connection as it's a singleton shared with other tests. |
| return nil |
| } |
| |
| // call is a thin wrapper of CallWithContext for convenience. |
| func (c *CrosDisks) call(ctx context.Context, method string, args ...interface{}) *dbus.Call { |
| return c.obj.CallWithContext(ctx, dbusInterface+"."+method, 0, args...) |
| } |
| |
| // EnumerateDevices calls CrosDisks.EnumerateDevices D-Bus method. |
| func (c *CrosDisks) EnumerateDevices(ctx context.Context) ([]string, error) { |
| var ds []string |
| if err := c.call(ctx, "EnumerateDevices").Store(&ds); err != nil { |
| return nil, err |
| } |
| return ds, nil |
| } |
| |
| // GetDeviceProperties calls CrosDisks.GetDeviceProperties D-Bus method. |
| func (c *CrosDisks) GetDeviceProperties(ctx context.Context, devicePath string) (map[string]dbus.Variant, error) { |
| var prop map[string]dbus.Variant |
| if err := c.call(ctx, "GetDeviceProperties", devicePath).Store(&prop); err != nil { |
| return nil, err |
| } |
| return prop, nil |
| } |
| |
| // Mount calls CrosDisks.Mount D-Bus method. |
| func (c *CrosDisks) Mount(ctx context.Context, devicePath, fsType string, options []string) error { |
| return c.call(ctx, "Mount", devicePath, fsType, options).Err |
| } |
| |
| // Unmount calls CrosDisks.Unmount D-Bus method. |
| func (c *CrosDisks) Unmount(ctx context.Context, devicePath string, options []string) (uint32, error) { |
| var status uint32 |
| if err := c.call(ctx, "Unmount", devicePath, options).Store(&status); err != nil { |
| return 0, err |
| } |
| return status, nil |
| } |
| |
| // Rename calls CrosDisks.Rename D-Bus method. |
| func (c *CrosDisks) Rename(ctx context.Context, path, volumeName string) error { |
| return c.call(ctx, "Rename", path, volumeName).Err |
| } |
| |
| // Format calls CrosDisks.Format D-Bus method. |
| func (c *CrosDisks) Format(ctx context.Context, path, fsType string, options []string) error { |
| return c.call(ctx, "Format", path, fsType, options).Err |
| } |
| |
| // AddDeviceToAllowlist calls CrosDisks.AddDeviceToAllowlist D-Bus method. |
| func (c *CrosDisks) AddDeviceToAllowlist(ctx context.Context, devicePath string) error { |
| return c.call(ctx, "AddDeviceToAllowlist", devicePath).Err |
| } |
| |
| // RemoveDeviceFromAllowlist calls CrosDisks.RemoveDeviceFromAllowlist D-Bus method. |
| func (c *CrosDisks) RemoveDeviceFromAllowlist(ctx context.Context, devicePath string) error { |
| return c.call(ctx, "RemoveDeviceFromAllowlist", devicePath).Err |
| } |
| |
| // MountCompletedWatcher is a thin wrapper of dbusutil.SignalWatcher to return |
| // signal content as MountCompleted. |
| type MountCompletedWatcher struct { |
| dbusutil.SignalWatcher |
| } |
| |
| // DeviceOperationCompletionWatcher is a thin wrapper of dbusutil.SignalWatcher to return |
| // signal content as DeviceOperationCompleted. |
| type DeviceOperationCompletionWatcher struct { |
| dbusutil.SignalWatcher |
| } |
| |
| // See MountErrorType defined in system_api/dbus/cros-disks/dbus-constants.h |
| const ( |
| MountErrorNone uint32 = 0 |
| MountErrorPathNotMounted uint32 = 6 |
| MountErrorMountProgramFailed uint32 = 12 |
| MountErrorNeedPassword uint32 = 13 |
| MountErrorInvalidDevicePath uint32 = 100 |
| ) |
| |
| // MountCompleted holds the body data of MountCompleted signal. |
| type MountCompleted struct { |
| Status uint32 |
| SourcePath string |
| SourceType uint32 |
| MountPath string |
| } |
| |
| // DeviceOperationCompleted holds status of the operation done. |
| type DeviceOperationCompleted struct { |
| Status uint32 |
| Device string |
| } |
| |
| // Wait waits for the MountCompleted signal, and returns the body data of the |
| // received signal. |
| func (m *MountCompletedWatcher) Wait(ctx context.Context) (MountCompleted, error) { |
| var ret MountCompleted |
| select { |
| case s := <-m.Signals: |
| if err := dbus.Store(s.Body, &ret.Status, &ret.SourcePath, &ret.SourceType, &ret.MountPath); err != nil { |
| return ret, errors.Wrap(err, "failed to store MountCompleted data") |
| } |
| return ret, nil |
| case <-ctx.Done(): |
| return ret, errors.Wrap(ctx.Err(), "didn't get MountCompleted signal") |
| } |
| } |
| |
| // Wait waits for the DeviceOperationCompleted signal, and returns the body data of the received signal. |
| func (m *DeviceOperationCompletionWatcher) Wait(ctx context.Context) (DeviceOperationCompleted, error) { |
| var ret DeviceOperationCompleted |
| select { |
| case s := <-m.Signals: |
| if err := dbus.Store(s.Body, &ret.Status, &ret.Device); err != nil { |
| return ret, errors.Wrap(err, "failed to store DeviceOperationCompleted data") |
| } |
| return ret, nil |
| case <-ctx.Done(): |
| return ret, errors.Wrap(ctx.Err(), "didn't get DeviceOperationCompleted signal") |
| } |
| } |
| |
| // WatchMountCompleted registers the signal watching and returns its watcher |
| // instance. |
| func (c *CrosDisks) WatchMountCompleted(ctx context.Context) (*MountCompletedWatcher, error) { |
| spec := dbusutil.MatchSpec{ |
| Type: "signal", |
| Path: dbusPath, |
| Interface: dbusInterface, |
| Member: "MountCompleted", |
| } |
| w, err := dbusutil.NewSignalWatcher(ctx, c.conn, spec) |
| if err != nil { |
| return nil, err |
| } |
| return &MountCompletedWatcher{*w}, nil |
| } |
| |
| // MountAndWaitForCompletion mounts and waits for the response signal. |
| // This is a convenience method for the odd CrosDisks' mounting API. |
| func (c *CrosDisks) MountAndWaitForCompletion(ctx context.Context, devicePath, fsType string, options []string) (MountCompleted, error) { |
| w, err := c.WatchMountCompleted(ctx) |
| if err != nil { |
| return MountCompleted{}, err |
| } |
| defer w.Close(ctx) |
| |
| if err := c.Mount(ctx, devicePath, fsType, options); err != nil { |
| return MountCompleted{}, err |
| } |
| |
| m, err := w.Wait(ctx) |
| if err != nil { |
| return MountCompleted{}, err |
| } |
| return m, nil |
| } |
| |
| // doSomethingAndWaitForCompletion performs an operation and waits for the response signal. |
| // This is a convenience method for the odd CrosDisks' signal-based API. |
| func (c *CrosDisks) doSomethingAndWaitForCompletion(ctx context.Context, f func() error, signalName string) (DeviceOperationCompleted, error) { |
| spec := dbusutil.MatchSpec{ |
| Type: "signal", |
| Path: dbusPath, |
| Interface: dbusInterface, |
| Member: signalName, |
| } |
| s, err := dbusutil.NewSignalWatcher(ctx, c.conn, spec) |
| if err != nil { |
| return DeviceOperationCompleted{}, err |
| } |
| w := DeviceOperationCompletionWatcher{*s} |
| defer w.Close(ctx) |
| |
| if err := f(); err != nil { |
| return DeviceOperationCompleted{}, err |
| } |
| |
| r, err := w.Wait(ctx) |
| if err != nil { |
| return DeviceOperationCompleted{}, err |
| } |
| return r, nil |
| } |
| |
| // RenameAndWaitForCompletion renames volume and waits for the response signal. |
| func (c *CrosDisks) RenameAndWaitForCompletion(ctx context.Context, path, volumeName string) (DeviceOperationCompleted, error) { |
| return c.doSomethingAndWaitForCompletion(ctx, func() error { return c.Rename(ctx, path, volumeName) }, "RenameCompleted") |
| } |
| |
| // FormatAndWaitForCompletion formats volume and waits for the response signal. |
| func (c *CrosDisks) FormatAndWaitForCompletion(ctx context.Context, path, fsType string, options []string) (DeviceOperationCompleted, error) { |
| return c.doSomethingAndWaitForCompletion(ctx, func() error { return c.Format(ctx, path, fsType, options) }, "FormatCompleted") |
| } |