gvisor/images/basic/integrationtest/test_copy_up.c

89 lines
2.8 KiB
C
Raw Normal View History

Enable overlayfs_stale_read by default for runsc. Linux 4.18 and later make reads and writes coherent between pre-copy-up and post-copy-up FDs representing the same file on an overlay filesystem. However, memory mappings remain incoherent: - Documentation/filesystems/overlayfs.rst, "Non-standard behavior": "If a file residing on a lower layer is opened for read-only and then memory mapped with MAP_SHARED, then subsequent changes to the file are not reflected in the memory mapping." - fs/overlay/file.c:ovl_mmap() passes through to the underlying FD without any management of coherence in the overlay. - Experimentally on Linux 5.2: ``` $ cat mmap_cat_page.c #include <err.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <unistd.h> int main(int argc, char **argv) { if (argc < 2) { errx(1, "syntax: %s [FILE]", argv[0]); } const int fd = open(argv[1], O_RDONLY); if (fd < 0) { err(1, "open(%s)", argv[1]); } const size_t page_size = sysconf(_SC_PAGE_SIZE); void* page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); if (page == MAP_FAILED) { err(1, "mmap"); } for (;;) { write(1, page, strnlen(page, page_size)); if (getc(stdin) == EOF) { break; } } return 0; } $ gcc -O2 -o mmap_cat_page mmap_cat_page.c $ mkdir lowerdir upperdir workdir overlaydir $ echo old > lowerdir/file $ sudo mount -t overlay -o "lowerdir=lowerdir,upperdir=upperdir,workdir=workdir" none overlaydir $ ./mmap_cat_page overlaydir/file old ^Z [1]+ Stopped ./mmap_cat_page overlaydir/file $ echo new > overlaydir/file $ cat overlaydir/file new $ fg ./mmap_cat_page overlaydir/file old ``` Therefore, while the VFS1 gofer client's behavior of reopening read FDs is only necessary pre-4.18, replacing existing memory mappings (in both sentry and application address spaces) with mappings of the new FD is required regardless of kernel version, and this latter behavior is common to both VFS1 and VFS2. Re-document accordingly, and change the runsc flag to enabled by default. New test: - Before this CL: https://source.cloud.google.com/results/invocations/5b222d2c-e918-4bae-afc4-407f5bac509b - After this CL: https://source.cloud.google.com/results/invocations/f28c747e-d89c-4d8c-a461-602b33e71aab PiperOrigin-RevId: 311361267
2020-05-13 17:51:50 +00:00
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char** argv) {
const char kTestFilePath[] = "copy_up_testfile.txt";
Enable overlayfs_stale_read by default for runsc. Linux 4.18 and later make reads and writes coherent between pre-copy-up and post-copy-up FDs representing the same file on an overlay filesystem. However, memory mappings remain incoherent: - Documentation/filesystems/overlayfs.rst, "Non-standard behavior": "If a file residing on a lower layer is opened for read-only and then memory mapped with MAP_SHARED, then subsequent changes to the file are not reflected in the memory mapping." - fs/overlay/file.c:ovl_mmap() passes through to the underlying FD without any management of coherence in the overlay. - Experimentally on Linux 5.2: ``` $ cat mmap_cat_page.c #include <err.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <unistd.h> int main(int argc, char **argv) { if (argc < 2) { errx(1, "syntax: %s [FILE]", argv[0]); } const int fd = open(argv[1], O_RDONLY); if (fd < 0) { err(1, "open(%s)", argv[1]); } const size_t page_size = sysconf(_SC_PAGE_SIZE); void* page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); if (page == MAP_FAILED) { err(1, "mmap"); } for (;;) { write(1, page, strnlen(page, page_size)); if (getc(stdin) == EOF) { break; } } return 0; } $ gcc -O2 -o mmap_cat_page mmap_cat_page.c $ mkdir lowerdir upperdir workdir overlaydir $ echo old > lowerdir/file $ sudo mount -t overlay -o "lowerdir=lowerdir,upperdir=upperdir,workdir=workdir" none overlaydir $ ./mmap_cat_page overlaydir/file old ^Z [1]+ Stopped ./mmap_cat_page overlaydir/file $ echo new > overlaydir/file $ cat overlaydir/file new $ fg ./mmap_cat_page overlaydir/file old ``` Therefore, while the VFS1 gofer client's behavior of reopening read FDs is only necessary pre-4.18, replacing existing memory mappings (in both sentry and application address spaces) with mappings of the new FD is required regardless of kernel version, and this latter behavior is common to both VFS1 and VFS2. Re-document accordingly, and change the runsc flag to enabled by default. New test: - Before this CL: https://source.cloud.google.com/results/invocations/5b222d2c-e918-4bae-afc4-407f5bac509b - After this CL: https://source.cloud.google.com/results/invocations/f28c747e-d89c-4d8c-a461-602b33e71aab PiperOrigin-RevId: 311361267
2020-05-13 17:51:50 +00:00
const char kOldFileData[] = "old data\n";
const char kNewFileData[] = "new data\n";
const size_t kPageSize = sysconf(_SC_PAGE_SIZE);
// Open a file that already exists in a host overlayfs lower layer.
const int fd_rdonly = open(kTestFilePath, O_RDONLY);
if (fd_rdonly < 0) {
err(1, "open(%s, O_RDONLY)", kTestFilePath);
}
// Check that the file's initial contents are what we expect when read via
// syscall.
char oldbuf[sizeof(kOldFileData)] = {};
ssize_t n = pread(fd_rdonly, oldbuf, sizeof(oldbuf), 0);
if (n < 0) {
err(1, "initial pread");
}
if (n != strlen(kOldFileData)) {
errx(1, "short initial pread (%ld/%lu bytes)", n, strlen(kOldFileData));
}
if (strcmp(oldbuf, kOldFileData) != 0) {
errx(1, "initial pread returned wrong data: %s", oldbuf);
}
// Check that the file's initial contents are what we expect when read via
// memory mapping.
void* page = mmap(NULL, kPageSize, PROT_READ, MAP_SHARED, fd_rdonly, 0);
if (page == MAP_FAILED) {
err(1, "mmap");
}
if (strcmp(page, kOldFileData) != 0) {
errx(1, "mapping contains wrong initial data: %s", (const char*)page);
}
// Open the same file writably, causing host overlayfs to copy it up, and
// replace its contents.
const int fd_rdwr = open(kTestFilePath, O_RDWR);
if (fd_rdwr < 0) {
err(1, "open(%s, O_RDWR)", kTestFilePath);
}
n = write(fd_rdwr, kNewFileData, strlen(kNewFileData));
if (n < 0) {
err(1, "write");
}
if (n != strlen(kNewFileData)) {
errx(1, "short write (%ld/%lu bytes)", n, strlen(kNewFileData));
}
if (ftruncate(fd_rdwr, strlen(kNewFileData)) < 0) {
err(1, "truncate");
}
int failed = 0;
// Check that syscalls on the old FD return updated contents. (Before Linux
// 4.18, this requires that runsc use a post-copy-up FD to service the read.)
char newbuf[sizeof(kNewFileData)] = {};
n = pread(fd_rdonly, newbuf, sizeof(newbuf), 0);
if (n < 0) {
err(1, "final pread");
}
if (n != strlen(kNewFileData)) {
warnx("short final pread (%ld/%lu bytes)", n, strlen(kNewFileData));
failed = 1;
} else if (strcmp(newbuf, kNewFileData) != 0) {
warnx("final pread returned wrong data: %s", newbuf);
failed = 1;
}
// Check that the memory mapping of the old FD has been updated. (Linux
// overlayfs does not do this, so regardless of kernel version this requires
// that runsc replace existing memory mappings with mappings of a
// post-copy-up FD.)
if (strcmp(page, kNewFileData) != 0) {
warnx("mapping contains wrong final data: %s", (const char*)page);
failed = 1;
}
return failed;
}