github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/dbusutil/dbusutil.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package dbusutil
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  
    26  	"github.com/godbus/dbus"
    27  
    28  	"github.com/snapcore/snapd/dirs"
    29  )
    30  
    31  // isSessionBusLikelyPresent checks for the apparent availability of DBus session bus.
    32  //
    33  // The code matches what go-dbus does when it tries to detect the session bus:
    34  // - the presence of the environment variable DBUS_SESSION_BUS_ADDRESS
    35  // - the presence of the bus socket address in the file /run/user/UID/dbus-session
    36  // - the presence of the bus socket in /run/user/UID/bus
    37  func isSessionBusLikelyPresent() bool {
    38  	if address := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); address != "" {
    39  		return true
    40  	}
    41  	uid := os.Getuid()
    42  	if fi, err := os.Stat(fmt.Sprintf("%s/%d/dbus-session", dirs.XdgRuntimeDirBase, uid)); err == nil {
    43  		if fi.Mode()&os.ModeType == 0 {
    44  			return true
    45  		}
    46  	}
    47  	if fi, err := os.Stat(fmt.Sprintf("%s/%d/bus", dirs.XdgRuntimeDirBase, uid)); err == nil {
    48  		if fi.Mode()&os.ModeType == os.ModeSocket {
    49  			return true
    50  		}
    51  	}
    52  	return false
    53  }
    54  
    55  // SessionBus is like dbus.SessionBus but it avoids auto-starting
    56  // a new dbus-daemon when a bus is not already available.
    57  //
    58  // The go-dbus package will launch a session bus instance on demand when none
    59  // is present, something we do not want to do. In all contexts where there is a need
    60  //  to use the session bus, we expect session bus daemon to have been started and
    61  // managed by the corresponding user session manager.
    62  //
    63  // This function is mockable by either MockConnections or MockSessionBus.
    64  var SessionBus = func() (*dbus.Conn, error) {
    65  	if isSessionBusLikelyPresent() {
    66  		return dbus.SessionBus()
    67  	}
    68  	return nil, fmt.Errorf("cannot find session bus")
    69  }
    70  
    71  // SystemBus is like dbus.SystemBus and is provided for completeness.
    72  //
    73  // This function is mockable by either MockConnections or MockSystemBus.
    74  var SystemBus = func() (*dbus.Conn, error) {
    75  	return dbus.SystemBus()
    76  }
    77  
    78  // MockConnections mocks the connection functions system and session buses.
    79  func MockConnections(system, session func() (*dbus.Conn, error)) (restore func()) {
    80  	oldSystem := SystemBus
    81  	oldSession := SessionBus
    82  	SystemBus = system
    83  	SessionBus = session
    84  	return func() {
    85  		SystemBus = oldSystem
    86  		SessionBus = oldSession
    87  	}
    88  }
    89  
    90  // MockOnlySystemBusAvailable makes SystemBus return the given connection.
    91  //
    92  // In addition calling SessionBus will panic.
    93  func MockOnlySystemBusAvailable(conn *dbus.Conn) (restore func()) {
    94  	systemBus := func() (*dbus.Conn, error) { return conn, nil }
    95  	sessionBus := func() (*dbus.Conn, error) {
    96  		panic("DBus session bus should not have been used")
    97  	}
    98  	return MockConnections(systemBus, sessionBus)
    99  }
   100  
   101  // MockOnlySessionBusAvailable makes SessionBus return the given connection.
   102  //
   103  // In addition calling SystemBus will panic.
   104  func MockOnlySessionBusAvailable(conn *dbus.Conn) (restore func()) {
   105  	systemBus := func() (*dbus.Conn, error) {
   106  		panic("DBus system bus should not have been used")
   107  	}
   108  	sessionBus := func() (*dbus.Conn, error) { return conn, nil }
   109  	return MockConnections(systemBus, sessionBus)
   110  }