The Linux Foundation

 
Burgdorf Packaging API

From The Linux Foundation

NOTE: This proposal has been superseded by the Psys Library. I am leaving this page here for historical purposes.

This document describes a proposed D-BUS API for LSB application installers, dubbed "Burgdorf Packaging API" (formally known as LSB Package API, but renamed to express it's unoffical status). 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. For a sample implementation of this proposal, see #Download.

Contents

Introduction

The Burgdorf Packaging 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 Burgdorf Packaging API comes from the "Berlin Packaging API" proposal. In fact, the Burgdorf API can be considered a proposed design 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. Other interesting reads regarding the Berlin API are:

The Interace

The distribution's packaging system is interfaced through a D-Bus service - the Burgdorf 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 Burgdorf 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 Burgdorf Packaging 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 Burgdorf Packaging API can be installed on any system providing the Burgdorf 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

<package
name='...'
provider='...'
version='...'
arch='...'
displayed-name='...'
displayed-provider='...'>
<displayed-name>?
<displayed-provider>?
<description>?
<lsb-dependency>*
<file>*
</package>
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. The values guaranteed to be supported are listed below.
arch Value Architecture
noarch Not architecture-specific
ia32 Intel Architecture (32-bit)
ia64 Intel Architecture (64-bit)
ppc32 PowerPC Architecture (32-bit)
ppc64 PowerPC Architecture (64-bit)
s390 IBM zSeries (S/390) Architecture
s390X IBM zSeries (S/390X) Architecture
amd64 AMD64 Architecture
NOTE: The list of architectures was taken from the list of architecture-specific LSB specifications here.
<displayed-name xml:lang='...'>...</displayed-name>
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.
* xml:lang (optional): The displayed name's language. This attribute allows to translate the displayed package name into multiple languages. If no language is specified, the generic "C" locale is assumed.
<displayed-provider xml:lang='...'>...</displayed-provider>
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.
* xml:lang (optional): The displayed provider's language. This attribute allows to translate the displayed package provider into multiple languages. If no language is specified, the generic "C" locale is assumed.
<description xml:lang='...'>...</description>
Defines a description for the package.
* xml:lang (optional): The description's language. This attribute allows to translate the package description into multiple languages. If no language is specified, the generic "C" locale is assumed.
<lsb-dependency module='...' version'...'>...</file>
Defines a dependency of the package on a specific LSB module. Attributes are:
* module (optional): The LSB module on which the package depends. If not specified, the core module is assumed.
* version: The version of the LSB module with which the base system must comply.
<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 Burgdorf 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 Burgdorf 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
etc configuration files /etc/opt/provider-name/
var variable data /var/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 Burgdorf API could look like. The program - including a simple installer based on #libburgdorf - is part of this proposal's sample implementation (see #Download).

<?xml version="1.0" encoding="UTF-8"?>


<package name="helloworld" provider="lsb" version="0.1" arch="ia32" >

  <displayed-name xml:lang="en">LSB Hello World</displayed-name>
  <displayed-name xml:lang="de">LSB Hallo-Welt</displayed-name>

  <displayed-provider>Linux Standard Base</displayed-provider>

  <description xml:lang="en">This program demonstrates how applications can be distributed using the Burgdorf Package API.</description>
  <description xml:lang="de">Dieses Programm zeigt, wie Anwendungen mittels der Burgdorf Paket-API verteilt werden können.</description>

  <lsb-dependency module="desktop" version="3.1"/>

  <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>

Implementation Details

The prototype implementation consists of three parts:

  • The #burgdorfd daemon, which provides the Burgdorf Package Service.
  • A set of #burgdorfd backends for different packaging systems. Currently implemented are simple backends for RPM and the Debian packaging system (dpkg).
  • The #libburgdorf library, a convenience interface for implementing installers and burgdorfd backends.

burgdorfd

burgdorfd is a daemon that implements the Burgdorf 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/libburgdorf-rpm.so.

The burgdorfd daemon is not persistent. Instead, D-Bus system activation to start the daemon if a method of the Burgdorf Packaging API is called. After all requests have been processed, burgdorfd will exit again. This limits the runtime of burgdorfd to the moments it is actually needed.

burgdorfd 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.
  • libburgdorf.

libburgdorf

The libburgdorf library provides a convenient programming interface to access the Burgdorf Packaging API from a program written in C or C++. Additionally, it defines a set of functions and structs for the implementation of burgdorfd 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:

typedef struct
{
int code;
char *msg;
}
regerr_t;
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_DEPENDENCY unmet dependency
RERR_ACCESS access to Burgdorf Packaging 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 Burgdorf Packaging API's RegisterPackage() method with the passed package manifest file path as argument. If an error occurs during package registration, *err is 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 Burgdorf Packaging 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:

typedef enum
{
FLOC_DATA,
FLOC_ETC,
FLOC_VAR,
FLOC_BIN,
FLOC_XDG_APPLICATIONS
}
pfloc_t;
Represents a package file location (see #Package File Locations). Despite the leading "FLOC_", the name of each of each enum constant 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.
typedef enum
{
FTYPE_REGULAR,
FTYPE_DIR,
FTYPE_SYMLINK
}
pftype_t;
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.
typedef struct _plstr
{
struct _plstr *next;
char *lang;
char *str;
}
plstr_t;
Represents a localized string like the package's displayed name or description. lang and str store the language code and content of the string in a particular language. The next member points to a plstr_t struct which represents the string in the next language to which it was translated, if any.
typedef struct
{
char *module;
char *version;
}
pdep_t;
Represents a package dependency. The module and version members specify the required LSB module and module version, respectively.
typedef struct
{
pdeplist_t *next;
pdep_t *dep;
}
pdeplist_t;
A singly-linked package dependency list structure.
typedef struct
{
char *path;
pftype_t type;
pfloc_t location;
}
pfile_t;
Represents a package file. The struct's members represent the values of the equally named <file> tag's attributes.
typedef struct
{
pfilelist_t *next;
pfile_t *file;
}
pfilelist_t;
A singly-linked package file list structure.
typedef struct
{
char *pkgname;
char *pkgprovider;
char *pkgversion;
char *pkgarch;
pdeplist_t *pkgdeps;
pfilelist_t *pkgfiles;
plstr_t *pkgdescription;
plstr_t *pkgdisplayedname;
plstr_t *pkgdisplayedprovider;
}
pmanifest_t;
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. pkgdeps specifies the package's dependencies. pkgfiles is a list of the files defined 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.

libburgdorf depends on the following libraries:

  • dbus-glib (>= 0.73) to call the Burgdorf Packaging API methods.
  • PolicyKit (>= 0.7) for obtain_install_rights().

burgdorfd Backends

An burgdorfd backend implements the Burgdorf Packaging 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 (unofficial) add-on to the dpkg codebase itself. The RPM backend uses librpm instead and can thus be deployed separately from RPM itself.

To Be Done

  • version values: The values allowed in the package manifest file's version attribute is yet completely unspecified. It must be specified to guarantee a reliable version comparision scheme (there are differences about how versions are sorted across packaging system IIRC), which in turn is a requirement for a cross-distro updating facility.
  • Dependencies to other packages: While dependencies to LSB modules can be defined, it might be a good idea to allow dependencies to other packages too - at least to a certain extent. A possible restriction could be that only dependencies between packages of the same provider are allowed. This would enable application vendors to share common data of application suites (like OpenOffice) between the suite's components.
  • More #package file locations: It must be evaluated which further file locations are needed and "safe" enough. 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 Burgdorf Package Service to show a severe 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 install .desktop files to /opt/share/applications then, for instance.
  • Uninstalling and updating: This 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.

Download

The complete source code of the sample implementation is located at a Git repository at Gitorious. To retrieve it, type the following in a terminal:

git clone git://gitorious.org/burgdorf/mainline.git burgdorf

or, if the above causes problems:

git clone http://git.gitorious.org/burgdorf/mainline.git burgdorf

If you have proposed changes or additions to the implementation, feel free to add a clone of the sample implementation's repository to the Gitorious page and implement your idea there. (Just click on the "mainline" repository and choose "Clone Repository" on the right. Note that you need create a Gitorious user account to do this - registration is free, naturally.)

Discussion

Comments and criticism are very welcome. Please add any commentary to the Discussion page.


[Article] [Discussion] [View source] [History]