LSB Package API
This document describes a proposed D-BUS API for LSB application installers, the LSB Package API. Note that the design is a work in progress and some desirable features are still missing, as also stated in the #To Be Done section. An initial implementation is attached to this page, see #Download.
The LSB Package API is an interface that allows ISVs to install LSB-compliant applications in such a way that they are integrated into the distribution's packaging system. This enables users to manage third-party software packages as easily as packages installed from the distributor, and frees ISVs from the need to provide packages for different packaging systems in order to achieve integration with the distribution.
The idea and basic design of the LSB Package API comes from the "Berlin Packaging API" proposal. In fact, the LSB Package API can be considered a further development and implementation of the Berlin API. It is very recommended to read Ian Murdock's summary of what the Berlin API is about before reading on.
The distribution's packaging system is interfaced through a D-Bus service - the LSB Package Service - registered as org.linuxbase.Packages. This service offers the following two methods:
RegisterPackage(in STRING manifest_path, out UINT32 pkgid)
ClosePackage(in UINT32 pkgid)
To install an application, an ISV-provided installer first calls the
RegisterPackage() method. As argument, it passes an absolute path to a package manifest file, which describes a software package and lists the files that need to be installed (see the #Package Manifest Files section). If the package manifest is successfully validated and the described package does not conflict with the base system (e.g. because a file that would need to be installed already exists on the file system), the LSB Package Service then adds the package to the package system's database. Finally, it creates the directories needed by the package and places empty "stub files" at the paths where the package's files need to be installed according to the manifest.
While the created directories are owned by the root user and group, the stub files' ownership is set to the UID of the calling process. Thus, the installer can overwrite these with the "real" files of the installed application and adjust the files' permissions, without a need for root privileges.
Once the installer is finished, it calls the ClosePackage() method, passing the package identifier returned by RegisterPackage() as argument. This causes the package service to change the former stub files' owner to root, thus protecting the installed application's files from being modified by unprivileged programs. Additionally, the service calls out to the packaging system again, which may gather information about the installed files to add it to the database; it might for instance determine the size of the installed package. With the return of this method, the installation is complete.
The methods of the LSB Package API are protected by a PolicyKit policy (org.linuxbase.packages.install). This enables system adminstrators to require authentication prior to installing an application, or to restrict the set of users which are allowed to install third-party software.
The advantages of this installation mechanism (as opposed to classical installation methods on Linux) are:
- Portability. As the distribution's packaging system does not need to be interfaced directly, an LSB-compliant application distributed using the LSB Package API can be installed on any system providing the LSB package service. Ideally, this service would be part of the LSB itself, which would mean any LSB-compliant distribution would provide it.
- System Integrity. The package service can verify any data passed to it before the installation is started. That way, damage to the base system due to the installation of third-party software can be prevented.
- Security. The indirect interfacing of the packaging system and the installation on top of stub files created by the package service allows most installers to run entirely without root privileges. This greatly reduces the danger of malicious installers.
Package Manifest Files
A package manifest file describes the software package to be installed. It contains:
- Metadata about the package, like its name, provider, version etc.
- Information about the files which need to be installed.
The package manifest file's format is an XML dialect.
Elements and attributes
- The top-level element of any package manifest file. The attributes have the following meaning:
- * name: The name of the package to install. May only consist of the characters A-Z, a-z, 0-9 and dashes. (The name must not start with a dash.)
- * provider: A LANANA-registered provider name, or a fully qualified domain name registered by the provider (e.g. "www.foobar.com").
- * version: The package's version. (NOTE: There is no specified version format yet.)
- * arch: The system architecture for which the package was built. (NOTE: There are no allowed values defined yet.)
- Specifies the name of the package like it should be represented to the user. If the manifest file lacks an <displayed-name> element, the displayed name equals the package name specified through the <package> tag's name attribute.
- Specifies the name of the provider like it should be represented to the user. If the manifest file lacks an <displayed-name> element, the displayed name equals the provider name through the <package> tag's provider attribute.
- Defines a description for the package.
<file type='...' location='...'>...</file>
- Defines a package file. Attributes are:
- * type (optional): The type of the file. Either "regular" for a regular file or "dir" for a directory. Omitting this attribute means the file is a regular one.
- * location (optional): The location where the file should be placed. See #Package File Locations for more information. Omitting this attribute means the file is placed into the "data" location.
- The content of a <file> tag must be the package file's destination path relative to the file's location. Although being interpreted as relative, it may start with a leading '/' character. To specify a file location itself as a package file, use "/" (without quotes) as path. The .. path component is not allowed.
- Only files and directories that don't exist on the installing system may be specified. If the LSB Package Service encounters a package file or directory which already exists on the file system, package registration will be canceled.
- Note: If a directory is not explicitly mentioned as a package file in the package manifest, it is NOT created by the LSB Package Service. Thus, every directory which cannot be expected to exist on the install target system must be explicitly mentioned as a <file> element.
Package File Locations
In order to reduce interference with the base system, the package files' destiation paths are restricted to a set of safe package file locations. These are listed below.
|location Attribute Value||Purpose||Path|
|data||application data files (private libraries, images, ...)||/opt/provider-name|
|bin||"public" application binaries (part of $PATH)||/opt/bin/|
|xdg-applications||.desktop files (for menu entries)||/usr/local/share/applications/|
provider and name are substituted by the package's provider and package name, respectively.
NOTE: The locations listed above are not meant to be the only ones, they are just the ones currently implemented; see #To Be Done.
A Simple Example
The example below is the package manifest file of LSB Hello World, a very simple application I have written to demonstrate how software deployment using the LSB Package API could look like. The program - including a simple installer based on #liblsb_package - is attached to this document.
<?xml version="1.0" encoding="UTF-8"?> <package name="helloworld" provider="lsb" version="0.1" arch="i386" > <displayed-name>LSB Hello World</displayed-name> <displayed-provider>Linux Standard Base</displayed-provider> <description>This program demonstrates how applications can be deployed using the LSB Package API.</description> <file location="bin">/lsb-helloworld</file> <file location="data" type="dir">/</file> <file location="data" type="dir">/share</file> <file location="data">/share/icon.png</file> <file location="data">/share/image.png</file> <file location="etc" type="dir">/</file> <file location="etc">/message</file> <file location="xdg-applications">/lsb-helloworld.desktop</file> </package>
The prototype implementation consists of three parts:
- The #lsb_packaged daemon, which provides the LSB Package Service.
- A set of #lsb_packaged backends for different packaging systems. Currently implemented are simple backends for RPM and the Debian packaging system (dpkg).
- The #liblsb_package library, a convenience interface for implementing installers and lsb_packaged backends.
lsb_packaged is a daemon that implements the LSB Package Service. The main duty of the actual daemon is to receive incoming D-Bus messages on the system bus and to verify them, including parsing and validating the manifest file, checking for file conflicts, and ensuring correct PolicyKit authentication. The actual package registration and stub file creation is done by the backend, which is dynamically loaded and called by the daemon using
dlopen(). Which backend is loaded is defined in the /etc/lsb_package/backend configuration file. It must contain nothing but the path to the object file to be loaded, e.g. /usr/share/liblsb_package-rpm.so.
The lsb_packaged daemon is not persistent. Instead, D-Bus system activation to start the daemon if a method of the LSB Package API is called. After all requests have been processed, lsb_packaged will exit again. This limits the runtime of lsb_packaged to the moments it is actually needed.
lsb_packaged depends on the following libraries:
- dbus-glib (>= 0.73) to register the org.linuxbase.Packages service and listen for incoming messages.
- libexpat to parse package manifest files.
- PolicyKit (>= 0.7) to enforce the org.linuxbase.packages.install policy.
The liblsb_package library provides a convenient programming interface to access the LSB Package API from a program written in C or C++. Additionally, it defines a set of functions and structs for the implementation of lsb_packaged backends. The former purpose is covered by the <lsb_package.h> header file, while the declarations for the latter reside in <lsb_package-backend.h>.
<lsb_package.h> declares the following structures and functions:
- int code;
- char *msg;
- Stores information about an error which occured during package registration. Every error has a code which indicates its type (code) and an error message (msg) with more details about the error. Valid error codes are:
Name Description RERR_MANIFEST invalid package manifest RERR_NAME_CONFLICT package name conflict (e.g. package with the same name and provider already installed) RERR_FILE_CONFLICT package file conflict (e.g. a specified file already exists) RERR_ACCESS access to LSB Package API denied (e.g. missing authentication) RERR_INTERNAL internal error (e.g. D-Bus error, IO, memory, ...)
typedef unisgned int pkgid_t;
- A package identifier as returned by register_package().
void free_regerr(regerr_t *err);
- Frees a regerr_t struct.
int obtain_install_auth(uint32_t xid);
- Tries to obtain the rights to install third-party software. If the program does not have these rights yet, the user is asked for authentication using PolicyKit. If PolicyKit creates an authentication dialog window and the xid argument is not 0, it defines the X window ID of the window for which the dialog should be transient. If install rights were successfully obtained, 0 is returned, otherwise -1.
- This function should be called before using register_package().
pkgid_t register_package(const char *manifest_path, regerr_t **err);
- Calls the LSB Package API's RegisterPackage() method with the passed package manifest file path as argument. If an error occurs during package registration,
*erris changed to point to a regerr_t struct describing the error, and 0 is returned. Otherwise, register_package() returns an identifier for the registered package, which should be passed to close_package() when the installation is complete.
void close_package(pkid_t pkgid);
- Calls the LSB Package API's ClosePackage() method with the passed package identifier as argument. Usually, the return value of a former register_package() call is passed.
<lsb_package-backend.h> declares the following:
- Represents a package file location (see #Package File Locations). Despite the leading FLOC_, the name of each of the enum constants equals the name of the represented file location in upper case, with dashes replaced by underscores. For instance, FLOC_XDG_APPLICATIONS stands for the "xdg-applications" location.
- Represents a package file type. The FTYPE_REGULAR constant stands for a regular file, while FTYPE_DIR is synonymous for the directory file type. Note that FTYPE_SYMLINK is not yet supported properly.
- char *path;
- pftype_t type;
- pfloc_t location;
- Represents a package file. The struct's members represent the values of the equally named <file> tag's attributes.
- pfilelist_t *next;
- pfile_t *file;
- A singly-linked package file list structure.
- char *pkgname;
- char *pkgprovider;
- char *pkgversion;
- char *pkgarch;
- pfilelist_t *pkgfiles;
- char *pkgdescription;
- char *pkgdisplayedname;
- char *pkgdisplayedprovider;
- Represents a complete package manifest. pkgname, pkgprovider, pkgversion, and pkgarch store the values of the <package> tag's name, provider, version, and arch attributes, respectively. pkgfiles is a list of the files soecified as belonging to the package. pkgdescription, pkgdisplayedname, and pkgdisplayedprovider store the contents of the manifest's <description>, <displayed-name> and <displayed-provider> elements.
regerr_t *new_regerr(int errcode, const char *fmt, ...);
- Creates a new regerr_t struct with the specified error code and message. The message is created from the passed format and format arguments using printf()-style formatting. The returned struct must be freed with free_regerr().
char *full_pfile_path(pfile_t *file, pmanifest_t *mf);
- Returns the canonical absolute path of a package file.
void mkpfile(pfile_t *file, pmanifest_t *mf, uid_t uid, regerr_t **err);
- Creates the passed package file on the file system. If it is not a directory, the created file's ownership is set to the passed UID.
liblsb_package depends on the following libraries:
- dbus-glib (>= 0.73) to call the LSB Package API methods.
- PolicyKit (>= 0.7) for obtain_install_rights().
An lsb_packaged backend implements the LSB Package API for a particular packaging system. A backend is simply a shared object file which exports the following interfaces:
void _register_package(pmanifest_t *mf, uid_t uid, regerr_t **err);
- In this function, the backend shall add the package described by the passed manifest to its database. Additionally, it shall create all package directories and stub files using mkpfile(). If the package cannot be registered or an error occurs, *err shall be set to point to a regerr_t struct describing the error.
void _close_package(pmanifest_t *mf);
- Called by the package service after a package was closed with the ClosePackage() method. In this function, the backend may retrieve information about the installed package files, e.g. to determine the package's size. However, the success of an installation shall NOT depend on the success of _close_package(); instead, a successful run of _register_package() shall suffice for a successful package installation.
Currently, there are simple backends for both RPM and dpkg. As the latter does not export a programming interface, the backend for dpkg is implemented as an addition to the dpkg codebase itself (to be turned on by passing --with-lsb to ./configure). The RPM backend uses librpm instead and can thus be deployed separately from RPM itself.
To Be Done
- The values allowed in the version and arch attributes are yet completely unspecified. For the former, this is needed to guarantee a reliable version comparision scheme (there are differences about how versions are sorted across packaging system IIRC), and for the latter because it is completely useless otherwise.
- A simple dependency system should be added. At least dependencies to particular versions of the LSB should be specifiable. Dependencies between packages of the same provider could also be supported. Contrary to the Berlin Packagaing API proposal, I think dependencies should not be expressed as interface function but in the package manifest file, as (a) they can be verified by the package service then and (b) they can be added to the package system's database (so that the user is warned if he/she attempts to remove a dependency).
- Internationalization of the package metadata was not taken into account yet. It should be possible to define <description>, <displayed-name> and <displayed-provider> for multiple languages. A possible solution would be to have multiple of these elements with different xml:lang attribute values like with the MIME type files, e.g.
<displayed-name xml:lang='de'>LSB Hallo Welt</displayed-name>.
- More sufficiently safe #package file locations should be added when needed. There may also be a need to provide a "system" location that maps to the root directory (/) as some low-level applications might need this; however, attempting to install to such a location should at least cause the LSB Package Service to show a BIG FAT WARNING to the user to inform him/her about possible interference with the base system.
- Generally, it is preferable to have as much installed into /opt as possible. For instance, it would be great if the XDG Base Directory Specification would be extended to include /opt/share in $XDG_DATA_DIRS. We could also install .desktop files to /opt/applications then, for instance.
- Uninstalling and updating an application is not handled yet in the API. While uninstalling is not that critical as this can be done with the package manager, a way of updating is desperately needed and would have to be added to this API.
Note: Make sure you read the descriptions on the linked file pages, they contain important information about getting everything working!
Comments and criticism are very welcome. Please add any commentary to the Discussion page.