summaryrefslogtreecommitdiff
path: root/components/dfs
diff options
context:
space:
mode:
authorBernard Xiong <[email protected]>2026-08-17 17:15:34 +0800
committerGitHub <[email protected]>2026-08-17 17:15:34 +0800
commit991f0ffca6ca475adcffc7833876bb8151f16e9c (patch)
tree5b69ef28de0b15e1a1d23e53136d37c5e86a8a59 /components/dfs
parentd2acd7587eec680b57f8777c5c7a8dce7369471e (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')
-rw-r--r--components/dfs/dfs_v2/filesystems/devfs/devtmpfs.c18
-rw-r--r--components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.c16
-rw-r--r--components/dfs/dfs_v2/filesystems/tmpfs/dfs_tmpfs.h2
-rw-r--r--components/dfs/dfs_v2/include/dfs.h6
-rw-r--r--components/dfs/dfs_v2/include/dfs_file.h1
-rw-r--r--components/dfs/dfs_v2/src/dfs.c114
-rw-r--r--components/dfs/dfs_v2/src/dfs_file.c133
-rw-r--r--components/dfs/utest/Kconfig3
-rw-r--r--components/dfs/utest/SConscript6
-rw-r--r--components/dfs/utest/v2/af_unix/Kconfig95
-rw-r--r--components/dfs/utest/v2/af_unix/SConscript27
-rw-r--r--components/dfs/utest/v2/af_unix/tc_af_unix.h39
-rw-r--r--components/dfs/utest/v2/af_unix/tc_af_unix_common.c98
-rw-r--r--components/dfs/utest/v2/af_unix/tc_af_unix_ipc.c291
-rw-r--r--components/dfs/utest/v2/af_unix/tc_af_unix_namespace.c175
-rw-r--r--components/dfs/utest/v2/af_unix/tc_af_unix_node.c79
-rw-r--r--components/dfs/utest/v2/af_unix/tc_af_unix_poll.c211
-rw-r--r--components/dfs/utest/v2/af_unix/tc_af_unix_rights.c219
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);