13#include <sys/syscall.h>
14#include <sys/sysmacros.h>
17static int failure(
const char* name) {
18 fprintf(stderr,
"SWAP-TEST: FAIL %s errno=%d\n", name, errno);
21static int read_header(
const char* path,
unsigned char* header) {
22 int fd = open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
27 received = pread(fd, header, 4096, 0);
28 }
while (received < 0 && errno == EINTR);
29 int error = received == 4096 ? 0 : received < 0 ? errno : EIO;
30 if (close(fd) && !error)
33 return error ? -1 : 0;
35static uint32_t header_word(
const unsigned char* data,
size_t offset) {
36 return (uint32_t)data[offset] | ((uint32_t)data[offset + 1] << 8) |
37 ((uint32_t)data[offset + 2] << 16) | ((uint32_t)data[offset + 3] << 24);
39static int discover_device(
char* selected,
size_t capacity) {
40 DIR* directory = opendir(
"/dev/block");
47 struct dirent* entry = readdir(directory);
52 if (strncmp(entry->d_name,
"disk", 4) || !entry->d_name[4])
54 const char* digit = entry->d_name + 4;
55 while (*digit >=
'0' && *digit <=
'9')
60 int length = snprintf(candidate,
sizeof(candidate),
"/dev/block/%s", entry->d_name);
61 if (length < 0 || (
size_t)length >=
sizeof(candidate)) {
65 struct stat attributes;
66 if (stat(candidate, &attributes)) {
70 if (!S_ISBLK(attributes.st_mode) || major(attributes.st_rdev) != 241 ||
71 attributes.st_size != (off_t)4097 * 4096)
73 int fd = open(candidate, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
79 unsigned char header[4096];
80 ssize_t received = -1;
81 if (fstat(fd, &opened))
83 else if (!S_ISBLK(opened.st_mode) || opened.st_rdev != attributes.st_rdev ||
84 opened.st_size != attributes.st_size)
88 received = pread(fd, header,
sizeof(header), 0);
89 }
while (received < 0 && errno == EINTR);
90 if (received != (ssize_t)
sizeof(header))
91 error = received < 0 ? errno : EIO;
93 if (close(fd) && !error)
97 if (memcmp(header + 4086,
"SWAPSPACE2", 10) || header_word(header, 1024) != 1 ||
98 header_word(header, 1028) != 4096 || header_word(header, 1032))
104 if ((
size_t)length >= capacity) {
105 error = ENAMETOOLONG;
108 memcpy(selected, candidate, (
size_t)length + 1);
110 if (closedir(directory) && !error)
112 if (!error && !matches)
115 return error ? -1 : 0;
117static unsigned char value(
size_t page,
size_t byte) {
118 return (
unsigned char)(page * 29 +
byte * 7 + 0x35);
120static int contents(
const unsigned char* p,
size_t pages,
size_t bytes) {
121 for (
size_t n = 0; n < pages; ++n)
122 for (
size_t i = 0; i < bytes; ++i) {
123 const unsigned char actual = p[n * bytes + i], expected = value(n, i);
124 if (actual != expected) {
126 fprintf(stderr,
"SWAP-TEST: CONTENT page=%zu byte=%zu expected=%u actual=%u\n", n, i,
127 (
unsigned)expected, (
unsigned)actual);
133static int resident(
unsigned char* p,
size_t pages,
size_t bytes,
int expected) {
134 unsigned char vector[16];
135 if (pages >
sizeof(vector)) {
140 if (mincore(p, pages * bytes, vector)) {
141 fprintf(stderr,
"SWAP-TEST: MINCORE error=%d\n", errno);
144 for (
size_t i = 0; i < pages; ++i)
145 if (!!(vector[i] & 1) != expected) {
146 fprintf(stderr,
"SWAP-TEST: RESIDENCY page=%zu expected=%d actual=%u\n", i, expected,
147 (
unsigned)vector[i]);
152int main(
int argc,
char** argv) {
153 setvbuf(stdout, NULL, _IONBF, 0);
155 if (argc != 2 || geteuid() || sysconf(_SC_PAGESIZE) != 4096)
156 return failure(
"requires root and a prepared disposable device or --discover");
157 const size_t page = 4096, pages = 8, bytes = pages * page;
158 const char* device = argv[1];
159 char discovered[512];
160 if (!strcmp(device,
"--discover")) {
161 if (discover_device(discovered,
sizeof(discovered)))
162 return failure(
"discovery requires exactly one prepared disposable disk");
166 if (stat(device, &st) || !S_ISBLK(st.st_mode))
167 return failure(
"physical device selection");
168 printf(
"SWAP-TEST: DEVICE path=%s size=%llu\n", device, (
unsigned long long)st.st_size);
169 unsigned char originalHeader[4096];
170 if (read_header(device, originalHeader))
171 return failure(
"initial header snapshot");
173 if (swapon((
const char*)1, 0) != -1 || errno != EFAULT)
174 return failure(
"path copy");
176 if (swapon(device, 0x80000000U) != -1 || errno != EINVAL)
177 return failure(
"unknown flags");
179 if (swapon(device, SWAP_FLAG_DISCARD) != -1 || errno != EOPNOTSUPP)
180 return failure(
"unsupported flags");
181 pid_t child = fork();
183 return failure(
"privilege fork");
188 if (swapon(device, 0) != -1 || errno != EPERM)
191 _exit(swapoff(device) == -1 && errno == EPERM ? 0 : 4);
194 if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status))
195 return failure(
"privilege gate");
196 puts(
"SWAP-TEST: PASS inputs-permissions");
197 unsigned char* p = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
199 return failure(
"anonymous mapping");
200 for (
size_t n = 0; n < pages; ++n)
201 for (
size_t i = 0; i < page; ++i)
202 p[n * page + i] = value(n, i);
203 int result = 1, active = 0;
204 if (swapon(device, 0)) {
210 if (swapon(device, 0) != -1 || errno != EBUSY) {
211 failure(
"duplicate activation");
214 if (madvise(p, bytes, MADV_PAGEOUT) || !resident(p, pages, page, 0)) {
215 failure(
"pageout residency");
218 int nullfd = open(
"/tmp/swap-contract-copy", O_WRONLY | O_CREAT | O_TRUNC, 0600);
219 if (nullfd < 0 || write(nullfd, p, bytes) != (ssize_t)bytes) {
222 failure(
"usercopy fault-in");
226 unlink(
"/tmp/swap-contract-copy");
227 if (!resident(p, pages, page, 1)) {
228 failure(
"usercopy residency");
231 if (!contents(p, pages, page)) {
232 failure(
"restored contents");
235 puts(
"SWAP-TEST: PASS eviction-usercopy-faultin");
236 if (madvise(p, bytes, MADV_PAGEOUT)) {
237 failure(
"fork preparation");
242 failure(
"swapped fork");
246 printf(
"SWAP-TEST: CHILD stage=read-swapped address=%p pages=%zu\n", (
void*)p, pages);
247 if (!contents(p, pages, page))
249 puts(
"SWAP-TEST: CHILD stage=private-write");
251 puts(
"SWAP-TEST: CHILD stage=unmap");
252 _exit(munmap(p, bytes) ? 6 : 0);
255 pid_t reaped = waitpid(child, &status, 0);
256 if (reaped != child) {
257 fprintf(stderr,
"SWAP-TEST: CHILD wait expected=%ld actual=%ld errno=%d\n", (
long)child,
258 (
long)reaped, errno);
259 failure(
"fork wait");
262 if (!WIFEXITED(status) || WEXITSTATUS(status)) {
263 fprintf(stderr,
"SWAP-TEST: CHILD status=%#x exited=%d code=%d signalled=%d signal=%d\n",
264 status, WIFEXITED(status), WIFEXITED(status) ? WEXITSTATUS(status) : -1,
265 WIFSIGNALED(status), WIFSIGNALED(status) ? WTERMSIG(status) : 0);
266 failure(
"fork child completion");
269 if (!contents(p, pages, page)) {
270 failure(
"fork parent contents");
273 if (madvise(p, bytes, MADV_PAGEOUT)) {
274 failure(
"remap preparation");
277 unsigned char* destination = mmap(0, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
278 if (destination == MAP_FAILED) {
279 failure(
"remap destination");
282 void* moved = mremap(p, bytes, bytes, MREMAP_MAYMOVE | MREMAP_FIXED, destination);
283 if (moved == MAP_FAILED) {
284 munmap(destination, bytes);
285 failure(
"swapped remap");
289 if (!resident(p, pages, page, 0) || !contents(p, pages, page)) {
290 failure(
"remap contents");
293 puts(
"SWAP-TEST: PASS fork-remap");
294 if (madvise(p, bytes, MADV_PAGEOUT) || mlock(p, page) || !resident(p, 1, page, 1)) {
295 failure(
"mlock fault-in");
299 if (madvise(p, bytes, MADV_PAGEOUT) != -1 || errno != EOPNOTSUPP || !resident(p, 1, page, 1) ||
301 failure(
"locked range admission");
304 if (swapoff(device)) {
309 if (!resident(p, pages, page, 1) || !contents(p, pages, page)) {
310 failure(
"drain contents");
314 if (swapoff(device) != -1 || errno != EINVAL || swapon(device, 0)) {
315 failure(
"retirement reuse");
319 if (madvise(p, bytes, MADV_PAGEOUT) || madvise(p, bytes, MADV_DONTNEED) || swapoff(device)) {
320 failure(
"discard releases slots");
324 for (
size_t i = 0; i < bytes; ++i)
326 failure(
"discard zero-fill");
329 puts(
"SWAP-TEST: PASS mlock-swapoff-discard-reuse");
335 result = failure(
"cleanup swapoff");
340 unsigned char currentHeader[4096];
341 if (read_header(device, currentHeader))
342 result = failure(
"final header snapshot");
344 for (
size_t i = 0; i <
sizeof(originalHeader); ++i)
345 if (currentHeader[i] != originalHeader[i]) {
347 fprintf(stderr,
"SWAP-TEST: HEADER byte=%zu expected=%u actual=%u\n", i,
348 (
unsigned)originalHeader[i], (
unsigned)currentHeader[i]);
349 result = failure(
"swap header changed");
355 puts(
"SWAP-TEST: END PASS");