The Pedigree Project 0.1
vmsplice.c
1#define _GNU_SOURCE
2#include <poll.h>
3#include <string.h>
4#include <unistd.h>
5
6#include "contract.h"
7#include <sys/inotify.h>
8#include <sys/mman.h>
9#include <sys/stat.h>
10
11static int copying(void) {
12 int failed = 0, p[2] = {-1, -1}, file = -1;
13 const size_t page = sysconf(_SC_PAGESIZE);
14 char* source = MAP_FAILED;
15 char output[8] = {0};
16 CHECK(!pipe(p));
17 source = mmap(NULL, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
18 CHECK(source != MAP_FAILED);
19 memcpy(source, "abcdefgh", 8);
20 struct iovec vectors[] = {{source, 3}, {source + 3, 5}};
21 CHECK(vmsplice(p[1], vectors, 2, SPLICE_F_MORE | SPLICE_F_MOVE) == 8);
22 memset(source, 'z', 8);
23 CHECK(!munmap(source, 2 * page));
24 source = MAP_FAILED;
25 struct iovec destinations[] = {{output, 3}, {output + 3, 5}};
26 CHECK(vmsplice(p[0], destinations, 2, SPLICE_F_GIFT) == 8 && !memcmp(output, "abcdefgh", 8));
27 CHECK(vmsplice(p[0], destinations, 2, SPLICE_F_NONBLOCK) == -1 && errno == EAGAIN);
28 CHECK(vmsplice(p[1], NULL, 0, 0) == 0);
29 CHECK((file = pt_file(0, 0)) >= 0);
30 CHECK(vmsplice(file, NULL, 0, 0) == 0);
31 CHECK(vmsplice(file, destinations, 2, 0) == -1 && errno == EBADF);
32 CHECK(vmsplice(-1, NULL, 0, 0) == -1 && errno == EBADF);
33 CHECK(!close(p[1]));
34 p[1] = -1;
35 CHECK(vmsplice(p[0], destinations, 2, 0) == 0);
36out:
37 if (file >= 0)
38 close(file);
39 if (source != MAP_FAILED)
40 munmap(source, 2 * page);
41 if (p[0] >= 0)
42 close(p[0]);
43 if (p[1] >= 0)
44 close(p[1]);
45 return failed;
46}
47static int faults_and_limits(void) {
48 int failed = 0, p[2] = {-1, -1};
49 const size_t page = sysconf(_SC_PAGESIZE);
50 void* denied = MAP_FAILED;
51 char first[3] = {0}, tail[5] = {0}, large[PT_CAPACITY + 7];
52 CHECK(!pipe(p));
53 denied = mmap(NULL, page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
54 CHECK(denied != MAP_FAILED);
55 struct iovec bad = {denied, 8};
56 CHECK(vmsplice(p[1], &bad, 1, 0) == -1 && errno == EFAULT);
57 CHECK(vmsplice(p[1], denied, 1, 0) == -1 && errno == EFAULT);
58 CHECK(vmsplice(p[1], denied, 1025, 0) == -1 && errno == EINVAL);
59 struct iovec overflow = {first, SIZE_MAX};
60 CHECK(vmsplice(p[1], &overflow, 1, 0) == -1 && errno == EINVAL);
61 struct iovec incoming[] = {{"abc", 3}, {denied, 5}};
62 CHECK(vmsplice(p[1], incoming, 2, 0) == 3);
63 CHECK(read(p[0], first, 3) == 3 && !memcmp(first, "abc", 3));
64 CHECK(write(p[1], "abcdefgh", 8) == 8);
65 CHECK(vmsplice(p[0], &bad, 1, 0) == -1 && errno == EFAULT);
66 struct iovec outgoing[] = {{first, 3}, {denied, 5}};
67 CHECK(vmsplice(p[0], outgoing, 2, 0) == 3 && !memcmp(first, "abc", 3));
68 struct iovec retry = {tail, 5};
69 CHECK(vmsplice(p[0], &retry, 1, 0) == 5 && !memcmp(tail, "defgh", 5));
70 struct iovec empty_first[] = {{denied, 0}, {"abc", 3}};
71 CHECK(vmsplice(p[1], empty_first, 2, 0) == 3);
72 CHECK(read(p[0], first, 3) == 3 && !memcmp(first, "abc", 3));
73 for (size_t n = 0; n < sizeof(large); ++n)
74 large[n] = pt_pattern(n, 61);
75 struct iovec bounded = {large, sizeof(large)};
76 ssize_t amount = vmsplice(p[1], &bounded, 1, 0);
77 CHECK(amount > 0 && amount <= PT_CAPACITY && !pt_read(p[0], amount, 0, 61));
78 CHECK(vmsplice(p[1], &retry, 1, 0x80000000U) == -1 && errno == EINVAL);
79 CHECK(vmsplice(p[1], &retry, 1, SPLICE_F_GIFT) == -1 && errno == EINVAL);
80out:
81 if (denied != MAP_FAILED)
82 munmap(denied, page);
83 if (p[0] >= 0)
84 close(p[0]);
85 if (p[1] >= 0)
86 close(p[1]);
87 return failed;
88}
89static int aligned_gift(void) {
90 int failed = 0, p[2] = {-1, -1};
91 const size_t page = sysconf(_SC_PAGESIZE);
92 unsigned char* memory = MAP_FAILED;
93 CHECK(!pipe(p) && page == PT_CAPACITY);
94 memory = mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
95 CHECK(memory != MAP_FAILED);
96 for (size_t n = 0; n < page; ++n)
97 memory[n] = pt_pattern(n, 79);
98 struct iovec vector = {memory, page};
99 CHECK(vmsplice(p[1], &vector, 1, SPLICE_F_GIFT) == (ssize_t)page);
100 /* Gift permits reuse by the kernel; this implementation still leaves the mapping accessible. */
101 CHECK(memory[0] == pt_pattern(0, 79) && memory[page - 1] == pt_pattern(page - 1, 79));
102 CHECK(!pt_read(p[0], page, 0, 79));
103out:
104 if (memory != MAP_FAILED)
105 munmap(memory, page);
106 if (p[0] >= 0)
107 close(p[0]);
108 if (p[1] >= 0)
109 close(p[1]);
110 return failed;
111}
112static int duplex_fifo(void) {
113 int failed = 0, fd = -1, notify = -1, created = 0;
114 char path[128], bytes[3];
115 struct stat metadata;
116 snprintf(path, sizeof(path), "/tmp/pipe-transfer-duplex-%ld", (long)getpid());
117 mode_t previous_mask = umask(0077);
118 int create_result = mkfifo(path, 0666);
119 int create_errno = errno;
120 umask(previous_mask);
121 errno = create_errno;
122 CHECK(!create_result);
123 created = 1;
124 CHECK((fd = open(path, O_RDWR | O_NONBLOCK)) >= 0);
125 CHECK(!fstat(fd, &metadata) && S_ISFIFO(metadata.st_mode) && (metadata.st_mode & 07777) == 0600 &&
126 metadata.st_uid == geteuid() && metadata.st_gid == getegid());
127 CHECK(mkfifo(path, 0666) == -1 && errno == EEXIST);
128 CHECK((notify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC)) >= 0);
129 int watch = inotify_add_watch(notify, path, IN_MODIFY);
130 CHECK(watch >= 0);
131 struct iovec vector = {"abc", 3};
132 CHECK(vmsplice(fd, &vector, 1, SPLICE_F_NONBLOCK) == 3);
133 union {
134 struct inotify_event event;
135 char bytes[256];
136 } event;
137 CHECK(pt_ready(notify, POLLIN, 1000) == 1 &&
138 read(notify, event.bytes, sizeof(event.bytes)) >= (ssize_t)sizeof(event.event) &&
139 event.event.wd == watch && (event.event.mask & IN_MODIFY));
140 CHECK(read(fd, bytes, sizeof(bytes)) == sizeof(bytes) && !memcmp(bytes, "abc", sizeof(bytes)));
141out:
142 if (notify >= 0)
143 close(notify);
144 if (fd >= 0)
145 close(fd);
146 if (created)
147 unlink(path);
148 return failed;
149}
150int pipe_transfer_vmsplice(void) {
151 return copying() || faults_and_limits() || aligned_gift() || duplex_fifo();
152}