Switch mbox locking from flock(2) to posix lockf(2)

flock() locks aren't portable; lockf() locks are.
This commit is contained in:
Nikolaus Schulz 2009-03-03 04:33:09 +01:00
parent d726589414
commit d706409c59
5 changed files with 24 additions and 35 deletions

6
TODO
View File

@ -6,12 +6,6 @@ Gracefully close IMAP connection upon unexptected error (currently archivemail
just terminates). just terminates).
LOCKING & Co: LOCKING & Co:
* switch to or add fcntl() locking; when combining with flock, be careful not to
break on Solaris/FreeBSD, where flock() is just fcntl() so using both would
block.
* Ensure that we don't accidently lose fcntl locks when closing some file
descriptor; this applies even for flock, since again, it might be emulated
with fcntl
* Block signals while writing changed mailbox back. Also, we probably shouldn't * Block signals while writing changed mailbox back. Also, we probably shouldn't
use shutil.copy2() for this; at least we have to handle symlinked targets in a use shutil.copy2() for this; at least we have to handle symlinked targets in a
sane way, see Debian bug #349068. mbox_sync_mailbox() in the mutt code might sane way, see Debian bug #349068. mbox_sync_mailbox() in the mutt code might

View File

@ -3,7 +3,7 @@
.\" <http://shell.ipoline.com/~elmert/comp/docbook2X/> .\" <http://shell.ipoline.com/~elmert/comp/docbook2X/>
.\" Please send any bug reports, improvements, comments, patches, .\" Please send any bug reports, improvements, comments, patches,
.\" etc. to Steve Cheng <steve@ggi-project.org>. .\" etc. to Steve Cheng <steve@ggi-project.org>.
.TH "ARCHIVEMAIL" "1" "08 April 2008" "SP" "" .TH "ARCHIVEMAIL" "1" "03 March 2009" "SP" ""
.SH NAME .SH NAME
archivemail \- archive and compress your old email archivemail \- archive and compress your old email
@ -258,7 +258,7 @@ Display brief summary information about how to run \fBarchivemail\fR\&.
When reading an \fBmbox\fR-format mailbox, \fBarchivemail\fR will When reading an \fBmbox\fR-format mailbox, \fBarchivemail\fR will
create a lockfile with the extension \fI\&.lock\fR so that create a lockfile with the extension \fI\&.lock\fR so that
procmail will not deliver to the mailbox while it is being processed. It will procmail will not deliver to the mailbox while it is being processed. It will
also create an advisory lock on the mailbox using \fBflock\fR(2)\&. also create an advisory lock on the mailbox using \fBlockf\fR(2)\&.
\fBarchivemail\fR will also complain and abort if a 3rd-party modifies the \fBarchivemail\fR will also complain and abort if a 3rd-party modifies the
mailbox while it is being read. mailbox while it is being read.
.PP .PP

View File

@ -351,15 +351,15 @@ class Mbox(mailbox.UnixMailbox):
os.utime(self.mbox_file_name, (self.original_atime, \ os.utime(self.mbox_file_name, (self.original_atime, \
self.original_mtime)) self.original_mtime))
def exclusive_lock(self): def posix_lock(self):
"""Set an advisory lock on the 'mbox' mailbox""" """Set an exclusive posix lock on the 'mbox' mailbox"""
vprint("obtaining exclusive lock on file '%s'" % self.mbox_file_name) vprint("obtaining exclusive lock on file '%s'" % self.mbox_file_name)
fcntl.flock(self.mbox_file.fileno(), fcntl.LOCK_EX) fcntl.lockf(self.mbox_file, fcntl.LOCK_EX|fcntl.LOCK_NB)
def exclusive_unlock(self): def posix_unlock(self):
"""Unset any advisory lock on the 'mbox' mailbox""" """Unset any posix lock on the 'mbox' mailbox"""
vprint("dropping exclusive lock on file '%s'" % self.mbox_file_name) vprint("dropping posix lock on file '%s'" % self.mbox_file_name)
fcntl.flock(self.mbox_file.fileno(), fcntl.LOCK_UN) fcntl.lockf(self.mbox_file, fcntl.LOCK_UN)
def dotlock_lock(self): def dotlock_lock(self):
"""Create a dotlock file on the 'mbox' mailbox""" """Create a dotlock file on the 'mbox' mailbox"""
@ -1133,7 +1133,7 @@ def _archive_mbox(mailbox_name, final_archive_name):
archive = ArchiveMbox(final_archive_name) archive = ArchiveMbox(final_archive_name)
original.dotlock_lock() original.dotlock_lock()
original.exclusive_lock() original.posix_lock()
msg = original.next() msg = original.next()
if not msg and (original.starting_size > 0): if not msg and (original.starting_size > 0):
user_error("'%s' is not a valid mbox-format mailbox" % mailbox_name) user_error("'%s' is not a valid mbox-format mailbox" % mailbox_name)
@ -1164,7 +1164,7 @@ def _archive_mbox(mailbox_name, final_archive_name):
archive.finalise() archive.finalise()
if retain: if retain:
retain.finalise() retain.finalise()
original.exclusive_unlock() original.posix_unlock()
original.close() original.close()
original.reset_timestamps() original.reset_timestamps()
original.dotlock_unlock() original.dotlock_unlock()

View File

@ -1,7 +1,7 @@
<!DOCTYPE RefEntry PUBLIC "-//Davenport//DTD DocBook V3.0//EN" [ <!DOCTYPE RefEntry PUBLIC "-//Davenport//DTD DocBook V3.0//EN" [
<!ENTITY flock "<CiteRefEntry> <!ENTITY lockf "<CiteRefEntry>
<RefEntryTitle><Command/flock/</RefEntryTitle> <RefEntryTitle><Command/lockf/</RefEntryTitle>
<ManVolNum/2/</CiteRefEntry>"> <ManVolNum/2/</CiteRefEntry>">
<!ENTITY gzip "<CiteRefEntry> <!ENTITY gzip "<CiteRefEntry>
@ -410,7 +410,7 @@ Display brief summary information about how to run <Command/archivemail/.
When reading an <application/mbox/-format mailbox, <command/archivemail/ will When reading an <application/mbox/-format mailbox, <command/archivemail/ will
create a lockfile with the extension <filename>.lock</filename> so that create a lockfile with the extension <filename>.lock</filename> so that
procmail will not deliver to the mailbox while it is being processed. It will procmail will not deliver to the mailbox while it is being processed. It will
also create an advisory lock on the mailbox using &flock;. also create an advisory lock on the mailbox using &lockf;.
<command/archivemail/ will also complain and abort if a 3rd-party modifies the <command/archivemail/ will also complain and abort if a 3rd-party modifies the
mailbox while it is being read. mailbox while it is being read.
</Para> </Para>

View File

@ -120,20 +120,15 @@ class TestMboxDotlock(TestCaseInTempdir):
self.mbox.dotlock_unlock() self.mbox.dotlock_unlock()
assert(not os.path.isfile(lock)) assert(not os.path.isfile(lock))
class TestMboxExclusiveLock(TestCaseInTempdir): class TestMboxPosixLock(TestCaseInTempdir):
def setUp(self): def setUp(self):
super(TestMboxExclusiveLock, self).setUp() super(TestMboxPosixLock, self).setUp()
self.mbox_name = make_mbox() self.mbox_name = make_mbox()
self.mbox = archivemail.Mbox(self.mbox_name) self.mbox = archivemail.Mbox(self.mbox_name)
def testExclusiveLock(self): def testPosixLock(self):
"""exclusive_lock/unlock should create/delete an advisory lock""" """posix_lock/unlock should create/delete an advisory lock"""
# We're using flock(2) locks; these aren't completely portable, and on
# some systems (e.g. Solaris) they may be emulated with fcntl(2) locks,
# which have pretty different semantics. We could test real flock
# locks within this process, but that doesn't work for fcntl locks.
#
# The following code snippet heavily lends from the Python 2.5 mailbox # The following code snippet heavily lends from the Python 2.5 mailbox
# unittest. # unittest.
# BEGIN robbery: # BEGIN robbery:
@ -143,29 +138,29 @@ class TestMboxExclusiveLock(TestCaseInTempdir):
pid = os.fork() pid = os.fork()
if pid == 0: if pid == 0:
# In the child, lock the mailbox. # In the child, lock the mailbox.
self.mbox.exclusive_lock() self.mbox.posix_lock()
time.sleep(2) time.sleep(2)
self.mbox.exclusive_unlock() self.mbox.posix_unlock()
os._exit(0) os._exit(0)
# In the parent, sleep a bit to give the child time to acquire # In the parent, sleep a bit to give the child time to acquire
# the lock. # the lock.
time.sleep(0.5) time.sleep(0.5)
# The parent's file self.mbox.mbox_file shares flock locks with the # The parent's file self.mbox.mbox_file shares fcntl locks with the
# duplicated FD in the child; reopen it so we get a different file # duplicated FD in the child; reopen it so we get a different file
# table entry. # table entry.
file = open(self.mbox_name, "r+") file = open(self.mbox_name, "r+")
lock_nb = fcntl.LOCK_EX | fcntl.LOCK_NB lock_nb = fcntl.LOCK_EX | fcntl.LOCK_NB
fd = file.fileno() fd = file.fileno()
try: try:
self.assertRaises(IOError, fcntl.flock, fd, lock_nb) self.assertRaises(IOError, fcntl.lockf, fd, lock_nb)
finally: finally:
# Wait for child to exit. Locking should now succeed. # Wait for child to exit. Locking should now succeed.
exited_pid, status = os.waitpid(pid, 0) exited_pid, status = os.waitpid(pid, 0)
fcntl.flock(fd, lock_nb) fcntl.lockf(fd, lock_nb)
fcntl.flock(fd, fcntl.LOCK_UN) fcntl.lockf(fd, fcntl.LOCK_UN)
# END robbery # END robbery