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