Table of Contents
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:
| name | the package name |
| variant | the package variant, can be NULL |
| version | the package version |
| creator | name and mail address of the package creator |
| arch | either i686,x86_64,mips or any, largely unused |
| desc | the package description |
| clog | the package's changelog, unused |
| deps | an array of names of the package's dependencies, can be NULL |
| removable | boolean of package is removable, absolutely unused |
| datasize | the sum of bytes in the data part of a package |
| filesize | the filesize of a package in lpc catalogue |
| abs_uri | the absolute uri of a package in lpc catalogue |
| rel_url | the relative uri of a package in lpc catalogue |
| install_time | the installation unix time of an installed package |
| install_type | X for an installed package if explicitely installed, D if installed as a dependency |
| install_head | if installed as a dependency, the name of the package having required it |
| update_time | an array of unix time when the package was updated, can be NULL |
| rsa_sig_data | the buffer and it's size containing the package's signature |
| rsa_sig_subj | the subject dn of the package signer |
| rsa_verified | a flag containing the vefication status |
| ext_attributes | an 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.
