openSUSE:YaST development DBUS service
tagline: From openSUSE
The YaST DBus Namespace Service
This document describes the state of the development for YaST package versions 2.18.x (means openSUSE 11.2) which is work in progress. The service may be changed anytime, this is unstable version.
The Purpose of the Service
YaST DBus name space service enables to use the YaST libraries and functionality from other applications via DBus interface.
The advantages are
- Access to YaST functions from every programming language or application which supports communication via DBus. That means the functionality is even available in shell (via dbus-send or qdbus tool) or in a DBus browser (e.g. kdbus, qdbusviewer...)
- PolicyKit integration - The service uses PolicyKit for checking user authorizations, this enables access to privileged operations to non-root users. This is very important when using YaST as a backend in desktop oriented applications.
This service moves the DBus support to higher level - the original SCR DBus service exports only low-level YaST name space (SCR::). This service can export every YaST name space written in any supported programming language (YCP, Perl, Python, C++, Ruby).
How to Install the Service
The DBus service is available in yast2-core-2.18.6 and newer. It is currently available in FACTORY, it will be included in openSUSE 11.2. The service is part of the basic YaST component, the service does not need any extra configuration, it is enabled by default. It uses PolicyKit for access control, unauthorized use is not possible.
How to Use the Service
The service has DBus name org.opensuse.YaST.modules and it is available on the system bus.
YaST name spaces are available as objects with path /org/opensuse/YaST/modules/<namespace_name>. Every exported object (YaST name space) has these interfaces:
- org.opensuse.YaST.Values - This is the standard YaST DBus interface, it uses DBus data types directly. This interface tries to autoconvert the passed DBus values to YCP values if possible. E.g. a string is converted to a YCP symbol if the function expects a symbol parameter.
- org.opensuse.YaST.YCPValues - This interface accepts marshaled YCP values (see #Marshalling YCP Values section). Any YCP value can be passed via this interface. This interface is rather a workaround for using YaST functions which would not be accessible otherwise or to pass special values. This interface should not be needed normally, all officially supported functions must be accessible using org.opensuse.YaST.Values interface.
Note: Name space separator '::' is replaced by '/' which is used as DBus path separator. This enables to create a hiearchy in name space names, e.g. YaPI::Samba and YaPI::USERS will be available in YaPI/ subpath.
Each object exports only the methods of the underlying name space, the global variables are not exported. If they need to be accessible they should be wrapped in get()/set() functions. See #Exporting Global Variables from Name Spaces section.
The Module Manager Interface
The service provides a special interface org.opensuse.YaST.modules.ModuleManager at object /org/opensuse/YaST/modules. This is the interface to access the YaST DBus service itself.
Currently there are available these methods:
- Import(string name_space) - explicitly import an YaST name space. Because name spaces are imported automatically this method should not be normally needed. It is intended for graphical DBus browsers which can send only method requested to known objects.
- Lock() and Unlock() - should be used for accessing stateful functions from a long running client. These functions prevent the service from shutting down after a timeout. The service is kept running when there is at least one running client which has called Lock() method.
Marshalling YCP Values
There is a special interface org.opensuse.yast.YCPValues for passing types or values which are not suppoerted directly by DBus. This interface uses a special structure for encoding any YCP value. The structure (let's call it bsv structure) has three components:
- boolean - nil flag, if it is true the structure represents the nil value, all other fields are unused and have an undefined state (there are just some dummy values to keep the same signature for all YCP values)
- string - name of the YCP type, e.g. string, integer, list...
- variant - the value transferred using same or similar DBus type, e.g. YCP symbol is transferred as DBus string, YCP list is transferred as DBus array... YCP containers are recursively encoded, the variant can be another bsv structure.
This encoding enables transferring values like [1, "2", `symbol, nil] which cannot be transferred via direct DBus values.
Access control via PolicyKit
Each incoming method request is checked by PolicyKit and the requested method is executed only when PolicyKit returns YES. If PolicyKit returns a different result the service returns DBus exception org.freedesktop.PolicyKit.Error.NotAuthorized followed by the PolicyKit result (see http://hal.freedesktop.org/docs/PolicyKit/polkit-polkit-simple.html#polkit-dbus-error-generate).
The client should authorize itself if needed and call the method again (see http://hal.freedesktop.org/docs/PolicyKit/model-theory-of-operation.html for details).
PolicyKit Action ID
The service checks whether the requester is allowed to call the YaST function. YaST uses prefix org.opensuse.yast.modules, name space name and function name to create the PolicyKit action ID.
PolicyKit allows only lower case letters and numbers in action ID - name space and function name are therefore lower cased. Name space separator '::' is replaced by '.', which is used as a separator in action IDs.
PolicyKit action ID for Mode::normal() call is org.opensuse.yast.modules.mode.normal.
Obtaining a PolicyKit Authorization
A PolicyKit authorization can be obtained
- implicitly from a *.policy configuration file
- the user authenticates as the administrator (root) user
polkit-auth --obtain org.opensuse.yast.modules.yapi.samba.getservicestatus
- the adminitrator grants the autorization to the user
polkit-auth --user login_name --grant org.opensuse.yast.modules.yapi.samba.getservicestatus
Service start and shutdown
The service is started automatically by the DBus daemon when it is not running.
The service is automatically shut down when at least 2 minutes have elapsed since the last request. This prevents from consuming memory by an unused service.
The service does not import any name space at start up by default. The name spaces are dynamically imported when needed.
Here are examples how to invoke YaPI::Samba::GetServiceStatus() function. This example requires org.opensuse.yast.modules.yapi.samba.getservicestatus PolicyKit authorization, see #Obtaining a PolicyKit Authorization.
bus = dbus.SystemBus()
obj = bus.get_object('org.opensuse.YaST.modules', '/org/opensuse/YaST/modules/YaPI/Samba')
dbus-send --system --dest=org.opensuse.YaST.modules --print-reply --type=method_call /org/opensuse/YaST/modules/YaPI/Samba org.opensuse.YaST.Values.GetServiceStatus
qdbus --system org.opensuse.YaST.modules /org/opensuse/YaST/modules/YaPI__Samba org.opensuse.YaST.Values.GetServiceStatus
How to export YaST functionality
This section is important mainly for YaST developers and users who are interested in implementation details.
The Dbus service can export already existing functions. But to avoid some problems the exported functions should follow these rules because of completely different usage:
- The exported functions should be stateless - no separate Read(), Edit() and Write() functions, but a single function for each exported functionality - e.g. DeleteUser() which calls all needed functions.
The reason is that the name space exported via DBus service is just one instance (a singleton) shared by all clients. The current state may contain internal data from previous calls from another clients. Stateless functions ensure that the same pre-conditions are used in all cases.
Running multiple clients in parallel can lead to race conditions, data loss, misconfiguration or other severe problems when a state-full API is used.
- Input and output parameters should have just basic data types like string, integer... YaST has more data types than DBus (e.g. symbol) or it has a special value which cannot be transferred by DBus directly (nil) or the transferred value can be is useless out side the service (like a function pointer).
- Use single type containers, when using list or map types DBus requires the same type for all values, e.g. [1, 2, 3] can be converted to a DBus array while [1, "2", `symbol] cannot.
- DBus does not support some special characters in object and method names, especially double-colon (:) is problematic for YaST. The DBus service automatically replaces invalid characters to underscore (_), double double-colon is replaced by slash (/). Example: YaPI::Samba module is available as YaPI/Samba DBus object.
- The exported function names should not differ only in upper or lower case letters. The reason is that e.g. Function() and function() will have the same PolicyKit action ID (see #PolicyKit Action ID section) and the access right would be shared between them which is usually not intended and it is error prone.
- Each exported function (actually its action ID) must be defined in a PolicyKit configuration file. See PolicyKit documentation http://hal.freedesktop.org/docs/PolicyKit/polkit-conf.html#conf-declaring-actions . Defining a PolicyKit action ID means that the function is officially supported over the DBus service.
It is a good idea to validate the PolicyKit config during make check, just add check-local target to Makefile.am:
check-local: polkit-policy-file-validate polkit_policy_file
- Neither the exported name space nor its imported name spaces should define constructors. The reason is security - because of the auto-import functionality the constructor could potentially execute an action which is not allowed to the calling client. Another problem is that the constructor is called just once (at first import) and using a constructor leads to a stateful API which should be avoided.
- The exported function must not use any UI:: functions, the service cannot open any dialog because it has no terminal or X session and all UI:: calls will very likely fail. The service should be used as a backend providing some functionality to an application, UI should not be used in the service anyway.
Starting the Service Manually
The service can be started explicitly using /usr/lib/YaST2/bin/yast_modules_dbus_server executable. The service may be owned only by root user, so it can be started only from a root shell. Use --disable-timer option to disable the automatic shutdown feature.
The optional arguments can be used to pre-load requested name spaces.
/usr/lib/YaST2/bin/yast_modules_dbus_server --disable-timer Label Pkg
The service uses the standard YaST log file /var/log/YaST2/y2log. The service log is marked with [y2dbus] component string on each line.
It is possible to enable debug logging with variable Y2DEBUG=1.
Reloading the DBus Configuration
During the development the service rarely refused to start claiming that it is already running. Reloading the DBus configuration solved the problem. Use ReloadConfig() method of the DBus daemon if it happens:
dbus-send --print-reply --system --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig
SCR Service Integration
This new DBus service should replace the previous YaST SCR service. Currently the SCR name space is not exported because it must be handled specially.
The reason is that SCR:: provides overloaded methods and every call must be resolved at runtime. Another reason is that the interpreter allows to start multiple SCR instances so it is handled in a different way than all other name spaces.
Exporting Global Variables from Name Spaces
Global variables could be exported via DBus object properties if really needed. Currently they have to be wrapped in get()/set() methods if they need to be exported.