summaryrefslogtreecommitdiff
path: root/src/t_open_tmpfiles.c
blob: 258b0c95189fcfe8208bca14186ee8e3daf74757 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// 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;
}