1 /*
2  * Copyright (c) 2018 Linaro Limited
3  * Copyright (c) 2024 Tenstorrent AI ULC
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /**
9  * @file
10  * @brief File descriptor table
11  *
12  * This file provides generic file descriptor table implementation, suitable
13  * for any I/O object implementing POSIX I/O semantics (i.e. read/write +
14  * aux operations).
15  */
16 
17 #include <errno.h>
18 #include <string.h>
19 #include <stdio.h>
20 
21 #include <zephyr/posix/fcntl.h>
22 #include <zephyr/kernel.h>
23 #include <zephyr/sys/fdtable.h>
24 #include <zephyr/sys/speculation.h>
25 #include <zephyr/internal/syscall_handler.h>
26 #include <zephyr/sys/atomic.h>
27 
28 struct stat;
29 
30 struct fd_entry {
31 	void *obj;
32 	const struct fd_op_vtable *vtable;
33 	atomic_t refcount;
34 	struct k_mutex lock;
35 	struct k_condvar cond;
36 	size_t offset;
37 	uint32_t mode;
38 };
39 
40 #if defined(CONFIG_POSIX_DEVICE_IO)
41 static const struct fd_op_vtable stdinout_fd_op_vtable;
42 
43 BUILD_ASSERT(CONFIG_ZVFS_OPEN_MAX >= 3, "CONFIG_ZVFS_OPEN_MAX >= 3 for CONFIG_POSIX_DEVICE_IO");
44 #endif /* defined(CONFIG_POSIX_DEVICE_IO) */
45 
46 static struct fd_entry fdtable[CONFIG_ZVFS_OPEN_MAX] = {
47 #if defined(CONFIG_POSIX_DEVICE_IO)
48 	/*
49 	 * Predefine entries for stdin/stdout/stderr.
50 	 */
51 	{
52 		/* STDIN */
53 		.vtable = &stdinout_fd_op_vtable,
54 		.refcount = ATOMIC_INIT(1),
55 		.lock = Z_MUTEX_INITIALIZER(fdtable[0].lock),
56 		.cond = Z_CONDVAR_INITIALIZER(fdtable[0].cond),
57 	},
58 	{
59 		/* STDOUT */
60 		.vtable = &stdinout_fd_op_vtable,
61 		.refcount = ATOMIC_INIT(1),
62 		.lock = Z_MUTEX_INITIALIZER(fdtable[1].lock),
63 		.cond = Z_CONDVAR_INITIALIZER(fdtable[1].cond),
64 	},
65 	{
66 		/* STDERR */
67 		.vtable = &stdinout_fd_op_vtable,
68 		.refcount = ATOMIC_INIT(1),
69 		.lock = Z_MUTEX_INITIALIZER(fdtable[2].lock),
70 		.cond = Z_CONDVAR_INITIALIZER(fdtable[2].cond),
71 	},
72 #else
73 	{0},
74 #endif
75 };
76 
77 static K_MUTEX_DEFINE(fdtable_lock);
78 
z_fd_ref(int fd)79 static int z_fd_ref(int fd)
80 {
81 	return atomic_inc(&fdtable[fd].refcount) + 1;
82 }
83 
z_fd_unref(int fd)84 static int z_fd_unref(int fd)
85 {
86 	atomic_val_t old_rc;
87 
88 	/* Reference counter must be checked to avoid decrement refcount below
89 	 * zero causing file descriptor leak. Loop statement below executes
90 	 * atomic decrement if refcount value is grater than zero. Otherwise,
91 	 * refcount is not going to be written.
92 	 */
93 	do {
94 		old_rc = atomic_get(&fdtable[fd].refcount);
95 		if (!old_rc) {
96 			return 0;
97 		}
98 	} while (!atomic_cas(&fdtable[fd].refcount, old_rc, old_rc - 1));
99 
100 	if (old_rc != 1) {
101 		return old_rc - 1;
102 	}
103 
104 	fdtable[fd].obj = NULL;
105 	fdtable[fd].vtable = NULL;
106 
107 	return 0;
108 }
109 
_find_fd_entry(void)110 static int _find_fd_entry(void)
111 {
112 	int fd;
113 
114 	for (fd = 0; fd < ARRAY_SIZE(fdtable); fd++) {
115 		if (!atomic_get(&fdtable[fd].refcount)) {
116 			return fd;
117 		}
118 	}
119 
120 	errno = ENFILE;
121 	return -1;
122 }
123 
_check_fd(int fd)124 static int _check_fd(int fd)
125 {
126 	if ((fd < 0) || (fd >= ARRAY_SIZE(fdtable))) {
127 		errno = EBADF;
128 		return -1;
129 	}
130 
131 	fd = k_array_index_sanitize(fd, ARRAY_SIZE(fdtable));
132 
133 	if (!atomic_get(&fdtable[fd].refcount)) {
134 		errno = EBADF;
135 		return -1;
136 	}
137 
138 	return 0;
139 }
140 
141 #ifdef CONFIG_ZTEST
fdtable_fd_is_initialized(int fd)142 bool fdtable_fd_is_initialized(int fd)
143 {
144 	struct k_mutex ref_lock;
145 	struct k_condvar ref_cond;
146 
147 	if (fd < 0 || fd >= ARRAY_SIZE(fdtable)) {
148 		return false;
149 	}
150 
151 	ref_lock = (struct k_mutex)Z_MUTEX_INITIALIZER(fdtable[fd].lock);
152 	if (memcmp(&ref_lock, &fdtable[fd].lock, sizeof(ref_lock)) != 0) {
153 		return false;
154 	}
155 
156 	ref_cond = (struct k_condvar)Z_CONDVAR_INITIALIZER(fdtable[fd].cond);
157 	if (memcmp(&ref_cond, &fdtable[fd].cond, sizeof(ref_cond)) != 0) {
158 		return false;
159 	}
160 
161 	return true;
162 }
163 #endif /* CONFIG_ZTEST */
164 
zvfs_get_fd_obj(int fd,const struct fd_op_vtable * vtable,int err)165 void *zvfs_get_fd_obj(int fd, const struct fd_op_vtable *vtable, int err)
166 {
167 	struct fd_entry *entry;
168 
169 	if (_check_fd(fd) < 0) {
170 		return NULL;
171 	}
172 
173 	entry = &fdtable[fd];
174 
175 	if ((vtable != NULL) && (entry->vtable != vtable)) {
176 		errno = err;
177 		return NULL;
178 	}
179 
180 	return entry->obj;
181 }
182 
z_get_fd_by_obj_and_vtable(void * obj,const struct fd_op_vtable * vtable)183 static int z_get_fd_by_obj_and_vtable(void *obj, const struct fd_op_vtable *vtable)
184 {
185 	int fd;
186 
187 	for (fd = 0; fd < ARRAY_SIZE(fdtable); fd++) {
188 		if (fdtable[fd].obj == obj && fdtable[fd].vtable == vtable) {
189 			return fd;
190 		}
191 	}
192 
193 	errno = ENFILE;
194 	return -1;
195 }
196 
zvfs_get_obj_lock_and_cond(void * obj,const struct fd_op_vtable * vtable,struct k_mutex ** lock,struct k_condvar ** cond)197 bool zvfs_get_obj_lock_and_cond(void *obj, const struct fd_op_vtable *vtable, struct k_mutex **lock,
198 			     struct k_condvar **cond)
199 {
200 	int fd;
201 	struct fd_entry *entry;
202 
203 	fd = z_get_fd_by_obj_and_vtable(obj, vtable);
204 	if (_check_fd(fd) < 0) {
205 		return false;
206 	}
207 
208 	entry = &fdtable[fd];
209 
210 	if (lock) {
211 		*lock = &entry->lock;
212 	}
213 
214 	if (cond) {
215 		*cond = &entry->cond;
216 	}
217 
218 	return true;
219 }
220 
zvfs_get_fd_obj_and_vtable(int fd,const struct fd_op_vtable ** vtable,struct k_mutex ** lock)221 void *zvfs_get_fd_obj_and_vtable(int fd, const struct fd_op_vtable **vtable,
222 			      struct k_mutex **lock)
223 {
224 	struct fd_entry *entry;
225 
226 	if (_check_fd(fd) < 0) {
227 		return NULL;
228 	}
229 
230 	entry = &fdtable[fd];
231 	*vtable = entry->vtable;
232 
233 	if (lock != NULL) {
234 		*lock = &entry->lock;
235 	}
236 
237 	return entry->obj;
238 }
239 
zvfs_reserve_fd(void)240 int zvfs_reserve_fd(void)
241 {
242 	int fd;
243 
244 	(void)k_mutex_lock(&fdtable_lock, K_FOREVER);
245 
246 	fd = _find_fd_entry();
247 	if (fd >= 0) {
248 		/* Mark entry as used, zvfs_finalize_fd() will fill it in. */
249 		(void)z_fd_ref(fd);
250 		fdtable[fd].obj = NULL;
251 		fdtable[fd].vtable = NULL;
252 		k_mutex_init(&fdtable[fd].lock);
253 		k_condvar_init(&fdtable[fd].cond);
254 	}
255 
256 	k_mutex_unlock(&fdtable_lock);
257 
258 	return fd;
259 }
260 
zvfs_finalize_typed_fd(int fd,void * obj,const struct fd_op_vtable * vtable,uint32_t mode)261 void zvfs_finalize_typed_fd(int fd, void *obj, const struct fd_op_vtable *vtable, uint32_t mode)
262 {
263 	/* Assumes fd was already bounds-checked. */
264 #ifdef CONFIG_USERSPACE
265 	/* descriptor context objects are inserted into the table when they
266 	 * are ready for use. Mark the object as initialized and grant the
267 	 * caller (and only the caller) access.
268 	 *
269 	 * This call is a no-op if obj is invalid or points to something
270 	 * not a kernel object.
271 	 */
272 	k_object_recycle(obj);
273 #endif
274 	fdtable[fd].obj = obj;
275 	fdtable[fd].vtable = vtable;
276 	fdtable[fd].mode = mode;
277 
278 	/* Let the object know about the lock just in case it needs it
279 	 * for something. For BSD sockets, the lock is used with condition
280 	 * variables to avoid keeping the lock for a long period of time.
281 	 */
282 	if (vtable && vtable->ioctl) {
283 		(void)zvfs_fdtable_call_ioctl(vtable, obj, ZFD_IOCTL_SET_LOCK,
284 					   &fdtable[fd].lock);
285 	}
286 }
287 
zvfs_free_fd(int fd)288 void zvfs_free_fd(int fd)
289 {
290 	/* Assumes fd was already bounds-checked. */
291 	(void)z_fd_unref(fd);
292 }
293 
zvfs_alloc_fd(void * obj,const struct fd_op_vtable * vtable)294 int zvfs_alloc_fd(void *obj, const struct fd_op_vtable *vtable)
295 {
296 	int fd;
297 
298 	fd = zvfs_reserve_fd();
299 	if (fd >= 0) {
300 		zvfs_finalize_fd(fd, obj, vtable);
301 	}
302 
303 	return fd;
304 }
305 
supports_pread_pwrite(uint32_t mode)306 static bool supports_pread_pwrite(uint32_t mode)
307 {
308 	switch (mode & ZVFS_MODE_IFMT) {
309 	case ZVFS_MODE_IFSHM:
310 		return true;
311 	default:
312 		return false;
313 	}
314 }
315 
zvfs_rw(int fd,void * buf,size_t sz,bool is_write,const size_t * from_offset)316 static ssize_t zvfs_rw(int fd, void *buf, size_t sz, bool is_write, const size_t *from_offset)
317 {
318 	bool prw;
319 	ssize_t res;
320 	const size_t *off;
321 
322 	if (_check_fd(fd) < 0) {
323 		return -1;
324 	}
325 
326 	(void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER);
327 
328 	prw = supports_pread_pwrite(fdtable[fd].mode);
329 	if (from_offset != NULL && !prw) {
330 		/*
331 		 * Seekable file types should support pread() / pwrite() and per-fd offset passing.
332 		 * Otherwise, it's a bug.
333 		 */
334 		errno = ENOTSUP;
335 		res = -1;
336 		goto unlock;
337 	}
338 
339 	/* If there is no specified from_offset, then use the current offset of the fd */
340 	off = (from_offset == NULL) ? &fdtable[fd].offset : from_offset;
341 
342 	if (is_write) {
343 		if (fdtable[fd].vtable->write_offs == NULL) {
344 			res = -1;
345 			errno = EIO;
346 		} else {
347 			res = fdtable[fd].vtable->write_offs(fdtable[fd].obj, buf, sz, *off);
348 		}
349 	} else {
350 		if (fdtable[fd].vtable->read_offs == NULL) {
351 			res = -1;
352 			errno = EIO;
353 		} else {
354 			res = fdtable[fd].vtable->read_offs(fdtable[fd].obj, buf, sz, *off);
355 		}
356 	}
357 	if (res > 0 && prw && from_offset == NULL) {
358 		/*
359 		 * only update the fd offset when from_offset is not specified
360 		 * See pread() / pwrite()
361 		 */
362 		fdtable[fd].offset += res;
363 	}
364 
365 unlock:
366 	k_mutex_unlock(&fdtable[fd].lock);
367 
368 	return res;
369 }
370 
zvfs_read(int fd,void * buf,size_t sz,const size_t * from_offset)371 ssize_t zvfs_read(int fd, void *buf, size_t sz, const size_t *from_offset)
372 {
373 	return zvfs_rw(fd, buf, sz, false, from_offset);
374 }
375 
zvfs_write(int fd,const void * buf,size_t sz,const size_t * from_offset)376 ssize_t zvfs_write(int fd, const void *buf, size_t sz, const size_t *from_offset)
377 {
378 	return zvfs_rw(fd, (void *)buf, sz, true, from_offset);
379 }
380 
zvfs_close(int fd)381 int zvfs_close(int fd)
382 {
383 	int res = 0;
384 
385 	if (_check_fd(fd) < 0) {
386 		return -1;
387 	}
388 
389 	(void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER);
390 	if (fdtable[fd].vtable->close != NULL) {
391 		/* close() is optional - e.g. stdinout_fd_op_vtable */
392 		if (fdtable[fd].mode & ZVFS_MODE_IFSOCK) {
393 			/* Network socket needs to know socket number so pass
394 			 * it via close2() call.
395 			 */
396 			res = fdtable[fd].vtable->close2(fdtable[fd].obj, fd);
397 		} else {
398 			res = fdtable[fd].vtable->close(fdtable[fd].obj);
399 		}
400 	}
401 	k_mutex_unlock(&fdtable[fd].lock);
402 
403 	zvfs_free_fd(fd);
404 
405 	return res;
406 }
407 
zvfs_fdopen(int fd,const char * mode)408 FILE *zvfs_fdopen(int fd, const char *mode)
409 {
410 	ARG_UNUSED(mode);
411 
412 	if (_check_fd(fd) < 0) {
413 		return NULL;
414 	}
415 
416 	return (FILE *)&fdtable[fd];
417 }
418 
zvfs_fileno(FILE * file)419 int zvfs_fileno(FILE *file)
420 {
421 	if (!IS_ARRAY_ELEMENT(fdtable, file)) {
422 		errno = EBADF;
423 		return -1;
424 	}
425 
426 	return (struct fd_entry *)file - fdtable;
427 }
428 
zvfs_fstat(int fd,struct stat * buf)429 int zvfs_fstat(int fd, struct stat *buf)
430 {
431 	if (_check_fd(fd) < 0) {
432 		return -1;
433 	}
434 
435 	return zvfs_fdtable_call_ioctl(fdtable[fd].vtable, fdtable[fd].obj, ZFD_IOCTL_STAT, buf);
436 }
437 
zvfs_fsync(int fd)438 int zvfs_fsync(int fd)
439 {
440 	if (_check_fd(fd) < 0) {
441 		return -1;
442 	}
443 
444 	return zvfs_fdtable_call_ioctl(fdtable[fd].vtable, fdtable[fd].obj, ZFD_IOCTL_FSYNC);
445 }
446 
zvfs_lseek_wrap(int fd,int cmd,...)447 static inline off_t zvfs_lseek_wrap(int fd, int cmd, ...)
448 {
449 	off_t res;
450 	va_list args;
451 
452 	__ASSERT_NO_MSG(fd < ARRAY_SIZE(fdtable));
453 
454 	(void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER);
455 	va_start(args, cmd);
456 	res = fdtable[fd].vtable->ioctl(fdtable[fd].obj, cmd, args);
457 	va_end(args);
458 	if (res >= 0) {
459 		switch (fdtable[fd].mode & ZVFS_MODE_IFMT) {
460 		case ZVFS_MODE_IFDIR:
461 		case ZVFS_MODE_IFBLK:
462 		case ZVFS_MODE_IFSHM:
463 		case ZVFS_MODE_IFREG:
464 			fdtable[fd].offset = res;
465 			break;
466 		default:
467 			break;
468 		}
469 	}
470 	k_mutex_unlock(&fdtable[fd].lock);
471 
472 	return res;
473 }
474 
zvfs_lseek(int fd,off_t offset,int whence)475 off_t zvfs_lseek(int fd, off_t offset, int whence)
476 {
477 	if (_check_fd(fd) < 0) {
478 		return -1;
479 	}
480 
481 	return zvfs_lseek_wrap(fd, ZFD_IOCTL_LSEEK, offset, whence, fdtable[fd].offset);
482 }
483 
zvfs_fcntl(int fd,int cmd,va_list args)484 int zvfs_fcntl(int fd, int cmd, va_list args)
485 {
486 	int res;
487 
488 	if (_check_fd(fd) < 0) {
489 		return -1;
490 	}
491 
492 	/* The rest of commands are per-fd, handled by ioctl vmethod. */
493 	res = fdtable[fd].vtable->ioctl(fdtable[fd].obj, cmd, args);
494 
495 	return res;
496 }
497 
zvfs_ftruncate_wrap(int fd,int cmd,...)498 static inline int zvfs_ftruncate_wrap(int fd, int cmd, ...)
499 {
500 	int res;
501 	va_list args;
502 
503 	__ASSERT_NO_MSG(fd < ARRAY_SIZE(fdtable));
504 
505 	(void)k_mutex_lock(&fdtable[fd].lock, K_FOREVER);
506 	va_start(args, cmd);
507 	res = fdtable[fd].vtable->ioctl(fdtable[fd].obj, cmd, args);
508 	va_end(args);
509 	k_mutex_unlock(&fdtable[fd].lock);
510 
511 	return res;
512 }
513 
zvfs_ftruncate(int fd,off_t length)514 int zvfs_ftruncate(int fd, off_t length)
515 {
516 	if (_check_fd(fd) < 0) {
517 		return -1;
518 	}
519 
520 	return zvfs_ftruncate_wrap(fd, ZFD_IOCTL_TRUNCATE, length);
521 }
522 
zvfs_ioctl(int fd,unsigned long request,va_list args)523 int zvfs_ioctl(int fd, unsigned long request, va_list args)
524 {
525 	if (_check_fd(fd) < 0) {
526 		return -1;
527 	}
528 
529 	return fdtable[fd].vtable->ioctl(fdtable[fd].obj, request, args);
530 }
531 
532 
533 #if defined(CONFIG_POSIX_DEVICE_IO)
534 /*
535  * fd operations for stdio/stdout/stderr
536  */
537 
538 int z_impl_zephyr_write_stdout(const char *buf, int nbytes);
539 
stdinout_read_vmeth(void * obj,void * buffer,size_t count)540 static ssize_t stdinout_read_vmeth(void *obj, void *buffer, size_t count)
541 {
542 	return 0;
543 }
544 
stdinout_write_vmeth(void * obj,const void * buffer,size_t count)545 static ssize_t stdinout_write_vmeth(void *obj, const void *buffer, size_t count)
546 {
547 #if defined(CONFIG_BOARD_NATIVE_POSIX)
548 	return zvfs_write(1, buffer, count, NULL);
549 #elif defined(CONFIG_NEWLIB_LIBC) || defined(CONFIG_ARCMWDT_LIBC)
550 	return z_impl_zephyr_write_stdout(buffer, count);
551 #else
552 	return 0;
553 #endif
554 }
555 
stdinout_ioctl_vmeth(void * obj,unsigned int request,va_list args)556 static int stdinout_ioctl_vmeth(void *obj, unsigned int request, va_list args)
557 {
558 	errno = EINVAL;
559 	return -1;
560 }
561 
562 
563 static const struct fd_op_vtable stdinout_fd_op_vtable = {
564 	.read = stdinout_read_vmeth,
565 	.write = stdinout_write_vmeth,
566 	.ioctl = stdinout_ioctl_vmeth,
567 };
568 
569 #endif /* defined(CONFIG_POSIX_DEVICE_IO) */
570