1 /*
2  * Copyright (c) 2024, Tenstorrent AI ULC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <sys/mman.h>
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/net/socket.h>
11 #include <zephyr/posix/fcntl.h>
12 #include <zephyr/sys/fdtable.h>
13 
14 #include <zephyr/ztest.h>
15 
16 #define SHM_SIZE 8
17 
18 #define VALID_SHM_PATH     "/foo"
19 #define INVALID_SHM_PATH   "foo"
20 #define EMPTY_SHM_PATH     ""
21 #define TOO_SHORT_SHM_PATH "/"
22 
23 #define INVALID_MODE 0
24 #define VALID_MODE   0666
25 
26 #define INVALID_FLAGS 0
27 #define VALID_FLAGS   (O_RDWR | O_CREAT)
28 #define CREATE_FLAGS  VALID_FLAGS
29 #define OPEN_FLAGS    (VALID_FLAGS & ~O_CREAT)
30 
31 /* account for stdin, stdout, stderr */
32 #define N (CONFIG_ZVFS_OPEN_MAX - 3)
33 
34 /* we need to have at least 2 shared memory objects */
35 BUILD_ASSERT(N >= 2, "CONFIG_ZVFS_OPEN_MAX must be > 4");
36 
37 #define S_TYPEISSHM(st) (((st)->st_mode & ZVFS_MODE_IFMT) == ZVFS_MODE_IFSHM)
38 
ZTEST(shm,test_shm_open)39 ZTEST(shm, test_shm_open)
40 {
41 	int ret;
42 	int fd[N];
43 	struct stat st;
44 
45 	{
46 		/* degenerate error cases */
47 		zassert_not_ok(shm_open(NULL, INVALID_FLAGS, INVALID_MODE));
48 		zassert_not_ok(shm_open(NULL, INVALID_FLAGS, VALID_MODE));
49 		zassert_not_ok(shm_open(NULL, VALID_FLAGS, INVALID_MODE));
50 		zassert_not_ok(shm_open(NULL, VALID_FLAGS, VALID_MODE));
51 		zassert_not_ok(shm_open(INVALID_SHM_PATH, VALID_FLAGS, VALID_MODE));
52 		zassert_not_ok(shm_open(EMPTY_SHM_PATH, VALID_FLAGS, VALID_MODE));
53 		zassert_not_ok(shm_open(TOO_SHORT_SHM_PATH, VALID_FLAGS, VALID_MODE));
54 		zassert_not_ok(shm_open(VALID_SHM_PATH, INVALID_FLAGS, INVALID_MODE));
55 		zassert_not_ok(shm_open(VALID_SHM_PATH, INVALID_FLAGS, VALID_MODE));
56 		zassert_not_ok(shm_open(VALID_SHM_PATH, VALID_FLAGS, INVALID_MODE));
57 	}
58 
59 	/* open / close 1 file descriptor referring to VALID_SHM_PATH */
60 	fd[0] = shm_open(VALID_SHM_PATH, VALID_FLAGS, VALID_MODE);
61 	zassert_true(fd[0] >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH, VALID_FLAGS,
62 		     VALID_MODE, errno);
63 
64 	/* should have size 0 and be a shared memory object */
65 	zassert_ok(fstat(fd[0], &st));
66 	zassert_equal(st.st_size, 0);
67 	zassert_true(S_TYPEISSHM(&st));
68 
69 	/* technically, the order of close / shm_unlink can be reversed too */
70 	zassert_ok(close(fd[0]));
71 	ret = shm_unlink(VALID_SHM_PATH);
72 	zassert_true(ret == 0 || (ret == -1 && errno == ENOENT),
73 		     "unexpected return / errno from shm_unlink: %d / %d", ret, errno);
74 
75 	/* open / close N file descriptors referring to VALID_SHM_PATH */
76 	for (size_t i = 0; i < N; ++i) {
77 		fd[i] = shm_open(VALID_SHM_PATH, i == 0 ? CREATE_FLAGS : OPEN_FLAGS, VALID_MODE);
78 		zassert_true(fd[i] >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH,
79 			     VALID_FLAGS, VALID_MODE, errno);
80 	}
81 	zassert_ok(shm_unlink(VALID_SHM_PATH));
82 	for (size_t i = N; i > 0; --i) {
83 		zassert_ok(close(fd[i - 1]));
84 	}
85 }
86 
ZTEST(shm,test_shm_unlink)87 ZTEST(shm, test_shm_unlink)
88 {
89 	int fd;
90 
91 	{
92 		/* degenerate error cases */
93 		zassert_not_ok(shm_unlink(NULL));
94 		zassert_not_ok(shm_unlink(INVALID_SHM_PATH));
95 		zassert_not_ok(shm_unlink(EMPTY_SHM_PATH));
96 		zassert_not_ok(shm_unlink(TOO_SHORT_SHM_PATH));
97 	}
98 
99 	/* open / close 1 file descriptor referring to VALID_SHM_PATH */
100 	fd = shm_open(VALID_SHM_PATH, VALID_FLAGS, VALID_MODE);
101 	zassert_true(fd >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH, VALID_FLAGS,
102 		     VALID_MODE, errno);
103 	/* technically, the order of close / shm_unlink can be reversed too */
104 	zassert_ok(close(fd));
105 	zassert_ok(shm_unlink(VALID_SHM_PATH));
106 	/* should not be able to re-open the same path without O_CREAT */
107 	zassert_not_ok(shm_open(VALID_SHM_PATH, OPEN_FLAGS, VALID_MODE));
108 }
109 
ZTEST(shm,test_shm_read_write)110 ZTEST(shm, test_shm_read_write)
111 {
112 	int fd[N];
113 
114 	for (size_t i = 0; i < N; ++i) {
115 		char cbuf = 0xff;
116 
117 		fd[i] = shm_open(VALID_SHM_PATH, i == 0 ? CREATE_FLAGS : OPEN_FLAGS, VALID_MODE);
118 		zassert_true(fd[i] >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH,
119 			     VALID_FLAGS, VALID_MODE, errno);
120 		if (i == 0) {
121 			/* size 0 on create / zero characters written */
122 			zassert_equal(write(fd[0], "", 1), 0,
123 				      "write() should fail on newly create shm fd with size 0");
124 			/* size 0 on create / zero characters read */
125 			zassert_equal(read(fd[0], &cbuf, 1), 0,
126 				      "read() should fail on newly create shm fd with size 0");
127 
128 			BUILD_ASSERT(SHM_SIZE >= 1);
129 			zassert_ok(ftruncate(fd[0], SHM_SIZE));
130 
131 			zassert_equal(write(fd[0], "\x42", 1), 1, "write() failed on fd %d: %d\n",
132 				      fd[0], errno);
133 
134 			continue;
135 		}
136 
137 		zassert_equal(read(fd[i], &cbuf, 1), 1, "read() failed on fd %d: %d\n", fd[i],
138 			      errno);
139 		zassert_equal(cbuf, 0x42,
140 			      "Failed to read byte over fd %d: expected: 0x%02x actual: 0x%02x",
141 			      fd[i], 0x42, cbuf);
142 	}
143 
144 	for (size_t i = N; i > 0; --i) {
145 		zassert_ok(close(fd[i - 1]));
146 	}
147 
148 	zassert_ok(shm_unlink(VALID_SHM_PATH));
149 }
150 
ZTEST(shm,test_shm_mmap)151 ZTEST(shm, test_shm_mmap)
152 {
153 	int fd[N];
154 	void *addr[N];
155 
156 	if (!IS_ENABLED(CONFIG_MMU)) {
157 		ztest_test_skip();
158 	}
159 
160 	for (size_t i = 0; i < N; ++i) {
161 		fd[i] = shm_open(VALID_SHM_PATH, i == 0 ? CREATE_FLAGS : OPEN_FLAGS, VALID_MODE);
162 		zassert_true(fd[i] >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH,
163 			     VALID_FLAGS, VALID_MODE, errno);
164 
165 		if (i == 0) {
166 			/* cannot map shm of size zero */
167 			zassert_not_ok(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
168 					    fd[0], 0));
169 
170 			zassert_ok(ftruncate(fd[0], PAGE_SIZE));
171 		}
172 
173 		addr[i] = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd[i], 0);
174 		zassert_not_equal(MAP_FAILED, addr[i], "mmap() failed: %d", errno);
175 
176 		if ((i & 1) == 0) {
177 			memset(addr[0], i & 0xff, PAGE_SIZE);
178 		} else {
179 			zassert_mem_equal(addr[i], addr[i - 1], PAGE_SIZE);
180 		}
181 	}
182 
183 	for (size_t i = N; i > 0; --i) {
184 		zassert_ok(close(fd[i - 1]));
185 	}
186 
187 	for (size_t i = N; i > 0; --i) {
188 		zassert_ok(munmap(addr[i - 1], PAGE_SIZE));
189 		/*
190 		 * Note: for some reason, in Zephyr, unmapping a physical page once, removes all
191 		 * virtual mappings. When that behaviour changes, remove the break below and adjust
192 		 * shm.c accordingly.
193 		 */
194 		break;
195 	}
196 
197 	zassert_ok(shm_unlink(VALID_SHM_PATH));
198 }
199 
200 ZTEST_SUITE(shm, NULL, NULL, NULL, NULL, NULL);
201