Introduction to writing plugins for libpkg in FreeBSD

Plugins are being used for extending pkgng’s functionallity allowing
you to write a custom code which hooks into libpkg.

Another use of plugins is the ability to write new commands for the
frontend.

In this handbook we will see how plugins work and how to write a few
simple plugins for pkgng.

At the end of this handbook you can find links to example pkgng
plugins and some demos of pkgng plugins in action.

Requirements

Basic programming skills

How plugins work inside pkgng

Here I will add a few short notes on how plugins work inside libpkg.

In general they can be summarized in these simple steps:

Plugins discovery

Plugins initialization

Plugins registration and hooking

Exposing libpkg to plugins

Plugins shutdown

In the next sections of this handbook we discuss about each of the
above.

Plugins discovery

The first thing we need in order to get plugins working in pkgng is
that we have a way to discover them.

Plugins are being discovered by pkgng during initialization and each
valid plugin is being loaded as a dynamic shared library.

So how does plugins discovery work?

Upon initialization pkgng will try to load the plugins defined by
the PLUGINS configuration option.

The PLUGINS configuration option is a comma-separated list, which
specifies the plugins we want to have loaded.

Example of setting the PLUGINS option in pkg.conf(5) would look
like this:

PLUGINS : [commands/mystats, zfssnap]

This would load the mystats plugins which provides a new pkg
mystats command and the zfssnap plugin for creating ZFS snapshots
before any install/deinstall actions are taken.

As mentioned in the beginning of this handbook, plugins a simply a
shared libraries and reside in the directory pointed by the
PKG_PLUGINS_DIR directory, which by default is set to
/usr/local/lib/pkg/ directory.

It is important to mention that only the plugins specified by the
PLUGINS configuration option will be loaded. With that being said
this means that if you just place a file in PKG_PLUGINS_DIR
directory, that plugin will not be loaded until you add it to the
PLUGINS list.

And that is how simple the plugins discover in pkgng is - just add
drop your plugin to PKG_PLUGINS_DIR directory and add the plugin to
the PLUGINS option in order to load the plugin and make it available
for use.

Plugins initialization

Discovering the plugins by pkgng and loading them is just the first
step.

Once a plugin is loaded libpkg will ask for the plugin to initialize
itself.

During the plugin initialization step a plugin generally does two
things:

Registering a hook into the library is not required by all
plugins. For example plugins that provide new commands to the pkg
frontend are not registering hooks into the library. Such example
plugin is the command-mystats
plugin.

Other plugins which directly hook into libpkg must register a
hook. Such plugins are being executed when a certain condition occurs

like for example executing a plugin before any install/deinstall
actions are taken.

These are the two required steps a plugin must perform during
initialization. Of course other initialization steps could be taken,
if for example the plugin requires it - like for example loading a
plugin specific configuration file. See the zfssnap
plugin
for such an example.

Enough theory, let’s look at some code now!

In order to get a plugin initialized libpkg will look for a function
with specific name provided by the plugin which does the
initialization. That function is called init().

The plugin’s init function has the following prototype:

intinit(structpkg_plugin*p);

The plugin’s init function should also take care of returning proper
return values, where EPKG_OK (0) means successful initialization and
EPKG_FATAL ( > 0 ) means failure during initialization. Plugins
which failed to initialize will not be loaded.

Below you can see the definition of the stats
plugininit() function, which performs the initialization of the stats
plugin and
returns EPKG_OK or EPKG_FATAL depending on whether the plugin
initialized successfully or not.

Don’t worry about the pkg_plugin_hook_register() function if it
doesn’t ring any bell to you for now as we’ll be discussing it a bit
later in the next section of the handbook.

In the next section we take a closer look at the init() functions
and how to register a hook into libpkg.

Plugins registration and hooking

In the plugin’s init() function we perform any internal
initialization of the plugin and also registering a hook into the
library, so that libpkg can trigger an execution of our plugin upon
certain event.

This is where we actually hook into the library using the
pkg_plugin_hook_register() and providing a callback function which
will be called by libpkg.

The second part of the plugin’s init function is the place where we
hook into the library and provide a callback function for performing
the real work of the plugin:

if(pkg_plugin_hook_register(p,PKG_PLUGIN_HOOK_PRE_INSTALL,&plugin_stats_callback)!=EPKG_OK){fprintf(stderr,"Plugin '%s' failed to hook into the library\n",PLUGIN_NAME);return(EPKG_FATAL);}if(pkg_plugin_hook_register(p,PKG_PLUGIN_HOOK_POST_INSTALL,&plugin_stats_callback)!=EPKG_OK){fprintf(stderr,"Plugin '%s' failed to hook into the library\n",PLUGIN_NAME);return(EPKG_FATAL);}if(pkg_plugin_hook_register(p,PKG_PLUGIN_HOOK_PRE_DEINSTALL,&plugin_stats_callback)!=EPKG_OK){fprintf(stderr,"Plugin '%s' failed to hook into the library\n",PLUGIN_NAME);return(EPKG_FATAL);}if(pkg_plugin_hook_register(p,PKG_PLUGIN_HOOK_POST_DEINSTALL,&plugin_stats_callback)!=EPKG_OK){fprintf(stderr,"Plugin '%s' failed to hook into the library\n",PLUGIN_NAME);return(EPKG_FATAL);}

As you can see here we have registered four hooks into the library
with a callback function named plugin_stats_callback() which will be
called prior any install/deinstall and after any install/deinstall
actions are taken.

More about the callback functions and its interaction with libpkg in
the next section of the handbook.

Exposing libpkg to plugins

In order to have plugins that do something really useful we need to
expose some of the capabilities of libpkg back to plugins.

The callback function prototype should have the following prototype:

intplugin_callback(void*data,structpkgdb*db);

In the above prototype data contains any data passed to the plugin
by the library. Proper casting of data to the correct type should be
done by the plugin itself, depending where a plugin actually hooks
in. The db argument contains a database pointer which can be used to
access the pkg database and perform any operations required by the
plugin to do it’s job.

In this chapter of the handbook we will define our callback function
for the stats plugin.

And below follows the definition of the plugin_stats_callback()
function:

And that is all. When libpkg starts installing/deinstalling packages
our plugin will get executed before and after the actual process
of installing/deinstalling packages in order to provide us with some
package statistics.

The callback function should also take care of returning proper codes
to the library - EPKG_OK (0) on success and EPKG_FATAL ( > 0 ) on
failure.

Plugins shutdown

Similar to the initialization phase of our plugin you should also
provide a shutdown function for the plugin which is executed right
before the plugin is unloaded by libpkg.

Having a shutdown function is something we use for things like -
free()’ing allocated memory, closing files and anything you can think
of a proper shutdown procedure should contain.

The plugin’s shutdown function prototype is as follows:

intpkg_plugins_shutdown_stats(structpkg_plugin*p);

And here’s the definition of the shutdown() function used by the
stats plugin:

The plugin’s shutdown function should also take care of returning
proper codes to the library - EPKG_OK (0) on success and
EPKG_FATAL ( > 0 ) on failure.

As the stats plugin is a simple one it does not perform any real
shutdown actions. You may want to have a look at the existing zfssnap
plugin
for example shutdown function performing a real shutdown procedure.

Plugin Applications

As this is just a short introduction to writing plugins for pkgng and
cannot cover all the possible plugin applications, here I’d like just
to mention what possible applications of plugins could be used.

Plugins could be used for writing new transfer protocols for pkgng,
for example we could have a custom package fetcher supporting rsync,
ssh or other protocols which are not supported natively by
pkgng. Such plugins should be generally hooking into
PKG_PLUGINS_HOOK_PRE_FETCH allowing them to perform the fetching.

We could also have plugins which could perform some post-actions like
for example when a package repository is created a plugin starts
serving the repository over HTTP to the clients.

Plugins would allow for extending pkgng’s capabilities a lot and in
general writing a plugin is just a matter of using your imagination
and innovation skills

So go ahead and write your new plugin plugin for pkgng! :)

Example pkgng plugins and demos

Here I will just add a few notes about example pkgng plugins and see
them in action.

In order to view the installed plugins on your pkgng-aware system you
should use the pkg plugins command:

As you can see from the above output libpkg is triggering our plugin
and we get useful stats for our database while pkgng is running.

zfssnap
plugin is
a plugin for creating ZFS snapshots on your system before doing any
install/deinstall actions, so in case something goes wrong after the
install/deinstall process you can easily rollback to a previously
known and working state.