// Copyright 2019 CoreOS, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package import1 provides integration with the systemd-importd API. See https://www.freedesktop.org/wiki/Software/systemd/importd/ // Note: Requires systemd v231 or higher package import1 import ( "fmt" "os" "strconv" "strings" "github.com/godbus/dbus/v5" ) const ( dbusInterface = "org.freedesktop.import1.Manager" dbusPath = "/org/freedesktop/import1" ) // Conn is a connection to systemds dbus endpoint. type Conn struct { conn *dbus.Conn object dbus.BusObject } // Transfer is an object in dbus for an import, export or download operation. type Transfer struct { Id uint32 // The numeric transfer ID of the transfer object Path dbus.ObjectPath // The dbus objectPath for the transfer } // TransferStatus is the status for an import, export or download operation. type TransferStatus struct { Id uint32 // The numeric transfer ID of the transfer object Local string // The local container name of this transfer Remote string // The remote source (in case of download: the URL, in case of import/export a string describing the file descriptor passed in) Type string // The type of operation Verify string // The selected verification setting, and is only defined for download operations Progress float64 // The current progress of the transfer, as a value between 0.0 and 1.0 } // New establishes a connection to the system bus and authenticates. // Note: systemd-importd will be activated via D-Bus, we don't need to check service status. func New() (*Conn, error) { c := new(Conn) if err := c.initConnection(); err != nil { return nil, err } return c, nil } // Connected returns whether conn is connected func (c *Conn) Connected() bool { return c.conn.Connected() } func (c *Conn) initConnection() error { var err error c.conn, err = dbus.SystemBusPrivate() if err != nil { return err } // Only use EXTERNAL method, and hardcode the uid (not username) // to avoid a username lookup (which requires a dynamically linked // libc) methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} err = c.conn.Auth(methods) if err != nil { c.conn.Close() return err } err = c.conn.Hello() if err != nil { c.conn.Close() return err } c.object = c.conn.Object("org.freedesktop.import1", dbus.ObjectPath(dbusPath)) return nil } // getResult will return: // - transfer object (*Transfer) // - err (error) func (c *Conn) getResult(method string, args ...interface{}) (*Transfer, error) { result := c.object.Call(fmt.Sprintf("%s.%s", dbusInterface, method), 0, args...) if result.Err != nil { return nil, result.Err } if len(result.Body) < 2 { return nil, fmt.Errorf("invalid number of result fields: %v", result.Body) } ok := false transfer := &Transfer{} transfer.Id, ok = result.Body[0].(uint32) if !ok { return nil, fmt.Errorf("unable to convert dbus response '%v' to uint32", result.Body[0]) } transfer.Path, ok = result.Body[1].(dbus.ObjectPath) if !ok { return nil, fmt.Errorf("unable to convert dbus response '%v' to dbus.ObjectPath", result.Body[1]) } return transfer, nil } // ImportTar imports a tar into systemd-importd. func (c *Conn) ImportTar( f *os.File, local_name string, force, read_only bool, ) (*Transfer, error) { return c.getResult("ImportTar", dbus.UnixFD(f.Fd()), local_name, force, read_only) } // ImportRaw imports a raw image into systemd-importd. func (c *Conn) ImportRaw( f *os.File, local_name string, force, read_only bool, ) (*Transfer, error) { return c.getResult("ImportRaw", dbus.UnixFD(f.Fd()), local_name, force, read_only) } // ExportTar exports a tar from systemd-importd. func (c *Conn) ExportTar( local_name string, f *os.File, format string, ) (*Transfer, error) { return c.getResult("ExportTar", local_name, dbus.UnixFD(f.Fd()), format) } // ExportRaw exports a raw image from systemd-importd. func (c *Conn) ExportRaw( local_name string, f *os.File, format string, ) (*Transfer, error) { return c.getResult("ExportRaw", local_name, dbus.UnixFD(f.Fd()), format) } // PullTar pulls a tar into systemd-importd. func (c *Conn) PullTar( url, local_name, verify_mode string, force bool, ) (*Transfer, error) { return c.getResult("PullTar", url, local_name, verify_mode, force) } // PullRaw pulls a raw image into systemd-importd. func (c *Conn) PullRaw( url, local_name, verify_mode string, force bool, ) (*Transfer, error) { return c.getResult("PullRaw", url, local_name, verify_mode, force) } // ListTransfers will list ongoing import, export or download operations. func (c *Conn) ListTransfers() ([]TransferStatus, error) { result := make([][]interface{}, 0) if err := c.object.Call(dbusInterface+".ListTransfers", 0).Store(&result); err != nil { return nil, err } transfers := make([]TransferStatus, 0) for _, v := range result { transfer, err := transferFromInterfaces(v) if err != nil { return nil, err } transfers = append(transfers, *transfer) } return transfers, nil } // CancelTransfer will cancel an ongoing import, export or download operations. func (c *Conn) CancelTransfer(transfer_id uint32) error { return c.object.Call(dbusInterface+".CancelTransfer", 0, transfer_id).Err } func transferFromInterfaces(transfer []interface{}) (*TransferStatus, error) { // Verify may be not defined in response. if len(transfer) < 5 { return nil, fmt.Errorf("invalid number of transfer fields: %d", len(transfer)) } ok := false ret := &TransferStatus{} ret.Id, ok = transfer[0].(uint32) if !ok { return nil, fmt.Errorf("failed to typecast transfer field 0 to uint32") } ret.Local, ok = transfer[1].(string) if !ok { return nil, fmt.Errorf("failed to typecast transfer field 1 to string") } ret.Remote, ok = transfer[2].(string) if !ok { return nil, fmt.Errorf("failed to typecast transfer field 2 to string") } ret.Type, ok = transfer[3].(string) if !ok { return nil, fmt.Errorf("failed to typecast transfer field 3 to string") } // Verify is only defined for download operations. // If operation is not pull, we should ignore Verify field. if !strings.HasPrefix(ret.Type, "pull-") { ret.Progress, ok = transfer[4].(float64) if !ok { return nil, fmt.Errorf("failed to typecast transfer field 4 to float64") } return ret, nil } ret.Verify, ok = transfer[4].(string) if !ok { return nil, fmt.Errorf("failed to typecast transfer field 4 to string") } ret.Progress, ok = transfer[5].(float64) if !ok { return nil, fmt.Errorf("failed to typecast transfer field 5 to float64") } return ret, nil }