diff options
Diffstat (limited to 'components/lwp/syscall/syscall_file.c')
| -rw-r--r-- | components/lwp/syscall/syscall_file.c | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/components/lwp/syscall/syscall_file.c b/components/lwp/syscall/syscall_file.c new file mode 100644 index 0000000000..1650215e67 --- /dev/null +++ b/components/lwp/syscall/syscall_file.c @@ -0,0 +1,773 @@ +/* + * Copyright (c) 2006-2025 RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "syscall_internal.h" + +/** + * @brief Reads data from a file descriptor into a buffer. + * + * This system call reads up to `nbyte` bytes of data from the file descriptor + * specified by `fd` into the buffer pointed to by `buf`. + * + * @param fd The file descriptor to read from. This should be a valid file + * descriptor obtained through system calls like `open()` or `socket()`. + * @param buf A pointer to the buffer where the read data will be stored. The buffer + * must have enough space to accommodate up to `nbyte` bytes. + * @param nbyte The maximum number of bytes to read. If the file contains fewer + * bytes than `nbyte`, only the available bytes will be read. + * @return The number of bytes actually read, which may be less than `nbyte` if fewer + * bytes are available or if the end of the file is reached. Returns `0` if + * the end of the file is encountered. On error, returns `errno`. + * @warning Ensure the buffer `buf` has sufficient space to hold the requested number + * of bytes, as failing to do so may result in undefined behavior. + */ +ssize_t sys_read(int fd, void *buf, size_t nbyte) +{ +#ifdef ARCH_MM_MMU + void *kmem = RT_NULL; + ssize_t ret = -1; + + if (nbyte) + { + if (!lwp_user_accessable((void *)buf, nbyte)) + { + return -EFAULT; + } + + kmem = kmem_get(nbyte); + if (!kmem) + { + return -ENOMEM; + } + } + + ret = read(fd, kmem, nbyte); + if (ret > 0) + { + if (ret != lwp_put_to_user(buf, kmem, ret)) + { + return -EFAULT; + } + } + + if (ret < 0) + { + ret = GET_ERRNO(); + } + + kmem_put(kmem); + + return ret; +#else + if (!lwp_user_accessable((void *)buf, nbyte)) + { + return -EFAULT; + } + ssize_t ret = read(fd, buf, nbyte); + return (ret < 0 ? GET_ERRNO() : ret); +#endif +} + +/** + * @brief Writes data from a buffer to a file descriptor. + * + * This system call writes up to `nbyte` bytes of data from the buffer pointed + * to by `buf` to the file descriptor specified by `fd`. + * + * @param fd The file descriptor to write to. This should be a valid file + * descriptor obtained through system calls like `open()` or `socket()`. + * @param buf A pointer to the buffer containing the data to be written. The + * buffer must remain valid and accessible for the duration of the + * write operation. + * @param nbyte The number of bytes to write from the buffer. If the file descriptor + * refers to a device or a socket, the actual number of bytes written + * may be less than `nbyte`. + * @return The number of bytes actually written. This may be less than `nbyte` if + * the underlying resource cannot accept more data at the moment. On error, + * returns `errno`. + * + * @warning Ensure the buffer `buf` contains at least `nbyte` valid bytes to + * avoid undefined behavior. Additionally, verify that `fd` is writable + * to prevent errors. + * + * @see sys_read(), write() + */ +ssize_t sys_write(int fd, const void *buf, size_t nbyte) +{ +#ifdef ARCH_MM_MMU + void *kmem = RT_NULL; + ssize_t ret = -1; + + if (nbyte) + { + if (!lwp_user_accessable((void *)buf, nbyte)) + { + return -EFAULT; + } + + kmem = kmem_get(nbyte); + if (!kmem) + { + return -ENOMEM; + } + + lwp_get_from_user(kmem, (void *)buf, nbyte); + } + + ret = write(fd, kmem, nbyte); + if (ret < 0) + { + ret = GET_ERRNO(); + } + + kmem_put(kmem); + + return ret; +#else + if (!lwp_user_accessable((void *)buf, nbyte)) + { + return -EFAULT; + } + ssize_t ret = write(fd, buf, nbyte); + return (ret < 0 ? GET_ERRNO() : ret); +#endif +} + +/** + * @brief Repositions the file offset of the open file descriptor. + * + * This system call sets the file offset for the open file descriptor `fd` + * to a new position based on the `offset` and `whence` parameters. It is used + * for seeking within files, enabling random access to file content. + * + * @param fd The file descriptor whose offset is to be modified. The descriptor + * must refer to a file capable of seeking (e.g., regular files, but + * not pipes or sockets). + * @param offset The new offset value relative to the position specified by `whence`. + * Can be a positive or negative value, depending on the seek direction. + * @param whence The reference point for the new offset. Must be one of the following: + * - `SEEK_SET`: Set the offset to `offset` bytes from the beginning of the file. + * - `SEEK_CUR`: Set the offset to its current position plus `offset`. + * - `SEEK_END`: Set the offset to the size of the file plus `offset`. + * @return On success, returns the resulting offset location as measured in bytes + * from the beginning of the file. On failure, returns `errno`. + * + * @warning Ensure the file descriptor `fd` supports seeking. Using this function + * on non-seekable file descriptors (e.g., pipes or sockets) will result in an error. + * + * @see sys_read(), sys_write(), lseek() + */ +size_t sys_lseek(int fd, size_t offset, int whence) +{ + ssize_t ret = lseek(fd, offset, whence); + return (ret < 0 ? GET_ERRNO() : ret); +} + +/** + * @brief Opens or creates a file, returning a file descriptor. + * + * This system call opens the file specified by `name` with the specified + * access mode and flags. If the file does not exist and the `O_CREAT` flag + * is provided, it will create the file with the specified mode. + * + * @param name The path to the file to be opened. This can be an absolute or + * relative path. + * @param flag Flags controlling how the file is opened. Common values include: + * - `O_RDONLY`: Open the file for read-only access. + * - `O_WRONLY`: Open the file for write-only access. + * - `O_RDWR`: Open the file for both reading and writing. + * - `O_CREAT`: Create the file if it does not exist (requires a mode argument). + * - `O_TRUNC`: Truncate the file to zero length if it exists. + * - `O_APPEND`: Open the file in append mode. + * @param ... Optional. If the `O_CREAT` flag is specified, an additional + * `mode_t` argument must be provided, defining the file permissions + * (e.g., `0644` for user-read/write and group/world-read). + * @return On success, returns the file descriptor. + * On failure, returns `errno`. + * + * @note The file descriptor returned can be used with other system calls like + * `sys_read()`, `sys_write()`, and `sys_close()`. + * + * @warning When using the `O_CREAT` flag, ensure to provide the `mode` argument + * to avoid undefined behavior. Additionally, verify that the process + * has the necessary permissions to access or create the file. + * + * @see sys_close(), sys_read(), sys_write(), open() + */ +sysret_t sys_open(const char *name, int flag, ...) +{ +#ifdef ARCH_MM_MMU + int ret = -1; + rt_size_t len = 0; + char *kname = RT_NULL; + mode_t mode = 0; + + if (!lwp_user_accessable((void *)name, 1)) + { + return -EFAULT; + } + + len = lwp_user_strlen(name); + if (!len) + { + return -EINVAL; + } + + kname = (char *)kmem_get(len + 1); + if (!kname) + { + return -ENOMEM; + } + + if ((flag & O_CREAT) || (flag & O_TMPFILE) == O_TMPFILE) + { + va_list ap; + va_start(ap, flag); + mode = va_arg(ap, mode_t); + va_end(ap); + } + + if (lwp_get_from_user(kname, (void *)name, len + 1) != (len + 1)) + { + kmem_put(kname); + return -EINVAL; + } + + ret = open(kname, flag, mode); + if (ret < 0) + { + ret = GET_ERRNO(); + } + + kmem_put(kname); + + return ret; +#else + int ret; + mode_t mode = 0; + + if (!lwp_user_accessable((void *)name, 1)) + { + return -EFAULT; + } + + if ((flag & O_CREAT) || (flag & O_TMPFILE) == O_TMPFILE) + { + va_list ap; + va_start(ap, flag); + mode = va_arg(ap, mode_t); + va_end(ap); + } + + ret = open(name, flag, mode); + return (ret < 0 ? GET_ERRNO() : ret); +#endif +} + +/** + * @brief Opens or creates a file relative to a directory file descriptor. + * + * This system call opens the file specified by `name`, relative to the directory + * indicated by `dirfd`, with the specified flags and mode. It provides more + * flexibility than `sys_open` for handling files in specific directory contexts. + * + * @param dirfd The file descriptor of the directory relative to which the file is opened. + * Special values include: + * - `AT_FDCWD`: Use the current working directory as the base. + * @param name The path to the file to be opened. If `name` is absolute, `dirfd` is ignored. + * @param flag Flags controlling how the file is opened. Common values include: + * - `O_RDONLY`: Open the file for read-only access. + * - `O_WRONLY`: Open the file for write-only access. + * - `O_RDWR`: Open the file for both reading and writing. + * - `O_CREAT`: Create the file if it does not exist. + * - `O_TRUNC`: Truncate the file to zero length if it exists. + * - `O_APPEND`: Open the file in append mode. + * - `O_EXCL`: Ensure the file is created exclusively (used with `O_CREAT`). + * @param mode The permissions to set if the file is created (e.g., `0644` for user read/write + * and group/world read). This parameter is ignored unless `O_CREAT` is specified. + * @return On success, returns the file descriptor. + * On failure, returns `errno`. + * + * @note The `sys_openat` system call is particularly useful for implementing secure + * directory traversal and operations, as it avoids race conditions when working + * with relative paths. + * + * @warning Ensure that `dirfd` is a valid directory file descriptor if a relative + * `name` is provided. Combining `O_CREAT` and `mode` requires proper handling + * to avoid unintended permission settings. + * + * @see sys_open(), sys_read(), sys_write(), sys_close() + */ +sysret_t sys_openat(int dirfd, const char *name, int flag, mode_t mode) +{ +#ifdef ARCH_MM_MMU + int ret = -1; + rt_size_t len = 0; + char *kname = RT_NULL; + + len = lwp_user_strlen(name); + if (len <= 0) + { + return -EINVAL; + } + + kname = (char *)kmem_get(len + 1); + if (!kname) + { + return -ENOMEM; + } + + lwp_get_from_user(kname, (void *)name, len + 1); + ret = openat(dirfd, kname, flag, mode); + if (ret < 0) + { + ret = GET_ERRNO(); + } + + kmem_put(kname); + + return ret; +#else + if (!lwp_user_accessable((void *)name, 1)) + { + return -EFAULT; + } + int ret = openat(dirfd, name, flag, mode); + return (ret < 0 ? GET_ERRNO() : ret); +#endif +} + +/** + * @brief Closes a file descriptor. + * + * This system call closes the file descriptor specified by `fd`, releasing any + * resources associated with it. Once closed, the file descriptor can no longer + * be used for operations such as reading or writing, and it may be reassigned + * by subsequent calls to functions like `sys_open()`. + * + * @param fd The file descriptor to be closed. This must be a valid open file descriptor. + * @return On success, returns 0. On failure, returns `errno`. + * + * @note Closing a file descriptor that is already closed results in an error. + * Additionally, if the file descriptor refers to a file or resource shared + * among multiple processes or threads, only the reference count is decremented, + * and the resource itself is not released until all references are closed. + * + * @warning Always ensure `fd` is valid before calling this function. Attempting + * to close an invalid or already closed descriptor may lead to undefined + * behavior or errors. + * + * @see sys_open(), sys_read(), sys_write(), close() + */ +sysret_t sys_close(int fd) +{ + int ret = close(fd); + return (ret < 0 ? GET_ERRNO() : ret); +} + +/** + * @brief Performs control operations on a file descriptor. + * + * This system call allows a program to manipulate the behavior of a device or file + * associated with the file descriptor `fd` by issuing a control command (`cmd`). + * The function provides an interface to interact with device drivers, such as modifying + * the settings of a device or performing custom operations. + * + * @param fd The file descriptor representing the device or file on which the ioctl + * command will be performed. This should be a valid, open file descriptor. + * @param cmd The control command to be executed. The exact behavior depends on the + * device and the command. Commands are usually defined by the device driver + * or the kernel and can vary widely. + * @param data A pointer to a buffer used for passing data to and from the command. + * The contents of the buffer depend on the specific command. For input + * commands, it may contain the data to be written to the device. For output + * commands, it may hold the data returned by the device. + * @return On success, returns 0. On failure, returns `errno`. + * + * @note The actual functionality of `sys_ioctl` depends heavily on the specific `cmd` + * and the type of device associated with the file descriptor `fd`. Each device + * may have a different set of valid ioctl commands. + * + * @warning Ensure that `fd` refers to a valid file descriptor for a device that supports + * ioctl commands. Providing an invalid command or using an unsupported device + * may lead to undefined behavior or errors. + * + * @see sys_open(), sys_read(), sys_write(), ioctl() + */ +sysret_t sys_ioctl(int fd, unsigned long cmd, void *data) +{ +#ifdef ARCH_MM_MMU + if (cmd == F_GETLK || cmd == F_SETLK || cmd == F_SETLKW) + { + struct flock lock; + int ret; + + if (data == RT_NULL || + !lwp_user_accessable(data, sizeof(lock)) || + lwp_get_from_user(&lock, data, sizeof(lock)) != sizeof(lock)) + { + return -EFAULT; + } + + ret = ioctl(fd, cmd, &lock); + if (ret >= 0 && cmd == F_GETLK && + lwp_put_to_user(data, &lock, sizeof(lock)) != sizeof(lock)) + { + return -EFAULT; + } + return ret < 0 ? GET_ERRNO() : ret; + } +#endif + int ret = ioctl(fd, cmd, data); + return (ret < 0 ? GET_ERRNO() : ret); +} + +/** + * @brief Retrieves information about a file associated with a file descriptor. + * + * This system call retrieves the status information about the file referred to + * by the file descriptor `file` and stores it in the structure pointed to by `buf`. + * It is typically used to obtain metadata such as file size, permissions, type, + * and timestamps. + * + * @param file The file descriptor referring to an open file. It must be a valid file + * descriptor returned by `sys_open()` or similar system calls. + * @param buf A pointer to a `struct stat` that will be populated with the file's + * status information, including attributes like size, access times, + * permissions, and more. + * @return On success, returns `0`. On failure, returns `errno` to indicate the error. + * + * @note The structure `struct stat` typically includes the following fields: + * - `st_size`: Size of the file in bytes. + * - `st_mode`: File type and permissions. + * - `st_mtime`: Last modification time. + * - `st_atime`: Last access time. + * - `st_ctime`: Time of last status change. + * + * @warning Ensure that `file` is a valid, open file descriptor. Invalid file + * descriptors or unsupported file types may lead to errors when using + * this function. + * + * @see sys_open(), sys_close(), stat(), fstat() + */ +sysret_t sys_fstat(int file, struct stat *buf) +{ +#ifdef ARCH_MM_MMU + int ret = -1; + struct stat statbuff = { 0 }; + + if (!lwp_user_accessable((void *)buf, sizeof(struct stat))) + { + return -EFAULT; + } + else + { + ret = fstat(file, &statbuff); + + if (ret == 0) + { + lwp_put_to_user(buf, &statbuff, sizeof statbuff); + } + else + { + ret = GET_ERRNO(); + } + + return ret; + } +#else + if (!lwp_user_accessable((void *)buf, sizeof(struct stat))) + { + return -EFAULT; + } + int ret = fstat(file, buf); + return (ret < 0 ? GET_ERRNO() : ret); +#endif +} + +/** + * @brief Monitors multiple file descriptors to see if they are ready for I/O. + * + * This system call monitors the file descriptors specified in `fds` for any + * I/O events such as readiness for reading, writing, or exceptional conditions. + * It waits up to the specified timeout and returns with information about which + * file descriptors are ready for the requested operations. + * + * @param fds An array of `struct pollfd` structures, each specifying a file descriptor + * to be monitored and the events to be checked for. On return, each structure + * will contain the result of the poll operation for that file descriptor. + * @param nfds The number of elements in the `fds` array. + * @param timeout The maximum time (in milliseconds) to wait for events. If `timeout` + * is `-1`, the call will block indefinitely. If `timeout` is `0`, + * the call will return immediately. + * @return On success, returns the number of file descriptors with events that are ready, + * or `0` if the timeout expired with no events. On error, returns `-1` and sets `errno`. + * + * @note The `pollfd` structure used for each file descriptor contains the following fields: + * - `fd`: The file descriptor to be monitored. + * - `events`: The events to monitor (e.g., `POLLIN`, `POLLOUT`). + * - `revents`: The events that actually occurred. + * + * @warning Ensure that `fds` contains valid file descriptors. Invalid descriptors or + * unsupported types (such as sockets) may result in errors. Also, be mindful + * of the timeout behavior - passing `0` will cause an immediate return, and passing + * `-1` will block indefinitely. + * + * @see sys_select(), poll(), select() + */ +sysret_t sys_poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + int ret = -1; +#ifdef ARCH_MM_MMU + struct pollfd *kfds = RT_NULL; + + if (!lwp_user_accessable((void *)fds, nfds * sizeof *fds)) + { + return -EFAULT; + } + + kfds = (struct pollfd *)kmem_get(nfds * sizeof *kfds); + if (!kfds) + { + return -ENOMEM; + } + + lwp_get_from_user(kfds, fds, nfds * sizeof *kfds); + + ret = poll(kfds, nfds, timeout); + if (ret > 0) + { + lwp_put_to_user(fds, kfds, nfds * sizeof *kfds); + } + + kmem_put(kfds); + return ret; +#else + if (!lwp_user_accessable((void *)fds, nfds * sizeof *fds)) + { + return -EFAULT; + } + ret = poll(fds, nfds, timeout); + return ret; +#endif /* ARCH_MM_MMU */ +} + +/** + * @brief Monitors multiple file descriptors for readiness to perform I/O operations. + * + * This system call allows a program to monitor multiple file descriptors to see if + * they are ready for reading, writing, or have exceptional conditions. It waits + * for one or more of the file descriptors to become ready or for the specified + * timeout to expire. + * + * @param nfds The highest-numbered file descriptor in any of the sets, plus one. + * This is the number of file descriptors to monitor. + * @param readfds A pointer to an `fd_set` structure specifying the file descriptors + * to be checked for readability. If a file descriptor is ready to + * read, it will be set in the returned `fd_set`. + * @param writefds A pointer to an `fd_set` structure specifying the file descriptors + * to be checked for writability. If a file descriptor is ready to + * write, it will be set in the returned `fd_set`. + * @param exceptfds A pointer to an `fd_set` structure specifying the file descriptors + * to be checked for exceptional conditions (e.g., out-of-band data). + * If a file descriptor has exceptional conditions, it will be set in + * the returned `fd_set`. + * @param timeout A pointer to a `struct timeval` that specifies the maximum time + * to wait for an event. If `NULL`, the call blocks indefinitely. If + * `timeout` is `0`, the call will return immediately. + * @return On success, returns the number of file descriptors that are ready for + * the requested operations, or `0` if the timeout expired without any events. + * On error, returns `-1` and sets `errno`. + * + * @note The `fd_set` structures should be initialized using the `FD_ZERO` macro + * and populated using the `FD_SET` macro. After the call, the `fd_set` structures + * will contain the file descriptors that are ready for the requested operations. + * + * @warning Ensure that the `fd_set` structures are properly initialized and that + * `nfds` correctly reflects the number of file descriptors to monitor. + * Passing invalid file descriptors or incorrect `nfds` can lead to undefined behavior. + * + * @see sys_poll(), sys_read(), sys_write(), poll(), select() + */ +sysret_t sys_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +{ +#ifdef ARCH_MM_MMU + int ret = -1; + fd_set *kreadfds = RT_NULL, *kwritefds = RT_NULL, *kexceptfds = RT_NULL; + struct timeval ktimeout, *ptimeout = RT_NULL; + + if (readfds) + { + if (!lwp_user_accessable((void *)readfds, sizeof *readfds)) + { + SET_ERRNO(EFAULT); + goto quit; + } + kreadfds = (fd_set *)kmem_get(sizeof *kreadfds); + if (!kreadfds) + { + SET_ERRNO(ENOMEM); + goto quit; + } + lwp_get_from_user(kreadfds, readfds, sizeof *kreadfds); + } + if (writefds) + { + if (!lwp_user_accessable((void *)writefds, sizeof *writefds)) + { + SET_ERRNO(EFAULT); + goto quit; + } + kwritefds = (fd_set *)kmem_get(sizeof *kwritefds); + if (!kwritefds) + { + SET_ERRNO(ENOMEM); + goto quit; + } + lwp_get_from_user(kwritefds, writefds, sizeof *kwritefds); + } + if (exceptfds) + { + if (!lwp_user_accessable((void *)exceptfds, sizeof *exceptfds)) + { + SET_ERRNO(EFAULT); + goto quit; + } + kexceptfds = (fd_set *)kmem_get(sizeof *kexceptfds); + if (!kexceptfds) + { + SET_ERRNO(EINVAL); + goto quit; + } + lwp_get_from_user(kexceptfds, exceptfds, sizeof *kexceptfds); + } + if (timeout) + { + if (!lwp_user_accessable((void *)timeout, sizeof *timeout)) + { + SET_ERRNO(EFAULT); + goto quit; + } + lwp_get_from_user(&ktimeout, timeout, sizeof ktimeout); + ptimeout = &ktimeout; + } + + ret = select(nfds, kreadfds, kwritefds, kexceptfds, ptimeout); + if (kreadfds) + { + lwp_put_to_user(readfds, kreadfds, sizeof *kreadfds); + } + if (kwritefds) + { + lwp_put_to_user(writefds, kwritefds, sizeof *kwritefds); + } + if (kexceptfds) + { + lwp_put_to_user(exceptfds, kexceptfds, sizeof *kexceptfds); + } +quit: + if (ret < 0) + { + ret = GET_ERRNO(); + } + + if (kreadfds) + { + kmem_put(kreadfds); + } + if (kwritefds) + { + kmem_put(kwritefds); + } + if (kexceptfds) + { + kmem_put(kexceptfds); + } + return ret; +#else + int ret; + + if (!lwp_user_accessable((void *)readfds, sizeof *readfds)) + { + return -EFAULT; + } + if (!lwp_user_accessable((void *)writefds, sizeof *writefds)) + { + return -EFAULT; + } + if (!lwp_user_accessable((void *)exceptfds, sizeof *exceptfds)) + { + return -EFAULT; + } + ret = select(nfds, readfds, writefds, exceptfds, timeout); + return (ret < 0 ? GET_ERRNO() : ret); +#endif +} + +/** + * @brief Removes a file or symbolic link from the filesystem. + * + * This system call deletes the file or symbolic link specified by `pathname`. + * After the call, the file will be unlinked from the filesystem, meaning it will + * no longer be accessible via its pathname. If the file is still open, it will + * remain available to processes that have it open until all file descriptors are + * closed. If the file is a regular file and has no other hard links, it will be + * removed from the disk once all references to it are closed. + * + * @param pathname The path to the file or symbolic link to be removed. It can be + * an absolute or relative path. + * @return On success, returns `0`. On failure, returns `errno` to indicate + * the error. + * + * @note If the file is currently being used by a process (i.e., the file descriptor + * is still open), it will not be immediately removed from the disk. The actual + * removal occurs once the file is no longer in use. Symbolic links are removed + * as soon as the call is executed. + * + * @warning Ensure that the file or link to be removed exists and that the `pathname` + * is correctly specified. The operation will fail if the file is open and + * in use by other processes or if the pathname does not exist. + * + * @see sys_open(), sys_remove(), unlink() + */ +sysret_t sys_unlink(const char *pathname) +{ +#ifdef ARCH_MM_MMU + int ret = -1; + rt_size_t len = 0; + char *kname = RT_NULL; + + len = lwp_user_strlen(pathname); + if (!len) + { + return -EINVAL; + } + + kname = (char *)kmem_get(len + 1); + if (!kname) + { + return -ENOMEM; + } + + if (lwp_get_from_user(kname, (void *)pathname, len + 1) != (len + 1)) + { + kmem_put(kname); + return -EINVAL; + } + ret = unlink(kname); + if (ret < 0) + { + ret = GET_ERRNO(); + } + + kmem_put(kname); + return ret; +#else + int ret = 0; + ret = unlink(pathname); + return (ret < 0 ? GET_ERRNO() : ret); +#endif +} |
