// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2019 Oracle. All Rights Reserved. * Author: Darrick J. Wong * * Test program to open unlinked files and leak them. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include "global.h" static int min_fd = -1; static int max_fd = -1; static unsigned int nr_opened = 0; static float start_time; static int shutdown_fd = -1; void clock_time(float *time) { static clockid_t clkid = CLOCK_MONOTONIC; struct timespec ts; int ret; retry: ret = clock_gettime(clkid, &ts); if (ret) { if (clkid == CLOCK_MONOTONIC) { clkid = CLOCK_REALTIME; goto retry; } perror("clock_gettime"); exit(2); } *time = ts.tv_sec + ((float)ts.tv_nsec / 1000000000); } /* * Exit the program due to an error. * * If we've exhausted all the file descriptors, make sure we close all the * open fds in the order we received them in order to exploit a quirk of ext4 * and xfs where the oldest unlinked inodes are at the /end/ of the unlinked * lists, which will make removing the unlinked files maximally painful. * * If it's some other error, just die and let the kernel sort it out. */ void die(void) { float end_time; int fd; switch (errno) { case EMFILE: case ENFILE: case ENOSPC: clock_time(&end_time); printf("Opened %u files in %.2fs.\n", nr_opened, end_time - start_time); fflush(stdout); if (shutdown_fd >= 0) { /* * Flush the log so that we have to process the * unlinked inodes the next time we mount. */ int flag = XFS_FSOP_GOING_FLAGS_LOGFLUSH; int ret; ret = ioctl(shutdown_fd, XFS_IOC_GOINGDOWN, &flag); if (ret) { perror("shutdown"); exit(2); } exit(0); } clock_time(&start_time); for (fd = min_fd; fd <= max_fd; fd++) close(fd); clock_time(&end_time); printf("Closed %u files in %.2fs.\n", nr_opened, end_time - start_time); exit(0); break; default: perror("open?"); exit(2); break; } } /* Remember how many file we open and all that. */ void remember_fd(int fd) { if (min_fd == -1 || min_fd > fd) min_fd = fd; if (max_fd == -1 || max_fd < fd) max_fd = fd; nr_opened++; } /* Put an opened file on the unlinked list and leak the fd. */ void leak_tmpfile(void) { int fd = -1; int ret; #ifdef O_TMPFILE static int try_o_tmpfile = 1; #endif /* Try to create an O_TMPFILE and leak the fd. */ #ifdef O_TMPFILE if (try_o_tmpfile) { fd = open(".", O_TMPFILE | O_RDWR, 0644); if (fd >= 0) { remember_fd(fd); return; } if (fd < 0) { if (errno == EOPNOTSUPP) try_o_tmpfile = 0; else die(); } } #endif /* Oh well, create a new file, unlink it, and leak the fd. */ fd = open("./moo", O_CREAT | O_RDWR, 0644); if (fd < 0) die(); ret = unlink("./moo"); if (ret) die(); remember_fd(fd); } /* * Try to put as many files on the unlinked list and then kill them. * The first argument is a directory to chdir into; the second argumennt (if * provided) is a file path that will be opened and then used to shut down the * fs before the program exits. */ int main(int argc, char *argv[]) { int ret; if (argc > 1) { ret = chdir(argv[1]); if (ret) perror(argv[1]); } if (argc > 2) { shutdown_fd = open(argv[2], O_RDONLY); if (shutdown_fd < 0) { perror(argv[2]); return 1; } } clock_time(&start_time); while (1) leak_tmpfile(); return 0; }