We want to provide the complete printing stack in Snaps, especially needed for full printing functionality in all-Snap Linux distributions like Ubuntu Core or Ubuntu Core Desktop, but also to have a future release of the “classic” Ubuntu using these Snaps to provide its printing functionality.
There are already Snaps of CUPS, ipp-usb, and of nearly all free software printer drivers in 6 Printer Application Snaps.
What is missing are the Common Print Dialog Backends (CPDB). With these backends print dialogs do not interact directly with the print technology in use (usually CUPS but also print-to-file, cloud printing services, …) and so changes in these technologies (like the transition from CUPS 2.x with PPDs and driver filters to all-IPP CUPS 3.x) or introduction of new cloud print services does not require changes in all print dialogs but only in the backend which is maintained by the maintainer of the corresponding print technology.
Each CPDB backend is a user daemon, providing a D-Bus service (on the session bus) for all print system interactions as listing all available printers, receiving continuous updates on this list, showing all capabilities and user-settable options of a selected printer, print on a selected printer sending print data and option settings. The daemon is designed to not stay permanently running but being D-Bus activated and closed when closing the print dialog (but adding an option to alternatively running it permanently would not be complicated).
A print dialog with CPDB support (CPDB frontend) lists all available D-Bus services and finds the CPDB backends under them (org.openprinting.Backend.XXX
service names). It then polls the printer lists and subscribes to updates on them for each backend to build the complete list. If a user chooses an entry, the dialog communicates with the backend which has provided that entry for knowing the options and/or printing.
Now we want to create a Snap of the most important backend, the one for CUPS. We take the GIT repository of cpdb-backend-cups (“snap” branch) where the following files are added:
snap/snapcraft.yaml
snap/local/run-cpdb-backend-cups
I want to say thank you to Biswadeep Purkayastha for starting the work on snapping this. He stepped up when he was attending my talk and my Snap workshop on the Opportunity Open Source which I organized in the IIT Mandi in India. He also went through the slides and exercises of my Daemon Snapper’s Workshop from the Ubuntu Summit 2022 and read @kenvandine’s 2nd blog about Ubuntu Core Desktop with a first example of a user daemon and a D-Bus-triggered daemon launch.
Below is what I have tested. Everything where I have doubts or questions, where I found a possible bug, or where snapd perhaps needs another feature, I have written in bold. Here I want to ask the Snap experts (@jamesh, @kenvandine, @sergiusens, …) for help and discussion. Thanks in advcance.
For everyone who wants to try it out, note that user daemons and D-Bus activation are only experimental features and need to get enabled first:
sudo snap set system experimental.user-daemons=true
sudo snap set system experimental.dbus-activation=true
The features exist already for longer time but they are not yet activated because they are not completely implemented yet, especially the service control commands (start, stop, reload, … the daemon) only support system daemons and no user daemons yet.
To get some deeper information about user daemons and D-Bus activation than only the short mention by @kenvandine in his blog, see the following two discussion threads where @jamesh (thanks a lot) explains everything:
The snapcraft.yaml
is a little long, as it has parts for Ghostscript and QPDF, for building libcupsfilters. The backend does not actually need the filter functions for PDF and PostScript processing, so I am looking into ./configure
options to optionally omit these dependency-causing filter functions in libcupsfilters 2.1.0.
But the important part is to make the backend considered a user daemon in the Snap and clients (CPDB frontends aka print dialogs) can access it. So to follow best its original design we put the following into snapcraft.yaml
(you find it near the file’s beginning):
slots:
dbus-openprinting-backend-cups:
interface: dbus
bus: session
name: org.openprinting.Backend.CUPS
apps:
cpdb-backend-cups:
command: scripts/run-cpdb-backend-cups
daemon: simple
daemon-scope: user
activates-on:
- dbus-openprinting-backend-cups
plugs: [cups]
We define the a slot for the D-Bus service which our backend provides. The service is provided on the session bus as the daemon is running as part of the user session, with the user’s rights, not as root, as a snapped system daemon would do. The name is the D-Bus service name as used by the backend.
The app definition for the backend contains as command a call of the wrapper daemon. As in other Snaps, like the CUPS Snap the script sets environment variables and creates directories so that the actual daemon can run inside the Snap’s sandbox. In the end the script starts the actual backend.
The daemon-scope: user
makes the daemon be a user daemon. It runs as normal user and not as root and by default it is started when the user logs in and stopped when they log out (or when the daemon terminates by itself). We do not want to have it running all the time and therefore we use D-Bus activation, via activates-on:
with the name of the slot which needs to get accessed so that the launch of the daemon gets triggered. Here we use the name of the D-Bus slot we just defined.
The plugs: [cups]
allows the backend to communicate with CUPS, to get the list of printers, the options, and to send off the jobs. These are exactly the tasks of printing via a print dialog, so cups
is good enough here and cups-control
is not needed.
Let us build the Snap via
snapcraft -v --debug
The -v
gives us verbose logging, showing especially the build processes, and --debug
leaves us in the shell of the build process when an error occurs, so that we can investigate whether everything is correct inside the build container (leave the shell with exit
when done). All logging is also thrown into a file, see the end of the build process’ output (after exiting the shell in case of an error).
Do not install your Snap yet.
Now prepare the test. Run
cpdb-text-frontend
The command is supposed to list an entry for each of your CUPS queues plus one entry to print into a PDF file, if appropriate CPDB backends are installed, the CUPS backend for the former and the print-to-file backend for the latter. It offers commands with which you could also show option settings and actually print, all operations which a print dialog is supposed to do. Enter help
to list the commands and stop
to quit. Do not worry that you often do not see a prompt. The commands also work without the prompt being visible.
Run
sudo apt install cpdb-libs-tools
if the command is not available.
If you do not get your CUPS queues listed even if you have some (lpstat -v
, create a CUPS queue for the further tests if you do not have one), you are fine for the test, if you get them listed, you need to remove a classically installed version of the backend:
sudo apt remove cpdb-backend-cups
or, if you installed right from source, do make uninstall
or rmove the following files:
/usr/share/dbus-1/services/org.openprinting.Backend.CUPS.service
/usr/lib/print-backends/cups
/usr/lib/*/print-backends/cups
cpdb-text-frontend
should not list your CUPS print queues any more.
Then install the Snap
sudo snap install --dangerous cpdb-backend-cups_1.0_amd64.snap
and connect the cups
interface (Note: CUPS Snap must be installed):
sudo snap connect cpdb-backend-cups:cups cups:cups
To test it, run
cpdb-text-frontend
again.
You will get something like (you may only get the errors and no Save_as_PDF
entry):
$ cpdb-text-frontend
[Error] [Frontend] Error getting CUPS printer list : GDBus.Error:org.freedesktop.DBus.Error.AccessDenied: An AppArmor policy prevents this sender from sending this message to this recipient; type="method_call", sender=":1.1546" (uid=1000 pid=369218 comm="cpdb-text-frontend" label="unconfined") interface="org.openprinting.PrintBackend" member="GetPrinterList" error name="(unset)" requested_reply="0" destination=":1.1547" (uid=1000 pid=369223 comm="/snap/cpdb-backend-cups/x9/usr/lib/print-backends/" label="snap.cpdb-backend-cu-------------------------
Printer Save_As_PDF
name: Save_As_PDF
location: localhost
info: Printing to a PDF File
make and model: Save_As_PDF
accepting jobs? yes
state: idle
backend: FILE
-------------------------
>
Stop it
> stop
** Message: 23:00:08.536: Stopping front end..
$
and run
$ ps aux | grep cpdb
till 369223 0.2 0.1 332092 16616 ? Ssl 22:58 0:00 /snap/cpdb-backend-cups/x9/usr/lib/print-backends/cups
$
You see now that the D-Bus activation worked. The ps
command shows that the daemon is started, and as user (you) and not as root. But instead of a list of the CUPS queues cpdb-text-frontend
gives you error messages, AppArmor blocking communication between the unconfined (classically installed) frontend and the snapped backend.
2023-10-20T23:14:28.376581+02:00 till-x1nano dbus-daemon[6549]: [session uid=1000 pid=6549] Activating via systemd: service name='org.openprinting.Backend.CUPS' unit='snap.cpdb-backend-cups.cpdb-backend-cups.service' requested by ':1.1551' (uid=1000 pid=370011 comm="cpdb-text-frontend" label="unconfined")
2023-10-20T23:14:28.402829+02:00 till-x1nano systemd[6505]: Started snap.cpdb-backend-cups.cpdb-backend-cups.service - Service for snap application cpdb-backend-cups.cpdb-backend-cups.
2023-10-20T23:14:28.441131+02:00 till-x1nano dbus-daemon[6549]: [session uid=1000 pid=6549] Successfully activated service 'org.openprinting.Backend.CUPS'
2023-10-20T23:14:28.441403+02:00 till-x1nano kernel: [127710.102634] audit: type=1326 audit(1697836468.435:4559): auid=1000 uid=1000 gid=1000 ses=3 subj=snap.cpdb-backend-cups.cpdb-backend-cups pid=370016 comm="cups" exe="/snap/cpdb-backend-cups/x9/usr/lib/print-backends/cups" sig=0 arch=c000003e syscall=314 compat=0 ip=0x7f893e4baa3d code=0x50000
2023-10-20T23:14:28.441404+02:00 till-x1nano kernel: [127710.103460] audit: type=1400 audit(1697836468.435:4560): apparmor="DENIED" operation="connect" class="file" profile="snap.cpdb-backend-cups.cpdb-backend-cups" name="/run/dbus/system_bus_socket" pid=370016 comm="cups" requested_mask="wr" denied_mask="wr" fsuid=1000 ouid=0
2023-10-20T23:14:28.441558+02:00 till-x1nano dbus-daemon[6549]: apparmor="DENIED" operation="dbus_method_call" bus="session" path="/" interface="org.freedesktop.DBus.Properties" member="GetAll" name=":1.1551" mask="receive" pid=370016 label="snap.cpdb-backend-cups.cpdb-backend-cups" peer_pid=370011 peer_label="unconfined"
2023-10-20T23:14:28.441733+02:00 till-x1nano dbus-daemon[6549]: apparmor="DENIED" operation="dbus_method_call" bus="session" path="/" interface="org.openprinting.PrintBackend" member="GetPrinterList" name=":1.1551" mask="receive" pid=370016 label="snap.cpdb-backend-cups.cpdb-backend-cups" peer_pid=370011 peer_label="unconfined"
[...]
2023-10-20T23:14:34.058663+02:00 till-x1nano dbus-daemon[6549]: apparmor="DENIED" operation="dbus_signal" bus="session" path="/" interface="org.openprinting.PrintFrontend" member="StopListing" name=":1.1551" mask="receive" pid=370016 label="snap.cpdb-backend-cups.cpdb-backend-cups" peer_pid=370011 peer_label="unconfined"
These lines show that the user daemon got successfully started via D-Bus activation (first 3 lines). The problem is that the actual D-Bus communication of the backend does not work, both with CUPS despite we have connected the cups
interface (so the cups
interface needs to get fixed, it does not allow the access to CUPS’ D-Bus services, as cups-control
allows, and these services are not administrative, they are for print dialogs, so Snaps plugging cups
should have access to them.) and with the CPDB frontend (cpdb-text-frontend
, lines 6-8).
If we look at the documentation for a slot definition for an interface: dbus
slot, the client would need to have a plug for this slot to get access. First, this requires the client to be a Snap and we are testing with a classically installed client, and second, if we have a snapped client, it would need to have a plug for each CPDB backend, which defeats the principle of CPDB. A CPDB frontend should be independent of the backends. It should use all backends which are present, also those which are created after the frontend got released. So we cannot require that each frontend Snap has an explicit plug for each backend Snap. See also above the actual method used. We need a general (session) D-Bus access interface for the frontends. We need the slots for the D-Bus activation but we need a general D-Bus access for being able to access arbitrary backends.
What we need is a D-Bus interface for Snaps providing a D-Bus service so that both classically installed clients and snapped clients can access the Snap’s D-Bus service. Also clients (both snapped or classically installed) must be able to browse D-Bus services (list all available ones) and pick the desired ones (for example all org.openprinting.Backend.*
). So a Snap containing a service must be able to make the service available (specifying itsomehow for best security) and an unconfined client must be able to access by this already. A snapped client should plug some kind of D-Bus interface, with options for general access to available (both snapped and unconfined) D-Bus services or to specify a group of services, also with wildcards or regexps.
Now let us install with --devmode
to drop all confinements:
sudo snap install --dangerous --devmode cpdb-backend-cups_1.0_amd64.snap
Kill any old instance of the backend:
killall cups
and test again:
$ cpdb-text-frontend
-------------------------
Printer Save_As_PDF
name: Save_As_PDF
location: localhost
info: Printing to a PDF File
make and model: Save_As_PDF
accepting jobs? yes
state: idle
backend: FILE
-------------------------
-------------------------
Printer HP_OfficeJet_Pro_8730_5B78A3_USB
name: HP_OfficeJet_Pro_8730_5B78A3_USB
location:
info: HP OfficeJet Pro 8730 [5B78A3] (USB)
make and model: HP HP OfficeJet Pro 8730
accepting jobs? no
state: NA
backend: CUPS
-------------------------
[...]
>
Now all the CUPS queues get listed.
I did also another test:
The CUPS Snap contains D-Bus services to notify about changes on print queues or on queued jobs. These services are available to unconfined clients (I can stop the cups-browsed contained in the CUPS Snap and attach an unconfined cups-browsed to the CUPS Snap’s cupsd and that cups-browsed works as well with the Snap’s cupsd as the Snap’s cups-browsed does).
This motivated me to modify the snapcraft.yaml
of cpdb-backend-cups as follows:
# Remove "slots: dbus-openprinting-backend-cups ..."
apps:
cpdb-backend-cups:
command: scripts/run-cpdb-backend-cups
daemon: simple
daemon-scope: user
plugs: [cups]
This makes the daemon just running right away, all the time while the user is logged in. And there is nothing specific to D-Bus.
We build the Snap again and install it (with confinement):
killall cups
snapcraft -v --debug
sudo snap install --dangerous cpdb-backend-cups_1.0_amd64.snap
Test again (damon already running):
$ ps auxwww | grep cpdb
till 376195 15.6 0.1 258372 16128 ? Ssl 00:09 0:00 /snap/cpdb-backend-cups/x12/usr/lib/print-backends/cups
$ cpdb-text-frontend
-------------------------
Printer Save_As_PDF
name: Save_As_PDF
location: localhost
info: Printing to a PDF File
make and model: Save_As_PDF
accepting jobs? yes
state: idle
backend: FILE
-------------------------
>
And with running daemon we do not see any CUPS queue but also no errors.
So let us see what we get in /var/log/syslog
. For the startup of the damon (when installing the Snap) we get:
023-10-21T00:12:05.370445+02:00 till-x1nano systemd[6505]: Started snap.cpdb-backend-cups.cpdb-backend-cups.service - Service for snap application cpdb-backend-cups.cpdb-backend-cups.
Only a report of successful start, no DENIED
messages.
And when running cpdb-text-frontend
no further lines concerning cpdb-backend-cups
appear.
What is strange here is that I can use the D-Bus services of the snapped CUPS with unsnapped clients, but I cannot do it with services of the snapped cpdb-backend-cups
. Is this due to the fact that CUPS is a system daemon and the CPDB backend a user daemon? Or why this difference?
I am grateful if we could find a solution for this, as it is needed ofr a smooth printing experience in both Ubuntu Core Desktop and in any future classic Ubuntu with the printing stack provided by Snaps.