sapling/eden/integration/mmap_test.py
Xavier Deguillard a29d465ee8 fs: fix license header
Summary:
With Facebook having been renamed Meta Platforms, we need to change the license
headers.

Reviewed By: fanzeyi

Differential Revision: D33407812

fbshipit-source-id: b11bfbbf13a48873f0cea75f212cc7b07a68fb2e
2022-01-04 15:00:07 -08:00

117 lines
4.2 KiB
Python

#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.
import ctypes
import mmap
import os
import platform
import subprocess
from ctypes import c_int, c_size_t, c_ssize_t, c_void_p
from .lib import testcase
# python's mmap does not allow mapping larger than the file's size, so call
# it directly with ctypes.
#
# We have to ignore type errors on the next line until we update to a version
# of typeshed that includes https://github.com/python/typeshed/pull/3945
libc = None
if platform.system() != "Windows":
libc = ctypes.CDLL(None)
c_off_t = c_ssize_t
libc.mmap.argtypes = [c_void_p, c_size_t, c_int, c_int, c_off_t]
libc.mmap.restype = ctypes.POINTER(ctypes.c_byte)
libc.munmap.restype = c_void_p
libc.munmap.argtypes = [c_void_p, c_size_t]
@testcase.eden_repo_test
# pyre-ignore[13]: T62487924
class MmapTest(testcase.EdenRepoTest):
contents = "abcdef"
filename: str
def populate_repo(self) -> None:
self.repo.write_file("filename", self.contents)
self.repo.commit("Initial commit.")
self.filename = os.path.join(self.mount, "filename")
def test_mmap_in_backed_file_is_null_terminated(self) -> None:
fd = os.open(self.filename, os.O_RDONLY)
try:
size = os.fstat(fd).st_size
self.assertEqual(len(self.contents), size)
map_size = (size + 4095) // 4096 * 4096
self.assertNotEqual(size, map_size)
m = libc.mmap(None, map_size, mmap.PROT_READ, mmap.MAP_PRIVATE, fd, 0)
try:
# assert the additional mapped bytes are null, per `man 2 mmap`
for i in range(size, map_size):
self.assertEqual(0, m[i])
finally:
libc.munmap(m, map_size)
finally:
os.close(fd)
def test_mmap_is_null_terminated_after_truncate_and_write_to_overlay(self) -> None:
# WARNING: This test is very fiddly.
# The bug is that if a file in Eden is opened with O_TRUNC followed by
# a series of writes, then mmap of that file with a size larger than the
# file (but still within the trailing page) does not zero the trailing
# bytes. Clang relies on this mmap behavior to enforce that the buffer
# is null-terminated. Since the buffer ends up not being null-
# terminated, Clang segfaults.
#
# It seems like this is a kernel or FUSE bug more than an Eden bug,
# but we should verify nonetheless that it does not occur.
# If this test uses the same file committed in populate_repo, the bug
# does not reproduce.
filename = os.path.join(self.mount, "filename2")
# Write to the file from another process. if this process writes the
# file, the bug is not reproduced.
subprocess.check_call(
["dd", "if=/dev/urandom", "of=" + filename, "bs=4096", "count=6"]
)
# A few pages, with data slightly beyond a page boundary.
new_contents = b"abcd" * 3072 + b"abcdef"
new_size = len(new_contents)
# Write to the file with another process. if this process writes the
# file, the bug is not reproduced.
with subprocess.Popen(
["dd", "of=" + filename, "bs=512"], stdin=subprocess.PIPE
) as p:
# pyre-fixme[16]: Optional type has no attribute `write`.
p.stdin.write(new_contents)
fd = os.open(filename, os.O_RDONLY)
try:
size = os.fstat(fd).st_size
self.assertEqual(new_size, size)
# Map all the way up to a page boundary.
map_size = (size + 4095) // 4096 * 4096
self.assertNotEqual(size, map_size)
m = libc.mmap(None, map_size, mmap.PROT_READ, mmap.MAP_PRIVATE, fd, 0)
try:
# Assert the additional mapped bytes are null, per `man 2 mmap`
for i in range(size, map_size):
self.assertEqual(0, m[i])
finally:
libc.munmap(m, map_size)
finally:
os.close(fd)