User Tools

Site Tools


etc:20-lpm-gui

Creating an lpm gui/frontend

The main lpm frontend is a command-line program called “lpm”. It allows installation, updates and removal of packages to the local package repository, either from locally available package files or from a configured package catalogue.

There is not a lot of detailed documentation available, so this document first tries to explain:

  • the basic rules of lpm
  • how the local package repository looks
  • how an lpk package looks
  • how lpm accesses package catalogues

1. LPM rules

Like other package management systems, the purpose of lpm is the controlled installation, update and removal of software, or documentation. In order to achieve this, lpm enforces a set of rules that must be met.

  • Any software destined to be installed must be contained in an lpk package.
  • An lpk package is basically defined by a name, a version, optionally a variant, optionally one or more dependencies, and also all files with full path-names that the package wants to install.
  • Any package name can only be installed once locally.
  • A variant defines an alternative implementation of one named package, only one variant can be installed per package.
  • A package can only be installed if all its dependencies are also installed
  • No package can overwrite a file already installed by another package
  • Only one package can be installed at the same time
  • A package installation either succeeds or fails, there most be no half-installed packages

There are some other features of lpm, but these rules build the core of how lpm tries to ensure the integrity of locally installed software.

2. Internal structure of the local package repository

The local package repository of lpm has a very simple structure.

Per default, the repository resides in the directory /var/lib/lpm. Below are a set of directories holding any information about the currently installed pacakges and other stuff:

  • installed : This directory contains one subdirectory for every installed package, with the package name serving as the directory name.
  • fileindex : This directory contains one file for every installed package, with the package name serving as the filename
  • scriptdir : This directory contains a busybox environment used as the root for any installation scripts of a package
  • stagearea : This directory is used to download and extract any package
  • certstore : This directory holds all cerficates of registered package creators
  • lpc-http-cache : If the lpc-http catalogue is used, this directory holds the downloaded package indexes

All information about installed packages is stored in world readable text files below installed and fileindex.

The fileindex file for any package is a simple list of filenames, one line per file. The filenames are fully qualified, with path names.

The package directory for any package contains a set of files containing other information about the package:

  • desc.txt: contains the basic info of the currently installed package version
  • inst.txt: contains information about when and why (per explicit request or as a dependency) the package was installed or updated.

There are usually some other files concerning file checksums, symlink information, install scripts and stuff that are important for proper working.

3. Format of an lpk package

lpm's native package format is called lpk. Like other package format such as dpkg or rpm, it basically is implemented as an archive containing all the files to be installed, some package description and optional scripts to be run after installation or deletion.

As archive format, lpk uses gzip compressed cpio, prepended by a header containing the package information. The prepended header looks like this:

  • 11 bytes made from 5 bytes containing the version signature (ELPM20) and 6 bytes containing the zero-padded size of the following header
  • The package description, starting at byte 12.

Right after, the gzipped cpio archive starts. It is divided into so-called forks, top-level directories of different meaning:

  • info: this forks contains again the package description called desc.txt, plus optionally
    - perm.txt, if special file-permisions (such as set-uid bits) have to be set
    - link.txt, if any symbolic links have to be set
  • data: this fork contains all files that should be installed by the package, except config files
  • conf: this fork contains all config files that should be installed by the package. They are handled separately due to different rules on updates.
  • init: this fork contains any scripts that should be run before or after package installation or removal
  • dump: files in this fork are just copied into the package's repository directory corresponding dump directory. This is currently used in LGL for locale data of packages that are then extracted by the lgl-lang program.
  • hash: this fork contains one file per fork containing the sha1 checksums for each file in the fork. This is used to ensure complete package extraction and installation.

The difference of the package description in the header and in the archive is that the header contains the package's signer DN and signature, with the signature being made over the whole gzipped cpio archive, but not the prepending header.

A package description looks like this:

elpmVersion: 2.0
elpkName: xfce4-terminal
elpkVersion: 1.0.3-1
elpkCreator: Tim Tassonis <stuff@decentral.ch>
elpkArchitecture: x86_64
elpkDependency: vte-gtk3-lib
elpkDependency: dbus
elpkDependency: xfce4ui-lib
elpkDescription: xfce4 terminal emulator
elpkDataSize: 545790
elpkRsaSigSubj:: L0NOPVRpbSBUYXNzb25pcy9lbWFpbEFkZHJlc3M9cGtnYWRtaW5AbGFjb25p
 YW4tbGludXgub3Jn
elpkRsaSigData:: wiulVyuSojih5X+1HvqF8csbXdepgkbmCMv0JlJyamiAs6P7Tv2QiDEZtofv
 IleAZt23qbpBqpzXVwReNR4JJN3/xpeHnPcV3jmkNmgXx7yaNcfg3WKW6jrHWsben8BUCfKzsXar
 NvyIQzr5OzWkbiNBjs2QIEkFTu8re909837+mEdNxg5tAbsdOGSXoXGv/ZJ+Ow/c9gHjLorBn88u
 v8cNrQzDjhjXLW4tfxszXF6ZklIauZq5iGKxj1mgkjK2SVsQPMYNpOlYi4jn0CcXhyZxi4ixvFj4
 n8JFB3RAZd8k/0P4UCl5zJNc2nNBiF8YgYez1eHEkoF3oKFJ4byxnQ==

The used text format is basically ldif, as described here . It allows multiple values per attribute (elpkDependency), as well as base64 encoded binary data (elpkRsaSigData) and is quite easy to parse, and much easier on the eye as XML.

3.1 Package scripts

A package can contains several scripts in its init fork that should be run when the package is installed, updated or removed. They are:

  • 00-add.sh: if present, this will be run before an actual installation or update. If it does not return 0, the action will be aborted.
  • 00-del.sh: if present, this will be run before an actual removal. If it does not return 0, the action will be aborted.
  • 99-add.sh: This will be run after an actual installation or update, also aborting if unsucessful.
  • 99-del.sh: You get the idea
  • 99-cfg.ch: This will run after after an actual installation or update, but never abort the action.

4. Package catalogues

While some other package managers handle the retrieval of packages via a wrapper over the actual package manager (apt for dpkg, yum/dnf for rpm), lpm goes an other way by directly integrating so-called package catalogues.This was actually one of the main motivations for creating lpm.

Package catalogues are implemented as plugins that have to implement a set of fuctions which are then called by lpm at the appropriate time. A local lpm installation can only load exactly one lpc plugin, as defined in /etc/lpm.conf. Per default, the plugins must reside in /usr/lib/lpm as a shared library.

Apart from the shared library, an lpc catalouge may also have separate config files and programs to manage the catalogue.

Any lpc must implement the following functions:

LPC  *lpc_connect(struct s_ekva **lpm_sys_conf,struct s_llog *main_llog)
int  lpc_refresh(LPC *lpc)
int  lpc_findpkg(LPC *lpc, char *name, char *variant,char *version, int arch, EKVA **ext_attrs, LPI ***lpi_list)
int  lpc_readpkg(LPC *lpc,LPI *lpi, FILE *dest_file)
int  lpc_release(LPC *lpc)
  • lpc_connect is used to connect lpm to the catalogue, so it can then be used to find and retrieve packages
  • lpc_findpkg is used to find packages in the catalogue, matching the specified criterias. it has to return the number of packages, plus their package descriptions as a list.
  • lpc_readpkg is used to retrieve exactly one package, as defined in the description and have it stored in the specified FILE *
  • lpc_refresh is used to refresh the contents of the catalogue.
  • lpc_release will disconnect lpm from the catalogue

Per default, lpm brings along a simple directory-based lpc called lpc-dirs, which is great for testing purposes and also for an initial distro installation. As part of its source code, lpm also contains the lpc-http lpc, which allows to access remote package catalogues over http and also ftp.

5. Basic information for creating lpm frontends

In order to create an lpm frontend, you first have to download its complete source code, as there is no separate development package with only the needed parts available. This can be done via git:

git clone http://git.decentral.ch/lpm.git
cd lpm/src
git clone http://git.decentral.ch/libstuff.git stuff

After building lpm successfully, the source directory will then contain the files liblpm.a and lpm.h that will contain all definitions and functions needed to create another working front end.

The main lpm frontend program (main.c) also implements all its functionality using those two files.

When creating another frontend for lpm, it is however advisable to only implement the non-prvileged parts directly and use the privileged plpm program for any actual package installations or updates. This is due to the fact that installs and updates require root privileges, and all gui frameworks strongly discourage running gui programs as root.

plpm is a reduced commandline program always running as root, but only allowing to be used by members of a defined group. It is therefore the preferred method to let a gui program perform any privileged action. It also implements a simple call to check if the user is allowed to use it, so a gui frontend can find out early on if the used is allowed to install packages.

6. The sample lpmsh frontend

In the subdirectoey guix, there is a file called lpmsh.c, implementing a sample lpm frontend in form as a shell. The shell example was chosen, as it:

  • needs minimal additional code for its implementation
  • has a division of startup code and a loop implementing several functions

It therefore might server as a good reference of how a gui might call the lpm funcions and process their data.

7. plplm usage

plpm implements the following commands:

  • allowed: This takes no argument and will return 0 if the user is allowed to use plpm.
  • refresh: This takes no argument and will refresh the currently used package catalogue
  • cleanup: This takes no argument and will clean the stagearea from package files
  • install: This takes arguments described below and will install new packages
  • upgrade: This may take arguments and will update installed packages

If upgrade is called without arguments, it will update all packages where a new version is available

Otherwise, both upgrade and install may be called with one or more package names to be installed or updated

If only one package is specified, the caller may also first specify a specific version and/or variant to be used.

The usage is then as follows:

plpm install|upgrade -o version=$my_version,variant=$my_variant $my_package

In all other cases, the usage is:

plpm install|upgrade $package_1 $package_2 ...

The function plpm_exec in lpmsh.c provides a working example on how to call plpm in all cases.

8. Important frontend data structures

8.1 LPI

Most relevant structures and functions are defined in the header file lpm.h, the most important structure is:

struct s_lpk_info {
    char *name;
    char *variant;
    char *version;
    char *creator;
    int arch;
    char *desc;
    char *clog;
    char **deps;
    int removable;
    unsigned long datasize;
    /* The next two are only for repository packages */
    unsigned long filesize;
    char *filehash;
    char *abs_uri;
    char *rel_uri;
    /* These three are only for locally installed packages */
    time_t install_time;
    char  install_type;
    char *install_head;
    time_t *update_time;
    /* The next three are for the signature */
    char *rsa_sig_subj;
    char *rsa_sig_data_buf;
    int rsa_sig_data_len;
    int rsa_verified;
    /* Now follows a ekva with any further extended attributes */
    struct s_ekva **ext_attributes;
};

typedef struct s_lpk_info LPI;

LPI is used throughout lpm for any package information, either for installed packages, available packages or package files. It contains all relevant information of any package, but not its actual contents. Consequently, all functions that will return information about one or more packages with either return one LPI, or an array of LPI's.

The meaning of its members are:

namethe package name
variantthe package variant, can be NULL
versionthe package version
creatorname and mail address of the package creator
archeither i686,x86_64,mips or any, largely unused
descthe package description
clogthe package's changelog, unused
depsan array of names of the package's dependencies, can be NULL
removableboolean of package is removable, absolutely unused
datasizethe sum of bytes in the data part of a package
filesizethe filesize of a package in lpc catalogue
abs_urithe absolute uri of a package in lpc catalogue
rel_urlthe relative uri of a package in lpc catalogue
install_timethe installation unix time of an installed package
install_type
X for an installed package if explicitely installed, D if installed as a dependency
install_headif installed as a dependency, the name of the package having required it
update_timean array of unix time when the package was updated, can be NULL
rsa_sig_datathe buffer and it's size containing the package's signature
rsa_sig_subjthe subject dn of the package signer
rsa_verifieda flag containing the vefication status
ext_attributesan EKVA of other package attributes, currently unused

8.2 EKVA

EKVA is an array of stuctures containg a pointer to a key and to a value and is defined in stuff/ekva.h:

struct s_ekva {
    char *key;
    char *val;
};

typedef struct s_ekva EKVA;

EKVA arrays are used extensively throughout lpm as text-based keyword-value stores, with multiple values for one keyword possible depending on its initialisation. As their first element, they hold a header containing their current size an mode, allowing for automatic growing and management of multiple values.

EKVA arrays are created, filled, read and deleted by a set of functions defined in ekva.h, the most important ones being:

- ekva_new: to create a new EKVA array
- ekva_addval: to add a key/val pair to the EKVA arrray
- ekva_getval: to read the first value for a key
- ekva_cntval: to count the number of vaules for a key
- ekva_getone: to read the specified value for a key
- ekva_getvals: to read all values for a key

8.3 LPA

LPA arrays are used to store information about available updates in an easily parsable form:

struct s_lpa {
    char action;
    char status;
    LPI *lpi;
    char *candidate;
};
typedef struct s_lpa LPA;

- action is 'U' or 'I', as an update might pull a new dependency
- status is always 'N'
- lpi contains the package info of the package to be installed or updated

9. important frontend functions

All frontend functions are defined int lpm.h, the ones relevant to a frontend using plpm for privileged actions are:

- lpm_open: “opens” the local lpm repository and return an LPM * structure for further use.

- lpmui_init: initializes the internally used lpmui, necessary, but has no effect…

- lpm_lpc_load: loads the configured package catalogue

- lpm_lpc_connect: connects the program to the package catalogie

- lpm_lpc_cache_fill: fills the package catalogue cache

- lpm_lpc_release: disconnects from the package catalogue

- lpm_cache_free: frees the cache

- lpk_info_ekva: converts an LPI * package description to a string-based EKVA * keyword/value list

- lpm_list_lpi: returns a list of locally installed package descriptions

- lpm_lpf_info: returns a package description from a package file

- lpm_findpkg_new: returns a list of available, not installed packages

- lconf_read: reads the lpm config file

- log_init: initializes the internal logging mechanism, necessary

- lpm_pre_update: checks for an available update of a specified package

- lpm_lpa_list_add_lpt: adds the results of lpm_pre_update to an easily readable LPA * list

10. Required globals

In order for the frontend to be able to use all further funtions correctly, it has to define a set of global variables:

void *lpc_backend_handle;
EKVA **lpm_sys_conf = NULL;
LPC *lpc;

int dry_run =  0;
int vote_mode = LPM_VOTE_DEFAULT;
LPMUI *lpmui = NULL;
struct s_llog *llog;

LPI **lpc_cache;

11. Required initialization sequence

Maybe in later releases, the initialisation sequence will be simplyfied and merged into some lpm_init function, but for the moment, the code from line 490 - 542 from lpmsh.c can be copied for that purpose.

etc/20-lpm-gui.txt · Last modified: by wikiadmin