Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/updates.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const updates = {
otp_password_scheme_removed: '2.4.5',
pbkdf2_hashing: '2.4.0',
passwd_file_iteration: '2.4.0',
plugin_dependencies_config: '2.4.6',
process_title_imap_process: '2.4.0',
process_title_initializing: '2.4.0',
process_title_mail_processes: '2.4.0',
Expand Down
54 changes: 35 additions & 19 deletions docs/developers/design/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,37 +42,53 @@ the API is too old to support your plugin. For example:

## Dependencies

Some plugins depend on another one. In some systems (but not all) it's
possible to handle this by giving a nicer error message than "symbol xyz
not found". There are two steps for this:
[[changed,plugin_dependencies_config]] Some plugins depend on another one.
The dependencies are declared with `struct setting_plugin_info`, which the
config process writes to the binary config. When loading the
[[setting,mail_plugins]], Dovecot verifies that all the required plugins are
also loaded before loading any of the plugins.

First create `<plugin_name>_dependencies` array listing plugin names that
the plugin depends on, like:
Plugins in Dovecot core declare these in their source files, which are
scanned by the config build:

```c
const char *imap_quota_plugin_dependencies[] = { "quota", NULL };
const struct setting_plugin_info imap_quota_plugin_info = {
.plugin = "lib11_imap_quota_plugin",
.required_plugins = (const char *const []) { "quota", NULL },
};
```

Then you'll also have to make the plugin .so binary link to the other
plugins:
The `plugin` field is the plugin's filename without the `.so` suffix. The
plugin name used in [[setting,mail_plugins]] is derived from it. The info is
ignored if the plugin file doesn't exist in the module directory.

```
if PLUGIN_DEPS
lib11_imap_quota_plugin_la_LIBADD = \
../quota/lib10_quota_plugin.la
endif
```
External plugins export them from their settings plugin (the plugin in the
`settings/` module directory) in a NULL-terminated
`<settings_plugin_name>_plugin_infos` array:

`PLUGIN_DEPS` is set only if plugin dependencies are actually supported.
Otherwise the build might fail or plugin loading might fail.
```c
const struct setting_plugin_info *foo_settings_plugin_infos[] = {
&foo_plugin_info,
NULL
};
```

Once all this is done, trying to load imap_quota plugin without quota
plugin gives a nice error message:
Trying to load imap_quota plugin without quota plugin then gives an error:

```
Error: Can't load plugin imap_quota_plugin: Plugin quota must be loaded also
Fatal: mail_plugins: Plugin imap_quota requires also plugin quota to be loaded
```

Don't link the plugin against the plugins it depends on. Linking against
loadable modules isn't portable. Instead, make sure the plugin's filename
prefix (e.g. `lib11_`) sorts after the plugins it depends on. Plugins are
loaded in that order, so the dependencies' symbols are already available
when the plugin is loaded. The config process logs a warning if a required
plugin would be loaded after the plugin requiring it.

The old `<plugin_name>_dependencies` array is still checked, but it can't
be checked before the plugin is loaded.

## Hooks

Different kinds of plugins can also hook into various things:
Expand Down
Loading