1/*-
2 * Copyright (c) 2009 Hudson River Trading LLC
3 * Written by: John H. Baldwin <jhb@FreeBSD.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD$");
30
31/*
32 * Regression tests for the closefrom(2) system call.
33 */
34
35#include <sys/param.h>
36#include <sys/mman.h>
37#include <sys/user.h>
38#include <sys/wait.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <libutil.h>
42#include <paths.h>
43#include <stdarg.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48
49struct shared_info {
50	int	failed;
51	char	tag[64];
52	char	message[0];
53};
54
55static int test = 1;
56
57static void
58ok(const char *descr)
59{
60
61	printf("ok %d - %s\n", test, descr);
62	test++;
63}
64
65static void
66fail(const char *descr, const char *fmt, ...)
67{
68	va_list ap;
69
70	printf("not ok %d - %s", test, descr);
71	test++;
72	if (fmt) {
73		va_start(ap, fmt);
74		printf(" # ");
75		vprintf(fmt, ap);
76		va_end(ap);
77	}
78	printf("\n");
79	exit(1);
80}
81
82#define	fail_err(descr)		fail((descr), "%s", strerror(errno))
83
84static void
85cok(struct shared_info *info, const char *descr)
86{
87
88	info->failed = 0;
89	strlcpy(info->tag, descr, sizeof(info->tag));
90	exit(0);
91}
92
93static void
94cfail(struct shared_info *info, const char *descr, const char *fmt, ...)
95{
96	va_list ap;
97
98	info->failed = 1;
99	strlcpy(info->tag, descr, sizeof(info->tag));
100	if (fmt) {
101		va_start(ap, fmt);
102		vsprintf(info->message, fmt, ap);
103		va_end(ap);
104	}
105	exit(0);
106}
107
108#define	cfail_err(info, descr)	cfail((info), (descr), "%s", strerror(errno))
109
110/*
111 * Use kinfo_getfile() to fetch the list of file descriptors and figure out
112 * the highest open file descriptor.
113 */
114static int
115highest_fd(void)
116{
117	struct kinfo_file *kif;
118	int cnt, i, highest;
119
120	kif = kinfo_getfile(getpid(), &cnt);
121	if (kif == NULL)
122		fail_err("kinfo_getfile");
123	highest = INT_MIN;
124	for (i = 0; i < cnt; i++)
125		if (kif[i].kf_fd > highest)
126			highest = kif[i].kf_fd;
127	free(kif);
128	return (highest);
129}
130
131static int
132devnull(void)
133{
134	int fd;
135
136	fd = open(_PATH_DEVNULL, O_RDONLY);
137	if (fd < 0)
138		fail_err("open(\" "_PATH_DEVNULL" \")");
139	return (fd);
140}
141
142int
143main(void)
144{
145	struct shared_info *info;
146	pid_t pid;
147	int fd, i, start;
148
149	printf("1..15\n");
150
151	/* We better start up with fd's 0, 1, and 2 open. */
152	start = devnull();
153	if (start == -1)
154		fail("open", "bad descriptor %d", start);
155	ok("open");
156
157	/* Make sure highest_fd() works. */
158	fd = highest_fd();
159	if (start != fd)
160		fail("highest_fd", "bad descriptor %d != %d", start, fd);
161	ok("highest_fd");
162
163	/* Try to use closefrom() for just closing fd 3. */
164	closefrom(start + 1);
165	fd = highest_fd();
166	if (fd != start)
167		fail("closefrom", "highest fd %d", fd);
168	ok("closefrom");
169
170	/* Eat up 16 descriptors. */
171	for (i = 0; i < 16; i++)
172		(void)devnull();
173	fd = highest_fd();
174	if (fd != start + 16)
175		fail("open 16", "highest fd %d", fd);
176	ok("open 16");
177
178	/* Close half of them. */
179	closefrom(11);
180	fd = highest_fd();
181	if (fd != 10)
182		fail("closefrom", "highest fd %d", fd);
183	ok("closefrom");
184
185	/* Explicitly close descriptors 6 and 8 to create holes. */
186	if (close(6) < 0 || close(8) < 0)
187		fail_err("close2 ");
188	ok("close 2");
189
190	/* Verify that close on 6 and 8 fails with EBADF. */
191	if (close(6) == 0)
192		fail("close(6)", "did not fail");
193	if (errno != EBADF)
194		fail_err("close(6)");
195	ok("close(6)");
196	if (close(8) == 0)
197		fail("close(8)", "did not fail");
198	if (errno != EBADF)
199		fail_err("close(8)");
200	ok("close(8)");
201
202	/* Close from 4 on. */
203	closefrom(4);
204	fd = highest_fd();
205	if (fd != 3)
206		fail("closefrom", "highest fd %d", fd);
207	ok("closefrom");
208
209	/* Allocate a small SHM region for IPC with our child. */
210	info = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANON |
211	    MAP_SHARED, -1, 0);
212	if (info == MAP_FAILED)
213		fail_err("mmap");
214	ok("mmap");
215
216	/* Fork a child process to test closefrom(0). */
217	pid = fork();
218	if (pid < 0)
219		fail_err("fork");
220	if (pid == 0) {
221		/* Child. */
222		closefrom(0);
223		fd = highest_fd();
224		if (fd >= 0)
225			cfail(info, "closefrom(0)", "highest fd %d", fd);
226		cok(info, "closefrom(0)");
227	}
228	if (wait(NULL) < 0)
229		fail_err("wait");
230	if (info->failed)
231		fail(info->tag, "%s", info->message);
232	ok(info->tag);
233
234	/* Fork a child process to test closefrom(-1). */
235	pid = fork();
236	if (pid < 0)
237		fail_err("fork");
238	if (pid == 0) {
239		/* Child. */
240		closefrom(-1);
241		fd = highest_fd();
242		if (fd >= 0)
243			cfail(info, "closefrom(-1)", "highest fd %d", fd);
244		cok(info, "closefrom(-1)");
245	}
246	if (wait(NULL) < 0)
247		fail_err("wait");
248	if (info->failed)
249		fail(info->tag, "%s", info->message);
250	ok(info->tag);
251
252	/* Dup stdout to 6. */
253	if (dup2(1, 6) < 0)
254		fail_err("dup2");
255	fd = highest_fd();
256	if (fd != 6)
257		fail("dup2", "highest fd %d", fd);
258	ok("dup2");
259
260	/* Do a closefrom() starting in a hole. */
261	closefrom(4);
262	fd = highest_fd();
263	if (fd != 3)
264		fail("closefrom", "highest fd %d", fd);
265	ok("closefrom");
266
267	/* Do a closefrom() beyond our highest open fd. */
268	closefrom(32);
269	fd = highest_fd();
270	if (fd != 3)
271		fail("closefrom", "highest fd %d", fd);
272	ok("closefrom");
273
274	return (0);
275}
276