1 /*
2  * Copyright © 2018 Alexey Dobriyan <adobriyan@gmail.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 // Test /proc/*/fd lookup.
17 
18 #undef NDEBUG
19 #include <assert.h>
20 #include <dirent.h>
21 #include <errno.h>
22 #include <limits.h>
23 #include <sched.h>
24 #include <stdio.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 
30 #include "proc.h"
31 
32 /* lstat(2) has more "coverage" in case non-symlink pops up somehow. */
33 static void test_lookup_pass(const char *pathname)
34 {
35 	struct stat st;
36 	ssize_t rv;
37 
38 	memset(&st, 0, sizeof(struct stat));
39 	rv = lstat(pathname, &st);
40 	assert(rv == 0);
41 	assert(S_ISLNK(st.st_mode));
42 }
43 
44 static void test_lookup_fail(const char *pathname)
45 {
46 	struct stat st;
47 	ssize_t rv;
48 
49 	rv = lstat(pathname, &st);
50 	assert(rv == -1 && errno == ENOENT);
51 }
52 
53 static void test_lookup(unsigned int fd)
54 {
55 	char buf[64];
56 	unsigned int c;
57 	unsigned int u;
58 	int i;
59 
60 	snprintf(buf, sizeof(buf), "/proc/self/fd/%u", fd);
61 	test_lookup_pass(buf);
62 
63 	/* leading junk */
64 	for (c = 1; c <= 255; c++) {
65 		if (c == '/')
66 			continue;
67 		snprintf(buf, sizeof(buf), "/proc/self/fd/%c%u", c, fd);
68 		test_lookup_fail(buf);
69 	}
70 
71 	/* trailing junk */
72 	for (c = 1; c <= 255; c++) {
73 		if (c == '/')
74 			continue;
75 		snprintf(buf, sizeof(buf), "/proc/self/fd/%u%c", fd, c);
76 		test_lookup_fail(buf);
77 	}
78 
79 	for (i = INT_MIN; i < INT_MIN + 1024; i++) {
80 		snprintf(buf, sizeof(buf), "/proc/self/fd/%d", i);
81 		test_lookup_fail(buf);
82 	}
83 	for (i = -1024; i < 0; i++) {
84 		snprintf(buf, sizeof(buf), "/proc/self/fd/%d", i);
85 		test_lookup_fail(buf);
86 	}
87 	for (u = INT_MAX - 1024; u <= (unsigned int)INT_MAX + 1024; u++) {
88 		snprintf(buf, sizeof(buf), "/proc/self/fd/%u", u);
89 		test_lookup_fail(buf);
90 	}
91 	for (u = UINT_MAX - 1024; u != 0; u++) {
92 		snprintf(buf, sizeof(buf), "/proc/self/fd/%u", u);
93 		test_lookup_fail(buf);
94 	}
95 
96 
97 }
98 
99 int main(void)
100 {
101 	struct dirent *de;
102 	unsigned int fd, target_fd;
103 
104 	if (unshare(CLONE_FILES) == -1)
105 		return 1;
106 
107 	/* Wipe fdtable. */
108 	do {
109 		DIR *d;
110 
111 		d = opendir("/proc/self/fd");
112 		if (!d)
113 			return 1;
114 
115 		de = xreaddir(d);
116 		assert(de->d_type == DT_DIR);
117 		assert(streq(de->d_name, "."));
118 
119 		de = xreaddir(d);
120 		assert(de->d_type == DT_DIR);
121 		assert(streq(de->d_name, ".."));
122 next:
123 		de = xreaddir(d);
124 		if (de) {
125 			unsigned long long fd_ull;
126 			unsigned int fd;
127 			char *end;
128 
129 			assert(de->d_type == DT_LNK);
130 
131 			fd_ull = xstrtoull(de->d_name, &end);
132 			assert(*end == '\0');
133 			assert(fd_ull == (unsigned int)fd_ull);
134 
135 			fd = fd_ull;
136 			if (fd == dirfd(d))
137 				goto next;
138 			close(fd);
139 		}
140 
141 		closedir(d);
142 	} while (de);
143 
144 	/* Now fdtable is clean. */
145 
146 	fd = open("/", O_PATH|O_DIRECTORY);
147 	assert(fd == 0);
148 	test_lookup(fd);
149 	close(fd);
150 
151 	/* Clean again! */
152 
153 	fd = open("/", O_PATH|O_DIRECTORY);
154 	assert(fd == 0);
155 	/* Default RLIMIT_NOFILE-1 */
156 	target_fd = 1023;
157 	while (target_fd > 0) {
158 		if (dup2(fd, target_fd) == target_fd)
159 			break;
160 		target_fd /= 2;
161 	}
162 	assert(target_fd > 0);
163 	close(fd);
164 	test_lookup(target_fd);
165 	close(target_fd);
166 
167 	return 0;
168 }
169