github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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
    64  // MockOnlySessionBusAvailable.
    65  var SessionBus = func() (*dbus.Conn, error) {
    66  	if isSessionBusLikelyPresent() {
    67  		return dbus.SessionBus()
    68  	}
    69  	return nil, fmt.Errorf("cannot find session bus")
    70  }
    71  
    72  // SystemBus is like dbus.SystemBus and is provided for completeness.
    73  //
    74  // This function is mockable by either MockConnections or
    75  // MockOnlySystemBusAvailable.
    76  var SystemBus = func() (*dbus.Conn, error) {
    77  	return dbus.SystemBus()
    78  }
    79  
    80  // MockConnections mocks the connection functions system and session buses.
    81  func MockConnections(system, session func() (*dbus.Conn, error)) (restore func()) {
    82  	oldSystem := SystemBus
    83  	oldSession := SessionBus
    84  	SystemBus = system
    85  	SessionBus = session
    86  	return func() {
    87  		SystemBus = oldSystem
    88  		SessionBus = oldSession
    89  	}
    90  }
    91  
    92  // MockOnlySystemBusAvailable makes SystemBus return the given connection.
    93  //
    94  // In addition calling SessionBus will panic.
    95  func MockOnlySystemBusAvailable(conn *dbus.Conn) (restore func()) {
    96  	systemBus := func() (*dbus.Conn, error) { return conn, nil }
    97  	sessionBus := func() (*dbus.Conn, error) {
    98  		panic("DBus session bus should not have been used")
    99  	}
   100  	return MockConnections(systemBus, sessionBus)
   101  }
   102  
   103  // MockOnlySessionBusAvailable makes SessionBus return the given connection.
   104  //
   105  // In addition calling SystemBus will panic.
   106  func MockOnlySessionBusAvailable(conn *dbus.Conn) (restore func()) {
   107  	systemBus := func() (*dbus.Conn, error) {
   108  		panic("DBus system bus should not have been used")
   109  	}
   110  	sessionBus := func() (*dbus.Conn, error) { return conn, nil }
   111  	return MockConnections(systemBus, sessionBus)
   112  }