2
0
mirror of https://github.com/vdukhovni/postfix synced 2025-08-22 18:07:41 +00:00

postfix-2.11-20131031

This commit is contained in:
Wietse Venema 2013-10-31 00:00:00 -05:00 committed by Viktor Dukhovni
parent 8b9901ce03
commit 274fd80ff6
31 changed files with 1330 additions and 747 deletions

3
postfix/.indent.pro vendored
View File

@ -1,4 +1,3 @@
-TMDB_val
-TABOUNCE -TABOUNCE
-TADDR_MATCH_LIST -TADDR_MATCH_LIST
-TADDR_PATTERN -TADDR_PATTERN
@ -178,6 +177,7 @@
-TMBLOCK -TMBLOCK
-TMBOX -TMBOX
-TMDB_txn -TMDB_txn
-TMDB_val
-TMILTER -TMILTER
-TMILTER8 -TMILTER8
-TMILTERS -TMILTERS
@ -270,6 +270,7 @@
-TSINGLE_SERVER -TSINGLE_SERVER
-TSINK_COMMAND -TSINK_COMMAND
-TSINK_STATE -TSINK_STATE
-TSLMDB
-TSMFICTX -TSMFICTX
-TSMTPD_CMD -TSMTPD_CMD
-TSMTPD_DEFER -TSMTPD_DEFER

View File

@ -10876,7 +10876,6 @@ Apologies for any names omitted.
Postfix 2.3 code review. Files: util/netstring.c, Postfix 2.3 code review. Files: util/netstring.c,
util/myaddrinfo.c, util/attr_clnt.c, util/vstream.c. util/myaddrinfo.c, util/attr_clnt.c, util/vstream.c.
Bugfix: the SMTP server now separates the message size check Bugfix: the SMTP server now separates the message size check
from the queue space check, so that the size check can be from the queue space check, so that the size check can be
done before an SMTPD proxy filter. Files: smtpd/smtpd.c, done before an SMTPD proxy filter. Files: smtpd/smtpd.c,
@ -13536,7 +13535,6 @@ Apologies for any names omitted.
Bugfix: Content-Transfer-Encoding: attribute values are Bugfix: Content-Transfer-Encoding: attribute values are
case insensitive. File: src/cleanup/cleanup_message.c. case insensitive. File: src/cleanup/cleanup_message.c.
20070514 20070514
Bugfix: the makedefs EPOLL workaround broke any attempt to Bugfix: the makedefs EPOLL workaround broke any attempt to
@ -17839,7 +17837,6 @@ Apologies for any names omitted.
util/Makefile.in, util/listen.h, util/recv_pass_attr.c, util/Makefile.in, util/listen.h, util/recv_pass_attr.c,
util/stream_listen.c, util/sys_defs.h, util/unix_pass_listen.c. util/stream_listen.c, util/sys_defs.h, util/unix_pass_listen.c.
20120618 20120618
Cleanup: made the postscreen-to-smtpd haproxy attribute Cleanup: made the postscreen-to-smtpd haproxy attribute
@ -18977,3 +18974,52 @@ Apologies for any names omitted.
in multi-programmed systems, and prohibit database sharing in multi-programmed systems, and prohibit database sharing
between privileged writer processes and unprivileged reader between privileged writer processes and unprivileged reader
processes. processes.
20131009
Documentation: inet_protols description was not updated
when smtp_address_preference was added. File: proto/postconf.proto
20131013
Documentation: why postscreen(8) uses hash-table lookups
instead of direct pointers to find the DNSBL lookup result
for a specific session. File: postscreen/postscreen_early.c.
20131022
Cleanup: add more &code; to postconf2man. Someone has been
writing documentation without checking the result, File:
mantools/postconf2man.
Documentation: in the discard(8) manpage, the reason is not
a host or domain name. File: discard/discard.c.
20131025
Documentation: specify the expected result format with
"list" tables. File: proto/DATABASE_README.html.
20131026
Future proofing: API changes in the PCRE library. File:
util/dict_pcre.c.
20131028
Feature: check_sasl_access to block hijacked logins. Files:
mantools/postlink, proto/postconf.proto, global/mail_params.h,
smtpd/smtpd_check.c, smtpd/smtpd_dsn_fix.h.
20131029-31
Cleanup: slmdb(3) simplified LMDB API that hides recoverable
LMDB errors from applications so that they can focus on
their own job. Files: util/slmdb.[hc].
Cleanup: LMDB functionality restored, after elimination of
1) world-writable lockfiles, 2) hard limits on the number
of concurrent readers, and 3) hard-coded database file inode
numbers in lockfiles that can prevent automatic crash
recovery. Files: proto/LMDB_README.html, proto/postconf.proto,
mantools/postlink, util/dict_lmdb.c.

View File

@ -55,12 +55,13 @@ new address) or access control (the lookup string is the client, sender or
recipient, and the result is an action such as "reject"). recipient, and the result is an action such as "reject").
With some tables, however, Postfix needs to know only if the lookup key exists. With some tables, however, Postfix needs to know only if the lookup key exists.
The lookup result itself is not used. Examples are the local_recipient_maps Any non-empty lookup result value may be used here: the lookup result is not
that determine what local recipients Postfix accepts in mail from the network, used. Examples are the local_recipient_maps that determine what local
the mydestination parameter that specifies what domains Postfix delivers recipients Postfix accepts in mail from the network, the mydestination
locally, or the mynetworks parameter that specifies the IP addresses of trusted parameter that specifies what domains Postfix delivers locally, or the
clients or client networks. Technically, these are lists, not tables. Despite mynetworks parameter that specifies the IP addresses of trusted clients or
the difference, Postfix lists are described here because they use the same client networks. Technically, these are lists, not tables. Despite the
difference, Postfix lists are described here because they use the same
underlying infrastructure as Postfix lookup tables. underlying infrastructure as Postfix lookup tables.
PPrreeppaarriinngg PPoossttffiixx ffoorr LLDDAAPP oorr SSQQLL llooookkuuppss PPrreeppaarriinngg PPoossttffiixx ffoorr LLDDAAPP oorr SSQQLL llooookkuuppss
@ -119,18 +120,21 @@ performance.
UUppddaattiinngg BBeerrkkeelleeyy DDBB ffiilleess ssaaffeellyy UUppddaattiinngg BBeerrkkeelleeyy DDBB ffiilleess ssaaffeellyy
Although Postfix uses file locking to avoid access conflicts while updating Postfix uses file locking to avoid access conflicts while updating Berkeley DB
Berkeley DB or other local database files, you still have a problem when the or other local database files. This used to be safe, but as Berkeley DB has
update fails because the disk is full or because something else happens. This evolved to use more aggressive caching, file locking may no longer be
is because commands such as postmap(1) or postalias(1) overwrite existing sufficient.
files. If the update fails in the middle then you have no usable database, and
Postfix will stop working. This is not an issue with the CDB database type Furthermore, file locking would not prevent problems when the update fails
because the disk is full or something else causes a database update to fail. In
particular, commands such as postmap(1) or postalias(1) overwrite existing
files. If the overwrite fails in the middle then you have no usable database,
and Postfix will stop working. This is not an issue with the CDB database type
available with Postfix 2.2 and later: CDB creates a new file, and renames the available with Postfix 2.2 and later: CDB creates a new file, and renames the
file upon successful completion. file upon successful completion.
With multi-file databases such as DBM, there is no simple solution. With With Berkeley DB and other "one file" databases, it is possible to add some
Berkeley DB and other "one file" databases, it is possible to add some extra extra robustness by using "mv" to REPLACE an existing database file instead of
robustness by using "mv" to REPLACE an existing database file instead of
overwriting it: overwriting it:
# ppoossttmmaapp aacccceessss..iinn &&&& mmvv aacccceessss..iinn..ddbb aacccceessss..ddbb # ppoossttmmaapp aacccceessss..iinn &&&& mmvv aacccceessss..iinn..ddbb aacccceessss..ddbb

View File

@ -1,9 +1,103 @@
PPoossttffiixx OOppeennLLDDAAPP LLMMDDBB HHoowwttoo PPoossttffiixx OOppeennLLDDAAPP LLMMDDBB HHoowwttoo
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Postfix LMDB support is forbidden due to problems with LMDB lock management. IInnttrroodduuccttiioonn
These problems hinder error recovery in multi-programmed systems, and prohibit
database sharing between privileged writer processes and unprivileged reader Postfix uses databases of various kinds to store and look up information.
processes. Postfix databases are specified as "type:name". OpenLDAP LMDB implements the
Postfix database type "lmdb". The name of a Postfix OpenLDAP LMDB database is
the name of the database file without the ".lmdb" suffix.
This document describes:
1. How to build Postfix with OpenLDAP LMDB support.
2. How to configure LMDB settings.
3. Missing pthread library trouble.
4. Unexpected failure modes that don't exist with other Postfix databases.
BBuuiillddiinngg PPoossttffiixx wwiitthh OOppeennLLDDAAPP LLMMDDBB ssuuppppoorrtt
Postfix normally does not enable OpenLDAP LMDB support. To build Postfix with
OpenLDAP LMDB support, use something like:
% make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
AUXLIBS="-L/usr/local/lib -llmdb"
% make
Solaris may need this:
% make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
AUXLIBS="-R/usr/local/lib -L/usr/local/lib -llmdb"
% make
The exact pathnames depend on how OpenLDAP LMDB was installed.
CCoonnffiigguurree LLMMDDBB sseettttiinnggss
Postfix provides one configuration parameter that controls OpenLDAP LMDB
database behavior.
* lmdb_map_size (default: 16777216). This setting specifies the initial
OpenLDAP LMDB database size limit in bytes. Each time a database becomes
full, its size limit is doubled. The maximum size is the largest signed
integer value of "long".
MMiissssiinngg pptthhrreeaadd lliibbrraarryy ttrroouubbllee
When building Postfix fails with:
undefined reference to `pthread_mutexattr_destroy'
undefined reference to `pthread_mutexattr_init'
undefined reference to `pthread_mutex_lock'
Add the "-lpthread" library to the "make makefiles" command.
% make makefiles .... AUXLIBS="... -lpthread"
Source code for OpenLDAP LMDB is available at http://www.openldap.org. More
information is available at http://highlandsun.com/hyc/mdb/.
UUnneexxppeecctteedd ffaaiilluurree mmooddeess ooff PPoossttffiixx LLMMDDBB ddaattaabbaasseess..
As documented below, conversion to LMDB introduces a number of failure modes
that don't exist with other Postfix databases. Some failure modes have been
eliminated in the course of time. The writeup below reflects the status as of
LMDB 0.9.9.
NNoonn--oobbvviioouuss rreeccoovveerryy wwiitthh ppoossttmmaapp((11)),, ppoossttaalliiaass((11)),, oorr ttllssmmggrr((88)) ffrroomm aa
ccoorrrruupptteedd ddaattaabbaassee..
Problem:
A corrupted LMDB database cann't be rebuilt simply by re-running postmap(1)
or postalias(1), or by waiting until a tlsmgr(8) daemon restarts. This
problem does not exist with other Postfix databases.
Background:
The Postfix LMDB database client does not truncate the database file.
Instead it attempts to create a transaction for a "drop" request plus
subsequent "store" requests. That is obviously not possible with a
corrupted database file.
Impact:
Postfix does not process mail until someone fixes the problem.
Recovery:
First delete the ".lmdb" file by hand. Then rebuild the file with the
postmap(1) or postalias(1) command if the file was created with those
commands, or restart postfix daemons if the file is maintained by tlsmgr
(8).
Prevention:
Arrange your file systems such that they never run out of free space.
Use ECC memory to detect and correct silent corruption of in-memory file
system data and metadata.
Use a file system such as ZFS to detect and correct silent corruption of
on-disk file system data and metadata. DO NOT use ZFS on systems without
ECC memory error correction.

View File

@ -14,13 +14,14 @@ specifies the release date of a stable release or snapshot release.
If you upgrade from Postfix 2.9 or earlier, read RELEASE_NOTES-2.10 If you upgrade from Postfix 2.9 or earlier, read RELEASE_NOTES-2.10
before proceeding. before proceeding.
Major changes with snapshot 20131001 Major changes with snapshot 20131031
==================================== ====================================
LMDB support is forbidden due to problems with LMDB lock management. LMDB support is enabled after changes to LMDB lock management. This
These problems hinder error recovery in multi-programmed systems, includes creating databases with postmap(1) and postalias(1);
and prohibit database sharing between privileged writer processes read/write access by postscreen(8), proxymap(8), verify(8), and
and unprivileged reader processes. tlsmgr(8); and database sharing between privileged writer processes
and unprivileged reader processes without world-writable files.
Major changes with snapshot 20130929 Major changes with snapshot 20130929
==================================== ====================================

View File

@ -1,5 +1,9 @@
Wish list: Wish list:
Per SASL account rate limits.
Add watchdog timer to postmap/postalias.
Things to do before the stable release: Things to do before the stable release:
Spell-check, double-word check, and HTML validator check. Spell-check, double-word check, and HTML validator check.

View File

@ -94,7 +94,8 @@ string is the client, sender or recipient, and the result is an
action such as "reject"). </p> action such as "reject"). </p>
<p> With some tables, however, Postfix needs to know only if the <p> With some tables, however, Postfix needs to know only if the
lookup key exists. The lookup result itself is not used. Examples lookup key exists. Any non-empty lookup result value may be used
here: the lookup result is not used. Examples
are the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> that determine what local recipients are the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> that determine what local recipients
Postfix accepts in mail from the network, the <a href="postconf.5.html#mydestination">mydestination</a> parameter Postfix accepts in mail from the network, the <a href="postconf.5.html#mydestination">mydestination</a> parameter
that specifies what domains Postfix delivers locally, or the that specifies what domains Postfix delivers locally, or the
@ -185,19 +186,22 @@ process can initialize with the new database. </p>
<h2><a name="safe_db">Updating Berkeley DB files safely</a></h2> <h2><a name="safe_db">Updating Berkeley DB files safely</a></h2>
<p> Although Postfix uses file locking to avoid access conflicts <p> Postfix uses file locking to avoid access conflicts while
while updating Berkeley DB or other local database files, you still updating Berkeley DB or other local database files. This used to
have a problem when the update fails because the disk is full or be safe, but as Berkeley DB has evolved to use more aggressive
because something else happens. This is because commands such as caching, file locking may no longer be sufficient. </p>
<a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> overwrite existing files. If the update
<p> Furthermore, file locking would not prevent problems when the
update fails because the disk is full or something else causes a
database update to fail. In particular, commands such as <a href="postmap.1.html">postmap(1)</a>
or <a href="postalias.1.html">postalias(1)</a> overwrite existing files. If the overwrite
fails in the middle then you have no usable database, and Postfix fails in the middle then you have no usable database, and Postfix
will stop working. This is not an issue with the CDB database type will stop working. This is not an issue with the CDB database type
available with Postfix 2.2 and later: <a href="CDB_README.html">CDB</a> available with Postfix 2.2 and later: <a href="CDB_README.html">CDB</a>
creates a new file, and renames the file upon successful completion. creates a new file, and renames the file upon successful completion.
</p> </p>
<p> With multi-file databases such as DBM, there is no simple <p> With Berkeley DB and other "one file" databases, it is
solution. With Berkeley DB and other "one file" databases, it is
possible to add some extra robustness by using "mv" to REPLACE an possible to add some extra robustness by using "mv" to REPLACE an
existing database file instead of overwriting it: </p> existing database file instead of overwriting it: </p>

View File

@ -17,35 +17,8 @@
<hr> <hr>
<hr>
<p> Postfix LMDB support is forbidden due to problems with LMDB lock
management. These problems hinder error recovery in multi-programmed
systems, and prohibit database sharing between privileged writer
processes and unprivileged reader processes. </p>
<!--
<h2>Introduction</h2> <h2>Introduction</h2>
<blockquote> <p> Warning: LMDB applications require write access
even when the application itself is read-only. This violates the
principle of least privilege, and causes all kinds of problems
when a non-root process needs to query a root-owned database such
as <a href="access.5.html">access(5)</a>, <a href="virtual.5.html">virtual(5)</a>, or <a href="transport.5.html">transport(5)</a>. </p>
<p> Support to create LMDB databases is no longer available for the
<a href="postmap.1.html">postmap(1)</a> and <a href="postalias.1.html">postalias(1)</a> commands. Instead, consider using <a href="CDB_README.html">cdb</a>:
to manage root-owned databases under the root-owned <a href="postconf.5.html#config_directory">config_directory</a>
(default: <tt>/etc/postfix</tt>) such as <a href="access.5.html">access(5)</a>, <a href="virtual.5.html">virtual(5)</a>, or
<a href="transport.5.html">transport(5)</a>. </p>
<p> Support to create LMDB databases is available only for unprivileged
Postfix daemon processes such as <a href="postscreen.8.html">postscreen(8)</a>, <a href="tlsmgr.8.html">tlsmgr(8)</a> and
<a href="verify.8.html">verify(8)</a> that manage postfix-owned databases under the postfix-owned
<a href="postconf.5.html#data_directory">data_directory</a> (default: <tt>/var/lib/postfix</tt>). </p> </blockquote>
<p> Postfix uses databases of various kinds to store and look up <p> Postfix uses databases of various kinds to store and look up
information. Postfix databases are specified as "type:name". information. Postfix databases are specified as "type:name".
OpenLDAP LMDB implements the Postfix database type "lmdb". OpenLDAP LMDB implements the Postfix database type "lmdb".
@ -95,21 +68,15 @@ build Postfix with OpenLDAP LMDB support, use something like: </p>
<h2><a name="configure">Configure LMDB settings</a></h2> <h2><a name="configure">Configure LMDB settings</a></h2>
<p> Postfix provides configuration parameters that control <p> Postfix provides one configuration parameter that controls
OpenLDAP LMDB database behavior. </p> OpenLDAP LMDB database behavior. </p>
<ul> <ul>
<li> <p> <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (default: 16777216). This setting specifies <li> <p> <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (default: 16777216). This setting specifies
the initial OpenLDAP LMDB database size limit in bytes. Each time the initial OpenLDAP LMDB database size limit in bytes. Each time
a database becomes full, its size limit is doubled. </p> a database becomes full, its size limit is doubled. The maximum
size is the largest signed integer value of "long". </p>
<li> <p> <a href="postconf.5.html#lmdb_max_readers">lmdb_max_readers</a> (default: $<a href="postconf.5.html#default_process_limit">default_process_limit</a>). This
specifies a hard limit on the number of read transactions that may
be open at the same time for the same OpenLDAP LMDB database. When
this number is too small, the Postfix LMDB client will log
MDB_READERS_FULL warnings, and will run with reduced performance.
</p>
</ul> </ul>
@ -144,7 +111,9 @@ databases. </a> </h2>
<p> As documented below, conversion to LMDB introduces a number of <p> As documented below, conversion to LMDB introduces a number of
failure modes that don't exist with other Postfix databases. Some failure modes that don't exist with other Postfix databases. Some
failure modes have been eliminated in the course of time. failure modes have been eliminated in the course of time.
The writeup below reflects the status as of of LMDB 0.9.8. </p> The writeup below reflects the status as of LMDB 0.9.9. </p>
<!--
<p> <strong>Unexpected "Permission denied" errors. </strong></p> <p> <strong>Unexpected "Permission denied" errors. </strong></p>
@ -171,6 +140,10 @@ the postfix-owned <a href="postconf.5.html#data_directory">data_directory</a> (d
</dl> </dl>
-->
<!--
<p> <strong>Unexpected "readers full" errors. </strong></p> <p> <strong>Unexpected "readers full" errors. </strong></p>
<dl> <dl>
@ -182,10 +155,10 @@ exist with other Postfix databases. </p> </dd>
<dt> Background: </dt> <dd> <p> The LMDB implementation enforces a <dt> Background: </dt> <dd> <p> The LMDB implementation enforces a
hard limit on the number of simultaneous read requests for the same hard limit on the number of simultaneous read requests for the same
database environment. This limit must be specified in advance with database environment. This limit must be specified in advance with
the <a href="postconf.5.html#lmdb_max_readers">lmdb_max_readers</a> configuration parameter. </p> </dd> the lmdb_max_readers configuration parameter. </p> </dd>
<dt> Mitigation: </dt> <dd> <p> Postfix logs a warning suggesting <dt> Mitigation: </dt> <dd> <p> Postfix logs a warning suggesting
that the <a href="postconf.5.html#lmdb_max_readers">lmdb_max_readers</a> parameter value be increased, and retries that the lmdb_max_readers parameter value be increased, and retries
the failed operation for a limited number of times while running the failed operation for a limited number of times while running
with reduced performance. </p> </dd> with reduced performance. </p> </dd>
@ -195,7 +168,9 @@ restart Postfix. </p> </dd>
</dl> </dl>
<!- - -->
<!--
<p> <strong>Unexpected <a href="postmap.1.html">postmap(1)</a>/<a href="postalias.1.html">postalias(1)</a> "database full" <p> <strong>Unexpected <a href="postmap.1.html">postmap(1)</a>/<a href="postalias.1.html">postalias(1)</a> "database full"
errors. </strong></p> errors. </strong></p>
@ -220,10 +195,16 @@ structures should share the same storage pool so that they can scale
with the database size, and so that all "out of storage" errors are with the database size, and so that all "out of storage" errors are
resolved by increasing the database size. </p> </dd> resolved by increasing the database size. </p> </dd>
<dt> Problem: </dt> <dd> <p> The "postmap <a href="LMDB_README.html">lmdb</a>:filename" command -->
<!--
<p> Problem: </dt> <dd> <p> The "postmap <a href="LMDB_README.html">lmdb</a>:filename" command
fails with an MDB_MAP_FULL error. This problem does not exist with fails with an MDB_MAP_FULL error. This problem does not exist with
other Postfix databases. </p> </dd> other Postfix databases. </p> </dd>
<dl>
<dt> Background: </dt> <dt> Background: </dt>
<dd> <dd>
@ -269,6 +250,12 @@ limit. </p>
sure that in <a href="postconf.5.html">main.cf</a>, <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> &gt; 3x the largest LMDB file sure that in <a href="postconf.5.html">main.cf</a>, <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> &gt; 3x the largest LMDB file
size. </p> </dd> </dl> size. </p> </dd> </dl>
</dl>
-->
<!--
<p> <strong>Unexpected Postfix daemon "database full" errors. <p> <strong>Unexpected Postfix daemon "database full" errors.
</strong></p> </strong></p>
@ -300,18 +287,17 @@ full" error will disappear, at least for a while. </p>
sure that <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> &gt; 3x the largest LMDB file size. </p> sure that <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> &gt; 3x the largest LMDB file size. </p>
</dd> </dl> </dd> </dl>
- -> -->
<p> <strong>Non-obvious recovery with <!- - <a href="postmap.1.html">postmap(1)</a>, <a href="postalias.1.html">postalias(1)</a>, - -> <p> <strong>Non-obvious recovery with <a href="postmap.1.html">postmap(1)</a>, <a href="postalias.1.html">postalias(1)</a>, or
<a href="postscreen.8.html">postscreen(8)</a>, <a href="tlsmgr.8.html">tlsmgr(8)</a>, or <a href="verify.8.html">verify(8)</a> from a corrupted database. <a href="tlsmgr.8.html">tlsmgr(8)</a> from a corrupted database. </strong></p>
</strong></p>
<dl> <dl>
<dt> Problem: </dt> <dd> <p> You cannot rebuild a corrupted LMDB <dt> Problem: </dt> <dd> <p> A corrupted LMDB database cann't be
database simply by <!- - re-running <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a>, or rebuilt simply by re-running <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a>, or by
by - -> waiting until a daemon restarts. This problem does not exist waiting until a <a href="tlsmgr.8.html">tlsmgr(8)</a> daemon restarts. This problem does not
with other Postfix databases. </p> </dd> exist with other Postfix databases. </p> </dd>
<dt> Background: </dt> <dd> <p> The Postfix LMDB database client <dt> Background: </dt> <dd> <p> The Postfix LMDB database client
does not truncate the database file. Instead it attempts to create does not truncate the database file. Instead it attempts to create
@ -323,10 +309,10 @@ That is obviously not possible with a corrupted database file. </p>
someone fixes the problem. </p> </dd> someone fixes the problem. </p> </dd>
<dt> Recovery: </dt> <dd> <p> First delete the ".lmdb" file by hand. <dt> Recovery: </dt> <dd> <p> First delete the ".lmdb" file by hand.
Then, <!- - rebuild the file with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> Then rebuild the file with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a>
command if the file was created with those commands, or - -> restart command if the file was created with those commands, or restart
postfix. <!- - daemons if the file is maintained by daemon processes. postfix daemons if the file is maintained by <a href="tlsmgr.8.html">tlsmgr(8)</a>.
- -> </p> </dd> </p> </dd>
<dt> Prevention: </dt> <dd> <dt> Prevention: </dt> <dd>
@ -337,10 +323,7 @@ space. </p>
in-memory file system data and metadata. </p> in-memory file system data and metadata. </p>
<p> Use a file system such as ZFS to detect and correct silent <p> Use a file system such as ZFS to detect and correct silent
corruption of on-disk file system data and metadata. </p> corruption of on-disk file system data and metadata. DO NOT
use ZFS on systems without ECC memory error correction. </p>
</dd> </dl> </dd> </dl>
-->

View File

@ -15,7 +15,7 @@ DISCARD(8) DISCARD(8)
<b>DESCRIPTION</b> <b>DESCRIPTION</b>
The Postfix <a href="discard.8.html"><b>discard</b>(8)</a> delivery agent processes delivery The Postfix <a href="discard.8.html"><b>discard</b>(8)</a> delivery agent processes delivery
requests from the queue manager. Each request specifies a requests from the queue manager. Each request specifies a
queue file, a sender address, a domain or host name that queue file, a sender address, a next-hop destination that
is treated as the reason for discarding the mail, and is treated as the reason for discarding the mail, and
recipient information. The reason may be prefixed with an recipient information. The reason may be prefixed with an
<a href="http://tools.ietf.org/html/rfc3463">RFC 3463</a>-compatible detail code. This program expects to <a href="http://tools.ietf.org/html/rfc3463">RFC 3463</a>-compatible detail code. This program expects to
@ -23,10 +23,10 @@ DISCARD(8) DISCARD(8)
The <a href="discard.8.html"><b>discard</b>(8)</a> delivery agent pretends to deliver all The <a href="discard.8.html"><b>discard</b>(8)</a> delivery agent pretends to deliver all
recipients in the delivery request, logs the "next-hop" recipients in the delivery request, logs the "next-hop"
domain or host information as the reason for discarding destination as the reason for discarding the mail, updates
the mail, updates the queue file and marks recipients as the queue file, and either marks recipients as finished or
finished or informs the queue manager that delivery should informs the queue manager that delivery should be tried
be tried again at a later time. again at a later time.
Delivery status reports are sent to the <a href="trace.8.html"><b>trace</b>(8)</a> daemon as Delivery status reports are sent to the <a href="trace.8.html"><b>trace</b>(8)</a> daemon as
appropriate. appropriate.

View File

@ -3630,8 +3630,9 @@ IPV6_V6ONLY support (<a href="http://tools.ietf.org/html/rfc3493">RFC 3493</a>).
Postfix will do DNS type AAAA record lookups. </p> Postfix will do DNS type AAAA record lookups. </p>
<p> When both IPv4 and IPv6 support are enabled, the Postfix SMTP <p> When both IPv4 and IPv6 support are enabled, the Postfix SMTP
client will attempt to connect via IPv6 before attempting to use client will choose the protocol as specified with the
IPv4. </p> <a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a> parameter. Postfix versions before 2.8
attempt to connect via IPv6 before attempting to use IPv4. </p>
<p> <p>
Examples: Examples:
@ -3794,18 +3795,6 @@ This feature is available in Postfix 2.11 and later.
</p> </p>
</DD>
<DT><b><a name="lmdb_max_readers">lmdb_max_readers</a>
(default: $<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b></DT><DD>
<p> The hard limit on the number of read transactions that may be
open at the same time for the same OpenLDAP LMDB database. When
this number is too small, the Postfix LMDB client will log
MDB_READERS_FULL errors, and will run with reduced performance.
</p>
</DD> </DD>
<DT><b><a name="lmtp_address_preference">lmtp_address_preference</a> <DT><b><a name="lmtp_address_preference">lmtp_address_preference</a>
@ -12746,6 +12735,16 @@ action. Note: a result of "OK" is not allowed for safety reasons.
Instead, use DUNNO in order to exclude specific hosts from blacklists. Instead, use DUNNO in order to exclude specific hosts from blacklists.
This feature is available in Postfix 2.7 and later. </dd> This feature is available in Postfix 2.7 and later. </dd>
<dt><b><a name="check_sasl_access">check_sasl_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
<dd> Use the remote SMTP client SASL user name as lookup key for
the specified <a href="access.5.html">access(5)</a> database. The lookup key has the form
"username@domainname" when the <a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a> parameter
value is non-empty. Unlike the <a href="postconf.5.html#check_client_access">check_client_access</a> feature,
<a href="postconf.5.html#check_sasl_access">check_sasl_access</a> does not perform matches of parent domains or IP
subnet ranges. This feature is available with Postfix version 2.11
and later. </dd>
<dt><b><a name="permit_inet_interfaces">permit_inet_interfaces</a></b></dt> <dt><b><a name="permit_inet_interfaces">permit_inet_interfaces</a></b></dt>
<dd>Permit the request when the client IP address matches <dd>Permit the request when the client IP address matches

View File

@ -2158,8 +2158,9 @@ When IPv6 support is enabled via the inet_protocols parameter,
Postfix will do DNS type AAAA record lookups. Postfix will do DNS type AAAA record lookups.
.PP .PP
When both IPv4 and IPv6 support are enabled, the Postfix SMTP When both IPv4 and IPv6 support are enabled, the Postfix SMTP
client will attempt to connect via IPv6 before attempting to use client will choose the protocol as specified with the
IPv4. smtp_address_preference parameter. Postfix versions before 2.8
attempt to connect via IPv6 before attempting to use IPv4.
.PP .PP
Examples: Examples:
.PP .PP
@ -2247,11 +2248,6 @@ The initial OpenLDAP LMDB database size limit in bytes. Each time
a database becomes full, its size limit is doubled. a database becomes full, its size limit is doubled.
.PP .PP
This feature is available in Postfix 2.11 and later. This feature is available in Postfix 2.11 and later.
.SH lmdb_max_readers (default: $default_process_limit)
The hard limit on the number of read transactions that may be
open at the same time for the same OpenLDAP LMDB database. When
this number is too small, the Postfix LMDB client will log
MDB_READERS_FULL errors, and will run with reduced performance.
.SH lmtp_address_preference (default: ipv6) .SH lmtp_address_preference (default: ipv6)
The LMTP-specific version of the smtp_address_preference The LMTP-specific version of the smtp_address_preference
configuration parameter. See there for details. configuration parameter. See there for details.
@ -5234,7 +5230,7 @@ For more fine-grained control, use check_ccert_access to select
an appropriate \fBaccess\fR(5) policy for each client. an appropriate \fBaccess\fR(5) policy for each client.
See RESTRICTION_CLASS_README. See RESTRICTION_CLASS_README.
.PP .PP
\fBNote:\fR Postfix 2.9.0&ndash;2.9.5 computed the public key \fBNote:\fR Postfix 2.9.0-2.9.5 computed the public key
fingerprint incorrectly. To use public-key fingerprints, upgrade fingerprint incorrectly. To use public-key fingerprints, upgrade
to Postfix 2.9.6 or later. to Postfix 2.9.6 or later.
.PP .PP
@ -7074,7 +7070,7 @@ The Postfix SMTP server and client log the peer (leaf) certificate
fingerprint and public key fingerprint when the TLS loglevel is 2 or fingerprint and public key fingerprint when the TLS loglevel is 2 or
higher. higher.
.PP .PP
\fBNote:\fR Postfix 2.9.0&ndash;2.9.5 computed the public key \fBNote:\fR Postfix 2.9.0-2.9.5 computed the public key
fingerprint incorrectly. To use public-key fingerprints, upgrade fingerprint incorrectly. To use public-key fingerprints, upgrade
to Postfix 2.9.6 or later. to Postfix 2.9.6 or later.
.PP .PP
@ -7118,7 +7114,7 @@ Each logging level also includes the information that is logged at
a lower logging level. a lower logging level.
.IP "" .IP ""
0 Log only a summary message on TLS handshake completion 0 Log only a summary message on TLS handshake completion
&mdash; no logging of remote SMTP server certificate trust-chain - no logging of remote SMTP server certificate trust-chain
verification errors if server certificate verification is not required. verification errors if server certificate verification is not required.
With Postfix 2.8 and earlier, disable logging of TLS activity. With Postfix 2.8 and earlier, disable logging of TLS activity.
.br .br
@ -8284,6 +8280,15 @@ action. Note: a result of "OK" is not allowed for safety reasons.
Instead, use DUNNO in order to exclude specific hosts from blacklists. Instead, use DUNNO in order to exclude specific hosts from blacklists.
This feature is available in Postfix 2.7 and later. This feature is available in Postfix 2.7 and later.
.br .br
.IP "\fBcheck_sasl_access \fItype:table\fR\fR"
Use the remote SMTP client SASL user name as lookup key for
the specified \fBaccess\fR(5) database. The lookup key has the form
"username@domainname" when the smtpd_sasl_local_domain parameter
value is non-empty. Unlike the check_client_access feature,
check_sasl_access does not perform matches of parent domains or IP
subnet ranges. This feature is available with Postfix version 2.11
and later.
.br
.IP "\fBpermit_inet_interfaces\fR" .IP "\fBpermit_inet_interfaces\fR"
Permit the request when the client IP address matches Permit the request when the client IP address matches
$inet_interfaces. $inet_interfaces.
@ -10442,7 +10447,7 @@ The Postfix SMTP server and client log the peer (leaf) certificate
fingerprint and public key fingerprint when the TLS loglevel is 2 or fingerprint and public key fingerprint when the TLS loglevel is 2 or
higher. higher.
.PP .PP
\fBNote:\fR Postfix 2.9.0&ndash;2.9.5 computed the public key \fBNote:\fR Postfix 2.9.0-2.9.5 computed the public key
fingerprint incorrectly. To use public-key fingerprints, upgrade fingerprint incorrectly. To use public-key fingerprints, upgrade
to Postfix 2.9.6 or later. to Postfix 2.9.6 or later.
.PP .PP
@ -10490,7 +10495,7 @@ Each logging level also includes the information that is logged at
a lower logging level. a lower logging level.
.IP "" .IP ""
0 Log only a summary message on TLS handshake completion 0 Log only a summary message on TLS handshake completion
&mdash; no logging of remote SMTP client certificate trust-chain verification - no logging of remote SMTP client certificate trust-chain verification
errors errors
if client certificate verification is not required. With Postfix 2.8 if client certificate verification is not required. With Postfix 2.8
and earlier, disable logging of TLS activity. and earlier, disable logging of TLS activity.

View File

@ -15,16 +15,16 @@ Postfix discard mail delivery agent
The Postfix \fBdiscard\fR(8) delivery agent processes The Postfix \fBdiscard\fR(8) delivery agent processes
delivery requests from delivery requests from
the queue manager. Each request specifies a queue file, a sender the queue manager. Each request specifies a queue file, a sender
address, a domain or host name that is treated as the reason for address, a next-hop destination that is treated as the reason for
discarding the mail, and recipient information. discarding the mail, and recipient information.
The reason may be prefixed with an RFC 3463-compatible detail code. The reason may be prefixed with an RFC 3463-compatible detail code.
This program expects to be run from the \fBmaster\fR(8) process This program expects to be run from the \fBmaster\fR(8) process
manager. manager.
The \fBdiscard\fR(8) delivery agent pretends to deliver all recipients The \fBdiscard\fR(8) delivery agent pretends to deliver all recipients
in the delivery request, logs the "next-hop" domain or host in the delivery request, logs the "next-hop" destination
information as the reason for discarding the mail, updates the as the reason for discarding the mail, updates the
queue file and marks recipients as finished or informs the queue file, and either marks recipients as finished or informs the
queue manager that delivery should be tried again at a later time. queue manager that delivery should be tried again at a later time.
Delivery status reports are sent to the \fBtrace\fR(8) Delivery status reports are sent to the \fBtrace\fR(8)

View File

@ -78,6 +78,8 @@ while(<>) {
$block =~ s/&ge;/>=/g; $block =~ s/&ge;/>=/g;
$block =~ s/&gt;/>/g; $block =~ s/&gt;/>/g;
$block =~ s/&amp;/\&/g; $block =~ s/&amp;/\&/g;
$block =~ s/&ndash;/-/g;
$block =~ s/&mdash;/-/g;
$block =~ s/\s+\n/\n/g; $block =~ s/\s+\n/\n/g;
$block =~ s/^\n//g; $block =~ s/^\n//g;
$block =~ s/([a-z][_a-zA-Z0-9-]*)(\([0-9]\))/\\fB\1\\fR\2/g; $block =~ s/([a-z][_a-zA-Z0-9-]*)(\([0-9]\))/\\fB\1\\fR\2/g;

View File

@ -209,7 +209,6 @@ while (<>) {
s;\bipc_ttl\b;<a href="postconf.5.html#ipc_ttl">$&</a>;g; s;\bipc_ttl\b;<a href="postconf.5.html#ipc_ttl">$&</a>;g;
s;\bline_length_limit\b;<a href="postconf.5.html#line_length_limit">$&</a>;g; s;\bline_length_limit\b;<a href="postconf.5.html#line_length_limit">$&</a>;g;
s;\blmdb_map_size\b;<a href="postconf.5.html#lmdb_map_size">$&</a>;g; s;\blmdb_map_size\b;<a href="postconf.5.html#lmdb_map_size">$&</a>;g;
s;\blmdb_max_readers\b;<a href="postconf.5.html#lmdb_max_readers">$&</a>;g;
s;\blmtp_address_preference\b;<a href="postconf.5.html#lmtp_address_preference">$&</a>;g; s;\blmtp_address_preference\b;<a href="postconf.5.html#lmtp_address_preference">$&</a>;g;
s;\blmtp_body_checks\b;<a href="postconf.5.html#lmtp_body_checks">$&</a>;g; s;\blmtp_body_checks\b;<a href="postconf.5.html#lmtp_body_checks">$&</a>;g;
s;\blmtp_cname_overrides_servername\b;<a href="postconf.5.html#lmtp_cname_overrides_servername">$&</a>;g; s;\blmtp_cname_overrides_servername\b;<a href="postconf.5.html#lmtp_cname_overrides_servername">$&</a>;g;
@ -864,6 +863,7 @@ while (<>) {
s;\bcheck_reverse_client_hostname_access\b;<a href="postconf.5.html#check_reverse_client_hostname_access">$&</a>;g; s;\bcheck_reverse_client_hostname_access\b;<a href="postconf.5.html#check_reverse_client_hostname_access">$&</a>;g;
s;\bcheck_reverse_client_hostname_mx_access\b;<a href="postconf.5.html#check_reverse_client_hostname_mx_access">$&</a>;g; s;\bcheck_reverse_client_hostname_mx_access\b;<a href="postconf.5.html#check_reverse_client_hostname_mx_access">$&</a>;g;
s;\bcheck_reverse_client_hostname_ns_access\b;<a href="postconf.5.html#check_reverse_client_hostname_ns_access">$&</a>;g; s;\bcheck_reverse_client_hostname_ns_access\b;<a href="postconf.5.html#check_reverse_client_hostname_ns_access">$&</a>;g;
s;\bcheck_sasl_access\b;<a href="postconf.5.html#check_sasl_access">$&</a>;g;
s;\bpermit_inet_interfaces\b;<a href="postconf.5.html#permit_inet_interfaces">$&</a>;g; s;\bpermit_inet_interfaces\b;<a href="postconf.5.html#permit_inet_interfaces">$&</a>;g;
s;\bpermit_mynetworks\b;<a href="postconf.5.html#permit_mynetworks">$&</a>;g; s;\bpermit_mynetworks\b;<a href="postconf.5.html#permit_mynetworks">$&</a>;g;
s;\bper[-</bB>]*\n* *[<bB>]*mit_sasl_authenticated\b;<a href="postconf.5.html#permit_sasl_authenticated">$&</a>;g; s;\bper[-</bB>]*\n* *[<bB>]*mit_sasl_authenticated\b;<a href="postconf.5.html#permit_sasl_authenticated">$&</a>;g;

View File

@ -94,7 +94,8 @@ string is the client, sender or recipient, and the result is an
action such as "reject"). </p> action such as "reject"). </p>
<p> With some tables, however, Postfix needs to know only if the <p> With some tables, however, Postfix needs to know only if the
lookup key exists. The lookup result itself is not used. Examples lookup key exists. Any non-empty lookup result value may be used
here: the lookup result is not used. Examples
are the local_recipient_maps that determine what local recipients are the local_recipient_maps that determine what local recipients
Postfix accepts in mail from the network, the mydestination parameter Postfix accepts in mail from the network, the mydestination parameter
that specifies what domains Postfix delivers locally, or the that specifies what domains Postfix delivers locally, or the
@ -185,19 +186,22 @@ process can initialize with the new database. </p>
<h2><a name="safe_db">Updating Berkeley DB files safely</a></h2> <h2><a name="safe_db">Updating Berkeley DB files safely</a></h2>
<p> Although Postfix uses file locking to avoid access conflicts <p> Postfix uses file locking to avoid access conflicts while
while updating Berkeley DB or other local database files, you still updating Berkeley DB or other local database files. This used to
have a problem when the update fails because the disk is full or be safe, but as Berkeley DB has evolved to use more aggressive
because something else happens. This is because commands such as caching, file locking may no longer be sufficient. </p>
postmap(1) or postalias(1) overwrite existing files. If the update
<p> Furthermore, file locking would not prevent problems when the
update fails because the disk is full or something else causes a
database update to fail. In particular, commands such as postmap(1)
or postalias(1) overwrite existing files. If the overwrite
fails in the middle then you have no usable database, and Postfix fails in the middle then you have no usable database, and Postfix
will stop working. This is not an issue with the CDB database type will stop working. This is not an issue with the CDB database type
available with Postfix 2.2 and later: <a href="CDB_README.html">CDB</a> available with Postfix 2.2 and later: <a href="CDB_README.html">CDB</a>
creates a new file, and renames the file upon successful completion. creates a new file, and renames the file upon successful completion.
</p> </p>
<p> With multi-file databases such as DBM, there is no simple <p> With Berkeley DB and other "one file" databases, it is
solution. With Berkeley DB and other "one file" databases, it is
possible to add some extra robustness by using "mv" to REPLACE an possible to add some extra robustness by using "mv" to REPLACE an
existing database file instead of overwriting it: </p> existing database file instead of overwriting it: </p>

View File

@ -17,35 +17,8 @@
<hr> <hr>
<hr>
<p> Postfix LMDB support is forbidden due to problems with LMDB lock
management. These problems hinder error recovery in multi-programmed
systems, and prohibit database sharing between privileged writer
processes and unprivileged reader processes. </p>
<!--
<h2>Introduction</h2> <h2>Introduction</h2>
<blockquote> <p> Warning: LMDB applications require write access
even when the application itself is read-only. This violates the
principle of least privilege, and causes all kinds of problems
when a non-root process needs to query a root-owned database such
as access(5), virtual(5), or transport(5). </p>
<p> Support to create LMDB databases is no longer available for the
postmap(1) and postalias(1) commands. Instead, consider using cdb:
to manage root-owned databases under the root-owned config_directory
(default: <tt>/etc/postfix</tt>) such as access(5), virtual(5), or
transport(5). </p>
<p> Support to create LMDB databases is available only for unprivileged
Postfix daemon processes such as postscreen(8), tlsmgr(8) and
verify(8) that manage postfix-owned databases under the postfix-owned
data_directory (default: <tt>/var/lib/postfix</tt>). </p> </blockquote>
<p> Postfix uses databases of various kinds to store and look up <p> Postfix uses databases of various kinds to store and look up
information. Postfix databases are specified as "type:name". information. Postfix databases are specified as "type:name".
OpenLDAP LMDB implements the Postfix database type "lmdb". OpenLDAP LMDB implements the Postfix database type "lmdb".
@ -95,21 +68,15 @@ build Postfix with OpenLDAP LMDB support, use something like: </p>
<h2><a name="configure">Configure LMDB settings</a></h2> <h2><a name="configure">Configure LMDB settings</a></h2>
<p> Postfix provides configuration parameters that control <p> Postfix provides one configuration parameter that controls
OpenLDAP LMDB database behavior. </p> OpenLDAP LMDB database behavior. </p>
<ul> <ul>
<li> <p> lmdb_map_size (default: 16777216). This setting specifies <li> <p> lmdb_map_size (default: 16777216). This setting specifies
the initial OpenLDAP LMDB database size limit in bytes. Each time the initial OpenLDAP LMDB database size limit in bytes. Each time
a database becomes full, its size limit is doubled. </p> a database becomes full, its size limit is doubled. The maximum
size is the largest signed integer value of "long". </p>
<li> <p> lmdb_max_readers (default: $default_process_limit). This
specifies a hard limit on the number of read transactions that may
be open at the same time for the same OpenLDAP LMDB database. When
this number is too small, the Postfix LMDB client will log
MDB_READERS_FULL warnings, and will run with reduced performance.
</p>
</ul> </ul>
@ -144,7 +111,9 @@ databases. </a> </h2>
<p> As documented below, conversion to LMDB introduces a number of <p> As documented below, conversion to LMDB introduces a number of
failure modes that don't exist with other Postfix databases. Some failure modes that don't exist with other Postfix databases. Some
failure modes have been eliminated in the course of time. failure modes have been eliminated in the course of time.
The writeup below reflects the status as of of LMDB 0.9.8. </p> The writeup below reflects the status as of LMDB 0.9.9. </p>
<!--
<p> <strong>Unexpected "Permission denied" errors. </strong></p> <p> <strong>Unexpected "Permission denied" errors. </strong></p>
@ -171,6 +140,10 @@ the postfix-owned data_directory (default: <tt>/var/lib/postfix</tt>).
</dl> </dl>
-->
<!--
<p> <strong>Unexpected "readers full" errors. </strong></p> <p> <strong>Unexpected "readers full" errors. </strong></p>
<dl> <dl>
@ -195,7 +168,9 @@ restart Postfix. </p> </dd>
</dl> </dl>
<!- - -->
<!--
<p> <strong>Unexpected postmap(1)/postalias(1) "database full" <p> <strong>Unexpected postmap(1)/postalias(1) "database full"
errors. </strong></p> errors. </strong></p>
@ -220,10 +195,16 @@ structures should share the same storage pool so that they can scale
with the database size, and so that all "out of storage" errors are with the database size, and so that all "out of storage" errors are
resolved by increasing the database size. </p> </dd> resolved by increasing the database size. </p> </dd>
<dt> Problem: </dt> <dd> <p> The "postmap lmdb:filename" command -->
<!--
<p> Problem: </dt> <dd> <p> The "postmap lmdb:filename" command
fails with an MDB_MAP_FULL error. This problem does not exist with fails with an MDB_MAP_FULL error. This problem does not exist with
other Postfix databases. </p> </dd> other Postfix databases. </p> </dd>
<dl>
<dt> Background: </dt> <dt> Background: </dt>
<dd> <dd>
@ -269,6 +250,12 @@ limit. </p>
sure that in main.cf, lmdb_map_size &gt; 3x the largest LMDB file sure that in main.cf, lmdb_map_size &gt; 3x the largest LMDB file
size. </p> </dd> </dl> size. </p> </dd> </dl>
</dl>
-->
<!--
<p> <strong>Unexpected Postfix daemon "database full" errors. <p> <strong>Unexpected Postfix daemon "database full" errors.
</strong></p> </strong></p>
@ -300,18 +287,17 @@ full" error will disappear, at least for a while. </p>
sure that lmdb_map_size &gt; 3x the largest LMDB file size. </p> sure that lmdb_map_size &gt; 3x the largest LMDB file size. </p>
</dd> </dl> </dd> </dl>
- -> -->
<p> <strong>Non-obvious recovery with <!- - postmap(1), postalias(1), - -> <p> <strong>Non-obvious recovery with postmap(1), postalias(1), or
postscreen(8), tlsmgr(8), or verify(8) from a corrupted database. tlsmgr(8) from a corrupted database. </strong></p>
</strong></p>
<dl> <dl>
<dt> Problem: </dt> <dd> <p> You cannot rebuild a corrupted LMDB <dt> Problem: </dt> <dd> <p> A corrupted LMDB database cann't be
database simply by <!- - re-running postmap(1) or postalias(1), or rebuilt simply by re-running postmap(1) or postalias(1), or by
by - -> waiting until a daemon restarts. This problem does not exist waiting until a tlsmgr(8) daemon restarts. This problem does not
with other Postfix databases. </p> </dd> exist with other Postfix databases. </p> </dd>
<dt> Background: </dt> <dd> <p> The Postfix LMDB database client <dt> Background: </dt> <dd> <p> The Postfix LMDB database client
does not truncate the database file. Instead it attempts to create does not truncate the database file. Instead it attempts to create
@ -323,10 +309,10 @@ That is obviously not possible with a corrupted database file. </p>
someone fixes the problem. </p> </dd> someone fixes the problem. </p> </dd>
<dt> Recovery: </dt> <dd> <p> First delete the ".lmdb" file by hand. <dt> Recovery: </dt> <dd> <p> First delete the ".lmdb" file by hand.
Then, <!- - rebuild the file with the postmap(1) or postalias(1) Then rebuild the file with the postmap(1) or postalias(1)
command if the file was created with those commands, or - -> restart command if the file was created with those commands, or restart
postfix. <!- - daemons if the file is maintained by daemon processes. postfix daemons if the file is maintained by tlsmgr(8).
- -> </p> </dd> </p> </dd>
<dt> Prevention: </dt> <dd> <dt> Prevention: </dt> <dd>
@ -337,8 +323,7 @@ space. </p>
in-memory file system data and metadata. </p> in-memory file system data and metadata. </p>
<p> Use a file system such as ZFS to detect and correct silent <p> Use a file system such as ZFS to detect and correct silent
corruption of on-disk file system data and metadata. </p> corruption of on-disk file system data and metadata. DO NOT
use ZFS on systems without ECC memory error correction. </p>
</dd> </dl> </dd> </dl>
-->

View File

@ -1975,8 +1975,9 @@ IPV6_V6ONLY support (RFC 3493). </p>
Postfix will do DNS type AAAA record lookups. </p> Postfix will do DNS type AAAA record lookups. </p>
<p> When both IPv4 and IPv6 support are enabled, the Postfix SMTP <p> When both IPv4 and IPv6 support are enabled, the Postfix SMTP
client will attempt to connect via IPv6 before attempting to use client will choose the protocol as specified with the
IPv4. </p> smtp_address_preference parameter. Postfix versions before 2.8
attempt to connect via IPv6 before attempting to use IPv4. </p>
<p> <p>
Examples: Examples:
@ -2848,15 +2849,6 @@ a database becomes full, its size limit is doubled.
This feature is available in Postfix 2.11 and later. This feature is available in Postfix 2.11 and later.
</p> </p>
%PARAM lmdb_max_readers $default_process_limit
<p> The hard limit on the number of read transactions that may be
open at the same time for the same OpenLDAP LMDB database. When
this number is too small, the Postfix LMDB client will log
MDB_READERS_FULL errors, and will run with reduced performance.
</p>
%PARAM message_size_limit 10240000 %PARAM message_size_limit 10240000
<p> <p>
@ -4982,6 +4974,16 @@ action. Note: a result of "OK" is not allowed for safety reasons.
Instead, use DUNNO in order to exclude specific hosts from blacklists. Instead, use DUNNO in order to exclude specific hosts from blacklists.
This feature is available in Postfix 2.7 and later. </dd> This feature is available in Postfix 2.7 and later. </dd>
<dt><b><a name="check_sasl_access">check_sasl_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
<dd> Use the remote SMTP client SASL user name as lookup key for
the specified access(5) database. The lookup key has the form
"username@domainname" when the smtpd_sasl_local_domain parameter
value is non-empty. Unlike the check_client_access feature,
check_sasl_access does not perform matches of parent domains or IP
subnet ranges. This feature is available with Postfix version 2.11
and later. </dd>
<dt><b><a name="permit_inet_interfaces">permit_inet_interfaces</a></b></dt> <dt><b><a name="permit_inet_interfaces">permit_inet_interfaces</a></b></dt>
<dd>Permit the request when the client IP address matches <dd>Permit the request when the client IP address matches

View File

@ -9,16 +9,16 @@
/* The Postfix \fBdiscard\fR(8) delivery agent processes /* The Postfix \fBdiscard\fR(8) delivery agent processes
/* delivery requests from /* delivery requests from
/* the queue manager. Each request specifies a queue file, a sender /* the queue manager. Each request specifies a queue file, a sender
/* address, a domain or host name that is treated as the reason for /* address, a next-hop destination that is treated as the reason for
/* discarding the mail, and recipient information. /* discarding the mail, and recipient information.
/* The reason may be prefixed with an RFC 3463-compatible detail code. /* The reason may be prefixed with an RFC 3463-compatible detail code.
/* This program expects to be run from the \fBmaster\fR(8) process /* This program expects to be run from the \fBmaster\fR(8) process
/* manager. /* manager.
/* /*
/* The \fBdiscard\fR(8) delivery agent pretends to deliver all recipients /* The \fBdiscard\fR(8) delivery agent pretends to deliver all recipients
/* in the delivery request, logs the "next-hop" domain or host /* in the delivery request, logs the "next-hop" destination
/* information as the reason for discarding the mail, updates the /* as the reason for discarding the mail, updates the
/* queue file and marks recipients as finished or informs the /* queue file, and either marks recipients as finished or informs the
/* queue manager that delivery should be tried again at a later time. /* queue manager that delivery should be tried again at a later time.
/* /*
/* Delivery status reports are sent to the \fBtrace\fR(8) /* Delivery status reports are sent to the \fBtrace\fR(8)

View File

@ -98,7 +98,6 @@
/* int var_db_read_buf; /* int var_db_read_buf;
/* long var_lmdb_map_size; /* long var_lmdb_map_size;
/* int var_proc_limit; /* int var_proc_limit;
/* int var_lmdb_max_readers;
/* int var_mime_maxdepth; /* int var_mime_maxdepth;
/* int var_mime_bound_len; /* int var_mime_bound_len;
/* int var_header_limit; /* int var_header_limit;
@ -291,7 +290,6 @@ char *var_proxywrite_service;
int var_db_create_buf; int var_db_create_buf;
int var_db_read_buf; int var_db_read_buf;
long var_lmdb_map_size; long var_lmdb_map_size;
int var_lmdb_max_readers;
int var_proc_limit; int var_proc_limit;
int var_mime_maxdepth; int var_mime_maxdepth;
int var_mime_bound_len; int var_mime_bound_len;
@ -614,13 +612,9 @@ void mail_params_init()
VAR_INET_WINDOW, DEF_INET_WINDOW, &var_inet_windowsize, 0, 0, VAR_INET_WINDOW, DEF_INET_WINDOW, &var_inet_windowsize, 0, 0,
0, 0,
}; };
static const CONFIG_NINT_TABLE nint_defaults[] = {
VAR_LMDB_MAX_READERS, DEF_LMDB_MAX_READERS, &var_lmdb_max_readers, 1, 0,
0,
};
static const CONFIG_LONG_TABLE long_defaults[] = { static const CONFIG_LONG_TABLE long_defaults[] = {
VAR_MESSAGE_LIMIT, DEF_MESSAGE_LIMIT, &var_message_limit, 0, 0, VAR_MESSAGE_LIMIT, DEF_MESSAGE_LIMIT, &var_message_limit, 0, 0,
VAR_LMDB_MAP_SIZE, DEF_LMDB_MAP_SIZE, &var_lmdb_map_size, 8192, 0, VAR_LMDB_MAP_SIZE, DEF_LMDB_MAP_SIZE, &var_lmdb_map_size, 1, 0,
0, 0,
}; };
static const CONFIG_TIME_TABLE time_defaults[] = { static const CONFIG_TIME_TABLE time_defaults[] = {
@ -718,7 +712,6 @@ void mail_params_init()
} }
#endif #endif
get_mail_conf_int_table(other_int_defaults); get_mail_conf_int_table(other_int_defaults);
get_mail_conf_nint_table(nint_defaults);
get_mail_conf_long_table(long_defaults); get_mail_conf_long_table(long_defaults);
get_mail_conf_bool_table(bool_defaults); get_mail_conf_bool_table(bool_defaults);
get_mail_conf_time_table(time_defaults); get_mail_conf_time_table(time_defaults);
@ -731,7 +724,6 @@ void mail_params_init()
#endif #endif
#ifdef HAS_LMDB #ifdef HAS_LMDB
dict_lmdb_map_size = var_lmdb_map_size; dict_lmdb_map_size = var_lmdb_map_size;
dict_lmdb_max_readers = var_lmdb_max_readers;
#endif #endif
inet_windowsize = var_inet_windowsize; inet_windowsize = var_inet_windowsize;

View File

@ -2184,6 +2184,7 @@ extern int var_map_defer_code;
#define CHECK_CLIENT_ACL "check_client_access" #define CHECK_CLIENT_ACL "check_client_access"
#define CHECK_REVERSE_CLIENT_ACL "check_reverse_client_hostname_access" #define CHECK_REVERSE_CLIENT_ACL "check_reverse_client_hostname_access"
#define CHECK_CCERT_ACL "check_ccert_access" #define CHECK_CCERT_ACL "check_ccert_access"
#define CHECK_SASL_ACL "check_sasl_access"
#define CHECK_HELO_ACL "check_helo_access" #define CHECK_HELO_ACL "check_helo_access"
#define CHECK_SENDER_ACL "check_sender_access" #define CHECK_SENDER_ACL "check_sender_access"
#define CHECK_RECIP_ACL "check_recipient_access" #define CHECK_RECIP_ACL "check_recipient_access"
@ -2775,10 +2776,6 @@ extern int var_db_read_buf;
#define DEF_LMDB_MAP_SIZE (16 * 1024 *1024) #define DEF_LMDB_MAP_SIZE (16 * 1024 *1024)
extern long var_lmdb_map_size; extern long var_lmdb_map_size;
#define VAR_LMDB_MAX_READERS "lmdb_max_readers"
#define DEF_LMDB_MAX_READERS "$" VAR_PROC_LIMIT
extern int var_lmdb_max_readers;
/* /*
* Named queue file attributes. * Named queue file attributes.
*/ */

View File

@ -20,7 +20,7 @@
* Patches change both the patchlevel and the release date. Snapshots have no * Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only. * patchlevel; they change the release date only.
*/ */
#define MAIL_RELEASE_DATE "20131001" #define MAIL_RELEASE_DATE "20131031"
#define MAIL_VERSION_NUMBER "2.11" #define MAIL_VERSION_NUMBER "2.11"
#ifdef SNAPSHOT #ifdef SNAPSHOT

View File

@ -103,7 +103,6 @@ static const MKMAP_OPEN_INFO mkmap_types[] = {
DICT_TYPE_BTREE, mkmap_btree_open, DICT_TYPE_BTREE, mkmap_btree_open,
#endif #endif
#ifdef HAS_LMDB #ifdef HAS_LMDB
#error "LMDB support is forbidden"
DICT_TYPE_LMDB, mkmap_lmdb_open, DICT_TYPE_LMDB, mkmap_lmdb_open,
#endif #endif
DICT_TYPE_FAIL, mkmap_fail_open, DICT_TYPE_FAIL, mkmap_fail_open,
@ -189,7 +188,11 @@ MKMAP *mkmap_open(const char *type, const char *path,
/* /*
* Truncate the database upon open, and update it. Read-write mode is * Truncate the database upon open, and update it. Read-write mode is
* needed because the underlying routines read as well as write. * needed because the underlying routines read as well as write. We
* explicitly clobber lock_fd to trigger a fatal error when a map wants
* to unlock the database after individual transactions: that would
* result in race condition problems. We clobbber stat_fd as well,
* because that, too, is used only for individual-transaction clients.
*/ */
mkmap->dict = mkmap->open(path, open_flags, dict_flags); mkmap->dict = mkmap->open(path, open_flags, dict_flags);
mkmap->dict->lock_fd = -1; /* XXX just in case */ mkmap->dict->lock_fd = -1; /* XXX just in case */

View File

@ -120,6 +120,9 @@ static void psc_early_event(int event, char *context)
* XXX We can avoid "forgetting" to do this by keeping a pointer to the * XXX We can avoid "forgetting" to do this by keeping a pointer to the
* DNSBL lookup buffer in the PSC_STATE structure. This also allows us to * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to
* shave off a hash table lookup when retrieving the DNSBL result. * shave off a hash table lookup when retrieving the DNSBL result.
*
* A direct pointer increases the odds of dangling pointers. Hash-table
* lookup is safer, and that is why it's done that way.
*/ */
switch (event) { switch (event) {

View File

@ -2823,6 +2823,26 @@ static int check_ccert_access(SMTPD_STATE *state, const char *table,
return (result); return (result);
} }
/* check_sasl_access - access by SASL user name */
#ifdef USE_SASL_AUTH
static int check_sasl_access(SMTPD_STATE *state, const char *table,
const char *def_acl)
{
int result;
int unused_found;
char *sane_username = printable(mystrdup(state->sasl_username), '_');
result = check_access(state, table, state->sasl_username,
DICT_FLAG_NONE, &unused_found, sane_username,
SMTPD_NAME_SASL_USER, def_acl);
myfree(sane_username);
return (result);
}
#endif
/* check_mail_access - OK/FAIL based on mail address lookup */ /* check_mail_access - OK/FAIL based on mail address lookup */
static int check_mail_access(SMTPD_STATE *state, const char *table, static int check_mail_access(SMTPD_STATE *state, const char *table,
@ -3882,6 +3902,14 @@ static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
} }
} else if (is_map_command(state, name, CHECK_CCERT_ACL, &cpp)) { } else if (is_map_command(state, name, CHECK_CCERT_ACL, &cpp)) {
status = check_ccert_access(state, *cpp, def_acl); status = check_ccert_access(state, *cpp, def_acl);
#ifdef USE_SASL_AUTH
} else if (is_map_command(state, name, CHECK_SASL_ACL, &cpp)) {
if (var_smtpd_sasl_enable) {
if (state->sasl_username && state->sasl_username[0])
status = check_sasl_access(state, *cpp, def_acl);
} else
#endif
msg_warn("restriction `%s' ignored: no SASL support", name);
} else if (is_map_command(state, name, CHECK_CLIENT_NS_ACL, &cpp)) { } else if (is_map_command(state, name, CHECK_CLIENT_NS_ACL, &cpp)) {
if (strcasecmp(state->name, "unknown") != 0) { if (strcasecmp(state->name, "unknown") != 0) {
status = check_server_access(state, *cpp, state->name, status = check_server_access(state, *cpp, state->name,

View File

@ -15,6 +15,7 @@
#define SMTPD_NAME_CLIENT "Client host" #define SMTPD_NAME_CLIENT "Client host"
#define SMTPD_NAME_REV_CLIENT "Unverified Client host" #define SMTPD_NAME_REV_CLIENT "Unverified Client host"
#define SMTPD_NAME_CCERT "Client certificate" #define SMTPD_NAME_CCERT "Client certificate"
#define SMTPD_NAME_SASL_USER "SASL login name"
#define SMTPD_NAME_HELO "Helo command" #define SMTPD_NAME_HELO "Helo command"
#define SMTPD_NAME_SENDER "Sender address" #define SMTPD_NAME_SENDER "Sender address"
#define SMTPD_NAME_RECIPIENT "Recipient address" #define SMTPD_NAME_RECIPIENT "Recipient address"

View File

@ -36,7 +36,7 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \
ip_match.c nbbio.c base32_code.c dict_test.c \ ip_match.c nbbio.c base32_code.c dict_test.c \
dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \ dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \
dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \ dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \
poll_fd.c timecmp.c poll_fd.c timecmp.c slmdb.c
OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
@ -74,7 +74,7 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
ip_match.o nbbio.o base32_code.o dict_test.o \ ip_match.o nbbio.o base32_code.o dict_test.o \
dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \ dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \
dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \ dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \
poll_fd.o timecmp.o poll_fd.o timecmp.o slmdb.o
HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \ chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \
dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \ dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \
@ -95,7 +95,8 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
username.h valid_hostname.h vbuf.h vbuf_print.h vstream.h vstring.h \ username.h valid_hostname.h vbuf.h vbuf_print.h vstream.h vstring.h \
vstring_vstream.h watchdog.h format_tv.h load_file.h killme_after.h \ vstring_vstream.h watchdog.h format_tv.h load_file.h killme_after.h \
edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \ edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \
dict_fail.h warn_stat.h dict_sockmap.h line_number.h timecmp.h dict_fail.h warn_stat.h dict_sockmap.h line_number.h timecmp.h \
slmdb.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c stream_test.c dup2_pass_on_exec.c
DEFS = -I. -D$(SYSTYPE) DEFS = -I. -D$(SYSTYPE)
@ -1002,6 +1003,7 @@ dict_lmdb.o: iostuff.h
dict_lmdb.o: msg.h dict_lmdb.o: msg.h
dict_lmdb.o: myflock.h dict_lmdb.o: myflock.h
dict_lmdb.o: mymalloc.h dict_lmdb.o: mymalloc.h
dict_lmdb.o: slmdb.h
dict_lmdb.o: stringops.h dict_lmdb.o: stringops.h
dict_lmdb.o: sys_defs.h dict_lmdb.o: sys_defs.h
dict_lmdb.o: vbuf.h dict_lmdb.o: vbuf.h
@ -1764,6 +1766,8 @@ skipblanks.o: stringops.h
skipblanks.o: sys_defs.h skipblanks.o: sys_defs.h
skipblanks.o: vbuf.h skipblanks.o: vbuf.h
skipblanks.o: vstring.h skipblanks.o: vstring.h
slmdb.o: slmdb.c
slmdb.o: slmdb.h
sock_addr.o: msg.h sock_addr.o: msg.h
sock_addr.o: sock_addr.c sock_addr.o: sock_addr.c
sock_addr.o: sock_addr.h sock_addr.o: sock_addr.h

View File

@ -7,7 +7,6 @@
/* #include <dict_lmdb.h> /* #include <dict_lmdb.h>
/* /*
/* size_t dict_lmdb_map_size; /* size_t dict_lmdb_map_size;
/* unsigned int dict_lmdb_max_readers;
/* /*
/* DICT *dict_lmdb_open(path, open_flags, dict_flags) /* DICT *dict_lmdb_open(path, open_flags, dict_flags)
/* const char *name; /* const char *name;
@ -22,18 +21,13 @@
/* The dict_lmdb_map_size variable specifies the initial /* The dict_lmdb_map_size variable specifies the initial
/* database memory map size. When a map becomes full its size /* database memory map size. When a map becomes full its size
/* is doubled, and other programs pick up the size change. /* is doubled, and other programs pick up the size change.
/*
/* The dict_lmdb_max_readers variable specifies the hard (ugh)
/* limit on the number of read transactions that may be open
/* at the same time. This should be propertional to the number
/* of processes that read the table.
/* DIAGNOSTICS /* DIAGNOSTICS
/* Fatal errors: cannot open file, file write error, out of /* Fatal errors: cannot open file, file write error, out of
/* memory. /* memory.
/* BUGS /* BUGS
/* The on-the-fly map resize operations require no concurrent /* The on-the-fly map resize operations require no concurrent
/* activity in the same database by other threads in the same /* activity in the same database by other threads in the same
/* process. /* memory address space.
/* SEE ALSO /* SEE ALSO
/* dict(3) generic dictionary manager /* dict(3) generic dictionary manager
/* LICENSE /* LICENSE
@ -61,21 +55,6 @@
#include <unistd.h> #include <unistd.h>
#include <limits.h> #include <limits.h>
#ifdef PATH_LMDB_H
#include PATH_LMDB_H
#else
#include <lmdb.h>
#endif
/*
* As of LMDB 0.9.8 the database size limit can be updated on-the-fly. The
* only limit that remains is imposed by the hardware address space. Earlier
* LMDB versions are not suitable for use with Postfix.
*/
#if MDB_VERSION_FULL < MDB_VERINT(0, 9, 8)
#error "Build with LMDB version 0.9.8 or later"
#endif
/* Utility library. */ /* Utility library. */
#include <msg.h> #include <msg.h>
@ -85,6 +64,7 @@
#include <vstring.h> #include <vstring.h>
#include <myflock.h> #include <myflock.h>
#include <stringops.h> #include <stringops.h>
#include <slmdb.h>
#include <dict.h> #include <dict.h>
#include <dict_lmdb.h> #include <dict_lmdb.h>
#include <warn_stat.h> #include <warn_stat.h>
@ -93,19 +73,9 @@
typedef struct { typedef struct {
DICT dict; /* generic members */ DICT dict; /* generic members */
MDB_env *env; /* LMDB environment */ SLMDB slmdb; /* sane LMDB API */
MDB_dbi dbi; /* database handle */
MDB_txn *txn; /* bulk update transaction */
MDB_cursor *cursor; /* for sequence ops */
size_t map_size; /* per-database size limit */
VSTRING *key_buf; /* key buffer */ VSTRING *key_buf; /* key buffer */
VSTRING *val_buf; /* value buffer */ VSTRING *val_buf; /* value buffer */
/* The following facilitate LMDB quirk workarounds. */
int dict_api_retries; /* workarounds per dict(3) call */
int bulk_mode_retries; /* workarounds per bulk transaction */
int dict_open_flags; /* dict(3) open flags */
int mdb_open_flags; /* LMDB open flags */
int readers_full; /* MDB_READERS_FULL errors */
} DICT_LMDB; } DICT_LMDB;
/* /*
@ -137,411 +107,15 @@ typedef struct {
#define DICT_LMDB_SIZE_INCR 2 /* Increase size by 1 bit on retry */ #define DICT_LMDB_SIZE_INCR 2 /* Increase size by 1 bit on retry */
#define DICT_LMDB_SIZE_MAX SSIZE_T_MAX #define DICT_LMDB_SIZE_MAX SSIZE_T_MAX
#define DICT_LMDB_API_RETRY_LIMIT 100 /* Retries per dict(3) API call */ #define DICT_LMDB_API_RETRY_LIMIT 2 /* Retries per dict(3) API call */
#define DICT_LMDB_BULK_RETRY_LIMIT \ #define DICT_LMDB_BULK_RETRY_LIMIT \
(2 * sizeof(size_t) * CHAR_BIT) /* Retries per bulk-mode transaction */ ((int) (2 * sizeof(size_t) * CHAR_BIT)) /* Retries per bulk-mode
* transaction */
/*
* XXX Should dict_lmdb_max_readers be configurable? Is this a per-database
* property? Per-process? Does it need to be the same for all processes?
*/
size_t dict_lmdb_map_size = 8192; /* Minimum size without SIGSEGV */ size_t dict_lmdb_map_size = 8192; /* Minimum size without SIGSEGV */
unsigned int dict_lmdb_max_readers = 216; /* 200 postfix processes,
* plus some extra */
/* #define msg_verbose 1 */ /* #define msg_verbose 1 */
/*
* The purpose of the error-recovering functions below is to hide LMDB
* quirks (MAP_FULL, MAP_CHANGED, READERS_FULL), so that the dict(3) API
* routines can pretend that those quirks don't exist, and focus on their
* own job.
*
* - To recover from a single-transaction LMDB error, each wrapper function
* uses tail recursion instead of goto. Since LMDB errors are rare, code
* clarity is more important than speed.
*
* - To recover from a bulk-mode transaction LMDB error, the error-recovery
* code jumps back into the caller to some pre-arranged point (the closest
* thing that C has to exception handling). With postmap, this means that
* bulk-mode LMDB error recovery is limited to input that is seekable.
*/
/* dict_lmdb_prepare - LMDB-specific (re)initialization before actual access */
static void dict_lmdb_prepare(DICT_LMDB *dict_lmdb)
{
int status;
/*
* This is called before accessing the database, or after recovery from
* an LMDB error. dict_lmdb->txn is either the database open()
* transaction or a freshly-created bulk-mode transaction.
*
* - With O_TRUNC we make a "drop" request before populating the database.
*
* - With DICT_FLAG_BULK_UPDATE we commit a bulk-mode transaction when the
* database is closed.
*/
if (dict_lmdb->dict_open_flags & O_TRUNC) {
if ((status = mdb_drop(dict_lmdb->txn, dict_lmdb->dbi, 0)) != 0)
msg_fatal("truncate %s:%s: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
if ((dict_lmdb->dict.flags & DICT_FLAG_BULK_UPDATE) == 0) {
if ((status = mdb_txn_commit(dict_lmdb->txn)))
msg_fatal("truncate %s:%s: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
dict_lmdb->txn = NULL;
}
} else if ((dict_lmdb->mdb_open_flags & MDB_RDONLY) != 0
|| (dict_lmdb->dict.flags & DICT_FLAG_BULK_UPDATE) == 0) {
mdb_txn_abort(dict_lmdb->txn);
dict_lmdb->txn = NULL;
}
}
/* dict_lmdb_recover - recover from LMDB errors */
static int dict_lmdb_recover(DICT_LMDB *dict_lmdb, int status)
{
const char *myname = "dict_lmdb_recover";
MDB_envinfo info;
/*
* Limit the number of recovery attempts per dict(3) API request.
*/
if ((dict_lmdb->dict_api_retries += 1) > DICT_LMDB_API_RETRY_LIMIT) {
if (msg_verbose)
msg_info("%s: %s:%s too many recovery attempts %d",
myname, dict_lmdb->dict.type, dict_lmdb->dict.name,
dict_lmdb->dict_api_retries);
return (status);
}
/*
* If we can recover from the error, we clear the error condition and the
* caller should retry the failed operation immediately. Otherwise, the
* caller should terminate with a fatal run-time error and the program
* should be re-run later.
*
* dict_lmdb->txn is either null (non-bulk transaction error), or an aborted
* bulk-mode transaction. If we want to make this wrapper layer suitable
* for general use, then the bulk/non-bulk distinction should be made
* less specific to Postfix.
*/
switch (status) {
/*
* As of LMDB 0.9.8 when a non-bulk update runs into a "map full"
* error, we can resize the environment's memory map and clear the
* error condition. The caller should retry immediately.
*/
case MDB_MAP_FULL:
/* Can we increase the memory map? Give up if we can't. */
if (dict_lmdb->map_size < DICT_LMDB_SIZE_MAX / DICT_LMDB_SIZE_INCR) {
dict_lmdb->map_size = dict_lmdb->map_size * DICT_LMDB_SIZE_INCR;
} else if (dict_lmdb->map_size < DICT_LMDB_SIZE_MAX) {
dict_lmdb->map_size = DICT_LMDB_SIZE_MAX;
} else {
/* Sorry, but we are already maxed out. */
break;
}
/* Resize the memory map. */
if (msg_verbose)
msg_info("updating database %s:%s size limit to %lu",
dict_lmdb->dict.type, dict_lmdb->dict.name,
(unsigned long) dict_lmdb->map_size);
if ((status = mdb_env_set_mapsize(dict_lmdb->env,
dict_lmdb->map_size)) != 0)
msg_fatal("env_set_mapsize %s:%s to %lu: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
(unsigned long) dict_lmdb->map_size,
mdb_strerror(status));
break;
/*
* When a writer resizes the database, read-only applications must
* increase their LMDB memory map size limit, too. Otherwise, they
* won't be able to read a table after it grows.
*
* As of LMDB 0.9.8 we can import the new memory map size limit into the
* database environment by calling mdb_env_set_mapsize() with a zero
* size argument. Then we extract the map size limit for later use.
* The caller should retry immediately.
*/
case MDB_MAP_RESIZED:
if ((status = mdb_env_set_mapsize(dict_lmdb->env, 0)) != 0)
msg_fatal("env_set_mapsize %s:%s to 0: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
/* Do not panic. Maps may shrink after bulk update. */
mdb_env_info(dict_lmdb->env, &info);
dict_lmdb->map_size = info.me_mapsize;
if (msg_verbose)
msg_info("importing database %s:%s new size limit %lu",
dict_lmdb->dict.type, dict_lmdb->dict.name,
(unsigned long) dict_lmdb->map_size);
break;
/*
* What is it with these built-in hard limits that cause systems to
* fail when resources are needed most? When the system is under
* stress it should slow down, not stop working.
*/
case MDB_READERS_FULL:
if (dict_lmdb->readers_full++ == 0)
msg_warn("database %s:%s: %s - increase lmdb_max_readers",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
rand_sleep(1000000, 1000000);
status = 0;
break;
/*
* We can't solve this problem. The application should terminate with
* a fatal run-time error and the program should be re-run later.
*/
default:
break;
}
/*
* If a bulk-mode transaction error is recoverable, build a new bulk-mode
* transaction from scratch, by making a long jump back into the caller
* at some pre-arranged point.
*/
if (dict_lmdb->txn != 0 && status == 0
&& (dict_lmdb->bulk_mode_retries += 1) <= DICT_LMDB_BULK_RETRY_LIMIT) {
status = mdb_txn_begin(dict_lmdb->env, NULL,
dict_lmdb->mdb_open_flags & MDB_RDONLY,
&dict_lmdb->txn);
if (status != 0)
msg_fatal("txn_begin %s:%s: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
dict_lmdb_prepare(dict_lmdb);
dict_longjmp(&dict_lmdb->dict, 1);
}
return (status);
}
/* dict_lmdb_txn_begin - mdb_txn_begin() wrapper with LMDB error recovery */
static void dict_lmdb_txn_begin(DICT_LMDB *dict_lmdb, int rdonly, MDB_txn **txn)
{
int status;
if ((status = mdb_txn_begin(dict_lmdb->env, NULL, rdonly, txn)) != 0) {
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0) {
dict_lmdb_txn_begin(dict_lmdb, rdonly, txn);
return;
}
msg_fatal("%s:%s: error starting %s transaction: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
rdonly ? "read" : "write", mdb_strerror(status));
}
}
/* dict_lmdb_get - mdb_get() wrapper with LMDB error recovery */
static int dict_lmdb_get(DICT_LMDB *dict_lmdb, MDB_val *mdb_key,
MDB_val *mdb_value)
{
MDB_txn *txn;
int status;
/*
* Start a read transaction if there's no bulk-mode txn.
*/
if (dict_lmdb->txn)
txn = dict_lmdb->txn;
else
dict_lmdb_txn_begin(dict_lmdb, MDB_RDONLY, &txn);
/*
* Do the lookup.
*/
if ((status = mdb_get(txn, dict_lmdb->dbi, mdb_key, mdb_value)) != 0
&& status != MDB_NOTFOUND) {
mdb_txn_abort(txn);
if (dict_lmdb->txn == 0)
txn = 0;
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0)
return (dict_lmdb_get(dict_lmdb, mdb_key, mdb_value));
}
/*
* Close the read txn if it's not the bulk-mode txn.
*/
if (txn && dict_lmdb->txn == 0)
mdb_txn_abort(txn);
return (status);
}
/* dict_lmdb_put - mdb_put() wrapper with LMDB error recovery */
static int dict_lmdb_put(DICT_LMDB *dict_lmdb, MDB_val *mdb_key,
MDB_val *mdb_value, int flags)
{
MDB_txn *txn;
int status;
/*
* Start a write transaction if there's no bulk-mode txn.
*/
if (dict_lmdb->txn)
txn = dict_lmdb->txn;
else
dict_lmdb_txn_begin(dict_lmdb, 0, &txn);
/*
* Do the update.
*/
if ((status = mdb_put(txn, dict_lmdb->dbi, mdb_key, mdb_value, flags)) != 0
&& status != MDB_KEYEXIST) {
mdb_txn_abort(txn);
if (dict_lmdb->txn == 0)
txn = 0;
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0)
return (dict_lmdb_put(dict_lmdb, mdb_key, mdb_value, flags));
}
/*
* Commit the transaction if it's not the bulk-mode txn.
*/
if (txn && dict_lmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0) {
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0)
return (dict_lmdb_put(dict_lmdb, mdb_key, mdb_value, flags));
msg_fatal("error committing database %s:%s: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
}
return (status);
}
/* dict_lmdb_del - mdb_del() wrapper with LMDB error recovery */
static int dict_lmdb_del(DICT_LMDB *dict_lmdb, MDB_val *mdb_key)
{
MDB_txn *txn;
int status;
/*
* Start a write transaction if there's no bulk-mode txn.
*/
if (dict_lmdb->txn)
txn = dict_lmdb->txn;
else
dict_lmdb_txn_begin(dict_lmdb, 0, &txn);
/*
* Do the update.
*/
if ((status = mdb_del(txn, dict_lmdb->dbi, mdb_key, NULL)) != 0
&& status != MDB_NOTFOUND) {
mdb_txn_abort(txn);
if (dict_lmdb->txn == 0)
txn = 0;
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0)
return (dict_lmdb_del(dict_lmdb, mdb_key));
}
/*
* Commit the transaction if it's not the bulk-mode txn.
*/
if (txn && dict_lmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0) {
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0)
return (dict_lmdb_del(dict_lmdb, mdb_key));
msg_fatal("error committing database %s:%s: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
}
return (status);
}
/* dict_lmdb_cursor_get - mdb_cursor_get() wrapper with LMDB error recovery */
static int dict_lmdb_cursor_get(DICT_LMDB *dict_lmdb, MDB_val *mdb_key,
MDB_val *mdb_value, MDB_cursor_op op)
{
MDB_txn *txn;
int status;
/*
* Open a read transaction and cursor if needed.
*/
if (dict_lmdb->cursor == 0) {
dict_lmdb_txn_begin(dict_lmdb, MDB_RDONLY, &txn);
if ((status = mdb_cursor_open(txn, dict_lmdb->dbi, &dict_lmdb->cursor))) {
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0)
return (dict_lmdb_cursor_get(dict_lmdb, mdb_key, mdb_value, op));
msg_fatal("%s:%s: cursor_open database: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
}
}
/*
* Database lookup.
*/
status = mdb_cursor_get(dict_lmdb->cursor, mdb_key, mdb_value, op);
/*
* Handle end-of-database or other error.
*/
if (status != 0) {
if (status == MDB_NOTFOUND) {
txn = mdb_cursor_txn(dict_lmdb->cursor);
mdb_cursor_close(dict_lmdb->cursor);
mdb_txn_abort(txn);
dict_lmdb->cursor = 0;
} else {
if ((status = dict_lmdb_recover(dict_lmdb, status)) == 0)
return (dict_lmdb_cursor_get(dict_lmdb, mdb_key, mdb_value, op));
}
}
return (status);
}
/* dict_lmdb_finish - wrapper with LMDB error recovery */
static void dict_lmdb_finish(DICT_LMDB *dict_lmdb)
{
int status;
/*
* Finish the bulk-mode transaction. If dict_lmdb_recover() returns after
* a bulk-mode transaction error, then it was unable to recover.
*/
if (dict_lmdb->txn) {
if ((status = mdb_txn_commit(dict_lmdb->txn)) != 0) {
(void) dict_lmdb_recover(dict_lmdb, status);
msg_fatal("%s:%s: closing dictionary: %s",
dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status));
}
}
/*
* Clean up after an unfinished sequence() operation.
*/
if (dict_lmdb->cursor) {
MDB_txn *txn = mdb_cursor_txn(dict_lmdb->cursor);
mdb_cursor_close(dict_lmdb->cursor);
mdb_txn_abort(txn);
}
}
/*
* With all recovery from LMDB quirks encapsulated in the routines above,
* the dict(3) API routines below can pretend that LMDB quirks don't exist
* and focus on their own job: accessing or updating the database.
*/
/* dict_lmdb_lookup - find database entry */ /* dict_lmdb_lookup - find database entry */
static const char *dict_lmdb_lookup(DICT *dict, const char *name) static const char *dict_lmdb_lookup(DICT *dict, const char *name)
@ -553,7 +127,6 @@ static const char *dict_lmdb_lookup(DICT *dict, const char *name)
int status, klen; int status, klen;
dict->error = 0; dict->error = 0;
dict_lmdb->dict_api_retries = 0;
klen = strlen(name); klen = strlen(name);
/* /*
@ -572,6 +145,13 @@ static const char *dict_lmdb_lookup(DICT *dict, const char *name)
name = lowercase(vstring_str(dict->fold_buf)); name = lowercase(vstring_str(dict->fold_buf));
} }
/*
* Acquire a shared lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
msg_fatal("%s: lock dictionary: %m", dict->name);
/* /*
* See if this LMDB file was written with one null byte appended to key * See if this LMDB file was written with one null byte appended to key
* and value. * and value.
@ -579,7 +159,7 @@ static const char *dict_lmdb_lookup(DICT *dict, const char *name)
if (dict->flags & DICT_FLAG_TRY1NULL) { if (dict->flags & DICT_FLAG_TRY1NULL) {
mdb_key.mv_data = (void *) name; mdb_key.mv_data = (void *) name;
mdb_key.mv_size = klen + 1; mdb_key.mv_size = klen + 1;
status = dict_lmdb_get(dict_lmdb, &mdb_key, &mdb_value); status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
if (status == 0) { if (status == 0) {
dict->flags &= ~DICT_FLAG_TRY0NULL; dict->flags &= ~DICT_FLAG_TRY0NULL;
result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
@ -598,7 +178,7 @@ static const char *dict_lmdb_lookup(DICT *dict, const char *name)
if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
mdb_key.mv_data = (void *) name; mdb_key.mv_data = (void *) name;
mdb_key.mv_size = klen; mdb_key.mv_size = klen;
status = dict_lmdb_get(dict_lmdb, &mdb_key, &mdb_value); status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
if (status == 0) { if (status == 0) {
dict->flags &= ~DICT_FLAG_TRY1NULL; dict->flags &= ~DICT_FLAG_TRY1NULL;
result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
@ -609,6 +189,14 @@ static const char *dict_lmdb_lookup(DICT *dict, const char *name)
mdb_strerror(status)); mdb_strerror(status));
} }
} }
/*
* Release the shared lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
msg_fatal("%s: unlock dictionary: %m", dict->name);
return (result); return (result);
} }
@ -622,7 +210,6 @@ static int dict_lmdb_update(DICT *dict, const char *name, const char *value)
int status; int status;
dict->error = 0; dict->error = 0;
dict_lmdb->dict_api_retries = 0;
/* /*
* Sanity check. * Sanity check.
@ -666,10 +253,17 @@ static int dict_lmdb_update(DICT *dict, const char *name, const char *value)
mdb_value.mv_size++; mdb_value.mv_size++;
} }
/*
* Acquire an exclusive lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
msg_fatal("%s: lock dictionary: %m", dict->name);
/* /*
* Do the update. * Do the update.
*/ */
status = dict_lmdb_put(dict_lmdb, &mdb_key, &mdb_value, status = slmdb_put(&dict_lmdb->slmdb, &mdb_key, &mdb_value,
(dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE); (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE);
if (status != 0) { if (status != 0) {
if (status == MDB_KEYEXIST) { if (status == MDB_KEYEXIST) {
@ -687,6 +281,14 @@ static int dict_lmdb_update(DICT *dict, const char *name, const char *value)
mdb_strerror(status)); mdb_strerror(status));
} }
} }
/*
* Release the exclusive lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
msg_fatal("%s: unlock dictionary: %m", dict->name);
return (status); return (status);
} }
@ -699,7 +301,6 @@ static int dict_lmdb_delete(DICT *dict, const char *name)
int status = 1, klen; int status = 1, klen;
dict->error = 0; dict->error = 0;
dict_lmdb->dict_api_retries = 0;
klen = strlen(name); klen = strlen(name);
/* /*
@ -718,6 +319,13 @@ static int dict_lmdb_delete(DICT *dict, const char *name)
name = lowercase(vstring_str(dict->fold_buf)); name = lowercase(vstring_str(dict->fold_buf));
} }
/*
* Acquire an exclusive lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
msg_fatal("%s: lock dictionary: %m", dict->name);
/* /*
* See if this LMDB file was written with one null byte appended to key * See if this LMDB file was written with one null byte appended to key
* and value. * and value.
@ -725,7 +333,7 @@ static int dict_lmdb_delete(DICT *dict, const char *name)
if (dict->flags & DICT_FLAG_TRY1NULL) { if (dict->flags & DICT_FLAG_TRY1NULL) {
mdb_key.mv_data = (void *) name; mdb_key.mv_data = (void *) name;
mdb_key.mv_size = klen + 1; mdb_key.mv_size = klen + 1;
status = dict_lmdb_del(dict_lmdb, &mdb_key); status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
if (status != 0) { if (status != 0) {
if (status == MDB_NOTFOUND) if (status == MDB_NOTFOUND)
status = 1; status = 1;
@ -745,7 +353,7 @@ static int dict_lmdb_delete(DICT *dict, const char *name)
if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
mdb_key.mv_data = (void *) name; mdb_key.mv_data = (void *) name;
mdb_key.mv_size = klen; mdb_key.mv_size = klen;
status = dict_lmdb_del(dict_lmdb, &mdb_key); status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
if (status != 0) { if (status != 0) {
if (status == MDB_NOTFOUND) if (status == MDB_NOTFOUND)
status = 1; status = 1;
@ -757,6 +365,14 @@ static int dict_lmdb_delete(DICT *dict, const char *name)
dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
} }
} }
/*
* Release the exclusive lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
msg_fatal("%s: unlock dictionary: %m", dict->name);
return (status); return (status);
} }
@ -773,7 +389,6 @@ static int dict_lmdb_sequence(DICT *dict, int function,
int status; int status;
dict->error = 0; dict->error = 0;
dict_lmdb->dict_api_retries = 0;
/* /*
* Determine the seek function. * Determine the seek function.
@ -789,10 +404,17 @@ static int dict_lmdb_sequence(DICT *dict, int function,
msg_panic("%s: invalid function: %d", myname, function); msg_panic("%s: invalid function: %d", myname, function);
} }
/*
* Acquire a shared lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
msg_fatal("%s: lock dictionary: %m", dict->name);
/* /*
* Database lookup. * Database lookup.
*/ */
status = dict_lmdb_cursor_get(dict_lmdb, &mdb_key, &mdb_value, op); status = slmdb_cursor_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value, op);
switch (status) { switch (status) {
@ -811,6 +433,7 @@ static int dict_lmdb_sequence(DICT *dict, int function,
*/ */
case MDB_NOTFOUND: case MDB_NOTFOUND:
status = 1; status = 1;
/* Not: mdb_cursor_close(). Wrong abstraction level. */
break; break;
/* /*
@ -821,28 +444,24 @@ static int dict_lmdb_sequence(DICT *dict, int function,
dict_lmdb->dict.type, dict_lmdb->dict.name, dict_lmdb->dict.type, dict_lmdb->dict.name,
mdb_strerror(status)); mdb_strerror(status));
} }
/*
* Release the shared lock.
*/
if ((dict->flags & DICT_FLAG_LOCK)
&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
msg_fatal("%s: unlock dictionary: %m", dict->name);
return (status); return (status);
} }
/* dict_lmdb_lock - noop lock handler */
static int dict_lmdb_lock(DICT *dict, int unused_op)
{
/* LMDB does its own concurrency control */
return 0;
}
/* dict_lmdb_close - disassociate from data base */ /* dict_lmdb_close - disassociate from data base */
static void dict_lmdb_close(DICT *dict) static void dict_lmdb_close(DICT *dict)
{ {
DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
dict_lmdb->dict_api_retries = 0; slmdb_close(&dict_lmdb->slmdb);
dict_lmdb_finish(dict_lmdb);
if (dict_lmdb->dict.stat_fd >= 0)
close(dict_lmdb->dict.stat_fd);
mdb_env_close(dict_lmdb->env);
if (dict_lmdb->key_buf) if (dict_lmdb->key_buf)
vstring_free(dict_lmdb->key_buf); vstring_free(dict_lmdb->key_buf);
if (dict_lmdb->val_buf) if (dict_lmdb->val_buf)
@ -852,62 +471,96 @@ static void dict_lmdb_close(DICT *dict)
dict_free(dict); dict_free(dict);
} }
/* dict_lmdb_longjmp - debug logging */
static void dict_lmdb_longjmp(void *context, int val)
{
DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
dict_longjmp(&dict_lmdb->dict, val);
}
/* dict_lmdb_notify - debug logging */
static void dict_lmdb_notify(void *context, int error_code,...)
{
DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
va_list ap;
va_start(ap, error_code);
switch (error_code) {
case MDB_SUCCESS:
msg_info("database %s:%s: using size limit %lu during open",
dict_lmdb->dict.type, dict_lmdb->dict.name,
(unsigned long) va_arg(ap, size_t));
break;
case MDB_MAP_FULL:
msg_info("database %s:%s: using size limit %lu after MDB_MAP_FULL",
dict_lmdb->dict.type, dict_lmdb->dict.name,
(unsigned long) va_arg(ap, size_t));
break;
case MDB_MAP_RESIZED:
msg_info("database %s:%s: using size limit %lu after MDB_MAP_RESIZED",
dict_lmdb->dict.type, dict_lmdb->dict.name,
(unsigned long) va_arg(ap, size_t));
break;
case MDB_READERS_FULL:
msg_info("database %s:%s: pausing after MDB_READERS_FULL",
dict_lmdb->dict.type, dict_lmdb->dict.name);
break;
default:
msg_warn("unknown MDB error code: %d", error_code);
break;
}
va_end(ap);
}
/* dict_lmdb_open - open LMDB data base */ /* dict_lmdb_open - open LMDB data base */
DICT *dict_lmdb_open(const char *path, int open_flags, int dict_flags) DICT *dict_lmdb_open(const char *path, int dict_open_flags, int dict_flags)
{ {
DICT_LMDB *dict_lmdb; DICT_LMDB *dict_lmdb;
DICT *dict;
struct stat st; struct stat st;
MDB_env *env; SLMDB slmdb;
MDB_txn *txn;
MDB_dbi dbi;
char *mdb_path; char *mdb_path;
int env_flags, status; int mdb_open_flags, status;
size_t map_size = dict_lmdb_map_size; int db_fd;
mdb_path = concatenate(path, "." DICT_TYPE_LMDB, (char *) 0); mdb_path = concatenate(path, "." DICT_TYPE_LMDB, (char *) 0);
env_flags = MDB_NOSUBDIR; mdb_open_flags = MDB_NOSUBDIR | MDB_NOLOCK;
if (open_flags == O_RDONLY) if (dict_open_flags == O_RDONLY)
env_flags |= MDB_RDONLY; mdb_open_flags |= MDB_RDONLY;
if ((status = mdb_env_create(&env)))
msg_fatal("env_create %s: %s", mdb_path, mdb_strerror(status));
if (stat(mdb_path, &st) == 0 && st.st_size > map_size) {
if (st.st_size / map_size < DICT_LMDB_SIZE_MAX / map_size) {
map_size = (st.st_size / map_size + 1) * map_size;
} else {
map_size = st.st_size;
}
if (msg_verbose)
msg_info("using %s:%s map size %lu",
DICT_TYPE_LMDB, path, (unsigned long) map_size);
}
if ((status = mdb_env_set_mapsize(env, map_size)))
msg_fatal("env_set_mapsize %s: %s", mdb_path, mdb_strerror(status));
if ((status = mdb_env_set_maxreaders(env, dict_lmdb_max_readers)))
msg_fatal("env_set_maxreaders %s: %s", mdb_path, mdb_strerror(status));
/* /*
* Gracefully handle the most common mistake. * Gracefully handle most database open errors.
*/ */
if ((status = mdb_env_open(env, mdb_path, env_flags, 0644))) { if ((status = slmdb_open(&slmdb, mdb_path, dict_open_flags, mdb_open_flags,
mdb_env_close(env); dict_flags & DICT_FLAG_BULK_UPDATE, dict_lmdb_map_size,
return (dict_surrogate(DICT_TYPE_LMDB, path, open_flags, dict_flags, DICT_LMDB_SIZE_INCR, DICT_LMDB_SIZE_MAX)) != 0) {
"open database %s: %m", mdb_path)); dict = dict_surrogate(DICT_TYPE_LMDB, path, dict_open_flags,
dict_flags, "open database %s: %m", mdb_path);
myfree(mdb_path);
return (dict);
} }
if ((status = mdb_txn_begin(env, NULL, env_flags & MDB_RDONLY, &txn)))
msg_fatal("txn_begin %s: %s", mdb_path, mdb_strerror(status));
/* /*
* mdb_open() requires a txn, but since the default DB always exists in * XXX Persistent locking belongs in mkmap_lmdb.
* an LMDB environment, we usually don't need to do anything else with *
* the txn. It is currently used for bulk transactions. * We just need to acquire exclusive access momentarily. This establishes
* that no readers are accessing old (obsoleted by copy-on-write) txn
* snapshots, so we are free to reuse all eligible old pages. Downgrade
* the lock right after acquiring it. This is sufficient to keep out
* other writers until we are done.
*/ */
if ((status = mdb_open(txn, NULL, 0, &dbi))) db_fd = slmdb_fd(&slmdb);
msg_fatal("mdb_open %s: %s", mdb_path, mdb_strerror(status)); if (dict_flags & DICT_FLAG_BULK_UPDATE) {
if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
msg_fatal("%s: lock dictionary: %m", mdb_path);
if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
msg_fatal("%s: unlock dictionary: %m", mdb_path);
}
/* /*
* Bundle up. * Bundle up.
@ -918,15 +571,17 @@ DICT *dict_lmdb_open(const char *path, int open_flags, int dict_flags)
dict_lmdb->dict.delete = dict_lmdb_delete; dict_lmdb->dict.delete = dict_lmdb_delete;
dict_lmdb->dict.sequence = dict_lmdb_sequence; dict_lmdb->dict.sequence = dict_lmdb_sequence;
dict_lmdb->dict.close = dict_lmdb_close; dict_lmdb->dict.close = dict_lmdb_close;
dict_lmdb->dict.lock = dict_lmdb_lock;
if ((dict_lmdb->dict.stat_fd = open(mdb_path, O_RDONLY)) < 0) if (fstat(db_fd, &st) < 0)
msg_fatal("dict_lmdb_open: %s: %m", mdb_path);
if (fstat(dict_lmdb->dict.stat_fd, &st) < 0)
msg_fatal("dict_lmdb_open: fstat: %m"); msg_fatal("dict_lmdb_open: fstat: %m");
dict_lmdb->dict.lock_fd = dict_lmdb->dict.stat_fd = db_fd;
dict_lmdb->dict.mtime = st.st_mtime; dict_lmdb->dict.mtime = st.st_mtime;
dict_lmdb->dict.owner.uid = st.st_uid; dict_lmdb->dict.owner.uid = st.st_uid;
dict_lmdb->dict.owner.status = (st.st_uid != 0); dict_lmdb->dict.owner.status = (st.st_uid != 0);
dict_lmdb->key_buf = 0;
dict_lmdb->val_buf = 0;
/* /*
* Warn if the source file is newer than the indexed file, except when * Warn if the source file is newer than the indexed file, except when
* the source file changed only seconds ago. * the source file changed only seconds ago.
@ -937,30 +592,37 @@ DICT *dict_lmdb_open(const char *path, int open_flags, int dict_flags)
&& st.st_mtime < time((time_t *) 0) - 100) && st.st_mtime < time((time_t *) 0) - 100)
msg_warn("database %s is older than source file %s", mdb_path, path); msg_warn("database %s is older than source file %s", mdb_path, path);
close_on_exec(dict_lmdb->dict.stat_fd, CLOSE_ON_EXEC);
dict_lmdb->dict.flags = dict_flags | DICT_FLAG_FIXED; dict_lmdb->dict.flags = dict_flags | DICT_FLAG_FIXED;
if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
dict_lmdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); dict_lmdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
if (dict_flags & DICT_FLAG_FOLD_FIX) if (dict_flags & DICT_FLAG_FOLD_FIX)
dict_lmdb->dict.fold_buf = vstring_alloc(10); dict_lmdb->dict.fold_buf = vstring_alloc(10);
dict_lmdb->env = env;
dict_lmdb->dbi = dbi;
dict_lmdb->map_size = map_size;
dict_lmdb->cursor = 0;
dict_lmdb->key_buf = 0;
dict_lmdb->val_buf = 0;
/* The following facilitate transparent error recovery. */
dict_lmdb->dict_api_retries = 0;
dict_lmdb->bulk_mode_retries = 0;
dict_lmdb->dict_open_flags = open_flags;
dict_lmdb->mdb_open_flags = env_flags;
dict_lmdb->txn = txn;
dict_lmdb->readers_full = 0;
dict_lmdb_prepare(dict_lmdb);
if (dict_flags & DICT_FLAG_BULK_UPDATE) if (dict_flags & DICT_FLAG_BULK_UPDATE)
dict_jmp_alloc(&dict_lmdb->dict); /* build into dict_alloc() */ dict_jmp_alloc(&dict_lmdb->dict);
/*
* The following requests return an error result only if we have serious
* memory corruption problem.
*/
slmdb_control(&slmdb,
SLMDB_CTL_API_RETRY_LIMIT, DICT_LMDB_API_RETRY_LIMIT,
SLMDB_CTL_BULK_RETRY_LIMIT, DICT_LMDB_BULK_RETRY_LIMIT,
SLMDB_CTL_LONGJMP_FN, dict_lmdb_longjmp,
SLMDB_CTL_CONTEXT, (void *) dict_lmdb,
SLMDB_CTL_END);
if (msg_verbose) {
slmdb_control(&slmdb,
SLMDB_CTL_NOTIFY_FN, dict_lmdb_notify,
SLMDB_CTL_END);
dict_lmdb_notify((void *) dict_lmdb, MDB_SUCCESS,
slmdb_curr_limit(&slmdb));
}
/*
* From here on no direct assignments to slmdb.
*/
dict_lmdb->slmdb = slmdb;
myfree(mdb_path); myfree(mdb_path);

View File

@ -299,7 +299,6 @@ static const DICT_OPEN_INFO dict_open_info[] = {
DICT_TYPE_BTREE, dict_btree_open, DICT_TYPE_BTREE, dict_btree_open,
#endif #endif
#ifdef HAS_LMDB #ifdef HAS_LMDB
#error "LMDB support is forbidden"
DICT_TYPE_LMDB, dict_lmdb_open, DICT_TYPE_LMDB, dict_lmdb_open,
#endif #endif
#ifdef HAS_NIS #ifdef HAS_NIS

View File

@ -61,6 +61,15 @@
#include "pcre.h" #include "pcre.h"
#include "warn_stat.h" #include "warn_stat.h"
/*
* Backwards compatibility.
*/
#ifdef PCRE_STUDY_JIT_COMPILE
#define DICT_PCRE_FREE_STUDY(x) pcre_free_study(x)
#else
#define DICT_PCRE_FREE_STUDY(x) pcre_free((char *) (x))
#endif
/* /*
* Support for IF/ENDIF based on an idea by Bert Driehuis. * Support for IF/ENDIF based on an idea by Bert Driehuis.
*/ */
@ -389,7 +398,7 @@ static void dict_pcre_close(DICT *dict)
if (match_rule->pattern) if (match_rule->pattern)
myfree((char *) match_rule->pattern); myfree((char *) match_rule->pattern);
if (match_rule->hints) if (match_rule->hints)
myfree((char *) match_rule->hints); DICT_PCRE_FREE_STUDY(match_rule->hints);
if (match_rule->replacement) if (match_rule->replacement)
myfree((char *) match_rule->replacement); myfree((char *) match_rule->replacement);
break; break;
@ -398,7 +407,7 @@ static void dict_pcre_close(DICT *dict)
if (if_rule->pattern) if (if_rule->pattern)
myfree((char *) if_rule->pattern); myfree((char *) if_rule->pattern);
if (if_rule->hints) if (if_rule->hints)
myfree((char *) if_rule->hints); DICT_PCRE_FREE_STUDY(if_rule->hints);
break; break;
case DICT_PCRE_OP_ENDIF: case DICT_PCRE_OP_ENDIF:
break; break;
@ -679,7 +688,7 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno,
if (engine.pattern) if (engine.pattern)
myfree((char *) engine.pattern); myfree((char *) engine.pattern);
if (engine.hints) if (engine.hints)
myfree((char *) engine.hints); DICT_PCRE_FREE_STUDY(engine.hints);
CREATE_MATCHOP_ERROR_RETURN(0); CREATE_MATCHOP_ERROR_RETURN(0);
} }
#endif #endif

664
postfix/src/util/slmdb.c Normal file
View File

@ -0,0 +1,664 @@
/*++
/* NAME
/* slmdb 3
/* SUMMARY
/* Simplified LMDB API
/* SYNOPSIS
/* #include <slmdb.h>
/*
/* size_t slmdb_map_size;
/*
/* int slmdb_open(slmdb, path, open_flags, lmdb_flags, bulk_mode,
/* curr_limit, size_incr, hard_limit)
/* SLMDB *slmdb;
/* const char *path;
/* int open_flags;
/* int lmdb_flags;
/* int bulk_mode;
/* size_t curr_limit;
/* int size_incr;
/* size_t hard_limit;
/*
/* int slmdb_close(slmdb)
/* SLMDB *slmdb;
/*
/* int slmdb_get(slmdb, mdb_key, mdb_value)
/* SLMDB *slmdb;
/* MDB_val *mdb_key;
/* MDB_val *mdb_value;
/*
/* int slmdb_put(slmdb, mdb_key, mdb_value, flags)
/* SLMDB *slmdb;
/* MDB_val *mdb_key;
/* MDB_val *mdb_value;
/* int flags;
/*
/* int slmdb_del(slmdb, mdb_key)
/* SLMDB *slmdb;
/* MDB_val *mdb_key;
/*
/* int slmdb_cursor_get(slmdb, mdb_key, mdb_value, op)
/* SLMDB *slmdb;
/* MDB_val *mdb_key;
/* MDB_val *mdb_value;
/* MDB_cursor_op op;
/* AUXILIARY FUNCTIONS
/* int slmdb_fd(slmdb)
/* SLMDB *slmdb;
/*
/* size_t slmdb_curr_limit(slmdb)
/* SLMDB *slmdb;
/*
/* int slmdb_control(slmdb, id, ...)
/* SLMDB *slmdb;
/* int id;
/* DESCRIPTION
/* This module simplifies the LMDB API by hiding recoverable
/* errors from the application. Details are given in the
/* section "ERROR RECOVERY".
/*
/* slmdb_open() opens an LMDB database. The result value is
/* an LMDB status code (zero in case of success).
/*
/* slmdb_close() finalizes an optional bulk-mode transaction
/* and closes a successfully-opened LMDB database. The result
/* value is an LMDB status code (zero in case of success).
/*
/* slmdb_get() is an mdb_get() wrapper with automatic error
/* recovery. The result value is an LMDB status code (zero
/* in case of success).
/*
/* slmdb_put() is an mdb_put() wrapper with automatic error
/* recovery. The result value is an LMDB status code (zero
/* in case of success).
/*
/* slmdb_del() is an mdb_del() wrapper with automatic error
/* recovery. The result value is an LMDB status code (zero
/* in case of success).
/*
/* slmdb_cursor_get() iterates over an LMDB database. The
/* result value is an LMDB status code (zero in case of success).
/*
/* slmdb_fd() returns the file descriptor for an open LMDB
/* database. This may be used for file status queries or
/* application-controlled locking.
/*
/* slmdb_curr_limit() returns the current database size limit
/* for the specified database.
/*
/* slmdb_control() specifies optional features. The arguments
/* are a list of (name, value) pairs, terminated with
/* SLMDB_CTL_END. The result is 0 in case of success, or -1
/* with errno indicating the nature of the problem. The following
/* text enumerates the symbolic request names and the types
/* of the corresponding additional arguments.
/* .IP "SLMDB_CTL_LONGJMP_FN (void (*)(void *, int))
/* Application long-jump call-back function pointer. The
/* function must not return and is called to repeat a failed
/* bulk-mode transaction from the start. The arguments are
/* the application context and the setjmp() or sigsetjmp()
/* result value.
/* .IP "SLMDB_CTL_NOTIFY_FN (void (*)(void *, int, ...))"
/* Application notification call-back function pointer. The
/* function is called after succesful error recovery with as
/* arguments the application context, the MDB error code, and
/* additional arguments that depend on the error code.
/* Details are given in the section "ERROR RECOVERY".
/* .IP "SLMDB_CTL_CONTEXT (void *)"
/* Application context that is passed in application notification
/* and long-jump call-back function calls.
/* .IP "SLMDB_CTL_API_RETRY_LIMIT (int)"
/* How many times to recover from LMDB errors within the
/* execution of a single slmdb(3) API call before giving up.
/* .IP "SLMDB_CTL_BULK_RETRY_LIMIT (int)"
/* How many times to recover from a bulk-mode transaction
/* before giving up.
/* ERROR RECOVERY
/* .ad
/* .fi
/* This module automatically repeats failed requests after
/* recoverable errors, up to limits specified with slmdb_control().
/*
/* Recoverable errors are reported through an optional
/* notification function specified with slmdb_control(). With
/* recoverable MDB_MAP_FULL and MDB_MAP_RESIZED errors, the
/* additional argument is a size_t value with the updated
/* current database size limit; with recoverable MDB_READERS_FULL
/* errors there is no additional argument.
/* BUGS
/* Recovery from MDB_MAP_FULL involves resizing the database
/* memory mapping. According to LMDB documentation this
/* requires that there is no concurrent activity in the same
/* database by other threads in the same memory address space.
/* SEE ALSO
/* lmdb(3) API manpage (currently, non-existent).
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Howard Chu
/* Symas Corporation
/*
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
/* System library. */
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
/* Application-specific. */
#include <slmdb.h>
/*
* LMDB 0.9.8 allows the application to update the database size limit
* on-the-fly (typically after an MDB_MAP_FULL error). The only limit that
* remains is imposed by the hardware address space. The implementation is
* supposed to handle databases larger than physical memory. However, at
* some point in time there was no such guarantee for (bulk) transactions
* larger than physical memory.
*
* LMDB 0.9.9 allows the application to manage locks. This elimimates multiple
* problems:
*
* - The need for a (world-)writable lockfile, which is a show-stopper for
* multiprogrammed applications that have privileged writers and
* unprivileged readers.
*
* - Hard-coded inode numbers (in ftok() output) in lockfile content that can
* prevent automatic crash recovery, and related to that, sub-optimal
* semaphore performance on BSD systems.
*/
#if MDB_VERSION_FULL < MDB_VERINT(0, 9, 9)
#error "Build with LMDB version 0.9.9 or later"
#endif
#define SLMDB_DEF_API_RETRY_LIMIT 2 /* Retries per dict(3) API call */
#define SLMDB_DEF_BULK_RETRY_LIMIT \
(2 * sizeof(size_t) * CHAR_BIT) /* Retries per bulk-mode transaction */
/*
* The purpose of the error-recovering functions below is to hide LMDB
* quirks (MAP_FULL, MAP_RESIZED, MDB_READERS_FULL), so that the caller can
* pretend that those quirks don't exist, and focus on its own job.
*
* - To recover from a single-transaction LMDB error, each wrapper function
* uses tail recursion instead of goto. Since LMDB errors are rare, code
* clarity is more important than speed.
*
* - To recover from a bulk-transaction LMDB error, the error-recovery code
* jumps back into the caller to some pre-arranged point (the closest thing
* that C has to exception handling). The application is then expected to
* repeat the bulk transaction from scratch.
*/
/*
* We increment the recursion counter each time we try to recover from
* error, and reset the recursion counter when returning to the application
* from the slmdb API.
*/
#define SLMDB_API_RETURN(slmdb, status) do { \
(slmdb)->api_retry_count = 0; \
return (status); \
} while (0)
/* slmdb_prepare - LMDB-specific (re)initialization before actual access */
static int slmdb_prepare(SLMDB *slmdb)
{
int status;
/*
* This is called before accessing the database, or after recovery from
* an LMDB error. Note: this code cannot recover from errors itself.
* slmdb->txn is either the database open() transaction or a
* freshly-created bulk-mode transaction.
*
* - With O_TRUNC we make a "drop" request before updating the database.
*
* - With a bulk-mode transaction we commit when the database is closed.
*
* XXX If we want to make the slmdb API suitable for general use, then the
* bulk/non-bulk handling must be generalized.
*/
if (slmdb->open_flags & O_TRUNC) {
if ((status = mdb_drop(slmdb->txn, slmdb->dbi, 0)) != 0)
return (status);
if ((slmdb->bulk_mode) == 0) {
if ((status = mdb_txn_commit(slmdb->txn)))
return (status);
slmdb->txn = 0;
}
} else if ((slmdb->lmdb_flags & MDB_RDONLY) != 0
|| (slmdb->bulk_mode) == 0) {
mdb_txn_abort(slmdb->txn);
slmdb->txn = 0;
}
slmdb->api_retry_count = 0;
return (status);
}
/* slmdb_recover - recover from LMDB errors */
static int slmdb_recover(SLMDB *slmdb, int status)
{
MDB_envinfo info;
/*
* Limit the number of recovery attempts per slmdb(3) API request.
*/
if ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit)
return (status);
/*
* If we can recover from the error, we clear the error condition and the
* caller should retry the failed operation immediately. Otherwise, the
* caller should terminate with a fatal run-time error and the program
* should be re-run later.
*
* slmdb->txn must be either null (non-bulk transaction error), or an
* aborted bulk-mode transaction.
*
* XXX If we want to make the slmdb API suitable for general use, then the
* bulk/non-bulk handling must be generalized.
*/
switch (status) {
/*
* As of LMDB 0.9.8 when a non-bulk update runs into a "map full"
* error, we can resize the environment's memory map and clear the
* error condition. The caller should retry immediately.
*/
case MDB_MAP_FULL:
/* Can we increase the memory map? Give up if we can't. */
if (slmdb->curr_limit < slmdb->hard_limit / slmdb->size_incr) {
slmdb->curr_limit = slmdb->curr_limit * slmdb->size_incr;
} else if (slmdb->curr_limit < slmdb->hard_limit) {
slmdb->curr_limit = slmdb->hard_limit;
} else {
/* Sorry, we are already maxed out. */
break;
}
if (slmdb->notify_fn)
slmdb->notify_fn(slmdb->cb_context, MDB_MAP_FULL,
slmdb->curr_limit);
status = mdb_env_set_mapsize(slmdb->env, slmdb->curr_limit);
break;
/*
* When a writer resizes the database, read-only applications must
* increase their LMDB memory map size limit, too. Otherwise, they
* won't be able to read a table after it grows.
*
* As of LMDB 0.9.8 we can import the new memory map size limit into the
* database environment by calling mdb_env_set_mapsize() with a zero
* size argument. Then we extract the map size limit for later use.
* The caller should retry immediately.
*/
case MDB_MAP_RESIZED:
if ((status = mdb_env_set_mapsize(slmdb->env, 0)) == 0) {
/* Do not panic. Maps may shrink after bulk update. */
mdb_env_info(slmdb->env, &info);
slmdb->curr_limit = info.me_mapsize;
if (slmdb->notify_fn)
slmdb->notify_fn(slmdb->cb_context, MDB_MAP_RESIZED,
slmdb->curr_limit);
}
break;
/*
* What is it with these built-in hard limits that cause systems to
* stop when demand is at its highest? When the system is under
* stress it should slow down and keep making progress.
*/
case MDB_READERS_FULL:
if (slmdb->notify_fn)
slmdb->notify_fn(slmdb->cb_context, MDB_READERS_FULL);
sleep(1);
status = 0;
break;
/*
* We can't solve this problem. The application should terminate with
* a fatal run-time error and the program should be re-run later.
*/
default:
break;
}
/*
* If a bulk-transaction error is recoverable, build a new bulk
* transaction from scratch, by making a long jump back into the caller
* at some pre-arranged point.
*/
if (slmdb->txn != 0 && status == 0 && slmdb->longjmp_fn != 0
&& (slmdb->bulk_retry_count += 1) <= slmdb->bulk_retry_limit) {
if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0,
slmdb->lmdb_flags & MDB_RDONLY,
&slmdb->txn)) == 0
&& (status = slmdb_prepare(slmdb)) == 0)
slmdb->longjmp_fn(slmdb->cb_context, 1);
}
return (status);
}
/* slmdb_txn_begin - mdb_txn_begin() wrapper with LMDB error recovery */
static int slmdb_txn_begin(SLMDB *slmdb, int rdonly, MDB_txn **txn)
{
int status;
if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0, rdonly, txn)) != 0
&& (status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_txn_begin(slmdb, rdonly, txn);
return (status);
}
/* slmdb_get - mdb_get() wrapper with LMDB error recovery */
int slmdb_get(SLMDB *slmdb, MDB_val *mdb_key, MDB_val *mdb_value)
{
MDB_txn *txn;
int status;
/*
* Start a read transaction if there's no bulk-mode txn.
*/
if (slmdb->txn)
txn = slmdb->txn;
else if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0)
SLMDB_API_RETURN(slmdb, status);
/*
* Do the lookup.
*/
if ((status = mdb_get(txn, slmdb->dbi, mdb_key, mdb_value)) != 0
&& status != MDB_NOTFOUND) {
mdb_txn_abort(txn);
if ((status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_get(slmdb, mdb_key, mdb_value);
SLMDB_API_RETURN(slmdb, status);
}
/*
* Close the read txn if it's not the bulk-mode txn.
*/
if (slmdb->txn == 0)
mdb_txn_abort(txn);
SLMDB_API_RETURN(slmdb, status);
}
/* slmdb_put - mdb_put() wrapper with LMDB error recovery */
int slmdb_put(SLMDB *slmdb, MDB_val *mdb_key,
MDB_val *mdb_value, int flags)
{
MDB_txn *txn;
int status;
/*
* Start a write transaction if there's no bulk-mode txn.
*/
if (slmdb->txn)
txn = slmdb->txn;
else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
SLMDB_API_RETURN(slmdb, status);
/*
* Do the update.
*/
if ((status = mdb_put(txn, slmdb->dbi, mdb_key, mdb_value, flags)) != 0) {
mdb_txn_abort(txn);
if (status != MDB_KEYEXIST) {
if ((status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
SLMDB_API_RETURN(slmdb, status);
}
}
/*
* Commit the transaction if it's not the bulk-mode txn.
*/
if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0
&& (status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
SLMDB_API_RETURN(slmdb, status);
}
/* slmdb_del - mdb_del() wrapper with LMDB error recovery */
int slmdb_del(SLMDB *slmdb, MDB_val *mdb_key)
{
MDB_txn *txn;
int status;
/*
* Start a write transaction if there's no bulk-mode txn.
*/
if (slmdb->txn)
txn = slmdb->txn;
else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
SLMDB_API_RETURN(slmdb, status);
/*
* Do the update.
*/
if ((status = mdb_del(txn, slmdb->dbi, mdb_key, (MDB_val *) 0)) != 0) {
mdb_txn_abort(txn);
if (status != MDB_NOTFOUND) {
if ((status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_del(slmdb, mdb_key);
SLMDB_API_RETURN(slmdb, status);
}
}
/*
* Commit the transaction if it's not the bulk-mode txn.
*/
if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0
&& (status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_del(slmdb, mdb_key);
SLMDB_API_RETURN(slmdb, status);
}
/* slmdb_cursor_get - mdb_cursor_get() wrapper with LMDB error recovery */
int slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key,
MDB_val *mdb_value, MDB_cursor_op op)
{
MDB_txn *txn;
int status;
/*
* Open a read transaction and cursor if needed.
*/
if (slmdb->cursor == 0) {
slmdb_txn_begin(slmdb, MDB_RDONLY, &txn);
if ((status = mdb_cursor_open(txn, slmdb->dbi, &slmdb->cursor)) != 0) {
mdb_txn_abort(txn);
if ((status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
SLMDB_API_RETURN(slmdb, status);
}
}
/*
* Database lookup.
*/
status = mdb_cursor_get(slmdb->cursor, mdb_key, mdb_value, op);
/*
* Handle end-of-database or other error.
*/
if (status != 0) {
if (status == MDB_NOTFOUND) {
txn = mdb_cursor_txn(slmdb->cursor);
mdb_cursor_close(slmdb->cursor);
mdb_txn_abort(txn);
slmdb->cursor = 0;
} else {
if ((status = slmdb_recover(slmdb, status)) == 0)
status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
SLMDB_API_RETURN(slmdb, status);
/* Do not hand-optimize out the above return statement. */
}
}
SLMDB_API_RETURN(slmdb, status);
}
/* slmdb_control - control optional settings */
int slmdb_control(SLMDB *slmdb, int first,...)
{
va_list ap;
int status = 0;
int reqno;
va_start(ap, first);
for (reqno = first; reqno != SLMDB_CTL_END; reqno = va_arg(ap, int)) {
switch (reqno) {
case SLMDB_CTL_LONGJMP_FN:
slmdb->longjmp_fn = va_arg(ap, SLMDB_LONGJMP_FN);
break;
case SLMDB_CTL_NOTIFY_FN:
slmdb->notify_fn = va_arg(ap, SLMDB_NOTIFY_FN);
break;
case SLMDB_CTL_CONTEXT:
slmdb->cb_context = va_arg(ap, void *);
break;
case SLMDB_CTL_API_RETRY_LIMIT:
slmdb->api_retry_limit = va_arg(ap, int);
break;
case SLMDB_CTL_BULK_RETRY_LIMIT:
slmdb->bulk_retry_limit = va_arg(ap, int);
break;
default:
errno = EINVAL;
status = -1;
break;
}
}
va_end(ap);
return (status);
}
/* slmdb_close - wrapper with LMDB error recovery */
int slmdb_close(SLMDB *slmdb)
{
int status = 0;
/*
* Finish an open bulk transaction. If slmdb_recover() returns after a
* bulk-transaction error, then it was unable to recover.
*/
if (slmdb->txn != 0
&& (status = mdb_txn_commit(slmdb->txn)) != 0)
status = slmdb_recover(slmdb, status);
/*
* Clean up after an unfinished sequence() operation.
*/
if (slmdb->cursor) {
MDB_txn *txn = mdb_cursor_txn(slmdb->cursor);
mdb_cursor_close(slmdb->cursor);
mdb_txn_abort(txn);
}
mdb_env_close(slmdb->env);
SLMDB_API_RETURN(slmdb, status);
}
/* slmdb_open - open wrapped LMDB database */
int slmdb_open(SLMDB *slmdb, const char *path, int open_flags,
int lmdb_flags, int bulk_mode, size_t curr_limit,
int size_incr, size_t hard_limit)
{
struct stat st;
MDB_env *env;
MDB_txn *txn;
MDB_dbi dbi;
int db_fd;
int status;
/*
* Create LMDB environment.
*/
if ((status = mdb_env_create(&env)) != 0)
return (status);
/*
* Make sure that the memory map has room to store and commit an initial
* "drop" transaction. We have no way to recover from errors before the
* first application-level request.
*/
#define SLMDB_FUDGE 8192
if (curr_limit < SLMDB_FUDGE)
curr_limit = SLMDB_FUDGE;
if (stat(path, &st) == 0 && st.st_size > curr_limit - SLMDB_FUDGE) {
if (st.st_size > hard_limit)
hard_limit = st.st_size;
if (st.st_size < hard_limit - SLMDB_FUDGE)
curr_limit = st.st_size + SLMDB_FUDGE;
else
curr_limit = hard_limit;
}
/*
* mdb_open() requires a txn, but since the default DB always exists in
* an LMDB environment, we usually don't need to do anything else with
* the txn. It is currently used for truncate and for bulk transactions.
*/
if ((status = mdb_env_set_mapsize(env, curr_limit)) != 0
|| (status = mdb_env_open(env, path, lmdb_flags, 0644)) != 0
|| (status = mdb_txn_begin(env, (MDB_txn *) 0,
lmdb_flags & MDB_RDONLY, &txn)) != 0
|| (status = mdb_open(txn, (const char *) 0, 0, &dbi)) != 0
|| (status = mdb_env_get_fd(env, &db_fd)) != 0) {
mdb_env_close(env);
return (status);
}
/*
* Bundle up.
*/
slmdb->open_flags = open_flags;
slmdb->lmdb_flags = lmdb_flags;
slmdb->bulk_mode = bulk_mode;
slmdb->curr_limit = curr_limit;
slmdb->size_incr = size_incr;
slmdb->hard_limit = hard_limit;
slmdb->env = env;
slmdb->dbi = dbi;
slmdb->db_fd = db_fd;
slmdb->cursor = 0;
slmdb->api_retry_count = 0;
slmdb->bulk_retry_count = 0;
slmdb->api_retry_limit = SLMDB_DEF_API_RETRY_LIMIT;
slmdb->bulk_retry_limit = SLMDB_DEF_BULK_RETRY_LIMIT;
slmdb->longjmp_fn = 0;
slmdb->notify_fn = 0;
slmdb->cb_context = 0;
slmdb->txn = txn;
if ((status = slmdb_prepare(slmdb)) != 0)
mdb_env_close(env);
return (status);
}

87
postfix/src/util/slmdb.h Normal file
View File

@ -0,0 +1,87 @@
#ifndef _SLMDB_H_INCLUDED_
#define _SLMDB_H_INCLUDED_
/*++
/* NAME
/* slmdb 3h
/* SUMMARY
/* LMDB API wrapper
/* SYNOPSIS
/* #include <slmdb.h>
/* DESCRIPTION
/* .nf
/*
* System library.
*/
#include <stdarg.h>
#include <setjmp.h>
#ifdef PATH_LMDB_H
#include PATH_LMDB_H
#else
#include <lmdb.h>
#endif
/*
* External interface.
*/
#ifdef NO_SIGSETJMP
#define SLMDB_JMP_BUF jmp_buf
#else
#define SLMDB_JMP_BUF sigjmp_buf
#endif
typedef struct {
int open_flags; /* open() flags */
int lmdb_flags; /* LMDB-specific flags */
int bulk_mode; /* bulk-mode flag */
size_t curr_limit; /* database soft size limit */
int size_incr; /* database growth factor */
size_t hard_limit; /* database hard size limit */
MDB_env *env; /* database environment */
MDB_dbi dbi; /* database instance */
MDB_txn *txn; /* bulk transaction */
int db_fd; /* database file handle */
MDB_cursor *cursor; /* iterator */
void (*longjmp_fn) (void *, int); /* exception handling */
void (*notify_fn) (void *, int,...); /* workaround notification */
void *cb_context; /* call-back context */
int api_retry_count; /* slmdb(3) API call retry count */
int bulk_retry_count; /* bulk_mode retry count */
int api_retry_limit; /* slmdb(3) API call retry limit */
int bulk_retry_limit; /* bulk_mode retry limit */
} SLMDB;
extern int slmdb_open(SLMDB *, const char *, int, int, int, size_t, int, size_t);
extern int slmdb_get(SLMDB *, MDB_val *, MDB_val *);
extern int slmdb_put(SLMDB *, MDB_val *, MDB_val *, int);
extern int slmdb_del(SLMDB *, MDB_val *);
extern int slmdb_cursor_get(SLMDB *, MDB_val *, MDB_val *, MDB_cursor_op);
extern int slmdb_control(SLMDB *, int, ...);
extern int slmdb_close(SLMDB *);
#define slmdb_fd(slmdb) ((slmdb)->db_fd)
#define slmdb_curr_limit(slmdb) ((slmdb)->curr_limit)
#define SLMDB_CTL_END 0
#define SLMDB_CTL_LONGJMP_FN 1 /* exception handling */
#define SLMDB_CTL_NOTIFY_FN 2 /* debug logging function */
#define SLMDB_CTL_CONTEXT 3 /* exception/debug logging context */
#define SLMDB_CTL_HARD_LIMIT 4 /* hard database size limit */
#define SLMDB_CTL_API_RETRY_LIMIT 5 /* per slmdb(3) API call */
#define SLMDB_CTL_BULK_RETRY_LIMIT 6 /* per bulk update */
typedef void (*SLMDB_NOTIFY_FN)(void *, int, ...);
typedef void (*SLMDB_LONGJMP_FN)(void *, int);
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Howard Chu
/* Symas Corporation
/*--*/
#endif