1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-21 12:03:55 -08:00

New D-Bus functions to support systemd inhibitor locks

* doc/misc/dbus.texi (Top): Add "Inhibitor Locks" submenu.
Remove trailing period from chapter and section titles.
(Inhibitor Locks): New node.

* etc/NEWS: New D-Bus functions to support systemd inhibitor locks.
Presentational fixes and improvements.

* src/dbusbind.c (xd_registered_inhibitor_locks): New variable.
(Fdbus_make_inhibitor_lock, Fdbus_close_inhibitor_lock)
(Fdbus_registered_inhibitor_locks): New DEFUNs.  (Bug#79963)
(syms_of_dbusbind_for_pdumper): Initialize
`xd_registered_inhibitor_locks'.
(syms_of_dbusbind): Declare subroutines
`Sdbus_make_inhibitor_lock', `Sdbus_close_inhibitor_lock' and
`Sdbus_registered_inhibitor_locks'.  Declare symbol `Qdbus_call_method'.
staticpro `xd_registered_inhibitor_locks'.

* test/lisp/net/dbus-tests.el (dbus--test-systemd-service)
(dbus--test-systemd-path, dbus--test-systemd-manager-interface):
New defconsts.
(dbus-test10-inhibitor-locks): New test.
This commit is contained in:
Michael Albinus 2026-01-17 11:40:31 +01:00
parent 6287637ccd
commit ab77b4b60c
4 changed files with 348 additions and 29 deletions

View file

@ -64,6 +64,7 @@ another. An overview of D-Bus can be found at
* Alternative Buses:: Alternative buses and environments.
* Errors and Events:: Errors and events.
* Monitoring Messages:: Monitoring messages.
* Inhibitor Locks:: Inhibit system shutdowns and sleep states.
* Index:: Index including concepts, functions, variables.
* GNU Free Documentation License:: The license for this documentation.
@ -124,7 +125,7 @@ name could be @samp{org.gnu.Emacs.TextEditor} or
@node Inspection
@chapter Inspection of D-Bus services.
@chapter Inspection of D-Bus services
@cindex inspection
@menu
@ -139,7 +140,7 @@ name could be @samp{org.gnu.Emacs.TextEditor} or
@node Version
@section D-Bus version.
@section D-Bus version
D-Bus has evolved over the years. New features have been added with
new D-Bus versions. There are two variables, which allow the determination
@ -158,7 +159,7 @@ It is also @code{nil}, if it cannot be determined at runtime.
@node Bus names
@section Bus names.
@section Bus names
There are several basic functions which inspect the buses for
registered names. Internally they use the basic interface
@ -267,7 +268,7 @@ at D-Bus @var{bus}, as a string.
@node Introspection
@section Knowing the details of D-Bus services.
@section Knowing the details of D-Bus services
D-Bus services publish their interfaces. This can be retrieved and
analyzed during runtime, in order to understand the used
@ -483,7 +484,7 @@ If @var{object} has no @var{attribute}, the function returns
@node Nodes and Interfaces
@section Detecting object paths and interfaces.
@section Detecting object paths and interfaces
The first elements, to be introspected for a D-Bus object, are further
object paths and interfaces.
@ -593,7 +594,7 @@ data from a running system:
@node Methods and Signal
@section Applying the functionality.
@section Applying the functionality
Methods and signals are the communication means to D-Bus. The
following functions return their specifications.
@ -673,7 +674,7 @@ Example:
@node Properties and Annotations
@section What else to know about interfaces.
@section What else to know about interfaces
Interfaces can have properties. These can be exposed via the
@samp{org.freedesktop.DBus.Properties} interface@footnote{See
@ -894,7 +895,7 @@ An attribute value can be retrieved by
@node Arguments and Signatures
@section The final details.
@section The final details
Methods and signals have arguments. They are described in the
@code{arg} XML elements.
@ -962,7 +963,7 @@ non-@code{nil}, @var{direction} must be @samp{out}. Example:
@node Type Conversion
@chapter Mapping Lisp types and D-Bus types.
@chapter Mapping Lisp types and D-Bus types
@cindex type conversion
D-Bus method calls and signals accept usually several arguments as
@ -975,7 +976,7 @@ applied Lisp object @expansion{} D-Bus type for input parameters, and
D-Bus type @expansion{} Lisp object for output parameters.
@section Input parameters.
@section Input parameters
Input parameters for D-Bus methods and signals occur as arguments of a
Lisp function call. The following mapping to D-Bus types is
@ -1116,7 +1117,7 @@ lower-case hex digits. As a special case, "" is escaped to
@end defun
@section Output parameters.
@section Output parameters
Output parameters of D-Bus methods and signals are mapped to Lisp
objects.
@ -1199,7 +1200,7 @@ that string:
@node Synchronous Methods
@chapter Calling methods in a blocking way.
@chapter Calling methods in a blocking way
@cindex method calls, synchronous
@cindex synchronous method calls
@ -1319,7 +1320,7 @@ emulate the @code{lshal} command on GNU/Linux systems:
@node Asynchronous Methods
@chapter Calling methods non-blocking.
@chapter Calling methods non-blocking
@cindex method calls, asynchronous
@cindex asynchronous method calls
@ -1371,7 +1372,7 @@ message arrives, and @var{handler} is called. Example:
@node Register Objects
@chapter Offering own services.
@chapter Offering own services
@cindex method calls, returning
@cindex returning method calls
@ -1722,7 +1723,7 @@ to the service from D-Bus.
@node Signals
@chapter Sending and receiving signals.
@chapter Sending and receiving signals
@cindex signals
Signals are one way messages. They carry input parameters, which are
@ -1859,7 +1860,7 @@ for a dummy signal, and check the result:
@node Alternative Buses
@chapter Alternative buses and environments.
@chapter Alternative buses and environments
@cindex bus names
@cindex UNIX domain socket
@cindex TCP/IP socket
@ -1986,7 +1987,7 @@ running. This could be achieved by
@node Errors and Events
@chapter Errors and events.
@chapter Errors and events
@cindex debugging
@cindex errors
@cindex events
@ -2145,7 +2146,7 @@ whether a given D-Bus error is related to them.
@node Monitoring Messages
@chapter Monitoring messages.
@chapter Monitoring messages
@cindex monitoring
@defun dbus-register-monitor bus &optional handler &key type sender destination path interface member
@ -2204,6 +2205,111 @@ switches to the monitor buffer.
@end deffn
@node Inhibitor Locks
@chapter Inhibit system shutdowns and sleep states
@uref{https://systemd.io/INHIBITOR_LOCKS/, Systemd} includes a logic to
inhibit system shutdowns and sleep states. It can be controlled by a
D-Bus API@footnote{@uref{https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.login1.html}}.
Because this API includes handling of file descriptors, not all
functions can be implemented by simple D-Bus method calls. Therefore,
the following functions are provided.
@defun dbus-make-inhibitor-lock what why &optional block
This function creates an inhibitor for system shutdowns and sleep states.
@var{what} is a colon-separated string of lock types: @samp{shutdown},
@samp{sleep}, @samp{idle}, @samp{handle-power-key},
@samp{handle-suspend-key}, @samp{handle-hibernate-key},
@samp{handle-lid-switch}. Example: @samp{shutdown:idle}.
@c@var{who} is a descriptive string of who is taking the lock. If it is
@c@code{nil}, it defaults to @samp{Emacs}.
@var{why} is a descriptive string of why the lock is taken. Example:
@samp{Package Update in Progress}.
The optional @var{block} is the mode of the inhibitor lock, either
@samp{block} (@var{block} is non-@code{nil}), or @samp{delay}.
Note, that the @code{who} argument of the inhibitor lock object of the
systemd manager is always set to the string @samp{Emacs}.
It returns a file descriptor or @code{nil}, if the lock cannot be
acquired. If there is already an inhibitor lock for the triple
@code{(WHAT WHY BLOCK)}, this lock is returned. Example:
@lisp
(dbus-make-inhibitor-lock "sleep" "Test")
@result{} 25
@end lisp
@end defun
@defun dbus-registered-inhibitor-locks
Return registered inhibitor locks, an alist.
This allows to check, whether other packages of the running Emacs
instance have acquired an inhibitor lock as well.
An entry in this list is a list @code{(@var{fd} @var{what} @var{why}
@var{block})}. The car of the list is the file descriptor retrieved
from a @code{dbus-make-inhibitor-lock} call. The cdr of the list
represents the three arguments @code{dbus-make-inhibitor-lock} was
called with. Example:
@lisp
(dbus-registered-inhibitor-locks)
@result{} ((25 "sleep" "Test" nil))
@end lisp
@end defun
@defun dbus-close-inhibitor-lock lock
Close inhibitor lock file descriptor.
@var{lock}, a file descriptor, must be the result of a
@code{dbus-make-inhibitor-lock} call. It returns @code{t} in case of
success, or @code{nil} if it isn't be possible to close the lock, or if
the lock is closed already. Example:
@lisp
(dbus-close-inhibitor-lock 25)
@result{} t
@end lisp
@end defun
A typical scenario for these functions is to register for the
D-Bus signal @samp{org.freedesktop.login1.Manager.PrepareForSleep}:
@lisp
(defvar my-inhibitor-lock
(dbus-make-inhibitor-lock "sleep" "Test"))
(defun my-dbus-PrepareForSleep-handler (start)
(if start ;; The system goes down for sleep
(progn
@dots{}
;; Release inhibitor lock.
(when (natnump my-inhibitor-lock)
(dbus-close-inhibitor-lock my-inhibitor-lock)
(setq my-inhibitor-lock nil)))
;; Reacquire inhibitor lock.
(setq my-inhibitor-lock
(dbus-make-inhibitor-lock "sleep" "Test"))))
(dbus-register-signal
:system "org.freedesktop.login1" "/org/freedesktop/login1/Manager"
"org.freedesktop.login1.Manager" "PrepareForSleep"
#'my-dbus-PrepareForSleep-handler)
@result{} ((:signal :system "org.freedesktop.login1.Manager" "PrepareForSleep")
("org.freedesktop.login1" "/org/freedesktop/login1/Manager"
my-dbus-PrepareForSleep-handler))
@end lisp
@node Index
@unnumbered Index

View file

@ -201,10 +201,10 @@ large or inefficient completion tables this can slow down typing.
+++
*** New optional value of 'minibuffer-visible-completions'.
If the value of this option is 'up-down', only the <UP> and <DOWN> arrow
keys move point between candidates shown in the *Completions* buffer
display, while <RIGHT> and <LEFT> arrows move point in the minibuffer
window.
If the value of this option is 'up-down', only the '<up>' and '<down>'
arrow keys move point between candidates shown in the "*Completions*"
buffer display, while '<right>' and '<left>' arrows move point in the
minibuffer window.
---
*** 'RET' chooses the completion selected with 'M-<up>/M-<down>'.
@ -513,7 +513,7 @@ Each non-tooltip frame is assigned a unique integer id. This allows you
to unambiguously identify frames even if they share the same name or
title. When 'undelete-frame-mode' is enabled, each deleted frame's id
is stored for resurrection. The function 'frame-id' returns a frame's
id (in C, use the frame struct member id).
id (in C, use the frame struct member 'id').
** Mode Line
@ -2062,9 +2062,9 @@ you exit the Emacs session or kill the IELM buffer.
---
*** New value 'point' for user option 'ielm-dynamic-return'.
When 'ielm-dynamic-return' is set to 'point', typing RET has dynamic
When 'ielm-dynamic-return' is set to 'point', typing 'RET' has dynamic
behavior based on whether point is inside an sexp. While point is
inside an sexp typing RET inserts a newline, and otherwise Emacs
inside an sexp typing 'RET' inserts a newline, and otherwise Emacs
proceeds with evaluating the expression. This is useful when
'electric-pair-mode', or a similar automatic pairing mode, is enabled.
@ -2889,7 +2889,7 @@ The user option 'package-review-policy' can configure which packages
the user should be allowed to review before any processing takes place.
The package review can include reading the downloaded source code,
presenting a diff between the downloaded code and a previous
installation or displaying a changelog.
installation or displaying a ChangeLog.
** Rcirc
@ -3750,12 +3750,21 @@ without marking it as automatically buffer-local.
** The obsolete face attribute ':reverse-video' has been removed.
Use ':inverse-video' instead.
** D-Bus
+++
** Support interactive D-Bus authorization.
*** Support interactive D-Bus authorization.
A new ':authorizable t' parameter has been added to 'dbus-call-method'
and 'dbus-call-method-asynchronously' to allow the user to interactively
authorize the invoked D-Bus method (for example via polkit).
+++
*** New D-Bus functions to support systemd inhibitor locks.
The functions 'dbus-make-inhibitor-lock', 'dbus-close-inhibitor-lock'
and 'dbus-registered-inhibitor-locks' implement acquiring and releasing
systemd inhibitor locks. See the Info node "(dbus) Inhibitor Locks" for
details.
** The customization group 'wp' has been removed.
It has been obsolete since Emacs 26.1. Use the group 'text' instead.
@ -3926,15 +3935,17 @@ When the theme is set on PGTK, Android, or MS-Windows systems,
variable 'toolkit-theme' as either symbol 'dark' or 'light', but may be
extended to encompass other toolkit-specific symbols in the future.
** Progress reporter
+++
** Progress reporter callbacks.
*** Progress reporter callbacks.
'make-progress-reporter' now accepts optional arguments UPDATE-CALLBACK,
called on progress steps, and DONE-CALLBACK, called when the progress
reporter is done. See the 'make-progress-reporter' docstring for a full
specification of these new optional arguments.
+++
** Progress reporter context.
*** Progress reporter context.
'make-progress-reporter' now accepts the optional argument CONTEXT,
which if it is the symbol 'async', inhibits updates in the echo area
when it is busy. This is useful, for example, if you want to monitor progress

View file

@ -1617,6 +1617,109 @@ usage: (dbus-message-internal &rest REST) */)
return result;
}
/* Alist of registered inhibitor locks for D-Bus.
An entry in this list is a list (FD WHAT WHY BLOCK).
The car of the list is a file descriptor retrieved from a
'dbus-make-inhibitor-lock` call. The cdr of the list represents the
three arguments 'dbus-make-inhibitor-lock` was called with. */
static Lisp_Object xd_registered_inhibitor_locks;
DEFUN ("dbus-make-inhibitor-lock", Fdbus_make_inhibitor_lock,
Sdbus_make_inhibitor_lock,
2, 3, 0,
doc: /* Inhibit system shutdowns and sleep states.
WHAT is a colon-separated string of lock types, i.e. "shutdown",
"sleep", "idle", "handle-power-key", "handle-suspend-key",
"handle-hibernate-key", "handle-lid-switch". Example: "shutdown:idle".
WHY is a descriptive string of why the lock is taken. Example: "Package
Update in Progress".
The optional BLOCK is the mode of the inhibitor lock, either "block"
(BLOCK is non-nil), or "delay".
It returns a file descriptor or nil, if the lock cannot be acquired. If
there is already an inhibitor lock for the triple (WHAT WHY BLOCK), this
lock is returned.
For details of the arguments, see Info node `(dbus)Inhibitor Locks'. */)
(Lisp_Object what, Lisp_Object why, Lisp_Object block)
{
CHECK_STRING (what);
CHECK_STRING (why);
if (!NILP (block))
block = Qt;
Lisp_Object who = build_string ("Emacs");
Lisp_Object mode =
(NILP (block)) ? build_string ("delay") : build_string ("block");
/* Check, whether it is registered already. */
Lisp_Object triple = list3 (what, why, block);
Lisp_Object registered = Frassoc (triple, xd_registered_inhibitor_locks);
if (!NILP (registered))
return CAR_SAFE (registered);
/* Register lock. */
Lisp_Object lock =
calln (Qdbus_call_method, QCsystem,
build_string ("org.freedesktop.login1"),
build_string ("/org/freedesktop/login1"),
build_string ("org.freedesktop.login1.Manager"),
build_string ("Inhibit"), what, who, why, mode);
xd_registered_inhibitor_locks =
Fcons (Fcons (lock, triple), xd_registered_inhibitor_locks);
return lock;
}
DEFUN ("dbus-close-inhibitor-lock", Fdbus_close_inhibitor_lock,
Sdbus_close_inhibitor_lock,
1, 1, 0,
doc: /* Close inhibitor lock file descriptor.
LOCK, a file descriptor, must be the result of a `dbus-make-inhibitor-lock'
call. It returns t in case of success, or nil if it isn't be possible
to close the lock, or if the lock is closed already.
For details, see Info node `(dbus)Inhibitor Locks'. */)
(Lisp_Object lock)
{
CHECK_FIXNUM (lock);
/* Check, whether it is registered. */
Lisp_Object registered = assoc_no_quit (lock, xd_registered_inhibitor_locks);
if (NILP (registered))
return Qnil;
else
{
xd_registered_inhibitor_locks =
Fdelete (registered, xd_registered_inhibitor_locks);
return (emacs_close (XFIXNAT (lock)) == 0) ? Qt : Qnil;
}
}
DEFUN ("dbus-registered-inhibitor-locks", Fdbus_registered_inhibitor_locks,
Sdbus_registered_inhibitor_locks,
0, 0, 0,
doc: /* Return registered inhibitor locks, an alist.
This allows to check, whether other packages of the running Emacs
instance have acquired an inhibitor lock as well.
An entry in this list is a list (FD WHAT WHY BLOCK).
The car of the list is the file descriptor retrieved from a
'dbus-make-inhibitor-lock` call. The cdr of the list represents the
three arguments 'dbus-make-inhibitor-lock` was called with. */)
()
{
/* We return a copy of xd_registered_inhibitor_locks, in order to
protect it against malicious manipulation. */
Lisp_Object registered = xd_registered_inhibitor_locks;
Lisp_Object result = Qnil;
for (; !NILP (registered); registered = CDR_SAFE (registered))
result = Fcons (Fcopy_sequence (CAR_SAFE (registered)), result);
return Fnreverse (result);
}
/* Construct a D-Bus event, and store it into the input event queue. */
static void
xd_store_event (Lisp_Object handler, Lisp_Object handler_args,
@ -1869,6 +1972,7 @@ static void
syms_of_dbusbind_for_pdumper (void)
{
xd_registered_buses = Qnil;
xd_registered_inhibitor_locks = Qnil;
}
void
@ -1876,6 +1980,9 @@ syms_of_dbusbind (void)
{
defsubr (&Sdbus__init_bus);
defsubr (&Sdbus_get_unique_name);
defsubr (&Sdbus_make_inhibitor_lock);
defsubr (&Sdbus_close_inhibitor_lock);
defsubr (&Sdbus_registered_inhibitor_locks);
DEFSYM (Qdbus_message_internal, "dbus-message-internal");
defsubr (&Sdbus_message_internal);
@ -1930,6 +2037,7 @@ syms_of_dbusbind (void)
/* Miscellaneous Lisp symbols. */
DEFSYM (Qdbus_get_name_owner, "dbus-get-name-owner");
DEFSYM (Qdbus_call_method, "dbus-call-method");
DEFVAR_LISP ("dbus-compiled-version",
Vdbus_compiled_version,
@ -2035,6 +2143,7 @@ be called when the D-Bus reply message arrives. */);
/* Initialize internal objects. */
pdumper_do_now_and_after_load (syms_of_dbusbind_for_pdumper);
staticpro (&xd_registered_buses);
staticpro (&xd_registered_inhibitor_locks);
Fprovide (intern_c_string ("dbusbind"), Qnil);
}

View file

@ -48,6 +48,15 @@
(defconst dbus--test-interface "org.gnu.Emacs.TestDBus.Interface"
"Test interface.")
(defconst dbus--test-systemd-service "org.freedesktop.login1"
"Systemd service.")
(defconst dbus--test-systemd-path "/org/freedesktop/login1"
"Systemd object path.")
(defconst dbus--test-systemd-manager-interface "org.freedesktop.login1.Manager"
"Systemd Manager interface.")
(defun dbus--test-availability (bus)
"Test availability of D-Bus BUS."
(should (dbus-list-names bus))
@ -2295,6 +2304,90 @@ The argument EXPECTED-ARGS is a list of expected arguments for the method."
;; Cleanup.
(dbus-unregister-service :session dbus--test-service)))
(ert-deftest dbus-test10-inhibitor-locks ()
"Check `dbus-*-inhibitor-locks'."
:tags '(:expensive-test)
(skip-unless dbus--test-enabled-system-bus)
(skip-unless (dbus-ping :system dbus--test-systemd-service 1000))
(let (lock1 lock2)
;; Create inhibitor lock.
(setq lock1 (dbus-make-inhibitor-lock "sleep" "Test delay"))
(should (natnump lock1))
;; The lock is reported by systemd.
(should
(member
(list "sleep" "Emacs" "Test delay" "delay" (user-uid) (emacs-pid))
(dbus-call-method
:system dbus--test-systemd-service dbus--test-systemd-path
dbus--test-systemd-manager-interface "ListInhibitors")))
;; The lock is registered internally.
(should
(member
(list lock1 "sleep" "Test delay" nil)
(dbus-registered-inhibitor-locks)))
;; There exist a file descriptor.
(when (file-directory-p (format "/proc/%d/fd" (emacs-pid)))
(should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1))))
;; It is not possible to modify registered inhibitor locks on Lisp level.
(setcar (assoc lock1 (dbus-registered-inhibitor-locks)) 'malicious)
(should (assoc lock1 (dbus-registered-inhibitor-locks)))
(should-not (assoc 'malicious (dbus-registered-inhibitor-locks)))
;; Creating it again returns the same inhibitor lock.
(should (= lock1 (dbus-make-inhibitor-lock "sleep" "Test delay")))
;; Create another inhibitor lock.
(setq lock2 (dbus-make-inhibitor-lock "sleep" "Test block" 'block))
(should (natnump lock2))
(should-not (= lock1 lock2))
;; The lock is reported by systemd.
(should
(member
(list "sleep" "Emacs" "Test block" "block" (user-uid) (emacs-pid))
(dbus-call-method
:system dbus--test-systemd-service dbus--test-systemd-path
dbus--test-systemd-manager-interface "ListInhibitors")))
;; The lock is registered internally.
(should
(member
(list lock2 "sleep" "Test block" t)
(dbus-registered-inhibitor-locks)))
;; There exist a file descriptor.
(when (file-directory-p (format "/proc/%d/fd" (emacs-pid)))
(should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock2))))
;; Close the first inhibitor lock.
(should (dbus-close-inhibitor-lock lock1))
;; The internal registration has gone.
(should-not
(member
(list lock1 "sleep" "Test delay" nil)
(dbus-registered-inhibitor-locks)))
;; The file descriptor has been deleted.
(when (file-directory-p (format "/proc/%d/fd" (emacs-pid)))
(should-not (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1))))
;; Closing it again is a noop.
(should-not (dbus-close-inhibitor-lock lock1))
;; Creating it again returns (another?) inhibitor lock.
(setq lock1 (dbus-make-inhibitor-lock "sleep" "Test delay"))
(should (natnump lock1))
;; The lock is registered internally.
(should
(member
(list lock1 "sleep" "Test delay" nil)
(dbus-registered-inhibitor-locks)))
;; There exist a file descriptor.
(when (file-directory-p (format "/proc/%d/fd" (emacs-pid)))
(should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1))))
;; Close the inhibitor locks.
(should (dbus-close-inhibitor-lock lock1))
(should (dbus-close-inhibitor-lock lock2))))
(defun dbus-test-all (&optional interactive)
"Run all tests for \\[dbus]."
(interactive "p")