1/*-
2 * Copyright (c) 2009 Advanced Computing Technologies 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 <stdarg.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47
48struct shared_info {
49	int	failed;
50	char	tag[64];
51	char	message[0];
52};
53
54static int test = 1;
55
56static void
57ok(const char *descr)
58{
59
60	printf("ok %d - %s\n", test, descr);
61	test++;
62}
63
64static void
65fail(const char *descr, const char *fmt, ...)
66{
67	va_list ap;
68
69	printf("not ok %d - %s", test, descr);
70	test++;
71	if (fmt) {
72		va_start(ap, fmt);
73		printf(" # ");
74		vprintf(fmt, ap);
75		va_end(ap);
76	}
77	printf("\n");
78	exit(1);
79}
80
81#define	fail_err(descr)		fail((descr), "%s", strerror(errno))
82
83static void
84cok(struct shared_info *info, const char *descr)
85{
86
87	info->failed = 0;
88	strlcpy(info->tag, descr, sizeof(info->tag));
89	exit(0);
90}
91
92static void
93cfail(struct shared_info *info, const char *descr, const char *fmt, ...)
94{
95	va_list ap;
96
97	info->failed = 1;
98	strlcpy(info->tag, descr, sizeof(info->tag));
99	if (fmt) {
100		va_start(ap, fmt);
101		vsprintf(info->message, fmt, ap);
102		va_end(ap);
103	}
104	exit(0);
105}
106
107#define	cfail_err(info, descr)	cfail((info), (descr), "%s", strerror(errno))
108
109/*
110 * Use kinfo_getfile() to fetch the list of file descriptors and figure out
111 * the highest open file descriptor.
112 */
113static int
114highest_fd(void)
115{
116	struct kinfo_file *kif;
117	int cnt, i, highest;
118
119	kif = kinfo_getfile(getpid(), &cnt);
120	if (kif == NULL)
121		fail_err("kinfo_getfile");
122	highest = INT_MIN;
123	for (i = 0; i < cnt; i++)
124		if (kif[i].kf_fd > highest)
125			highest = kif[i].kf_fd;
126	free(kif);
127	return (highest);
128}
129
130static int
131devnull(void)
132{
133	int fd;
134
135	fd = open("/dev/null", O_RDONLY);
136	if (fd < 0)
137		fail_err("open(\"/dev/null\")");
138	return (fd);
139}
140
141int
142main(int __unused argc, char __unused *argv[])
143{
144	struct shared_info *info;
145	pid_t pid;
146	int fd, i;
147
148	printf("1..15\n");
149
150	/* We better start up with fd's 0, 1, and 2 open. */
151	fd = devnull();
152	if (fd != 3)
153		fail("open", "bad descriptor %d", fd);
154	ok("open");
155
156	/* Make sure highest_fd() works. */
157	fd = highest_fd();
158	if (fd != 3)
159		fail("highest_fd", "bad descriptor %d", fd);
160	ok("highest_fd");
161
162	/* Try to use closefrom() for just closing fd 3. */
163	closefrom(3);
164	fd = highest_fd();
165	if (fd != 2)
166		fail("closefrom", "highest fd %d", fd);
167	ok("closefrom");
168
169	/* Eat up 16 descriptors. */
170	for (i = 0; i < 16; i++)
171		(void)devnull();
172	fd = highest_fd();
173	if (fd != 18)
174		fail("open 16", "highest fd %d", fd);
175	ok("open 16");
176
177	/* Close half of them. */
178	closefrom(11);
179	fd = highest_fd();
180	if (fd != 10)
181		fail("closefrom", "highest fd %d", fd);
182	ok("closefrom");
183
184	/* Explicitly close descriptors 6 and 8 to create holes. */
185	if (close(6) < 0 || close(8) < 0)
186		fail_err("close2 ");
187	ok("close 2");
188
189	/* Verify that close on 6 and 8 fails with EBADF. */
190	if (close(6) == 0)
191		fail("close(6)", "did not fail");
192	if (errno != EBADF)
193		fail_err("close(6)");
194	ok("close(6)");
195	if (close(8) == 0)
196		fail("close(8)", "did not fail");
197	if (errno != EBADF)
198		fail_err("close(8)");
199	ok("close(8)");
200
201	/* Close from 4 on. */
202	closefrom(4);
203	fd = highest_fd();
204	if (fd != 3)
205		fail("closefrom", "highest fd %d", fd);
206	ok("closefrom");
207
208	/* Allocate a small SHM region for IPC with our child. */
209	info = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANON |
210	    MAP_SHARED, -1, 0);
211	if (info == MAP_FAILED)
212		fail_err("mmap");
213	ok("mmap");
214
215	/* Fork a child process to test closefrom(0). */
216	pid = fork();
217	if (pid < 0)
218		fail_err("fork");
219	if (pid == 0) {
220		/* Child. */
221		closefrom(0);
222		fd = highest_fd();
223		if (fd >= 0)
224			cfail(info, "closefrom(0)", "highest fd %d", fd);
225		cok(info, "closefrom(0)");
226	}
227	if (wait(NULL) < 0)
228		fail_err("wait");
229	if (info->failed)
230		fail(info->tag, "%s", info->message);
231	ok(info->tag);
232
233	/* Fork a child process to test closefrom(-1). */
234	pid = fork();
235	if (pid < 0)
236		fail_err("fork");
237	if (pid == 0) {
238		/* Child. */
239		closefrom(-1);
240		fd = highest_fd();
241		if (fd >= 0)
242			cfail(info, "closefrom(-1)", "highest fd %d", fd);
243		cok(info, "closefrom(-1)");
244	}
245	if (wait(NULL) < 0)
246		fail_err("wait");
247	if (info->failed)
248		fail(info->tag, "%s", info->message);
249	ok(info->tag);
250
251	/* Dup stdout to 6. */
252	if (dup2(1, 6) < 0)
253		fail_err("dup2");
254	fd = highest_fd();
255	if (fd != 6)
256		fail("dup2", "highest fd %d", fd);
257	ok("dup2");
258
259	/* Do a closefrom() starting in a hole. */
260	closefrom(4);
261	fd = highest_fd();
262	if (fd != 3)
263		fail("closefrom", "highest fd %d", fd);
264	ok("closefrom");
265
266	/* Do a closefrom() beyond our highest open fd. */
267	closefrom(32);
268	fd = highest_fd();
269	if (fd != 3)
270		fail("closefrom", "highest fd %d", fd);
271	ok("closefrom");
272
273	return (0);
274}
275