blob: 258b0c95189fcfe8208bca14186ee8e3daf74757 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*
* Test program to open unlinked files and leak them.
*/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#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;
}