Merge branch 'feature/flocks' into 'master'

Use flocks to lock the database instead of purely relying on file existence.

See merge request pacman/pacman!171
This commit is contained in:
Stephan Brunner 2025-06-22 01:16:23 +02:00
commit 5f3df8a3d7

View file

@ -27,6 +27,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <syslog.h> #include <syslog.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h> #include <fcntl.h>
/* libalpm */ /* libalpm */
@ -118,6 +119,7 @@ void _alpm_handle_free(alpm_handle_t *handle)
int _alpm_handle_lock(alpm_handle_t *handle) int _alpm_handle_lock(alpm_handle_t *handle)
{ {
char *dir, *ptr; char *dir, *ptr;
int ret;
ASSERT(handle->lockfile != NULL, return -1); ASSERT(handle->lockfile != NULL, return -1);
ASSERT(handle->lockfd < 0, return 0); ASSERT(handle->lockfd < 0, return 0);
@ -135,22 +137,73 @@ int _alpm_handle_lock(alpm_handle_t *handle)
FREE(dir); FREE(dir);
do { do {
handle->lockfd = open(handle->lockfile, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); handle->lockfd = open(handle->lockfile, O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
} while(handle->lockfd == -1 && errno == EINTR); } while(handle->lockfd == -1 && errno == EINTR);
return (handle->lockfd >= 0 ? 0 : -1); if (handle->lockfd == -1) {
return -1;
}
do {
ret = flock(handle->lockfd, LOCK_EX | LOCK_NB);
} while(ret != 0 && errno == EINTR);
if(ret != 0) {
/* Do not unlink here as someone else seems to hold the lock!
* We do not check for the exact error, as many errors may occur
* and we don't want to unlock the file by deleting it without
* being absolutely sure no one else has locked it.
*/
close(handle->lockfd);
handle->lockfd = -1;
}
return ret == 0 ? 0 : -1;
} }
int SYMEXPORT alpm_unlock(alpm_handle_t *handle) int SYMEXPORT alpm_unlock(alpm_handle_t *handle)
{ {
int ret_flock, ret_unlink;
ASSERT(handle != NULL, return -1); ASSERT(handle != NULL, return -1);
ASSERT(handle->lockfile != NULL, return 0); ASSERT(handle->lockfile != NULL, return 0);
ASSERT(handle->lockfd >= 0, return 0); ASSERT(handle->lockfd >= 0, return 0);
/* We can't get rid of the unlink because of backwards compatibility.
* And it would make the bash script very convoluted when implementing flocks.
* But first unlink, then unlock, so that no race condition can occur.
*
* Unlock-then-Unlink:
* 1. TX1: flock(fd1, LOCK_UN)
* 2. TX2: fd1 = open(fn)
* 3. TX1: unlink(fd1)
* 4. TX2: flock(fd1, LOCK_EX)
* ^ Succeeds, TX2 should be the exclusive owner now
* 5. TX3: fd2 = open(fn)/flock(fd2, LOCK_EX)
* ^ Would succeed too, as the file was first unlinked and is now
* opened as a new file. We have now two exclusive locks.
*
* Unlink-then-Unlock:
* 1. TX1: unlink(fd1)
* 2. TX2: fd2 = open(fn)
* ^ Creates a new file
* 3. TX1: flock(fd1, LOCK_UN)
* 4. TX2: flock(fd2, LOCK_EX)
* 5. TX3: open(fn2)/flock(fd2, LOCK_EX)
* ^ Would fail, as the file is now shared between the two TX.
*
* Basically, the unlink() is the actual unlock here, as it was without flock()
* But now, we can handle the case when the lock file already existed, too.
* Check for both unlink and flock errors, because the flock-call unlocks
* the file even without the unlink call.
*/
ret_unlink = unlink(handle->lockfile);
ret_flock = flock(handle->lockfd, LOCK_UN);
close(handle->lockfd); close(handle->lockfd);
handle->lockfd = -1; handle->lockfd = -1;
if(unlink(handle->lockfile) != 0) { if(ret_unlink != 0 || ret_flock != 0) {
RET_ERR_ASYNC_SAFE(handle, ALPM_ERR_SYSTEM, -1); RET_ERR_ASYNC_SAFE(handle, ALPM_ERR_SYSTEM, -1);
} else { } else {
return 0; return 0;