diff options
| author | Bernard Xiong <[email protected]> | 2026-08-17 17:15:34 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-08-17 17:15:34 +0800 |
| commit | 991f0ffca6ca475adcffc7833876bb8151f16e9c (patch) | |
| tree | 5b69ef28de0b15e1a1d23e53136d37c5e86a8a59 /components/dfs | |
| parent | d2acd7587eec680b57f8777c5c7a8dce7369471e (diff) | |
Add AF_UNIX sockets implementation (#11681)
* components/dfs: support unix socket nodes
AF_UNIX pathname binding and descriptor passing require DFSv2 socket
nodes and retained open file descriptions across fd tables.
Add socket-node creation for tmpfs and devtmpfs, fd reference helpers,
and socket F_SETFL forwarding.
Impact: DFSv2 socket nodes and descriptor reference handling.
Validation: git diff --cached --check.
* components/net/sal: support local protocol providers
Local IPC protocol families do not have a backing network device.
Store the selected provider in each SAL socket and add a local provider
registry while preserving netdev checks for Internet sockets.
Handle DFSv2 close semantics, socketpair flags, and MSG_CTRUNC for
AF_UNIX integration.
Impact: SAL protocol dispatch for all socket families.
Validation: git diff --cached --check.
* components/lwp: support unix socket messages
Musl AF_UNIX addresses and ancillary data require explicit ABI and
user-memory conversion at the LWP syscall boundary.
Add the musl msghdr layout, bounded address and message copying,
control-message level conversion, and MSG_CTRUNC translation. Correct
receive buffer allocation and copy lengths while handling messages.
Impact: LWP socket syscalls when SAL is enabled.
Validation: git diff --cached --check.
* components/net/af_unix: add local sockets
Add an opt-in AF_UNIX provider for pathname-based local IPC without
a synthetic network device.
Support datagram and stream sockets, blocking and nonblocking I/O,
timeouts, poll, socketpair, and SCM_RIGHTS descriptor passing. Include
bounded Kconfig settings, component documentation, and utest coverage.
Impact: enabled only by RT_USING_AF_UNIX and requires SAL POSIX with
DFSv2.
Validation: git diff --cached --check.
* Fix AF_UNIX CI checks
Apply the repository clang-format rules to the affected source lines.
Suppress the cppcheck false positive for the devtmpfs list iterator.
No functional behavior is changed.
* components/lwp: preserve NULL optional msghdr buffers
When msg_name or msg_control is NULL, keep the kernel pointer NULL
instead of substituting an uninitialized buffer. Reject a NULL control
buffer with a nonzero length as EFAULT.
Impact: LWP sendmsg/recvmsg conversion on MMU targets.
Validation: git diff --cached --check.
* components/net/af_unix: fix poll UAF, namespace leak, and SCM_RIGHTS cycles
Poll only the local wait queue and wake writers from the receiver.
Free namespace entries on detach, and reject AF_UNIX descriptors in
SCM_RIGHTS until cycle collection exists.
Impact: AF_UNIX poll, bind lifetime, and descriptor passing.
Validation: git diff --cached --check.
* components/dfs: add DFSv2 AF_UNIX utest suite
Add opt-in tests under dfs/utest/v2/af_unix for socket nodes, IPC,
poll peer-close, namespace cleanup, and SCM_RIGHTS rejection.
Sources are built only when RT_UTEST_TC_USING_DFS_V2_AF_UNIX and the
selected test groups are enabled.
Impact: DFS utest menu and build; no production AF_UNIX behavior change.
Validation: git diff --cached --check.
Diffstat (limited to 'components/dfs')
18 files changed, 1525 insertions, 8 deletions
diff --git a/components/dfs/dfs_v2/filesystems/devfs/devtmpfs.c b/components/dfs/dfs_v2/filesystems/devfs/devtmpfs.c index 82ab422529..b60e5c5d0f 100644 --- a/components/dfs/dfs_v2/filesystems/devfs/devtmpfs.c +++ b/components/dfs/dfs_v2/filesystems/devfs/devtmpfs.c @@ -27,6 +27,7 @@ #define TMPFS_TYPE_FILE 0x00 #define TMPFS_TYPE_DIR 0x01 #define TMPFS_TYPE_DYN_DEV 0x02 /* dynamic device */ +#define TMPFS_TYPE_SOCKET 0x03 struct devtmpfs_sb; @@ -223,6 +224,7 @@ find_subpath: if (rt_strcmp(file->name, filename) == 0) { rt_spin_unlock(&superblock->lock); + /* cppcheck-suppress uninitvar */ return file; } } @@ -325,6 +327,10 @@ static int devtmpfs_getdents(struct dfs_file *file, struct dirent *dirp, uint32_ { d->d_type = DT_DIR; } + if (n_file->type == TMPFS_TYPE_SOCKET) + { + d->d_type = DT_SOCK; + } d->d_reclen = (rt_uint16_t)sizeof(struct dirent); rt_strncpy(d->d_name, n_file->name, DIRENT_NAME_MAX); @@ -540,6 +546,14 @@ static struct dfs_vnode *devtmpfs_create_vnode(struct dfs_dentry *dentry, int ty vnode->mode &= ~S_IFMT; vnode->mode |= S_IFDIR; } + else if (type == FT_SOCKET || + (type == FT_REGULAR && S_ISSOCK(mode))) + { + d_file->type = TMPFS_TYPE_SOCKET; + vnode->type = FT_SOCKET; + vnode->mode &= ~S_IFMT; + vnode->mode |= S_IFSOCK; + } else { d_file->type = TMPFS_TYPE_FILE; @@ -585,6 +599,10 @@ static struct dfs_vnode *devtmpfs_lookup(struct dfs_dentry *dentry) { vnode->type = FT_DIRECTORY; } + else if (d_file->type == TMPFS_TYPE_SOCKET) + { + vnode->type = FT_SOCKET; + } else if (d_file->link) { vnode->type = FT_SYMLINK; diff --git a/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c b/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c index 011e6e14c2..cc511415a3 100644 --- a/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c +++ b/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c @@ -535,6 +535,10 @@ static int dfs_tmpfs_getdents(struct dfs_file *file, { d->d_type = DT_DIR; } + if (n_file->type == TMPFS_TYPE_SOCKET) + { + d->d_type = DT_SOCK; + } d->d_namlen = RT_NAME_MAX; d->d_reclen = (rt_uint16_t)sizeof(struct dirent); rt_strncpy(d->d_name, n_file->name, TMPFS_NAME_MAX); @@ -664,6 +668,11 @@ static struct dfs_vnode *_dfs_tmpfs_lookup(struct dfs_dentry *dentry) vnode->mode = S_IFDIR | (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); vnode->type = FT_DIRECTORY; } + else if (d_file->type == TMPFS_TYPE_SOCKET) + { + vnode->mode = S_IFSOCK | (S_IRWXU | S_IRWXG | S_IRWXO); + vnode->type = FT_SOCKET; + } else { vnode->mode = S_IFREG | (S_IRWXU | S_IRWXG | S_IRWXO); @@ -750,6 +759,13 @@ static struct dfs_vnode *dfs_tmpfs_create_vnode(struct dfs_dentry *dentry, int t vnode->mode = S_IFDIR | (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); vnode->type = FT_DIRECTORY; } + else if (type == FT_SOCKET || + (type == FT_REGULAR && S_ISSOCK(mode))) + { + d_file->type = TMPFS_TYPE_SOCKET; + vnode->mode = S_IFSOCK | (mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + vnode->type = FT_SOCKET; + } else { d_file->type = TMPFS_TYPE_FILE; diff --git a/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.h b/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.h index 8f822c6f78..0807f06d19 100644 --- a/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.h +++ b/components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.h @@ -19,6 +19,7 @@ #define TMPFS_TYPE_FILE 0x00 #define TMPFS_TYPE_DIR 0x01 +#define TMPFS_TYPE_SOCKET 0x02 struct tmpfs_sb; @@ -46,4 +47,3 @@ struct tmpfs_sb int dfs_tmpfs_init(void); #endif - diff --git a/components/dfs/dfs_v2/include/dfs.h b/components/dfs/dfs_v2/include/dfs.h index 582ec428ac..a5128b2678 100644 --- a/components/dfs/dfs_v2/include/dfs.h +++ b/components/dfs/dfs_v2/include/dfs.h @@ -134,6 +134,12 @@ int fdt_fd_associate_file(struct dfs_fdtable *fdt, int fd, struct dfs_file *file struct dfs_file *fd_get(int fd); void fd_release(int fd); +/* Reference helpers used when an open file description crosses fd tables. */ +int dfs_file_get_refs(const int *fds, size_t count, struct dfs_file **files); +/* Successful installation transfers the supplied references to the fd table. */ +int dfs_file_install_refs(struct dfs_file **files, size_t count, int *fds); +void dfs_file_put_ref(struct dfs_file *file); + void fd_init(struct dfs_file *fd); struct dfs_fdtable *dfs_fdtable_get(void); diff --git a/components/dfs/dfs_v2/include/dfs_file.h b/components/dfs/dfs_v2/include/dfs_file.h index 0e609e9eef..280d0dbcb9 100644 --- a/components/dfs/dfs_v2/include/dfs_file.h +++ b/components/dfs/dfs_v2/include/dfs_file.h @@ -149,6 +149,7 @@ void dfs_file_init(struct dfs_file *file); void dfs_file_deinit(struct dfs_file *file); int dfs_file_open(struct dfs_file *file, const char *path, int flags, mode_t mode); +int dfs_file_mknod(const char *path, int type, mode_t mode); int dfs_file_close(struct dfs_file *file); off_t dfs_file_get_fpos(struct dfs_file *file); diff --git a/components/dfs/dfs_v2/src/dfs.c b/components/dfs/dfs_v2/src/dfs.c index de1ea79edc..92c688a8a0 100644 --- a/components/dfs/dfs_v2/src/dfs.c +++ b/components/dfs/dfs_v2/src/dfs.c @@ -349,23 +349,29 @@ int fdt_fd_new(struct dfs_fdtable *fdt) */ void fdt_fd_release(struct dfs_fdtable *fdt, int fd) { - if (fd < fdt->maxfd) + if (fdt == RT_NULL || dfs_file_lock() != RT_EOK) + { + return; + } + + if (fd >= 0 && fd < (int)fdt->maxfd) { struct dfs_file *file; file = fdt_get_file(fdt, fd); - if (file && file->ref_count == 1) + if (file != RT_NULL && file->ref_count == 1) { dfs_file_destroy(file); } - else + else if (file != RT_NULL) { rt_atomic_sub(&(file->ref_count), 1); } fdt->fds[fd] = RT_NULL; } + dfs_file_unlock(); } /** @@ -493,6 +499,106 @@ struct dfs_file *fd_get(int fd) return fdt_get_file(fdt, fd); } +int dfs_file_get_refs(const int *fds, size_t count, struct dfs_file **files) +{ + size_t index; + struct dfs_fdtable *fdt; + + if ((count != 0 && (fds == RT_NULL || files == RT_NULL)) || + dfs_file_lock() != RT_EOK) + { + return -EINVAL; + } + + fdt = dfs_fdtable_get(); + for (index = 0; index < count; index++) + { + files[index] = fdt_get_file(fdt, fds[index]); + if (files[index] == RT_NULL || + (files[index]->dentry == RT_NULL && files[index]->vnode == RT_NULL)) + { + dfs_file_unlock(); + return -EBADF; + } + } + + for (index = 0; index < count; index++) + { + rt_atomic_add(&files[index]->ref_count, 1); + } + dfs_file_unlock(); + return 0; +} + +int dfs_file_install_refs(struct dfs_file **files, size_t count, int *fds) +{ + int fd; + int startfd; + size_t index; + struct dfs_fdtable *fdt; + + if ((count != 0 && (files == RT_NULL || fds == RT_NULL)) || + dfs_file_lock() != RT_EOK) + { + return -EINVAL; + } + + fdt = dfs_fdtable_get(); + startfd = (fdt == &_fdtab) ? DFS_STDIO_OFFSET : 0; + for (index = 0; index < count; index++) + { + if (files[index] == RT_NULL || files[index]->magic != DFS_FD_MAGIC) + { + break; + } + + fd = _fdt_slot_alloc(fdt, startfd); + if (fd < 0) + { + break; + } + fdt->fds[fd] = files[index]; + fds[index] = fd; + } + + if (index != count) + { + while (index > 0) + { + index--; + fdt->fds[fds[index]] = RT_NULL; + } + dfs_file_unlock(); + return -EMFILE; + } + + dfs_file_unlock(); + return 0; +} + +void dfs_file_put_ref(struct dfs_file *file) +{ + if (file == RT_NULL || dfs_file_lock() != RT_EOK) + { + return; + } + + if (file->magic == DFS_FD_MAGIC && + rt_atomic_load(&file->ref_count) > 0 && + dfs_file_close(file) == 0) + { + if (rt_atomic_load(&file->ref_count) == 1) + { + dfs_file_destroy(file); + } + else + { + rt_atomic_sub(&file->ref_count, 1); + } + } + dfs_file_unlock(); +} + /** * This function will get the file descriptor table of current process. */ @@ -1238,4 +1344,4 @@ MSH_CMD_EXPORT(dfs_dlog, dfs dlog on|off); #endif #endif -/** @} */
\ No newline at end of file +/** @} */ diff --git a/components/dfs/dfs_v2/src/dfs_file.c b/components/dfs/dfs_v2/src/dfs_file.c index 32a18dab3d..6b16159396 100644 --- a/components/dfs/dfs_v2/src/dfs_file.c +++ b/components/dfs/dfs_v2/src/dfs_file.c @@ -823,6 +823,128 @@ _ERR_RET: return ret; } +int dfs_file_mknod(const char *path, int type, mode_t mode) +{ + int ret = -EINVAL; + int create_type = type; + mode_t create_mode = mode; + char *fullpath; + struct dfs_mnt *mnt; + struct dfs_dentry *dentry; + + if (path == RT_NULL || type < FT_REGULAR || type > FT_NONLOCK) + { + return -EINVAL; + } + + fullpath = dfs_normalize_path(RT_NULL, path); + if (fullpath == RT_NULL) + { + return -ENOMEM; + } + + mnt = dfs_mnt_lookup(fullpath); + if (mnt == RT_NULL) + { + ret = -ENOENT; + goto __exit; + } + + { + char *realpath; + + realpath = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST); + if (realpath != RT_NULL) + { + rt_free(fullpath); + fullpath = realpath; + } + } + + if (strcmp(mnt->fullpath, fullpath) == 0) + { + ret = -EEXIST; + goto __exit; + } + + dentry = dfs_dentry_lookup(mnt, fullpath, 0); + if (dentry != RT_NULL) + { + dfs_dentry_unref(dentry); + ret = -EEXIST; + goto __exit; + } + + if (mnt->fs_ops->create_vnode == RT_NULL) + { + ret = -ENOSYS; + goto __exit; + } + + if (type == FT_SOCKET) + { + create_type = FT_REGULAR; + create_mode = (mode & ~S_IFMT) | S_IFSOCK; + } + + ret = dfs_file_lock(); + if (ret != RT_EOK) + { + goto __exit; + } + + dentry = dfs_dentry_create(mnt, fullpath); + if (dentry != RT_NULL) + { + struct dfs_vnode *vnode = RT_NULL; + + if (dfs_is_mounted(mnt) == 0) + { + vnode = mnt->fs_ops->create_vnode(dentry, create_type, + create_mode); + } + if (vnode != RT_NULL) + { + if (type == FT_SOCKET && !S_ISSOCK(vnode->mode)) + { + if (mnt->fs_ops->unlink != RT_NULL) + { + dentry->vnode = vnode; + (void)mnt->fs_ops->unlink(dentry); + dentry->vnode = RT_NULL; + } + dfs_vnode_unref(vnode); + ret = -EOPNOTSUPP; + } + else + { + vnode->type = type; + dentry->vnode = vnode; + dfs_dentry_insert(dentry); + ret = RT_EOK; + } + } + else + { + ret = -ENOENT; + } + } + else + { + ret = -ENOMEM; + } + dfs_file_unlock(); + + if (dentry != RT_NULL) + { + dfs_dentry_unref(dentry); + } + +__exit: + rt_free(fullpath); + return ret; +} + /** * @brief Close a file and release associated resources * @@ -1588,6 +1710,15 @@ int dfs_file_fcntl(int fd, int cmd, unsigned long arg) O_APPEND | O_NONBLOCK; flags &= mask; + if (file->vnode->type == FT_SOCKET && file->fops != RT_NULL && + file->fops->ioctl != RT_NULL) + { + ret = file->fops->ioctl(file, F_SETFL, (void *)(rt_base_t)flags); + if (ret < 0) + { + break; + } + } file->flags &= ~mask; file->flags |= flags; break; @@ -3035,4 +3166,4 @@ void copy(const char *src, const char *dst) } FINSH_FUNCTION_EXPORT(copy, copy file or dir) -#endif
\ No newline at end of file +#endif diff --git a/components/dfs/utest/Kconfig b/components/dfs/utest/Kconfig index dde4cffca0..32acadbf1a 100644 --- a/components/dfs/utest/Kconfig +++ b/components/dfs/utest/Kconfig @@ -40,5 +40,8 @@ if RT_USING_DFS This device will be formatted and mounted during testing endif + + rsource "v2/af_unix/Kconfig" + endmenu endif diff --git a/components/dfs/utest/SConscript b/components/dfs/utest/SConscript index 2375461741..bb8fa66a35 100644 --- a/components/dfs/utest/SConscript +++ b/components/dfs/utest/SConscript @@ -1,5 +1,6 @@ Import('rtconfig') from building import * +import os cwd = GetCurrentDir() src = [] @@ -15,6 +16,7 @@ if GetDepend('RT_UTEST_TC_USING_DFS_API'): src += ['tc_posix_api.c'] # Define the test group with proper dependencies -group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_DFS'], CPPPATH = CPPPATH) +objs = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_DFS'], CPPPATH = CPPPATH) +objs = objs + SConscript(os.path.join('v2', 'af_unix', 'SConscript')) -Return('group') +Return('objs') diff --git a/components/dfs/utest/v2/af_unix/Kconfig b/components/dfs/utest/v2/af_unix/Kconfig new file mode 100644 index 0000000000..775daf5814 --- /dev/null +++ b/components/dfs/utest/v2/af_unix/Kconfig @@ -0,0 +1,95 @@ +menuconfig RT_UTEST_TC_USING_DFS_V2_AF_UNIX + bool "DFSv2 AF_UNIX Test" + default n + depends on RT_USING_DFS_V2 + depends on RT_USING_AF_UNIX + depends on SAL_USING_POSIX + help + Enable DFSv2 AF_UNIX unit tests. Sources are compiled only when + this option and the selected test groups below are enabled. + + Coverage includes socket-node creation, transferable file + references, datagram and stream IPC, poll readiness, pathname + lifetime, namespace cleanup, and SCM_RIGHTS handling. + +if RT_UTEST_TC_USING_DFS_V2_AF_UNIX + +config RT_UTEST_DFS_V2_AF_UNIX_DIR + string "Directory for AF_UNIX pathname tests" + default "/dev" + help + Directory used to create temporary AF_UNIX pathname nodes. + The filesystem mounted here must support S_IFSOCK nodes + through create_vnode(), such as tmpfs or devtmpfs. + +config RT_UTEST_DFS_V2_AF_UNIX_NODE + bool "Socket node and file-reference tests" + default y + help + Build tests for DFSv2 socket nodes and transferable file + references: + * dfs_file_mknod(FT_SOCKET) creates an S_ISSOCK node + * creating the same path again returns EEXIST + * unlink() removes the socket node + * dfs_file_get_refs() rejects an invalid fd + * get_refs() + put_ref() keeps the original fd usable + * get_refs() + install_refs() transfers a pipe and shares + the open file description + +config RT_UTEST_DFS_V2_AF_UNIX_IPC + bool "Datagram, stream and socketpair IPC tests" + default y + help + Build tests for AF_UNIX IPC operations: + * SOCK_DGRAM bind, connect, send and recvfrom + * SOCK_STREAM listen, accept, send, recv and shutdown + * socketpair() for both SOCK_DGRAM and SOCK_STREAM + * sendmsg()/recvmsg() scatter-gather I/O + * connected sendmsg() with a NULL msg_name + * sendmsg() with a NULL msg_control and nonzero length + * listen() on SOCK_DGRAM returns EOPNOTSUPP + * connect() to a missing pathname returns ENOENT + * non-blocking recv() returns EAGAIN + +config RT_UTEST_DFS_V2_AF_UNIX_POLL + bool "Poll readiness and peer-close tests" + default y + help + Build tests for AF_UNIX poll/select readiness: + * empty stream socket reports POLLOUT and not POLLIN + * received data reports POLLIN + * filling the stream buffer clears POLLOUT; a peer recv + makes POLLOUT ready again + * blocking poll(POLLIN) wakes with POLLHUP/POLLIN when + the peer is closed (covers the peer-queue UAF case) + * datagram queue-full and POLLIN readiness + +config RT_UTEST_DFS_V2_AF_UNIX_NAMESPACE + bool "Pathname bind, unlink and namespace tests" + default y + help + Build tests for pathname lifetime and namespace cleanup: + * bind() creates a persistent S_IFSOCK node + * close() leaves the pathname in the filesystem + * rebinding without unlink() returns EADDRINUSE + * unlink() then bind() succeeds + * connect() after close without unlink() returns + ECONNREFUSED + * connect() after unlink() returns ENOENT + * repeated bind/close/unlink on distinct paths does not + leak namespace entries + +config RT_UTEST_DFS_V2_AF_UNIX_RIGHTS + bool "SCM_RIGHTS transfer and rejection tests" + default y + help + Build tests for SCM_RIGHTS descriptor passing: + * transfer pipe descriptors over SOCK_DGRAM and + SOCK_STREAM + * recvmsg() without a control buffer reports MSG_CTRUNC + * MSG_PEEK does not install or consume descriptors + * an invalid fd returns EBADF + * passing an AF_UNIX socket descriptor returns + EOPNOTSUPP and does not create a reference cycle + +endif diff --git a/components/dfs/utest/v2/af_unix/SConscript b/components/dfs/utest/v2/af_unix/SConscript new file mode 100644 index 0000000000..19cbb9e8e3 --- /dev/null +++ b/components/dfs/utest/v2/af_unix/SConscript @@ -0,0 +1,27 @@ +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = [] +CPPPATH = [cwd] + +if GetDepend('RT_UTEST_TC_USING_DFS_V2_AF_UNIX'): + cases = [] + if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_NODE'): + cases += ['tc_af_unix_node.c'] + if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_IPC'): + cases += ['tc_af_unix_ipc.c'] + if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_POLL'): + cases += ['tc_af_unix_poll.c'] + if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_NAMESPACE'): + cases += ['tc_af_unix_namespace.c'] + if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_RIGHTS'): + cases += ['tc_af_unix_rights.c'] + if cases: + src += ['tc_af_unix_common.c'] + cases + +group = DefineGroup('utestcases', src, + depend = ['RT_USING_UTESTCASES', 'RT_USING_DFS_V2', 'RT_USING_AF_UNIX'], + CPPPATH = CPPPATH) + +Return('group') diff --git a/components/dfs/utest/v2/af_unix/tc_af_unix.h b/components/dfs/utest/v2/af_unix/tc_af_unix.h new file mode 100644 index 0000000000..334ec22973 --- /dev/null +++ b/components/dfs/utest/v2/af_unix/tc_af_unix.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef TC_AF_UNIX_H__ +#define TC_AF_UNIX_H__ + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <dfs.h> +#include <dfs_file.h> +#include <rtthread.h> +#include <utest.h> + +#ifndef RT_UTEST_DFS_V2_AF_UNIX_DIR +#define RT_UTEST_DFS_V2_AF_UNIX_DIR "/dev" +#endif + +#define TC_AF_UNIX_PATH_MAX 64 + +void tc_af_unix_path(char *buf, rt_size_t size, const char *name); +void tc_af_unix_make_address(struct sockaddr_un *address, const char *path); +void tc_af_unix_closesocket(int *fd); +void tc_af_unix_close(int *fd); +int tc_af_unix_send_rights(int socket_fd, const int *fds, size_t fd_count, + char payload); +int tc_af_unix_receive_rights(int socket_fd, int flags, size_t control_length, + int *fds, size_t *fd_count, int *message_flags, + char *payload); + +#endif /* TC_AF_UNIX_H__ */ diff --git a/components/dfs/utest/v2/af_unix/tc_af_unix_common.c b/components/dfs/utest/v2/af_unix/tc_af_unix_common.c new file mode 100644 index 0000000000..faa206cee4 --- /dev/null +++ b/components/dfs/utest/v2/af_unix/tc_af_unix_common.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tc_af_unix.h" + +void tc_af_unix_path(char *buf, rt_size_t size, const char *name) +{ + rt_snprintf(buf, size, "%s/%s", RT_UTEST_DFS_V2_AF_UNIX_DIR, name); +} + +void tc_af_unix_make_address(struct sockaddr_un *address, const char *path) +{ + rt_memset(address, 0, sizeof(*address)); + address->sa_family = AF_UNIX; + rt_strncpy(address->sun_path, path, sizeof(address->sun_path) - 1); +} + +void tc_af_unix_closesocket(int *fd) +{ + if (*fd >= 0) + { + closesocket(*fd); + *fd = -1; + } +} + +void tc_af_unix_close(int *fd) +{ + if (*fd >= 0) + { + close(*fd); + *fd = -1; + } +} + +int tc_af_unix_send_rights(int socket_fd, const int *fds, size_t fd_count, + char payload) +{ + char control[CMSG_SPACE(2 * sizeof(int))]; + struct cmsghdr *cmsg; + struct iovec iov; + struct msghdr message; + + rt_memset(&message, 0, sizeof(message)); + rt_memset(control, 0, sizeof(control)); + iov.iov_base = &payload; + iov.iov_len = sizeof(payload); + message.msg_iov = &iov; + message.msg_iovlen = 1; + message.msg_control = control; + message.msg_controllen = CMSG_SPACE(fd_count * sizeof(int)); + cmsg = CMSG_FIRSTHDR(&message); + cmsg->cmsg_len = CMSG_LEN(fd_count * sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + rt_memcpy(CMSG_DATA(cmsg), fds, fd_count * sizeof(int)); + return sendmsg(socket_fd, &message, 0); +} + +int tc_af_unix_receive_rights(int socket_fd, int flags, size_t control_length, + int *fds, size_t *fd_count, int *message_flags, + char *payload) +{ + char control[CMSG_SPACE(2 * sizeof(int))]; + int result; + size_t count = 0; + struct cmsghdr *cmsg; + struct iovec iov; + struct msghdr message; + + rt_memset(&message, 0, sizeof(message)); + rt_memset(control, 0, sizeof(control)); + iov.iov_base = payload; + iov.iov_len = sizeof(*payload); + message.msg_iov = &iov; + message.msg_iovlen = 1; + message.msg_control = control_length != 0 ? control : RT_NULL; + message.msg_controllen = control_length; + + result = recvmsg(socket_fd, &message, flags); + if (result >= 0) + { + cmsg = CMSG_FIRSTHDR(&message); + if (cmsg != RT_NULL && cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS && + cmsg->cmsg_len >= CMSG_LEN(sizeof(int))) + { + count = (cmsg->cmsg_len - sizeof(*cmsg)) / sizeof(int); + rt_memcpy(fds, CMSG_DATA(cmsg), count * sizeof(int)); + } + *fd_count = count; + *message_flags = message.msg_flags; + } + return result; +} diff --git a/components/dfs/utest/v2/af_unix/tc_af_unix_ipc.c b/components/dfs/utest/v2/af_unix/tc_af_unix_ipc.c new file mode 100644 index 0000000000..26fde60454 --- /dev/null +++ b/components/dfs/utest/v2/af_unix/tc_af_unix_ipc.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tc_af_unix.h" + +static void tc_af_unix_dgram_ipc(void) +{ + char buffer[16]; + char client_path[TC_AF_UNIX_PATH_MAX]; + char server_path[TC_AF_UNIX_PATH_MAX]; + const char payload[] = "datagram"; + int client = -1; + int error; + int result; + int server = -1; + socklen_t source_length; + struct sockaddr_un client_address; + struct sockaddr_un server_address; + struct sockaddr_un source_address; + + tc_af_unix_path(server_path, sizeof(server_path), "utafudgs"); + tc_af_unix_path(client_path, sizeof(client_path), "utafudgc"); + (void)unlink(server_path); + (void)unlink(client_path); + tc_af_unix_make_address(&server_address, server_path); + tc_af_unix_make_address(&client_address, client_path); + + server = socket(AF_UNIX, SOCK_DGRAM, 0); + client = socket(AF_UNIX, SOCK_DGRAM, 0); + uassert_true(server >= 0); + uassert_true(client >= 0); + if (server < 0 || client < 0) + { + goto __exit; + } + + uassert_int_equal(bind(server, (struct sockaddr *)&server_address, + sizeof(server_address)), + 0); + uassert_int_equal(bind(client, (struct sockaddr *)&client_address, + sizeof(client_address)), + 0); + uassert_int_equal(connect(client, (struct sockaddr *)&server_address, + sizeof(server_address)), + 0); + uassert_int_equal(send(client, payload, sizeof(payload), 0), + sizeof(payload)); + + source_length = sizeof(source_address); + result = recvfrom(server, buffer, sizeof(buffer), 0, + (struct sockaddr *)&source_address, &source_length); + uassert_int_equal(result, sizeof(payload)); + uassert_buf_equal(buffer, payload, sizeof(payload)); + uassert_str_equal(source_address.sun_path, client_path); + + result = fcntl(server, F_SETFL, O_NONBLOCK); + uassert_int_equal(result, 0); + result = recv(server, buffer, sizeof(buffer), 0); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_true(error == EAGAIN || error == EWOULDBLOCK); + + result = listen(server, 1); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, EOPNOTSUPP); + +__exit: + tc_af_unix_closesocket(&client); + tc_af_unix_closesocket(&server); + (void)unlink(client_path); + (void)unlink(server_path); +} + +static void tc_af_unix_stream_ipc(void) +{ + char accepted_path[TC_AF_UNIX_PATH_MAX]; + char buffer[16]; + const char request[] = "request"; + const char response[] = "response"; + int accepted = -1; + int client = -1; + int listener = -1; + struct sockaddr_un address; + struct sockaddr_un local_address; + struct sockaddr_un peer_address; + socklen_t local_length; + socklen_t peer_length; + + tc_af_unix_path(accepted_path, sizeof(accepted_path), "utafustr"); + (void)unlink(accepted_path); + tc_af_unix_make_address(&address, accepted_path); + + listener = socket(AF_UNIX, SOCK_STREAM, 0); + client = socket(AF_UNIX, SOCK_STREAM, 0); + uassert_true(listener >= 0); + uassert_true(client >= 0); + if (listener < 0 || client < 0) + { + goto __exit; + } + + uassert_int_equal(bind(listener, (struct sockaddr *)&address, + sizeof(address)), + 0); + uassert_int_equal(listen(listener, 2), 0); + uassert_int_equal(connect(client, (struct sockaddr *)&address, + sizeof(address)), + 0); + accepted = accept(listener, RT_NULL, RT_NULL); + uassert_true(accepted >= 0); + if (accepted < 0) + { + goto __exit; + } + + local_length = sizeof(local_address); + peer_length = sizeof(peer_address); + uassert_int_equal(getsockname(accepted, (struct sockaddr *)&local_address, + &local_length), + 0); + uassert_int_equal(getpeername(client, (struct sockaddr *)&peer_address, + &peer_length), + 0); + uassert_str_equal(local_address.sun_path, accepted_path); + uassert_str_equal(peer_address.sun_path, accepted_path); + + uassert_int_equal(send(client, request, sizeof(request), 0), + sizeof(request)); + uassert_int_equal(recv(accepted, buffer, sizeof(buffer), 0), + sizeof(request)); + uassert_buf_equal(buffer, request, sizeof(request)); + uassert_int_equal(send(accepted, response, sizeof(response), 0), + sizeof(response)); + uassert_int_equal(recv(client, buffer, sizeof(buffer), 0), + sizeof(response)); + uassert_buf_equal(buffer, response, sizeof(response)); + + uassert_int_equal(shutdown(client, SHUT_WR), 0); + uassert_int_equal(recv(accepted, buffer, sizeof(buffer), 0), 0); + +__exit: + tc_af_unix_closesocket(&accepted); + tc_af_unix_closesocket(&client); + tc_af_unix_closesocket(&listener); + (void)unlink(accepted_path); +} + +static void tc_af_unix_socketpair_msg(void) +{ + char buffer[8]; + char first_part[3]; + char second_part[3]; + const char payload[] = "pair"; + int sockets[2] = { -1, -1 }; + int type_index; + int types[2] = { SOCK_DGRAM, SOCK_STREAM }; + struct iovec receive_iov[2]; + struct iovec send_iov[2]; + struct msghdr receive_message; + struct msghdr send_message; + + for (type_index = 0; type_index < 2; type_index++) + { + uassert_int_equal(socketpair(AF_UNIX, types[type_index], 0, sockets), + 0); + uassert_int_equal(send(sockets[0], payload, sizeof(payload), 0), + sizeof(payload)); + uassert_int_equal(recv(sockets[1], buffer, sizeof(buffer), 0), + sizeof(payload)); + uassert_buf_equal(buffer, payload, sizeof(payload)); + + rt_memset(&send_message, 0, sizeof(send_message)); + send_iov[0].iov_base = (void *)payload; + send_iov[0].iov_len = 2; + send_iov[1].iov_base = (void *)(payload + 2); + send_iov[1].iov_len = sizeof(payload) - 2; + send_message.msg_iov = send_iov; + send_message.msg_iovlen = 2; + send_message.msg_name = RT_NULL; + send_message.msg_namelen = sizeof(struct sockaddr_un); + uassert_int_equal(sendmsg(sockets[0], &send_message, 0), + sizeof(payload)); + + rt_memset(&receive_message, 0, sizeof(receive_message)); + receive_iov[0].iov_base = first_part; + receive_iov[0].iov_len = sizeof(first_part); + receive_iov[1].iov_base = second_part; + receive_iov[1].iov_len = sizeof(second_part); + receive_message.msg_iov = receive_iov; + receive_message.msg_iovlen = 2; + uassert_int_equal(recvmsg(sockets[1], &receive_message, 0), + sizeof(payload)); + uassert_buf_equal(first_part, payload, sizeof(first_part)); + uassert_buf_equal(second_part, payload + sizeof(first_part), + sizeof(payload) - sizeof(first_part)); + + send_message.msg_control = RT_NULL; + send_message.msg_controllen = CMSG_SPACE(sizeof(int)); + uassert_int_equal(sendmsg(sockets[0], &send_message, 0), -1); + uassert_int_equal(rt_get_errno(), EINVAL); + + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); + } +} + +static void tc_af_unix_connect_missing(void) +{ + char path[TC_AF_UNIX_PATH_MAX]; + int client = -1; + int error; + int result; + struct sockaddr_un address; + + tc_af_unix_path(path, sizeof(path), "utafumiss"); + (void)unlink(path); + tc_af_unix_make_address(&address, path); + + client = socket(AF_UNIX, SOCK_STREAM, 0); + uassert_true(client >= 0); + if (client < 0) + { + return; + } + + result = connect(client, (struct sockaddr *)&address, sizeof(address)); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, ENOENT); + + tc_af_unix_closesocket(&client); +} + +static void tc_af_unix_pending_close(void) +{ + char path[TC_AF_UNIX_PATH_MAX]; + const char payload[] = "pending"; + int client = -1; + int error; + int listener = -1; + int result; + struct sockaddr_un address; + + tc_af_unix_path(path, sizeof(path), "utafupend"); + (void)unlink(path); + tc_af_unix_make_address(&address, path); + + listener = socket(AF_UNIX, SOCK_STREAM, 0); + client = socket(AF_UNIX, SOCK_STREAM, 0); + uassert_true(listener >= 0); + uassert_true(client >= 0); + if (listener < 0 || client < 0) + { + goto __exit; + } + + uassert_int_equal(bind(listener, (struct sockaddr *)&address, + sizeof(address)), + 0); + uassert_int_equal(listen(listener, 1), 0); + uassert_int_equal(connect(client, (struct sockaddr *)&address, + sizeof(address)), + 0); + tc_af_unix_closesocket(&listener); + + result = send(client, payload, sizeof(payload), 0); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_true(error == ENOTCONN || error == EPIPE); + +__exit: + tc_af_unix_closesocket(&client); + tc_af_unix_closesocket(&listener); + (void)unlink(path); +} + +static void tc_af_unix_ipc_testcase(void) +{ + UTEST_UNIT_RUN(tc_af_unix_dgram_ipc); + UTEST_UNIT_RUN(tc_af_unix_stream_ipc); + UTEST_UNIT_RUN(tc_af_unix_socketpair_msg); + UTEST_UNIT_RUN(tc_af_unix_connect_missing); + UTEST_UNIT_RUN(tc_af_unix_pending_close); +} + +UTEST_TC_EXPORT(tc_af_unix_ipc_testcase, "components.dfs.v2.af_unix.ipc", + RT_NULL, RT_NULL, 15); diff --git a/components/dfs/utest/v2/af_unix/tc_af_unix_namespace.c b/components/dfs/utest/v2/af_unix/tc_af_unix_namespace.c new file mode 100644 index 0000000000..557d4e879c --- /dev/null +++ b/components/dfs/utest/v2/af_unix/tc_af_unix_namespace.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tc_af_unix.h" + +#define TC_AF_UNIX_NAMESPACE_REPEAT 8 + +static void tc_af_unix_pathname_lifetime(void) +{ + char path[TC_AF_UNIX_PATH_MAX]; + int error; + int first = -1; + int result; + int second = -1; + struct sockaddr_un address; + struct stat file_stat; + + tc_af_unix_path(path, sizeof(path), "utafupath"); + (void)unlink(path); + tc_af_unix_make_address(&address, path); + + first = socket(AF_UNIX, SOCK_DGRAM, 0); + uassert_true(first >= 0); + if (first < 0) + { + goto __exit; + } + uassert_int_equal(bind(first, (struct sockaddr *)&address, sizeof(address)), + 0); + uassert_int_equal(stat(path, &file_stat), 0); + uassert_true(S_ISSOCK(file_stat.st_mode)); + + tc_af_unix_closesocket(&first); + uassert_int_equal(stat(path, &file_stat), 0); + uassert_true(S_ISSOCK(file_stat.st_mode)); + + second = socket(AF_UNIX, SOCK_DGRAM, 0); + uassert_true(second >= 0); + if (second < 0) + { + goto __exit; + } + result = bind(second, (struct sockaddr *)&address, sizeof(address)); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, EADDRINUSE); + + uassert_int_equal(unlink(path), 0); + uassert_int_equal(bind(second, (struct sockaddr *)&address, + sizeof(address)), + 0); + +__exit: + tc_af_unix_closesocket(&second); + tc_af_unix_closesocket(&first); + (void)unlink(path); +} + +static void tc_af_unix_lookup_after_close(void) +{ + char path[TC_AF_UNIX_PATH_MAX]; + int client = -1; + int error; + int result; + int server = -1; + struct sockaddr_un address; + + tc_af_unix_path(path, sizeof(path), "utafuref"); + (void)unlink(path); + tc_af_unix_make_address(&address, path); + + server = socket(AF_UNIX, SOCK_STREAM, 0); + client = socket(AF_UNIX, SOCK_STREAM, 0); + uassert_true(server >= 0); + uassert_true(client >= 0); + if (server < 0 || client < 0) + { + goto __exit; + } + + uassert_int_equal(bind(server, (struct sockaddr *)&address, + sizeof(address)), + 0); + uassert_int_equal(listen(server, 1), 0); + tc_af_unix_closesocket(&server); + + result = connect(client, (struct sockaddr *)&address, sizeof(address)); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, ECONNREFUSED); + + uassert_int_equal(unlink(path), 0); + result = connect(client, (struct sockaddr *)&address, sizeof(address)); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, ENOENT); + +__exit: + tc_af_unix_closesocket(&client); + tc_af_unix_closesocket(&server); + (void)unlink(path); +} + +static void tc_af_unix_namespace_reuse(void) +{ + char name[16]; + char path[TC_AF_UNIX_PATH_MAX]; + int client = -1; + int error; + int index; + int result; + int sock = -1; + struct sockaddr_un address; + + for (index = 0; index < TC_AF_UNIX_NAMESPACE_REPEAT; index++) + { + rt_snprintf(name, sizeof(name), "utafuns%d", index); + tc_af_unix_path(path, sizeof(path), name); + (void)unlink(path); + tc_af_unix_make_address(&address, path); + + sock = socket(AF_UNIX, SOCK_DGRAM, 0); + uassert_true(sock >= 0); + if (sock < 0) + { + return; + } + uassert_int_equal(bind(sock, (struct sockaddr *)&address, + sizeof(address)), + 0); + tc_af_unix_closesocket(&sock); + uassert_int_equal(unlink(path), 0); + } + + tc_af_unix_path(path, sizeof(path), "utafuns0"); + tc_af_unix_make_address(&address, path); + client = socket(AF_UNIX, SOCK_DGRAM, 0); + uassert_true(client >= 0); + if (client < 0) + { + return; + } + result = connect(client, (struct sockaddr *)&address, sizeof(address)); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, ENOENT); + tc_af_unix_closesocket(&client); + + tc_af_unix_path(path, sizeof(path), "utafunew"); + (void)unlink(path); + tc_af_unix_make_address(&address, path); + sock = socket(AF_UNIX, SOCK_DGRAM, 0); + uassert_true(sock >= 0); + if (sock < 0) + { + return; + } + uassert_int_equal(bind(sock, (struct sockaddr *)&address, sizeof(address)), + 0); + tc_af_unix_closesocket(&sock); + (void)unlink(path); +} + +static void tc_af_unix_namespace_testcase(void) +{ + UTEST_UNIT_RUN(tc_af_unix_pathname_lifetime); + UTEST_UNIT_RUN(tc_af_unix_lookup_after_close); + UTEST_UNIT_RUN(tc_af_unix_namespace_reuse); +} + +UTEST_TC_EXPORT(tc_af_unix_namespace_testcase, + "components.dfs.v2.af_unix.namespace", RT_NULL, RT_NULL, 15); diff --git a/components/dfs/utest/v2/af_unix/tc_af_unix_node.c b/components/dfs/utest/v2/af_unix/tc_af_unix_node.c new file mode 100644 index 0000000000..c55679c654 --- /dev/null +++ b/components/dfs/utest/v2/af_unix/tc_af_unix_node.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tc_af_unix.h" + +static void tc_af_unix_mknod_socket(void) +{ + char path[TC_AF_UNIX_PATH_MAX]; + int result; + struct stat file_stat; + + tc_af_unix_path(path, sizeof(path), "utafumkn"); + (void)unlink(path); + + result = dfs_file_mknod(path, FT_SOCKET, S_IRWXU | S_IRWXG | S_IRWXO); + uassert_int_equal(result, 0); + if (result != 0) + { + return; + } + + uassert_int_equal(stat(path, &file_stat), 0); + uassert_true(S_ISSOCK(file_stat.st_mode)); + + result = dfs_file_mknod(path, FT_SOCKET, S_IRWXU | S_IRWXG | S_IRWXO); + uassert_int_equal(result, -EEXIST); + + uassert_int_equal(unlink(path), 0); + uassert_true(stat(path, &file_stat) < 0); +} + +static void tc_af_unix_file_refs(void) +{ + char data[4]; + int installed = -1; + int invalid_fd = -1; + int pipe_fds[2] = { -1, -1 }; + struct dfs_file *files[1]; + + files[0] = RT_NULL; + uassert_int_equal(dfs_file_get_refs(&invalid_fd, 1, files), -EBADF); + uassert_true(files[0] == RT_NULL); + + uassert_int_equal(pipe(pipe_fds), 0); + if (pipe_fds[0] < 0) + { + return; + } + + uassert_int_equal(write(pipe_fds[1], "abcd", 4), 4); + uassert_int_equal(dfs_file_get_refs(&pipe_fds[0], 1, files), 0); + uassert_true(files[0] != RT_NULL); + + dfs_file_put_ref(files[0]); + uassert_int_equal(read(pipe_fds[0], data, 2), 2); + uassert_buf_equal(data, "ab", 2); + + uassert_int_equal(dfs_file_get_refs(&pipe_fds[0], 1, files), 0); + uassert_int_equal(dfs_file_install_refs(files, 1, &installed), 0); + uassert_true(installed >= 0); + uassert_int_equal(read(installed, data, 2), 2); + uassert_buf_equal(data, "cd", 2); + + tc_af_unix_close(&installed); + tc_af_unix_close(&pipe_fds[0]); + tc_af_unix_close(&pipe_fds[1]); +} + +static void tc_af_unix_node_testcase(void) +{ + UTEST_UNIT_RUN(tc_af_unix_mknod_socket); + UTEST_UNIT_RUN(tc_af_unix_file_refs); +} + +UTEST_TC_EXPORT(tc_af_unix_node_testcase, "components.dfs.v2.af_unix.node", + RT_NULL, RT_NULL, 10); diff --git a/components/dfs/utest/v2/af_unix/tc_af_unix_poll.c b/components/dfs/utest/v2/af_unix/tc_af_unix_poll.c new file mode 100644 index 0000000000..95d29f078d --- /dev/null +++ b/components/dfs/utest/v2/af_unix/tc_af_unix_poll.c @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tc_af_unix.h" + +#define TC_AF_UNIX_POLL_STACK 4096 +#define TC_AF_UNIX_POLL_PRIO 20 + +struct tc_af_unix_poll_ctx +{ + int fd; + int result; + short revents; + struct rt_event done; +}; + +static void tc_af_unix_poll_entry(void *parameter) +{ + struct pollfd poll_fd; + struct tc_af_unix_poll_ctx *ctx; + + ctx = (struct tc_af_unix_poll_ctx *)parameter; + poll_fd.fd = ctx->fd; + poll_fd.events = POLLIN; + poll_fd.revents = 0; + ctx->result = poll(&poll_fd, 1, 3000); + ctx->revents = poll_fd.revents; + rt_event_send(&ctx->done, 0x1); +} + +static void tc_af_unix_stream_readiness(void) +{ + char buffer[8]; + char *fill; + const char payload[] = "poll"; + int result; + int sockets[2] = { -1, -1 }; + rt_size_t sent = 0; + struct pollfd poll_fd; + + result = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + uassert_int_equal(result, 0); + if (result < 0) + { + return; + } + + poll_fd.fd = sockets[1]; + poll_fd.events = POLLIN | POLLOUT; + poll_fd.revents = 0; + uassert_int_equal(poll(&poll_fd, 1, 0), 1); + uassert_true((poll_fd.revents & POLLOUT) != 0); + uassert_true((poll_fd.revents & POLLIN) == 0); + + uassert_int_equal(send(sockets[0], payload, sizeof(payload), 0), + sizeof(payload)); + poll_fd.revents = 0; + uassert_int_equal(poll(&poll_fd, 1, 0), 1); + uassert_true((poll_fd.revents & POLLIN) != 0); + uassert_int_equal(recv(sockets[1], buffer, sizeof(buffer), 0), + sizeof(payload)); + + fill = (char *)rt_malloc(AF_UNIX_STREAM_BUFFER_SIZE); + uassert_true(fill != RT_NULL); + if (fill != RT_NULL) + { + rt_memset(fill, 'x', AF_UNIX_STREAM_BUFFER_SIZE); + uassert_int_equal(fcntl(sockets[0], F_SETFL, O_NONBLOCK), 0); + while (sent < AF_UNIX_STREAM_BUFFER_SIZE) + { + result = send(sockets[0], fill + sent, + AF_UNIX_STREAM_BUFFER_SIZE - sent, 0); + if (result <= 0) + { + break; + } + sent += (rt_size_t)result; + } + uassert_int_equal(sent, AF_UNIX_STREAM_BUFFER_SIZE); + + poll_fd.fd = sockets[0]; + poll_fd.events = POLLOUT; + poll_fd.revents = 0; + uassert_int_equal(poll(&poll_fd, 1, 0), 0); + + uassert_int_equal(recv(sockets[1], fill, 64, 0), 64); + poll_fd.revents = 0; + uassert_int_equal(poll(&poll_fd, 1, 0), 1); + uassert_true((poll_fd.revents & POLLOUT) != 0); + rt_free(fill); + } + + uassert_int_equal(shutdown(sockets[0], SHUT_WR), 0); + poll_fd.fd = sockets[1]; + poll_fd.events = POLLIN; + poll_fd.revents = 0; + uassert_int_equal(poll(&poll_fd, 1, 0), 1); + uassert_true((poll_fd.revents & POLLIN) != 0); + + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); +} + +static void tc_af_unix_dgram_readiness(void) +{ + char buffer[2]; + const char payload[] = "x"; + int error; + int index; + int result; + int sockets[2] = { -1, -1 }; + struct pollfd poll_fd; + + result = socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, sockets); + uassert_int_equal(result, 0); + if (result < 0) + { + return; + } + + poll_fd.fd = sockets[1]; + poll_fd.events = POLLIN; + poll_fd.revents = 0; + uassert_int_equal(poll(&poll_fd, 1, 0), 0); + + for (index = 0; index < AF_UNIX_DGRAM_QUEUE_LEN; index++) + { + uassert_int_equal(send(sockets[0], payload, sizeof(payload), 0), + sizeof(payload)); + } + result = send(sockets[0], payload, sizeof(payload), 0); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, EAGAIN); + + poll_fd.revents = 0; + uassert_int_equal(poll(&poll_fd, 1, 0), 1); + uassert_true((poll_fd.revents & POLLIN) != 0); + uassert_int_equal(recv(sockets[1], buffer, sizeof(buffer), 0), + sizeof(payload)); + uassert_int_equal(send(sockets[0], payload, sizeof(payload), 0), + sizeof(payload)); + + result = send(sockets[0], payload, AF_UNIX_DGRAM_MAX_SIZE + 1, 0); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, EMSGSIZE); + + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); +} + +static void tc_af_unix_poll_peer_close(void) +{ + int sockets[2] = { -1, -1 }; + rt_err_t wait; + rt_thread_t thread; + rt_uint32_t recved; + struct tc_af_unix_poll_ctx ctx; + + uassert_int_equal(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), 0); + if (sockets[0] < 0) + { + return; + } + + rt_memset(&ctx, 0, sizeof(ctx)); + ctx.fd = sockets[0]; + ctx.result = -2; + uassert_int_equal(rt_event_init(&ctx.done, "utafup", RT_IPC_FLAG_PRIO), + RT_EOK); + + thread = rt_thread_create("utafup", tc_af_unix_poll_entry, &ctx, + TC_AF_UNIX_POLL_STACK, TC_AF_UNIX_POLL_PRIO, 10); + uassert_true(thread != RT_NULL); + if (thread == RT_NULL) + { + rt_event_detach(&ctx.done); + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); + return; + } + + rt_thread_startup(thread); + rt_thread_mdelay(50); + tc_af_unix_closesocket(&sockets[1]); + + wait = rt_event_recv(&ctx.done, 0x1, + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + rt_tick_from_millisecond(3000), &recved); + uassert_int_equal(wait, RT_EOK); + uassert_int_equal(ctx.result, 1); + uassert_true((ctx.revents & (POLLIN | POLLHUP)) != 0); + + rt_event_detach(&ctx.done); + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); +} + +static void tc_af_unix_poll_testcase(void) +{ + UTEST_UNIT_RUN(tc_af_unix_stream_readiness); + UTEST_UNIT_RUN(tc_af_unix_dgram_readiness); + UTEST_UNIT_RUN(tc_af_unix_poll_peer_close); +} + +UTEST_TC_EXPORT(tc_af_unix_poll_testcase, "components.dfs.v2.af_unix.poll", + RT_NULL, RT_NULL, 20); diff --git a/components/dfs/utest/v2/af_unix/tc_af_unix_rights.c b/components/dfs/utest/v2/af_unix/tc_af_unix_rights.c new file mode 100644 index 0000000000..fab056e735 --- /dev/null +++ b/components/dfs/utest/v2/af_unix/tc_af_unix_rights.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tc_af_unix.h" + +static void tc_af_unix_rights_transfer(void) +{ + char data; + char payload; + int message_flags; + int pipe_fds[2][2]; + int received_fds[2]; + int send_fds[2]; + int sockets[2]; + int type_index; + int types[2] = { SOCK_DGRAM, SOCK_STREAM }; + size_t fd_count; + + for (type_index = 0; type_index < 2; type_index++) + { + received_fds[0] = -1; + received_fds[1] = -1; + pipe_fds[0][0] = -1; + pipe_fds[0][1] = -1; + pipe_fds[1][0] = -1; + pipe_fds[1][1] = -1; + sockets[0] = -1; + sockets[1] = -1; + + uassert_int_equal(socketpair(AF_UNIX, types[type_index], 0, sockets), + 0); + uassert_int_equal(pipe(pipe_fds[0]), 0); + uassert_int_equal(pipe(pipe_fds[1]), 0); + if (sockets[0] < 0 || pipe_fds[0][0] < 0 || pipe_fds[1][0] < 0) + { + goto __iteration_exit; + } + + uassert_int_equal(write(pipe_fds[0][1], "a", 1), 1); + uassert_int_equal(write(pipe_fds[1][1], "b", 1), 1); + send_fds[0] = pipe_fds[0][0]; + send_fds[1] = pipe_fds[1][0]; + uassert_int_equal(tc_af_unix_send_rights(sockets[0], send_fds, 2, 'r'), + 1); + tc_af_unix_close(&pipe_fds[0][0]); + tc_af_unix_close(&pipe_fds[1][0]); + + fd_count = 0; + message_flags = 0; + uassert_int_equal(tc_af_unix_receive_rights( + sockets[1], 0, CMSG_SPACE(2 * sizeof(int)), + received_fds, &fd_count, &message_flags, + &payload), + 1); + uassert_int_equal(payload, 'r'); + uassert_int_equal(fd_count, 2); + uassert_int_equal(message_flags & MSG_CTRUNC, 0); + uassert_int_equal(read(received_fds[0], &data, 1), 1); + uassert_int_equal(data, 'a'); + uassert_int_equal(read(received_fds[1], &data, 1), 1); + uassert_int_equal(data, 'b'); + + __iteration_exit: + tc_af_unix_close(&received_fds[0]); + tc_af_unix_close(&received_fds[1]); + tc_af_unix_close(&pipe_fds[0][0]); + tc_af_unix_close(&pipe_fds[0][1]); + tc_af_unix_close(&pipe_fds[1][0]); + tc_af_unix_close(&pipe_fds[1][1]); + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); + } +} + +static void tc_af_unix_rights_truncation(void) +{ + char payload; + int message_flags = 0; + int pipe_fds[2][2] = { { -1, -1 }, { -1, -1 } }; + int received_fds[2] = { -1, -1 }; + int send_fds[2]; + int sockets[2] = { -1, -1 }; + size_t fd_count = 0; + + uassert_int_equal(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets), 0); + uassert_int_equal(pipe(pipe_fds[0]), 0); + uassert_int_equal(pipe(pipe_fds[1]), 0); + if (sockets[0] < 0 || pipe_fds[0][0] < 0 || pipe_fds[1][0] < 0) + { + goto __exit; + } + + send_fds[0] = pipe_fds[0][0]; + send_fds[1] = pipe_fds[1][0]; + uassert_int_equal(tc_af_unix_send_rights(sockets[0], send_fds, 2, 't'), 1); + uassert_int_equal(tc_af_unix_receive_rights(sockets[1], 0, 0, received_fds, + &fd_count, &message_flags, + &payload), + 1); + uassert_int_equal(fd_count, 0); + uassert_true((message_flags & MSG_CTRUNC) != 0); + +__exit: + tc_af_unix_close(&received_fds[0]); + tc_af_unix_close(&pipe_fds[0][0]); + tc_af_unix_close(&pipe_fds[0][1]); + tc_af_unix_close(&pipe_fds[1][0]); + tc_af_unix_close(&pipe_fds[1][1]); + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); +} + +static void tc_af_unix_rights_peek_and_invalid(void) +{ + char payload; + int error; + int invalid_fd = -1; + int message_flags = 0; + int pipe_fds[2] = { -1, -1 }; + int received_fds[2] = { -1, -1 }; + int result; + int sockets[2] = { -1, -1 }; + size_t fd_count = 0; + + uassert_int_equal(socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, + sockets), + 0); + uassert_int_equal(pipe(pipe_fds), 0); + if (sockets[0] < 0 || pipe_fds[0] < 0) + { + goto __exit; + } + + result = tc_af_unix_send_rights(sockets[0], &invalid_fd, 1, 'x'); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, EBADF); + + uassert_int_equal(tc_af_unix_send_rights(sockets[0], pipe_fds, 1, 'p'), 1); + fd_count = 0; + uassert_int_equal(tc_af_unix_receive_rights( + sockets[1], MSG_PEEK, CMSG_SPACE(sizeof(int)), + received_fds, &fd_count, &message_flags, &payload), + 1); + uassert_int_equal(payload, 'p'); + uassert_int_equal(fd_count, 0); + + fd_count = 0; + message_flags = 0; + uassert_int_equal(tc_af_unix_receive_rights( + sockets[1], 0, CMSG_SPACE(sizeof(int)), + received_fds, &fd_count, &message_flags, &payload), + 1); + uassert_int_equal(fd_count, 1); + +__exit: + tc_af_unix_close(&received_fds[0]); + tc_af_unix_close(&pipe_fds[0]); + tc_af_unix_close(&pipe_fds[1]); + tc_af_unix_closesocket(&sockets[0]); + tc_af_unix_closesocket(&sockets[1]); +} + +static void tc_af_unix_rights_reject_socket(void) +{ + char payload; + int carrier[2] = { -1, -1 }; + int error; + int message_flags = 0; + int passed[2] = { -1, -1 }; + int received_fds[2] = { -1, -1 }; + int result; + size_t fd_count = 0; + + uassert_int_equal(socketpair(AF_UNIX, SOCK_STREAM, 0, carrier), 0); + uassert_int_equal(socketpair(AF_UNIX, SOCK_DGRAM, 0, passed), 0); + if (carrier[0] < 0 || passed[0] < 0) + { + goto __exit; + } + + result = tc_af_unix_send_rights(carrier[0], &passed[1], 1, 's'); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_int_equal(error, EOPNOTSUPP); + + result = tc_af_unix_receive_rights(carrier[1], MSG_DONTWAIT, + CMSG_SPACE(sizeof(int)), received_fds, + &fd_count, &message_flags, &payload); + error = rt_get_errno(); + uassert_int_equal(result, -1); + uassert_true(error == EAGAIN || error == EWOULDBLOCK); + uassert_int_equal(fd_count, 0); + + uassert_int_equal(send(passed[0], "q", 1, 0), 1); + uassert_int_equal(recv(passed[1], &payload, 1, 0), 1); + uassert_int_equal(payload, 'q'); + +__exit: + tc_af_unix_close(&received_fds[0]); + tc_af_unix_closesocket(&passed[0]); + tc_af_unix_closesocket(&passed[1]); + tc_af_unix_closesocket(&carrier[0]); + tc_af_unix_closesocket(&carrier[1]); +} + +static void tc_af_unix_rights_testcase(void) +{ + UTEST_UNIT_RUN(tc_af_unix_rights_transfer); + UTEST_UNIT_RUN(tc_af_unix_rights_truncation); + UTEST_UNIT_RUN(tc_af_unix_rights_peek_and_invalid); + UTEST_UNIT_RUN(tc_af_unix_rights_reject_socket); +} + +UTEST_TC_EXPORT(tc_af_unix_rights_testcase, "components.dfs.v2.af_unix.rights", + RT_NULL, RT_NULL, 15); |
