Fix msync() flags on FreeBSD

This commit is contained in:
Justine Tunney 2023-08-21 04:15:05 -07:00
parent ebf784d4f5
commit fffcd98b0e
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
7 changed files with 95 additions and 15 deletions

View file

@ -25,6 +25,7 @@
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Synchronize memory mapping changes to disk.
@ -37,20 +38,61 @@
* @return 0 on success or -1 w/ errno
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINTR if we needed to block and a signal was delivered instead
* @raise EINVAL if `MS_SYNC` and `MS_ASYNC` were both specified
* @raise EINVAL if unknown `flags` were passed
* @cancellationpoint
*/
int msync(void *addr, size_t size, int flags) {
int rc;
BEGIN_CANCELLATION_POINT;
unassert(((flags & MS_SYNC) ^ (flags & MS_ASYNC)) || !(MS_SYNC && MS_ASYNC));
if (!IsWindows()) {
rc = sys_msync(addr, size, flags);
} else {
rc = sys_msync_nt(addr, size, flags);
if ((flags & ~(MS_SYNC | MS_ASYNC | MS_INVALIDATE)) ||
(flags & (MS_SYNC | MS_ASYNC)) == (MS_SYNC | MS_ASYNC)) {
rc = einval();
goto Finished;
}
// According to POSIX, either MS_SYNC or MS_ASYNC must be specified
// in flags, and indeed failure to include one of these flags will
// cause msync() to fail on some systems. However, Linux permits a
// call to msync() that specifies neither of these flags, with
// semantics that are (currently) equivalent to specifying MS_ASYNC.
// ──Quoth msync(2) of Linux Programmer's Manual
int sysflags = flags;
sysflags = flags;
if (flags & MS_ASYNC) {
sysflags = MS_ASYNC;
} else if (flags & MS_SYNC) {
sysflags = MS_SYNC;
} else {
sysflags = MS_ASYNC;
}
if (flags & MS_INVALIDATE) {
sysflags |= MS_INVALIDATE;
}
// FreeBSD's manual says "The flags argument was both MS_ASYNC and
// MS_INVALIDATE. Only one of these flags is allowed." which makes
// following the POSIX recommendation somewhat difficult.
if (IsFreebsd()) {
if (sysflags == (MS_ASYNC | MS_INVALIDATE)) {
sysflags = MS_INVALIDATE;
}
}
// FreeBSD specifies MS_SYNC as 0 so we shift the Cosmo constants
if (IsFreebsd()) {
sysflags >>= 1;
}
BEGIN_CANCELLATION_POINT;
if (!IsWindows()) {
rc = sys_msync(addr, size, sysflags);
} else {
rc = sys_msync_nt(addr, size, sysflags);
}
END_CANCELLATION_POINT;
Finished:
STRACE("msync(%p, %'zu, %#x) → %d% m", addr, size, flags, rc);
return rc;
}

View file

@ -959,9 +959,9 @@ syscon pf PF_X25 9 9 0 0 0 0 0 0
# msync() flags
#
# group name GNU/Systemd GNU/Systemd (Aarch64) XNU's Not UNIX! MacOS (Arm64) FreeBSD OpenBSD NetBSD The New Technology Commentary
syscon ms MS_SYNC 4 4 16 16 0 2 4 4 # faked nt
syscon ms MS_ASYNC 1 1 1 1 1 1 1 1 # consensus (faked nt)
syscon ms MS_INVALIDATE 2 2 2 2 2 4 2 0
syscon ms MS_SYNC 4 4 16 16 1 2 4 4 # faked nt; actually 0 on freebsd
syscon ms MS_ASYNC 1 1 1 1 2 1 1 1 # faked nt; actually 1 on freebsd
syscon ms MS_INVALIDATE 2 2 2 2 4 4 2 0 # actually 2 on freebsd
# statfs() flags
#

View file

@ -1,2 +1,2 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon ms,MS_ASYNC,1,1,1,1,1,1,1,1
.syscon ms,MS_ASYNC,1,1,1,1,2,1,1,1

View file

@ -1,2 +1,2 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon ms,MS_INVALIDATE,2,2,2,2,2,4,2,0
.syscon ms,MS_INVALIDATE,2,2,2,2,4,4,2,0

View file

@ -1,2 +1,2 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon ms,MS_SYNC,4,4,16,16,0,2,4,4
.syscon ms,MS_SYNC,4,4,16,16,1,2,4,4

View file

@ -7,11 +7,10 @@ extern const int MS_SYNC;
extern const int MS_ASYNC;
extern const int MS_INVALIDATE;
#define MS_ASYNC 1
#define MS_SYNC MS_SYNC
#define MS_ASYNC MS_ASYNC
#define MS_INVALIDATE MS_INVALIDATE
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_MSYNC_H_ */

View file

@ -16,13 +16,17 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/sysv/consts/msync.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/intrin/kprintf.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/msync.h"
#include "libc/sysv/consts/prot.h"
#include "libc/testlib/testlib.h"
// msync() is a difficult function to test because it doesn't do much
TEST(msync, changeFileMappingAndExit) {
int ws;
char byte;
@ -33,6 +37,7 @@ TEST(msync, changeFileMappingAndExit) {
(map = mmap(0, 4, PROT_READ | PROT_WRITE, MAP_SHARED, 3, 0)));
if (!fork()) {
map[0] = 1;
// commenting this out will fail on openbsd
ASSERT_SYS(0, 0, msync(map, 4, MS_SYNC));
_Exit(123);
}
@ -45,3 +50,37 @@ TEST(msync, changeFileMappingAndExit) {
ASSERT_SYS(0, 0, munmap(map, 4));
ASSERT_SYS(0, 0, close(3));
}
TEST(msync, changeFileMappingAndWakeSpinLockWaiter) {
int ws;
char byte;
char *map;
atomic_char *sem = _mapshared(1);
ASSERT_SYS(0, 3, tmpfd());
ASSERT_SYS(0, 0, ftruncate(3, 1));
ASSERT_NE(MAP_FAILED,
(map = mmap(0, 1, PROT_READ | PROT_WRITE, MAP_SHARED, 3, 0)));
if (!fork()) {
// wait for other process to enter spin sem
while (*sem == 0) donothing;
// change the file mapping
map[0] = 1;
// openbsd fails with this line commented out
ASSERT_SYS(0, 0, msync(map, 1, MS_ASYNC));
// immediately wake other process
*sem = 2;
_Exit(123);
}
*sem = 1;
while (*sem == 1) donothing;
ASSERT_EQ(1, map[0]);
ASSERT_SYS(0, 1, pread(3, &byte, 1, 0));
ASSERT_EQ(1, byte);
ASSERT_EQ(1, map[0]);
ASSERT_NE(-1, wait(&ws));
ASSERT_TRUE(WIFEXITED(ws));
ASSERT_EQ(123, WEXITSTATUS(ws));
ASSERT_SYS(0, 0, munmap(map, 1));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(0, 0, munmap(sem, 1));
}