From 83e95bdff4c48c661b50e1eec56d5fd293b28fbb Mon Sep 17 00:00:00 2001 From: Shell Date: Thu, 28 Mar 2024 23:42:56 +0800 Subject: sync smart & dfs (#8672) Signed-off-by: xqyjlj Signed-off-by: Shell Co-authored-by: xqyjlj --- components/lwp/Kconfig | 21 +- components/lwp/SConscript | 11 +- components/lwp/arch/aarch64/cortex-a/lwp_arch.c | 60 +- components/lwp/arch/aarch64/cortex-a/lwp_arch.h | 19 +- components/lwp/arch/aarch64/cortex-a/lwp_gcc.S | 70 +- components/lwp/arch/arm/cortex-a/lwp_arch.c | 6 + components/lwp/arch/arm/cortex-a/lwp_arch.h | 2 +- components/lwp/arch/risc-v/rv64/lwp_arch.c | 7 + components/lwp/libc_musl.h | 6 +- components/lwp/lwp.c | 268 ++-- components/lwp/lwp.h | 159 ++- components/lwp/lwp_arch_comm.h | 4 + components/lwp/lwp_elf.c | 1 + components/lwp/lwp_futex.c | 29 +- components/lwp/lwp_internal.c | 69 +- components/lwp/lwp_internal.h | 111 +- components/lwp/lwp_jobctrl.c | 86 ++ components/lwp/lwp_pgrp.c | 542 +++++++++ components/lwp/lwp_pid.c | 662 +++++++--- components/lwp/lwp_pid.h | 38 +- components/lwp/lwp_pmutex.c | 459 ------- components/lwp/lwp_session.c | 432 +++++++ components/lwp/lwp_setsid.c | 27 - components/lwp/lwp_signal.c | 542 +++++++-- components/lwp/lwp_signal.h | 83 +- components/lwp/lwp_sys_socket.h | 21 +- components/lwp/lwp_syscall.c | 751 ++++++------ components/lwp/lwp_syscall.h | 7 +- components/lwp/lwp_tid.c | 21 +- components/lwp/lwp_user_mm.c | 35 +- components/lwp/lwp_user_mm.h | 4 + components/lwp/terminal/Kconfig | 13 + components/lwp/terminal/bsd_porting.h | 741 ++++++++++++ components/lwp/terminal/bsd_ttydisc.h | 81 ++ components/lwp/terminal/bsd_ttyqueue.h | 180 +++ components/lwp/terminal/freebsd/tty.c | 1447 ++++++++++++++++++++++ components/lwp/terminal/freebsd/tty_compat.c | 711 +++++++++++ components/lwp/terminal/freebsd/tty_inq.c | 507 ++++++++ components/lwp/terminal/freebsd/tty_outq.c | 370 ++++++ components/lwp/terminal/freebsd/tty_pts.c | 837 +++++++++++++ components/lwp/terminal/freebsd/tty_ttydisc.c | 1479 +++++++++++++++++++++++ components/lwp/terminal/terminal.h | 415 +++++++ components/lwp/terminal/tty_config.h | 136 +++ components/lwp/terminal/tty_cons.c | 128 ++ components/lwp/terminal/tty_ctty.c | 106 ++ components/lwp/terminal/tty_device.c | 456 +++++++ components/lwp/terminal/tty_internal.h | 67 + components/lwp/terminal/tty_ptmx.c | 350 ++++++ 48 files changed, 11208 insertions(+), 1369 deletions(-) create mode 100644 components/lwp/lwp_jobctrl.c create mode 100644 components/lwp/lwp_pgrp.c delete mode 100644 components/lwp/lwp_pmutex.c create mode 100644 components/lwp/lwp_session.c delete mode 100644 components/lwp/lwp_setsid.c create mode 100644 components/lwp/terminal/Kconfig create mode 100644 components/lwp/terminal/bsd_porting.h create mode 100644 components/lwp/terminal/bsd_ttydisc.h create mode 100644 components/lwp/terminal/bsd_ttyqueue.h create mode 100644 components/lwp/terminal/freebsd/tty.c create mode 100644 components/lwp/terminal/freebsd/tty_compat.c create mode 100644 components/lwp/terminal/freebsd/tty_inq.c create mode 100644 components/lwp/terminal/freebsd/tty_outq.c create mode 100644 components/lwp/terminal/freebsd/tty_pts.c create mode 100644 components/lwp/terminal/freebsd/tty_ttydisc.c create mode 100644 components/lwp/terminal/terminal.h create mode 100644 components/lwp/terminal/tty_config.h create mode 100644 components/lwp/terminal/tty_cons.c create mode 100644 components/lwp/terminal/tty_ctty.c create mode 100644 components/lwp/terminal/tty_device.c create mode 100644 components/lwp/terminal/tty_internal.h create mode 100644 components/lwp/terminal/tty_ptmx.c (limited to 'components/lwp') diff --git a/components/lwp/Kconfig b/components/lwp/Kconfig index 719e8ffca4..9b79e51207 100644 --- a/components/lwp/Kconfig +++ b/components/lwp/Kconfig @@ -8,7 +8,7 @@ menuconfig RT_USING_LWP if RT_USING_LWP config LWP_DEBUG bool "Enable debugging features of LwP" - default n + default y config RT_LWP_MAX_NR int "The max number of light-weight process" @@ -51,24 +51,6 @@ if RT_USING_LWP default y endif - config LWP_UNIX98_PTY - bool "The unix98 PTY support" - default n - - if LWP_UNIX98_PTY - config LWP_PTY_INPUT_BFSZ - int "The unix98 PTY input buffer size" - default 1024 - - config LWP_PTY_PTS_SIZE - int "The unix98 PTY device max num" - default 3 - - config LWP_PTY_USING_DEBUG - bool "The unix98 PTY debug output" - default n - endif - menuconfig RT_USING_LDSO bool "LDSO: dynamic load shared objects" depends on RT_USING_DFS_V2 @@ -85,5 +67,6 @@ if RT_USING_LWP default n endif +source "$RTT_DIR/components/lwp/terminal/Kconfig" endif diff --git a/components/lwp/SConscript b/components/lwp/SConscript index 3c43dcd86e..5f4a618d48 100644 --- a/components/lwp/SConscript +++ b/components/lwp/SConscript @@ -22,11 +22,6 @@ if arch == 'risc-v': if cpu in rv64: cpu = 'rv64' -if GetDepend('LWP_UNIX98_PTY'): - # print("LWP_UNIX98_PTY") - src += Glob('unix98pty/*.c') - CPPPATH += ['unix98pty/'] - if platform in platform_file.keys(): # support platforms if arch in support_arch.keys() and cpu in support_arch[arch]: asm_path = 'arch/' + arch + '/' + cpu + '/*_' + platform_file[platform] @@ -40,6 +35,12 @@ if platform in platform_file.keys(): # support platforms CPPPATH = [cwd] CPPPATH += [cwd + '/arch/' + arch + '/' + cpu] +# Terminal I/O Subsystem +termios_path = ['./terminal/', './terminal/freebsd/'] +for item in termios_path: + src += Glob(item + '*.c') +CPPPATH += ['./terminal/'] + group = DefineGroup('lwP', src, depend = ['RT_USING_SMART'], CPPPATH = CPPPATH) Return('group') diff --git a/components/lwp/arch/aarch64/cortex-a/lwp_arch.c b/components/lwp/arch/aarch64/cortex-a/lwp_arch.c index b9f9ec58dd..3df6b2f4f7 100644 --- a/components/lwp/arch/aarch64/cortex-a/lwp_arch.c +++ b/components/lwp/arch/aarch64/cortex-a/lwp_arch.c @@ -8,6 +8,7 @@ * 2021-05-18 Jesven first version * 2023-07-16 Shell Move part of the codes to C from asm in signal handling * 2023-10-16 Shell Support a new backtrace framework + * 2023-08-03 Shell Support of syscall restart (SA_RESTART) */ #include @@ -123,6 +124,7 @@ int arch_set_thread_context(void (*exit)(void), void *new_thread_stack, #define ALGIN_BYTES (16) +/* the layout is part of ABI, dont change it */ struct signal_ucontext { rt_int64_t sigreturn; @@ -130,11 +132,62 @@ struct signal_ucontext siginfo_t si; - rt_align(16) + rt_align(ALGIN_BYTES) struct rt_hw_exp_stack frame; }; -void *arch_signal_ucontext_restore(rt_base_t user_sp) +RT_STATIC_ASSERT(abi_offset_compatible, offsetof(struct signal_ucontext, si) == UCTX_ABI_OFFSET_TO_SI); + +void *arch_signal_ucontext_get_frame(struct signal_ucontext *uctx) +{ + return &uctx->frame; +} + +/* internal used only */ +void arch_syscall_prepare_signal(rt_base_t rc, struct rt_hw_exp_stack *exp_frame) +{ + long x0 = exp_frame->x0; + exp_frame->x0 = rc; + exp_frame->x7 = x0; + return ; +} + +void arch_syscall_restart(void *sp, void *ksp); + +void arch_syscall_set_errno(void *eframe, int expected, int code) +{ + struct rt_hw_exp_stack *exp_frame = eframe; + if (exp_frame->x0 == -expected) + exp_frame->x0 = -code; + return ; +} + +void arch_signal_check_erestart(void *eframe, void *ksp) +{ + struct rt_hw_exp_stack *exp_frame = eframe; + long rc = exp_frame->x0; + long sys_id = exp_frame->x8; + (void)sys_id; + + if (rc == -ERESTART) + { + LOG_D("%s(rc=%ld,sys_id=%ld,pid=%d)", __func__, rc, sys_id, lwp_self()->pid); + LOG_D("%s: restart rc = %ld", lwp_get_syscall_name(sys_id), rc); + exp_frame->x0 = exp_frame->x7; + arch_syscall_restart(eframe, ksp); + } + + return ; +} + +static void arch_signal_post_action(struct signal_ucontext *new_sp, rt_base_t kernel_sp) +{ + arch_signal_check_erestart(&new_sp->frame, (void *)kernel_sp); + + return ; +} + +void *arch_signal_ucontext_restore(rt_base_t user_sp, rt_base_t kernel_sp) { struct signal_ucontext *new_sp; new_sp = (void *)user_sp; @@ -142,6 +195,7 @@ void *arch_signal_ucontext_restore(rt_base_t user_sp) if (lwp_user_accessable(new_sp, sizeof(*new_sp))) { lwp_thread_signal_mask(rt_thread_self(), LWP_SIG_MASK_CMD_SET_MASK, &new_sp->save_sigmask, RT_NULL); + arch_signal_post_action(new_sp, kernel_sp); } else { @@ -157,7 +211,7 @@ void *arch_signal_ucontext_save(rt_base_t user_sp, siginfo_t *psiginfo, lwp_sigset_t *save_sig_mask) { struct signal_ucontext *new_sp; - new_sp = (void *)(user_sp - sizeof(struct signal_ucontext)); + new_sp = (void *)((user_sp - sizeof(struct signal_ucontext)) & ~0xf); if (lwp_user_accessable(new_sp, sizeof(*new_sp))) { diff --git a/components/lwp/arch/aarch64/cortex-a/lwp_arch.h b/components/lwp/arch/aarch64/cortex-a/lwp_arch.h index c7d9c8d74e..191f8e1bdc 100644 --- a/components/lwp/arch/aarch64/cortex-a/lwp_arch.h +++ b/components/lwp/arch/aarch64/cortex-a/lwp_arch.h @@ -11,8 +11,7 @@ #ifndef LWP_ARCH_H__ #define LWP_ARCH_H__ -#include -#include +#include #ifdef ARCH_MM_MMU @@ -26,6 +25,13 @@ #define USER_VADDR_START 0x00200000UL #define USER_LOAD_VADDR USER_VADDR_START +#define UCTX_ABI_OFFSET_TO_SI 16 + +#ifndef __ASSEMBLY__ + +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -34,7 +40,7 @@ unsigned long rt_hw_ffz(unsigned long x); rt_inline void icache_invalid_all(void) { - asm volatile ("ic ialluis\n\tisb sy":::"memory"); + __asm__ volatile ("ic ialluis\n\tisb sy":::"memory"); } /** @@ -57,11 +63,14 @@ void *arch_signal_ucontext_save(rt_base_t user_sp, siginfo_t *psiginfo, * @param user_sp sp of user * @return void* */ -void *arch_signal_ucontext_restore(rt_base_t user_sp); +void *arch_signal_ucontext_restore(rt_base_t user_sp, rt_base_t kernel_sp); +void arch_syscall_restart(void *sp, void *ksp); + #ifdef __cplusplus } #endif +#endif /* __ASSEMBLY__ */ -#endif +#endif /* ARCH_MM_MMU */ #endif /*LWP_ARCH_H__*/ diff --git a/components/lwp/arch/aarch64/cortex-a/lwp_gcc.S b/components/lwp/arch/aarch64/cortex-a/lwp_gcc.S index e33127b62b..f5a93698d9 100644 --- a/components/lwp/arch/aarch64/cortex-a/lwp_gcc.S +++ b/components/lwp/arch/aarch64/cortex-a/lwp_gcc.S @@ -7,6 +7,7 @@ * Date Author Notes * 2021-05-18 Jesven first version * 2023-07-16 Shell Move part of the codes to C from asm in signal handling + * 2023-08-03 Shell Support of syscall restart (SA_RESTART) */ #ifndef __ASSEMBLY__ @@ -17,6 +18,7 @@ #include "asm-generic.h" #include "asm-fpu.h" #include "armv8.h" +#include "lwp_arch.h" /********************* * SPSR BIT * @@ -155,6 +157,14 @@ START_POINT_END(SVC_Handler) .global arch_syscall_exit arch_syscall_exit: + + /** + * @brief back up former x0 which is required to restart syscall, then setup + * syscall return value in stack frame + */ + mov x1, sp + bl arch_syscall_prepare_signal + msr daifset, #3 ldp x2, x3, [sp], #0x10 /* SPSR and ELR. */ @@ -177,7 +187,10 @@ arch_syscall_exit: ldp x12, x13, [sp], #0x10 ldp x10, x11, [sp], #0x10 ldp x8, x9, [sp], #0x10 - add sp, sp, #0x40 + ldp x6, x7, [sp], #0x10 + ldp x4, x5, [sp], #0x10 + ldp x2, x3, [sp], #0x10 + ldp x0, x1, [sp], #0x10 RESTORE_FPU sp /* the sp is reset to the outer most level, irq and fiq are disabled */ @@ -383,10 +396,53 @@ lwp_check_debug_quit: ldp x29, x30, [sp], #0x10 ret +.global arch_syscall_restart +arch_syscall_restart: + msr daifset, 3 + + mov sp, x1 + /* drop exception frame in user stack */ + msr sp_el0, x0 + + /* restore previous exception frame */ + msr spsel, #0 + + ldp x2, x3, [sp], #0x10 + msr elr_el1, x2 + msr spsr_el1, x3 + + ldp x29, x30, [sp], #0x10 + + ldp x28, x29, [sp], #0x10 + msr fpcr, x28 + msr fpsr, x29 + + ldp x28, x29, [sp], #0x10 + ldp x26, x27, [sp], #0x10 + ldp x24, x25, [sp], #0x10 + ldp x22, x23, [sp], #0x10 + ldp x20, x21, [sp], #0x10 + ldp x18, x19, [sp], #0x10 + ldp x16, x17, [sp], #0x10 + ldp x14, x15, [sp], #0x10 + ldp x12, x13, [sp], #0x10 + ldp x10, x11, [sp], #0x10 + ldp x8, x9, [sp], #0x10 + ldp x6, x7, [sp], #0x10 + ldp x4, x5, [sp], #0x10 + ldp x2, x3, [sp], #0x10 + ldp x0, x1, [sp], #0x10 + RESTORE_FPU sp + + msr spsel, #1 + + b vector_exception + arch_signal_quit: /* drop current exception frame */ add sp, sp, #CONTEXT_SIZE + mov x1, sp mrs x0, sp_el0 bl arch_signal_ucontext_restore add x0, x0, #-CONTEXT_SIZE @@ -456,6 +512,11 @@ arch_thread_signal_enter: /* arch_signal_ucontext_save(user_sp, psiginfo, exp_frame, save_sig_mask); */ bl arch_signal_ucontext_save + mov x22, x0 + /* get and saved pointer to uframe */ + bl arch_signal_ucontext_get_frame + mov x2, x0 + mov x0, x22 dc cvau, x0 dsb sy @@ -483,12 +544,15 @@ arch_thread_signal_enter: /** set the return address to the sigreturn */ mov x30, x0 + cbnz x21, 1f + mov x21, x30 +1: /** set the entry address of signal handler */ msr elr_el1, x21 /* siginfo is above the return address */ - add x2, x30, 16 - add x1, x2, #CONTEXT_SIZE + add x1, x30, UCTX_ABI_OFFSET_TO_SI + /* uframe is saved in x2 */ mov x0, x19 /** diff --git a/components/lwp/arch/arm/cortex-a/lwp_arch.c b/components/lwp/arch/arm/cortex-a/lwp_arch.c index 0d8f965429..0582ea70b7 100644 --- a/components/lwp/arch/arm/cortex-a/lwp_arch.c +++ b/components/lwp/arch/arm/cortex-a/lwp_arch.c @@ -192,6 +192,12 @@ void *arch_signal_ucontext_save(rt_base_t lr, siginfo_t *psiginfo, return new_sp; } +void arch_syscall_set_errno(void *eframe, int expected, int code) +{ + /* NO support */ + return ; +} + #ifdef LWP_ENABLE_ASID #define MAX_ASID_BITS 8 #define MAX_ASID (1 << MAX_ASID_BITS) diff --git a/components/lwp/arch/arm/cortex-a/lwp_arch.h b/components/lwp/arch/arm/cortex-a/lwp_arch.h index 7262f446c8..6f5518e4c4 100644 --- a/components/lwp/arch/arm/cortex-a/lwp_arch.h +++ b/components/lwp/arch/arm/cortex-a/lwp_arch.h @@ -35,7 +35,7 @@ rt_inline unsigned long rt_hw_ffz(unsigned long x) rt_inline void icache_invalid_all(void) { - asm volatile ("mcr p15, 0, r0, c7, c5, 0\ndsb\nisb":::"memory");//iciallu + __asm__ volatile ("mcr p15, 0, r0, c7, c5, 0\ndsb\nisb":::"memory");//iciallu } unsigned int arch_get_asid(struct rt_lwp *lwp); diff --git a/components/lwp/arch/risc-v/rv64/lwp_arch.c b/components/lwp/arch/risc-v/rv64/lwp_arch.c index 4b3dfc2e19..62383f91fa 100644 --- a/components/lwp/arch/risc-v/rv64/lwp_arch.c +++ b/components/lwp/arch/risc-v/rv64/lwp_arch.c @@ -242,6 +242,7 @@ int arch_set_thread_context(void (*exit)(void), void *new_thread_stack, * | | * +------------------------+ --> thread sp */ + return 0; } #define ALGIN_BYTES (16) @@ -317,6 +318,12 @@ void *arch_signal_ucontext_save(int signo, siginfo_t *psiginfo, return new_sp; } +void arch_syscall_set_errno(void *eframe, int expected, int code) +{ + /* NO support */ + return ; +} + /** * void lwp_exec_user(void *args, void *kernel_stack, void *user_entry) */ diff --git a/components/lwp/libc_musl.h b/components/lwp/libc_musl.h index 3129a0a6b7..fbc2ad5cb4 100644 --- a/components/lwp/libc_musl.h +++ b/components/lwp/libc_musl.h @@ -28,9 +28,9 @@ #define FUTEX_CLOCK_REALTIME 256 -#define FUTEX_WAITERS 0x80000000 -#define FUTEX_OWNER_DIED 0x40000000 -#define FUTEX_TID_MASK 0x3fffffff +#define FUTEX_WAITERS 0x80000000 +#define FUTEX_OWNER_DIED 0x40000000 +#define FUTEX_TID_MASK 0x3fffffff struct robust_list { diff --git a/components/lwp/lwp.c b/components/lwp/lwp.c index 7e85aaff9e..27d9f2f234 100644 --- a/components/lwp/lwp.c +++ b/components/lwp/lwp.c @@ -12,6 +12,8 @@ * 2023-02-20 wangxiaoyao inv icache before new app startup * 2023-02-20 wangxiaoyao fix bug on foreground app switch * 2023-10-16 Shell Support a new backtrace framework + * 2023-11-17 xqyjlj add process group and session support + * 2023-11-30 Shell add lwp_startup() */ #define DBG_TAG "lwp" @@ -39,7 +41,7 @@ #include "lwp_arch_comm.h" #include "lwp_signal.h" #include "lwp_dbg.h" -#include "console.h" +#include #ifdef ARCH_MM_MMU #include @@ -59,16 +61,31 @@ static const char elf_magic[] = {0x7f, 'E', 'L', 'F'}; #ifdef DFS_USING_WORKDIR extern char working_directory[]; #endif -static struct termios stdin_termios, old_stdin_termios; -int load_ldso(struct rt_lwp *lwp, char *exec_name, char *const argv[], char *const envp[]); - -struct termios *get_old_termios(void) +/** + * @brief The default console is only a backup device with lowest priority. + * It's always recommended to scratch the console from the boot arguments. + * And dont forget to register the device with a higher priority. + */ +static rt_err_t lwp_default_console_setup(void) { - return &old_stdin_termios; + rt_device_t bakdev = rt_device_find("ttyS0"); + rt_err_t rc; + + if (bakdev) + { + lwp_console_register_backend(bakdev, LWP_CONSOLE_LOWEST_PRIOR); + rc = RT_EOK; + } + else + { + rc = -RT_EINVAL; + } + + return rc; } -int lwp_component_init(void) +static int lwp_component_init(void) { int rc; if ((rc = lwp_tid_init()) != RT_EOK) @@ -83,10 +100,99 @@ int lwp_component_init(void) { LOG_E("%s: rt_channel_component_init failed", __func__); } + else if ((rc = lwp_futex_init()) != RT_EOK) + { + LOG_E("%s: lwp_futex_init() failed", __func__); + } + else if ((rc = lwp_default_console_setup()) != RT_EOK) + { + LOG_E("%s: lwp_default_console_setup() failed", __func__); + } return rc; } INIT_COMPONENT_EXPORT(lwp_component_init); +rt_weak int lwp_startup_debug_request(void) +{ + return 0; +} + +#define LATENCY_TIMES (3) +#define LATENCY_IN_MSEC (128) +#define LWP_CONSOLE_PATH "CONSOLE=/dev/console" +const char *init_search_path[] = { + "/sbin/init", + "/bin/init", +}; + +/** + * Startup process 0 and do the essential works + * This is the "Hello World" point of RT-Smart + */ +static int lwp_startup(void) +{ + int error; + + const char *init_path; + char *argv[] = {0, "&"}; + char *envp[] = {LWP_CONSOLE_PATH, 0}; + +#ifdef LWP_DEBUG + int command; + int countdown = LATENCY_TIMES; + while (countdown) + { + command = lwp_startup_debug_request(); + if (command) + { + return 0; + } + rt_kprintf("Press any key to stop init process startup ... %d\n", countdown); + countdown -= 1; + rt_thread_mdelay(LATENCY_IN_MSEC); + } + rt_kprintf("Starting init ...\n"); +#endif + + for (size_t i = 0; i < sizeof(init_search_path)/sizeof(init_search_path[0]); i++) + { + struct stat s; + init_path = init_search_path[i]; + error = stat(init_path, &s); + if (error == 0) + { + argv[0] = (void *)init_path; + error = lwp_execve((void *)init_path, 0, sizeof(argv)/sizeof(argv[0]), argv, envp); + if (error < 0) + { + LOG_E("%s: failed to startup process 0 (init)\n" + "Switching to legacy mode...", __func__); + } + else if (error != 1) + { + LOG_E("%s: pid 1 is already allocated", __func__); + error = -EBUSY; + } + else + { + rt_lwp_t p = lwp_from_pid_locked(1); + p->sig_protected = 0; + + error = 0; + } + break; + } + } + + if (error) + { + LOG_E("%s: init program not found\n" + "Switching to legacy mode...", __func__); + } + return error; +} +INIT_APP_EXPORT(lwp_startup); + void lwp_setcwd(char *buf) { struct rt_lwp *lwp = RT_NULL; @@ -100,11 +206,11 @@ void lwp_setcwd(char *buf) lwp = (struct rt_lwp *)rt_thread_self()->lwp; if (lwp) { - rt_strncpy(lwp->working_directory, buf, DFS_PATH_MAX); + rt_strncpy(lwp->working_directory, buf, DFS_PATH_MAX - 1); } else { - rt_strncpy(working_directory, buf, DFS_PATH_MAX); + rt_strncpy(working_directory, buf, DFS_PATH_MAX - 1); } return ; @@ -114,8 +220,13 @@ char *lwp_getcwd(void) { char *dir_buf = RT_NULL; struct rt_lwp *lwp = RT_NULL; + rt_thread_t thread = rt_thread_self(); + + if (thread) + { + lwp = (struct rt_lwp *)thread->lwp; + } - lwp = (struct rt_lwp *)rt_thread_self()->lwp; if (lwp) { if(lwp->working_directory[0] != '/') @@ -1077,25 +1188,35 @@ void lwp_cleanup(struct rt_thread *tid) return; } -static void lwp_copy_stdio_fdt(struct rt_lwp *lwp) +static void lwp_execve_setup_stdio(struct rt_lwp *lwp) { - struct dfs_file *d; struct dfs_fdtable *lwp_fdt; + struct dfs_file *cons_file; + int cons_fd; lwp_fdt = &lwp->fdt; + + /* open console */ + cons_fd = open("/dev/console", O_RDWR); + if (cons_fd < 0) + { + LOG_E("%s: Cannot open console tty", __func__); + return ; + } + LOG_D("%s: open console as fd %d", __func__, cons_fd); + /* init 4 fds */ lwp_fdt->fds = rt_calloc(4, sizeof(void *)); if (lwp_fdt->fds) { + cons_file = fd_get(cons_fd); lwp_fdt->maxfd = 4; - d = fd_get(0); - fdt_fd_associate_file(lwp_fdt, 0, d); - d = fd_get(1); - fdt_fd_associate_file(lwp_fdt, 1, d); - d = fd_get(2); - fdt_fd_associate_file(lwp_fdt, 2, d); + fdt_fd_associate_file(lwp_fdt, 0, cons_file); + fdt_fd_associate_file(lwp_fdt, 1, cons_file); + fdt_fd_associate_file(lwp_fdt, 2, cons_file); } + close(cons_fd); return; } @@ -1197,11 +1318,8 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) int result; struct rt_lwp *lwp; char *thread_name; - char *argv_last = argv[argc - 1]; - int bg = 0; struct process_aux *aux; int tid = 0; - int ret; if (filename == RT_NULL) { @@ -1213,7 +1331,7 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) return -EACCES; } - lwp = lwp_create(LWP_CREATE_FLAG_ALLOC_PID); + lwp = lwp_create(LWP_CREATE_FLAG_ALLOC_PID | LWP_CREATE_FLAG_NOTRACE_EXEC); if (lwp == RT_NULL) { @@ -1236,12 +1354,6 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) } #endif - if (argv_last[0] == '&' && argv_last[1] == '\0') - { - argc--; - bg = 1; - } - if ((aux = lwp_argscopy(lwp, argc, argv, envp)) == RT_NULL) { lwp_tid_put(tid); @@ -1263,7 +1375,7 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) rt_thread_t thread = RT_NULL; rt_uint32_t priority = 25, tick = 200; - lwp_copy_stdio_fdt(lwp); + lwp_execve_setup_stdio(lwp); /* obtain the base name */ thread_name = strrchr(filename, '/'); @@ -1284,88 +1396,46 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) if (thread != RT_NULL) { struct rt_lwp *self_lwp; + rt_session_t session; + rt_processgroup_t group; thread->tid = tid; lwp_tid_set_thread(tid, thread); LOG_D("lwp kernel => (0x%08x, 0x%08x)\n", (rt_size_t)thread->stack_addr, (rt_size_t)thread->stack_addr + thread->stack_size); self_lwp = lwp_self(); + + /* when create init, self_lwp == null */ + if (self_lwp == RT_NULL && lwp_to_pid(lwp) != 1) + { + self_lwp = lwp_from_pid_and_lock(1); + } + if (self_lwp) { - //lwp->tgroup_leader = &thread; //add thread group leader for lwp - lwp->__pgrp = tid; - lwp->session = self_lwp->session; /* lwp add to children link */ lwp_children_register(self_lwp, lwp); } - else - { - //lwp->tgroup_leader = &thread; //add thread group leader for lwp - lwp->__pgrp = tid; - } - if (!bg) + + session = RT_NULL; + group = RT_NULL; + + group = lwp_pgrp_create(lwp); + if (group) { - if (lwp->session == -1) + lwp_pgrp_insert(group, lwp); + if (self_lwp == RT_NULL) { - struct tty_struct *tty = RT_NULL; - struct rt_lwp *old_lwp; - tty = (struct tty_struct *)console_tty_get(); - old_lwp = tty->foreground; - if (old_lwp) - { - rt_mutex_take(&tty->lock, RT_WAITING_FOREVER); - ret = tty_push(&tty->head, old_lwp); - rt_mutex_release(&tty->lock); - - if (ret < 0) - { - lwp_tid_put(tid); - lwp_ref_dec(lwp); - LOG_E("malloc fail!\n"); - return -ENOMEM; - } - } - - lwp->tty = tty; - lwp->tty->pgrp = lwp->__pgrp; - lwp->tty->session = lwp->session; - lwp->tty->foreground = lwp; - tcgetattr(1, &stdin_termios); - old_stdin_termios = stdin_termios; - stdin_termios.c_lflag |= ICANON | ECHO | ECHOCTL; - tcsetattr(1, 0, &stdin_termios); + session = lwp_session_create(lwp); + lwp_session_insert(session, group); } else { - if (self_lwp != RT_NULL) - { - rt_mutex_take(&self_lwp->tty->lock, RT_WAITING_FOREVER); - ret = tty_push(&self_lwp->tty->head, self_lwp); - rt_mutex_release(&self_lwp->tty->lock); - if (ret < 0) - { - lwp_tid_put(tid); - lwp_ref_dec(lwp); - LOG_E("malloc fail!\n"); - return -ENOMEM; - } - - lwp->tty = self_lwp->tty; - lwp->tty->pgrp = lwp->__pgrp; - lwp->tty->session = lwp->session; - lwp->tty->foreground = lwp; - } - else - { - lwp->tty = RT_NULL; - } - + session = lwp_session_find(lwp_sid_get_byprocess(self_lwp)); + lwp_session_insert(session, group); } } - else - { - lwp->background = RT_TRUE; - } + thread->lwp = lwp; #ifndef ARCH_MM_MMU struct lwp_app_head *app_head = (struct lwp_app_head*)lwp->text_entry; @@ -1381,6 +1451,8 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) #endif /* not defined ARCH_MM_MMU */ rt_list_insert_after(&lwp->t_grp, &thread->sibling); + lwp->did_exec = RT_TRUE; + if (debug && rt_dbg_ops) { lwp->debug = debug; @@ -1482,30 +1554,30 @@ rt_err_t lwp_backtrace_frame(rt_thread_t uthread, struct rt_hw_backtrace_frame * char **argv; rt_lwp_t lwp; - if (uthread->lwp) + if (uthread && uthread->lwp && rt_scheduler_is_available()) { lwp = uthread->lwp; argv = lwp_get_command_line_args(lwp); if (argv) { - LOG_RAW("please use: addr2line -e %s -a -f", argv[0]); + rt_kprintf("please use: addr2line -e %s -a -f", argv[0]); lwp_free_command_line_args(argv); } else { - LOG_RAW("please use: addr2line -e %s -a -f", lwp->cmd); + rt_kprintf("please use: addr2line -e %s -a -f", lwp->cmd); } while (nesting < RT_BACKTRACE_LEVEL_MAX_NR) { - LOG_RAW(" 0x%lx", frame->pc); + rt_kprintf(" 0x%lx", frame->pc); if (rt_hw_backtrace_frame_unwind(uthread, frame)) { break; } nesting++; } - LOG_RAW("\n"); + rt_kprintf("\n"); rc = RT_EOK; } return rc; diff --git a/components/lwp/lwp.h b/components/lwp/lwp.h index 1ac98493a1..c504ef60ad 100644 --- a/components/lwp/lwp.h +++ b/components/lwp/lwp.h @@ -9,6 +9,9 @@ * 2019-10-12 Jesven Add MMU and userspace support * 2020-10-08 Bernard Architecture and code cleanup * 2021-08-26 linzhenxing add lwp_setcwd\lwp_getcwd + * 2023-11-17 xqyjlj add process group and session support + * 2023-12-02 Shell Add macro to create lwp status and + * fix dead lock problem on pgrp */ /* @@ -48,10 +51,6 @@ #include #endif /* RT_USING_MUSLLIBC */ -#ifdef RT_USING_TTY -struct tty_struct; -#endif /* RT_USING_TTY */ - #ifdef __cplusplus extern "C" { #endif @@ -76,12 +75,48 @@ struct rt_lwp_notify rt_slist_t list_node; }; +struct lwp_tty; + #ifdef RT_USING_MUSLLIBC -#define LWP_CREATE_STAT(exit_code) (((exit_code) & 0xff) << 8) +#define LWP_COREDUMP_FLAG 0x80 +#define LWP_CREATE_STAT_EXIT(exit_code) (((exit_code)&0xff) << 8) +#define LWP_CREATE_STAT_SIGNALED(signo, coredump) (((signo) & 0x7f) | (coredump ? LWP_COREDUMP_FLAG : 0)) +#define LWP_CREATE_STAT_STOPPED(signo) (LWP_CREATE_STAT_EXIT(signo) | 0x7f) +#define LWP_CREATE_STAT_CONTINUED (0xffff) #else #error "No compatible lwp set status provided for this libc" #endif +typedef struct rt_lwp *rt_lwp_t; +typedef struct rt_session *rt_session_t; +typedef struct rt_processgroup *rt_processgroup_t; + +struct rt_session { + struct rt_object object; + rt_lwp_t leader; + rt_list_t processgroup; + pid_t sid; + pid_t foreground_pgid; + struct rt_mutex mutex; + struct lwp_tty *ctty; +}; + +struct rt_processgroup { + struct rt_object object; + rt_lwp_t leader; + rt_list_t process; + rt_list_t pgrp_list_node; + pid_t pgid; + pid_t sid; + struct rt_session *session; + struct rt_mutex mutex; + + rt_atomic_t ref; + + /* flags on process group */ + unsigned int is_orphaned:1; +}; + struct rt_lwp { #ifdef ARCH_MM_MMU @@ -100,14 +135,21 @@ struct rt_lwp uint8_t lwp_type; uint8_t reserv[3]; - struct rt_lwp *parent; - struct rt_lwp *first_child; - struct rt_lwp *sibling; + /* flags */ + unsigned int terminated:1; + unsigned int background:1; + unsigned int term_ctrlterm:1; /* have control terminal? */ + unsigned int did_exec:1; /* Whether exec has been performed */ + unsigned int jobctl_stopped:1; /* job control: current proc is stopped */ + unsigned int wait_reap_stp:1; /* job control: has wait event for parent */ + unsigned int sig_protected:1; /* signal: protected proc cannot be killed or stopped */ + + struct rt_lwp *parent; /* parent process */ + struct rt_lwp *first_child; /* first child process */ + struct rt_lwp *sibling; /* sibling(child) process */ - rt_list_t wait_list; - rt_bool_t terminated; - rt_bool_t background; - int lwp_ret; + struct rt_wqueue waitpid_waiters; + lwp_status_t lwp_status; void *text_entry; uint32_t text_size; @@ -118,15 +160,16 @@ struct rt_lwp void *args; uint32_t args_length; pid_t pid; - pid_t __pgrp; /*Accessed via process_group()*/ - pid_t tty_old_pgrp; - pid_t session; - rt_list_t t_grp; - rt_list_t timer; /* POSIX timer object binding to a process */ + pid_t sid; /* session ID */ + pid_t pgid; /* process group ID */ + struct rt_processgroup *pgrp; + rt_list_t pgrp_node; /* process group node */ + rt_list_t t_grp; /* thread group */ + rt_list_t timer; /* POSIX timer object binding to a process */ - int leader; /* boolean value for session group_leader*/ struct dfs_fdtable fdt; char cmd[RT_NAME_MAX]; + char *exe_file; /* process file path */ /* POSIX signal */ struct lwp_signal signal; @@ -135,7 +178,7 @@ struct rt_lwp struct rt_mutex object_mutex; struct rt_user_context user_ctx; - struct rt_wqueue wait_queue; /*for console */ + struct rt_wqueue wait_queue; /* for console */ struct tty_struct *tty; /* NULL if no tty */ struct lwp_avl_struct *address_search_head; /* for addressed object fast search */ @@ -152,8 +195,9 @@ struct rt_lwp uint64_t generation; unsigned int asid; #endif + struct rusage rt_rusage; }; -typedef struct rt_lwp *rt_lwp_t; + struct rt_lwp *lwp_self(void); rt_err_t lwp_children_register(struct rt_lwp *parent, struct rt_lwp *child); @@ -182,7 +226,7 @@ void lwp_tid_put(int tid); * @return rt_thread_t */ rt_thread_t lwp_tid_get_thread_and_inc_ref(int tid); - +rt_thread_t lwp_tid_get_thread_raw(int tid); /** * @brief Decrease a reference count * @@ -216,6 +260,78 @@ rt_err_t lwp_futex_init(void); rt_err_t lwp_futex(struct rt_lwp *lwp, int *uaddr, int op, int val, const struct timespec *timeout, int *uaddr2, int val3); +/* processgroup api */ +rt_inline pid_t lwp_pgid_get_bypgrp(rt_processgroup_t group) +{ + return group ? group->pgid : 0; +} + +rt_inline pid_t lwp_pgid_get_byprocess(rt_lwp_t process) +{ + return process ? process->pgid : 0; +} +rt_processgroup_t lwp_pgrp_find(pid_t pgid); +void lwp_pgrp_dec_ref(rt_processgroup_t pgrp); +rt_processgroup_t lwp_pgrp_find_and_inc_ref(pid_t pgid); +rt_processgroup_t lwp_pgrp_create(rt_lwp_t leader); +int lwp_pgrp_delete(rt_processgroup_t group); + +/** + * Note: all the pgrp with process operation must be called in the context where + * process lock is taken. This is protect us from a possible dead lock condition + * + * The order is mandatory in the case: + * PGRP_LOCK(pgrp); + * LWP_LOCK(p); + * ... bussiness logic + * LWP_UNLOCK(p); + * PGRP_UNLOCK(pgrp); + */ +int lwp_pgrp_insert(rt_processgroup_t group, rt_lwp_t process); +int lwp_pgrp_remove(rt_processgroup_t group, rt_lwp_t process); +int lwp_pgrp_move(rt_processgroup_t group, rt_lwp_t process); + +int lwp_pgrp_update_children_info(rt_processgroup_t group, pid_t sid, pid_t pgid); + +/* session api */ +rt_inline pid_t lwp_sid_get_bysession(rt_session_t session) +{ + return session ? session->sid : 0; +} + +rt_inline pid_t lwp_sid_get_bypgrp(rt_processgroup_t group) +{ + return group ? group->sid : 0; +} + +rt_inline pid_t lwp_sid_get_byprocess(rt_lwp_t process) +{ + return process ? process->sid : 0; +} + +rt_session_t lwp_session_find(pid_t sid); +rt_session_t lwp_session_create(struct rt_lwp *leader); +int lwp_session_delete(rt_session_t session); + +/** + * Note: all the session operation must be called in the context where + * process lock is taken. This is protect us from a possible dead lock condition + * + * The order is mandatory in the case: + * PGRP_LOCK(pgrp); + * LWP_LOCK(p); + * ... bussiness logic + * LWP_UNLOCK(p); + * PGRP_UNLOCK(pgrp); + */ +int lwp_session_insert(rt_session_t session, rt_processgroup_t group); +int lwp_session_remove(rt_session_t session, rt_processgroup_t group); +int lwp_session_move(rt_session_t session, rt_processgroup_t group); +int lwp_session_update_children_info(rt_session_t session, pid_t sid); +int lwp_session_set_foreground(rt_session_t session, pid_t pgid); + +/* complete the job control related bussiness on process exit */ +void lwp_jobctrl_on_exit(struct rt_lwp *lwp); #ifdef __cplusplus } @@ -301,6 +417,7 @@ int dbg_step_type(void); void dbg_attach_req(void *pc); int dbg_check_suspend(void); void rt_hw_set_process_id(int pid); +void lwp_futex_exit_robust_list(rt_thread_t thread); /* backtrace service */ rt_err_t lwp_backtrace_frame(rt_thread_t uthread, struct rt_hw_backtrace_frame *frame); diff --git a/components/lwp/lwp_arch_comm.h b/components/lwp/lwp_arch_comm.h index 885cd63ca9..3eec407862 100644 --- a/components/lwp/lwp_arch_comm.h +++ b/components/lwp/lwp_arch_comm.h @@ -61,6 +61,10 @@ rt_noreturn void arch_thread_signal_enter(int signo, siginfo_t *psiginfo, void *exp_frame, void *entry_uaddr, lwp_sigset_t *save_sig_mask); +void arch_signal_check_erestart(void *eframe, void *ksp); + +void arch_syscall_set_errno(void *eframe, int expected, int code); + int arch_backtrace_uthread(rt_thread_t thread); #endif /* __LWP_ARCH_COMM__ */ diff --git a/components/lwp/lwp_elf.c b/components/lwp/lwp_elf.c index 0610769b26..f654adc5b7 100644 --- a/components/lwp/lwp_elf.c +++ b/components/lwp/lwp_elf.c @@ -797,6 +797,7 @@ int lwp_load(const char *filename, struct rt_lwp *lwp, uint8_t *load_addr, size_ /* copy file name to process name */ rt_strncpy(lwp->cmd, filename, RT_NAME_MAX); + lwp->exe_file = dfs_normalize_path(NULL, filename); // malloc ret = elf_file_load(&load_info); if (ret != RT_EOK) diff --git a/components/lwp/lwp_futex.c b/components/lwp/lwp_futex.c index f32b4a18f4..a49a86680a 100644 --- a/components/lwp/lwp_futex.c +++ b/components/lwp/lwp_futex.c @@ -475,6 +475,11 @@ static long _futex_wake(rt_futex_t futex, struct rt_lwp *lwp, int number, { number--; woken_cnt++; + is_empty = RT_FALSE; + } + else + { + is_empty = RT_TRUE; } _futex_unlock(lwp, op_flags); } @@ -512,23 +517,16 @@ static long _futex_requeue(rt_futex_t futex1, rt_futex_t futex2, */ while (nr_wake && !is_empty) { - rt_sched_lock_level_t slvl; - rt_sched_lock(&slvl); - is_empty = rt_list_isempty(&(futex1->waiting_thread)); - if (!is_empty) + if (rt_susp_list_dequeue(&futex1->waiting_thread, RT_EOK)) { - thread = RT_THREAD_LIST_NODE_ENTRY(futex1->waiting_thread.next); - /* remove from waiting list */ - rt_list_remove(&RT_THREAD_LIST_NODE(thread)); - - thread->error = RT_EOK; - /* resume the suspended thread */ - rt_thread_resume(thread); - nr_wake--; woken_cnt++; + is_empty = RT_FALSE; + } + else + { + is_empty = RT_TRUE; } - rt_sched_unlock(slvl); } rtn = woken_cnt; @@ -542,7 +540,10 @@ static long _futex_requeue(rt_futex_t futex1, rt_futex_t futex2, { rt_sched_lock_level_t slvl; rt_sched_lock(&slvl); + + /* moving from one susp list to another */ is_empty = rt_list_isempty(&(futex1->waiting_thread)); + if (!is_empty) { thread = RT_THREAD_LIST_NODE_ENTRY(futex1->waiting_thread.next); @@ -914,7 +915,7 @@ void lwp_futex_exit_robust_list(rt_thread_t thread) rc = _fetch_robust_entry(&next_entry, &entry->next, &next_pi); if (entry != pending) { - if (_handle_futex_death((void *)entry + futex_offset, thread, pi, + if (_handle_futex_death((int *)((size_t)entry + futex_offset), thread, pi, RT_FALSE)) return; } diff --git a/components/lwp/lwp_internal.c b/components/lwp/lwp_internal.c index bb31650790..e460a1ac3f 100644 --- a/components/lwp/lwp_internal.c +++ b/components/lwp/lwp_internal.c @@ -6,6 +6,7 @@ * Change Logs: * Date Author Notes * 2023-07-25 Shell first version + * 2023-11-25 Shell Add pgrp, session lock API */ #define DBG_TAG "lwp.internal" @@ -15,7 +16,7 @@ #include #include "lwp_internal.h" -static rt_err_t _mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, rt_bool_t interruptable) +static rt_err_t _mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, int flags) { LWP_DEF_RETURN_CODE(rc); int retry; @@ -45,15 +46,15 @@ static rt_err_t _mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, rt_bool_t i do { retry = 0; - if (interruptable) + if (flags & LWP_MTX_FLAGS_INTR) rc = rt_mutex_take_interruptible(mtx, effect_timeout); else - rc = rt_mutex_take(mtx, effect_timeout); + rc = rt_mutex_take_killable(mtx, effect_timeout); #ifdef LWP_DEBUG if (rc == RT_EOK) { - if (rt_mutex_get_hold(mtx) > 1) + if (!(flags & LWP_MTX_FALGS_NESTED) && rt_mutex_get_hold(mtx) > 1) { LOG_W("Already hold the lock"); rt_backtrace(); @@ -88,6 +89,7 @@ static rt_err_t _mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, rt_bool_t i } else { + rc = -RT_EINVAL; LOG_W("%s: mtx should not be NULL", __func__); RT_ASSERT(0); } @@ -95,10 +97,10 @@ static rt_err_t _mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, rt_bool_t i LWP_RETURN(rc); } -rt_err_t lwp_mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, rt_bool_t interruptable) +rt_err_t lwp_mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, int flags) { LWP_DEF_RETURN_CODE(rc); - rc = _mutex_take_safe(mtx, timeout, interruptable); + rc = _mutex_take_safe(mtx, timeout, flags); LWP_RETURN(rc); } @@ -116,18 +118,17 @@ rt_err_t lwp_mutex_release_safe(rt_mutex_t mtx) LWP_RETURN(rc); } -rt_err_t lwp_critical_enter(struct rt_lwp *lwp) +rt_err_t lwp_critical_enter(struct rt_lwp *lwp, int flags) { rt_err_t rc; - rc = lwp_mutex_take_safe(&lwp->lwp_lock, RT_WAITING_FOREVER, 0); + do { + rc = lwp_mutex_take_safe(&lwp->lwp_lock, RT_WAITING_FOREVER, flags); + } while (rc != RT_EOK && !(flags & LWP_MTX_FLAGS_INTR) && rc == -RT_EINTR); /* if current process is force killed */ - if (rc != RT_EOK) + if (rc != RT_EOK && rc != -RT_EINTR) { - if (rc == -RT_EINTR && lwp_self() != RT_NULL) - sys_exit(EXIT_SUCCESS); - else - LOG_I("%s: unexpected return code = %ld", __func__, rc); + LOG_I("%s: unexpected return code = %ld", __func__, rc); } return rc; @@ -137,3 +138,45 @@ rt_err_t lwp_critical_exit(struct rt_lwp *lwp) { return lwp_mutex_release_safe(&lwp->lwp_lock); } + +rt_err_t lwp_pgrp_critical_enter(struct rt_processgroup *pgrp, int flags) +{ + rt_err_t rc; + do { + rc = lwp_mutex_take_safe(&pgrp->mutex, RT_WAITING_FOREVER, flags); + } while (rc != RT_EOK && !(flags & LWP_MTX_FLAGS_INTR) && rc == -RT_EINTR); + + /* if current process is force killed */ + if (rc != RT_EOK && rc != -RT_EINTR) + { + LOG_I("%s: unexpected return code = %ld", __func__, rc); + } + + return rc; +} + +rt_err_t lwp_pgrp_critical_exit(struct rt_processgroup *pgrp) +{ + return lwp_mutex_release_safe(&pgrp->mutex); +} + +rt_err_t lwp_sess_critical_enter(struct rt_session *sess, int flags) +{ + rt_err_t rc; + do { + rc = lwp_mutex_take_safe(&sess->mutex, RT_WAITING_FOREVER, flags); + } while (rc != RT_EOK && !(flags & LWP_MTX_FLAGS_INTR) && rc == -RT_EINTR); + + /* if current process is force killed */ + if (rc != RT_EOK && rc != -RT_EINTR) + { + LOG_I("%s: unexpected return code = %ld", __func__, rc); + } + + return rc; +} + +rt_err_t lwp_sess_critical_exit(struct rt_session *sess) +{ + return lwp_mutex_release_safe(&sess->mutex); +} diff --git a/components/lwp/lwp_internal.h b/components/lwp/lwp_internal.h index 1a605b007a..c942be0136 100644 --- a/components/lwp/lwp_internal.h +++ b/components/lwp/lwp_internal.h @@ -6,21 +6,32 @@ * Change Logs: * Date Author Notes * 2023-07-25 Shell first version + * 2023-11-25 Shell Add pgrp, session lock API */ #ifndef __LWP_INTERNAL_H__ #define __LWP_INTERNAL_H__ #include "lwp.h" +#include "lwp_arch.h" #include "lwp_user_mm.h" +#include "lwp_mm.h" #include #include "libc_musl.h" struct rt_lwp; -rt_err_t lwp_mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, rt_bool_t interruptable); + +#define LWP_MTX_FLAGS_INTR 0x1 /* interruptible waiting */ +#define LWP_MTX_FALGS_NESTED 0x2 /* allow nested */ +rt_err_t lwp_mutex_take_safe(rt_mutex_t mtx, rt_int32_t timeout, int flags); rt_err_t lwp_mutex_release_safe(rt_mutex_t mtx); +rt_inline rt_bool_t lwp_in_user_space(const char *addr) +{ + return (addr >= (char *)USER_VADDR_START && addr < (char *)USER_VADDR_TOP); +} + #ifdef RT_USING_SMP #define LOCAL_IRQ_MASK() rt_hw_local_irq_disable() #define LOCAL_IRQ_UNMASK(level) rt_hw_local_irq_enable(level) @@ -30,16 +41,34 @@ rt_err_t lwp_mutex_release_safe(rt_mutex_t mtx); #endif #ifndef LWP_USING_CPUS_LOCK -rt_err_t lwp_critical_enter(struct rt_lwp *lwp); +rt_err_t lwp_sess_critical_enter(struct rt_session *sess, int flags); +rt_err_t lwp_sess_critical_exit(struct rt_session *sess); +rt_err_t lwp_pgrp_critical_enter(struct rt_processgroup *pgrp, int flags); +rt_err_t lwp_pgrp_critical_exit(struct rt_processgroup *pgrp); +rt_err_t lwp_critical_enter(struct rt_lwp *lwp, int flags); rt_err_t lwp_critical_exit(struct rt_lwp *lwp); -#define LWP_LOCK(lwp) \ - do { \ - RT_DEBUG_SCHEDULER_AVAILABLE(1); \ - if (lwp_critical_enter(lwp) != RT_EOK) \ - { \ - RT_ASSERT(0); \ - } \ +#define LWP_ASSERT_LOCKED(proc) RT_ASSERT(rt_mutex_get_owner(&(proc)->lwp_lock) == rt_thread_self()) +#define PGRP_ASSERT_LOCKED(pgrp) RT_ASSERT(rt_mutex_get_owner(&(pgrp)->mutex) == rt_thread_self()) + +#define LWP_LOCK(lwp) \ + do \ + { \ + RT_DEBUG_SCHEDULER_AVAILABLE(1); \ + if (lwp_critical_enter(lwp, 0) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ + } while (0) + +#define LWP_LOCK_NESTED(lwp) \ + do \ + { \ + RT_DEBUG_SCHEDULER_AVAILABLE(1); \ + if (lwp_critical_enter(lwp, LWP_MTX_FALGS_NESTED) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ } while (0) #define LWP_UNLOCK(lwp) \ @@ -50,10 +79,72 @@ rt_err_t lwp_critical_exit(struct rt_lwp *lwp); } \ } while (0) +#define PGRP_LOCK(pgrp) \ + do \ + { \ + RT_DEBUG_SCHEDULER_AVAILABLE(1); \ + if (lwp_pgrp_critical_enter(pgrp, 0) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ + } while (0) + +#define PGRP_LOCK_NESTED(pgrp) \ + do \ + { \ + RT_DEBUG_SCHEDULER_AVAILABLE(1); \ + if (lwp_pgrp_critical_enter(pgrp, LWP_MTX_FALGS_NESTED) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ + } while (0) + +#define PGRP_UNLOCK(pgrp) \ + do \ + { \ + if (lwp_pgrp_critical_exit(pgrp) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ + } while (0) + +#define SESS_LOCK(sess) \ + do \ + { \ + RT_DEBUG_SCHEDULER_AVAILABLE(1); \ + if (lwp_sess_critical_enter(sess, 0) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ + } while (0) + +#define SESS_LOCK_NESTED(sess) \ + do \ + { \ + RT_DEBUG_SCHEDULER_AVAILABLE(1); \ + if (lwp_sess_critical_enter(sess, LWP_MTX_FALGS_NESTED) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ + } while (0) + +#define SESS_UNLOCK(sess) \ + do \ + { \ + if (lwp_sess_critical_exit(sess) != RT_EOK) \ + { \ + RT_ASSERT(0); \ + } \ + } while (0) + #else #define LWP_LOCK(lwp) rt_base_t level = rt_hw_interrupt_disable() #define LWP_UNLOCK(lwp) rt_hw_interrupt_enable(level) +#define PGRP_LOCK(pgrp) rt_base_t level = rt_hw_interrupt_disable() +#define PGRP_UNLOCK(pgrp) rt_hw_interrupt_enable(level) +#define SESS_LOCK(sess) rt_base_t level = rt_hw_interrupt_disable() +#define SESS_UNLOCK(sess) rt_hw_interrupt_enable(level) #endif /* LWP_USING_CPUS_LOCK */ @@ -95,4 +186,6 @@ rt_err_t lwp_critical_exit(struct rt_lwp *lwp); #define LWP_RETURN(name) {RT_ASSERT(name != _LWP_UNINITIALIZED_RC);return name;} #endif /* LWP_DEBUG */ +int load_ldso(struct rt_lwp *lwp, char *exec_name, char *const argv[], char *const envp[]); + #endif /* __LWP_INTERNAL_H__ */ diff --git a/components/lwp/lwp_jobctrl.c b/components/lwp/lwp_jobctrl.c new file mode 100644 index 0000000000..5f4848f500 --- /dev/null +++ b/components/lwp/lwp_jobctrl.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#define DBG_TAG "lwp.tty" +#define DBG_LVL DBG_INFO +#include + +#include +#include "lwp_internal.h" + +static void jobctrl_set_pgrp_orphaned(struct rt_processgroup *pgrp) +{ + rt_lwp_t proc, nx_proc; + PGRP_LOCK(pgrp); + + pgrp->is_orphaned = 1; + rt_list_for_each_entry(proc, &pgrp->process, pgrp_node) + { + LWP_LOCK(proc); + if (proc->jobctl_stopped) + { + LWP_UNLOCK(proc); + rt_list_for_each_entry_safe(proc, nx_proc, &pgrp->process, pgrp_node) + { + LWP_LOCK(proc); + lwp_signal_kill(proc, SIGHUP, SI_KERNEL, 0); + lwp_signal_kill(proc, SIGCONT, SI_KERNEL, 0); + LWP_UNLOCK(proc); + } + } + LWP_UNLOCK(proc); + } + + PGRP_UNLOCK(pgrp); +} + +void lwp_jobctrl_on_exit(struct rt_lwp *lwp) +{ + rt_processgroup_t pgrp; + rt_session_t session; + lwp_tty_t tp; + + pgrp = lwp->pgrp; + RT_ASSERT(pgrp); + session = pgrp->session; + RT_ASSERT(session); + + /** + * as a session leader, we have to mark tty as freed. So others can race to + * take it before we actually close and released that tty + */ + SESS_LOCK(session); + if (session->sid == lwp->pid) + { + tp = session->ctty; + session->leader = 0; + + /* signal to foreground group that modem is disconnected */ + if (tp) + { + tty_lock(tp); + if (tp->t_session == session) + lwp_tty_signal_pgrp(tp, SIGHUP); + tty_unlock(tp); + } + + /* revoke tty vnode ? */ + + rt_list_for_each_entry(pgrp, &session->processgroup, pgrp_list_node) + { + jobctrl_set_pgrp_orphaned(pgrp); + } + + } + SESS_UNLOCK(session); + + /* release tty */ + /* allow tty stolen? */ +} diff --git a/components/lwp/lwp_pgrp.c b/components/lwp/lwp_pgrp.c new file mode 100644 index 0000000000..700565b165 --- /dev/null +++ b/components/lwp/lwp_pgrp.c @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-17 xqyjlj the first version + * 2023-11-28 Shell Add reference management for pgrp; + * Using lwp lock API and fix the dead lock problem + */ + +#include "lwp.h" +#include "lwp_internal.h" +#include "lwp_syscall.h" + +#define DBG_TAG "lwp.pgrp" +#define DBG_LVL DBG_WARNING +#include + +void lwp_pgrp_dec_ref(rt_processgroup_t pgrp) +{ + if (rt_atomic_add(&pgrp->ref, -1) == 1) + { + rt_mutex_detach(&(pgrp->mutex)); + + /* clear self pgid */ + pgrp->pgid = 0; + rt_free(pgrp); + } +} + +rt_processgroup_t lwp_pgrp_find_and_inc_ref(pid_t pgid) +{ + rt_processgroup_t group; + + group = lwp_pgrp_find(pgid); + if (group) + { + rt_atomic_add(&(group->ref), 1); + } + + return group; +} + +rt_processgroup_t lwp_pgrp_find(pid_t pgid) +{ + rt_base_t level; + rt_processgroup_t group = RT_NULL; + rt_list_t *node = RT_NULL; + struct rt_object_information *information = RT_NULL; + + information = rt_object_get_information(RT_Object_Class_ProcessGroup); + + /* parameter check */ + if ((pgid < 0) || (information == RT_NULL)) + { + return RT_NULL; + } + + if (pgid == 0) + { + pgid = lwp_getpid(); + } + + /* enter critical */ + level = rt_spin_lock_irqsave(&(information->spinlock)); + + /* try to find process group */ + rt_list_for_each(node, &(information->object_list)) + { + group = (rt_processgroup_t)rt_list_entry(node, struct rt_object, list); + if (group->pgid == pgid) + { + rt_spin_unlock_irqrestore(&(information->spinlock), level); + + return group; + } + } + + rt_spin_unlock_irqrestore(&(information->spinlock), level); + + LOG_I("cannot find(pgid:%d)() by (pid:%d, pgid:%d)", pgid, lwp_getpid(), lwp_pgid_get_byprocess(lwp_self())); + + return RT_NULL; +} + +rt_processgroup_t lwp_pgrp_create(rt_lwp_t leader) +{ + rt_processgroup_t group = RT_NULL; + + /* parameter check */ + if (leader == RT_NULL) + { + return RT_NULL; + } + + group = rt_malloc(sizeof(struct rt_processgroup)); + if (group != RT_NULL) + { + rt_object_init(&(group->object), RT_Object_Class_ProcessGroup, "pgrp"); + rt_list_init(&(group->process)); + rt_list_init(&(group->pgrp_list_node)); + rt_mutex_init(&(group->mutex), "pgrp", RT_IPC_FLAG_PRIO); + group->leader = leader; + group->sid = 0; + group->session = RT_NULL; + group->is_orphaned = 0; + group->pgid = lwp_to_pid(leader); + rt_atomic_store(&group->ref, 1); + } + + LOG_I("create(ptr:%p, pgid:%d)() by pid:%d", group, group->pgid, lwp_getpid()); + + return group; +} + +#include + +int lwp_pgrp_delete(rt_processgroup_t group) +{ + int retry = 1; + rt_session_t session = RT_NULL; + int is_session_free = 0; + lwp_tty_t ctty; + + /* parameter check */ + if (group == RT_NULL) + { + return -EINVAL; + } + + LOG_I("delete(ptr:%p, pgid:%d)() by pid:%d", group, group->pgid, lwp_getpid()); + + while (retry) + { + retry = 0; + session = lwp_session_find(lwp_sid_get_bypgrp(group)); + if (session) + { + ctty = session->ctty; + if (ctty) + { + /** + * Note: it's safe to release pgrp even we do this multiple, + * the neccessary check is done before the tty actually detach + */ + tty_lock(ctty); + tty_rel_pgrp(ctty, group); // tty_unlock + } + + SESS_LOCK(session); + PGRP_LOCK_NESTED(group); + if (group->session == session && session->ctty == ctty) + { + rt_object_detach(&(group->object)); + is_session_free = lwp_session_remove(session, group); + } + else + { + retry = 1; + + } + + PGRP_UNLOCK(group); + if (is_session_free != 1) + SESS_UNLOCK(session); + } + else + { + rt_object_detach(&(group->object)); + } + } + + lwp_pgrp_dec_ref(group); + return 0; +} + +int lwp_pgrp_insert(rt_processgroup_t group, rt_lwp_t process) +{ + /* parameter check */ + if (group == RT_NULL || process == RT_NULL) + { + return -EINVAL; + } + + PGRP_LOCK_NESTED(group); + LWP_LOCK_NESTED(process); + RT_ASSERT(rt_mutex_get_hold(&process->lwp_lock) <= rt_mutex_get_hold(&group->mutex)); + + process->pgid = group->pgid; + process->pgrp = group; + process->sid = group->sid; + rt_list_insert_after(&(group->process), &(process->pgrp_node)); + + LWP_UNLOCK(process); + PGRP_UNLOCK(group); + + return 0; +} + +int lwp_pgrp_remove(rt_processgroup_t group, rt_lwp_t process) +{ + rt_bool_t is_empty = RT_FALSE; + + /* parameter check */ + if (group == RT_NULL || process == RT_NULL) + { + return -EINVAL; + } + + PGRP_LOCK_NESTED(group); + LWP_LOCK_NESTED(process); + RT_ASSERT(rt_mutex_get_hold(&process->lwp_lock) <= rt_mutex_get_hold(&group->mutex)); + + rt_list_remove(&(process->pgrp_node)); + /* clear children sid and pgid */ + process->pgrp = RT_NULL; + process->pgid = 0; + process->sid = 0; + + LWP_UNLOCK(process); + + is_empty = rt_list_isempty(&(group->process)); + + PGRP_UNLOCK(group); + + if (is_empty) + { + lwp_pgrp_delete(group); + return 1; + } + + return 0; +} + +int lwp_pgrp_move(rt_processgroup_t group, rt_lwp_t process) +{ + int retry = 1; + rt_processgroup_t old_group; + + /* parameter check */ + if (group == RT_NULL || process == RT_NULL) + { + return -EINVAL; + } + + if (lwp_pgid_get_bypgrp(group) == lwp_pgid_get_byprocess(process)) + { + return 0; + } + + PGRP_LOCK(group); + while (retry) + { + retry = 0; + old_group = lwp_pgrp_find_and_inc_ref(lwp_pgid_get_byprocess(process)); + + PGRP_LOCK(old_group); + LWP_LOCK(process); + + if (process->pgrp == old_group) + { + lwp_pgrp_remove(old_group, process); + lwp_pgrp_insert(group, process); + } + else + { + retry = 1; + } + PGRP_UNLOCK(old_group); + LWP_UNLOCK(process); + + lwp_pgrp_dec_ref(old_group); + } + PGRP_UNLOCK(group); + + return 0; +} + +int lwp_pgrp_update_children_info(rt_processgroup_t group, pid_t sid, pid_t pgid) +{ + rt_list_t *node = RT_NULL; + rt_lwp_t process = RT_NULL; + + if (group == RT_NULL) + { + return -EINVAL; + } + + PGRP_LOCK_NESTED(group); + + /* try to find process group */ + rt_list_for_each(node, &(group->process)) + { + process = (rt_lwp_t)rt_list_entry(node, struct rt_lwp, pgrp_node); + LWP_LOCK(process); + if (sid != -1) + { + process->sid = sid; + } + if (pgid != -1) + { + process->pgid = pgid; + process->pgrp = group; + } + LWP_UNLOCK(process); + } + + PGRP_UNLOCK(group); + return 0; +} + +/** + * setpgid() sets the PGID of the process specified by pid to pgid. + * If pid is zero, then the process ID of the calling process is used. + * If pgid is zero, then the PGID of the process specified by pid is made the same as its process ID. + * If setpgid() is used to move a process from one process group to another (as is done by some shells when + * creating pipelines), both process groups must be part of the same session (see setsid(2) and credentials(7)). + * In this case, the pgid specifies an existing process group to be joined and the session ID of that group must + * match the session ID of the joining process. + */ +sysret_t sys_setpgid(pid_t pid, pid_t pgid) +{ + rt_lwp_t process, self_process; + pid_t sid; + rt_processgroup_t group; + rt_session_t session; + sysret_t err = 0; + + if (pgid == 0) + { + pgid = pid; + } + if (pgid < 0) + { + return -EINVAL; + } + + self_process = lwp_self(); + + if (pid == 0) + { + pid = self_process->pid; + process = self_process; + } + else + { + lwp_pid_lock_take(); + process = lwp_from_pid_locked(pid); + lwp_pid_lock_release(); + + if (process == RT_NULL) + { + return -ESRCH; + } + } + + LWP_LOCK(process); + + if (process->parent == self_process) + { + /** + * change the process group ID of one of the children of the calling process and the child was in + * a different session + */ + if (lwp_sid_get_byprocess(process) != lwp_sid_get_byprocess(self_process)) + { + err = -EPERM; + LWP_UNLOCK(process); + goto exit; + } + /** + * An attempt was made to change the process group ID of one of the children of the calling process + * and the child had already performed an execve(2) + */ + if (process->did_exec) + { + err = -EACCES; + LWP_UNLOCK(process); + goto exit; + } + } + else + { + /** + * pid is not the calling process and not a child of the calling process. + */ + if (process != self_process) + { + err = -ESRCH; + LWP_UNLOCK(process); + goto exit; + } + } + + LWP_UNLOCK(process); + + sid = lwp_sid_get_byprocess(self_process); + if (pgid != pid) + { + group = lwp_pgrp_find(pgid); + if (group == RT_NULL) + { + group = lwp_pgrp_create(process); + } + else + { + /** + * An attempt was made to move a process into a process group in a different session + */ + if (sid != lwp_sid_get_bypgrp(group)) + { + err = -EPERM; + goto exit; + } + /** + * or to change the process group ID of a session leader + */ + if (sid == lwp_to_pid(process)) + { + err = -EPERM; + goto exit; + } + lwp_pgrp_move(group, process); + } + } + else + { + group = lwp_pgrp_find(pgid); + if (group == RT_NULL) + { + group = lwp_pgrp_create(process); + lwp_pgrp_move(group, process); + session = lwp_session_find(sid); + if (session == RT_NULL) + { + LOG_E("the session of sid: %d cannot be found", sid); + err = -EPERM; + goto exit; + } + else + { + lwp_session_insert(session, group); + } + } + else // this represents repeated calls + { + /** + * or to change the process group ID of a session leader + */ + if (lwp_sid_get_bypgrp(group) == lwp_pgid_get_bypgrp(group)) + { + err = -EPERM; + goto exit; + } + else + { + err = 0; + } + } + } + +exit: + return err; +} + +/** + * getpgid() returns the PGID of the process specified by pid. + * If pid is zero, the process ID of the calling process is used. (Retrieving the PGID of a process other + * than the caller is rarely necessary, and the POSIX.1 getpgrp() is preferred for that task.) + */ +sysret_t sys_getpgid(pid_t pid) +{ + rt_lwp_t process; + + lwp_pid_lock_take(); + process = lwp_from_pid_locked(pid); + lwp_pid_lock_release(); + + if (process == RT_NULL) + { + return -ESRCH; + } + + return lwp_pgid_get_byprocess(process); +} + +#ifdef RT_USING_FINSH + +#include "finsh.h" + +long list_processgroup(void) +{ + int count = 0, index; + rt_processgroup_t *groups; + rt_processgroup_t group; + rt_thread_t thread; + char name[RT_NAME_MAX]; + + rt_kprintf("PGID SID leader process\n"); + rt_kprintf("---- ---- ----------------\n"); + + count = rt_object_get_length(RT_Object_Class_ProcessGroup); + if (count > 0) + { + /* get pointers */ + groups = (rt_processgroup_t *)rt_calloc(count, sizeof(rt_processgroup_t)); + if (groups) + { + index = rt_object_get_pointers(RT_Object_Class_ProcessGroup, (rt_object_t *)groups, count); + if (index > 0) + { + for (index = 0; index < count; index++) + { + struct rt_processgroup pgrp; + group = groups[index]; + PGRP_LOCK(group); + rt_memcpy(&pgrp, group, sizeof(struct rt_processgroup)); + PGRP_UNLOCK(group); + + if (pgrp.leader) + { + thread = rt_list_entry(pgrp.leader->t_grp.prev, struct rt_thread, sibling); + rt_strncpy(name, thread->parent.name, RT_NAME_MAX); + } + else + { + rt_strncpy(name, "nil", RT_NAME_MAX); + } + + rt_kprintf("%4d %4d %-*.*s\n", pgrp.pgid, pgrp.sid, RT_NAME_MAX, RT_NAME_MAX, name); + } + } + rt_free(groups); + } + } + + return 0; +} +MSH_CMD_EXPORT(list_processgroup, list process group); +#endif diff --git a/components/lwp/lwp_pid.c b/components/lwp/lwp_pid.c index 7479df0357..5b9f1f4098 100644 --- a/components/lwp/lwp_pid.c +++ b/components/lwp/lwp_pid.c @@ -14,32 +14,47 @@ * error * 2023-10-27 shell Format codes of sys_exit(). Fix the data racing where lock is missed * Add reference on pid/tid, so the resource is not freed while using. + * Add support for waitpid(options=WNOHANG) + * 2023-11-16 xqyjlj Fix the case where pid is 0 + * 2023-11-17 xqyjlj add process group and session support + * 2023-11-24 shell Support of waitpid(options=WNOTRACED|WCONTINUED); + * Reimplement the waitpid with a wait queue method, and fixup problem + * with waitpid(pid=-1)/waitpid(pid=-pgid)/waitpid(pid=0) that only one + * process can be traced while waiter suspend * 2024-01-25 shell porting to new sched API */ /* includes scheduler related API */ #define __RT_IPC_SOURCE__ -#include -#include +/* for waitpid, we are compatible to GNU extension */ +#define _GNU_SOURCE #define DBG_TAG "lwp.pid" #define DBG_LVL DBG_INFO #include +#include "lwp_internal.h" + +#include +#include #include #include #include /* rename() */ +#include #include #include /* statfs() */ - -#include "lwp_internal.h" -#include "tty.h" +#include #ifdef ARCH_MM_MMU #include "lwp_user_mm.h" #endif +#ifdef RT_USING_DFS_PROCFS +#include "proc.h" +#include "procfs.h" +#endif + #define PID_MAX 10000 #define PID_CT_ASSERT(name, x) \ @@ -147,8 +162,23 @@ static void lwp_pid_put_locked(pid_t pid) } } +#ifdef RT_USING_DFS_PROCFS + rt_inline void _free_proc_dentry(rt_lwp_t lwp) + { + char pid_str[64] = {0}; + + rt_snprintf(pid_str, 64, "%d", lwp->pid); + pid_str[63] = 0; + proc_remove_dentry(pid_str, 0); + } +#else + #define _free_proc_dentry(lwp) +#endif + void lwp_pid_put(struct rt_lwp *lwp) { + _free_proc_dentry(lwp); + lwp_pid_lock_take(); lwp_pid_put_locked(lwp->pid); lwp_pid_lock_release(); @@ -168,6 +198,13 @@ static void lwp_pid_set_lwp_locked(pid_t pid, struct rt_lwp *lwp) { p->data = lwp; lwp_ref_inc(lwp); + +#ifdef RT_USING_DFS_PROCFS + if (pid) + { + proc_pid(pid); + } +#endif } } @@ -352,19 +389,23 @@ rt_lwp_t lwp_create(rt_base_t flags) if (new_lwp) { /* minimal setup of lwp object */ - new_lwp->session = -1; new_lwp->ref = 1; #ifdef RT_USING_SMP new_lwp->bind_cpu = RT_CPUS_NR; #endif - rt_list_init(&new_lwp->wait_list); + new_lwp->exe_file = RT_NULL; rt_list_init(&new_lwp->t_grp); + rt_list_init(&new_lwp->pgrp_node); rt_list_init(&new_lwp->timer); lwp_user_object_lock_init(new_lwp); rt_wqueue_init(&new_lwp->wait_queue); + rt_wqueue_init(&new_lwp->waitpid_waiters); lwp_signal_init(&new_lwp->signal); rt_mutex_init(&new_lwp->lwp_lock, "lwp_lock", RT_IPC_FLAG_PRIO); + if (flags & LWP_CREATE_FLAG_NOTRACE_EXEC) + new_lwp->did_exec = RT_TRUE; + /* lwp with pid */ if (flags & LWP_CREATE_FLAG_ALLOC_PID) { @@ -375,7 +416,7 @@ rt_lwp_t lwp_create(rt_base_t flags) lwp_user_object_lock_destroy(new_lwp); rt_free(new_lwp); new_lwp = RT_NULL; - LOG_E("pid slot fulled!\n"); + LOG_E("%s: pid slot fulled", __func__); } else { @@ -384,6 +425,20 @@ rt_lwp_t lwp_create(rt_base_t flags) } lwp_pid_lock_release(); } + rt_memset(&new_lwp->rt_rusage,0, sizeof(new_lwp->rt_rusage)); + + if (flags & LWP_CREATE_FLAG_INIT_USPACE) + { + rt_err_t error = lwp_user_space_init(new_lwp, 0); + if (error) + { + lwp_pid_put(new_lwp); + lwp_user_object_lock_destroy(new_lwp); + rt_free(new_lwp); + new_lwp = RT_NULL; + LOG_E("%s: failed to initialize user space", __func__); + } + } } LOG_D("%s(pid=%d) => %p", __func__, new_lwp ? new_lwp->pid : -1, new_lwp); @@ -393,6 +448,8 @@ rt_lwp_t lwp_create(rt_base_t flags) /** when reference is 0, a lwp can be released */ void lwp_free(struct rt_lwp* lwp) { + rt_processgroup_t group = RT_NULL; + if (lwp == RT_NULL) { return; @@ -406,6 +463,10 @@ void lwp_free(struct rt_lwp* lwp) * all the reference is clear) */ LOG_D("lwp free: %p", lwp); + rt_free(lwp->exe_file); + group = lwp_pgrp_find(lwp_pgid_get_byprocess(lwp)); + if (group) + lwp_pgrp_remove(group, lwp); LWP_LOCK(lwp); @@ -472,6 +533,15 @@ void lwp_free(struct rt_lwp* lwp) rt_inline rt_noreturn void _thread_exit(rt_lwp_t lwp, rt_thread_t thread) { + LWP_LOCK(lwp); + lwp->rt_rusage.ru_stime.tv_sec += thread->system_time / RT_TICK_PER_SECOND; + lwp->rt_rusage.ru_stime.tv_usec += thread->system_time % RT_TICK_PER_SECOND * (1000000 / RT_TICK_PER_SECOND); + lwp->rt_rusage.ru_utime.tv_sec += thread->user_time / RT_TICK_PER_SECOND; + lwp->rt_rusage.ru_utime.tv_usec += thread->user_time % RT_TICK_PER_SECOND * (1000000 / RT_TICK_PER_SECOND); + rt_list_remove(&thread->sibling); + LWP_UNLOCK(lwp); + lwp_futex_exit_robust_list(thread); + /** * Note: the tid tree always hold a reference to thread, hence the tid must * be release before cleanup of thread @@ -479,10 +549,6 @@ void _thread_exit(rt_lwp_t lwp, rt_thread_t thread) lwp_tid_put(thread->tid); thread->tid = 0; - LWP_LOCK(lwp); - rt_list_remove(&thread->sibling); - LWP_UNLOCK(lwp); - rt_thread_delete(thread); rt_schedule(); while (1) ; @@ -497,11 +563,11 @@ rt_inline void _clear_child_tid(rt_thread_t thread) thread->clear_child_tid = RT_NULL; lwp_put_to_user(clear_child_tid, &t, sizeof t); - sys_futex(clear_child_tid, FUTEX_WAKE | FUTEX_PRIVATE, 1, RT_NULL, RT_NULL, 0); + sys_futex(clear_child_tid, FUTEX_WAKE, 1, RT_NULL, RT_NULL, 0); } } -void lwp_exit(rt_lwp_t lwp, rt_base_t status) +void lwp_exit(rt_lwp_t lwp, lwp_status_t status) { rt_thread_t thread; @@ -523,7 +589,7 @@ void lwp_exit(rt_lwp_t lwp, rt_base_t status) * Brief: only one thread should calls exit_group(), * but we can not ensured that during run-time */ - lwp->lwp_ret = LWP_CREATE_STAT(status); + lwp->lwp_status = status; LWP_UNLOCK(lwp); lwp_terminate(lwp); @@ -550,7 +616,7 @@ void lwp_exit(rt_lwp_t lwp, rt_base_t status) _thread_exit(lwp, thread); } -void lwp_thread_exit(rt_thread_t thread, rt_base_t status) +void lwp_thread_exit(rt_thread_t thread, int status) { rt_thread_t header_thr; struct rt_lwp *lwp; @@ -568,7 +634,11 @@ void lwp_thread_exit(rt_thread_t thread, rt_base_t status) header_thr = rt_list_entry(lwp->t_grp.prev, struct rt_thread, sibling); if (header_thr == thread && thread->sibling.prev == &lwp->t_grp) { - lwp->lwp_ret = LWP_CREATE_STAT(status); + /** + * if thread exit, treated as process exit normally. + * This is reasonable since trap event is exited through lwp_exit() + */ + lwp->lwp_status = LWP_CREATE_STAT_EXIT(status); LWP_UNLOCK(lwp); lwp_terminate(lwp); @@ -582,38 +652,6 @@ void lwp_thread_exit(rt_thread_t thread, rt_base_t status) _thread_exit(lwp, thread); } -static void _pop_tty(rt_lwp_t lwp) -{ - if (!lwp->background) - { - struct termios *old_stdin_termios = get_old_termios(); - struct rt_lwp *old_lwp = NULL; - - if (lwp->session == -1) - { - tcsetattr(1, 0, old_stdin_termios); - } - if (lwp->tty != RT_NULL) - { - rt_mutex_take(&lwp->tty->lock, RT_WAITING_FOREVER); - if (lwp->tty->foreground == lwp) - { - old_lwp = tty_pop(&lwp->tty->head, RT_NULL); - lwp->tty->foreground = old_lwp; - } - else - { - tty_pop(&lwp->tty->head, lwp); - } - rt_mutex_release(&lwp->tty->lock); - - LWP_LOCK(lwp); - lwp->tty = RT_NULL; - LWP_UNLOCK(lwp); - } - } -} - /** @note the reference is not for synchronization, but for the release of resource. the synchronization is done through lwp & pid lock */ int lwp_ref_inc(struct rt_lwp *lwp) { @@ -657,7 +695,7 @@ int lwp_ref_dec(struct rt_lwp *lwp) return ref; } -struct rt_lwp* lwp_from_pid_locked(pid_t pid) +struct rt_lwp* lwp_from_pid_raw_locked(pid_t pid) { struct lwp_avl_struct *p; struct rt_lwp *lwp = RT_NULL; @@ -671,6 +709,13 @@ struct rt_lwp* lwp_from_pid_locked(pid_t pid) return lwp; } +struct rt_lwp* lwp_from_pid_locked(pid_t pid) +{ + struct rt_lwp* lwp; + lwp = pid ? lwp_from_pid_raw_locked(pid) : lwp_self(); + return lwp; +} + pid_t lwp_to_pid(struct rt_lwp* lwp) { if (!lwp) @@ -733,162 +778,377 @@ pid_t lwp_name2pid(const char *name) int lwp_getpid(void) { - return ((struct rt_lwp *)rt_thread_self()->lwp)->pid; + rt_lwp_t lwp = lwp_self(); + return lwp ? lwp->pid : 1; + // return ((struct rt_lwp *)rt_thread_self()->lwp)->pid; } -/** - * @brief Wait for a child lwp to terminate. Do the essential recycling. Setup - * status code for user - */ -static sysret_t _lwp_wait_and_recycle(struct rt_lwp *child, rt_thread_t cur_thr, - struct rt_lwp *self_lwp, int *status, - int options) +rt_inline void _update_ru(struct rt_lwp *child, struct rt_lwp *self_lwp, struct rusage *uru) +{ + struct rusage rt_rusage; + if (uru != RT_NULL) + { + rt_rusage.ru_stime.tv_sec = child->rt_rusage.ru_stime.tv_sec; + rt_rusage.ru_stime.tv_usec = child->rt_rusage.ru_stime.tv_usec; + rt_rusage.ru_utime.tv_sec = child->rt_rusage.ru_utime.tv_sec; + rt_rusage.ru_utime.tv_usec = child->rt_rusage.ru_utime.tv_usec; + lwp_data_put(self_lwp, uru, &rt_rusage, sizeof(*uru)); + } +} + +/* do statistical summary and reap the child if neccessary */ +static rt_err_t _stats_and_reap_child(rt_lwp_t child, rt_thread_t cur_thr, + struct rt_lwp *self_lwp, int *ustatus, + int options, struct rusage *uru) { - sysret_t error; - int lwp_stat; - int terminated; + int lwp_stat = child->lwp_status; - if (!child) + /* report statistical data to process */ + _update_ru(child, self_lwp, uru); + + if (child->terminated && !(options & WNOWAIT)) + { + /** Reap the child process if it's exited */ + LOG_D("func %s: child detached", __func__); + lwp_pid_put(child); + lwp_children_unregister(self_lwp, child); + } + + if (ustatus) + lwp_data_put(self_lwp, ustatus, &lwp_stat, sizeof(*ustatus)); + + return RT_EOK; +} + +#define HAS_CHILD_BUT_NO_EVT (-1024) + +/* check if the process is already terminate */ +static sysret_t _query_event_from_lwp(rt_lwp_t child, rt_thread_t cur_thr, rt_lwp_t self_lwp, + int options, int *status) +{ + sysret_t rc; + + LWP_LOCK(child); + if (child->terminated) + { + rc = child->pid; + } + else if ((options & WSTOPPED) && child->jobctl_stopped && !child->wait_reap_stp) { - error = -RT_ERROR; + child->wait_reap_stp = 1; + rc = child->pid; } else + { + rc = HAS_CHILD_BUT_NO_EVT; + } + LWP_UNLOCK(child); + + LOG_D("%s(child_pid=%d ('%s'), stopped=%d) => %d", __func__, child->pid, child->cmd, child->jobctl_stopped, rc); + return rc; +} + +/* verify if the process is child, and reap it */ +static pid_t _verify_child_and_reap(rt_thread_t cur_thr, rt_lwp_t self_lwp, + pid_t wait_pid, int options, int *ustatus, + struct rusage *uru) +{ + sysret_t rc; + struct rt_lwp *child; + + /* check if pid is reference to a valid child */ + lwp_pid_lock_take(); + child = lwp_from_pid_locked(wait_pid); + if (!child) + rc = -EINVAL; + else if (child->parent != self_lwp) + rc = -ESRCH; + else + rc = wait_pid; + + lwp_pid_lock_release(); + + if (rc > 0) + { + rc = _query_event_from_lwp(child, cur_thr, self_lwp, options, ustatus); + if (rc > 0) + { + _stats_and_reap_child(child, cur_thr, self_lwp, ustatus, options, uru); + } + } + return rc; +} + +/* try to reap any child */ +static pid_t _reap_any_child_pid(rt_thread_t cur_thr, rt_lwp_t self_lwp, pid_t pair_pgid, + int options, int *ustatus, struct rusage *uru) +{ + sysret_t rc = -ECHILD; + struct rt_lwp *child; + + LWP_LOCK(self_lwp); + child = self_lwp->first_child; + + /* find a exited child if any */ + while (child) + { + if (pair_pgid && child->pgid != pair_pgid) + continue; + + rc = _query_event_from_lwp(child, cur_thr, self_lwp, options, ustatus); + if (rc > 0) + break; + + child = child->sibling; + } + LWP_UNLOCK(self_lwp); + + if (rc > 0) + { + _stats_and_reap_child(child, cur_thr, self_lwp, ustatus, options, uru); + } + return rc; +} + +rt_err_t lwp_waitpid_kick(rt_lwp_t parent, rt_lwp_t self_lwp) +{ + /* waker provide the message mainly through its lwp_status */ + rt_wqueue_wakeup(&parent->waitpid_waiters, self_lwp); + return RT_EOK; +} + +struct waitpid_handle { + struct rt_wqueue_node wq_node; + int options; + rt_lwp_t waker_lwp; +}; + +/* the IPC message is setup and notify the parent */ +static int _waitq_filter(struct rt_wqueue_node *wait_node, void *key) +{ + int can_accept_evt = 0; + rt_thread_t waiter = wait_node->polling_thread; + pid_t destiny = (pid_t)wait_node->key; + rt_lwp_t waker_lwp = key; + struct waitpid_handle *handle; + rt_ubase_t options; + + handle = rt_container_of(wait_node, struct waitpid_handle, wq_node); + + RT_ASSERT(waiter != RT_NULL); + options = handle->options; + + /* filter out if waker is not the one */ + if (destiny > 0) { /** - * Note: Critical Section - * - child lwp (RW. This will modify its parent if valid) + * in waitpid immediately return routine, we already do the check + * that pid is one of the child process of waiting thread */ - LWP_LOCK(child); - if (child->terminated) + can_accept_evt = waker_lwp->pid == destiny; + } + else if (destiny == -1) + { + can_accept_evt = waker_lwp->parent == waiter->lwp; + } + else + { + /* destiny == 0 || destiny == -pgid */ + pid_t waiter_pgid; + if (destiny == 0) { - error = child->pid; + waiter_pgid = lwp_pgid_get_byprocess(waiter->lwp); } - else if (rt_list_isempty(&child->wait_list)) + else { - /** - * Note: only one thread can wait on wait_list. - * dont reschedule before mutex unlock - */ - rt_enter_critical(); + waiter_pgid = -destiny; + } + can_accept_evt = waiter_pgid == lwp_pgid_get_byprocess(waker_lwp); + } - error = rt_thread_suspend_with_flag(cur_thr, RT_INTERRUPTIBLE); - if (error == 0) - { - rt_list_insert_before(&child->wait_list, &RT_THREAD_LIST_NODE(cur_thr)); - LWP_UNLOCK(child); + /* filter out if event is not desired */ + if (can_accept_evt) + { + if ((options & WEXITED) && waker_lwp->terminated) + can_accept_evt = 1; + else if ((options & WSTOPPED) && WIFSTOPPED(waker_lwp->lwp_status)) + can_accept_evt = 1; + else if ((options & WCONTINUED) && WIFCONTINUED(waker_lwp->lwp_status)) + can_accept_evt = 1; + else + can_accept_evt = 0; + } - rt_set_errno(RT_EINTR); - rt_exit_critical(); - rt_schedule(); + /* setup message for waiter if accepted */ + if (can_accept_evt) + handle->waker_lwp = waker_lwp; - /** - * Since parent is holding a reference to children this lock will - * not be freed before parent dereference to it. - */ - LWP_LOCK(child); + /* 0 if event is accepted, otherwise discard */ + return !can_accept_evt; +} - error = rt_get_errno(); - if (error == RT_EINTR) - { - error = -EINTR; - } - else if (error != RT_EOK) - { - LOG_W("%s: unexpected error code %ld", __func__, error); - } - else - { - error = child->pid; - } - } - else - rt_exit_critical(); - } - else - error = -RT_EINTR; +/* the waiter cleanup IPC message and wait for desired event here */ +static rt_err_t _wait_for_event(rt_thread_t cur_thr, rt_lwp_t self_lwp, + struct waitpid_handle *handle, pid_t destiny) +{ + rt_err_t ret; - lwp_stat = child->lwp_ret; - terminated = child->terminated; - LWP_UNLOCK(child); + /* current context checking */ + RT_DEBUG_SCHEDULER_AVAILABLE(RT_TRUE); - if (error > 0) - { - if (terminated) - { - LOG_D("func %s: child detached", __func__); - /** Reap the child process if it's exited */ - lwp_pid_put(child); - lwp_children_unregister(self_lwp, child); - } + handle->wq_node.polling_thread = cur_thr; + handle->wq_node.key = destiny; + handle->wq_node.wakeup = _waitq_filter; + handle->wq_node.wqueue = &self_lwp->waitpid_waiters; + rt_list_init(&handle->wq_node.list); - if (status) - lwp_data_put(self_lwp, status, &lwp_stat, sizeof(*status)); - } + cur_thr->error = RT_EOK; + + LOG_D("%s(self_lwp=%d) wait for event", __func__, self_lwp->pid); + + rt_enter_critical(); + ret = rt_thread_suspend_with_flag(cur_thr, RT_INTERRUPTIBLE); + if (ret == RT_EOK) + { + rt_wqueue_add(handle->wq_node.wqueue, &handle->wq_node); + rt_exit_critical(); + + rt_schedule(); + + ret = cur_thr->error; + + /** + * cur_thr error is a positive value, but some legacy implementation + * use a negative one. So we check to avoid errors + */ + ret = ret > 0 ? -ret : ret; + + /** + * we dont rely on this actually, but we cleanup it since wakeup API + * set this up durint operation, and this will cause some messy condition + */ + handle->wq_node.wqueue->flag = RT_WQ_FLAG_CLEAN; + rt_wqueue_remove(&handle->wq_node); + } + else + { + /* failed to suspend, return immediately with failure */ + rt_exit_critical(); } - return error; + return ret; } -pid_t waitpid(pid_t pid, int *status, int options) __attribute__((alias("lwp_waitpid"))); +/* wait for IPC event and do the cleanup if neccessary */ +static sysret_t _wait_and_reap(rt_thread_t cur_thr, rt_lwp_t self_lwp, const pid_t pid, + int options, int *ustatus, struct rusage *uru) +{ + sysret_t rc; + struct waitpid_handle handle; + rt_lwp_t waker; + + /* wait for SIGCHLD or other async events */ + handle.options = options; + handle.waker_lwp = 0; + rc = _wait_for_event(cur_thr, self_lwp, &handle, pid); + + waker = handle.waker_lwp; + if (waker != RT_NULL) + { + rc = waker->pid; + + /* check out if any process exited */ + LOG_D("%s: woken up by lwp=%d", __func__, waker->pid); + _stats_and_reap_child(waker, cur_thr, self_lwp, ustatus, options, uru); + } + /** + * else if (rc != RT_EOK) + * unable to do a suspend, or wakeup unexpectedly + * -> then returned a failure + */ + + return rc; +} -pid_t lwp_waitpid(const pid_t pid, int *status, int options) +pid_t lwp_waitpid(const pid_t pid, int *status, int options, struct rusage *ru) { pid_t rc = -1; - struct rt_thread *thread; - struct rt_lwp *child; + struct rt_thread *cur_thr; struct rt_lwp *self_lwp; - thread = rt_thread_self(); + cur_thr = rt_thread_self(); self_lwp = lwp_self(); - if (!self_lwp) + if (!cur_thr || !self_lwp) { - rc = -RT_EINVAL; + rc = -EINVAL; } else { + /* check if able to reap desired child immediately */ if (pid > 0) { - lwp_pid_lock_take(); - child = lwp_from_pid_locked(pid); - if (child->parent != self_lwp) - rc = -RT_ERROR; - else - rc = RT_EOK; - lwp_pid_lock_release(); - - if (rc == RT_EOK) - rc = _lwp_wait_and_recycle(child, thread, self_lwp, status, options); + /* if pid is child then try to reap it */ + rc = _verify_child_and_reap(cur_thr, self_lwp, pid, options, status, ru); } else if (pid == -1) { - LWP_LOCK(self_lwp); - child = self_lwp->first_child; - LWP_UNLOCK(self_lwp); - RT_ASSERT(!child || child->parent == self_lwp); - - rc = _lwp_wait_and_recycle(child, thread, self_lwp, status, options); + /* any terminated child */ + rc = _reap_any_child_pid(cur_thr, self_lwp, 0, options, status, ru); } else { - /* not supported yet */ - rc = -RT_EINVAL; + /** + * (pid < -1 || pid == 0) + * any terminated child with matched pgid + */ + + pid_t pair_pgid; + if (pid == 0) + { + pair_pgid = lwp_pgid_get_byprocess(self_lwp); + } + else + { + pair_pgid = -pid; + } + rc = _reap_any_child_pid(cur_thr, self_lwp, pair_pgid, options, status, ru); } - } - if (rc > 0) - { - LOG_D("%s: recycle child id %ld (status=0x%x)", __func__, (long)rc, status ? *status : 0); - } - else - { - RT_ASSERT(rc != 0); - LOG_D("%s: wait failed with code %ld", __func__, (long)rc); + if (rc == HAS_CHILD_BUT_NO_EVT) + { + if (!(options & WNOHANG)) + { + /* otherwise, arrange a suspend and wait for async event */ + options |= WEXITED; + rc = _wait_and_reap(cur_thr, self_lwp, pid, options, status, ru); + } + else + { + /** + * POSIX.1: If waitpid() was invoked with WNOHANG set in options, + * it has at least one child process specified by pid for which + * status is not available, and status is not available for any + * process specified by pid, 0 is returned + */ + rc = 0; + } + } + else + { + RT_ASSERT(rc != 0); + } } + LOG_D("waitpid() => %d, *status=0x%x", rc, status ? *status:0); return rc; } +pid_t waitpid(pid_t pid, int *status, int options) +{ + return lwp_waitpid(pid, status, options, RT_NULL); +} + #ifdef RT_USING_FINSH /* copy from components/finsh/cmd.c */ static void object_split(int len) @@ -910,7 +1170,7 @@ static void print_thread_info(struct rt_thread* thread, int maxlen) else rt_kprintf("%-*.*s N/A %3d ", maxlen, RT_NAME_MAX, thread->parent.name, RT_SCHED_PRIV(thread).current_priority); #else - rt_kprintf("%-*.*s %3d ", maxlen, RT_NAME_MAX, thread->parent.name, thread->current_priority); + rt_kprintf("%-*.*s %3d ", maxlen, RT_NAME_MAX, thread->parent.name, RT_SCHED_PRIV(thread).current_priority); #endif /*RT_USING_SMP*/ stat = (RT_SCHED_CTX(thread).stat & RT_THREAD_STAT_MASK); @@ -1044,7 +1304,7 @@ static void cmd_kill(int argc, char** argv) } } lwp_pid_lock_take(); - lwp_signal_kill(lwp_from_pid_locked(pid), sig, SI_USER, 0); + lwp_signal_kill(lwp_from_pid_raw_locked(pid), sig, SI_USER, 0); lwp_pid_lock_release(); } MSH_CMD_EXPORT_ALIAS(cmd_kill, kill, send a signal to a process); @@ -1061,7 +1321,7 @@ static void cmd_killall(int argc, char** argv) while((pid = lwp_name2pid(argv[1])) > 0) { lwp_pid_lock_take(); - lwp_signal_kill(lwp_from_pid_locked(pid), SIGKILL, SI_USER, 0); + lwp_signal_kill(lwp_from_pid_raw_locked(pid), SIGKILL, SI_USER, 0); lwp_pid_lock_release(); rt_thread_mdelay(100); } @@ -1073,15 +1333,15 @@ MSH_CMD_EXPORT_ALIAS(cmd_killall, killall, kill processes by name); int lwp_check_exit_request(void) { rt_thread_t thread = rt_thread_self(); - rt_base_t expected = LWP_EXIT_REQUEST_TRIGGERED; + rt_size_t expected = LWP_EXIT_REQUEST_TRIGGERED; if (!thread->lwp) { return 0; } - return rt_atomic_compare_exchange_strong(&thread->exit_request, &expected, - LWP_EXIT_REQUEST_IN_PROCESS); + return atomic_compare_exchange_strong(&thread->exit_request, &expected, + LWP_EXIT_REQUEST_IN_PROCESS); } static void _wait_sibling_exit(rt_lwp_t lwp, rt_thread_t curr_thread); @@ -1119,7 +1379,7 @@ static void _wait_sibling_exit(rt_lwp_t lwp, rt_thread_t curr_thread) rt_sched_lock_level_t slvl; rt_list_t *list; rt_thread_t thread; - rt_base_t expected = LWP_EXIT_REQUEST_NONE; + rt_size_t expected = LWP_EXIT_REQUEST_NONE; /* broadcast exit request for sibling threads */ LWP_LOCK(lwp); @@ -1127,8 +1387,8 @@ static void _wait_sibling_exit(rt_lwp_t lwp, rt_thread_t curr_thread) { thread = rt_list_entry(list, struct rt_thread, sibling); - rt_atomic_compare_exchange_strong(&thread->exit_request, &expected, - LWP_EXIT_REQUEST_TRIGGERED); + atomic_compare_exchange_strong(&thread->exit_request, &expected, + LWP_EXIT_REQUEST_TRIGGERED); rt_sched_lock(&slvl); /* dont release, otherwise thread may have been freed */ @@ -1213,8 +1473,43 @@ static void _wait_sibling_exit(rt_lwp_t lwp, rt_thread_t curr_thread) } } +static void _notify_parent(rt_lwp_t lwp) +{ + int si_code; + int signo_or_exitcode; + lwp_siginfo_ext_t ext; + lwp_status_t lwp_status = lwp->lwp_status; + rt_lwp_t parent = lwp->parent; + + if (WIFSIGNALED(lwp_status)) + { + si_code = (lwp_status & LWP_COREDUMP_FLAG) ? CLD_DUMPED : CLD_KILLED; + signo_or_exitcode = WTERMSIG(lwp_status); + } + else + { + si_code = CLD_EXITED; + signo_or_exitcode = WEXITSTATUS(lwp->lwp_status); + } + + lwp_waitpid_kick(parent, lwp); + + ext = rt_malloc(sizeof(struct lwp_siginfo)); + + if (ext) + { + rt_thread_t cur_thr = rt_thread_self(); + ext->sigchld.status = signo_or_exitcode; + ext->sigchld.stime = cur_thr->system_time; + ext->sigchld.utime = cur_thr->user_time; + } + lwp_signal_kill(parent, SIGCHLD, si_code, ext); +} + static void _resr_cleanup(struct rt_lwp *lwp) { + lwp_jobctrl_on_exit(lwp); + LWP_LOCK(lwp); lwp_signal_detach(&lwp->signal); @@ -1254,8 +1549,6 @@ static void _resr_cleanup(struct rt_lwp *lwp) } LWP_UNLOCK(lwp); - _pop_tty(lwp); - /** * @brief Wakeup parent if it's waiting for this lwp, otherwise a signal * will be sent to parent @@ -1264,29 +1557,26 @@ static void _resr_cleanup(struct rt_lwp *lwp) * - the parent lwp (RW.) */ LWP_LOCK(lwp); - if (lwp->parent) + if (lwp->parent && + !lwp_sigismember(&lwp->parent->signal.sig_action_nocldwait, SIGCHLD)) { - struct rt_thread *thread; - + /* if successfully race to setup lwp->terminated before parent detach */ LWP_UNLOCK(lwp); - if (!rt_list_isempty(&lwp->wait_list)) - { - thread = RT_THREAD_LIST_NODE_ENTRY(lwp->wait_list.next); - thread->error = RT_EOK; - thread->msg_ret = (void*)(rt_size_t)lwp->lwp_ret; - rt_thread_resume(thread); - } - else - { - /* children cannot detach itself and must wait for parent to take care of it */ - lwp_signal_kill(lwp->parent, SIGCHLD, CLD_EXITED, 0); - } + + /** + * Note: children cannot detach itself and must wait for parent to take + * care of it + */ + _notify_parent(lwp); } else { LWP_UNLOCK(lwp); - /* INFO: orphan hasn't parents to do the reap of pid */ + /** + * if process is orphan, it doesn't have parent to do the recycling. + * Otherwise, its parent had setup a flag to mask out recycling event + */ lwp_pid_put(lwp); } @@ -1315,10 +1605,8 @@ static int _lwp_setaffinity(pid_t pid, int cpu) int ret = -1; lwp_pid_lock_take(); - if(pid == 0) - lwp = lwp_self(); - else - lwp = lwp_from_pid_locked(pid); + lwp = lwp_from_pid_locked(pid); + if (lwp) { #ifdef RT_USING_SMP diff --git a/components/lwp/lwp_pid.h b/components/lwp/lwp_pid.h index c9e8b945b7..5e170f652d 100644 --- a/components/lwp/lwp_pid.h +++ b/components/lwp/lwp_pid.h @@ -11,14 +11,14 @@ #ifndef LWP_PID_H__ #define LWP_PID_H__ -#include "lwp.h" - #ifdef __cplusplus extern "C" { #endif -#define LWP_CREATE_FLAG_NONE 0x0000 -#define LWP_CREATE_FLAG_ALLOC_PID 0x0001 /* allocate pid on lwp object create */ +#define LWP_CREATE_FLAG_NONE 0x0000 +#define LWP_CREATE_FLAG_ALLOC_PID 0x0001 /* allocate pid on lwp object create */ +#define LWP_CREATE_FLAG_INIT_USPACE 0x0002 /* do user space initialization */ +#define LWP_CREATE_FLAG_NOTRACE_EXEC 0x0004 /* not trace if execve() after fork() */ struct rt_lwp; @@ -46,6 +46,7 @@ void lwp_free(struct rt_lwp* lwp); int lwp_ref_inc(struct rt_lwp *lwp); int lwp_ref_dec(struct rt_lwp *lwp); +struct rt_lwp* lwp_from_pid_raw_locked(pid_t pid); struct rt_lwp* lwp_from_pid_locked(pid_t pid); pid_t lwp_to_pid(struct rt_lwp* lwp); @@ -54,7 +55,29 @@ char* lwp_pid2name(int32_t pid); int lwp_getpid(void); -pid_t lwp_waitpid(const pid_t pid, int *status, int options); +struct rusage +{ + struct timeval ru_utime; + struct timeval ru_stime; + + long ru_maxrss; + long ru_ixrss; + long ru_idrss; + long ru_isrss; + long ru_minflt; + long ru_majflt; + long ru_nswap; + long ru_inblock; + long ru_oublock; + long ru_msgsnd; + long ru_msgrcv; + long ru_nsignals; + long ru_nvcsw; + long ru_nivcsw; + long reserved[16]; +}; +pid_t lwp_waitpid(const pid_t pid, int *status, int options, struct rusage *ru); +rt_err_t lwp_waitpid_kick(struct rt_lwp *parent, struct rt_lwp *self_lwp); pid_t waitpid(pid_t pid, int *status, int options); long list_process(void); @@ -85,8 +108,9 @@ rt_inline void lwp_from_pid_release_lock(struct rt_lwp *lwp) lwp_ref_dec(lwp); } -void lwp_thread_exit(rt_thread_t thread, rt_base_t status); -void lwp_exit(struct rt_lwp *lwp, rt_base_t status); +typedef rt_base_t lwp_status_t; +void lwp_thread_exit(rt_thread_t thread, int status); +void lwp_exit(struct rt_lwp *lwp, lwp_status_t status); #ifdef __cplusplus } diff --git a/components/lwp/lwp_pmutex.c b/components/lwp/lwp_pmutex.c deleted file mode 100644 index 08c8b3b082..0000000000 --- a/components/lwp/lwp_pmutex.c +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright (c) 2006-2021, RT-Thread Development Team - * - * SPDX-License-Identifier: Apache-2.0 - * - * Change Logs: - * Date Author Notes - * 2021/01/02 bernard the first version - * 2022/12/18 bernard fix the _m_lock to tid in user land. - */ - -#include "lwp_internal.h" -#include -#ifdef ARCH_MM_MMU -#include -#endif -#include -#include - -#define PMUTEX_NORMAL 0 /* Unable to recursion */ -#define PMUTEX_RECURSIVE 1 /* Can be recursion */ -#define PMUTEX_ERRORCHECK 2 /* This type of mutex provides error checking */ - -struct rt_pmutex -{ - union - { - rt_mutex_t kmutex; - rt_sem_t ksem; /* use sem to emulate the mutex without recursive */ - } lock; - - struct lwp_avl_struct node; - struct rt_object *custom_obj; - rt_uint8_t type; /* pmutex type */ -}; - -/* - * userspace mutex definitions in musl - */ -struct rt_umutex -{ - union - { - int __i[6]; - volatile int __vi[6]; - volatile void *volatile __p[6]; - } __u; -}; -#define _m_type __u.__i[0] -#define _m_lock __u.__vi[1] -#define _m_waiters __u.__vi[2] -#define _m_prev __u.__p[3] -#define _m_next __u.__p[4] -#define _m_count __u.__i[5] - -static struct rt_mutex _pmutex_lock; - -static int pmutex_system_init(void) -{ - rt_mutex_init(&_pmutex_lock, "pmtxLock", RT_IPC_FLAG_FIFO); - return 0; -} -INIT_PREV_EXPORT(pmutex_system_init); - -static rt_err_t pmutex_destory(void *data) -{ - rt_err_t ret = -1; - struct rt_pmutex *pmutex = (struct rt_pmutex *)data; - - if (pmutex) - { - lwp_mutex_take_safe(&_pmutex_lock, RT_WAITING_FOREVER, 0); - /* remove pmutex from pmutext avl */ - lwp_avl_remove(&pmutex->node, (struct lwp_avl_struct **)pmutex->node.data); - lwp_mutex_release_safe(&_pmutex_lock); - - if (pmutex->type == PMUTEX_NORMAL) - { - rt_sem_delete(pmutex->lock.ksem); - } - else - { - rt_mutex_delete(pmutex->lock.kmutex); - } - - /* release object */ - rt_free(pmutex); - ret = 0; - } - return ret; -} - -static struct rt_pmutex* pmutex_create(void *umutex, struct rt_lwp *lwp) -{ - struct rt_pmutex *pmutex = RT_NULL; - struct rt_object *obj = RT_NULL; - rt_ubase_t type; - - if (!lwp) - { - return RT_NULL; - } - - long *p = (long *)umutex; - /* umutex[0] bit[0-1] saved mutex type */ - type = *p & 3; - if (type != PMUTEX_NORMAL && type != PMUTEX_RECURSIVE && type != PMUTEX_ERRORCHECK) - { - return RT_NULL; - } - - pmutex = (struct rt_pmutex *)rt_malloc(sizeof(struct rt_pmutex)); - if (!pmutex) - { - return RT_NULL; - } - - if (type == PMUTEX_NORMAL) - { - pmutex->lock.ksem = rt_sem_create("pmutex", 1, RT_IPC_FLAG_PRIO); - if (!pmutex->lock.ksem) - { - rt_free(pmutex); - return RT_NULL; - } - } - else - { - pmutex->lock.kmutex = rt_mutex_create("pmutex", RT_IPC_FLAG_PRIO); - if (!pmutex->lock.kmutex) - { - rt_free(pmutex); - return RT_NULL; - } - } - - obj = rt_custom_object_create("pmutex", (void *)pmutex, pmutex_destory); - if (!obj) - { - if (pmutex->type == PMUTEX_NORMAL) - { - rt_sem_delete(pmutex->lock.ksem); - } - else - { - rt_mutex_delete(pmutex->lock.kmutex); - } - rt_free(pmutex); - return RT_NULL; - } - pmutex->node.avl_key = (avl_key_t)umutex; - pmutex->node.data = &lwp->address_search_head; - pmutex->custom_obj = obj; - pmutex->type = type; - - /* insert into pmutex head */ - lwp_avl_insert(&pmutex->node, &lwp->address_search_head); - return pmutex; -} - -static struct rt_pmutex* pmutex_get(void *umutex, struct rt_lwp *lwp) -{ - struct rt_pmutex *pmutex = RT_NULL; - struct lwp_avl_struct *node = RT_NULL; - - node = lwp_avl_find((avl_key_t)umutex, lwp->address_search_head); - if (!node) - { - return RT_NULL; - } - pmutex = rt_container_of(node, struct rt_pmutex, node); - return pmutex; -} - -static int _pthread_mutex_init(void *umutex) -{ - struct rt_lwp *lwp = RT_NULL; - struct rt_pmutex *pmutex = RT_NULL; - rt_err_t lock_ret = 0; - - /* umutex union is 6 x (void *) */ - if (!lwp_user_accessable(umutex, sizeof(void *) * 6)) - { - rt_set_errno(EINVAL); - return -EINVAL; - } - - lock_ret = rt_mutex_take_interruptible(&_pmutex_lock, RT_WAITING_FOREVER); - if (lock_ret != RT_EOK) - { - rt_set_errno(EAGAIN); - return -EAGAIN; - } - - lwp = lwp_self(); - pmutex = pmutex_get(umutex, lwp); - if (pmutex == RT_NULL) - { - /* create a pmutex according to this umutex */ - pmutex = pmutex_create(umutex, lwp); - if (pmutex == RT_NULL) - { - rt_mutex_release(&_pmutex_lock); - rt_set_errno(ENOMEM); - return -ENOMEM; - } - if (lwp_user_object_add(lwp, pmutex->custom_obj) != 0) - { - rt_custom_object_destroy(pmutex->custom_obj); - rt_set_errno(ENOMEM); - return -ENOMEM; - } - } - else - { - lwp_mutex_take_safe(&_pmutex_lock, RT_WAITING_FOREVER, 1); - if (pmutex->type == PMUTEX_NORMAL) - { - pmutex->lock.ksem->value = 1; - } - else - { - pmutex->lock.kmutex->owner = RT_NULL; - pmutex->lock.kmutex->priority = 0xFF; - pmutex->lock.kmutex->hold = 0; - pmutex->lock.kmutex->ceiling_priority = 0xFF; - } - lwp_mutex_release_safe(&_pmutex_lock); - } - - rt_mutex_release(&_pmutex_lock); - - return 0; -} - -static int _pthread_mutex_lock_timeout(void *umutex, struct timespec *timeout) -{ - struct rt_lwp *lwp = RT_NULL; - struct rt_pmutex *pmutex = RT_NULL; - struct rt_umutex *umutex_p = (struct rt_umutex*)umutex; - rt_err_t lock_ret = 0; - rt_int32_t time = RT_WAITING_FOREVER; - - if (!lwp_user_accessable((void *)umutex, sizeof(struct rt_umutex))) - { - rt_set_errno(EINVAL); - return -EINVAL; - } - - if (timeout) - { - if (!lwp_user_accessable((void *)timeout, sizeof(struct timespec))) - { - rt_set_errno(EINVAL); - return -EINVAL; - } - time = rt_timespec_to_tick(timeout); - } - - lock_ret = rt_mutex_take_interruptible(&_pmutex_lock, RT_WAITING_FOREVER); - if (lock_ret != RT_EOK) - { - rt_set_errno(EINTR); - return -EINTR; - } - - lwp = lwp_self(); - pmutex = pmutex_get(umutex, lwp); - if (pmutex == RT_NULL) - { - rt_mutex_release(&_pmutex_lock); - rt_set_errno(EINVAL); - return -ENOMEM; /* umutex not recored in kernel */ - } - - rt_mutex_release(&_pmutex_lock); - - switch (pmutex->type) - { - case PMUTEX_NORMAL: - lock_ret = rt_sem_take_interruptible(pmutex->lock.ksem, time); - break; - case PMUTEX_RECURSIVE: - lock_ret = rt_mutex_take_interruptible(pmutex->lock.kmutex, time); - if (lock_ret == RT_EOK) - { - umutex_p->_m_lock = rt_thread_self()->tid; - } - break; - case PMUTEX_ERRORCHECK: - lock_ret = lwp_mutex_take_safe(&_pmutex_lock, RT_WAITING_FOREVER, 1); - if (lock_ret != RT_EOK) - { - return -EINTR; - } - if (pmutex->lock.kmutex->owner == rt_thread_self()) - { - lwp_mutex_release_safe(&_pmutex_lock); - return -EDEADLK; - } - lwp_mutex_release_safe(&_pmutex_lock); - lock_ret = rt_mutex_take_interruptible(pmutex->lock.kmutex, time); - if (lock_ret == RT_EOK) - { - umutex_p->_m_lock = rt_thread_self()->tid; - } - break; - default: /* unknown type */ - return -EINVAL; - } - - if (lock_ret != RT_EOK) - { - if (lock_ret == -RT_ETIMEOUT) - { - if (time == 0) /* timeout is 0, means try lock failed */ - { - rt_set_errno(EBUSY); - return -EBUSY; - } - else - { - rt_set_errno(ETIMEDOUT); - return -ETIMEDOUT; - } - } - else if (lock_ret == -RT_EINTR) - { - rt_set_errno(EINTR); - return -EINTR; - } - else - { - rt_set_errno(EAGAIN); - return -EAGAIN; - } - } - return 0; -} - -static int _pthread_mutex_unlock(void *umutex) -{ - rt_err_t lock_ret = 0; - struct rt_lwp *lwp = RT_NULL; - struct rt_pmutex *pmutex = RT_NULL; - struct rt_umutex *umutex_p = (struct rt_umutex*)umutex; - - lock_ret = rt_mutex_take_interruptible(&_pmutex_lock, RT_WAITING_FOREVER); - if (lock_ret != RT_EOK) - { - rt_set_errno(EAGAIN); - return -EAGAIN; - } - - lwp = lwp_self(); - pmutex = pmutex_get(umutex, lwp); - if (pmutex == RT_NULL) - { - rt_mutex_release(&_pmutex_lock); - rt_set_errno(EPERM); - return -EPERM;//unlock static mutex of unlock state - } - - rt_mutex_release(&_pmutex_lock); - - switch (pmutex->type) - { - case PMUTEX_NORMAL: - if(pmutex->lock.ksem->value >=1) - { - rt_set_errno(EPERM); - return -EPERM;//unlock dynamic mutex of unlock state - } - else - { - lock_ret = rt_sem_release(pmutex->lock.ksem); - } - break; - case PMUTEX_RECURSIVE: - case PMUTEX_ERRORCHECK: - lock_ret = rt_mutex_release(pmutex->lock.kmutex); - if ((lock_ret == RT_EOK) && pmutex->lock.kmutex->owner == NULL) - { - umutex_p->_m_lock = 0; - } - break; - default: /* unknown type */ - return -EINVAL; - } - - if (lock_ret != RT_EOK) - { - rt_set_errno(EPERM); - return -EPERM; - } - return 0; -} - -static int _pthread_mutex_destroy(void *umutex) -{ - struct rt_lwp *lwp = RT_NULL; - struct rt_pmutex *pmutex = RT_NULL; - rt_err_t lock_ret = 0; - - lock_ret = rt_mutex_take_interruptible(&_pmutex_lock, RT_WAITING_FOREVER); - if (lock_ret != RT_EOK) - { - rt_set_errno(EAGAIN); - return -EAGAIN; - } - - lwp = lwp_self(); - pmutex = pmutex_get(umutex, lwp); - if (pmutex == RT_NULL) - { - rt_mutex_release(&_pmutex_lock); - rt_set_errno(EINVAL); - return -EINVAL; - } - - lwp_user_object_delete(lwp, pmutex->custom_obj); - rt_mutex_release(&_pmutex_lock); - - return 0; -} - -sysret_t sys_pmutex(void *umutex, int op, void *arg) -{ - int ret = -EINVAL; - - switch (op) - { - case PMUTEX_INIT: - ret = _pthread_mutex_init(umutex); - break; - case PMUTEX_LOCK: - ret = _pthread_mutex_lock_timeout(umutex, (struct timespec*)arg); - if (ret == -ENOMEM) - { - /* lock not init, try init it and lock again. */ - ret = _pthread_mutex_init(umutex); - if (ret == 0) - { - ret = _pthread_mutex_lock_timeout(umutex, (struct timespec*)arg); - } - } - break; - case PMUTEX_UNLOCK: - ret = _pthread_mutex_unlock(umutex); - break; - case PMUTEX_DESTROY: - ret = _pthread_mutex_destroy(umutex); - break; - default: - rt_set_errno(EINVAL); - break; - } - return ret; -} diff --git a/components/lwp/lwp_session.c b/components/lwp/lwp_session.c new file mode 100644 index 0000000000..6d51948267 --- /dev/null +++ b/components/lwp/lwp_session.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-17 xqyjlj the first version + * 2023-11-29 Shell Add direct reference of sess for group + */ + +#include "lwp.h" +#include "lwp_internal.h" +#include "lwp_syscall.h" +#include "terminal/terminal.h" + +#define DBG_TAG "lwp.session" +#define DBG_LVL DBG_WARNING +#include + +rt_session_t lwp_session_find(pid_t sid) +{ + rt_base_t level; + rt_session_t session = RT_NULL; + rt_list_t *node = RT_NULL; + struct rt_object_information *information = RT_NULL; + + information = rt_object_get_information(RT_Object_Class_Session); + + /* parameter check */ + if ((sid < 0) || (information == RT_NULL)) + { + return RT_NULL; + } + + if (sid == 0) + { + sid = lwp_getpid(); + } + + /* enter critical */ + level = rt_spin_lock_irqsave(&(information->spinlock)); + + /* try to find session */ + rt_list_for_each(node, &(information->object_list)) + { + session = (rt_session_t)rt_list_entry(node, struct rt_object, list); + if (session->sid == sid) + { + rt_spin_unlock_irqrestore(&(information->spinlock), level); + + return session; + } + } + + rt_spin_unlock_irqrestore(&(information->spinlock), level); + + return RT_NULL; +} + +rt_session_t lwp_session_create(rt_lwp_t leader) +{ + rt_session_t session = RT_NULL; + + /* parameter check */ + if (leader == RT_NULL) + { + return RT_NULL; + } + + session = rt_malloc(sizeof(struct rt_session)); + if (session != RT_NULL) + { + rt_object_init(&(session->object), RT_Object_Class_Session, "session"); + rt_list_init(&(session->processgroup)); + rt_mutex_init(&(session->mutex), "session", RT_IPC_FLAG_PRIO); + session->leader = leader; + session->sid = leader->pid; + lwp_pgrp_update_children_info(leader->pgrp, session->sid, leader->pgid); + session->foreground_pgid = session->sid; + session->ctty = RT_NULL; + } + return session; +} + +int lwp_session_delete(rt_session_t session) +{ + int retry = 1; + lwp_tty_t ctty; + + /* parameter check */ + if (session == RT_NULL) + { + return -EINVAL; + } + + /* clear children sid */ + lwp_session_update_children_info(session, 0); + + while (retry) + { + retry = 0; + ctty = session->ctty; + SESS_LOCK_NESTED(session); + + if (session->ctty == ctty) + { + if (ctty) + { + SESS_UNLOCK(session); + + /** + * Note: it's safe to release the session lock now. Even if someone + * race to acquire the tty, it's safe under protection of tty_lock() + * and the check inside + */ + tty_lock(ctty); + tty_rel_sess(ctty, session); + session->ctty = RT_NULL; + } + else + { + SESS_UNLOCK(session); + } + } + else + { + SESS_UNLOCK(session); + retry = 1; + } + } + + rt_object_detach(&(session->object)); + rt_mutex_detach(&(session->mutex)); + rt_free(session); + + return 0; +} + +int lwp_session_insert(rt_session_t session, rt_processgroup_t group) +{ + /* parameter check */ + if (session == RT_NULL || group == RT_NULL) + { + return -EINVAL; + } + + SESS_LOCK_NESTED(session); + PGRP_LOCK_NESTED(group); + + group->sid = session->sid; + group->session = session; + lwp_pgrp_update_children_info(group, session->sid, group->pgid); + rt_list_insert_after(&(session->processgroup), &(group->pgrp_list_node)); + + PGRP_UNLOCK(group); + SESS_UNLOCK(session); + + return 0; +} + +int lwp_session_remove(rt_session_t session, rt_processgroup_t group) +{ + rt_bool_t is_empty = RT_FALSE; + + /* parameter check */ + if (session == RT_NULL || group == RT_NULL) + { + return -EINVAL; + } + + SESS_LOCK_NESTED(session); + PGRP_LOCK_NESTED(group); + + rt_list_remove(&(group->pgrp_list_node)); + /* clear children sid */ + lwp_pgrp_update_children_info(group, 0, group->pgid); + group->sid = 0; + group->session = RT_NULL; + + PGRP_UNLOCK(group); + + is_empty = rt_list_isempty(&(session->processgroup)); + + SESS_UNLOCK(session); + + if (is_empty) + { + lwp_session_delete(session); + return 1; + } + + return 0; +} + +int lwp_session_move(rt_session_t session, rt_processgroup_t group) +{ + rt_session_t prev_session; + + /* parameter check */ + if (session == RT_NULL || group == RT_NULL) + { + return -EINVAL; + } + + if (lwp_sid_get_bysession(session) == lwp_sid_get_bypgrp(group)) + { + return 0; + } + + SESS_LOCK(session); + + prev_session = group->session; + if (prev_session) + { + SESS_LOCK(prev_session); + lwp_session_remove(prev_session, group); + SESS_UNLOCK(prev_session); + } + + lwp_session_insert(session, group); + + SESS_UNLOCK(session); + + return 0; +} + +int lwp_session_update_children_info(rt_session_t session, pid_t sid) +{ + rt_list_t *node = RT_NULL; + rt_processgroup_t group = RT_NULL; + + if (session == RT_NULL) + { + return -EINVAL; + } + + SESS_LOCK_NESTED(session); + + rt_list_for_each(node, &(session->processgroup)) + { + group = (rt_processgroup_t)rt_list_entry(node, struct rt_processgroup, pgrp_list_node); + PGRP_LOCK_NESTED(group); + if (sid != -1) + { + group->sid = sid; + group->session = session; + lwp_pgrp_update_children_info(group, sid, group->pgid); + } + PGRP_UNLOCK(group); + } + + SESS_UNLOCK(session); + return 0; +} + +int lwp_session_set_foreground(rt_session_t session, pid_t pgid) +{ + rt_processgroup_t group = RT_NULL; + rt_list_t *node = RT_NULL; + rt_bool_t is_contains = RT_FALSE; + + /* parameter check */ + if (session == RT_NULL || pgid <= 0) + { + return -EINVAL; + } + + SESS_LOCK(session); + + rt_list_for_each(node, &(session->processgroup)) + { + group = (rt_processgroup_t)rt_list_entry(node, struct rt_processgroup, pgrp_list_node); + PGRP_LOCK(group); + if (group->pgid == pgid) + { + is_contains = RT_TRUE; + } + PGRP_UNLOCK(group); + } + + if (is_contains) + { + session->foreground_pgid = pgid; + // TODO: maybe notify tty + } + + SESS_UNLOCK(session); + + return is_contains ? 0 : -EINVAL; +} + +/** + * setsid() creates a new session if the calling process is not a process group leader. + * The calling process is the leader of the new session (i.e., its session ID is made the same as its process ID). + * The calling process also becomes the process group leader of a new process group in the session + * (i.e., its process group ID is made the same as its process ID). + */ +sysret_t sys_setsid(void) +{ + rt_lwp_t process; + pid_t pid; + rt_processgroup_t group; + rt_session_t session; + sysret_t err = 0; + + process = lwp_self(); + pid = lwp_to_pid(process); + + /** + * if the calling process is already a process group leader. + */ + if (lwp_pgrp_find(pid)) + { + err = -EPERM; + goto exit; + } + + group = lwp_pgrp_create(process); + if (group) + { + lwp_pgrp_move(group, process); + session = lwp_session_create(process); + if (session) + { + lwp_session_move(session, group); + } + else + { + lwp_pgrp_delete(group); + } + err = lwp_sid_get_bysession(session); + } + else + { + err = -ENOMEM; + } + + +exit: + return err; +} + +/** + * getsid() returns the session ID of the process with process ID pid. + * If pid is 0, getsid() returns the session ID of the calling process. + */ +sysret_t sys_getsid(pid_t pid) +{ + rt_lwp_t process, self_process; + pid_t sid; + + lwp_pid_lock_take(); + process = lwp_from_pid_locked(pid); + lwp_pid_lock_release(); + + if (process == RT_NULL) + { + return -ESRCH; + } + + self_process = lwp_self(); + sid = lwp_sid_get_byprocess(process); + + if (sid != lwp_sid_get_byprocess(self_process)) + { + /** + * A process with process ID pid exists, but it is not in the same session as the calling process, + * and the implementation considers this an error. + * + * Note: Linux does not return EPERM. + */ + return -EPERM; + } + + return sid; +} + +#ifdef RT_USING_FINSH + +#include "finsh.h" + +long list_session(void) +{ + int count = 0, index; + rt_session_t *sessions; + rt_session_t session; + rt_thread_t thread; + char name[RT_NAME_MAX]; + + rt_kprintf("SID leader process\n"); + rt_kprintf("---- ----------------\n"); + + count = rt_object_get_length(RT_Object_Class_Session); + if (count > 0) + { + /* get pointers */ + sessions = (rt_session_t *)rt_calloc(count, sizeof(rt_session_t)); + if (sessions) + { + index = rt_object_get_pointers(RT_Object_Class_Session, (rt_object_t *)sessions, count); + if (index > 0) + { + for (index = 0; index < count; index++) + { + struct rt_session se; + session = sessions[index]; + SESS_LOCK(session); + rt_memcpy(&se, session, sizeof(struct rt_session)); + SESS_UNLOCK(session); + + if (se.leader && se.leader) + { + thread = rt_list_entry(se.leader->t_grp.prev, struct rt_thread, sibling); + rt_strncpy(name, thread->parent.name, RT_NAME_MAX); + } + else + { + rt_strncpy(name, "nil", RT_NAME_MAX); + } + + rt_kprintf("%4d %-*.*s\n", se.sid, RT_NAME_MAX, RT_NAME_MAX, name); + } + } + rt_free(sessions); + } + } + + return 0; +} +MSH_CMD_EXPORT(list_session, list session); +#endif diff --git a/components/lwp/lwp_setsid.c b/components/lwp/lwp_setsid.c deleted file mode 100644 index d72363bde5..0000000000 --- a/components/lwp/lwp_setsid.c +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include - -#include "lwp.h" -//#include "lwp_tid.h" -#include "lwp_pid.h" - -int setsid(void) -{ - int err = -EPERM; - struct rt_thread *current_thread = rt_thread_self(); - struct rt_lwp *current_lwp = (struct rt_lwp *)rt_thread_self()->lwp; - - if (current_lwp->session == current_thread->tid) - { - return err; - } - - current_lwp->session = current_thread->tid; - current_lwp->__pgrp = current_thread->tid; - current_lwp->leader = 1; - current_lwp->tty = RT_NULL; - current_lwp->tty_old_pgrp = 0; - - err = current_lwp->session; - return err; -} diff --git a/components/lwp/lwp_signal.c b/components/lwp/lwp_signal.c index 9e947ce7de..1f20ad5096 100644 --- a/components/lwp/lwp_signal.c +++ b/components/lwp/lwp_signal.c @@ -10,6 +10,9 @@ * 2023-07-04 Shell Support siginfo, sigqueue * remove lwp_signal_backup/restore() to reduce architecture codes * update the generation, pending and delivery routines + * 2023-11-22 Shell Support for job control signal. Fixup of signal catch while + * some of the signals is blocked, but no more further dequeue is applied. + * Add itimer support */ #define __RT_IPC_SOURCE__ #define DBG_TAG "lwp.signal" @@ -24,7 +27,7 @@ #include "sys/signal.h" #include "syscall_generic.h" -static lwp_siginfo_t siginfo_create(int signo, int code, int value) +static lwp_siginfo_t siginfo_create(rt_thread_t current, int signo, int code, lwp_siginfo_ext_t ext) { lwp_siginfo_t siginfo; struct rt_lwp *self_lwp; @@ -35,10 +38,10 @@ static lwp_siginfo_t siginfo_create(int signo, int code, int value) { siginfo->ksiginfo.signo = signo; siginfo->ksiginfo.code = code; - siginfo->ksiginfo.value = value; + siginfo->ext = ext; - self_lwp = lwp_self(); - self_thr = rt_thread_self(); + self_thr = current; + self_lwp = current->lwp; if (self_lwp) { siginfo->ksiginfo.from_pid = self_lwp->pid; @@ -56,6 +59,12 @@ static lwp_siginfo_t siginfo_create(int signo, int code, int value) rt_inline void siginfo_delete(lwp_siginfo_t siginfo) { + if (siginfo->ext) + { + rt_free(siginfo->ext); + siginfo->ext = RT_NULL; + } + rt_free(siginfo); } @@ -296,10 +305,30 @@ static lwp_siginfo_t sigqueue_dequeue(lwp_sigqueue_t sigqueue, int signo) return found; } +/** + * Discard all the signal matching `signo` in sigqueue + */ static void sigqueue_discard(lwp_sigqueue_t sigqueue, int signo) { lwp_siginfo_t queuing_si; - while (!sigqueue_isempty(sigqueue)) + while (sigqueue_ismember(sigqueue, signo)) + { + queuing_si = sigqueue_dequeue(sigqueue, signo); + siginfo_delete(queuing_si); + } +} + +/** + * Discard all the queuing signals in sigset + */ +static void sigqueue_discard_sigset(lwp_sigqueue_t sigqueue, lwp_sigset_t *sigset) +{ + lwp_siginfo_t queuing_si; + lwp_sigset_t mask; + int signo; + + _signotsets(&mask, sigset); + while ((signo = sigqueue_peek(sigqueue, &mask)) != 0) { queuing_si = sigqueue_dequeue(sigqueue, signo); siginfo_delete(queuing_si); @@ -312,11 +341,21 @@ RT_STATIC_ASSERT(lp_width_same, sizeof(void *) == sizeof(long)); /** translate lwp siginfo to user siginfo_t */ rt_inline void siginfo_k2u(lwp_siginfo_t ksigi, siginfo_t *usigi) { + int signo = ksigi->ksiginfo.signo; usigi->si_code = ksigi->ksiginfo.code; - usigi->si_signo = ksigi->ksiginfo.signo; - usigi->si_value.sival_ptr = (void *)ksigi->ksiginfo.value; + usigi->si_signo = signo; usigi->si_pid = ksigi->ksiginfo.from_pid; + if (ksigi->ext) + { + if (signo == SIGCHLD) + { + usigi->si_status = ksigi->ext->sigchld.status; + usigi->si_utime = ksigi->ext->sigchld.stime; + usigi->si_stime = ksigi->ext->sigchld.utime; + } + } + /* deprecated field */ usigi->si_errno = 0; } @@ -417,7 +456,10 @@ rt_err_t lwp_signal_init(struct lwp_signal *sig) memset(&sig->sig_action_onstack, 0, sizeof(sig->sig_action_onstack)); memset(&sig->sig_action_restart, 0, sizeof(sig->sig_action_restart)); memset(&sig->sig_action_siginfo, 0, sizeof(sig->sig_action_siginfo)); + memset(&sig->sig_action_nocldstop, 0, sizeof(sig->sig_action_nocldstop)); + memset(&sig->sig_action_nocldwait, 0, sizeof(sig->sig_action_nocldwait)); lwp_sigqueue_init(&sig->sig_queue); + return rc; } @@ -433,17 +475,19 @@ rt_err_t lwp_signal_detach(struct lwp_signal *signal) int lwp_thread_signal_suspend_check(rt_thread_t thread, int suspend_flag) { - struct rt_lwp *lwp = (struct rt_lwp*)thread->lwp; + struct rt_lwp *lwp = (struct rt_lwp *)thread->lwp; + lwp_sigset_t sigmask = thread->signal.sigset_mask; int ret = 0; + _sigaddset(&sigmask, SIGCONT); switch (suspend_flag) { case RT_INTERRUPTIBLE: - if (!sigqueue_isempty(_SIGQ(thread))) + if (sigqueue_peek(_SIGQ(thread), &sigmask)) { break; } - if (thread->lwp && !sigqueue_isempty(_SIGQ(lwp))) + if (thread->lwp && sigqueue_peek(_SIGQ(lwp), &sigmask)) { break; } @@ -470,84 +514,202 @@ int lwp_thread_signal_suspend_check(rt_thread_t thread, int suspend_flag) return ret; } -void lwp_thread_signal_catch(void *exp_frame) +rt_inline rt_bool_t _is_jobctl_signal(rt_lwp_t lwp, int signo) { - int signo = 0; - struct rt_thread *thread; - struct rt_lwp *lwp; - lwp_siginfo_t siginfo = 0; - lwp_sigqueue_t pending; - lwp_sigset_t *sig_mask; - lwp_sigset_t save_sig_mask; - lwp_sigset_t new_sig_mask; - lwp_sighandler_t handler = 0; - siginfo_t usiginfo; - siginfo_t *p_usi = RT_NULL; + lwp_sigset_t jobctl_sigset = lwp_sigset_init(LWP_SIG_JOBCTL_SET); - thread = rt_thread_self(); - lwp = (struct rt_lwp*)thread->lwp; + return lwp_sigismember(&jobctl_sigset, signo); +} - RT_ASSERT(!!lwp); - LWP_LOCK(lwp); +rt_inline rt_bool_t _is_stop_signal(rt_lwp_t lwp, int signo) +{ + lwp_sigset_t stop_sigset = lwp_sigset_init(LWP_SIG_STOP_SET); + + return lwp_sigismember(&stop_sigset, signo); +} + +rt_inline rt_bool_t _need_notify_status_changed(rt_lwp_t lwp, int signo) +{ + RT_ASSERT(lwp_sigismember(&lwp_sigset_init(LWP_SIG_JOBCTL_SET), signo)); + return !lwp_sigismember(&lwp->signal.sig_action_nocldstop, SIGCHLD); +} + +/** + * wakeup the waitpid_waiters if any, and try to generate SIGCHLD if they are + * not disable explicitly by user. + * + * TODO: This event is always per-process and doesn't make whole lot of + * sense for ptracers, who shouldn't consume the state via wait(2) either, + * but, for backward compatibility, notify the ptracer of the group leader + * too unless it's gonna be a duplicate. + */ +static void _notify_parent_and_leader(rt_lwp_t child_lwp, rt_thread_t child_thr, int trig_signo, rt_bool_t is_stop) +{ + int si_code; + lwp_siginfo_ext_t ext; + rt_lwp_t parent_lwp = child_lwp->parent; + + if (!parent_lwp) + return ; - /* check if signal exist */ - if (!sigqueue_isempty(_SIGQ(thread))) + /* prepare the event data for parent to query */ + if (is_stop) { - pending = _SIGQ(thread); - sig_mask = &thread->signal.sigset_mask; + si_code = CLD_STOPPED; + child_lwp->lwp_status = LWP_CREATE_STAT_STOPPED(trig_signo); } - else if (!sigqueue_isempty(_SIGQ(lwp))) + else { - pending = _SIGQ(lwp); - sig_mask = &thread->signal.sigset_mask; + si_code = CLD_CONTINUED; + child_lwp->lwp_status = LWP_CREATE_STAT_CONTINUED; } - else + + /* wakeup waiter on waitpid(2) */ + lwp_waitpid_kick(parent_lwp, child_lwp); + + if (_need_notify_status_changed(parent_lwp, trig_signo)) { - pending = RT_NULL; + ext = rt_malloc(sizeof(struct lwp_siginfo_ext)); + if (ext) + { + ext->sigchld.status = trig_signo; + + /* TODO: signal usage is not supported */ + ext->sigchld.stime = child_thr->system_time; + ext->sigchld.utime = child_thr->user_time; + } + + /* generate SIGCHLD for parent */ + lwp_signal_kill(parent_lwp, SIGCHLD, si_code, ext); } +} + +static int _do_signal_wakeup(rt_thread_t thread, int sig); +static rt_err_t _stop_thread_locked(rt_lwp_t self_lwp, rt_thread_t cur_thr, int signo, + lwp_siginfo_t si, lwp_sigqueue_t sq) +{ + rt_err_t error; + int jobctl_stopped = self_lwp->jobctl_stopped; + rt_thread_t iter; - if (pending) + /* race to setup jobctl stopped flags */ + if (!jobctl_stopped) { - /* peek the pending signal */ - signo = sigqueue_peek(pending, sig_mask); - if (signo) + self_lwp->jobctl_stopped = RT_TRUE; + self_lwp->wait_reap_stp = RT_FALSE; + rt_list_for_each_entry(iter, &self_lwp->t_grp, sibling) { - siginfo = sigqueue_dequeue(pending, signo); - RT_ASSERT(siginfo != RT_NULL); - handler = _get_sighandler_locked(lwp, signo); + if (iter != cur_thr) + _do_signal_wakeup(iter, signo); + } + } - /* IGN signal will never be queued */ - RT_ASSERT(handler != LWP_SIG_ACT_IGN); + /** + * raise the event again so siblings is able to catch it again. + * `si` will be discarded while SIGCONT is generatd + */ + sigqueue_enqueue(sq, si); - /* copy the blocked signal mask from the registered signal action */ - memcpy(&new_sig_mask, &lwp->signal.sig_action_mask[signo - 1], sizeof(new_sig_mask)); + /* release the lwp lock so we can happily suspend */ + LWP_UNLOCK(self_lwp); - if (!_sigismember(&lwp->signal.sig_action_nodefer, signo)) - _sigaddset(&new_sig_mask, signo); + rt_set_errno(RT_EOK); - _thread_signal_mask(thread, LWP_SIG_MASK_CMD_BLOCK, &new_sig_mask, &save_sig_mask); + /* After suspension, only the SIGKILL and SIGCONT will wake this thread up */ + error = rt_thread_suspend_with_flag(cur_thr, RT_KILLABLE); + if (error == RT_EOK) + { + rt_schedule(); + error = rt_get_errno(); + error = error > 0 ? -error : error; + } - /* siginfo is need for signal action */ - if (_sigismember(&lwp->signal.sig_action_siginfo, signo)) - { - siginfo_k2u(siginfo, &usiginfo); - p_usi = &usiginfo; - } - else - p_usi = RT_NULL; - } + if (!jobctl_stopped && + (sigqueue_ismember(_SIGQ(self_lwp), SIGCONT) || + sigqueue_ismember(_SIGQ(cur_thr), SIGCONT))) + { + /** + * if we are resumed by a SIGCONT and we are the winner of racing + * notify parent of the incoming event + */ + _notify_parent_and_leader(self_lwp, cur_thr, SIGCONT, RT_FALSE); } + + /* reacquire the lock since we release it before */ + LWP_LOCK(self_lwp); + + return error; +} + +static void _catch_signal_locked(rt_lwp_t lwp, rt_thread_t thread, int signo, + lwp_siginfo_t siginfo, lwp_sighandler_t handler, + void *exp_frame) +{ + lwp_sigset_t new_sig_mask; + lwp_sigset_t save_sig_mask; + siginfo_t usiginfo; + siginfo_t *p_usi; + + /* siginfo is need for signal action */ + if (_sigismember(&lwp->signal.sig_action_siginfo, signo)) + { + siginfo_k2u(siginfo, &usiginfo); + p_usi = &usiginfo; + } + else + { + p_usi = RT_NULL; + } + + /** + * lock is acquired by caller. Release it so that we can happily go to the + * signal handler in user space + */ LWP_UNLOCK(lwp); - if (pending && signo) + siginfo_delete(siginfo); + + /* signal default handler */ + if (handler == LWP_SIG_ACT_DFL) { - siginfo_delete(siginfo); + lwp_sigset_t ign_sigset; - /* signal default handler */ - if (handler == LWP_SIG_ACT_DFL) + ign_sigset = lwp_sigset_init(LWP_SIG_IGNORE_SET); + if (signo == SIGCONT) + { + arch_syscall_set_errno(exp_frame, EINTR, ERESTART); + arch_thread_signal_enter(signo, p_usi, exp_frame, 0, &thread->signal.sigset_mask); + } + else if (!lwp_sigismember(&ign_sigset, signo) && !lwp->sig_protected) { + /* for those defautl handler is to terminate process */ LOG_D("%s: default handler; and exit", __func__); - sys_exit_group(0); + + /* TODO: coredump if neccessary */ + lwp_exit(lwp, LWP_CREATE_STAT_SIGNALED(signo, 0)); + } + /** + * otherwise is to ignore the signal, + * -> then reacquire the lock and return + */ + } + else if (handler == LWP_SIG_ACT_IGN) + { + /* do nothing */ + } + else + { + /* copy the blocked signal mask from the registered signal action */ + memcpy(&new_sig_mask, &lwp->signal.sig_action_mask[signo - 1], sizeof(new_sig_mask)); + + if (!_sigismember(&lwp->signal.sig_action_nodefer, signo)) + _sigaddset(&new_sig_mask, signo); + + _thread_signal_mask(thread, LWP_SIG_MASK_CMD_BLOCK, &new_sig_mask, &save_sig_mask); + + if (_sigismember(&lwp->signal.sig_action_restart, signo)) + { + arch_syscall_set_errno(exp_frame, EINTR, ERESTART); } /** @@ -557,18 +719,92 @@ void lwp_thread_signal_catch(void *exp_frame) */ LOG_D("%s: enter signal handler(signo=%d) at %p", __func__, signo, handler); arch_thread_signal_enter(signo, p_usi, exp_frame, handler, &save_sig_mask); + /* the arch_thread_signal_enter() never return */ RT_ASSERT(0); } + + /* reacquire the lock because we release it before */ + LWP_LOCK(lwp); } + +void lwp_thread_signal_catch(void *exp_frame) +{ + struct rt_thread *thread; + struct rt_lwp *lwp; + lwp_sigqueue_t pending; + lwp_sigset_t *sig_mask; + int retry_signal_catch; + int signo; + + thread = rt_thread_self(); + lwp = (struct rt_lwp *)thread->lwp; + + RT_ASSERT(!!lwp); + LWP_LOCK(lwp); + + do { + /* if stopped process resume, we will retry to catch the signal */ + retry_signal_catch = 0; + signo = 0; + + /* try to peek a signal which is pending and not blocked by this thread */ + if (!sigqueue_isempty(_SIGQ(thread))) + { + pending = _SIGQ(thread); + sig_mask = &thread->signal.sigset_mask; + signo = sigqueue_peek(pending, sig_mask); + } + if (!signo && !sigqueue_isempty(_SIGQ(lwp))) + { + pending = _SIGQ(lwp); + sig_mask = &thread->signal.sigset_mask; + signo = sigqueue_peek(pending, sig_mask); + } + + if (signo) + { + lwp_siginfo_t siginfo; + lwp_sighandler_t handler; + + LOG_D("%s(signo=%d)", __func__, signo); + + siginfo = sigqueue_dequeue(pending, signo); + RT_ASSERT(siginfo != RT_NULL); + handler = _get_sighandler_locked(lwp, signo); + + if (_is_stop_signal(lwp, signo) && handler == LWP_SIG_ACT_DFL) + { + /* notify the status update for parent process */ + _notify_parent_and_leader(lwp, thread, signo, RT_TRUE); + + LOG_D("%s: pid=%d stopped", __func__, lwp->pid); + _stop_thread_locked(lwp, thread, signo, siginfo, pending); + LOG_D("%s: pid=%d continued", __func__, lwp->pid); + + /* wakeup and retry to catch signals send to us */ + retry_signal_catch = 1; + } + else + { + /* do a normal, non-jobctl signal handling */ + _catch_signal_locked(lwp, thread, signo, siginfo, handler, exp_frame); + } + } + } while (retry_signal_catch); + + LWP_UNLOCK(lwp); +} + static int _do_signal_wakeup(rt_thread_t thread, int sig) { int need_schedule; rt_sched_lock_level_t slvl; if (!_sigismember(&thread->signal.sigset_mask, sig)) { + int stat; rt_sched_lock(&slvl); - int stat = rt_sched_thread_get_stat(thread); + stat = rt_sched_thread_get_stat(thread); if ((stat & RT_THREAD_SUSPEND_MASK) == RT_THREAD_SUSPEND_MASK) { if ((stat & RT_SIGNAL_COMMON_WAKEUP_MASK) != RT_SIGNAL_COMMON_WAKEUP_MASK) @@ -599,6 +835,8 @@ static int _do_signal_wakeup(rt_thread_t thread, int sig) rt_sched_unlock(slvl); need_schedule = 0; } + + RT_SCHED_DEBUG_IS_UNLOCKED; } else need_schedule = 0; @@ -689,7 +927,63 @@ rt_inline rt_bool_t _sighandler_cannot_caught(struct rt_lwp *lwp, int signo) return signo == SIGKILL || signo == SIGSTOP; } -rt_err_t lwp_signal_kill(struct rt_lwp *lwp, long signo, long code, long value) +/* before signal is killed to target process/thread */ +static void _before_sending_jobctl_signal(int signo, rt_lwp_t target_lwp, lwp_siginfo_t si) +{ + rt_thread_t thr_iter; + rt_sched_lock_level_t slvl; + lwp_sigset_t jobctl_sigset = lwp_sigset_init(LWP_SIG_JOBCTL_SET); + + LWP_ASSERT_LOCKED(target_lwp); + + /** + * dequeue all the pending jobctl signals (including + * the one we are adding, since we don't want to pend it) + */ + sigqueue_discard_sigset(_SIGQ(target_lwp), &jobctl_sigset); + + if (signo == SIGCONT) + { + target_lwp->jobctl_stopped = RT_FALSE; + rt_list_for_each_entry(thr_iter, &target_lwp->t_grp, sibling) + { + rt_base_t stat; + sigqueue_discard_sigset(_SIGQ(thr_iter), &jobctl_sigset); + + /** + * Note: all stopped thread will be resumed + */ + rt_sched_lock(&slvl); + stat = rt_sched_thread_get_stat(thr_iter); + if ((stat & RT_THREAD_SUSPEND_MASK) == RT_THREAD_SUSPEND_MASK && + (stat & RT_SIGNAL_KILL_WAKEUP_MASK) == 0) + { + thr_iter->error = RT_EINTR; + + /** + * don't matter if we failed to resume the thread, since we + * only care about the event passing, but not ordering here + */ + rt_sched_unlock(slvl); + rt_thread_wakeup(thr_iter); + } + else + { + rt_sched_unlock(slvl); + } + + } + } + else + { + rt_list_for_each_entry(thr_iter, &target_lwp->t_grp, sibling) + { + sigqueue_discard_sigset(_SIGQ(thr_iter), &jobctl_sigset); + } + } +} + +rt_err_t lwp_signal_kill(struct rt_lwp *lwp, long signo, long code, lwp_siginfo_ext_t value) { rt_err_t ret = -1; @@ -700,10 +994,15 @@ rt_err_t lwp_signal_kill(struct rt_lwp *lwp, long signo, long code, long value) /** must be able to be suspended */ RT_DEBUG_SCHEDULER_AVAILABLE(RT_TRUE); - if (!lwp || signo <= 0 || signo > _LWP_NSIG) + if (!lwp || signo < 0 || signo > _LWP_NSIG) { ret = -RT_EINVAL; } + else if (signo == 0) + { + /* process exist and current process have privileges */ + ret = 0; + } else { LOG_D("%s(lwp=%p \"%s\",signo=%ld,code=%ld,value=%ld)", @@ -715,19 +1014,25 @@ rt_err_t lwp_signal_kill(struct rt_lwp *lwp, long signo, long code, long value) terminated = lwp->terminated; /* short-circuit code for inactive task, ignored signals */ - if (terminated || _sighandler_is_ignored(lwp, signo)) + if (terminated) { + /* no one rely on this, then free the resource */ + if (value) + rt_free(value); ret = 0; } else { - siginfo = siginfo_create(signo, code, value); + siginfo = siginfo_create(rt_thread_self(), signo, code, value); if (siginfo) { + if (_is_jobctl_signal(lwp, signo)) + _before_sending_jobctl_signal(signo, lwp, siginfo); + need_schedule = _siginfo_deliver_to_lwp(lwp, siginfo); - ret = 0; lwp_signal_notify(&lwp->signalfd_notify_head, siginfo); + ret = 0; } else { @@ -755,6 +1060,10 @@ static void _signal_action_flag_k2u(int signo, struct lwp_signal *signal, struct flags |= SA_RESTART; if (_sigismember(&signal->sig_action_siginfo, signo)) flags |= SA_SIGINFO; + if (_sigismember(&signal->sig_action_nocldstop, signo)) + flags |= SA_NOCLDSTOP; + if (_sigismember(&signal->sig_action_nocldwait, signo)) + flags |= SA_NOCLDWAIT; act->sa_flags = flags; } @@ -770,6 +1079,25 @@ static void _signal_action_flag_u2k(int signo, struct lwp_signal *signal, const _sigaddset(&signal->sig_action_restart, signo); if (flags & SA_SIGINFO) _sigaddset(&signal->sig_action_siginfo, signo); + if (signo == SIGCHLD) + { + /* These flags are meaningful only when establishing a handler for SIGCHLD */ + if (flags & SA_NOCLDSTOP) + _sigaddset(&signal->sig_action_nocldstop, signo); + if (flags & SA_NOCLDWAIT) + _sigaddset(&signal->sig_action_nocldwait, signo); + } + + #define _HANDLE_FLAGS (SA_RESTORER | SA_NODEFER | SA_ONSTACK | SA_RESTART | SA_SIGINFO | SA_NOCLDSTOP | SA_NOCLDWAIT) + if (flags & ~_HANDLE_FLAGS) + LOG_W("Unhandled flags: 0x%lx", flags & ~_HANDLE_FLAGS); +} + +rt_bool_t lwp_sigisign(struct rt_lwp *lwp, int _sig) +{ + unsigned long sig = _sig - 1; + + return lwp->signal.sig_action[sig] == LWP_SIG_ACT_IGN; } rt_err_t lwp_signal_action(struct rt_lwp *lwp, int signo, @@ -793,6 +1121,7 @@ rt_err_t lwp_signal_action(struct rt_lwp *lwp, int signo, oact->sa_restorer = RT_NULL; _signal_action_flag_k2u(signo, &lwp->signal, oact); } + if (act) { /** @@ -800,15 +1129,29 @@ rt_err_t lwp_signal_action(struct rt_lwp *lwp, int signo, * argument succeed, even in the case of signals that cannot be caught or ignored */ if (_sighandler_cannot_caught(lwp, signo)) - ret = -RT_EINVAL; + ret = -EINVAL; else { prev_handler = _get_sighandler_locked(lwp, signo); lwp->signal.sig_action_mask[signo - 1] = act->sa_mask; if (act->__sa_handler._sa_handler == SIG_IGN) - lwp->signal.sig_action[signo - 1] = LWP_SIG_ACT_IGN; + { + lwp_sigset_t no_ign_set = lwp_sigset_init(LWP_SIG_NO_IGN_SET); + if (!lwp_sigismember(&no_ign_set, signo)) + { + /* except those unignorable signals, discard them for proc */ + lwp->signal.sig_action[signo - 1] = LWP_SIG_ACT_IGN; + } + else + { + /* POSIX.1: SIG_IGN and SIG_DFL are equivalent for SIGCONT */ + lwp->signal.sig_action[signo - 1] = LWP_SIG_ACT_DFL; + } + } else + { lwp->signal.sig_action[signo - 1] = act->__sa_handler._sa_handler; + } _signal_action_flag_u2k(signo, &lwp->signal, act); @@ -837,12 +1180,12 @@ rt_err_t lwp_signal_action(struct rt_lwp *lwp, int signo, LWP_UNLOCK(lwp); } else - ret = -RT_EINVAL; + ret = -EINVAL; return ret; } -rt_err_t lwp_thread_signal_kill(rt_thread_t thread, long signo, long code, long value) +rt_err_t lwp_thread_signal_kill(rt_thread_t thread, long signo, long code, lwp_siginfo_ext_t value) { rt_err_t ret = -1; @@ -855,10 +1198,15 @@ rt_err_t lwp_thread_signal_kill(rt_thread_t thread, long signo, long code, long LOG_D("%s(signo=%d)", __func__, signo); - if (!thread || signo <= 0 || signo >= _LWP_NSIG) + if (!thread || signo < 0 || signo >= _LWP_NSIG) { ret = -RT_EINVAL; } + else if (signo == 0) + { + /* thread exist and current thread have privileges */ + ret = 0; + } else { lwp = thread->lwp; @@ -874,7 +1222,7 @@ rt_err_t lwp_thread_signal_kill(rt_thread_t thread, long signo, long code, long ret = 0; else { - siginfo = siginfo_create(signo, code, value); + siginfo = siginfo_create(rt_thread_self(), signo, code, value); if (siginfo) { @@ -919,7 +1267,7 @@ rt_err_t lwp_thread_signal_mask(rt_thread_t thread, lwp_sig_mask_cmd_t how, if (thread) { - lwp = (struct rt_lwp*)thread->lwp; + lwp = (struct rt_lwp *)thread->lwp; LWP_LOCK(lwp); if (!lwp) @@ -948,13 +1296,14 @@ static int _dequeue_signal(rt_thread_t thread, lwp_sigset_t *mask, siginfo_t *us lwp_sigset_t *pending; lwp_sigqueue_t sigqueue; + lwp = thread->lwp; + RT_ASSERT(lwp); + sigqueue = _SIGQ(thread); pending = &sigqueue->sigset_pending; signo = _next_signal(pending, mask); if (!signo) { - lwp = thread->lwp; - RT_ASSERT(lwp); sigqueue = _SIGQ(lwp); pending = &sigqueue->sigset_pending; signo = _next_signal(pending, mask); @@ -978,6 +1327,7 @@ rt_err_t lwp_thread_signal_timedwait(rt_thread_t thread, lwp_sigset_t *sigset, rt_err_t ret; lwp_sigset_t saved_sigset; lwp_sigset_t blocked_sigset; + lwp_sigset_t dontwait_sigset; int sig; struct rt_lwp *lwp = thread->lwp; @@ -990,10 +1340,10 @@ rt_err_t lwp_thread_signal_timedwait(rt_thread_t thread, lwp_sigset_t *sigset, /* Create a mask of signals user dont want or cannot catch */ _sigdelset(sigset, SIGKILL); _sigdelset(sigset, SIGSTOP); - _signotsets(sigset, sigset); + _signotsets(&dontwait_sigset, sigset); LWP_LOCK(lwp); - sig = _dequeue_signal(thread, sigset, usi); + sig = _dequeue_signal(thread, &dontwait_sigset, usi); LWP_UNLOCK(lwp); if (sig) return sig; @@ -1007,13 +1357,12 @@ rt_err_t lwp_thread_signal_timedwait(rt_thread_t thread, lwp_sigset_t *sigset, * Note: If the pending signal arrives before thread suspend, the suspend * operation will return a failure */ - _sigandsets(&blocked_sigset, &thread->signal.sigset_mask, sigset); + _sigandsets(&blocked_sigset, &thread->signal.sigset_mask, &dontwait_sigset); _thread_signal_mask(thread, LWP_SIG_MASK_CMD_SET_MASK, &blocked_sigset, &saved_sigset); if (timeout) { - rt_uint32_t time; - time = rt_timespec_to_tick(timeout); - + rt_tick_t time; + time = (timeout->tv_sec * RT_TICK_PER_SECOND) + ((timeout->tv_nsec * RT_TICK_PER_SECOND) / NANOSECOND_PER_SECOND); /** * Brief: POSIX * If the timespec structure pointed to by timeout is zero-valued and @@ -1023,11 +1372,13 @@ rt_err_t lwp_thread_signal_timedwait(rt_thread_t thread, lwp_sigset_t *sigset, if (time == 0) return -EAGAIN; + rt_enter_critical(); ret = rt_thread_suspend_with_flag(thread, RT_INTERRUPTIBLE); rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &time); rt_timer_start(&(thread->thread_timer)); + rt_exit_critical(); } else @@ -1040,7 +1391,7 @@ rt_err_t lwp_thread_signal_timedwait(rt_thread_t thread, lwp_sigset_t *sigset, { rt_schedule(); /* If thread->error reliable? */ - if (thread->error == -RT_EINTR) + if (thread->error == RT_EINTR) ret = -EINTR; else ret = -EAGAIN; @@ -1049,7 +1400,7 @@ rt_err_t lwp_thread_signal_timedwait(rt_thread_t thread, lwp_sigset_t *sigset, _thread_signal_mask(thread, LWP_SIG_MASK_CMD_SET_MASK, &saved_sigset, RT_NULL); LWP_LOCK(lwp); - sig = _dequeue_signal(thread, sigset, usi); + sig = _dequeue_signal(thread, &dontwait_sigset, usi); LWP_UNLOCK(lwp); return sig ? sig : ret; @@ -1072,3 +1423,22 @@ void lwp_thread_signal_pending(rt_thread_t thread, lwp_sigset_t *pending) _sigandsets(pending, pending, &thread->signal.sigset_mask); } } + +rt_err_t lwp_pgrp_signal_kill(rt_processgroup_t pgrp, long signo, long code, + lwp_siginfo_ext_t value) +{ + struct rt_lwp *lwp; + rt_err_t rc = 0; + + PGRP_ASSERT_LOCKED(pgrp); + + if (pgrp) + { + rt_list_for_each_entry(lwp, &pgrp->process, pgrp_node) + { + lwp_signal_kill(lwp, signo, code, value); + } + } + + return rc; +} diff --git a/components/lwp/lwp_signal.h b/components/lwp/lwp_signal.h index 3b5fd91321..f98b43dd3f 100644 --- a/components/lwp/lwp_signal.h +++ b/components/lwp/lwp_signal.h @@ -7,6 +7,7 @@ * Date Author Notes * 2020-02-23 Jesven first version. * 2023-07-06 Shell update the generation, pending and delivery API + * 2023-11-22 Shell support for job control signal */ #ifndef __LWP_SIGNAL_H__ @@ -17,23 +18,33 @@ #include #include -struct timespec; -struct itimerspec; - #ifdef __cplusplus extern "C" { #endif #define _USIGNAL_SIGMASK(signo) (1u << ((signo)-1)) -#define LWP_SIG_IGNORE_SET (_USIGNAL_SIGMASK(SIGCHLD) | _USIGNAL_SIGMASK(SIGURG)) -#define LWP_SIG_ACT_DFL ((lwp_sighandler_t)0) -#define LWP_SIG_ACT_IGN ((lwp_sighandler_t)1) -#define LWP_SIG_USER_SA_FLAGS \ - (SA_NOCLDSTOP | SA_NOCLDWAIT | SA_SIGINFO | SA_ONSTACK | SA_RESTART | \ +#define LWP_SIG_NO_IGN_SET \ + (_USIGNAL_SIGMASK(SIGCONT) | _USIGNAL_SIGMASK(SIGSTOP) | \ + _USIGNAL_SIGMASK(SIGKILL)) +#define LWP_SIG_IGNORE_SET \ + (_USIGNAL_SIGMASK(SIGCHLD) | _USIGNAL_SIGMASK(SIGURG) | \ + _USIGNAL_SIGMASK(SIGWINCH) /* from 4.3 BSD, not POSIX.1 */) +#define LWP_SIG_JOBCTL_SET \ + (_USIGNAL_SIGMASK(SIGCONT) | _USIGNAL_SIGMASK(SIGSTOP) | \ + _USIGNAL_SIGMASK(SIGTSTP) | _USIGNAL_SIGMASK(SIGTTIN) | \ + _USIGNAL_SIGMASK(SIGTTOU)) +#define LWP_SIG_STOP_SET \ + (_USIGNAL_SIGMASK(SIGSTOP) | _USIGNAL_SIGMASK(SIGTSTP) | \ + _USIGNAL_SIGMASK(SIGTTIN) | _USIGNAL_SIGMASK(SIGTTOU)) +#define LWP_SIG_ACT_DFL ((lwp_sighandler_t)0) +#define LWP_SIG_ACT_IGN ((lwp_sighandler_t)1) +#define LWP_SIG_USER_SA_FLAGS \ + (SA_NOCLDSTOP | SA_NOCLDWAIT | SA_SIGINFO | SA_ONSTACK | SA_RESTART | \ SA_NODEFER | SA_RESETHAND | SA_EXPOSE_TAGBITS) #define LWP_SIG_INVALID_TIMER ((timer_t)-1) -typedef enum { +typedef enum +{ LWP_SIG_MASK_CMD_BLOCK, LWP_SIG_MASK_CMD_UNBLOCK, LWP_SIG_MASK_CMD_SET_MASK, @@ -43,7 +54,8 @@ typedef enum { /** * LwP implementation of POSIX signal */ -struct lwp_signal { +struct lwp_signal +{ timer_t real_timer; struct lwp_sigqueue sig_queue; rt_thread_t sig_dispatch_thr[_LWP_NSIG]; @@ -55,9 +67,12 @@ struct lwp_signal { lwp_sigset_t sig_action_onstack; lwp_sigset_t sig_action_restart; lwp_sigset_t sig_action_siginfo; + lwp_sigset_t sig_action_nocldstop; + lwp_sigset_t sig_action_nocldwait; }; struct rt_lwp; +struct rt_processgroup; #ifndef ARCH_MM_MMU void lwp_sighandler_set(int sig, lwp_sighandler_t func); @@ -93,12 +108,14 @@ rt_inline void lwp_thread_signal_detach(struct lwp_thread_signal *tsig) * @param signo the signal number * @param code as in siginfo * @param value as in siginfo - * @return rt_err_t RT_EINVAL if the parameter is invalid, RT_EOK as successful + * @return rt_err_t RT_EINVAL if the parameter is invalid, RT_EOK as + * successful * * @note the *signal_kill have the same definition of a successful return as * kill() in IEEE Std 1003.1-2017 */ -rt_err_t lwp_signal_kill(struct rt_lwp *lwp, long signo, long code, long value); +rt_err_t lwp_signal_kill(struct rt_lwp *lwp, long signo, long code, + lwp_siginfo_ext_t value); /** * @brief set or examine the signal action of signo @@ -119,9 +136,11 @@ rt_err_t lwp_signal_action(struct rt_lwp *lwp, int signo, * @param signo the signal number * @param code as in siginfo * @param value as in siginfo - * @return rt_err_t RT_EINVAL if the parameter is invalid, RT_EOK as successful + * @return rt_err_t RT_EINVAL if the parameter is invalid, RT_EOK as + * successful */ -rt_err_t lwp_thread_signal_kill(rt_thread_t thread, long signo, long code, long value); +rt_err_t lwp_thread_signal_kill(rt_thread_t thread, long signo, long code, + lwp_siginfo_ext_t value); /** * @brief set signal mask of target thread @@ -136,7 +155,8 @@ rt_err_t lwp_thread_signal_mask(rt_thread_t thread, lwp_sig_mask_cmd_t how, const lwp_sigset_t *sigset, lwp_sigset_t *oset); /** - * @brief Catch signal if exists and no return, otherwise return with no side effect + * @brief Catch signal if exists and no return, otherwise return with no + * side effect * * @param exp_frame the exception frame on kernel stack */ @@ -172,10 +192,43 @@ rt_err_t lwp_thread_signal_timedwait(rt_thread_t thread, lwp_sigset_t *sigset, */ void lwp_thread_signal_pending(rt_thread_t thread, lwp_sigset_t *sigset); +/** + * @brief send a signal to the process group + * + * @param pgrp target process group + * @param signo the signal number + * @param code as in siginfo + * @param value as in siginfo + * @return rt_err_t RT_EINVAL if the parameter is invalid, RT_EOK as + * successful + */ +rt_err_t lwp_pgrp_signal_kill(struct rt_processgroup *pgrp, long signo, + long code, lwp_siginfo_ext_t value); + +rt_inline int lwp_sigismember(lwp_sigset_t *set, int _sig) +{ + unsigned long sig = _sig - 1; + + if (_LWP_NSIG_WORDS == 1) + { + return 1 & (set->sig[0] >> sig); + } + else + { + return 1 & (set->sig[sig / _LWP_NSIG_BPW] >> (sig % _LWP_NSIG_BPW)); + } +} + +struct itimerspec; + +rt_bool_t lwp_sigisign(struct rt_lwp *lwp, int _sig); + rt_err_t lwp_signal_setitimer(struct rt_lwp *lwp, int which, const struct itimerspec *restrict new, struct itimerspec *restrict old); +rt_bool_t lwp_signal_restart_syscall(struct rt_lwp *lwp, int error_code); + #ifdef __cplusplus } #endif diff --git a/components/lwp/lwp_sys_socket.h b/components/lwp/lwp_sys_socket.h index 527d5ebbe8..28a0887203 100644 --- a/components/lwp/lwp_sys_socket.h +++ b/components/lwp/lwp_sys_socket.h @@ -113,7 +113,7 @@ struct musl_ifreq { union { -#define IFNAMSIZ 16 +#define IFNAMSIZ 16 char ifrn_name[IFNAMSIZ]; } ifr_ifrn; union @@ -133,4 +133,23 @@ struct musl_ifreq } ifr_ifru; }; +struct musl_rtentry +{ + unsigned long int rt_pad1; + struct musl_sockaddr rt_dst; + struct musl_sockaddr rt_gateway; + struct musl_sockaddr rt_genmask; + unsigned short int rt_flags; + short int rt_pad2; + unsigned long int rt_pad3; + unsigned char rt_tos; + unsigned char rt_class; + short int rt_pad4[sizeof(long)/2-1]; + short int rt_metric; + char *rt_dev; + unsigned long int rt_mtu; + unsigned long int rt_window; + unsigned short int rt_irtt; +}; + #endif /* __LWP_SYS_SOCKET_H__ */ diff --git a/components/lwp/lwp_syscall.c b/components/lwp/lwp_syscall.c index 718636e102..5d9cf05b63 100644 --- a/components/lwp/lwp_syscall.c +++ b/components/lwp/lwp_syscall.c @@ -13,6 +13,9 @@ * 2023-03-13 WangXiaoyao Format & fix syscall return value * 2023-07-06 Shell adapt the signal API, and clone, fork to new implementation of lwp signal * 2023-07-27 Shell Move tid_put() from lwp_free() to sys_exit() + * 2023-11-16 xqyjlj fix some syscalls (about sched_*, get/setpriority) + * 2023-11-17 xqyjlj add process group and session support + * 2023-11-30 Shell Fix sys_setitimer() and exit(status) */ #define __RT_IPC_SOURCE__ #define _GNU_SOURCE @@ -22,7 +25,6 @@ #include #include -#include #include #include @@ -57,6 +59,7 @@ #include #include /* statfs() */ #include +#include #ifdef RT_USING_MUSLLIBC #include #endif @@ -88,7 +91,6 @@ #define SYSCALL_USPACE(f) SYSCALL_SIGN(sys_notimpl) #endif /* defined(RT_USING_DFS) && defined(ARCH_MM_MMU) */ -#include #include "lwp_ipc_internal.h" #include @@ -329,10 +331,14 @@ static void _crt_thread_entry(void *parameter) sysret_t sys_exit_group(int value) { sysret_t rc = 0; + lwp_status_t lwp_status; struct rt_lwp *lwp = lwp_self(); if (lwp) - lwp_exit(lwp, value); + { + lwp_status = LWP_CREATE_STAT_EXIT(value); + lwp_exit(lwp, lwp_status); + } else { LOG_E("Can't find matching process of current thread"); @@ -350,7 +356,10 @@ sysret_t sys_exit(int status) tid = rt_thread_self(); if (tid && tid->lwp) + { lwp_thread_exit(tid, status); + } + else { LOG_E("Can't find matching process of current thread"); rc = -EINVAL; @@ -616,102 +625,9 @@ sysret_t sys_fstat(int file, struct stat *buf) #endif } -/* DFS and lwip definitions */ -#define IMPL_POLLIN (0x01) - -#define IMPL_POLLOUT (0x02) - -#define IMPL_POLLERR (0x04) -#define IMPL_POLLHUP (0x08) -#define IMPL_POLLNVAL (0x10) - -/* musl definitions */ -#define INTF_POLLIN 0x001 -#define INTF_POLLPRI 0x002 -#define INTF_POLLOUT 0x004 -#define INTF_POLLERR 0x008 -#define INTF_POLLHUP 0x010 -#define INTF_POLLNVAL 0x020 -#define INTF_POLLRDNORM 0x040 -#define INTF_POLLRDBAND 0x080 -#define INTF_POLLWRNORM 0x100 -#define INTF_POLLWRBAND 0x200 -#define INTF_POLLMSG 0x400 -#define INTF_POLLRDHUP 0x2000 - -#define INTF_POLLIN_MASK (INTF_POLLIN | INTF_POLLRDNORM | INTF_POLLRDBAND | INTF_POLLPRI) -#define INTF_POLLOUT_MASK (INTF_POLLOUT | INTF_POLLWRNORM | INTF_POLLWRBAND) - -static void musl2dfs_events(short *events) -{ - short origin_e = *events; - short result_e = 0; - - if (origin_e & INTF_POLLIN_MASK) - { - result_e |= IMPL_POLLIN; - } - - if (origin_e & INTF_POLLOUT_MASK) - { - result_e |= IMPL_POLLOUT; - } - - if (origin_e & INTF_POLLERR) - { - result_e |= IMPL_POLLERR; - } - - if (origin_e & INTF_POLLHUP) - { - result_e |= IMPL_POLLHUP; - } - - if (origin_e & INTF_POLLNVAL) - { - result_e |= IMPL_POLLNVAL; - } - - *events = result_e; -} - -static void dfs2musl_events(short *events) -{ - short origin_e = *events; - short result_e = 0; - - if (origin_e & IMPL_POLLIN) - { - result_e |= INTF_POLLIN_MASK; - } - - if (origin_e & IMPL_POLLOUT) - { - result_e |= INTF_POLLOUT_MASK; - } - - if (origin_e & IMPL_POLLERR) - { - result_e |= INTF_POLLERR; - } - - if (origin_e & IMPL_POLLHUP) - { - result_e |= INTF_POLLHUP; - } - - if (origin_e & IMPL_POLLNVAL) - { - result_e |= INTF_POLLNVAL; - } - - *events = result_e; -} - sysret_t sys_poll(struct pollfd *fds, nfds_t nfds, int timeout) { int ret = -1; - int i = 0; #ifdef ARCH_MM_MMU struct pollfd *kfds = RT_NULL; @@ -728,43 +644,20 @@ sysret_t sys_poll(struct pollfd *fds, nfds_t nfds, int timeout) lwp_get_from_user(kfds, fds, nfds * sizeof *kfds); - for (i = 0; i < nfds; i++) - { - musl2dfs_events(&kfds[i].events); - } ret = poll(kfds, nfds, timeout); if (ret > 0) { - for (i = 0; i < nfds; i++) - { - dfs2musl_events(&kfds->revents); - } lwp_put_to_user(fds, kfds, nfds * sizeof *kfds); } kmem_put(kfds); return ret; #else -#ifdef RT_USING_MUSLLIBC - for (i = 0; i < nfds; i++) - { - musl2dfs_events(&fds->events); - } -#endif /* RT_USING_MUSLLIBC */ if (!lwp_user_accessable((void *)fds, nfds * sizeof *fds)) { return -EFAULT; } ret = poll(fds, nfds, timeout); -#ifdef RT_USING_MUSLLIBC - if (ret > 0) - { - for (i = 0; i < nfds; i++) - { - dfs2musl_events(&fds->revents); - } - } -#endif /* RT_USING_MUSLLIBC */ return ret; #endif /* ARCH_MM_MMU */ } @@ -1014,9 +907,9 @@ sysret_t sys_exec(char *filename, int argc, char **argv, char **envp) sysret_t sys_kill(int pid, int signo) { - rt_err_t kret; + rt_err_t kret = 0; sysret_t sysret; - struct rt_lwp *lwp; + struct rt_lwp *lwp = RT_NULL; /* handling the semantics of sys_kill */ if (pid > 0) @@ -1027,31 +920,60 @@ sysret_t sys_kill(int pid, int signo) * - pid tree (READ. since the lwp is fetch from the pid tree, it must stay there) */ lwp_pid_lock_take(); - lwp = lwp_from_pid_locked(pid); + lwp = lwp_from_pid_raw_locked(pid); if (lwp) { lwp_ref_inc(lwp); lwp_pid_lock_release(); - - kret = lwp_signal_kill(lwp, signo, SI_USER, 0); - lwp_ref_dec(lwp); - kret = 0; } else { lwp_pid_lock_release(); kret = -RT_ENOENT; } + + if (lwp) + { + kret = lwp_signal_kill(lwp, signo, SI_USER, 0); + lwp_ref_dec(lwp); + } } - else if (pid == 0) + else if (pid < -1 || pid == 0) { - /** - * sig shall be sent to all processes (excluding an unspecified set - * of system processes) whose process group ID is equal to the process - * group ID of the sender, and for which the process has permission to - * send a signal. - */ - kret = -RT_ENOSYS; + pid_t pgid = 0; + rt_processgroup_t group; + + if (pid == 0) + { + /** + * sig shall be sent to all processes (excluding an unspecified set + * of system processes) whose process group ID is equal to the process + * group ID of the sender, and for which the process has permission to + * send a signal. + */ + pgid = lwp_pgid_get_byprocess(lwp_self()); + } + else + { + /** + * sig shall be sent to all processes (excluding an unspecified set + * of system processes) whose process group ID is equal to the absolute + * value of pid, and for which the process has permission to send a signal. + */ + pgid = -pid; + } + + group = lwp_pgrp_find(pgid); + if (group != RT_NULL) + { + PGRP_LOCK(group); + kret = lwp_pgrp_signal_kill(group, signo, SI_USER, 0); + PGRP_UNLOCK(group); + } + else + { + kret = -ECHILD; + } } else if (pid == -1) { @@ -1062,15 +984,6 @@ sysret_t sys_kill(int pid, int signo) */ kret = -RT_ENOSYS; } - else /* pid < -1 */ - { - /** - * sig shall be sent to all processes (excluding an unspecified set - * of system processes) whose process group ID is equal to the absolute - * value of pid, and for which the process has permission to send a signal. - */ - kret = -RT_ENOSYS; - } switch (kret) { @@ -1102,6 +1015,22 @@ sysret_t sys_getpid(void) return lwp_getpid(); } +sysret_t sys_getppid(void) +{ + rt_lwp_t process; + + process = lwp_self(); + if (process->parent == RT_NULL) + { + LOG_E("%s: process %d has no parent process", __func__, lwp_to_pid(process)); + return 0; + } + else + { + return lwp_to_pid(process->parent); + } +} + /* syscall: "getpriority" ret: "int" args: "int" "id_t" */ sysret_t sys_getpriority(int which, id_t who) { @@ -1112,10 +1041,7 @@ sysret_t sys_getpriority(int which, id_t who) struct rt_lwp *lwp = RT_NULL; lwp_pid_lock_take(); - if(who == 0) - lwp = lwp_self(); - else - lwp = lwp_from_pid_locked(who); + lwp = lwp_from_pid_locked(who); if (lwp) { @@ -1137,10 +1063,7 @@ sysret_t sys_setpriority(int which, id_t who, int prio) struct rt_lwp *lwp = RT_NULL; lwp_pid_lock_take(); - if(who == 0) - lwp = lwp_self(); - else - lwp = lwp_from_pid_locked(who); + lwp = lwp_from_pid_locked(who); if (lwp && prio >= 0 && prio < RT_THREAD_PRIORITY_MAX) { @@ -1317,7 +1240,7 @@ sysret_t sys_munmap(void *addr, size_t length) void *sys_mremap(void *old_address, size_t old_size, size_t new_size, int flags, void *new_address) { - return (void *)-1; + return lwp_mremap(lwp_self(), old_address, old_size, new_size, flags, new_address); } sysret_t sys_madvise(void *addr, size_t len, int behav) @@ -1475,6 +1398,34 @@ sysret_t sys_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout) return ret; } +rt_weak int syslog_ctrl(int type, char *buf, int len) +{ + return -EINVAL; +} + +sysret_t sys_syslog(int type, char *buf, int len) +{ + char *tmp; + int ret = -1; + + if (!lwp_user_accessable((void *)buf, len)) + { + return -EFAULT; + } + + tmp = (char *)rt_malloc(len); + if (!tmp) + { + return -ENOMEM; + } + + ret = syslog_ctrl(type, tmp, len); + lwp_put_to_user(buf, tmp, len); + rt_free(tmp); + + return ret; +} + rt_mq_t sys_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, @@ -2012,13 +1963,21 @@ static void lwp_struct_copy(struct rt_lwp *dst, struct rt_lwp *src) dst->data_entry = src->data_entry; dst->data_size = src->data_size; dst->args = src->args; - dst->leader = 0; - dst->session = src->session; dst->background = src->background; - dst->tty_old_pgrp = 0; - dst->__pgrp = src->__pgrp; dst->tty = src->tty; + + /* terminal API */ + dst->term_ctrlterm = src->term_ctrlterm; + rt_memcpy(dst->cmd, src->cmd, RT_NAME_MAX); + if (src->exe_file) + { + if (dst->exe_file) + { + rt_free(dst->exe_file); + } + dst->exe_file = strndup(src->exe_file, DFS_PATH_MAX); + } rt_memcpy(&dst->signal.sig_action, &src->signal.sig_action, sizeof(dst->signal.sig_action)); rt_memcpy(&dst->signal.sig_action_mask, &src->signal.sig_action_mask, sizeof(dst->signal.sig_action_mask)); @@ -2026,6 +1985,8 @@ static void lwp_struct_copy(struct rt_lwp *dst, struct rt_lwp *src) rt_memcpy(&dst->signal.sig_action_onstack, &src->signal.sig_action_onstack, sizeof(dst->signal.sig_action_onstack)); rt_memcpy(&dst->signal.sig_action_restart, &dst->signal.sig_action_restart, sizeof(dst->signal.sig_action_restart)); rt_memcpy(&dst->signal.sig_action_siginfo, &dst->signal.sig_action_siginfo, sizeof(dst->signal.sig_action_siginfo)); + rt_memcpy(&dst->signal.sig_action_nocldstop, &dst->signal.sig_action_nocldstop, sizeof(dst->signal.sig_action_nocldstop)); + rt_memcpy(&dst->signal.sig_action_nocldwait, &dst->signal.sig_action_nocldwait, sizeof(dst->signal.sig_action_nocldwait)); rt_strcpy(dst->working_directory, src->working_directory); } @@ -2071,6 +2032,7 @@ sysret_t _sys_fork(void) rt_thread_t thread = RT_NULL; rt_thread_t self_thread = RT_NULL; void *user_stack = RT_NULL; + rt_processgroup_t group; /* new lwp */ lwp = lwp_create(LWP_CREATE_FLAG_ALLOC_PID); @@ -2145,6 +2107,17 @@ sysret_t _sys_fork(void) lwp_children_register(self_lwp, lwp); + /* set pgid and sid */ + group = lwp_pgrp_find(lwp_pgid_get_byprocess(self_lwp)); + if (group) + { + lwp_pgrp_insert(group, lwp); + } + else + { + LOG_W("the process group of pid: %d cannot be found", lwp_pgid_get_byprocess(self_lwp)); + } + /* copy kernel stack context from self thread */ lwp_memcpy(thread->stack_addr, self_thread->stack_addr, self_thread->stack_size); lwp_tid_set_thread(tid, thread); @@ -2157,28 +2130,6 @@ sysret_t _sys_fork(void) (void *)((char *)thread->stack_addr + thread->stack_size), user_stack, &thread->sp); - /* new thread never reach there */ - LWP_LOCK(lwp); - if (lwp->tty != RT_NULL) - { - int ret; - struct rt_lwp *old_lwp; - - old_lwp = lwp->tty->foreground; - rt_mutex_take(&lwp->tty->lock, RT_WAITING_FOREVER); - ret = tty_push(&lwp->tty->head, old_lwp); - rt_mutex_release(&lwp->tty->lock); - if (ret < 0) - { - LOG_E("malloc fail!\n"); - SET_ERRNO(ENOMEM); - goto fail; - } - - lwp->tty->foreground = lwp; - } - LWP_UNLOCK(lwp); - rt_thread_startup(thread); return lwp_to_pid(lwp); fail: @@ -2246,7 +2197,7 @@ static char *_insert_args(int new_argc, char *new_argv[], struct lwp_args_info * nsize = new_argc * sizeof(char *); for (i = 0; i < new_argc; i++) { - nsize += lwp_user_strlen(new_argv[i]) + 1; + nsize += lwp_strlen(lwp_self(), new_argv[i]) + 1; } if (nsize + args->size > ARCH_PAGE_SIZE) { @@ -2260,7 +2211,7 @@ static char *_insert_args(int new_argc, char *new_argv[], struct lwp_args_info * for (i = 0; i < new_argc; i++) { nargv[i] = p; - len = lwp_user_strlen(new_argv[i]) + 1; + len = lwp_strlen(lwp_self(), new_argv[i]) + 1; lwp_memcpy(p, new_argv[i], len); p += len; } @@ -2269,7 +2220,7 @@ static char *_insert_args(int new_argc, char *new_argv[], struct lwp_args_info * for (i = 0; i < args->argc; i++) { nargv[i] = p; - len = lwp_user_strlen(args->argv[i]) + 1; + len = lwp_strlen(lwp_self(), args->argv[i]) + 1; lwp_memcpy(p, args->argv[i], len); p += len; } @@ -2278,7 +2229,7 @@ static char *_insert_args(int new_argc, char *new_argv[], struct lwp_args_info * for (i = 0; i < args->envc; i++) { nenvp[i] = p; - len = lwp_user_strlen(args->envp[i]) + 1; + len = lwp_strlen(lwp_self(), args->envp[i]) + 1; lwp_memcpy(p, args->envp[i], len); p += len; } @@ -2427,7 +2378,7 @@ int load_ldso(struct rt_lwp *lwp, char *exec_name, char *const argv[], char *con { break; } - len = lwp_user_strlen_ext(lwp, (const char *)argv[argc]); + len = lwp_strlen(lwp, (const char *)argv[argc]); size += sizeof(char *) + len + 1; argc++; } @@ -2440,7 +2391,7 @@ int load_ldso(struct rt_lwp *lwp, char *exec_name, char *const argv[], char *con { break; } - len = lwp_user_strlen_ext(lwp, (const char *)envp[envc]); + len = lwp_strlen(lwp, (const char *)envp[envc]); size += sizeof(char *) + len + 1; envc++; } @@ -2461,7 +2412,7 @@ int load_ldso(struct rt_lwp *lwp, char *exec_name, char *const argv[], char *con for (i = 0; i < argc; i++) { kargv[i] = p; - len = lwp_user_strlen_ext(lwp, argv[i]) + 1; + len = lwp_strlen(lwp, argv[i]) + 1; lwp_memcpy(p, argv[i], len); p += len; } @@ -2473,7 +2424,7 @@ int load_ldso(struct rt_lwp *lwp, char *exec_name, char *const argv[], char *con for (i = 0; i < envc; i++) { kenvp[i] = p; - len = lwp_user_strlen_ext(lwp, envp[i]) + 1; + len = lwp_strlen(lwp, envp[i]) + 1; lwp_memcpy(p, envp[i], len); p += len; } @@ -2523,7 +2474,7 @@ int load_ldso(struct rt_lwp *lwp, char *exec_name, char *const argv[], char *con ret = lwp_load("/lib/ld.so", lwp, RT_NULL, 0, aux); - rt_strncpy(lwp->cmd, exec_name, RT_NAME_MAX); + rt_strncpy(lwp->cmd, exec_name, RT_NAME_MAX - 1); quit: if (page) { @@ -2751,6 +2702,8 @@ sysret_t sys_execve(const char *path, char *const argv[], char *const envp[]) strncpy(thread->parent.name, run_name + last_backslash, RT_NAME_MAX); strncpy(lwp->cmd, new_lwp->cmd, RT_NAME_MAX); + rt_free(lwp->exe_file); + lwp->exe_file = strndup(new_lwp->exe_file, DFS_PATH_MAX); rt_pages_free(page, 0); @@ -4412,7 +4365,7 @@ sysret_t sys_waitpid(int32_t pid, int *status, int options) } else { - ret = lwp_waitpid(pid, status, options); + ret = lwp_waitpid(pid, status, options, RT_NULL); } #else if (!lwp_user_accessable((void *)status, sizeof(int))) @@ -4715,7 +4668,7 @@ sysret_t sys_gethostbyname2_r(const char *name, int af, struct hostent *ret, rt_strncpy(ptr, k_name, buflen - (ptr - buf)); ret->h_name = ptr; - ptr += lwp_user_strlen(k_name); + ptr += strlen(k_name); ret->h_addr_list = (char**)ptr; ptr += cnt * sizeof(char *); @@ -4733,6 +4686,10 @@ sysret_t sys_gethostbyname2_r(const char *name, int af, struct hostent *ret, #endif ret_val = 0; } + else + { + SET_ERRNO(EINVAL); + } __exit: if (ret_val < 0) @@ -4794,6 +4751,7 @@ sysret_t sys_chdir(const char *path) #ifdef ARCH_MM_MMU int err = 0; int len = 0; + int errcode; char *kpath = RT_NULL; len = lwp_user_strlen(path); @@ -4815,16 +4773,46 @@ sysret_t sys_chdir(const char *path) } err = chdir(kpath); + errcode = err != 0 ? GET_ERRNO() : 0; kmem_put(kpath); - return (err < 0 ? GET_ERRNO() : err); + return errcode; #else int ret = chdir(path); return (ret < 0 ? GET_ERRNO() : ret); #endif } +sysret_t sys_fchdir(int fd) +{ + int errcode = -ENOSYS; +#ifdef ARCH_MM_MMU +#ifdef RT_USING_DFS_V2 + int err = -1; + struct dfs_file *d; + char *kpath; + + d = fd_get(fd); + if (!d || !d->vnode) + { + return -EBADF; + } + kpath = dfs_dentry_full_path(d->dentry); + if (!kpath) + { + return -EACCES; + } + + err = chdir(kpath); + errcode = err != 0 ? GET_ERRNO() : 0; + + kmem_put(kpath); +#endif +#endif + return errcode; +} + sysret_t sys_mkdir(const char *path, mode_t mode) { #ifdef ARCH_MM_MMU @@ -5055,6 +5043,11 @@ sysret_t sys_pipe(int fd[2]) return (ret < 0 ? GET_ERRNO() : ret); } +sysret_t sys_wait4(pid_t pid, int *status, int options, struct rusage *ru) +{ + return lwp_waitpid(pid, status, options, ru); +} + sysret_t sys_clock_settime(clockid_t clk, const struct timespec *ts) { int ret = 0; @@ -5271,7 +5264,7 @@ sysret_t sys_getrlimit(unsigned int resource, unsigned long rlim[2]) } lwp_put_to_user((void *)rlim, krlim, sizeof(unsigned long [2])); - kmem_put(krlim); + return (ret < 0 ? GET_ERRNO() : ret); } @@ -5280,13 +5273,6 @@ sysret_t sys_setrlimit(unsigned int resource, struct rlimit *rlim) return -ENOSYS; } -sysret_t sys_setsid(void) -{ - int ret = 0; - ret = setsid(); - return (ret < 0 ? GET_ERRNO() : ret); -} - sysret_t sys_getrandom(void *buf, size_t buflen, unsigned int flags) { int ret = -1; @@ -5353,12 +5339,16 @@ sysret_t sys_getrandom(void *buf, size_t buflen, unsigned int flags) return ret; } +/** + * readlink() places the contents of the symbolic link pathname in the buffer buf, which has size bufsiz. + * readlink() does not append a null byte to buf. + * It will (silently) truncate the contents(to a length of bufsiz characters), + * in case the buffer is too small to hold all of the contents. + */ ssize_t sys_readlink(char* path, char *buf, size_t bufsz) { size_t len, copy_len; int err, rtn; - int fd = -1; - struct dfs_file *d; char *copy_path; len = lwp_user_strlen(path); @@ -5381,91 +5371,27 @@ ssize_t sys_readlink(char* path, char *buf, size_t bufsz) copy_len = lwp_get_from_user(copy_path, path, len); copy_path[copy_len] = '\0'; - /* musl __procfdname */ - err = sscanf(copy_path, "/proc/self/fd/%d", &fd); - - if (err != 1) + char *link_fn = (char *)rt_malloc(DFS_PATH_MAX); + if (link_fn) { - rtn = 0; - if (access(copy_path, 0)) + err = dfs_file_readlink(copy_path, link_fn, DFS_PATH_MAX); + if (err > 0) { - rtn = -ENOENT; - LOG_E("readlink: path not is /proc/self/fd/* and path not exits, call by musl __procfdname()?"); + rtn = lwp_put_to_user(buf, link_fn, bufsz > err ? err : bufsz - 1); } else { -#ifdef RT_USING_DFS_V2 - char *link_fn = (char *)rt_malloc(DFS_PATH_MAX); - if (link_fn) - { - err = dfs_file_readlink(copy_path, link_fn, DFS_PATH_MAX); - if (err > 0) - { - rtn = lwp_put_to_user(buf, link_fn, bufsz > err ? err : bufsz - 1); - } - else - { - rtn = -EIO; - } - rt_free(link_fn); - } - else - { - rtn = -ENOMEM; - } -#else - rtn = lwp_put_to_user(buf, copy_path, copy_len); -#endif + rtn = -EIO; } - rt_free(copy_path); - return rtn; + rt_free(link_fn); } else { - rt_free(copy_path); - } - - d = fd_get(fd); - if (!d) - { - return -EBADF; - } - - if (!d->vnode) - { - return -EBADF; + rtn = -ENOMEM; } -#ifdef RT_USING_DFS_V2 - { - char *fullpath = dfs_dentry_full_path(d->dentry); - if (fullpath) - { - copy_len = strlen(fullpath); - if (copy_len > bufsz) - { - copy_len = bufsz; - } - - bufsz = lwp_put_to_user(buf, fullpath, copy_len); - rt_free(fullpath); - } - else - { - bufsz = 0; - } - } -#else - copy_len = strlen(d->vnode->fullpath); - if (copy_len > bufsz) - { - copy_len = bufsz; - } - - bufsz = lwp_put_to_user(buf, d->vnode->fullpath, copy_len); -#endif - - return bufsz; + rt_free(copy_path); + return rtn; } sysret_t sys_sched_setaffinity(pid_t pid, size_t size, void *set) @@ -5511,7 +5437,6 @@ sysret_t sys_sched_getaffinity(const pid_t pid, size_t size, void *set) LWP_DEF_RETURN_CODE(rc); void *mask; struct rt_lwp *lwp; - rt_bool_t need_release = RT_FALSE; if (size <= 0 || size > sizeof(cpu_set_t)) { @@ -5529,16 +5454,8 @@ sysret_t sys_sched_getaffinity(const pid_t pid, size_t size, void *set) CPU_ZERO_S(size, mask); - if (pid == 0) - { - lwp = lwp_self(); - } - else - { - need_release = RT_TRUE; - lwp_pid_lock_take(); - lwp = lwp_from_pid_locked(pid); - } + lwp_pid_lock_take(); + lwp = lwp_from_pid_locked(pid); if (!lwp) { @@ -5569,8 +5486,7 @@ sysret_t sys_sched_getaffinity(const pid_t pid, size_t size, void *set) rc = size; } - if (need_release) - lwp_pid_lock_release(); + lwp_pid_lock_release(); kmem_put(mask); @@ -5626,12 +5542,10 @@ sysret_t sys_sysinfo(void *info) #endif } -sysret_t sys_sched_setparam(pid_t pid, void *param) +sysret_t sys_sched_setparam(pid_t tid, void *param) { struct sched_param *sched_param = RT_NULL; - struct rt_lwp *lwp = NULL; - rt_thread_t main_thread; - rt_bool_t need_release = RT_FALSE; + rt_thread_t thread; int ret = -1; if (!lwp_user_accessable(param, sizeof(struct sched_param))) @@ -5651,26 +5565,15 @@ sysret_t sys_sched_setparam(pid_t pid, void *param) return -EINVAL; } - if (pid > 0) - { - need_release = RT_TRUE; - lwp_pid_lock_take(); - lwp = lwp_from_pid_locked(pid); - } - else if (pid == 0) - { - lwp = lwp_self(); - } + thread = lwp_tid_get_thread_and_inc_ref(tid); - if (lwp) + if (thread) { - main_thread = rt_list_entry(lwp->t_grp.prev, struct rt_thread, sibling); - if (need_release) - lwp_pid_lock_release(); - - ret = rt_thread_control(main_thread, RT_THREAD_CTRL_CHANGE_PRIORITY, (void *)&sched_param->sched_priority); + ret = rt_thread_control(thread, RT_THREAD_CTRL_CHANGE_PRIORITY, (void *)&sched_param->sched_priority); } + lwp_tid_dec_ref(thread); + kmem_put(sched_param); return ret; @@ -5767,37 +5670,24 @@ sysret_t sys_sched_setscheduler(int tid, int policy, void *param) return ret; } -sysret_t sys_sched_getscheduler(int tid, int *policy, void *param) +sysret_t sys_sched_getscheduler(int tid) { - struct sched_param *sched_param = RT_NULL; rt_thread_t thread = RT_NULL; + int rtn; - if (!lwp_user_accessable(param, sizeof(struct sched_param))) - { - return -EFAULT; - } + thread = lwp_tid_get_thread_and_inc_ref(tid); + lwp_tid_dec_ref(thread); - sched_param = kmem_get(sizeof(struct sched_param)); - if (sched_param == RT_NULL) + if (thread) { - return -ENOMEM; + rtn = SCHED_RR; } - - if (lwp_get_from_user(sched_param, param, sizeof(struct sched_param)) != sizeof(struct sched_param)) + else { - kmem_put(sched_param); - return -EINVAL; + rtn = -ESRCH; } - thread = lwp_tid_get_thread_and_inc_ref(tid); - sched_param->sched_priority = RT_SCHED_PRIV(thread).current_priority; - lwp_tid_dec_ref(thread); - - lwp_put_to_user((void *)param, sched_param, sizeof(struct sched_param)); - kmem_put(sched_param); - - *policy = 0; - return 0; + return rtn; } sysret_t sys_fsync(int fd) @@ -6409,24 +6299,31 @@ sysret_t sys_epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) int ret = 0; struct epoll_event *kev = RT_NULL; - if (!lwp_user_accessable((void *)ev, sizeof(struct epoll_event))) + if (ev) + { + if (!lwp_user_accessable((void *)ev, sizeof(struct epoll_event))) return -EFAULT; - kev = kmem_get(sizeof(struct epoll_event)); - if (kev == RT_NULL) - { - return -ENOMEM; - } + kev = kmem_get(sizeof(struct epoll_event)); + if (kev == RT_NULL) + { + return -ENOMEM; + } - if (lwp_get_from_user(kev, ev, sizeof(struct epoll_event)) != sizeof(struct epoll_event)) - { - kmem_put(kev); - return -EINVAL; - } + if (lwp_get_from_user(kev, ev, sizeof(struct epoll_event)) != sizeof(struct epoll_event)) + { + kmem_put(kev); + return -EINVAL; + } - ret = epoll_ctl(fd, op, fd2, kev); + ret = epoll_ctl(fd, op, fd2, kev); - kmem_put(kev); + kmem_put(kev); + } + else + { + ret = epoll_ctl(fd, op, fd2, RT_NULL); + } return (ret < 0 ? GET_ERRNO() : ret); } @@ -6537,45 +6434,93 @@ sysret_t sys_utimensat(int __fd, const char *__path, const struct timespec __tim #endif } -sysret_t sys_chmod(const char *fileName, mode_t mode) +sysret_t sys_chmod(const char *pathname, mode_t mode) { - char *copy_fileName; - size_t len_fileName, copy_len_fileName; -#ifdef RT_USING_DFS_V2 - struct dfs_attr attr; + char *copy_file; + size_t len_file, copy_len_file; + struct dfs_attr attr = {0}; + int ret = 0; + + len_file = lwp_user_strlen(pathname); + if (len_file <= 0) + { + return -EFAULT; + } + + copy_file = (char*)rt_malloc(len_file + 1); + if (!copy_file) + { + return -ENOMEM; + } + + copy_len_file = lwp_get_from_user(copy_file, (void *)pathname, len_file); + attr.st_mode = mode; -#endif + attr.ia_valid |= ATTR_MODE_SET; + + copy_file[copy_len_file] = '\0'; + ret = dfs_file_setattr(copy_file, &attr); + rt_free(copy_file); + + return (ret < 0 ? GET_ERRNO() : ret); +} + +sysret_t sys_chown(const char *pathname, uid_t owner, gid_t group) +{ + char *copy_file; + size_t len_file, copy_len_file; + struct dfs_attr attr = {0}; int ret = 0; - len_fileName = lwp_user_strlen(fileName); - if (len_fileName <= 0) + len_file = lwp_user_strlen(pathname); + if (len_file <= 0) { return -EFAULT; } - copy_fileName = (char*)rt_malloc(len_fileName + 1); - if (!copy_fileName) + copy_file = (char*)rt_malloc(len_file + 1); + if (!copy_file) { return -ENOMEM; } - copy_len_fileName = lwp_get_from_user(copy_fileName, (void *)fileName, len_fileName); - copy_fileName[copy_len_fileName] = '\0'; -#ifdef RT_USING_DFS_V2 - ret = dfs_file_setattr(copy_fileName, &attr); -#else - SET_ERRNO(EFAULT); -#endif - rt_free(copy_fileName); + copy_len_file = lwp_get_from_user(copy_file, (void *)pathname, len_file); + + if(owner >= 0) + { + attr.st_uid = owner; + attr.ia_valid |= ATTR_UID_SET; + } + + if(group >= 0) + { + attr.st_gid = group; + attr.ia_valid |= ATTR_GID_SET; + } + + copy_file[copy_len_file] = '\0'; + ret = dfs_file_setattr(copy_file, &attr); + rt_free(copy_file); return (ret < 0 ? GET_ERRNO() : ret); } -sysret_t sys_reboot(int magic) +#include +sysret_t sys_reboot(int magic, int magic2, int type) { - rt_hw_cpu_reset(); + sysret_t rc; + switch (type) + { + /* TODO add software poweroff protocols */ + case RB_AUTOBOOT: + case RB_POWER_OFF: + rt_hw_cpu_reset(); + break; + default: + rc = -ENOSYS; + } - return 0; + return rc; } ssize_t sys_pread64(int fd, void *buf, int size, size_t offset) @@ -6824,6 +6769,59 @@ sysret_t sys_setitimer(int which, const struct itimerspec *restrict new, struct return rc; } +sysret_t sys_set_robust_list(struct robust_list_head *head, size_t len) +{ + if (len != sizeof(*head)) + return -EINVAL; + + rt_thread_self()->robust_list = head; + return 0; +} + +sysret_t sys_get_robust_list(int tid, struct robust_list_head **head_ptr, size_t *len_ptr) +{ + rt_thread_t thread; + size_t len; + struct robust_list_head *head; + + if (!lwp_user_accessable((void *)head_ptr, sizeof(struct robust_list_head *))) + { + return -EFAULT; + } + if (!lwp_user_accessable((void *)len_ptr, sizeof(size_t))) + { + return -EFAULT; + } + + if (tid == 0) + { + thread = rt_thread_self(); + head = thread->robust_list; + } + else + { + thread = lwp_tid_get_thread_and_inc_ref(tid); + if (thread) + { + head = thread->robust_list; + lwp_tid_dec_ref(thread); + } + else + { + return -ESRCH; + } + } + + len = sizeof(*(head)); + + if (!lwp_put_to_user(len_ptr, &len, sizeof(size_t))) + return -EFAULT; + if (!lwp_put_to_user(head_ptr, &head, sizeof(struct robust_list_head *))) + return -EFAULT; + + return 0; +} + const static struct rt_syscall_def func_table[] = { SYSCALL_SIGN(sys_exit), /* 01 */ @@ -6994,22 +6992,22 @@ const static struct rt_syscall_def func_table[] = SYSCALL_SIGN(sys_clock_settime), SYSCALL_SIGN(sys_clock_gettime), SYSCALL_SIGN(sys_clock_getres), - SYSCALL_USPACE(SYSCALL_SIGN(sys_clone)), /* 130 */ + SYSCALL_USPACE(SYSCALL_SIGN(sys_clone)), /* 130 */ SYSCALL_USPACE(SYSCALL_SIGN(sys_futex)), - SYSCALL_USPACE(SYSCALL_SIGN(sys_pmutex)), + SYSCALL_SIGN(sys_notimpl), /* discarded: sys_pmutex */ SYSCALL_SIGN(sys_dup), SYSCALL_SIGN(sys_dup2), - SYSCALL_SIGN(sys_rename), /* 135 */ + SYSCALL_SIGN(sys_rename), /* 135 */ SYSCALL_USPACE(SYSCALL_SIGN(sys_fork)), SYSCALL_USPACE(SYSCALL_SIGN(sys_execve)), SYSCALL_USPACE(SYSCALL_SIGN(sys_vfork)), SYSCALL_SIGN(sys_gettid), - SYSCALL_SIGN(sys_prlimit64), /* 140 */ + SYSCALL_SIGN(sys_prlimit64), /* 140 */ SYSCALL_SIGN(sys_getrlimit), SYSCALL_SIGN(sys_setrlimit), SYSCALL_SIGN(sys_setsid), SYSCALL_SIGN(sys_getrandom), - SYSCALL_SIGN(sys_readlink), // SYSCALL_SIGN(sys_readlink) /* 145 */ + SYSCALL_SIGN(sys_readlink), /* 145 */ SYSCALL_USPACE(SYSCALL_SIGN(sys_mremap)), SYSCALL_USPACE(SYSCALL_SIGN(sys_madvise)), SYSCALL_SIGN(sys_sched_setparam), @@ -7044,7 +7042,7 @@ const static struct rt_syscall_def func_table[] = SYSCALL_SIGN(sys_umount2), SYSCALL_SIGN(sys_link), SYSCALL_SIGN(sys_symlink), - SYSCALL_SIGN(sys_sched_getaffinity), /* 180 */ + SYSCALL_SIGN(sys_sched_getaffinity), /* 180 */ SYSCALL_SIGN(sys_sysinfo), SYSCALL_SIGN(sys_chmod), SYSCALL_SIGN(sys_reboot), @@ -7069,12 +7067,21 @@ const static struct rt_syscall_def func_table[] = SYSCALL_SIGN(sys_setitimer), SYSCALL_SIGN(sys_utimensat), #ifdef RT_USING_POSIX_SOCKET - SYSCALL_SIGN(sys_notimpl), - SYSCALL_SIGN(sys_socketpair), /* 205 */ + SYSCALL_SIGN(sys_syslog), + SYSCALL_SIGN(sys_socketpair), /* 205 */ #else SYSCALL_SIGN(sys_notimpl), - SYSCALL_SIGN(sys_notimpl), + SYSCALL_SIGN(sys_notimpl), /* 205 */ #endif + SYSCALL_SIGN(sys_wait4), + SYSCALL_SIGN(sys_set_robust_list), + SYSCALL_SIGN(sys_get_robust_list), + SYSCALL_SIGN(sys_setpgid), + SYSCALL_SIGN(sys_getpgid), /* 210 */ + SYSCALL_SIGN(sys_getsid), + SYSCALL_SIGN(sys_getppid), + SYSCALL_SIGN(sys_fchdir), + SYSCALL_SIGN(sys_chown), }; const void *lwp_get_sys_api(rt_uint32_t number) diff --git a/components/lwp/lwp_syscall.h b/components/lwp/lwp_syscall.h index b7e7b09f55..1527a764f1 100644 --- a/components/lwp/lwp_syscall.h +++ b/components/lwp/lwp_syscall.h @@ -108,10 +108,15 @@ sysret_t sys_log(const char* log, int size); #ifdef ARCH_MM_MMU sysret_t sys_futex(int *uaddr, int op, int val, const struct timespec *timeout, int *uaddr2, int val3); -sysret_t sys_pmutex(void *umutex, int op, void *arg); sysret_t sys_cacheflush(void *addr, int len, int cache); #endif /* ARCH_MM_MMU */ +sysret_t sys_setsid(void); +sysret_t sys_getsid(pid_t pid); +sysret_t sys_setpgid(pid_t pid, pid_t pgid); +sysret_t sys_getpgid(pid_t pid); + + #ifdef __cplusplus } #endif diff --git a/components/lwp/lwp_tid.c b/components/lwp/lwp_tid.c index fdac54a36f..3e330027de 100644 --- a/components/lwp/lwp_tid.c +++ b/components/lwp/lwp_tid.c @@ -6,6 +6,7 @@ * Change Logs: * Date Author Notes * 2021-01-15 shaojinchun first version + * 2023-11-16 xqyjlj Fix the case where tid is 0 */ #define DBG_TAG "lwp.tid" @@ -126,20 +127,28 @@ void lwp_tid_put(int tid) lwp_mutex_release_safe(&tid_lock); } -rt_thread_t lwp_tid_get_thread_and_inc_ref(int tid) +rt_thread_t lwp_tid_get_thread_raw(int tid) { struct lwp_avl_struct *p; rt_thread_t thread = RT_NULL; - lwp_mutex_take_safe(&tid_lock, RT_WAITING_FOREVER, 0); p = lwp_avl_find(tid, lwp_tid_root); if (p) { thread = (rt_thread_t)p->data; - if (thread != RT_NULL) - { - thread->tid_ref_count += 1; - } + } + return thread; +} + +rt_thread_t lwp_tid_get_thread_and_inc_ref(int tid) +{ + rt_thread_t thread = RT_NULL; + + lwp_mutex_take_safe(&tid_lock, RT_WAITING_FOREVER, 0); + thread = tid ? lwp_tid_get_thread_raw(tid) : rt_thread_self(); + if (thread != RT_NULL) + { + thread->tid_ref_count += 1; } lwp_mutex_release_safe(&tid_lock); return thread; diff --git a/components/lwp/lwp_user_mm.c b/components/lwp/lwp_user_mm.c index 3c23e59e05..084b5bcf28 100644 --- a/components/lwp/lwp_user_mm.c +++ b/components/lwp/lwp_user_mm.c @@ -23,10 +23,7 @@ #ifdef ARCH_MM_MMU -#include -#include -#include -#include +#include "lwp_internal.h" #include #include @@ -161,10 +158,10 @@ void lwp_aspace_switch(struct rt_thread *thread) void lwp_unmap_user_space(struct rt_lwp *lwp) { - arch_user_space_free(lwp); + if (lwp->aspace) + arch_user_space_free(lwp); } - static void *_lwp_map_user(struct rt_lwp *lwp, void *map_va, size_t map_size, int text) { @@ -566,6 +563,14 @@ int lwp_munmap(struct rt_lwp *lwp, void *addr, size_t length) return lwp_errno_to_posix(ret); } +void *lwp_mremap(struct rt_lwp *lwp, void *old_address, size_t old_size, + size_t new_size, int flags, void *new_address) +{ + RT_ASSERT(lwp); + + return rt_aspace_mremap_range(lwp->aspace, old_address, old_size, new_size, flags, new_address); +} + size_t lwp_get_from_user(void *dst, void *src, size_t size) { struct rt_lwp *lwp = RT_NULL; @@ -621,11 +626,6 @@ size_t lwp_put_to_user(void *dst, void *src, size_t size) return lwp_data_put(lwp, dst, src, size); } -rt_inline rt_bool_t _in_user_space(const char *addr) -{ - return (addr >= (char *)USER_VADDR_START && addr < (char *)USER_VADDR_TOP); -} - rt_inline rt_bool_t _can_unaligned_access(const char *addr) { return rt_kmem_v2p((char *)addr) - PV_OFFSET == addr; @@ -636,9 +636,9 @@ void *lwp_memcpy(void * __restrict dst, const void * __restrict src, size_t size void *rc = dst; long len; - if (_in_user_space(dst)) + if (lwp_in_user_space(dst)) { - if (!_in_user_space(src)) + if (!lwp_in_user_space(src)) { len = lwp_put_to_user(dst, (void *)src, size); if (!len) @@ -654,7 +654,7 @@ void *lwp_memcpy(void * __restrict dst, const void * __restrict src, size_t size } else { - if (_in_user_space(src)) + if (lwp_in_user_space(src)) { len = lwp_get_from_user(dst, (void *)src, size); if (!len) @@ -979,6 +979,13 @@ size_t lwp_user_strlen(const char *s) return lwp_user_strlen_ext(lwp, s); } +size_t lwp_strlen(struct rt_lwp *lwp, const char *s) +{ + if (lwp_in_user_space(s)) + return lwp_user_strlen_ext(lwp, s); + else + return strlen(s); +} char** lwp_get_command_line_args(struct rt_lwp *lwp) { diff --git a/components/lwp/lwp_user_mm.h b/components/lwp/lwp_user_mm.h index cfc5b57152..232038c914 100644 --- a/components/lwp/lwp_user_mm.h +++ b/components/lwp/lwp_user_mm.h @@ -56,6 +56,9 @@ void* lwp_mmap2(struct rt_lwp *lwp, void *addr, size_t length, int prot, int fla */ int lwp_munmap(struct rt_lwp *lwp, void *addr, size_t length); +void *lwp_mremap(struct rt_lwp *lwp, void *old_address, size_t old_size, + size_t new_size, int flags, void *new_address); + /** * @brief Test if address from user is accessible address by user * @@ -163,6 +166,7 @@ rt_base_t lwp_brk(void *addr); size_t lwp_user_strlen(const char *s); size_t lwp_user_strlen_ext(struct rt_lwp *lwp, const char *s); +size_t lwp_strlen(struct rt_lwp *lwp, const char *s); int lwp_fork_aspace(struct rt_lwp *dest_lwp, struct rt_lwp *src_lwp); diff --git a/components/lwp/terminal/Kconfig b/components/lwp/terminal/Kconfig new file mode 100644 index 0000000000..7fd77e7505 --- /dev/null +++ b/components/lwp/terminal/Kconfig @@ -0,0 +1,13 @@ +menuconfig LWP_USING_TERMINAL + bool "Terminal I/O Subsystem" + depends on RT_USING_SMART + default y + +if LWP_USING_TERMINAL + config LWP_PTY_MAX_PARIS_LIMIT + int "Max number of pty devices registered at the same time" + default 64 + help + This upper limit is set to protect kernel memory from draining + out by the application if it keeps allocating pty devices. +endif diff --git a/components/lwp/terminal/bsd_porting.h b/components/lwp/terminal/bsd_porting.h new file mode 100644 index 0000000000..fba8f47fb3 --- /dev/null +++ b/components/lwp/terminal/bsd_porting.h @@ -0,0 +1,741 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell Add compatible layer for FreeBSD + */ + +#ifndef __LWP_TTY_BSD_PORTING_H__ +#define __LWP_TTY_BSD_PORTING_H__ + +#include +#include +#define _KERNEL + +#ifndef __unused +#define __unused __attribute__((__unused__)) +#endif + +/* functionability of bsd tty layer */ +#if 0 +#define USING_BSD_HOOK +#endif + +/* Only for devfs d_close() flags. */ +#define FLASTCLOSE O_DIRECTORY +#define FREVOKE 0x00200000 + +/* + * Output flags - software output processing + */ +#if !((OPOST | OLCUC | ONLCR) & 0x8) +#define ONOEOT 0x0008 /* discard EOT's (^D) on output) */ +#endif + +/* + * Kernel encoding of open mode; separate read and write bits that are + * independently testable: 1 greater than the above. + * + * XXX + * FREAD and FWRITE are excluded from the #ifdef _KERNEL so that TIOCFLUSH, + * which was documented to use FREAD/FWRITE, continues to work. + */ +#define FREAD 0x0001 +#define FWRITE 0x0002 + +/* + * Flags to memory allocation functions. + */ +#define M_NOWAIT 0x0001 /* do not block */ +#define M_WAITOK 0x0002 /* ok to block */ +#define M_NORECLAIM 0x0080 /* do not reclaim after failure */ +#define M_ZERO 0x0100 /* bzero the allocation */ +#define M_NOVM 0x0200 /* don't ask VM for pages */ +#define M_USE_RESERVE 0x0400 /* can alloc out of reserve memory */ +#define M_NODUMP 0x0800 /* don't dump pages in this allocation */ +#define M_FIRSTFIT 0x1000 /* only for vmem, fast fit */ +#define M_BESTFIT 0x2000 /* only for vmem, low fragmentation */ +#define M_EXEC 0x4000 /* allocate executable space */ +#define M_NEXTFIT 0x8000 /* only for vmem, follow cursor */ + +#define M_VERSION 2020110501 + +/* + * The INVARIANTS-enabled mtx_assert() functionality. + * + * The constants need to be defined for INVARIANT_SUPPORT infrastructure + * support as _mtx_assert() itself uses them and the latter implies that + * _mtx_assert() must build. + */ +#define MA_OWNED (1) +#define MA_NOTOWNED (2) +#define MA_RECURSED (4) +#define MA_NOTRECURSED (8) + +/* + * Indentification of modem control signals. These definitions match + * the TIOCMGET definitions in shifted a bit down, and + * that identity is enforced with CTASSERT at the bottom of kern/tty.c + * Both the modem bits and delta bits must fit in 16 bit. + */ +#define SER_DTR 0x0001 /* data terminal ready */ +#define SER_RTS 0x0002 /* request to send */ +#define SER_STX 0x0004 /* secondary transmit */ +#define SER_SRX 0x0008 /* secondary receive */ +#define SER_CTS 0x0010 /* clear to send */ +#define SER_DCD 0x0020 /* data carrier detect */ +#define SER_RI 0x0040 /* ring indicate */ +#define SER_DSR 0x0080 /* data set ready */ + +#define SER_MASK_STATE 0x00ff + +/* + * Flags for ioflag. (high 16 bits used to ask for read-ahead and + * help with write clustering) + * NB: IO_NDELAY and IO_DIRECT are linked to fcntl.h + */ +#if 0 +#define IO_UNIT 0x0001 /* do I/O as atomic unit */ +#define IO_APPEND 0x0002 /* append write to end */ +#endif /* not porting */ + +#define IO_NDELAY 0x0004 /* FNDELAY flag set in file table */ +#if 0 +#define IO_NODELOCKED 0x0008 /* underlying node already locked */ +#define IO_ASYNC 0x0010 /* bawrite rather then bdwrite */ +#define IO_VMIO 0x0020 /* data already in VMIO space */ +#define IO_INVAL 0x0040 /* invalidate after I/O */ +#define IO_SYNC 0x0080 /* do I/O synchronously */ +#define IO_DIRECT 0x0100 /* attempt to bypass buffer cache */ +#define IO_NOREUSE 0x0200 /* VMIO data won't be reused */ +#define IO_EXT 0x0400 /* operate on external attributes */ +#define IO_NORMAL 0x0800 /* operate on regular data */ +#define IO_NOMACCHECK 0x1000 /* MAC checks unnecessary */ +#define IO_BUFLOCKED 0x2000 /* ffs flag; indir buf is locked */ +#define IO_RANGELOCKED 0x4000 /* range locked */ +#define IO_DATASYNC 0x8000 /* do only data I/O synchronously */ + +#define IO_SEQMAX 0x7F /* seq heuristic max value */ +#define IO_SEQSHIFT 16 /* seq heuristic in upper 16 bits */ +#endif /* not porting */ + +/** Used to distinguish between normal, callout, lock and init devices. + * Note: this is not used in smart system. + */ +#define TTYUNIT_INIT 0x1 +#define TTYUNIT_LOCK 0x2 +#define TTYUNIT_CALLOUT 0x4 + +/* + * TTY privileges. + */ +#define PRIV_TTY_CONSOLE 250 /* Set console to tty. */ +#define PRIV_TTY_DRAINWAIT 251 /* Set tty drain wait time. */ +#define PRIV_TTY_DTRWAIT 252 /* Set DTR wait on tty. */ +#define PRIV_TTY_EXCLUSIVE 253 /* Override tty exclusive flag. */ +#define _PRIV_TTY_PRISON 254 /* Removed. */ +#define PRIV_TTY_STI 255 /* Simulate input on another tty. */ +#define PRIV_TTY_SETA 256 /* Set tty termios structure. */ + +#define MPASS(ex) RT_ASSERT(ex) + +#if !defined(MIN) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#if !defined(MAX) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define curthread rt_thread_self() + +#ifdef USING_BSD_HOOK +#define ttyhook_hashook(tp, hook) \ + ((tp)->t_hook != NULL && (tp)->t_hook->th_##hook != NULL) +#else +#define ttyhook_hashook(tp, hook) (RT_FALSE) +#endif + +/* condvar API */ +#include + +#define cv_init(cvp, name) rt_condvar_init(cvp, name) +#define cv_destroy(cvp) rt_condvar_detach(cvp) +#define cv_wait(cvp, mp) \ + rt_condvar_timedwait(cvp, mp, RT_KILLABLE, RT_WAITING_FOREVER) +#define cv_wait_sig(cvp, mp) \ + rt_condvar_timedwait(cvp, mp, RT_INTERRUPTIBLE, RT_WAITING_FOREVER) +#define cv_signal(cvp) rt_condvar_signal(cvp) +#define cv_broadcast(cvp) rt_condvar_broadcast(cvp) +#define cv_timedwait(cvp, mp, t) rt_condvar_timedwait(cvp, mp, RT_KILLABLE, t) +#define cv_timedwait_sig(cvp, mp, t) \ + rt_condvar_timedwait(cvp, mp, RT_INTERRUPTIBLE, t) + +struct lwp_tty; +struct uio; + +/* TODO: just a place holder since devfs is not capable of doing this currently + */ +struct file +{ +}; + +typedef rt_base_t sbintime_t; +typedef rt_ubase_t vm_offset_t; +typedef rt_base_t vm_ooffset_t; +typedef rt_ubase_t vm_paddr_t; +typedef rt_ubase_t vm_pindex_t; +typedef rt_ubase_t vm_size_t; +typedef char *rt_caddr_t; + +/* + * The exact set of memory attributes is machine dependent. However, + * every machine is required to define VM_MEMATTR_DEFAULT and + * VM_MEMATTR_UNCACHEABLE. + */ +typedef char vm_memattr_t; /* memory attribute codes */ + +typedef int d_open_t(struct lwp_tty *tp, int oflags, int devtype, + struct rt_thread *td); +typedef int d_fdopen_t(struct lwp_tty *tp, int oflags, struct rt_thread *td, + struct file *fp); +typedef int d_close_t(struct lwp_tty *tp, int fflag, int devtype, + struct rt_thread *td); + +#ifdef USING_BSD_DEVICE_STRATEGY +typedef void d_strategy_t(struct bio *bp); +#endif + +typedef int d_ioctl_t(struct lwp_tty *tp, rt_ubase_t cmd, rt_caddr_t data, + int fflag, struct rt_thread *td); + +typedef int d_read_t(struct lwp_tty *tp, struct uio *uio, int ioflag); +typedef int d_write_t(struct lwp_tty *tp, struct uio *uio, int ioflag); +typedef int d_poll_t(struct lwp_tty *tp, rt_pollreq_t *req, + struct rt_thread *td); + +#ifdef USING_BSD_KNOTE +typedef int d_kqfilter_t(struct lwp_tty *tp, struct knote *kn); +#endif /* USING_BSD_KNOTE */ + +typedef int d_mmap_t(struct lwp_tty *tp, vm_ooffset_t offset, vm_paddr_t *paddr, + int nprot, vm_memattr_t *memattr); + +#ifdef USING_BSD_MMAP_SINGLE +typedef int d_mmap_single_t(struct cdev *cdev, vm_ooffset_t *offset, + vm_size_t size, struct vm_object **object, + int nprot); +#endif /* USING_BSD_MMAP_SINGLE */ + +typedef void d_purge_t(struct lwp_tty *tp); + +/* + * Character device switch table + */ +struct cdevsw +{ +#ifdef USING_BSD_RAW_CDEVSW + int d_version; + u_int d_flags; + const char *d_name; +#endif /* USING_BSD_RAW_CDEVSW */ + + d_open_t *d_open; + d_fdopen_t *d_fdopen; + d_close_t *d_close; + d_read_t *d_read; + d_write_t *d_write; + d_ioctl_t *d_ioctl; + d_poll_t *d_poll; + d_mmap_t *d_mmap; +#ifdef USING_BSD_DEVICE_STRATEGY + d_strategy_t *d_strategy; +#endif /* USING_BSD_DEVICE_STRATEGY */ + +#ifdef USING_BSD_RAW_CDEVSW + void *d_spare0; + d_kqfilter_t *d_kqfilter; + d_purge_t *d_purge; + d_mmap_single_t *d_mmap_single; + + int32_t d_spare1[3]; + void *d_spare2[3]; + + /* These fields should not be messed with by drivers */ + LIST_HEAD(, cdev) d_devs; + int d_spare3; + union + { + struct cdevsw *gianttrick; + SLIST_ENTRY(cdevsw) postfree_list; + } __d_giant; +#endif +}; + +struct iovec +{ + void *iov_base; /* Base address. */ + size_t iov_len; /* Length. */ +}; + +enum uio_rw +{ + UIO_READ, + UIO_WRITE +}; + +struct uio +{ + struct iovec *uio_iov; /* scatter/gather list */ + int uio_iovcnt; /* length of scatter/gather list */ + off_t uio_offset; /* offset in target object */ + ssize_t uio_resid; /* remaining bytes to process */ +#ifdef USING_BSD_UIO + enum uio_seg uio_segflg; /* address space */ +#endif + enum uio_rw uio_rw; /* operation */ +#ifdef USING_BSD_UIO + struct rt_thread *uio_td; /* owner */ + +#endif /* USING_BSD_UIO */ +}; + +#include +rt_inline int uiomove(void *operand, int n, struct uio *uio) +{ + switch (uio->uio_rw) + { + case UIO_READ: + memcpy(uio->uio_iov->iov_base, operand, n); + break; + case UIO_WRITE: + memcpy(operand, uio->uio_iov->iov_base, n); + break; + default: + return -1; + } + + uio->uio_iov->iov_base += n; + uio->uio_iov->iov_len--; + uio->uio_offset += n; + uio->uio_resid -= n; + return 0; +} + +/* privileges checking: 0 if okay */ +rt_inline int priv_check(struct rt_thread *td, int priv) +{ + return 0; +} + +/* Disable console redirection to a tty. */ +rt_inline int constty_clear(struct lwp_tty *tp) +{ + // rt_kprintf("\nTODO: %s unimplemented!\n", __func__); + return 0; +} + +rt_inline int constty_set(struct lwp_tty *tp) +{ + // rt_kprintf("\nTODO: %s unimplemented!\n", __func__); + return 0; +} + +/** + * UMA (Universal Memory Allocator) + */ +#define UMA_ALIGN_PTR (sizeof(void *) - 1) /* Alignment fit for ptr */ + +typedef int (*uma_ctor)(void *mem, int size, void *arg, int flags); +typedef void (*uma_dtor)(void *mem, int size, void *arg); +typedef int (*uma_init)(void *mem, int size, int flags); +typedef void (*uma_fini)(void *mem, int size); + +struct uma_zone +{ + char *name; + int align; + int size; +}; + +/* Opaque type used as a handle to the zone */ +typedef struct uma_zone *uma_zone_t; + +rt_inline uma_zone_t uma_zcreate(char *name, int size, uma_ctor ctor, + uma_dtor dtor, uma_init zinit, uma_fini zfini, + int align, uint16_t flags) +{ + uma_zone_t zone = rt_malloc(sizeof(struct uma_zone)); + if (zone) + { + RT_ASSERT(ctor == RT_NULL); + RT_ASSERT(dtor == RT_NULL); + RT_ASSERT(zinit == RT_NULL); + RT_ASSERT(zfini == RT_NULL); + + zone->size = size; + zone->name = name; + zone->align = align; + } + return zone; +} + +rt_inline void *uma_zalloc(uma_zone_t zone, int flags) +{ + void *buf = rt_malloc_align(zone->size, zone->align + 1); + if (buf) + rt_memset(buf, 0, sizeof(zone->size)); + return buf; +} + +rt_inline void uma_zfree(uma_zone_t zone, void *item) +{ + rt_free_align(item); +} + +/** + * bsd type of speed to linux type. + * Note: with switch blocks, compiler can generate the optimized version for us + */ +#include +rt_inline long bsd_speed_to_integer(speed_t speed) +{ + long speed_value; + switch (speed) + { + case B0: + speed_value = 0; + break; + case B50: + speed_value = 50; + break; + case B75: + speed_value = 75; + break; + case B110: + speed_value = 110; + break; + case B134: + speed_value = 134; + break; + case B150: + speed_value = 150; + break; + case B200: + speed_value = 200; + break; + case B300: + speed_value = 300; + break; + case B600: + speed_value = 600; + break; + case B1200: + speed_value = 1200; + break; + case B1800: + speed_value = 1800; + break; + case B2400: + speed_value = 2400; + break; + case B4800: + speed_value = 4800; + break; + case B9600: + speed_value = 9600; + break; + case B19200: + speed_value = 19200; + break; + case B38400: + speed_value = 38400; + break; + case B57600: + speed_value = 57600; + break; + case B115200: + speed_value = 115200; + break; + case B230400: + speed_value = 230400; + break; + case B460800: + speed_value = 460800; + break; + case B500000: + speed_value = 500000; + break; + case B576000: + speed_value = 576000; + break; + case B921600: + speed_value = 921600; + break; + case B1000000: + speed_value = 1000000; + break; + case B1152000: + speed_value = 1152000; + break; + case B1500000: + speed_value = 1500000; + break; + case B2000000: + speed_value = 2000000; + break; + case B2500000: + speed_value = 2500000; + break; + case B3000000: + speed_value = 3000000; + break; + case B3500000: + speed_value = 3500000; + break; + case B4000000: + speed_value = 4000000; + break; + default: + speed_value = -1; // invalid speed + break; + } + return speed_value; +} + +/* time.h */ + +/* Operations on timevals. */ + +#define timevalclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#define timevalisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#define timevalcmp(tvp, uvp, cmp) \ + (((tvp)->tv_sec == (uvp)->tv_sec) ? ((tvp)->tv_usec cmp(uvp)->tv_usec) \ + : ((tvp)->tv_sec cmp(uvp)->tv_sec)) + +rt_inline void getmicrotime(struct timeval *now) +{ + gettimeofday(now, RT_NULL); +} + +rt_inline void timevalfix(struct timeval *tv) +{ + if (tv->tv_usec < 0) + { + tv->tv_sec--; + tv->tv_usec += 1000000; + } + if (tv->tv_usec >= 1000000) + { + tv->tv_sec++; + tv->tv_usec -= 1000000; + } +} + +rt_inline void timevaladd(struct timeval *op1, const struct timeval *op2) +{ + op1->tv_sec += op2->tv_sec; + op1->tv_usec += op2->tv_usec; + timevalfix(op1); +} + +rt_inline void timevalsub(struct timeval *op1, const struct timeval *op2) +{ + op1->tv_sec -= op2->tv_sec; + op1->tv_usec -= op2->tv_usec; + timevalfix(op1); +} + +rt_inline rt_tick_t tvtohz(struct timeval *tv) +{ + rt_tick_t rc; + rc = tv->tv_sec * RT_TICK_PER_SECOND; + rc += tv->tv_usec * RT_TICK_PER_SECOND / MICROSECOND_PER_SECOND; + return rc; +} + +/* ioctl */ +#define _BSD_TIOCTL(val) ((val) << 16) +enum bsd_ioctl_cmd +{ + BSD_TIOCDRAIN = 1, + BSD_TIOCFLUSH, + BSD_TIOCSTART, + BSD_TIOCSTOP, + BSD_TIOCSTAT, + BSD_TIOCGDRAINWAIT, + BSD_TIOCSDRAINWAIT, + BSD_TIOCSDTR, + BSD_TIOCCDTR, +}; + +#ifndef TIOCGETA /* get termios struct */ +#define TIOCGETA TCGETS +#endif +#ifndef TIOCSETA /* set termios struct */ +#define TIOCSETA TCSETS +#endif +#ifndef TIOCSETAW /* drain output, set */ +#define TIOCSETAW TCSETSW +#endif +#ifndef TIOCSETAF /* drn out, fls in, set */ +#define TIOCSETAF TCSETSF +#endif +#ifndef TIOCDRAIN /* wait till output drained */ +#define TIOCDRAIN _BSD_TIOCTL(BSD_TIOCDRAIN) +#endif +#ifndef TIOCFLUSH /* flush buffers */ +#define TIOCFLUSH _BSD_TIOCTL(BSD_TIOCFLUSH) +#endif +#ifndef TIOCSTART /* start output, like ^Q */ +#define TIOCSTART _BSD_TIOCTL(BSD_TIOCSTART) +#endif +#ifndef TIOCSTOP /* stop output, like ^S */ +#define TIOCSTOP _BSD_TIOCTL(BSD_TIOCSTOP) +#endif +#ifndef TIOCSTAT /* simulate ^T status message */ +#define TIOCSTAT _BSD_TIOCTL(BSD_TIOCSTAT) +#endif +#ifndef TIOCGDRAINWAIT /* get ttywait timeout */ +#define TIOCGDRAINWAIT _BSD_TIOCTL(BSD_TIOCGDRAINWAIT) +#endif +#ifndef TIOCSDRAINWAIT /* set ttywait timeout */ +#define TIOCSDRAINWAIT _BSD_TIOCTL(BSD_TIOCSDRAINWAIT) +#endif +#ifndef TIOCSDTR /* set data terminal ready */ +#define TIOCSDTR _BSD_TIOCTL(BSD_TIOCSDTR) +#endif +#ifndef TIOCCDTR /* clear data terminal ready */ +#define TIOCCDTR _BSD_TIOCTL(BSD_TIOCCDTR) +#endif + +#define ENOIOCTL ENOSYS +#define NO_PID -1 + +/* line discipline */ +#define TTYDISC 0 /* termios tty line discipline */ +#define SLIPDISC 4 /* serial IP discipline */ +#define PPPDISC 5 /* PPP discipline */ +#define NETGRAPHDISC 6 /* Netgraph tty node discipline */ +#define H4DISC 7 /* Netgraph Bluetooth H4 discipline */ + +/* + * Control flags - hardware control of terminal + */ +#if __BSD_VISIBLE +#define CIGNORE 0x00000001 /* ignore control flags */ +#define CCTS_OFLOW 0x00010000 /* CTS flow control of output */ +#define CRTSCTS (CCTS_OFLOW | CRTS_IFLOW) +#define CRTS_IFLOW 0x00020000 /* RTS flow control of input */ +#define CDTR_IFLOW 0x00040000 /* DTR flow control of input */ +#define CDSR_OFLOW 0x00080000 /* DSR flow control of output */ +#define CCAR_OFLOW 0x00100000 /* DCD flow control of output */ +#define CNO_RTSDTR 0x00200000 /* Do not assert RTS or DTR automatically */ +#else +#define CIGNORE 0 /* ignore control flags */ +#define CCTS_OFLOW 0 /* CTS flow control of output */ +#define CRTS_IFLOW 0 /* RTS flow control of input */ +#define CDTR_IFLOW 0 /* DTR flow control of input */ +#define CDSR_OFLOW 0 /* DSR flow control of output */ +#define CCAR_OFLOW 0 /* DCD flow control of output */ +#define CNO_RTSDTR 0 /* Do not assert RTS or DTR automatically */ +#endif + +#ifndef CRTSCTS +#define CRTSCTS (CCTS_OFLOW | CRTS_IFLOW) +#endif + +#ifndef howmany +#define howmany(x, y) (((x) + ((y)-1)) / (y)) +#endif + +struct ucred +{ +}; +#define NOCRED ((struct ucred *)0) /* no credential available */ +#define FSCRED ((struct ucred *)-1) /* filesystem credential */ + +/* convert from open() flags to/from fflags; convert O_RD/WR to FREAD/FWRITE */ +#include +#define FFLAGS(oflags) ((oflags)&O_EXEC ? (oflags) : (oflags) + 1) +#define OFLAGS(fflags) \ + (((fflags) & (O_EXEC | O_PATH)) != 0 ? (fflags) : (fflags)-1) + +typedef int fo_rdwr_t(struct lwp_tty *tp, struct uio *uio, + struct ucred *active_cred, int flags, + struct rt_thread *td); +typedef int fo_truncate_t(struct lwp_tty *tp, off_t length, + struct ucred *active_cred, struct rt_thread *td); +typedef int fo_ioctl_t(struct lwp_tty *tp, rt_ubase_t com, void *data, + struct ucred *active_cred, int fflags, struct rt_thread *td); +typedef int fo_poll_t(struct lwp_tty *tp, struct rt_pollreq *rq, struct ucred *active_cred, + struct rt_thread *td); +typedef int fo_stat_t(struct lwp_tty *tp, struct stat *sb, + struct ucred *active_cred); +typedef int fo_close_t(struct lwp_tty *tp, struct rt_thread *td); + +#ifdef USING_BSD_FO_EXT +typedef int fo_chmod_t(struct file *fp, mode_t mode, struct ucred *active_cred, + struct rt_thread *td); +typedef int fo_chown_t(struct file *fp, uid_t uid, gid_t gid, + struct ucred *active_cred, struct rt_thread *td); +typedef int fo_sendfile_t(struct file *fp, int sockfd, struct uio *hdr_uio, + struct uio *trl_uio, off_t offset, size_t nbytes, + off_t *sent, int flags, struct rt_thread *td); +typedef int fo_seek_t(struct file *fp, off_t offset, int whence, + struct rt_thread *td); + +typedef int fo_kqfilter_t(struct file *fp, struct knote *kn); +typedef int fo_fill_kinfo_t(struct file *fp, struct kinfo_file *kif, + struct filedesc *fdp); +typedef int fo_mmap_t(struct file *fp, vm_map_t map, vm_offset_t *addr, + vm_size_t size, vm_prot_t prot, vm_prot_t cap_maxprot, + int flags, vm_ooffset_t foff, struct rt_thread *td); +typedef int fo_aio_queue_t(struct file *fp, struct kaiocb *job); + +typedef int fo_add_seals_t(struct file *fp, int flags); +typedef int fo_get_seals_t(struct file *fp, int *flags); +typedef int fo_fallocate_t(struct file *fp, off_t offset, off_t len, + struct rt_thread *td); +typedef int fo_fspacectl_t(struct file *fp, int cmd, off_t *offset, + off_t *length, int flags, struct ucred *active_cred, + struct rt_thread *td); +typedef int fo_spare_t(struct file *fp); +#endif /* USING_BSD_FO_EXT */ + +typedef int fo_flags_t; + +struct bsd_fileops +{ + fo_rdwr_t *fo_read; + fo_rdwr_t *fo_write; + fo_truncate_t *fo_truncate; + fo_ioctl_t *fo_ioctl; + fo_poll_t *fo_poll; + fo_stat_t *fo_stat; + fo_close_t *fo_close; +#ifdef USING_BSD_FO_EXT + fo_chmod_t *fo_chmod; + fo_chown_t *fo_chown; + fo_sendfile_t *fo_sendfile; + fo_seek_t *fo_seek; + fo_kqfilter_t *fo_kqfilter; + fo_fill_kinfo_t *fo_fill_kinfo; + fo_mmap_t *fo_mmap; + fo_aio_queue_t *fo_aio_queue; + fo_add_seals_t *fo_add_seals; + fo_get_seals_t *fo_get_seals; + fo_fallocate_t *fo_fallocate; + fo_fspacectl_t *fo_fspacectl; + fo_spare_t *fo_spares[8]; /* Spare slots */ +#endif + fo_flags_t fo_flags; /* DFLAG_* below */ +}; + +#define DFLAG_PASSABLE 0x01 /* may be passed via unix sockets. */ +#define DFLAG_SEEKABLE 0x02 /* seekable / nonsequential */ + +#endif /* __LWP_TTY_BSD_PORTING_H__ */ diff --git a/components/lwp/terminal/bsd_ttydisc.h b/components/lwp/terminal/bsd_ttydisc.h new file mode 100644 index 0000000000..3d25f61e12 --- /dev/null +++ b/components/lwp/terminal/bsd_ttydisc.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _SYS_TTYDISC_H_ +#define _SYS_TTYDISC_H_ + +#ifndef __LWP_TERMINAL_H__ +#error "can only be included through " +#endif /* !__LWP_TERMINAL_H__ */ + +#include + +struct rt_wqueue; +struct rt_thread; +struct lwp_tty; +struct uio; + +/* Top half routines. */ +void ttydisc_open(struct lwp_tty *tp); +void ttydisc_close(struct lwp_tty *tp); +int ttydisc_read(struct lwp_tty *tp, struct uio *uio, int ioflag); +int ttydisc_write(struct lwp_tty *tp, struct uio *uio, int ioflag); +void ttydisc_optimize(struct lwp_tty *tp); + +/* Bottom half routines. */ +void ttydisc_modem(struct lwp_tty *tp, int open); +#define ttydisc_can_bypass(tp) ((tp)->t_flags & TF_BYPASS) +int ttydisc_rint(struct lwp_tty *tp, char c, int flags); +size_t ttydisc_rint_simple(struct lwp_tty *tp, const void *buf, size_t len); +size_t ttydisc_rint_bypass(struct lwp_tty *tp, const void *buf, size_t len); +void ttydisc_rint_done(struct lwp_tty *tp); +size_t ttydisc_rint_poll(struct lwp_tty *tp); +size_t ttydisc_getc(struct lwp_tty *tp, void *buf, size_t len); +int ttydisc_getc_uio(struct lwp_tty *tp, struct uio *uio); +size_t ttydisc_getc_poll(struct lwp_tty *tp); + +/* Error codes for ttydisc_rint(). */ +#define TRE_FRAMING 0x01 +#define TRE_PARITY 0x02 +#define TRE_OVERRUN 0x04 +#define TRE_BREAK 0x08 + +#endif /* !_SYS_TTYDISC_H_ */ diff --git a/components/lwp/terminal/bsd_ttyqueue.h b/components/lwp/terminal/bsd_ttyqueue.h new file mode 100644 index 0000000000..96b7ae3d8b --- /dev/null +++ b/components/lwp/terminal/bsd_ttyqueue.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#include "bsd_porting.h" + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _SYS_TTYQUEUE_H_ +#define _SYS_TTYQUEUE_H_ + +#ifndef __LWP_TERMINAL_H__ +#error "can only be included through " +#endif /* !__LWP_TERMINAL_H__ */ + +struct lwp_tty; +struct ttyinq_block; +struct ttyoutq_block; +struct uio; + +/* Data input queue. */ +struct ttyinq +{ + struct ttyinq_block *ti_firstblock; + struct ttyinq_block *ti_startblock; + struct ttyinq_block *ti_reprintblock; + struct ttyinq_block *ti_lastblock; + unsigned int ti_begin; + unsigned int ti_linestart; + unsigned int ti_reprint; + unsigned int ti_end; + unsigned int ti_nblocks; + unsigned int ti_quota; +}; +#define TTYINQ_DATASIZE 128 + +/* Data output queue. */ +struct ttyoutq +{ + struct ttyoutq_block *to_firstblock; + struct ttyoutq_block *to_lastblock; + unsigned int to_begin; + unsigned int to_end; + unsigned int to_nblocks; + unsigned int to_quota; +}; +#define TTYOUTQ_DATASIZE (256 - sizeof(struct ttyoutq_block *)) + +/* Input queue handling routines. */ +int ttyinq_setsize(struct ttyinq *ti, struct lwp_tty *tp, size_t len); +void ttyinq_free(struct ttyinq *ti); +int ttyinq_read_uio(struct ttyinq *ti, struct lwp_tty *tp, struct uio *uio, + size_t readlen, size_t flushlen); +size_t ttyinq_write(struct ttyinq *ti, const void *buf, size_t len, int quote); +int ttyinq_write_nofrag(struct ttyinq *ti, const void *buf, size_t len, + int quote); +void ttyinq_canonicalize(struct ttyinq *ti); +size_t ttyinq_findchar(struct ttyinq *ti, const char *breakc, size_t maxlen, + char *lastc); +void ttyinq_flush(struct ttyinq *ti); +int ttyinq_peekchar(struct ttyinq *ti, char *c, int *quote); +void ttyinq_unputchar(struct ttyinq *ti); +void ttyinq_reprintpos_set(struct ttyinq *ti); +void ttyinq_reprintpos_reset(struct ttyinq *ti); + +rt_inline size_t ttyinq_getsize(struct ttyinq *ti) +{ + return (ti->ti_nblocks * TTYINQ_DATASIZE); +} + +rt_inline size_t ttyinq_getallocatedsize(struct ttyinq *ti) +{ + return (ti->ti_quota * TTYINQ_DATASIZE); +} + +rt_inline size_t ttyinq_bytesleft(struct ttyinq *ti) +{ + size_t len; + + /* Make sure the usage never exceeds the length. */ + len = ti->ti_nblocks * TTYINQ_DATASIZE; + MPASS(len >= ti->ti_end); + + return (len - ti->ti_end); +} + +rt_inline size_t ttyinq_bytescanonicalized(struct ttyinq *ti) +{ + MPASS(ti->ti_begin <= ti->ti_linestart); + + return (ti->ti_linestart - ti->ti_begin); +} + +rt_inline size_t ttyinq_bytesline(struct ttyinq *ti) +{ + MPASS(ti->ti_linestart <= ti->ti_end); + + return (ti->ti_end - ti->ti_linestart); +} + +/* Input buffer iteration. */ +typedef void ttyinq_line_iterator_t(void *data, char c, int flags); +void ttyinq_line_iterate_from_linestart(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, + void *data); +void ttyinq_line_iterate_from_reprintpos(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, + void *data); + +/* Output queue handling routines. */ +void ttyoutq_flush(struct ttyoutq *to); +int ttyoutq_setsize(struct ttyoutq *to, struct lwp_tty *tp, size_t len); +void ttyoutq_free(struct ttyoutq *to); +size_t ttyoutq_read(struct ttyoutq *to, void *buf, size_t len); +int ttyoutq_read_uio(struct ttyoutq *to, struct lwp_tty *tp, struct uio *uio); +size_t ttyoutq_write(struct ttyoutq *to, const void *buf, size_t len); +int ttyoutq_write_nofrag(struct ttyoutq *to, const void *buf, size_t len); + +rt_inline size_t ttyoutq_getsize(struct ttyoutq *to) +{ + return (to->to_nblocks * TTYOUTQ_DATASIZE); +} + +rt_inline size_t ttyoutq_getallocatedsize(struct ttyoutq *to) +{ + return (to->to_quota * TTYOUTQ_DATASIZE); +} + +rt_inline size_t ttyoutq_bytesleft(struct ttyoutq *to) +{ + size_t len; + + /* Make sure the usage never exceeds the length. */ + len = to->to_nblocks * TTYOUTQ_DATASIZE; + MPASS(len >= to->to_end); + + return (len - to->to_end); +} + +rt_inline size_t ttyoutq_bytesused(struct ttyoutq *to) +{ + return (to->to_end - to->to_begin); +} + +#endif /* !_SYS_TTYQUEUE_H_ */ diff --git a/components/lwp/terminal/freebsd/tty.c b/components/lwp/terminal/freebsd/tty.c new file mode 100644 index 0000000000..cdd9006279 --- /dev/null +++ b/components/lwp/terminal/freebsd/tty.c @@ -0,0 +1,1447 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ +#include "../bsd_porting.h" +#include "../tty_config.h" +#include "../terminal.h" +#include "../tty_internal.h" + +#include +#include + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +static void tty_rel_free(struct lwp_tty *tp); + +/* Character device of /dev/console. */ +static struct rt_device *dev_console; +#ifdef USING_BSD_CONSOLE_NAME +static const char *dev_console_filename; +#endif + +/* + * Flags that are supported and stored by this implementation. + */ +#ifndef ALTWERASE +#define ALTWERASE 0 +#endif +#ifndef NOKERNINFO +#define NOKERNINFO 0 +#endif +#define TTYSUP_IFLAG \ + (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | \ + ICRNL | IXON | IXOFF | IXANY | IMAXBEL | IUTF8) +#define TTYSUP_OFLAG (OPOST | ONLCR | TAB3 | ONOEOT | OCRNL | ONOCR | ONLRET) +#define TTYSUP_LFLAG \ + (ECHOKE | ECHOE | ECHOK | ECHO | ECHONL | ECHOPRT | ECHOCTL | ISIG | \ + ICANON | ALTWERASE | IEXTEN | TOSTOP | FLUSHO | NOKERNINFO | NOFLSH) +#define TTYSUP_CFLAG \ + (CIGNORE | CSIZE | CSTOPB | CREAD | PARENB | PARODD | HUPCL | CLOCAL | \ + CCTS_OFLOW | CRTS_IFLOW | CDTR_IFLOW | CDSR_OFLOW | CCAR_OFLOW | \ + CNO_RTSDTR) + +/* + * Set TTY buffer sizes. + */ + +#define TTYBUF_MAX 65536 + +#ifdef PRINTF_BUFR_SIZE +#define TTY_PRBUF_SIZE PRINTF_BUFR_SIZE +#else +#define TTY_PRBUF_SIZE 256 +#endif + +/* Note: access to struct cdev:si_drv0. + Since pull-in, pull-out is not provided, this field + is constant value 0 in smart system */ +#define dev2unit(d) (0) +#define TTY_CALLOUT(tp, d) (dev2unit(d) & TTYUNIT_CALLOUT) + +/* + * Allocate buffer space if necessary, and set low watermarks, based on speed. + * Note that the ttyxxxq_setsize() functions may drop and then reacquire the tty + * lock during memory allocation. They will return ENXIO if the tty disappears + * while unlocked. + */ +static int tty_watermarks(struct lwp_tty *tp) +{ + size_t bs = 0; + int error; + + /* Provide an input buffer for 2 seconds of data. */ + if (tp->t_termios.c_cflag & CREAD) + bs = + MIN(bsd_speed_to_integer(tp->t_termios.__c_ispeed) / 5, TTYBUF_MAX); + error = ttyinq_setsize(&tp->t_inq, tp, bs); + if (error != 0) + return error; + + /* Set low watermark at 10% (when 90% is available). */ + tp->t_inlow = (ttyinq_getallocatedsize(&tp->t_inq) * 9) / 10; + + /* Provide an output buffer for 2 seconds of data. */ + bs = MIN(bsd_speed_to_integer(tp->t_termios.__c_ospeed) / 5, TTYBUF_MAX); + error = ttyoutq_setsize(&tp->t_outq, tp, bs); + if (error != 0) + return error; + + /* Set low watermark at 10% (when 90% is available). */ + tp->t_outlow = (ttyoutq_getallocatedsize(&tp->t_outq) * 9) / 10; + + return 0; +} + +/** + * Drain outq + */ +static int tty_drain(struct lwp_tty *tp, int leaving) +{ + rt_tick_t timeout_tick; + size_t bytes; + int error; + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, getc_inject)) + /* buffer is inaccessible */ + return 0; +#endif /* USING_BSD_HOOK */ + + /* + * For close(), use the recent historic timeout of "1 second without + * making progress". For tcdrain(), use t_drainwait as the timeout, + * with zero meaning "no timeout" which gives POSIX behavior. + */ + if (leaving) + timeout_tick = rt_tick_get() + RT_TICK_PER_SECOND; + else if (tp->t_drainwait != 0) + timeout_tick = rt_tick_get() + RT_TICK_PER_SECOND * tp->t_drainwait; + else + timeout_tick = 0; + + /* + * Poll the output buffer and the hardware for completion, at 10 Hz. + * Polling is required for devices which are not able to signal an + * interrupt when the transmitter becomes idle (most USB serial devs). + * The unusual structure of this loop ensures we check for busy one more + * time after tty_timedwait() returns EWOULDBLOCK, so that success has + * higher priority than timeout if the IO completed in the last 100mS. + */ + error = 0; + bytes = ttyoutq_bytesused(&tp->t_outq); + for (;;) + { + if (ttyoutq_bytesused(&tp->t_outq) == 0 && !ttydevsw_busy(tp)) + return 0; + if (error != 0) + return error; + ttydevsw_outwakeup(tp); + error = tty_timedwait(tp, &tp->t_outwait, RT_TICK_PER_SECOND / 10); + if (error != 0 && error != EWOULDBLOCK) + return error; + else if (timeout_tick == 0 || rt_tick_get() < timeout_tick) + error = 0; + else if (leaving && ttyoutq_bytesused(&tp->t_outq) < bytes) + { + /* In close, making progress, grant an extra second. */ + error = 0; + timeout_tick += RT_TICK_PER_SECOND; + bytes = ttyoutq_bytesused(&tp->t_outq); + } + } +} + +/* + * Though ttydev_enter() and ttydev_leave() seem to be related, they + * don't have to be used together. ttydev_enter() is used by the cdev + * operations to prevent an actual operation from being processed when + * the TTY has been abandoned. ttydev_leave() is used by ttydev_open() + * and ttydev_close() to determine whether per-TTY data should be + * deallocated. + */ + +rt_inline int ttydev_enter(struct lwp_tty *tp) +{ + rt_err_t error = tty_lock(tp); + if (error) + RT_ASSERT(0); + + if (tty_gone(tp) || !tty_opened(tp)) + { + /* Device is already gone. */ + tty_unlock(tp); + return -ENXIO; + } + + return 0; +} + +static void ttydev_leave(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + + if (tty_opened(tp) || tp->t_flags & TF_OPENCLOSE) + { + /* Device is still opened somewhere. */ + tty_unlock(tp); + return; + } + + tp->t_flags |= TF_OPENCLOSE; + + /* Remove console TTY. */ + constty_clear(tp); + + /* Drain any output. */ + if (!tty_gone(tp)) + tty_drain(tp, 1); + + ttydisc_close(tp); + + /* Free i/o queues now since they might be large. */ + ttyinq_free(&tp->t_inq); + tp->t_inlow = 0; + ttyoutq_free(&tp->t_outq); + tp->t_outlow = 0; + +#ifdef USING_BSD_KNOTE + knlist_clear(&tp->t_inpoll.si_note, 1); + knlist_clear(&tp->t_outpoll.si_note, 1); +#endif + + if (!tty_gone(tp)) + ttydevsw_close(tp); + + tp->t_flags &= ~TF_OPENCLOSE; + cv_broadcast(&tp->t_dcdwait); + tty_rel_free(tp); +} + +/* + * Operations that are exposed through the character device in /dev. + */ +static int ttydev_open(struct lwp_tty *tp, int oflags, int devtype, + struct rt_thread *td) +{ + rt_device_t dev = &tp->parent; + int error; + + error = 0; + tty_lock(tp); + if (tty_gone(tp)) + { + /* Device is already gone. */ + tty_unlock(tp); + return -ENXIO; + } + + /* + * Block when other processes are currently opening or closing + * the TTY. + */ + while (tp->t_flags & TF_OPENCLOSE) + { + error = tty_wait(tp, &tp->t_dcdwait); + if (error != 0) + { + tty_unlock(tp); + return error; + } + } + tp->t_flags |= TF_OPENCLOSE; + + /* + * Make sure the "tty" and "cua" device cannot be opened at the + * same time. The console is a "tty" device. + */ + if (TTY_CALLOUT(tp, dev)) + { + if (tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) + { + error = EBUSY; + goto done; + } + } + else + { + if (tp->t_flags & TF_OPENED_OUT) + { + error = EBUSY; + goto done; + } + } + + if (tp->t_flags & TF_EXCLUDE && priv_check(td, PRIV_TTY_EXCLUSIVE)) + { + error = EBUSY; + goto done; + } + + if (!tty_opened(tp)) + { + /* Set proper termios flags. */ + if (TTY_CALLOUT(tp, dev)) +#ifdef USING_BSD_INIT_LOCK_DEVICE + tp->t_termios = tp->t_termios_init_out; +#else + ; +#endif /* USING_BSD_INIT_LOCK_DEVICE */ + else + tp->t_termios = tp->t_termios_init_in; + + ttydevsw_param(tp, &tp->t_termios); + /* Prevent modem control on callout devices and /dev/console. */ + if (TTY_CALLOUT(tp, dev) || dev == dev_console) + tp->t_termios.c_cflag |= CLOCAL; + + if ((tp->t_termios.c_cflag & CNO_RTSDTR) == 0) + ttydevsw_modem(tp, SER_DTR | SER_RTS, 0); + + error = ttydevsw_open(tp); + if (error != 0) + goto done; + + ttydisc_open(tp); + error = tty_watermarks(tp); + if (error != 0) + goto done; + } + + /* Wait for Carrier Detect. */ + if ((oflags & O_NONBLOCK) == 0 && (tp->t_termios.c_cflag & CLOCAL) == 0) + { + while ((ttydevsw_modem(tp, 0, 0) & SER_DCD) == 0) + { + error = tty_wait(tp, &tp->t_dcdwait); + if (error != 0) + goto done; + } + } + + if (dev == dev_console) + tp->t_flags |= TF_OPENED_CONS; + else if (TTY_CALLOUT(tp, dev)) + tp->t_flags |= TF_OPENED_OUT; + else + tp->t_flags |= TF_OPENED_IN; + MPASS((tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) == 0 || + (tp->t_flags & TF_OPENED_OUT) == 0); + +done: + tp->t_flags &= ~TF_OPENCLOSE; + cv_broadcast(&tp->t_dcdwait); + ttydev_leave(tp); + + return error; +} + +static int ttydev_close(struct lwp_tty *tp, int fflag, int devtype __unused, + struct rt_thread *td __unused) +{ + rt_device_t dev = &tp->parent; + + tty_lock(tp); + + /* + * Don't actually close the device if it is being used as the + * console. + */ + MPASS((tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) == 0 || + (tp->t_flags & TF_OPENED_OUT) == 0); + if (dev == dev_console) + tp->t_flags &= ~TF_OPENED_CONS; + else + tp->t_flags &= ~(TF_OPENED_IN | TF_OPENED_OUT); + + if (tp->t_flags & TF_OPENED) + { + tty_unlock(tp); + return 0; + } + + /* If revoking, flush output now to avoid draining it later. */ + if (fflag & FREVOKE) + tty_flush(tp, FWRITE); + + tp->t_flags &= ~TF_EXCLUDE; + + /* Properly wake up threads that are stuck - revoke(). */ + tp->t_revokecnt++; + tty_wakeup(tp, FREAD | FWRITE); + cv_broadcast(&tp->t_bgwait); + cv_broadcast(&tp->t_dcdwait); + + ttydev_leave(tp); + + return 0; +} + +int tty_wait_background(struct lwp_tty *tp, struct rt_thread *td, int sig) +{ + struct rt_lwp *p; + struct rt_processgroup *pg; + int error; + + MPASS(sig == SIGTTIN || sig == SIGTTOU); + tty_assert_locked(tp); + + p = td->lwp; + for (;;) + { + pg = p->pgrp; + PGRP_LOCK(pg); + LWP_LOCK(p); + + /* + * pg may no longer be our process group. + * Re-check after locking. + */ + if (p->pgrp != pg) + { + LWP_UNLOCK(p); + PGRP_UNLOCK(pg); + continue; + } + + /* + * The process should only sleep, when: + * - This terminal is the controlling terminal + * - Its process group is not the foreground process + * group + * - The parent process isn't waiting for the child to + * exit + * - the signal to send to the process isn't masked + */ + if (!tty_is_ctty(tp, p) || pg == tp->t_pgrp) + { + /* Allow the action to happen. */ + LWP_UNLOCK(p); + PGRP_UNLOCK(pg); + return 0; + } + + /* Note: process itself don't have a sigmask in smart */ + if (lwp_sigisign(p, sig) || + lwp_sigismember(&td->signal.sigset_mask, sig)) + { + /* Only allow them in write()/ioctl(). */ + LWP_UNLOCK(p); + PGRP_UNLOCK(pg); + return (sig == SIGTTOU ? 0 : -EIO); + } + +#ifdef USING_VFORK_FLAG + if ((p->p_flag & P_PPWAIT) != 0 || pg->is_orphaned) +#else + if (pg->is_orphaned) +#endif + { + /* Don't allow the action to happen. */ + LWP_UNLOCK(p); + PGRP_UNLOCK(pg); + return -EIO; + } + LWP_UNLOCK(p); + + /* + * Send the signal and sleep until we're the new + * foreground process group. + */ + if (sig != 0) + { + lwp_pgrp_signal_kill(pg, sig, SI_KERNEL, 0); + } + + PGRP_UNLOCK(pg); + + error = tty_wait(tp, &tp->t_bgwait); + if (error) + return error; + } +} + +static int ttydev_read(struct lwp_tty *tp, struct uio *uio, int ioflag) +{ + int error; + + error = ttydev_enter(tp); + if (error) + goto done; + error = ttydisc_read(tp, uio, ioflag); + tty_unlock(tp); + + /* + * The read() call should not throw an error when the device is + * being destroyed. Silently convert it to an EOF. + */ +done: + if (error == -ENXIO) + error = 0; + return error; +} + +static int ttydev_write(struct lwp_tty *tp, struct uio *uio, int ioflag) +{ +#ifdef USING_BSD_DEFER_STOP + int defer; +#endif + int error; + + error = ttydev_enter(tp); + if (error) + return error; + + if (tp->t_termios.c_lflag & TOSTOP) + { + error = tty_wait_background(tp, curthread, SIGTTOU); + if (error) + goto done; + } + + if (ioflag & IO_NDELAY && tp->t_flags & TF_BUSY_OUT) + { + /* Allow non-blocking writes to bypass serialization. */ + error = ttydisc_write(tp, uio, ioflag); + } + else + { + /* Serialize write() calls. */ + while (tp->t_flags & TF_BUSY_OUT) + { + error = tty_wait(tp, &tp->t_outserwait); + if (error) + goto done; + } + + tp->t_flags |= TF_BUSY_OUT; +#ifdef USING_BSD_DEFER_STOP + defer = sigdeferstop(SIGDEFERSTOP_ERESTART); +#endif + error = ttydisc_write(tp, uio, ioflag); +#ifdef USING_BSD_DEFER_STOP + sigallowstop(defer); +#endif + tp->t_flags &= ~TF_BUSY_OUT; + cv_signal(&tp->t_outserwait); + } + +done: + tty_unlock(tp); + return error; +} + +static int ttydev_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, rt_caddr_t data, int fflag, + struct rt_thread *td) +{ + int error; + + error = ttydev_enter(tp); + if (error) + return (error); + + switch (cmd) + { + case TIOCCBRK: + case TIOCCONS: + case TIOCDRAIN: + case TIOCEXCL: + case TIOCFLUSH: + case TIOCNXCL: + case TIOCSBRK: + case TIOCSCTTY: + case TIOCSETA: + case TIOCSETAF: + case TIOCSETAW: + case TIOCSPGRP: + case TIOCSTART: + case TIOCSTAT: + case TIOCSTI: + case TIOCSTOP: + case TIOCSWINSZ: +#if USING_BSD_TIOCSDRAINWAIT + case TIOCSDRAINWAIT: + case TIOCSETD: +#endif /* USING_BSD_TIOCSDRAINWAIT */ +#ifdef COMPAT_43TTY + case TIOCLBIC: + case TIOCLBIS: + case TIOCLSET: + case TIOCSETC: + case OTIOCSETD: + case TIOCSETN: + case TIOCSETP: + case TIOCSLTC: +#endif /* COMPAT_43TTY */ + /* + * If the ioctl() causes the TTY to be modified, let it + * wait in the background. + */ + error = tty_wait_background(tp, curthread, SIGTTOU); + if (error) + goto done; + } + +#ifdef USING_BSD_INIT_LOCK_DEVICE + if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) + { + struct termios *old = &tp->t_termios; + struct termios *new = (struct termios *)data; + struct termios *lock = TTY_CALLOUT(tp, dev) ? &tp->t_termios_lock_out + : &tp->t_termios_lock_in; + int cc; + + /* + * Lock state devices. Just overwrite the values of the + * commands that are currently in use. + */ + new->c_iflag = + (old->c_iflag & lock->c_iflag) | (new->c_iflag & ~lock->c_iflag); + new->c_oflag = + (old->c_oflag & lock->c_oflag) | (new->c_oflag & ~lock->c_oflag); + new->c_cflag = + (old->c_cflag & lock->c_cflag) | (new->c_cflag & ~lock->c_cflag); + new->c_lflag = + (old->c_lflag & lock->c_lflag) | (new->c_lflag & ~lock->c_lflag); + for (cc = 0; cc < NCCS; ++cc) + if (lock->c_cc[cc]) + new->c_cc[cc] = old->c_cc[cc]; + if (lock->__c_ispeed) + new->__c_ispeed = old->__c_ispeed; + if (lock->__c_ospeed) + new->__c_ospeed = old->__c_ospeed; + } +#endif /* USING_BSD_INIT_LOCK_DEVICE */ + + error = tty_ioctl(tp, cmd, data, fflag, td); +done: + tty_unlock(tp); + + return (error); +} + +static int ttydev_poll(struct lwp_tty *tp, rt_pollreq_t *req, struct rt_thread *td) +{ + int events = req->_key; + int error, revents = 0; + + error = ttydev_enter(tp); + if (error) + return ((events & (POLLIN | POLLRDNORM)) | POLLHUP); + + if (events & (POLLIN | POLLRDNORM)) + { + /* See if we can read something. */ + if (ttydisc_read_poll(tp) > 0) + revents |= events & (POLLIN | POLLRDNORM); + } + + if (tp->t_flags & TF_ZOMBIE) + { + /* Hangup flag on zombie state. */ + revents |= POLLHUP; + } + else if (events & (POLLOUT | POLLWRNORM)) + { + /* See if we can write something. */ + if (ttydisc_write_poll(tp) > 0) + revents |= events & (POLLOUT | POLLWRNORM); + } + + if (revents == 0) + { + if (events & (POLLIN | POLLRDNORM)) + rt_poll_add(&tp->t_inpoll, req); + if (events & (POLLOUT | POLLWRNORM)) + rt_poll_add(&tp->t_outpoll, req); + } + + tty_unlock(tp); + + return revents; +} + +static struct cdevsw ttydev_cdevsw = { + .d_open = ttydev_open, + .d_close = ttydev_close, + .d_read = ttydev_read, + .d_write = ttydev_write, + .d_ioctl = ttydev_ioctl, +#if 0 + .d_kqfilter = ttydev_kqfilter, +#endif + .d_poll = ttydev_poll, +#if 0 + .d_mmap = ttydev_mmap, +#endif + +#ifdef USING_BSD_RAW_CDEVSW + .d_version = D_VERSION.d_name = "ttydev", + .d_flags = D_TTY, +#endif /* USING_BSD_RAW_CDEVSW */ +}; + +extern struct cdevsw bsd_ttydev_methods __attribute__((alias("ttydev_cdevsw"))); + +/* + * Standard device routine implementations, mostly meant for + * pseudo-terminal device drivers. When a driver creates a new terminal + * device class, missing routines are patched. + */ +#define panic(msg) RT_ASSERT(0 && msg) + +static int ttydevsw_defopen(struct lwp_tty *tp __unused) +{ + return 0; +} + +static void ttydevsw_defclose(struct lwp_tty *tp __unused) +{ +} + +static void ttydevsw_defoutwakeup(struct lwp_tty *tp __unused) +{ + panic("Terminal device has output, while not implemented"); +} + +static void ttydevsw_definwakeup(struct lwp_tty *tp __unused) +{ +} + +static int ttydevsw_defioctl(struct lwp_tty *tp __unused, rt_ubase_t cmd __unused, + rt_caddr_t data __unused, + struct rt_thread *td __unused) +{ + return -ENOSYS; +} + +static int ttydevsw_defcioctl(struct lwp_tty *tp __unused, int unit __unused, + rt_ubase_t cmd __unused, rt_caddr_t data __unused, + struct rt_thread *td __unused) +{ + return -ENOSYS; +} + +static int ttydevsw_defparam(struct lwp_tty *tp __unused, struct termios *t) +{ + /* + * Allow the baud rate to be adjusted for pseudo-devices, but at + * least restrict it to 115200 to prevent excessive buffer + * usage. Also disallow 0, to prevent foot shooting. + */ + if (t->__c_ispeed < B50) + t->__c_ispeed = B50; + else if (t->__c_ispeed > B115200) + t->__c_ispeed = B115200; + if (t->__c_ospeed < B50) + t->__c_ospeed = B50; + else if (t->__c_ospeed > B115200) + t->__c_ospeed = B115200; + t->c_cflag |= CREAD; + + return 0; +} + +static int ttydevsw_defmodem(struct lwp_tty *tp __unused, int sigon __unused, + int sigoff __unused) +{ + /* Simulate a carrier to make the TTY layer happy. */ + return (SER_DCD); +} + +static int ttydevsw_defmmap(struct lwp_tty *tp __unused, + vm_ooffset_t offset __unused, + vm_paddr_t *paddr __unused, int nprot __unused, + vm_memattr_t *memattr __unused) +{ + return (-1); +} + +static void ttydevsw_defpktnotify(struct lwp_tty *tp __unused, + char event __unused) +{ +} + +static void ttydevsw_deffree(void *softc __unused) +{ + panic("Terminal device freed without a free-handler"); +} + +static rt_bool_t ttydevsw_defbusy(struct lwp_tty *tp __unused) +{ + return (RT_FALSE); +} + +void bsd_devsw_init(struct lwp_ttydevsw *tsw) +{ + /* Make sure the driver defines all routines. */ +#define PATCH_FUNC(x) \ + do \ + { \ + if (tsw->tsw_##x == NULL) \ + tsw->tsw_##x = ttydevsw_def##x; \ + } while (0) + + PATCH_FUNC(open); + PATCH_FUNC(close); + PATCH_FUNC(outwakeup); + PATCH_FUNC(inwakeup); + PATCH_FUNC(ioctl); + PATCH_FUNC(cioctl); + PATCH_FUNC(param); + PATCH_FUNC(modem); + PATCH_FUNC(mmap); + PATCH_FUNC(pktnotify); + PATCH_FUNC(free); + PATCH_FUNC(busy); +#undef PATCH_FUNC +} + +/* release tty, and free the cdev resource */ +static void tty_rel_free(struct lwp_tty *tp) +{ +#ifdef USING_BSD_CHAR_DEVICE + struct cdev *dev; +#endif + + tty_assert_locked(tp); + +#define TF_ACTIVITY (TF_GONE | TF_OPENED | TF_HOOK | TF_OPENCLOSE) + if (tp->t_sessioncnt != 0 || (tp->t_flags & TF_ACTIVITY) != TF_GONE) + { + /* TTY is still in use. */ + tty_unlock(tp); + return; + } + +#ifdef USING_BSD_AIO + /* Stop asynchronous I/O. */ + funsetown(&tp->t_sigio); +#endif /* USING_BSD_AIO */ + +#ifdef USING_BSD_CHAR_DEVICE + /* TTY can be deallocated. */ + dev = tp->t_dev; + tp->t_dev = NULL; +#endif /* USING_BSD_CHAR_DEVICE */ + + tty_unlock(tp); + +#ifdef USING_BSD_CHAR_DEVICE + if (dev != NULL) + { + sx_xlock(&tty_list_sx); + TAILQ_REMOVE(&tty_list, tp, t_list); + tty_list_count--; + sx_xunlock(&tty_list_sx); + destroy_dev_sched_cb(dev, tty_dealloc, tp); + } +#else + lwp_tty_delete(tp); +#endif +} + +void tty_rel_pgrp(struct lwp_tty *tp, struct rt_processgroup *pg) +{ + MPASS(tp->t_sessioncnt > 0); + tty_assert_locked(tp); + + if (tp->t_pgrp == pg) + tp->t_pgrp = NULL; + + tty_unlock(tp); +} + +void tty_rel_sess(struct lwp_tty *tp, struct rt_session *sess) +{ + MPASS(tp->t_sessioncnt > 0); + + /* Current session has left. */ + if (tp->t_session == sess) + { + tp->t_session = NULL; + MPASS(tp->t_pgrp == NULL); + } + tp->t_sessioncnt--; + tty_rel_free(tp); +} + +/* deallocate the tty */ +void tty_rel_gone(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + /* Simulate carrier removal. */ + ttydisc_modem(tp, 0); + + /* Wake up all blocked threads. */ + tty_wakeup(tp, FREAD | FWRITE); + cv_broadcast(&tp->t_bgwait); + cv_broadcast(&tp->t_dcdwait); + + tp->t_flags |= TF_GONE; + tty_rel_free(tp); +} + +static int tty_drop_ctty(struct lwp_tty *tp, struct rt_lwp *p) +{ + struct rt_session *session; +#ifdef USING_BSD_VNODE + struct vnode *vp; +#endif + + /* + * This looks terrible, but it's generally safe as long as the tty + * hasn't gone away while we had the lock dropped. All of our sanity + * checking that this operation is OK happens after we've picked it back + * up, so other state changes are generally not fatal and the potential + * for this particular operation to happen out-of-order in a + * multithreaded scenario is likely a non-issue. + */ + tty_unlock(tp); + LWP_LOCK(p); + tty_lock(tp); + if (tty_gone(tp)) + { + LWP_UNLOCK(p); + return -ENODEV; + } + + /* + * If the session doesn't have a controlling TTY, or if we weren't + * invoked on the controlling TTY, we'll return ENOIOCTL as we've + * historically done. + */ + session = p->pgrp->session; + if (session->ctty == NULL || session->ctty != tp) + { + LWP_UNLOCK(p); + return -ENOTTY; + } + + if (!is_sess_leader(p)) + { + LWP_UNLOCK(p); + return -EPERM; + } + + SESS_LOCK(session); +#ifdef USING_BSD_VNODE + vp = session->s_ttyvp; +#endif + session->ctty = NULL; +#ifdef USING_BSD_VNODE + session->s_ttyvp = NULL; + session->s_ttydp = NULL; +#endif + SESS_UNLOCK(session); + + tp->t_sessioncnt--; + p->term_ctrlterm = RT_FALSE; + LWP_UNLOCK(p); + +#ifdef USING_BSD_VNODE + /* + * If we did have a vnode, release our reference. Ordinarily we manage + * these at the devfs layer, but we can't necessarily know that we were + * invoked on the vnode referenced in the session (i.e. the vnode we + * hold a reference to). We explicitly don't check VBAD/VIRF_DOOMED here + * to avoid a vnode leak -- in circumstances elsewhere where we'd hit a + * VIRF_DOOMED vnode, release has been deferred until the controlling TTY + * is either changed or released. + */ + if (vp != NULL) + devfs_ctty_unref(vp); +#endif + + return 0; +} + +void tty_wakeup(struct lwp_tty *tp, int flags) +{ +#ifdef USING_BSD_AIO + if (tp->t_flags & TF_ASYNC && tp->t_sigio != NULL) + pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); +#endif + + if (flags & FWRITE) + { + cv_broadcast(&tp->t_outwait); +#ifdef USING_BSD_POLL + selwakeup(&tp->t_outpoll); + KNOTE_LOCKED(&tp->t_outpoll.si_note, 0); +#else + rt_wqueue_wakeup_all(&tp->t_outpoll, (void *)POLLOUT); +#endif + } + if (flags & FREAD) + { + cv_broadcast(&tp->t_inwait); +#ifdef USING_BSD_POLL + selwakeup(&tp->t_inpoll); + KNOTE_LOCKED(&tp->t_inpoll.si_note, 0); +#else + rt_wqueue_wakeup_all(&tp->t_inpoll, (void *)POLLIN); +#endif + } +} + +int tty_wait(struct lwp_tty *tp, struct rt_condvar *cv) +{ + int error; + int revokecnt = tp->t_revokecnt; + + tty_lock_assert(tp, MA_OWNED | MA_NOTRECURSED); + MPASS(!tty_gone(tp)); + + error = cv_wait_sig(cv, tp->t_mtx); + + /* Bail out when the device slipped away. */ + if (tty_gone(tp)) + return -ENXIO; + + /* Restart the system call when we may have been revoked. */ + if (tp->t_revokecnt != revokecnt) + return -ERESTART; + + return error; +} + +int tty_timedwait(struct lwp_tty *tp, struct rt_condvar *cv, rt_tick_t timeout) +{ + int error; + int revokecnt = tp->t_revokecnt; + + tty_lock_assert(tp, MA_OWNED | MA_NOTRECURSED); + MPASS(!tty_gone(tp)); + + error = cv_timedwait_sig(cv, tp->t_mtx, timeout); + + /* Bail out when the device slipped away. */ + if (tty_gone(tp)) + return -ENXIO; + + /* Restart the system call when we may have been revoked. */ + if (tp->t_revokecnt != revokecnt) + return -ERESTART; + + return error; +} + +/* discard data in I/O buffers */ +void tty_flush(struct lwp_tty *tp, int flags) +{ + if (flags & FWRITE) + { + tp->t_flags &= ~TF_HIWAT_OUT; + ttyoutq_flush(&tp->t_outq); + tty_wakeup(tp, FWRITE); + if (!tty_gone(tp)) + { + ttydevsw_outwakeup(tp); + ttydevsw_pktnotify(tp, TIOCPKT_FLUSHWRITE); + } + } + if (flags & FREAD) + { + tty_hiwat_in_unblock(tp); + ttyinq_flush(&tp->t_inq); + tty_wakeup(tp, FREAD); + if (!tty_gone(tp)) + { + ttydevsw_inwakeup(tp); + ttydevsw_pktnotify(tp, TIOCPKT_FLUSHREAD); + } + } +} + +void tty_set_winsize(struct lwp_tty *tp, const struct winsize *wsz) +{ + if (memcmp(&tp->t_winsize, wsz, sizeof(*wsz)) == 0) + return; + tp->t_winsize = *wsz; + lwp_tty_signal_pgrp(tp, SIGWINCH); +} + +static int tty_generic_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, + int fflag, struct rt_thread *td) +{ + int error; + + switch (cmd) + { + /* + * Modem commands. + * The SER_* and TIOCM_* flags are the same, but one bit + * shifted. I don't know why. + */ + case TIOCSDTR: + ttydevsw_modem(tp, SER_DTR, 0); + return 0; + case TIOCCDTR: + ttydevsw_modem(tp, 0, SER_DTR); + return 0; + case TIOCMSET: { + int bits = *(int *)data; + ttydevsw_modem(tp, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, + ((~bits) & (TIOCM_DTR | TIOCM_RTS)) >> 1); + return 0; + } + case TIOCMBIS: { + int bits = *(int *)data; + ttydevsw_modem(tp, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, 0); + return 0; + } + case TIOCMBIC: { + int bits = *(int *)data; + ttydevsw_modem(tp, 0, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1); + return 0; + } + case TIOCMGET: + *(int *)data = TIOCM_LE + (ttydevsw_modem(tp, 0, 0) << 1); + return 0; + + case FIOASYNC: + if (*(int *)data) + tp->t_flags |= TF_ASYNC; + else + tp->t_flags &= ~TF_ASYNC; + return 0; + case FIONBIO: + /* This device supports non-blocking operation. */ + return 0; + case FIONREAD: + *(int *)data = ttyinq_bytescanonicalized(&tp->t_inq); + return 0; + case FIONWRITE: + case TIOCOUTQ: + *(int *)data = ttyoutq_bytesused(&tp->t_outq); + return 0; +#if BSD_USING_FIO_OWNERSHIP + case FIOSETOWN: + if (tp->t_session != NULL && !tty_is_ctty(tp, td->lwp)) + /* Not allowed to set ownership. */ + return -ENOTTY; + + /* Temporarily unlock the TTY to set ownership. */ + tty_unlock(tp); + error = fsetown(*(int *)data, &tp->t_sigio); + tty_lock(tp); + return (error); + case FIOGETOWN: + if (tp->t_session != NULL && !tty_is_ctty(tp, td->lwp)) + /* Not allowed to set ownership. */ + return -ENOTTY; + + /* Get ownership. */ + *(int *)data = fgetown(&tp->t_sigio); + return 0; +#endif + case TIOCGETA: + /* Obtain terminal flags through tcgetattr(). */ + *(struct termios *)data = tp->t_termios; + return 0; + case TIOCSETA: + case TIOCSETAW: + case TIOCSETAF: { + struct termios *t = data; + + /* + * Who makes up these funny rules? According to POSIX, + * input baud rate is set equal to the output baud rate + * when zero. + */ + if (t->__c_ispeed == 0) + t->__c_ispeed = t->__c_ospeed; + + /* Discard any unsupported bits. */ + t->c_iflag &= TTYSUP_IFLAG; + t->c_oflag &= TTYSUP_OFLAG; + t->c_lflag &= TTYSUP_LFLAG; + t->c_cflag &= TTYSUP_CFLAG; + + /* Set terminal flags through tcsetattr(). */ + if (cmd == TIOCSETAW || cmd == TIOCSETAF) + { + error = tty_drain(tp, 0); + if (error) + return (error); + if (cmd == TIOCSETAF) + tty_flush(tp, FREAD); + } + + /* + * Only call param() when the flags really change. + */ + if ((t->c_cflag & CIGNORE) == 0 && + (tp->t_termios.c_cflag != t->c_cflag || + ((tp->t_termios.c_iflag ^ t->c_iflag) & + (IXON | IXOFF | IXANY)) || + tp->t_termios.__c_ispeed != t->__c_ispeed || + tp->t_termios.__c_ospeed != t->__c_ospeed)) + { + error = ttydevsw_param(tp, t); + if (error) + return (error); + + /* XXX: CLOCAL? */ + + tp->t_termios.c_cflag = t->c_cflag & ~CIGNORE; + tp->t_termios.__c_ispeed = t->__c_ispeed; + tp->t_termios.__c_ospeed = t->__c_ospeed; + + /* Baud rate has changed - update watermarks. */ + error = tty_watermarks(tp); + if (error) + return (error); + } + + /* Copy new non-device driver parameters. */ + tp->t_termios.c_iflag = t->c_iflag; + tp->t_termios.c_oflag = t->c_oflag; + tp->t_termios.c_lflag = t->c_lflag; + memcpy(&tp->t_termios.c_cc, t->c_cc, sizeof t->c_cc); + + ttydisc_optimize(tp); + + if ((t->c_lflag & ICANON) == 0) + { + /* + * When in non-canonical mode, wake up all + * readers. Canonicalize any partial input. VMIN + * and VTIME could also be adjusted. + */ + ttyinq_canonicalize(&tp->t_inq); + tty_wakeup(tp, FREAD); + } + + /** + * For packet mode: notify the PTY consumer that VSTOP + * and VSTART may have been changed. + * + * TODO: change the _CONTROL('S') to a CSTOP? + */ + if (tp->t_termios.c_iflag & IXON && + tp->t_termios.c_cc[VSTOP] == _CONTROL('S') && + tp->t_termios.c_cc[VSTART] == _CONTROL('Q')) + ttydevsw_pktnotify(tp, TIOCPKT_DOSTOP); + else + ttydevsw_pktnotify(tp, TIOCPKT_NOSTOP); + return 0; + } + case TIOCGETD: + /* For compatibility - we only support TTYDISC. */ + *(int *)data = TTYDISC; + return 0; + case TIOCGPGRP: + if (!tty_is_ctty(tp, td->lwp)) + return -ENOTTY; + + if (tp->t_pgrp != NULL) + *(int *)data = tp->t_pgrp->pgid; + else + *(int *)data = NO_PID; + return 0; + case TIOCGSID: + if (!tty_is_ctty(tp, td->lwp)) + return -ENOTTY; + + MPASS(tp->t_session); + *(int *)data = tp->t_session->sid; + return 0; + case TIOCNOTTY: + return tty_drop_ctty(tp, td->lwp); + case TIOCSCTTY: + return lwp_tty_set_ctrl_proc(tp, td); + case TIOCSPGRP: { + int pgid; + if (lwp_in_user_space((void *)data)) + { + if (lwp_get_from_user(&pgid, data, sizeof(int)) != sizeof(int)) + return -EFAULT; + } + else + { + pgid = *(int *)data; + } + return lwp_tty_assign_foreground(tp, td, pgid); + } + case TIOCFLUSH: { + int flags = *(int *)data; + + if (flags == 0) + flags = (FREAD | FWRITE); + else + flags &= (FREAD | FWRITE); + tty_flush(tp, flags); + return 0; + } + case TIOCDRAIN: + /* Drain TTY output. */ + return tty_drain(tp, 0); + case TIOCGDRAINWAIT: + *(int *)data = tp->t_drainwait; + return 0; + case TIOCSDRAINWAIT: + error = priv_check(td, PRIV_TTY_DRAINWAIT); + if (error == 0) + tp->t_drainwait = *(int *)data; + return (error); + case TIOCCONS: + /* Set terminal as console TTY. */ + if (*(int *)data) + { + error = priv_check(td, PRIV_TTY_CONSOLE); + if (error) + return (error); + error = constty_set(tp); + } + else + { + error = constty_clear(tp); + } + return (error); + case TIOCGWINSZ: + /* Obtain window size. */ + *(struct winsize *)data = tp->t_winsize; + return 0; + case TIOCSWINSZ: + /* Set window size. */ + tty_set_winsize(tp, data); + return 0; + case TIOCEXCL: + tp->t_flags |= TF_EXCLUDE; + return 0; + case TIOCNXCL: + tp->t_flags &= ~TF_EXCLUDE; + return 0; + case TIOCSTOP: + tp->t_flags |= TF_STOPPED; + ttydevsw_pktnotify(tp, TIOCPKT_STOP); + return 0; + case TIOCSTART: + tp->t_flags &= ~TF_STOPPED; + tp->t_termios.c_lflag &= ~FLUSHO; + ttydevsw_outwakeup(tp); + ttydevsw_pktnotify(tp, TIOCPKT_START); + return 0; + case TIOCSTAT: + tty_info(tp); + return 0; + case TIOCSTI: + if ((fflag & FREAD) == 0 && priv_check(td, PRIV_TTY_STI)) + return -EPERM; + if (!tty_is_ctty(tp, td->lwp) && priv_check(td, PRIV_TTY_STI)) + return -EACCES; + ttydisc_rint(tp, *(char *)data, 0); + ttydisc_rint_done(tp); + return 0; + } + +#ifdef COMPAT_43TTY + return tty_ioctl_compat(tp, cmd, data, fflag, td); +#else /* !COMPAT_43TTY */ + return -ENOIOCTL; +#endif /* COMPAT_43TTY */ +} + +int tty_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, int fflag, + struct rt_thread *td) +{ + int error; + + tty_assert_locked(tp); + + if (tty_gone(tp)) + return -ENXIO; + + error = ttydevsw_ioctl(tp, cmd, data, td); + if (error == -ENOIOCTL) + error = tty_generic_ioctl(tp, cmd, data, fflag, td); + + return error; +} + +int tty_checkoutq(struct lwp_tty *tp) +{ + /* 256 bytes should be enough to print a log message. */ + return (ttyoutq_bytesleft(&tp->t_outq) >= 256); +} + +void tty_hiwat_in_block(struct lwp_tty *tp) +{ + if ((tp->t_flags & TF_HIWAT_IN) == 0 && tp->t_termios.c_iflag & IXOFF && + tp->t_termios.c_cc[VSTOP] != _POSIX_VDISABLE) + { + /* + * Input flow control. Only enter the high watermark when we + * can successfully store the VSTOP character. + */ + if (ttyoutq_write_nofrag(&tp->t_outq, &tp->t_termios.c_cc[VSTOP], 1) == + 0) + tp->t_flags |= TF_HIWAT_IN; + } + else + { + /* No input flow control. */ + tp->t_flags |= TF_HIWAT_IN; + } +} + +void tty_hiwat_in_unblock(struct lwp_tty *tp) +{ + if (tp->t_flags & TF_HIWAT_IN && tp->t_termios.c_iflag & IXOFF && + tp->t_termios.c_cc[VSTART] != _POSIX_VDISABLE) + { + /* + * Input flow control. Only leave the high watermark when we + * can successfully store the VSTART character. + */ + if (ttyoutq_write_nofrag(&tp->t_outq, &tp->t_termios.c_cc[VSTART], 1) == + 0) + tp->t_flags &= ~TF_HIWAT_IN; + } + else + { + /* No input flow control. */ + tp->t_flags &= ~TF_HIWAT_IN; + } + + if (!tty_gone(tp)) + ttydevsw_inwakeup(tp); +} diff --git a/components/lwp/terminal/freebsd/tty_compat.c b/components/lwp/terminal/freebsd/tty_compat.c new file mode 100644 index 0000000000..0f2c70a5c5 --- /dev/null +++ b/components/lwp/terminal/freebsd/tty_compat.c @@ -0,0 +1,711 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * (tty_compat.c) + * The compatible layer which interacts with process management core (lwp) + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#define DBG_TAG "lwp.tty" +#define DBG_LVL DBG_INFO +#include + +#include "../tty_config.h" +#include "../tty_internal.h" +#include "../terminal.h" + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 1994-1995 Søren Schmidt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* is the tty and session leader already binding ? */ +static rt_bool_t _is_already_binding(lwp_tty_t tp, rt_lwp_t p) +{ + rt_bool_t rc; + rt_processgroup_t pgrp = p->pgrp; + + /* lwp is already locked */ + RT_ASSERT(pgrp); + + /* Note: pgrp->session is constant after process group is created */ + if (tp->t_session && tp->t_session == pgrp->session) + { + rc = RT_TRUE; + } + else + { + rc = RT_FALSE; + } + return rc; +} + +static rt_bool_t _is_tty_or_sess_busy(lwp_tty_t tp, rt_lwp_t p) +{ + rt_bool_t rc; + rt_session_t sess = p->pgrp->session; + + SESS_LOCK(sess); + if (sess->ctty) + { + rc = RT_TRUE; + } + else if (tp->t_session) + { + /** + * TODO: allow TTY stolen if the sess leader is killed while resource + * had not been collected + */ + if (tp->t_session->leader == RT_NULL) + rc = RT_FALSE; + else + rc = RT_TRUE; + } + else + { + rc = RT_FALSE; + } + SESS_UNLOCK(sess); + return rc; +} + +int lwp_tty_bg_stop(struct lwp_tty *tp, struct rt_condvar *cv) +{ + int error; + int revokecnt = tp->t_revokecnt; + rt_lwp_t self_lwp; + rt_thread_t header_thr; + rt_thread_t cur_thr = rt_thread_self(); + int jobctl_stopped; + + self_lwp = cur_thr->lwp; + RT_ASSERT(self_lwp); + + jobctl_stopped = self_lwp->jobctl_stopped; + + tty_lock_assert(tp, MA_OWNED | MA_NOTRECURSED); + MPASS(!tty_gone(tp)); + + LWP_LOCK(self_lwp); + header_thr = rt_list_entry(self_lwp->t_grp.prev, struct rt_thread, sibling); + if (!jobctl_stopped && header_thr == cur_thr && + cur_thr->sibling.prev == &self_lwp->t_grp) + { + /* update lwp status */ + jobctl_stopped = self_lwp->jobctl_stopped = RT_TRUE; + } + LWP_UNLOCK(self_lwp); + + error = cv_wait(cv, tp->t_mtx); + + if (jobctl_stopped) + { + self_lwp->jobctl_stopped = RT_FALSE; + } + + /* Bail out when the device slipped away. */ + if (tty_gone(tp)) + return -ENXIO; + + /* Restart the system call when we may have been revoked. */ + if (tp->t_revokecnt != revokecnt) + return -ERESTART; + + return error; +} + +/* process management */ +int lwp_tty_set_ctrl_proc(lwp_tty_t tp, rt_thread_t td) +{ + int rc = -1; + struct rt_lwp *p = td->lwp; + + tty_unlock(tp); + LWP_LOCK(p); + tty_lock(tp); + + if (is_sess_leader(p)) + { + if (_is_already_binding(tp, p)) + { + rc = 0; + } + else if (_is_tty_or_sess_busy(tp, p)) + { + rc = -EPERM; + } + else + { + /** + * Binding controlling process + * note: p->pgrp is protected by lwp lock; + * pgrp->session is always constant. + */ + tp->t_session = p->pgrp->session; + tp->t_session->ctty = tp; + tp->t_sessioncnt++; + + /* Assign foreground process group */ + tp->t_pgrp = p->pgrp; + p->term_ctrlterm = RT_TRUE; + + LOG_D("%s(sid=%d)", __func__, tp->t_session->sid); + rc = 0; + } + } + else + { + rc = -EPERM; + } + + LWP_UNLOCK(p); + + return rc; +} + +int lwp_tty_assign_foreground(lwp_tty_t tp, rt_thread_t td, int pgid) +{ + struct rt_processgroup *pg; + rt_lwp_t cur_lwp = td->lwp; + + tty_unlock(tp); + pg = lwp_pgrp_find_and_inc_ref(pgid); + if (pg == NULL || cur_lwp == NULL) + { + tty_lock(tp); + return -EPERM; + } + else + { + PGRP_LOCK(pg); + + if (pg->sid != cur_lwp->sid) + { + PGRP_UNLOCK(pg); + lwp_pgrp_dec_ref(pg); + LOG_D("%s: NoPerm current process (pid=%d, pgid=%d, sid=%d), " + "tagget group (pgid=%d, sid=%d)", __func__, + cur_lwp->pid, cur_lwp->pgid, cur_lwp->sid, pgid, pg->sid); + tty_lock(tp); + return -EPERM; + } + } + tty_lock(tp); + + /** + * Determine if this TTY is the controlling TTY after + * relocking the TTY. + */ + if (!tty_is_ctty(tp, td->lwp)) + { + PGRP_UNLOCK(pg); + LOG_D("%s: NoCTTY current process (pid=%d, pgid=%d, sid=%d), " + "tagget group (pgid=%d, sid=%d)", __func__, + cur_lwp->pid, cur_lwp->pgid, cur_lwp->sid, pgid, pg->sid); + return -ENOTTY; + } + tp->t_pgrp = pg; + PGRP_UNLOCK(pg); + lwp_pgrp_dec_ref(pg); + + /* Wake up the background process groups. */ + cv_broadcast(&tp->t_bgwait); + + LOG_D("%s: Foreground group %p (pgid=%d)", __func__, tp->t_pgrp, + tp->t_pgrp ? tp->t_pgrp->pgid : -1); + + return 0; +} + +/** + * Signalling processes. + */ + +void lwp_tty_signal_sessleader(struct lwp_tty *tp, int sig) +{ + struct rt_lwp *p; + struct rt_session *s; + + tty_assert_locked(tp); + MPASS(sig >= 1 && sig < _LWP_NSIG); + + /* Make signals start output again. */ + tp->t_flags &= ~TF_STOPPED; + tp->t_termios.c_lflag &= ~FLUSHO; + + /** + * Load s.leader exactly once to avoid race where s.leader is + * set to NULL by a concurrent invocation of killjobc() by the + * session leader. Note that we are not holding t_session's + * lock for the read. + */ + if ((s = tp->t_session) != NULL && + (p = (void *)rt_atomic_load((rt_atomic_t *)&s->leader)) != NULL) + { + lwp_signal_kill(p, sig, SI_KERNEL, 0); + } +} + +void lwp_tty_signal_pgrp(struct lwp_tty *tp, int sig) +{ + tty_assert_locked(tp); + MPASS(sig >= 1 && sig < _LWP_NSIG); + + /* Make signals start output again. */ + tp->t_flags &= ~TF_STOPPED; + tp->t_termios.c_lflag &= ~FLUSHO; + +#ifdef USING_BSD_SIGINFO + if (sig == SIGINFO && !(tp->t_termios.c_lflag & NOKERNINFO)) + tty_info(tp); +#endif /* USING_BSD_SIGINFO */ + + if (tp->t_pgrp != NULL) + { + PGRP_LOCK(tp->t_pgrp); + lwp_pgrp_signal_kill(tp->t_pgrp, sig, SI_KERNEL, 0); + PGRP_UNLOCK(tp->t_pgrp); + } +} + +/* bsd_ttydev_methods.d_ioctl */ + +rt_inline size_t _copy_to_user(void *to, void *from, size_t n) +{ + return lwp_put_to_user(to, from, n) == n ? 0 : -EFAULT; +} + +rt_inline size_t _copy_from_user(void *to, void *from, size_t n) +{ + return lwp_get_from_user(to, from, n) == n ? 0 : -EFAULT; +} + +static void termios_to_termio(struct termios *tios, struct termio *tio) +{ + memset(tio, 0, sizeof(*tio)); + tio->c_iflag = tios->c_iflag; + tio->c_oflag = tios->c_oflag; + tio->c_cflag = tios->c_cflag; + tio->c_lflag = tios->c_lflag; + tio->c_line = tios->c_line; + memcpy(tio->c_cc, tios->c_cc, NCC); +} + +static void termio_to_termios(struct termio *tio, struct termios *tios) +{ + int i; + + tios->c_iflag = tio->c_iflag; + tios->c_oflag = tio->c_oflag; + tios->c_cflag = tio->c_cflag; + tios->c_lflag = tio->c_lflag; + for (i = NCC; i < NCCS; i++) + tios->c_cc[i] = _POSIX_VDISABLE; + memcpy(tios->c_cc, tio->c_cc, NCC); +} + +#define IOCTL(cmd, data, fflags, td) \ + bsd_ttydev_methods.d_ioctl(tp, cmd, data, fflags, td) + +int lwp_tty_ioctl_adapter(lwp_tty_t tp, int cmd, int oflags, void *args, rt_thread_t td) +{ + long fflags = FFLAGS(oflags); + struct termios tios; + struct termio tio; + int error; + + LOG_D("%s(cmd=0x%x, args=%p)", __func__, cmd, args); + switch (cmd & 0xffff) + { + case TCGETS: + error = IOCTL(TIOCGETA, (rt_caddr_t)&tios, fflags, td); + if (error) + break; + error = _copy_to_user(args, &tios, sizeof(tios)); + break; + + case TCSETS: + error = _copy_from_user(&tios, args, sizeof(tios)); + if (error) + break; + error = (IOCTL(TIOCSETA, (rt_caddr_t)&tios, fflags, td)); + break; + + case TCSETSW: + error = _copy_from_user(&tios, args, sizeof(tios)); + if (error) + break; + error = (IOCTL(TIOCSETAW, (rt_caddr_t)&tios, fflags, td)); + break; + + case TCSETSF: + error = _copy_from_user(&tios, args, sizeof(tios)); + if (error) + break; + error = (IOCTL(TIOCSETAF, (rt_caddr_t)&tios, fflags, td)); + break; + + case TCGETA: + error = IOCTL(TIOCGETA, (rt_caddr_t)&tios, fflags, td); + if (error) + break; + termios_to_termio(&tios, &tio); + error = _copy_to_user((void *)args, &tio, sizeof(tio)); + break; + + case TCSETA: + error = _copy_from_user(&tio, (void *)args, sizeof(tio)); + if (error) + break; + termio_to_termios(&tio, &tios); + error = (IOCTL(TIOCSETA, (rt_caddr_t)&tios, fflags, td)); + break; + + case TCSETAW: + error = _copy_from_user(&tio, (void *)args, sizeof(tio)); + if (error) + break; + termio_to_termios(&tio, &tios); + error = (IOCTL(TIOCSETAW, (rt_caddr_t)&tios, fflags, td)); + break; + + case TCSETAF: + error = _copy_from_user(&tio, (void *)args, sizeof(tio)); + if (error) + break; + termio_to_termios(&tio, &tios); + error = (IOCTL(TIOCSETAF, (rt_caddr_t)&tios, fflags, td)); + break; + + case TCSBRK: + if (args != 0) + { + /** + * Linux manual: SVr4, UnixWare, Solaris, and Linux treat + * tcsendbreak(fd,arg) with nonzero arg like tcdrain(fd). + */ + error = IOCTL(TIOCDRAIN, (rt_caddr_t)&tios, fflags, td); + } + else + { + /** + * Linux manual: If the terminal is using asynchronous serial + * data transmission, and arg is zero, then send a break (a + * stream of zero bits) for between 0.25 and 0.5 seconds. + */ + LOG_D("%s: ioctl TCSBRK arg 0 not implemented", __func__); + error = -ENOSYS; + } + break; + +#ifdef USING_BSD_IOCTL_EXT + /* Software flow control */ + case TCXONC: { + switch (args->arg) + { + case TCOOFF: + args->cmd = TIOCSTOP; + break; + case TCOON: + args->cmd = TIOCSTART; + break; + case TCIOFF: + case TCION: { + int c; + struct write_args wr; + error = IOCTL(TIOCGETA, (rt_caddr_t)&tios, fflags, + td); + if (error) + break; + fdrop(fp, td); + c = (args->arg == TCIOFF) ? VSTOP : VSTART; + c = tios.c_cc[c]; + if (c != _POSIX_VDISABLE) + { + wr.fd = args->fd; + wr.buf = &c; + wr.nbyte = sizeof(c); + return (sys_write(td, &wr)); + } + else + return 0; + } + default: + fdrop(fp, td); + return -EINVAL; + } + args->arg = 0; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + } +#endif /* USING_BSD_IOCTL_EXT */ + case TCFLSH: { + int val; + error = 0; + switch ((rt_base_t)args) + { + case TCIFLUSH: + val = FREAD; + break; + case TCOFLUSH: + val = FWRITE; + break; + case TCIOFLUSH: + val = FREAD | FWRITE; + break; + default: + error = -EINVAL; + break; + } + if (!error) + error = (IOCTL(TIOCFLUSH, (rt_caddr_t)&val, fflags, td)); + break; + } + +#ifdef USING_BSD_IOCTL_EXT + case TIOCEXCL: + args->cmd = TIOCEXCL; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case TIOCNXCL: + args->cmd = TIOCNXCL; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; +#endif /* USING_BSD_IOCTL_EXT */ + + /* Controlling terminal */ + case TIOCSCTTY: + case TIOCNOTTY: + + /* Process group and session ID */ + case TIOCGPGRP: + case TIOCSPGRP: + case TIOCGSID: + + /* TIOCOUTQ */ + /* TIOCSTI */ + case TIOCGWINSZ: + case TIOCSWINSZ: + error = IOCTL(cmd, (rt_caddr_t)args, fflags, td); + break; +#ifdef USING_BSD_IOCTL_EXT + case TIOCMGET: + args->cmd = TIOCMGET; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case TIOCMBIS: + args->cmd = TIOCMBIS; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case TIOCMBIC: + args->cmd = TIOCMBIC; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case TIOCMSET: + args->cmd = TIOCMSET; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + /* TIOCGSOFTCAR */ + /* TIOCSSOFTCAR */ + + case FIONREAD: /* TIOCINQ */ + args->cmd = FIONREAD; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + /* TIOCLINUX */ + + case TIOCCONS: + args->cmd = TIOCCONS; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case TIOCGSERIAL: { + struct linux_serial_struct lss; + + bzero(&lss, sizeof(lss)); + lss.type = PORT_16550A; + lss.flags = 0; + lss.close_delay = 0; + error = copyout(&lss, (void *)args->arg, sizeof(lss)); + break; + } + + case TIOCSSERIAL: { + struct linux_serial_struct lss; + error = copyin((void *)args->arg, &lss, sizeof(lss)); + if (error) + break; + /* XXX - It really helps to have an implementation that + * does nothing. NOT! + */ + error = 0; + break; + } + + case TIOCPKT: + args->cmd = TIOCPKT; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case FIONBIO: + args->cmd = FIONBIO; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case TIOCSETD: { + int line; + switch (args->arg) + { + case N_TTY: + line = TTYDISC; + break; + case N_SLIP: + line = SLIPDISC; + break; + case N_PPP: + line = PPPDISC; + break; + default: + fdrop(fp, td); + return -EINVAL; + } + error = (ioctl_emit(TIOCSETD, (rt_caddr_t)&line, fflags, td)); + break; + } + + case TIOCGETD: { + int linux_line; + int bsd_line = TTYDISC; + error = + ioctl_emit(TIOCGETD, (rt_caddr_t)&bsd_line, fflags, td); + if (error) + break; + switch (bsd_line) + { + case TTYDISC: + linux_line = N_TTY; + break; + case SLIPDISC: + linux_line = N_SLIP; + break; + case PPPDISC: + linux_line = N_PPP; + break; + default: + fdrop(fp, td); + return -EINVAL; + } + error = (copyout(&linux_line, (void *)args->arg, sizeof(int))); + break; + } + + /* TCSBRKP */ + /* TIOCTTYGSTRUCT */ + + case FIONCLEX: + args->cmd = FIONCLEX; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case FIOCLEX: + args->cmd = FIOCLEX; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case FIOASYNC: + args->cmd = FIOASYNC; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + /* TIOCSERCONFIG */ + /* TIOCSERGWILD */ + /* TIOCSERSWILD */ + /* TIOCGLCKTRMIOS */ + /* TIOCSLCKTRMIOS */ + + case TIOCSBRK: + args->cmd = TIOCSBRK; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + + case TIOCCBRK: + args->cmd = TIOCCBRK; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; + case TIOCGPTN: { + int nb; + + error = ioctl_emit(TIOCGPTN, (rt_caddr_t)&nb, fflags, td); + if (!error) + error = copyout(&nb, (void *)args->arg, sizeof(int)); + break; + } + case TIOCGPTPEER: + linux_msg(td, "unsupported ioctl TIOCGPTPEER"); + error = -ENOIOCTL; + break; + case TIOCSPTLCK: + /* + * Our unlockpt() does nothing. Check that fd refers + * to a pseudo-terminal master device. + */ + args->cmd = TIOCPTMASTER; + error = (sys_ioctl(td, (struct ioctl_args *)args)); + break; +#endif /* USING_BSD_IOCTL_EXT */ + + /** + * those are for current implementation of devfs, and we dont want to + * log them + */ + case F_DUPFD: + case F_DUPFD_CLOEXEC: + case F_GETFD: + case F_SETFD: + case F_GETFL: + case F_SETFL: + /* fall back to fs */ + error = -ENOIOCTL; + break; + default: + LOG_I("%s: unhandle commands 0x%x", __func__, cmd); + error = -ENOSYS; + break; + } + + return (error); +} diff --git a/components/lwp/terminal/freebsd/tty_inq.c b/components/lwp/terminal/freebsd/tty_inq.c new file mode 100644 index 0000000000..1dc748d6c2 --- /dev/null +++ b/components/lwp/terminal/freebsd/tty_inq.c @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#include "../bsd_porting.h" +#include "../terminal.h" + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * TTY input queue buffering. + * + * Unlike the output queue, the input queue has more features that are + * needed to properly implement various features offered by the TTY + * interface: + * + * - Data can be removed from the tail of the queue, which is used to + * implement backspace. + * - Once in a while, input has to be `canonicalized'. When ICANON is + * turned on, this will be done after a CR has been inserted. + * Otherwise, it should be done after any character has been inserted. + * - The input queue can store one bit per byte, called the quoting bit. + * This bit is used by TTYDISC to make backspace work on quoted + * characters. + * + * In most cases, there is probably less input than output, so unlike + * the outq, we'll stick to 128 byte blocks here. + */ + +static int ttyinq_flush_secure = 1; + +#define TTYINQ_QUOTESIZE (TTYINQ_DATASIZE / BMSIZE) +#define BMSIZE 32 +#define GETBIT(tib, boff) \ + ((tib)->tib_quotes[(boff) / BMSIZE] & (1 << ((boff) % BMSIZE))) +#define SETBIT(tib, boff) \ + ((tib)->tib_quotes[(boff) / BMSIZE] |= (1 << ((boff) % BMSIZE))) +#define CLRBIT(tib, boff) \ + ((tib)->tib_quotes[(boff) / BMSIZE] &= ~(1 << ((boff) % BMSIZE))) + +struct ttyinq_block +{ + struct ttyinq_block *tib_prev; + struct ttyinq_block *tib_next; + uint32_t tib_quotes[TTYINQ_QUOTESIZE]; + char tib_data[TTYINQ_DATASIZE]; +}; + +static uma_zone_t ttyinq_zone; + +#define TTYINQ_INSERT_TAIL(ti, tib) \ + do \ + { \ + if (ti->ti_end == 0) \ + { \ + tib->tib_prev = NULL; \ + tib->tib_next = ti->ti_firstblock; \ + ti->ti_firstblock = tib; \ + } \ + else \ + { \ + tib->tib_prev = ti->ti_lastblock; \ + tib->tib_next = ti->ti_lastblock->tib_next; \ + ti->ti_lastblock->tib_next = tib; \ + } \ + if (tib->tib_next != NULL) \ + tib->tib_next->tib_prev = tib; \ + ti->ti_nblocks++; \ + } while (0) + +#define TTYINQ_REMOVE_HEAD(ti) \ + do \ + { \ + ti->ti_firstblock = ti->ti_firstblock->tib_next; \ + if (ti->ti_firstblock != NULL) \ + ti->ti_firstblock->tib_prev = NULL; \ + ti->ti_nblocks--; \ + } while (0) + +#define TTYINQ_RECYCLE(ti, tib) \ + do \ + { \ + if (ti->ti_quota <= ti->ti_nblocks) \ + uma_zfree(ttyinq_zone, tib); \ + else \ + TTYINQ_INSERT_TAIL(ti, tib); \ + } while (0) + +int ttyinq_setsize(struct ttyinq *ti, struct lwp_tty *tp, size_t size) +{ + struct ttyinq_block *tib; + + ti->ti_quota = howmany(size, TTYINQ_DATASIZE); + + while (ti->ti_quota > ti->ti_nblocks) + { + /* + * List is getting bigger. + * Add new blocks to the tail of the list. + * + * We must unlock the TTY temporarily, because we need + * to allocate memory. This won't be a problem, because + * in the worst case, another thread ends up here, which + * may cause us to allocate too many blocks, but this + * will be caught by the loop below. + */ + tty_unlock(tp); + tib = uma_zalloc(ttyinq_zone, M_WAITOK); + tty_lock(tp); + + if (tty_gone(tp)) + { + uma_zfree(ttyinq_zone, tib); + return -ENXIO; + } + + TTYINQ_INSERT_TAIL(ti, tib); + } + return 0; +} + +void ttyinq_free(struct ttyinq *ti) +{ + struct ttyinq_block *tib; + + ttyinq_flush(ti); + ti->ti_quota = 0; + + while ((tib = ti->ti_firstblock) != NULL) + { + TTYINQ_REMOVE_HEAD(ti); + uma_zfree(ttyinq_zone, tib); + } + + MPASS(ti->ti_nblocks == 0); +} + +int ttyinq_read_uio(struct ttyinq *ti, struct lwp_tty *tp, struct uio *uio, + size_t rlen, size_t flen) +{ + MPASS(rlen <= uio->uio_resid); + + while (rlen > 0) + { + int error; + struct ttyinq_block *tib; + size_t cbegin, cend, clen; + + /* See if there still is data. */ + if (ti->ti_begin == ti->ti_linestart) + return 0; + tib = ti->ti_firstblock; + if (tib == NULL) + return 0; + + /* + * The end address should be the lowest of these three: + * - The write pointer + * - The blocksize - we can't read beyond the block + * - The end address if we could perform the full read + */ + cbegin = ti->ti_begin; + cend = MIN(MIN(ti->ti_linestart, ti->ti_begin + rlen), TTYINQ_DATASIZE); + clen = cend - cbegin; + MPASS(clen >= flen); + rlen -= clen; + + /* + * We can prevent buffering in some cases: + * - We need to read the block until the end. + * - We don't need to read the block until the end, but + * there is no data beyond it, which allows us to move + * the write pointer to a new block. + */ + if (cend == TTYINQ_DATASIZE || cend == ti->ti_end) + { + /* + * Fast path: zero copy. Remove the first block, + * so we can unlock the TTY temporarily. + */ + TTYINQ_REMOVE_HEAD(ti); + ti->ti_begin = 0; + + /* + * Because we remove the first block, we must + * fix up the block offsets. + */ +#define CORRECT_BLOCK(t) \ + do \ + { \ + if (t <= TTYINQ_DATASIZE) \ + t = 0; \ + else \ + t -= TTYINQ_DATASIZE; \ + } while (0) + CORRECT_BLOCK(ti->ti_linestart); + CORRECT_BLOCK(ti->ti_reprint); + CORRECT_BLOCK(ti->ti_end); +#undef CORRECT_BLOCK + + /* + * Temporary unlock and copy the data to + * userspace. We may need to flush trailing + * bytes, like EOF characters. + */ + tty_unlock(tp); + error = uiomove(tib->tib_data + cbegin, clen - flen, uio); + tty_lock(tp); + + /* Block can now be readded to the list. */ + TTYINQ_RECYCLE(ti, tib); + } + else + { + char ob[TTYINQ_DATASIZE - 1]; + + /* + * Slow path: store data in a temporary buffer. + */ + memcpy(ob, tib->tib_data + cbegin, clen - flen); + ti->ti_begin += clen; + MPASS(ti->ti_begin < TTYINQ_DATASIZE); + + /* Temporary unlock and copy the data to userspace. */ + tty_unlock(tp); + error = uiomove(ob, clen - flen, uio); + tty_lock(tp); + } + + if (error != 0) + return error; + if (tty_gone(tp)) + return -ENXIO; + } + + return 0; +} + +rt_inline void ttyinq_set_quotes(struct ttyinq_block *tib, size_t offset, + size_t length, int value) +{ + if (value) + { + /* Set the bits. */ + for (; length > 0; length--, offset++) SETBIT(tib, offset); + } + else + { + /* Unset the bits. */ + for (; length > 0; length--, offset++) CLRBIT(tib, offset); + } +} + +size_t ttyinq_write(struct ttyinq *ti, const void *buf, size_t nbytes, + int quote) +{ + const char *cbuf = buf; + struct ttyinq_block *tib; + unsigned int boff; + size_t l; + + while (nbytes > 0) + { + boff = ti->ti_end % TTYINQ_DATASIZE; + + if (ti->ti_end == 0) + { + /* First time we're being used or drained. */ + MPASS(ti->ti_begin == 0); + tib = ti->ti_firstblock; + if (tib == NULL) + { + /* Queue has no blocks. */ + break; + } + ti->ti_lastblock = tib; + } + else if (boff == 0) + { + /* We reached the end of this block on last write. */ + tib = ti->ti_lastblock->tib_next; + if (tib == NULL) + { + /* We've reached the watermark. */ + break; + } + ti->ti_lastblock = tib; + } + else + { + tib = ti->ti_lastblock; + } + + /* Don't copy more than was requested. */ + l = MIN(nbytes, TTYINQ_DATASIZE - boff); + MPASS(l > 0); + memcpy(tib->tib_data + boff, cbuf, l); + + /* Set the quoting bits for the proper region. */ + ttyinq_set_quotes(tib, boff, l, quote); + + cbuf += l; + nbytes -= l; + ti->ti_end += l; + } + + return (cbuf - (const char *)buf); +} + +int ttyinq_write_nofrag(struct ttyinq *ti, const void *buf, size_t nbytes, + int quote) +{ + size_t ret __unused; + + if (ttyinq_bytesleft(ti) < nbytes) + return -1; + + /* We should always be able to write it back. */ + ret = ttyinq_write(ti, buf, nbytes, quote); + MPASS(ret == nbytes); + + return 0; +} + +void ttyinq_canonicalize(struct ttyinq *ti) +{ + ti->ti_linestart = ti->ti_reprint = ti->ti_end; + ti->ti_startblock = ti->ti_reprintblock = ti->ti_lastblock; +} + +size_t ttyinq_findchar(struct ttyinq *ti, const char *breakc, size_t maxlen, + char *lastc) +{ + struct ttyinq_block *tib = ti->ti_firstblock; + unsigned int boff = ti->ti_begin; + unsigned int bend = + MIN(MIN(TTYINQ_DATASIZE, ti->ti_linestart), ti->ti_begin + maxlen); + + MPASS(maxlen > 0); + + if (tib == NULL) + return 0; + + while (boff < bend) + { + if (strchr(breakc, tib->tib_data[boff]) && !GETBIT(tib, boff)) + { + *lastc = tib->tib_data[boff]; + return (boff - ti->ti_begin + 1); + } + boff++; + } + + /* Not found - just process the entire block. */ + return (bend - ti->ti_begin); +} + +void ttyinq_flush(struct ttyinq *ti) +{ + struct ttyinq_block *tib; + + ti->ti_begin = 0; + ti->ti_linestart = 0; + ti->ti_reprint = 0; + ti->ti_end = 0; + + /* Zero all data in the input queue to get rid of passwords. */ + if (ttyinq_flush_secure) + { + for (tib = ti->ti_firstblock; tib != NULL; tib = tib->tib_next) + memset(&tib->tib_data, 0, sizeof tib->tib_data); + } +} + +int ttyinq_peekchar(struct ttyinq *ti, char *c, int *quote) +{ + unsigned int boff; + struct ttyinq_block *tib = ti->ti_lastblock; + + if (ti->ti_linestart == ti->ti_end) + return -1; + + MPASS(ti->ti_end > 0); + boff = (ti->ti_end - 1) % TTYINQ_DATASIZE; + + *c = tib->tib_data[boff]; + *quote = GETBIT(tib, boff); + + return 0; +} + +void ttyinq_unputchar(struct ttyinq *ti) +{ + MPASS(ti->ti_linestart < ti->ti_end); + + if (--ti->ti_end % TTYINQ_DATASIZE == 0) + { + /* Roll back to the previous block. */ + ti->ti_lastblock = ti->ti_lastblock->tib_prev; + /* + * This can only fail if we are unputchar()'ing the + * first character in the queue. + */ + MPASS((ti->ti_lastblock == NULL) == (ti->ti_end == 0)); + } +} + +void ttyinq_reprintpos_set(struct ttyinq *ti) +{ + ti->ti_reprint = ti->ti_end; + ti->ti_reprintblock = ti->ti_lastblock; +} + +void ttyinq_reprintpos_reset(struct ttyinq *ti) +{ + ti->ti_reprint = ti->ti_linestart; + ti->ti_reprintblock = ti->ti_startblock; +} + +static void ttyinq_line_iterate(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, void *data, + unsigned int offset, struct ttyinq_block *tib) +{ + unsigned int boff; + + /* Use the proper block when we're at the queue head. */ + if (offset == 0) + tib = ti->ti_firstblock; + + /* Iterate all characters and call the iterator function. */ + for (; offset < ti->ti_end; offset++) + { + boff = offset % TTYINQ_DATASIZE; + MPASS(tib != NULL); + + /* Call back the iterator function. */ + iterator(data, tib->tib_data[boff], GETBIT(tib, boff)); + + /* Last byte iterated - go to the next block. */ + if (boff == TTYINQ_DATASIZE - 1) + tib = tib->tib_next; + } +} + +void ttyinq_line_iterate_from_linestart(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, + void *data) +{ + ttyinq_line_iterate(ti, iterator, data, ti->ti_linestart, + ti->ti_startblock); +} + +void ttyinq_line_iterate_from_reprintpos(struct ttyinq *ti, + ttyinq_line_iterator_t *iterator, + void *data) +{ + ttyinq_line_iterate(ti, iterator, data, ti->ti_reprint, + ti->ti_reprintblock); +} + +static int ttyinq_startup(void) +{ + ttyinq_zone = uma_zcreate("ttyinq", sizeof(struct ttyinq_block), NULL, NULL, + NULL, NULL, UMA_ALIGN_PTR, 0); + return 0; +} +INIT_PREV_EXPORT(ttyinq_startup); + +#if 0 + +SYSINIT(ttyinq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyinq_startup, NULL); +#endif diff --git a/components/lwp/terminal/freebsd/tty_outq.c b/components/lwp/terminal/freebsd/tty_outq.c new file mode 100644 index 0000000000..214309813e --- /dev/null +++ b/components/lwp/terminal/freebsd/tty_outq.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#include "../bsd_porting.h" +#include "../terminal.h" + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * TTY output queue buffering. + * + * The previous design of the TTY layer offered the so-called clists. + * These clists were used for both the input queues and the output + * queue. We don't use certain features on the output side, like quoting + * bits for parity marking and such. This mechanism is similar to the + * old clists, but only contains the features we need to buffer the + * output. + */ + +struct ttyoutq_block +{ + struct ttyoutq_block *tob_next; + char tob_data[TTYOUTQ_DATASIZE]; +}; + +static uma_zone_t ttyoutq_zone; + +#define TTYOUTQ_INSERT_TAIL(to, tob) \ + do \ + { \ + if (to->to_end == 0) \ + { \ + tob->tob_next = to->to_firstblock; \ + to->to_firstblock = tob; \ + } \ + else \ + { \ + tob->tob_next = to->to_lastblock->tob_next; \ + to->to_lastblock->tob_next = tob; \ + } \ + to->to_nblocks++; \ + } while (0) + +#define TTYOUTQ_REMOVE_HEAD(to) \ + do \ + { \ + to->to_firstblock = to->to_firstblock->tob_next; \ + to->to_nblocks--; \ + } while (0) + +#define TTYOUTQ_RECYCLE(to, tob) \ + do \ + { \ + if (to->to_quota <= to->to_nblocks) \ + uma_zfree(ttyoutq_zone, tob); \ + else \ + TTYOUTQ_INSERT_TAIL(to, tob); \ + } while (0) + +void ttyoutq_flush(struct ttyoutq *to) +{ + to->to_begin = 0; + to->to_end = 0; +} + +int ttyoutq_setsize(struct ttyoutq *to, struct lwp_tty *tp, size_t size) +{ + struct ttyoutq_block *tob; + + to->to_quota = howmany(size, TTYOUTQ_DATASIZE); + + while (to->to_quota > to->to_nblocks) + { + /* + * List is getting bigger. + * Add new blocks to the tail of the list. + * + * We must unlock the TTY temporarily, because we need + * to allocate memory. This won't be a problem, because + * in the worst case, another thread ends up here, which + * may cause us to allocate too many blocks, but this + * will be caught by the loop below. + */ + tty_unlock(tp); + tob = uma_zalloc(ttyoutq_zone, M_WAITOK); + tty_lock(tp); + + if (tty_gone(tp)) + { + uma_zfree(ttyoutq_zone, tob); + return -ENXIO; + } + + TTYOUTQ_INSERT_TAIL(to, tob); + } + return 0; +} + +void ttyoutq_free(struct ttyoutq *to) +{ + struct ttyoutq_block *tob; + + ttyoutq_flush(to); + to->to_quota = 0; + + while ((tob = to->to_firstblock) != NULL) + { + TTYOUTQ_REMOVE_HEAD(to); + uma_zfree(ttyoutq_zone, tob); + } + + MPASS(to->to_nblocks == 0); +} + +size_t ttyoutq_read(struct ttyoutq *to, void *buf, size_t len) +{ + char *cbuf = buf; + + while (len > 0) + { + struct ttyoutq_block *tob; + size_t cbegin, cend, clen; + + /* See if there still is data. */ + if (to->to_begin == to->to_end) + break; + tob = to->to_firstblock; + if (tob == NULL) + break; + + /* + * The end address should be the lowest of these three: + * - The write pointer + * - The blocksize - we can't read beyond the block + * - The end address if we could perform the full read + */ + cbegin = to->to_begin; + cend = MIN(MIN(to->to_end, to->to_begin + len), TTYOUTQ_DATASIZE); + clen = cend - cbegin; + + /* Copy the data out of the buffers. */ + memcpy(cbuf, tob->tob_data + cbegin, clen); + cbuf += clen; + len -= clen; + + if (cend == to->to_end) + { + /* Read the complete queue. */ + to->to_begin = 0; + to->to_end = 0; + } + else if (cend == TTYOUTQ_DATASIZE) + { + /* Read the block until the end. */ + TTYOUTQ_REMOVE_HEAD(to); + to->to_begin = 0; + to->to_end -= TTYOUTQ_DATASIZE; + TTYOUTQ_RECYCLE(to, tob); + } + else + { + /* Read the block partially. */ + to->to_begin += clen; + } + } + + return cbuf - (char *)buf; +} + +/* + * An optimized version of ttyoutq_read() which can be used in pseudo + * TTY drivers to directly copy data from the outq to userspace, instead + * of buffering it. + * + * We can only copy data directly if we need to read the entire block + * back to the user, because we temporarily remove the block from the + * queue. Otherwise we need to copy it to a temporary buffer first, to + * make sure data remains in the correct order. + */ +int ttyoutq_read_uio(struct ttyoutq *to, struct lwp_tty *tp, struct uio *uio) +{ + while (uio->uio_resid > 0) + { + int error; + struct ttyoutq_block *tob; + size_t cbegin, cend, clen; + + /* See if there still is data. */ + if (to->to_begin == to->to_end) + return 0; + tob = to->to_firstblock; + if (tob == NULL) + return 0; + + /* + * The end address should be the lowest of these three: + * - The write pointer + * - The blocksize - we can't read beyond the block + * - The end address if we could perform the full read + */ + cbegin = to->to_begin; + cend = MIN(MIN(to->to_end, to->to_begin + uio->uio_resid), + TTYOUTQ_DATASIZE); + clen = cend - cbegin; + + /* + * We can prevent buffering in some cases: + * - We need to read the block until the end. + * - We don't need to read the block until the end, but + * there is no data beyond it, which allows us to move + * the write pointer to a new block. + */ + if (cend == TTYOUTQ_DATASIZE || cend == to->to_end) + { + /* + * Fast path: zero copy. Remove the first block, + * so we can unlock the TTY temporarily. + */ + TTYOUTQ_REMOVE_HEAD(to); + to->to_begin = 0; + if (to->to_end <= TTYOUTQ_DATASIZE) + to->to_end = 0; + else + to->to_end -= TTYOUTQ_DATASIZE; + + /* Temporary unlock and copy the data to userspace. */ + tty_unlock(tp); + error = uiomove(tob->tob_data + cbegin, clen, uio); + tty_lock(tp); + + /* Block can now be readded to the list. */ + TTYOUTQ_RECYCLE(to, tob); + } + else + { + char ob[TTYOUTQ_DATASIZE - 1]; + + /* + * Slow path: store data in a temporary buffer. + */ + memcpy(ob, tob->tob_data + cbegin, clen); + to->to_begin += clen; + MPASS(to->to_begin < TTYOUTQ_DATASIZE); + + /* Temporary unlock and copy the data to userspace. */ + tty_unlock(tp); + error = uiomove(ob, clen, uio); + tty_lock(tp); + } + + if (error != 0) + return error; + } + + return 0; +} + +size_t ttyoutq_write(struct ttyoutq *to, const void *buf, size_t nbytes) +{ + const char *cbuf = buf; + struct ttyoutq_block *tob; + unsigned int boff; + size_t l; + + while (nbytes > 0) + { + boff = to->to_end % TTYOUTQ_DATASIZE; + + if (to->to_end == 0) + { + /* First time we're being used or drained. */ + MPASS(to->to_begin == 0); + tob = to->to_firstblock; + if (tob == NULL) + { + /* Queue has no blocks. */ + break; + } + to->to_lastblock = tob; + } + else if (boff == 0) + { + /* We reached the end of this block on last write. */ + tob = to->to_lastblock->tob_next; + if (tob == NULL) + { + /* We've reached the watermark. */ + break; + } + to->to_lastblock = tob; + } + else + { + tob = to->to_lastblock; + } + + /* Don't copy more than was requested. */ + l = MIN(nbytes, TTYOUTQ_DATASIZE - boff); + MPASS(l > 0); + memcpy(tob->tob_data + boff, cbuf, l); + + cbuf += l; + nbytes -= l; + to->to_end += l; + } + + return (cbuf - (const char *)buf); +} + +int ttyoutq_write_nofrag(struct ttyoutq *to, const void *buf, size_t nbytes) +{ + size_t ret __unused; + + if (ttyoutq_bytesleft(to) < nbytes) + return -1; + + /* We should always be able to write it back. */ + ret = ttyoutq_write(to, buf, nbytes); + MPASS(ret == nbytes); + + return 0; +} + +static int ttyoutq_startup(void) +{ + ttyoutq_zone = uma_zcreate("ttyoutq", sizeof(struct ttyoutq_block), NULL, + NULL, NULL, NULL, UMA_ALIGN_PTR, 0); + return 0; +} +INIT_PREV_EXPORT(ttyoutq_startup); + +#if 0 +SYSINIT(ttyoutq, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyoutq_startup, NULL); +#endif diff --git a/components/lwp/terminal/freebsd/tty_pts.c b/components/lwp/terminal/freebsd/tty_pts.c new file mode 100644 index 0000000000..2c75ff3009 --- /dev/null +++ b/components/lwp/terminal/freebsd/tty_pts.c @@ -0,0 +1,837 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-12-07 Shell init ver. + */ + +#include +#include +#include +#include +#include + +#include + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#define PTS_EXTERNAL + +/* + * Per-PTS structure. + * + * List of locks + * (t) locked by tty_lock() + * (c) const until freeing + */ +struct pts_softc +{ + int pts_unit; /* (c) Device unit number. */ + unsigned int pts_flags; /* (t) Device flags. */ +#define PTS_PKT 0x1 /* Packet mode. */ +#define PTS_FINISHED 0x2 /* Return errors on read()/write(). */ +#define PTS_PTLOCKED 0x4 /* ioctl %TIOCSPTLCK/%TIOCGPTLCK */ + char pts_pkt; /* (t) Unread packet mode data. */ + + struct rt_condvar pts_inwait; /* (t) Blocking write() on master. */ + struct rt_wqueue pts_inpoll; /* (t) Select queue for write(). */ + struct rt_condvar pts_outwait; /* (t) Blocking read() on master. */ + struct rt_wqueue pts_outpoll; /* (t) Select queue for read(). */ + + struct ucred *pts_cred; /* (c) Resource limit. */ + rt_device_t pts_master; /** (c) Master device. + * (Note: in rtsmart kernel, we support + * multi-instance ptmx ) + */ +}; + +/** + * Controller-side file operations. + * (P)seudo-(T)erminal (M)ultiple(X)er + */ + +static int ptsdev_read(struct lwp_tty *tp, struct uio *uio, + struct ucred *active_cred, int oflags, + struct rt_thread *td) +{ + struct pts_softc *psc = tty_softc(tp); + int error = 0; + char pkt; + + if (uio->uio_resid == 0) + return (0); + + tty_lock(tp); + + for (;;) + { + /* + * Implement packet mode. When packet mode is turned on, + * the first byte contains a bitmask of events that + * occurred (start, stop, flush, window size, etc). + */ + if (psc->pts_flags & PTS_PKT && psc->pts_pkt) + { + pkt = psc->pts_pkt; + psc->pts_pkt = 0; + tty_unlock(tp); + + error = uiomove(&pkt, 1, uio); + return (error); + } + + /* + * Transmit regular data. + * + * XXX: We shouldn't use ttydisc_getc_poll()! Even + * though in this implementation, there is likely going + * to be data, we should just call ttydisc_getc_uio() + * and use its return value to sleep. + */ + if (ttydisc_getc_poll(tp)) + { + if (psc->pts_flags & PTS_PKT) + { + /* + * XXX: Small race. Fortunately PTY + * consumers aren't multithreaded. + */ + + tty_unlock(tp); + pkt = TIOCPKT_DATA; + error = uiomove(&pkt, 1, uio); + if (error) + return (error); + tty_lock(tp); + } + + error = ttydisc_getc_uio(tp, uio); + break; + } + + /* Maybe the device isn't used anyway. */ + if (psc->pts_flags & PTS_FINISHED) + break; + + /* Wait for more data. */ + if (oflags & O_NONBLOCK) + { + error = EWOULDBLOCK; + break; + } + error = cv_wait_sig(&psc->pts_outwait, tp->t_mtx); + if (error != 0) + break; + } + + tty_unlock(tp); + + return (error); +} + +static int ptsdev_write(struct lwp_tty *tp, struct uio *uio, + struct ucred *active_cred, int oflags, + struct rt_thread *td) +{ + struct pts_softc *psc = tty_softc(tp); + char ib[256], *ibstart; + size_t iblen, rintlen; + int error = 0; + + if (uio->uio_resid == 0) + return (0); + + for (;;) + { + ibstart = ib; + iblen = MIN(uio->uio_resid, sizeof ib); + error = uiomove(ib, iblen, uio); + + tty_lock(tp); + if (error != 0) + { + iblen = 0; + goto done; + } + + /* + * When possible, avoid the slow path. rint_bypass() + * copies all input to the input queue at once. + */ + MPASS(iblen > 0); + do + { + rintlen = ttydisc_rint_simple(tp, ibstart, iblen); + ibstart += rintlen; + iblen -= rintlen; + if (iblen == 0) + { + /* All data written. */ + break; + } + + /* Maybe the device isn't used anyway. */ + if (psc->pts_flags & PTS_FINISHED) + { + error = -EIO; + goto done; + } + + /* Wait for more data. */ + if (oflags & O_NONBLOCK) + { + error = -EWOULDBLOCK; + goto done; + } + + /* Wake up users on the slave side. */ + ttydisc_rint_done(tp); + error = cv_wait_sig(&psc->pts_inwait, tp->t_mtx); + if (error != 0) + goto done; + } while (iblen > 0); + + if (uio->uio_resid == 0) + break; + tty_unlock(tp); + } + +done: + ttydisc_rint_done(tp); + tty_unlock(tp); + + /* + * Don't account for the part of the buffer that we couldn't + * pass to the TTY. + */ + uio->uio_resid += iblen; + return (error); +} + +static int ptsdev_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, + struct ucred *active_cred, int fflags, + struct rt_thread *td) +{ + struct pts_softc *psc = tty_softc(tp); + int error = 0, sig; + + switch (cmd) + { +#ifdef USING_BSD_IOCTL_EXT + case FIODTYPE: + *(int *)data = D_TTY; + return (0); +#endif + case FIONBIO: + /* This device supports non-blocking operation. */ + return (0); + case FIONREAD: + tty_lock(tp); + if (psc->pts_flags & PTS_FINISHED) + { + /* Force read() to be called. */ + *(int *)data = 1; + } + else + { + *(int *)data = ttydisc_getc_poll(tp); + } + tty_unlock(tp); + return (0); +#ifdef USING_BSD_IOCTL_EXT + case FIODGNAME: +#ifdef COMPAT_FREEBSD32 + case FIODGNAME_32: +#endif + { + struct fiodgname_arg *fgn; + const char *p; + int i; + + /* Reverse device name lookups, for ptsname() and ttyname(). */ + fgn = data; + p = tty_devname(tp); + i = strlen(p) + 1; + if (i > fgn->len) + return -EINVAL; + return (copyout(p, fiodgname_buf_get_ptr(fgn, cmd), i)); + } +#endif + /* + * We need to implement TIOCGPGRP and TIOCGSID here again. When + * called on the pseudo-terminal master, it should not check if + * the terminal is the foreground terminal of the calling + * process. + * + * TIOCGETA is also implemented here. Various Linux PTY routines + * often call isatty(), which is implemented by tcgetattr(). + */ + case TIOCGETA: + /* Obtain terminal flags through tcgetattr(). */ + tty_lock(tp); + *(struct termios *)data = tp->t_termios; + tty_unlock(tp); + return (0); + case TIOCSETAF: + case TIOCSETAW: + /* + * We must make sure we turn tcsetattr() calls of TCSAFLUSH and + * TCSADRAIN into something different. If an application would + * call TCSAFLUSH or TCSADRAIN on the master descriptor, it may + * deadlock waiting for all data to be read. + */ + cmd = TIOCSETA; + break; + case TIOCGPTN: + /* + * Get the device unit number. + */ + if (psc->pts_unit < 0) + return -ENOTTY; + *(unsigned int *)data = psc->pts_unit; + return (0); + case TIOCGPGRP: + /* Get the foreground process group ID. */ + tty_lock(tp); + if (tp->t_pgrp != NULL) + *(int *)data = tp->t_pgrp->pgid; + else + *(int *)data = NO_PID; + tty_unlock(tp); + return (0); + case TIOCGSID: + /* Get the session leader process ID. */ + tty_lock(tp); + if (tp->t_session == NULL) + error = -ENOTTY; + else + *(int *)data = tp->t_session->sid; + tty_unlock(tp); + return (error); +#ifdef USING_BSD_IOCTL_EXT + case TIOCPTMASTER: + /* Yes, we are a pseudo-terminal master. */ + return (0); +#endif /* USING_BSD_IOCTL_EXT */ + case TIOCSIG: + /* Signal the foreground process group. */ + sig = *(int *)data; + if (sig < 1 || sig >= _LWP_NSIG) + return -EINVAL; + + tty_lock(tp); + lwp_tty_signal_pgrp(tp, sig); + tty_unlock(tp); + return (0); + case TIOCPKT: + /* Enable/disable packet mode. */ + tty_lock(tp); + if (*(int *)data) + psc->pts_flags |= PTS_PKT; + else + psc->pts_flags &= ~PTS_PKT; + tty_unlock(tp); + return (0); + } + + /* Just redirect this ioctl to the slave device. */ + tty_lock(tp); + error = tty_ioctl(tp, cmd, data, fflags, td); + tty_unlock(tp); + if (error == -ENOIOCTL) + error = -ENOTTY; + + return error; +} + +static int ptsdev_poll(struct lwp_tty *tp, struct rt_pollreq *req, + struct ucred *active_cred, struct rt_thread *td) +{ + struct pts_softc *psc = tty_softc(tp); + int revents = 0; + int events = req->_key; + + tty_lock(tp); + + if (psc->pts_flags & PTS_FINISHED) + { + /* Slave device is not opened. */ + tty_unlock(tp); + return ((events & (POLLIN | POLLRDNORM)) | POLLHUP); + } + + if (events & (POLLIN | POLLRDNORM)) + { + /* See if we can getc something. */ + if (ttydisc_getc_poll(tp) || (psc->pts_flags & PTS_PKT && psc->pts_pkt)) + revents |= events & (POLLIN | POLLRDNORM); + } + if (events & (POLLOUT | POLLWRNORM)) + { + /* See if we can rint something. */ + if (ttydisc_rint_poll(tp)) + revents |= events & (POLLOUT | POLLWRNORM); + } + + /* + * No need to check for POLLHUP here. This device cannot be used + * as a callout device, which means we always have a carrier, + * because the master is. + */ + + if (revents == 0) + { + /* + * This code might look misleading, but the naming of + * poll events on this side is the opposite of the slave + * device. + */ + if (events & (POLLIN | POLLRDNORM)) + rt_poll_add(&psc->pts_outpoll, req); + if (events & (POLLOUT | POLLWRNORM)) + rt_poll_add(&psc->pts_inpoll, req); + } + + tty_unlock(tp); + + return (revents); +} + +#if USING_BSD_KQUEUE +/* + * kqueue support. + */ + +static void pts_kqops_read_detach(struct knote *kn) +{ + struct file *fp = kn->kn_fp; + struct lwp_tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + + knlist_remove(&psc->pts_outpoll.si_note, kn, 0); +} + +static int pts_kqops_read_event(struct knote *kn, long hint) +{ + struct file *fp = kn->kn_fp; + struct lwp_tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + + if (psc->pts_flags & PTS_FINISHED) + { + kn->kn_flags |= EV_EOF; + return (1); + } + else + { + kn->kn_data = ttydisc_getc_poll(tp); + return (kn->kn_data > 0); + } +} + +static void pts_kqops_write_detach(struct knote *kn) +{ + struct file *fp = kn->kn_fp; + struct lwp_tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + + knlist_remove(&psc->pts_inpoll.si_note, kn, 0); +} + +static int pts_kqops_write_event(struct knote *kn, long hint) +{ + struct file *fp = kn->kn_fp; + struct lwp_tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + + if (psc->pts_flags & PTS_FINISHED) + { + kn->kn_flags |= EV_EOF; + return (1); + } + else + { + kn->kn_data = ttydisc_rint_poll(tp); + return (kn->kn_data > 0); + } +} + +static struct filterops pts_kqops_read = { + .f_isfd = 1, + .f_detach = pts_kqops_read_detach, + .f_event = pts_kqops_read_event, +}; +static struct filterops pts_kqops_write = { + .f_isfd = 1, + .f_detach = pts_kqops_write_detach, + .f_event = pts_kqops_write_event, +}; + +static int ptsdev_kqfilter(struct file *fp, struct knote *kn) +{ + struct lwp_tty *tp = fp->f_data; + struct pts_softc *psc = tty_softc(tp); + int error = 0; + + tty_lock(tp); + + switch (kn->kn_filter) + { + case EVFILT_READ: + kn->kn_fop = &pts_kqops_read; + knlist_add(&psc->pts_outpoll.si_note, kn, 1); + break; + case EVFILT_WRITE: + kn->kn_fop = &pts_kqops_write; + knlist_add(&psc->pts_inpoll.si_note, kn, 1); + break; + default: + error = EINVAL; + break; + } + + tty_unlock(tp); + return (error); +} +#endif + +#if USING_BSD_STAT +static int ptsdev_stat(struct file *fp, struct stat *sb, + struct ucred *active_cred) +{ + struct lwp_tty *tp = fp->f_data; +#ifdef PTS_EXTERNAL + struct pts_softc *psc = tty_softc(tp); +#endif /* PTS_EXTERNAL */ + struct cdev *dev = tp->t_dev; + + /* + * According to POSIX, we must implement an fstat(). This also + * makes this implementation compatible with Linux binaries, + * because Linux calls fstat() on the pseudo-terminal master to + * obtain st_rdev. + * + * XXX: POSIX also mentions we must fill in st_dev, but how? + */ + + bzero(sb, sizeof *sb); +#ifdef PTS_EXTERNAL + if (psc->pts_cdev != NULL) + sb->st_ino = sb->st_rdev = dev2udev(psc->pts_cdev); + else +#endif /* PTS_EXTERNAL */ + sb->st_ino = sb->st_rdev = tty_udev(tp); + + sb->st_atim = dev->si_atime; + sb->st_ctim = dev->si_ctime; + sb->st_mtim = dev->si_mtime; + sb->st_uid = dev->si_uid; + sb->st_gid = dev->si_gid; + sb->st_mode = dev->si_mode | S_IFCHR; + + return (0); +} +#endif + +static int ptsdev_close(struct lwp_tty *tp, struct rt_thread *td) +{ + /* Deallocate TTY device. */ + tty_lock(tp); + tty_rel_gone(tp); + +#ifdef USING_BSD_VNODE + /* TODO: consider the vnode operation on DFS */ + + /* + * Open of /dev/ptmx or /dev/ptyXX changes the type of file + * from DTYPE_VNODE to DTYPE_PTS. vn_open() increases vnode + * use count, we need to decrement it, and possibly do other + * required cleanup. + */ + if (fp->f_vnode != NULL) + return (vnops.fo_close(fp, td)); +#endif /* USING_BSD_VNODE */ + + return 0; +} + +#ifdef USING_BSD_KINFO +static int ptsdev_fill_kinfo(struct file *fp, struct kinfo_file *kif, + struct filedesc *fdp) +{ + struct lwp_tty *tp; + + kif->kf_type = KF_TYPE_PTS; + tp = fp->f_data; + kif->kf_un.kf_pts.kf_pts_dev = tty_udev(tp); + kif->kf_un.kf_pts.kf_pts_dev_freebsd11 = + kif->kf_un.kf_pts.kf_pts_dev; /* truncate */ + strlcpy(kif->kf_path, tty_devname(tp), sizeof(kif->kf_path)); + return (0); +} +#endif + +struct bsd_fileops bsd_ptsdev_methods = { + .fo_read = ptsdev_read, + .fo_write = ptsdev_write, + // .fo_truncate = invfo_truncate, + .fo_ioctl = ptsdev_ioctl, + .fo_poll = ptsdev_poll, + // .fo_kqfilter = ptsdev_kqfilter, + // .fo_stat = ptsdev_stat, + .fo_close = ptsdev_close, + // .fo_chmod = invfo_chmod, + // .fo_chown = invfo_chown, + // .fo_sendfile = invfo_sendfile, + // .fo_fill_kinfo = ptsdev_fill_kinfo, + .fo_flags = DFLAG_PASSABLE, +}; + +/* + * Driver-side hooks. + */ + +static void ptsdrv_outwakeup(struct lwp_tty *tp) +{ + struct pts_softc *psc = tty_softc(tp); + + cv_broadcast(&psc->pts_outwait); + rt_wqueue_wakeup_all(&psc->pts_outpoll, (void *)POLLIN); +} + +static void ptsdrv_inwakeup(struct lwp_tty *tp) +{ + struct pts_softc *psc = tty_softc(tp); + + cv_broadcast(&psc->pts_inwait); + rt_wqueue_wakeup_all(&psc->pts_inpoll, (void *)POLLOUT); +} + +static int ptsdrv_open(struct lwp_tty *tp) +{ + struct pts_softc *psc = tty_softc(tp); + + /* for ioctl(TIOCSPTLCK) */ + if (psc->pts_flags & PTS_PTLOCKED) + return -EIO; + + psc->pts_flags &= ~PTS_FINISHED; + + return 0; +} + +static void ptsdrv_close(struct lwp_tty *tp) +{ + struct pts_softc *psc = tty_softc(tp); + + /* Wake up any blocked readers/writers. */ + psc->pts_flags |= PTS_FINISHED; + ptsdrv_outwakeup(tp); + ptsdrv_inwakeup(tp); +} + +static void ptsdrv_pktnotify(struct lwp_tty *tp, char event) +{ + struct pts_softc *psc = tty_softc(tp); + + /* + * Clear conflicting flags. + */ + + switch (event) + { + case TIOCPKT_STOP: + psc->pts_pkt &= ~TIOCPKT_START; + break; + case TIOCPKT_START: + psc->pts_pkt &= ~TIOCPKT_STOP; + break; + case TIOCPKT_NOSTOP: + psc->pts_pkt &= ~TIOCPKT_DOSTOP; + break; + case TIOCPKT_DOSTOP: + psc->pts_pkt &= ~TIOCPKT_NOSTOP; + break; + } + + psc->pts_pkt |= event; + + /** + * Note: on smart, we don't wakeup master until it's willing to accept + * packet event. Because on poll, we setup POLLIN for PTS_PKT only. So There + * is a chance when we wakeup ipc but we can't wakeup user again. Since + * current wakeup will remove the wakequeue node on the meanwhile + */ + if (psc->pts_flags & PTS_PKT) + ptsdrv_outwakeup(tp); +} + +static void ptsdrv_free(void *softc) +{ + struct pts_softc *psc = softc; + + /* Make device number available again. */ + if (psc->pts_unit >= 0) + ptyfs_unregister_pts(psc->pts_master, psc->pts_unit); + +#ifdef USING_BSD_UCRED + chgptscnt(psc->pts_cred->cr_ruidinfo, -1, 0); + racct_sub_cred(psc->pts_cred, RACCT_NPTS, 1); + crfree(psc->pts_cred); +#endif + + rt_wqueue_wakeup_all(&psc->pts_inpoll, (void *)POLLHUP); + rt_wqueue_wakeup_all(&psc->pts_outpoll, (void *)POLLHUP); + + rt_free(psc); +} + +static struct lwp_ttydevsw pts_class = { + .tsw_flags = TF_NOPREFIX, + .tsw_outwakeup = ptsdrv_outwakeup, + .tsw_inwakeup = ptsdrv_inwakeup, + .tsw_open = ptsdrv_open, + .tsw_close = ptsdrv_close, + .tsw_pktnotify = ptsdrv_pktnotify, + .tsw_free = ptsdrv_free, +}; + +int pts_alloc(int fflags, struct rt_thread *td, struct dfs_file *ptm_file) +{ + int unit; + struct lwp_tty *tp; + struct pts_softc *psc; + char name_buf[DIRENT_NAME_MAX]; + const char *rootpath; + rt_device_t ptmx_device = ptm_file->vnode->data; + +#ifdef USING_BSD_UCRED + struct rt_lwp *p = td->lwp; + int ok, error; + struct ucred *cred = td->td_ucred; +#endif + + /* Resource limiting. */ +#ifdef USING_BSD_UCRED + LWP_LOCK(p); + error = racct_add(p, RACCT_NPTS, 1); + if (error != 0) + { + LWP_UNLOCK(p); + return -EAGAIN; + } + ok = chgptscnt(cred->cr_ruidinfo, 1, lim_cur(td, RLIMIT_NPTS)); + if (!ok) + { + racct_sub(p, RACCT_NPTS, 1); + LWP_UNLOCK(p); + return -EAGAIN; + } + LWP_UNLOCK(p); +#endif + + /* Allocate TTY and softc. */ + psc = rt_calloc(1, sizeof(struct pts_softc)); + cv_init(&psc->pts_inwait, "ptsin"); + cv_init(&psc->pts_outwait, "ptsout"); + rt_wqueue_init(&psc->pts_inpoll); + rt_wqueue_init(&psc->pts_outpoll); + + psc->pts_master = ptmx_device; +#ifdef USING_BSD_UCRED + psc->pts_cred = crhold(cred); +#else + psc->pts_cred = 0; +#endif + + tp = lwp_tty_create(&pts_class, psc); + if (!tp) + { + rt_free(psc); + rt_condvar_detach(&psc->pts_inwait); + rt_condvar_detach(&psc->pts_outwait); + return -ENOMEM; + } + + /* Try to allocate a new pts uint*/ + unit = ptyfs_register_pts(ptmx_device, &tp->parent); + if (unit < 0) + { +#ifdef USING_BSD_UCRED + racct_sub(p, RACCT_NPTS, 1); + chgptscnt(cred->cr_ruidinfo, -1, 0); +#endif + lwp_tty_delete(tp); + return -EAGAIN; + } + psc->pts_unit = unit; + + /* Expose the slave device as well. */ +#ifdef USING_BSD_UCRED + tty_makedev(tp, td->td_ucred, "pts/%u", psc->pts_unit); +#else + rootpath = ptyfs_get_rootpath(ptmx_device); + RT_ASSERT(rootpath[strlen(rootpath) - 1] != '/'); + snprintf(name_buf, DIRENT_NAME_MAX, "%s/%u", rootpath, psc->pts_unit); + + /* setup the pts */ + lwp_tty_register(tp, name_buf); + + /* now this file operating on new pty */ + ptm_file->vnode->data = tp; +#endif + + return 0; +} + +void pts_set_lock(lwp_tty_t pts, rt_bool_t is_lock) +{ + struct pts_softc *psc = tty_softc(pts); + if (is_lock) + psc->pts_flags |= PTS_PTLOCKED; + else + psc->pts_flags &= ~PTS_PTLOCKED; +} + +rt_bool_t pts_is_locked(lwp_tty_t pts) +{ + struct pts_softc *psc = tty_softc(pts); + return !!(psc->pts_flags & PTS_PTLOCKED); +} + +int pts_get_pktmode(lwp_tty_t pts) +{ + struct pts_softc *psc = tty_softc(pts); + return !!(psc->pts_flags & PTS_PKT); +} diff --git a/components/lwp/terminal/freebsd/tty_ttydisc.c b/components/lwp/terminal/freebsd/tty_ttydisc.c new file mode 100644 index 0000000000..932ef2ec00 --- /dev/null +++ b/components/lwp/terminal/freebsd/tty_ttydisc.c @@ -0,0 +1,1479 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ +#include "../bsd_porting.h" +#include "../tty_config.h" +#include "../tty_internal.h" +#include "../terminal.h" + +#include + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Standard TTYDISC `termios' line discipline. + */ + +/* Statistics. */ +static rt_atomic_t tty_nin = 0; +#ifdef USING_BSD_SYSCTL +SYSCTL_ULONG(_kern, OID_AUTO, tty_nin, CTLFLAG_RD, &tty_nin, 0, + "Total amount of bytes received"); +#endif + +static rt_atomic_t tty_nout = 0; +#ifdef USING_BSD_SYSCTL +SYSCTL_ULONG(_kern, OID_AUTO, tty_nout, CTLFLAG_RD, &tty_nout, 0, + "Total amount of bytes transmitted"); +#endif + +/* termios comparison macro's. */ +#define CMP_CC(v, c) \ + (tp->t_termios.c_cc[v] != _POSIX_VDISABLE && tp->t_termios.c_cc[v] == (c)) +#define CMP_FLAG(field, opt) (tp->t_termios.c_##field##flag & (opt)) + +/* Characters that cannot be modified through c_cc. */ +#define CTAB '\t' +#define CNL '\n' +#define CCR '\r' + +/* Character is a control character. */ +#define CTL_VALID(c) ((c) == 0x7f || (unsigned char)(c) < 0x20) +/* Control character should be processed on echo. */ +#define CTL_ECHO(c, q) \ + (!(q) && ((c) == CERASE2 || (c) == CTAB || (c) == CNL || (c) == CCR)) +/* Control character should be printed using ^X notation. */ +#define CTL_PRINT(c, q) \ + ((c) == 0x7f || \ + ((unsigned char)(c) < 0x20 && ((q) || ((c) != CTAB && (c) != CNL)))) +/* Character is whitespace. */ +#define CTL_WHITE(c) ((c) == ' ' || (c) == CTAB) +/* Character is alphanumeric. */ +#define CTL_ALNUM(c) \ + (((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'z') || \ + ((c) >= 'A' && (c) <= 'Z')) +/* Character is UTF8-encoded. */ +#define CTL_UTF8(c) (!!((c)&0x80)) +/* Character is a UTF8 continuation byte. */ +#define CTL_UTF8_CONT(c) (((c)&0xc0) == 0x80) + +#define TTY_STACKBUF 256 +#define UTF8_STACKBUF 4 + +void ttydisc_open(struct lwp_tty *tp) +{ + ttydisc_optimize(tp); +} + +void ttydisc_close(struct lwp_tty *tp) +{ + /* Clean up our flags when leaving the discipline. */ + tp->t_flags &= ~(TF_STOPPED | TF_HIWAT | TF_ZOMBIE); + tp->t_termios.c_lflag &= ~FLUSHO; + + /* + * POSIX states that we must drain output and flush input on + * last close. Draining has already been done if possible. + */ + tty_flush(tp, FREAD | FWRITE); + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, close)) + ttyhook_close(tp); +#endif +} + +static int ttydisc_read_canonical(struct lwp_tty *tp, struct uio *uio, + int ioflag) +{ + char breakc[4] = {CNL}; /* enough to hold \n, VEOF and VEOL. */ + int error; + size_t clen, flen = 0, n = 1; + unsigned char lastc = _POSIX_VDISABLE; + +#define BREAK_ADD(c) \ + do \ + { \ + if (tp->t_termios.c_cc[c] != _POSIX_VDISABLE) \ + breakc[n++] = tp->t_termios.c_cc[c]; \ + } while (0) + /* Determine which characters we should trigger on. */ + BREAK_ADD(VEOF); + BREAK_ADD(VEOL); +#undef BREAK_ADD + breakc[n] = '\0'; + + do + { + error = tty_wait_background(tp, curthread, SIGTTIN); + if (error) + return error; + + /* + * Quite a tricky case: unlike the old TTY + * implementation, this implementation copies data back + * to userspace in large chunks. Unfortunately, we can't + * calculate the line length on beforehand if it crosses + * ttyinq_block boundaries, because multiple reads could + * then make this code read beyond the newline. + * + * This is why we limit the read to: + * - The size the user has requested + * - The blocksize (done in tty_inq.c) + * - The amount of bytes until the newline + * + * This causes the line length to be recalculated after + * each block has been copied to userspace. This will + * cause the TTY layer to return data in chunks using + * the blocksize (except the first and last blocks). + */ + clen = + ttyinq_findchar(&tp->t_inq, breakc, uio->uio_resid, (char *)&lastc); + + /* No more data. */ + if (clen == 0) + { + if (tp->t_flags & TF_ZOMBIE) + return 0; + else if (ioflag & IO_NDELAY) + return -EWOULDBLOCK; + + error = tty_wait(tp, &tp->t_inwait); + if (error) + return error; + continue; + } + + /* Don't send the EOF char back to userspace. */ + if (CMP_CC(VEOF, lastc)) + flen = 1; + + MPASS(flen <= clen); + + /* Read and throw away the EOF character. */ + error = ttyinq_read_uio(&tp->t_inq, tp, uio, clen, flen); + if (error) + return error; + + /** Notes: proceed when read buf frees and not reaching breaks */ + } while (uio->uio_resid > 0 && lastc == _POSIX_VDISABLE); + + return 0; +} + +static int ttydisc_read_raw_no_timer(struct lwp_tty *tp, struct uio *uio, + int ioflag) +{ + size_t vmin = tp->t_termios.c_cc[VMIN]; + ssize_t oresid = uio->uio_resid; + int error; + + MPASS(tp->t_termios.c_cc[VTIME] == 0); + + /* + * This routine implements the easy cases of read()s while in + * non-canonical mode, namely case B and D, where we don't have + * any timers at all. + */ + + for (;;) + { + error = tty_wait_background(tp, curthread, SIGTTIN); + if (error) + return error; + + error = ttyinq_read_uio(&tp->t_inq, tp, uio, uio->uio_resid, 0); + if (error) + return error; + if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) + return 0; + + /* We have to wait for more. */ + if (tp->t_flags & TF_ZOMBIE) + return 0; + else if (ioflag & IO_NDELAY) + return -EWOULDBLOCK; + + error = tty_wait(tp, &tp->t_inwait); + if (error) + return error; + } +} + +static int ttydisc_read_raw_read_timer(struct lwp_tty *tp, struct uio *uio, + int ioflag, int oresid) +{ + size_t vmin = MAX(tp->t_termios.c_cc[VMIN], 1); + unsigned int vtime = tp->t_termios.c_cc[VTIME]; + struct timeval end, now, left; + int error, hz; + + MPASS(tp->t_termios.c_cc[VTIME] != 0); + + /* Determine when the read should be expired. */ + end.tv_sec = vtime / 10; + end.tv_usec = (vtime % 10) * 100000; + getmicrotime(&now); + timevaladd(&end, &now); + + for (;;) + { + error = tty_wait_background(tp, curthread, SIGTTIN); + if (error) + return error; + + error = ttyinq_read_uio(&tp->t_inq, tp, uio, uio->uio_resid, 0); + if (error) + return error; + if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) + return 0; + + /* Calculate how long we should wait. */ + getmicrotime(&now); + if (timevalcmp(&now, &end, >)) + return 0; + left = end; + timevalsub(&left, &now); + hz = tvtohz(&left); + + /* + * We have to wait for more. If the timer expires, we + * should return a 0-byte read. + */ + if (tp->t_flags & TF_ZOMBIE) + return 0; + else if (ioflag & IO_NDELAY) + return -EWOULDBLOCK; + + error = tty_timedwait(tp, &tp->t_inwait, hz); + if (error) + return (error == -EWOULDBLOCK ? 0 : error); + } + + return 0; +} + +static int ttydisc_read_raw_interbyte_timer(struct lwp_tty *tp, struct uio *uio, + int ioflag) +{ + size_t vmin = tp->t_termios.c_cc[VMIN]; + ssize_t oresid = uio->uio_resid; + int error; + + MPASS(tp->t_termios.c_cc[VMIN] != 0); + MPASS(tp->t_termios.c_cc[VTIME] != 0); + + /* + * When using the interbyte timer, the timer should be started + * after the first byte has been received. We just call into the + * generic read timer code after we've received the first byte. + */ + + for (;;) + { + error = tty_wait_background(tp, curthread, SIGTTIN); + if (error) + return error; + + error = ttyinq_read_uio(&tp->t_inq, tp, uio, uio->uio_resid, 0); + if (error) + return error; + if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) + return 0; + + /* + * Not enough data, but we did receive some, which means + * we'll now start using the interbyte timer. + */ + if (oresid != uio->uio_resid) + break; + + /* We have to wait for more. */ + if (tp->t_flags & TF_ZOMBIE) + return 0; + else if (ioflag & IO_NDELAY) + return -EWOULDBLOCK; + + error = tty_wait(tp, &tp->t_inwait); + if (error) + return error; + } + + return ttydisc_read_raw_read_timer(tp, uio, ioflag, oresid); +} + +int ttydisc_read(struct lwp_tty *tp, struct uio *uio, int ioflag) +{ + int error; + + tty_assert_locked(tp); + + if (uio->uio_resid == 0) + return 0; + + if (CMP_FLAG(l, ICANON)) + error = ttydisc_read_canonical(tp, uio, ioflag); + else if (tp->t_termios.c_cc[VTIME] == 0) + error = ttydisc_read_raw_no_timer(tp, uio, ioflag); + else if (tp->t_termios.c_cc[VMIN] == 0) + error = ttydisc_read_raw_read_timer(tp, uio, ioflag, uio->uio_resid); + else + error = ttydisc_read_raw_interbyte_timer(tp, uio, ioflag); + + if (ttyinq_bytesleft(&tp->t_inq) >= tp->t_inlow || + ttyinq_bytescanonicalized(&tp->t_inq) == 0) + { + /* Unset the input watermark when we've got enough space. */ + tty_hiwat_in_unblock(tp); + } + + return error; +} + +/* return the offset to special character from string start */ +rt_inline unsigned int ttydisc_findchar(const char *obstart, unsigned int oblen) +{ + const char *c = obstart; + + while (oblen--) + { + if (CTL_VALID(*c)) + break; + c++; + } + + return (c - obstart); +} + +static int ttydisc_write_oproc(struct lwp_tty *tp, char c) +{ + unsigned int scnt, error; + + MPASS(CMP_FLAG(o, OPOST)); + MPASS(CTL_VALID(c)); + +#define PRINT_NORMAL() ttyoutq_write_nofrag(&tp->t_outq, &c, 1) + switch (c) + { + case CEOF: + /* End-of-text dropping. */ + if (CMP_FLAG(o, ONOEOT)) + return 0; + return PRINT_NORMAL(); + + case CERASE2: + /* Handle backspace to fix tab expansion. */ + if (PRINT_NORMAL() != 0) + return (-1); + if (tp->t_column > 0) + tp->t_column--; + return 0; + + case CTAB: + /* Tab expansion. */ + scnt = 8 - (tp->t_column & 7); + if (CMP_FLAG(o, TAB3)) + { + error = ttyoutq_write_nofrag(&tp->t_outq, " ", scnt); + } + else + { + error = PRINT_NORMAL(); + } + if (error) + return (-1); + + tp->t_column += scnt; + MPASS((tp->t_column % 8) == 0); + return 0; + + case CNL: + /* Newline conversion. */ + if (CMP_FLAG(o, ONLCR)) + { + /* Convert \n to \r\n. */ + error = ttyoutq_write_nofrag(&tp->t_outq, "\r\n", 2); + } + else + { + error = PRINT_NORMAL(); + } + if (error) + return (-1); + + if (CMP_FLAG(o, ONLCR | ONLRET)) + { + tp->t_column = tp->t_writepos = 0; + ttyinq_reprintpos_set(&tp->t_inq); + } + return 0; + + case CCR: + /* Carriage return to newline conversion. */ + if (CMP_FLAG(o, OCRNL)) + c = CNL; + /* Omit carriage returns on column 0. */ + if (CMP_FLAG(o, ONOCR) && tp->t_column == 0) + return 0; + if (PRINT_NORMAL() != 0) + return (-1); + + tp->t_column = tp->t_writepos = 0; + ttyinq_reprintpos_set(&tp->t_inq); + return 0; + } + + /* + * Invisible control character. Print it, but don't + * increase the column count. + */ + return PRINT_NORMAL(); +#undef PRINT_NORMAL +} + +/* + * Just like the old TTY implementation, we need to copy data in chunks + * into a temporary buffer. One of the reasons why we need to do this, + * is because output processing (only TAB3 though) may allow the buffer + * to grow eight times. + */ +int ttydisc_write(struct lwp_tty *tp, struct uio *uio, int ioflag) +{ + char ob[TTY_STACKBUF]; + char *obstart; + int error = 0; + unsigned int oblen = 0; + + tty_assert_locked(tp); + + if (tp->t_flags & TF_ZOMBIE) + return -EIO; + + /* + * We don't need to check whether the process is the foreground + * process group or if we have a carrier. This is already done + * in ttydev_write(). + */ + + while (uio->uio_resid > 0) + { + unsigned int nlen; + + MPASS(oblen == 0); + + if (CMP_FLAG(l, FLUSHO)) + { + uio->uio_offset += uio->uio_resid; + uio->uio_resid = 0; + return 0; + } + + /* Step 1: read data. */ + obstart = ob; + nlen = MIN(uio->uio_resid, sizeof ob); + tty_unlock(tp); + error = uiomove(ob, nlen, uio); + tty_lock(tp); + if (error != 0) + break; + oblen = nlen; + + if (tty_gone(tp)) + { + error = -ENXIO; + break; + } + + MPASS(oblen > 0); + + /* Step 2: process data. */ + do + { + unsigned int plen, wlen; + + if (CMP_FLAG(l, FLUSHO)) + { + uio->uio_offset += uio->uio_resid; + uio->uio_resid = 0; + return 0; + } + + /* Search for special characters for post processing. */ + if (CMP_FLAG(o, OPOST)) + { + plen = ttydisc_findchar(obstart, oblen); + } + else + { + plen = oblen; + } + + if (plen == 0) + { + /* + * We're going to process a character + * that needs processing + */ + if (ttydisc_write_oproc(tp, *obstart) == 0) + { + obstart++; + oblen--; + + tp->t_writepos = tp->t_column; + ttyinq_reprintpos_set(&tp->t_inq); + continue; + } + } + else + { + /* We're going to write regular data. */ + wlen = ttyoutq_write(&tp->t_outq, obstart, plen); + obstart += wlen; + oblen -= wlen; + tp->t_column += wlen; + + tp->t_writepos = tp->t_column; + ttyinq_reprintpos_set(&tp->t_inq); + + if (wlen == plen) + continue; + } + + /* Watermark reached. Try to sleep. */ + tp->t_flags |= TF_HIWAT_OUT; + + if (ioflag & IO_NDELAY) + { + error = EWOULDBLOCK; + goto done; + } + + /* + * The driver may write back the data + * synchronously. Be sure to check the high + * water mark before going to sleep. + */ + ttydevsw_outwakeup(tp); + if ((tp->t_flags & TF_HIWAT_OUT) == 0) + continue; + + error = tty_wait(tp, &tp->t_outwait); + if (error) + goto done; + + if (tp->t_flags & TF_ZOMBIE) + { + error = EIO; + goto done; + } + } while (oblen > 0); + } + +done: + if (!tty_gone(tp)) + ttydevsw_outwakeup(tp); + + /* + * Add the amount of bytes that we didn't process back to the + * uio counters. We need to do this to make sure write() doesn't + * count the bytes we didn't store in the queue. + */ + uio->uio_resid += oblen; + return error; +} + +/* set optimize flags if possible */ +void ttydisc_optimize(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + + if (ttyhook_hashook(tp, rint_bypass)) + { + tp->t_flags |= TF_BYPASS; + } + else if (ttyhook_hashook(tp, rint)) + { + tp->t_flags &= ~TF_BYPASS; + } + else if (!CMP_FLAG(i, ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON) && + (!CMP_FLAG(i, BRKINT) || CMP_FLAG(i, IGNBRK)) && + (!CMP_FLAG(i, PARMRK) || + CMP_FLAG(i, IGNPAR | IGNBRK) == (IGNPAR | IGNBRK)) && + !CMP_FLAG(l, ECHO | ICANON | IEXTEN | ISIG | PENDIN)) + { + tp->t_flags |= TF_BYPASS; + } + else + { + tp->t_flags &= ~TF_BYPASS; + } +} + +void ttydisc_modem(struct lwp_tty *tp, int open) +{ + tty_assert_locked(tp); + + if (open) + cv_broadcast(&tp->t_dcdwait); + + /* + * Ignore modem status lines when CLOCAL is turned on, but don't + * enter the zombie state when the TTY isn't opened, because + * that would cause the TTY to be in zombie state after being + * opened. + */ + if (!tty_opened(tp) || CMP_FLAG(c, CLOCAL)) + return; + + if (open == 0) + { + /* + * Lost carrier. + */ + tp->t_flags |= TF_ZOMBIE; + + lwp_tty_signal_sessleader(tp, SIGHUP); + tty_flush(tp, FREAD | FWRITE); + } + else + { + /* + * Carrier is back again. + */ + + /* XXX: what should we do here? */ + } +} + +static int ttydisc_echo_force(struct lwp_tty *tp, char c, int quote) +{ + if (CMP_FLAG(l, FLUSHO)) + return 0; + + if (CMP_FLAG(o, OPOST) && CTL_ECHO(c, quote)) + { + /* + * Only perform postprocessing when OPOST is turned on + * and the character is an unquoted BS/TB/NL/CR. + */ + return ttydisc_write_oproc(tp, c); + } + else if (CMP_FLAG(l, ECHOCTL) && CTL_PRINT(c, quote)) + { + /* + * Only use ^X notation when ECHOCTL is turned on and + * we've got an quoted control character. + * + * Print backspaces when echoing an end-of-file. + */ + char ob[4] = "^?\b\b"; + + /* Print ^X notation. */ + if (c != 0x7f) + ob[1] = c + 'A' - 1; + + if (!quote && CMP_CC(VEOF, c)) + { + return ttyoutq_write_nofrag(&tp->t_outq, ob, 4); + } + else + { + tp->t_column += 2; + return ttyoutq_write_nofrag(&tp->t_outq, ob, 2); + } + } + else + { + /* Can just be printed. */ + tp->t_column++; + return ttyoutq_write_nofrag(&tp->t_outq, &c, 1); + } +} + +static int ttydisc_echo(struct lwp_tty *tp, char c, int quote) +{ + /* + * Only echo characters when ECHO is turned on, or ECHONL when + * the character is an unquoted newline. + */ + if (!CMP_FLAG(l, ECHO) && (!CMP_FLAG(l, ECHONL) || c != CNL || quote)) + return 0; + + return ttydisc_echo_force(tp, c, quote); +} + +static void ttydisc_reprint_char(void *d, char c, int quote) +{ + struct lwp_tty *tp = d; + + ttydisc_echo(tp, c, quote); +} + +static void ttydisc_reprint(struct lwp_tty *tp) +{ + cc_t c; + + /* Print ^R\n, followed by the line. */ + c = tp->t_termios.c_cc[VREPRINT]; + if (c != _POSIX_VDISABLE) + ttydisc_echo(tp, c, 0); + ttydisc_echo(tp, CNL, 0); + ttyinq_reprintpos_reset(&tp->t_inq); + + ttyinq_line_iterate_from_linestart(&tp->t_inq, ttydisc_reprint_char, tp); +} + +struct ttydisc_recalc_length +{ + struct lwp_tty *tp; + unsigned int curlen; +}; + +static void ttydisc_recalc_charlength(void *d, char c, int quote) +{ + struct ttydisc_recalc_length *data = d; + struct lwp_tty *tp = data->tp; + + if (CTL_PRINT(c, quote)) + { + if (CMP_FLAG(l, ECHOCTL)) + data->curlen += 2; + } + else if (c == CTAB) + { + data->curlen += 8 - (data->curlen & 7); + } + else + { + data->curlen++; + } +} + +static unsigned int ttydisc_recalc_linelength(struct lwp_tty *tp) +{ + struct ttydisc_recalc_length data = {tp, tp->t_writepos}; + + ttyinq_line_iterate_from_reprintpos(&tp->t_inq, ttydisc_recalc_charlength, + &data); + return (data.curlen); +} + +static int ttydisc_rubchar(struct lwp_tty *tp) +{ + char c; + int quote; + unsigned int prevpos, tablen; + + if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) + return (-1); + ttyinq_unputchar(&tp->t_inq); + + if (CMP_FLAG(l, ECHO)) + { + /* + * Remove the character from the screen. This is even + * safe for characters that span multiple characters + * (tabs, quoted, etc). + */ + if (tp->t_writepos >= tp->t_column) + { + /* Retype the sentence. */ + ttydisc_reprint(tp); + } + else if (CMP_FLAG(l, ECHOE)) + { + if (CTL_PRINT(c, quote)) + { + /* Remove ^X formatted chars. */ + if (CMP_FLAG(l, ECHOCTL)) + { + tp->t_column -= 2; + ttyoutq_write_nofrag(&tp->t_outq, "\b\b \b\b", 6); + } + } + else if (c == ' ') + { + /* Space character needs no rubbing. */ + tp->t_column -= 1; + ttyoutq_write_nofrag(&tp->t_outq, "\b", 1); + } + else if (c == CTAB) + { + /* + * Making backspace work with tabs is + * quite hard. Recalculate the length of + * this character and remove it. + * + * Because terminal settings could be + * changed while the line is being + * inserted, the calculations don't have + * to be correct. Make sure we keep the + * tab length within proper bounds. + */ + prevpos = ttydisc_recalc_linelength(tp); + if (prevpos >= tp->t_column) + tablen = 1; + else + tablen = tp->t_column - prevpos; + if (tablen > 8) + tablen = 8; + + tp->t_column = prevpos; + ttyoutq_write_nofrag(&tp->t_outq, "\b\b\b\b\b\b\b\b", tablen); + return 0; + } +#ifdef USING_BSD_UTF8 + else if ((tp->t_termios.c_iflag & IUTF8) != 0 && CTL_UTF8(c)) + { + uint8_t bytes[UTF8_STACKBUF] = {0}; + int curidx = UTF8_STACKBUF - 1, cwidth = 1, nb = 0; + teken_char_t codepoint; + + /* Save current byte. */ + bytes[curidx] = c; + curidx--; + nb++; + /* Loop back through inq until we hit the + * leading byte. */ + while (CTL_UTF8_CONT(c) && nb < UTF8_STACKBUF) + { + /* + * Check if we've reached the beginning + * of the line. + */ + if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) + break; + ttyinq_unputchar(&tp->t_inq); + bytes[curidx] = c; + curidx--; + nb++; + } + /* + * Shift array so that the leading + * byte ends up at idx 0. + */ + if (nb < UTF8_STACKBUF) + memmove(&bytes[0], &bytes[curidx + 1], + nb * sizeof(uint8_t)); + /* Check for malformed UTF8 characters. */ + if (nb == UTF8_STACKBUF && CTL_UTF8_CONT(bytes[0])) + { + /* + * Place all bytes back into the inq and + * delete the last byte only. + */ + ttyinq_write(&tp->t_inq, bytes, UTF8_STACKBUF, 0); + ttyinq_unputchar(&tp->t_inq); + } + else + { + /* Find codepoint and width. */ + codepoint = teken_utf8_bytes_to_codepoint(bytes, nb); + if (codepoint == TEKEN_UTF8_INVALID_CODEPOINT || + (cwidth = teken_wcwidth(codepoint)) == -1) + { + /* + * Place all bytes back into the + * inq and fall back to + * default behaviour. + */ + cwidth = 1; + ttyinq_write(&tp->t_inq, bytes, nb, 0); + ttyinq_unputchar(&tp->t_inq); + } + } + tp->t_column -= cwidth; + /* + * Delete character by punching + * 'cwidth' spaces over it. + */ + if (cwidth == 1) + ttyoutq_write_nofrag(&tp->t_outq, "\b \b", 3); + else if (cwidth == 2) + ttyoutq_write_nofrag(&tp->t_outq, "\b\b \b\b", 6); + } +#endif + else + { + /* + * Remove a regular character by + * punching a space over it. + */ + tp->t_column -= 1; + ttyoutq_write_nofrag(&tp->t_outq, "\b \b", 3); + } + } + else + { + /* Don't print spaces. */ + ttydisc_echo(tp, tp->t_termios.c_cc[VERASE], 0); + } + } + + return 0; +} + +static void ttydisc_rubword(struct lwp_tty *tp) +{ + char c; + int quote; +#ifdef ALTWERASE + int alnum; +#endif + + /* Strip whitespace first. */ + for (;;) + { + if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) + return; + if (!CTL_WHITE(c)) + break; + ttydisc_rubchar(tp); + } + + /* + * Record whether the last character from the previous iteration + * was alphanumeric or not. We need this to implement ALTWERASE. + */ +#ifdef ALTWERASE + alnum = CTL_ALNUM(c); +#endif + for (;;) + { + ttydisc_rubchar(tp); + + if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) + return; + if (CTL_WHITE(c)) + return; +#ifdef ALTWERASE + if (CMP_FLAG(l, ALTWERASE) && CTL_ALNUM(c) != alnum) + return; +#endif + } +} + +int ttydisc_rint(struct lwp_tty *tp, char c, int flags) +{ + int signal, quote = 0; + char ob[3] = {0xff, 0x00}; + size_t ol; + + tty_assert_locked(tp); + + rt_atomic_add(&tty_nin, 1); + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, rint)) + return ttyhook_rint(tp, c, flags); +#endif + + if (tp->t_flags & TF_BYPASS) + goto processed; + + if (flags) + { + if (flags & TRE_BREAK) + { + if (CMP_FLAG(i, IGNBRK)) + { + /* Ignore break characters. */ + return 0; + } + else if (CMP_FLAG(i, BRKINT)) + { + /* Generate SIGINT on break. */ + tty_flush(tp, FREAD | FWRITE); + lwp_tty_signal_pgrp(tp, SIGINT); + return 0; + } + else + { + /* Just print it. */ + goto parmrk; + } + } + else if (flags & TRE_FRAMING || + (flags & TRE_PARITY && CMP_FLAG(i, INPCK))) + { + if (CMP_FLAG(i, IGNPAR)) + { + /* Ignore bad characters. */ + return 0; + } + else + { + /* Just print it. */ + goto parmrk; + } + } + } + + /* Allow any character to perform a wakeup. */ + if (CMP_FLAG(i, IXANY)) + { + tp->t_flags &= ~TF_STOPPED; + tp->t_termios.c_lflag &= ~FLUSHO; /* FLUSHO: Output is being flushed */ + } + + /* Remove the top bit. */ + if (CMP_FLAG(i, ISTRIP)) + c &= ~0x80; + + /* Skip input processing when we want to print it literally. */ + if (tp->t_flags & TF_LITERAL) + { + tp->t_flags &= ~TF_LITERAL; + quote = 1; + goto processed; + } + + /* Special control characters that are implementation dependent. */ + if (CMP_FLAG(l, IEXTEN)) + { + /* Accept the next character as literal. */ + if (CMP_CC(VLNEXT, c)) + { + if (CMP_FLAG(l, ECHO)) + { + if (CMP_FLAG(l, ECHOE)) + ttyoutq_write_nofrag(&tp->t_outq, "^\b", 2); + else + ttydisc_echo(tp, c, 0); + } + tp->t_flags |= TF_LITERAL; + return 0; + } + /* Discard processing */ + if (CMP_CC(VDISCARD, c)) + { + if (CMP_FLAG(l, FLUSHO)) + { + tp->t_termios.c_lflag &= ~FLUSHO; + } + else + { + tty_flush(tp, FWRITE); + ttydisc_echo(tp, c, 0); + if (tp->t_inq.ti_end > 0) + ttydisc_reprint(tp); + tp->t_termios.c_lflag |= FLUSHO; + } + } + } + + /* + * Handle signal processing. + */ + if (CMP_FLAG(l, ISIG)) + { +#ifdef USING_BSD_SIGINFO + if (CMP_FLAG(l, ICANON | IEXTEN) == (ICANON | IEXTEN)) + { + if (CMP_CC(VSTATUS, c)) + { + tty_signal_pgrp(tp, SIGINFO); + return 0; + } + } +#endif /* USING_BSD_SIGINFO */ + + /* + * When compared to the old implementation, this + * implementation also flushes the output queue. POSIX + * is really brief about this, but does makes us assume + * we have to do so. + */ + signal = 0; + if (CMP_CC(VINTR, c)) + { + signal = SIGINT; + } + else if (CMP_CC(VQUIT, c)) + { + signal = SIGQUIT; + } + else if (CMP_CC(VSUSP, c)) + { + signal = SIGTSTP; + } + + if (signal != 0) + { + /* + * Echo the character before signalling the + * processes. + */ + if (!CMP_FLAG(l, NOFLSH)) + tty_flush(tp, FREAD | FWRITE); + ttydisc_echo(tp, c, 0); + lwp_tty_signal_pgrp(tp, signal); + return 0; + } + } + + /* + * Handle start/stop characters. + */ + if (CMP_FLAG(i, IXON)) + { + if (CMP_CC(VSTOP, c)) + { + /* Stop it if we aren't stopped yet. */ + if ((tp->t_flags & TF_STOPPED) == 0) + { + tp->t_flags |= TF_STOPPED; + return 0; + } + /* + * Fallthrough: + * When VSTART == VSTOP, we should make this key + * toggle it. + */ + if (!CMP_CC(VSTART, c)) + return 0; + } + if (CMP_CC(VSTART, c)) + { + tp->t_flags &= ~TF_STOPPED; + return 0; + } + } + + /* Conversion of CR and NL. */ + switch (c) + { + case CCR: + if (CMP_FLAG(i, IGNCR)) + return 0; + if (CMP_FLAG(i, ICRNL)) + c = CNL; + break; + case CNL: + if (CMP_FLAG(i, INLCR)) + c = CCR; + break; + } + + /* Canonical line editing. */ + if (CMP_FLAG(l, ICANON)) + { + if (CMP_CC(VERASE, c) +#ifdef VERASE2 + || CMP_CC(VERASE2, c) +#endif + ) + { + ttydisc_rubchar(tp); + return 0; + } + else if (CMP_CC(VKILL, c)) + { + while (ttydisc_rubchar(tp) == 0) + ; + return 0; + } + else if (CMP_FLAG(l, IEXTEN)) + { + if (CMP_CC(VWERASE, c)) + { + ttydisc_rubword(tp); + return 0; + } + else if (CMP_CC(VREPRINT, c)) + { + ttydisc_reprint(tp); + return 0; + } + } + } + +processed: + if (CMP_FLAG(i, PARMRK) && (unsigned char)c == 0xff) + { + /* Print 0xff 0xff. */ + ob[1] = 0xff; + ol = 2; + quote = 1; + } + else + { + ob[0] = c; + ol = 1; + } + + goto print; + +parmrk: + if (CMP_FLAG(i, PARMRK)) + { + /* Prepend 0xff 0x00 0x.. */ + ob[2] = c; + ol = 3; + quote = 1; + } + else + { + ob[0] = c; + ol = 1; + } + +print: + /* See if we can store this on the input queue. */ + if (ttyinq_write_nofrag(&tp->t_inq, ob, ol, quote) != 0) + { + if (CMP_FLAG(i, IMAXBEL)) + ttyoutq_write_nofrag(&tp->t_outq, "\a", 1); + + /* + * Prevent a deadlock here. It may be possible that a + * user has entered so much data, there is no data + * available to read(), but the buffers are full anyway. + * + * Only enter the high watermark if the device driver + * can actually transmit something. + */ + if (ttyinq_bytescanonicalized(&tp->t_inq) == 0) + return 0; + + tty_hiwat_in_block(tp); + return (-1); + } + + /* + * In raw mode, we canonicalize after receiving a single + * character. Otherwise, we canonicalize when we receive a + * newline, VEOL or VEOF, but only when it isn't quoted. + */ + if (!CMP_FLAG(l, ICANON) || + (!quote && (c == CNL || CMP_CC(VEOL, c) || CMP_CC(VEOF, c)))) + { + ttyinq_canonicalize(&tp->t_inq); + } + + ttydisc_echo(tp, c, quote); + + return 0; +} + +size_t ttydisc_rint_simple(struct lwp_tty *tp, const void *buf, size_t len) +{ + const char *cbuf; + + if (ttydisc_can_bypass(tp)) + return (ttydisc_rint_bypass(tp, buf, len)); + + for (cbuf = buf; len-- > 0; cbuf++) + { + if (ttydisc_rint(tp, *cbuf, 0) != 0) + break; + } + + return (cbuf - (const char *)buf); +} + +size_t ttydisc_rint_bypass(struct lwp_tty *tp, const void *buf, size_t len) +{ + size_t ret; + + tty_assert_locked(tp); + + MPASS(tp->t_flags & TF_BYPASS); + + rt_atomic_add(&tty_nin, len); + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, rint_bypass)) + return ttyhook_rint_bypass(tp, buf, len); +#endif + + ret = ttyinq_write(&tp->t_inq, buf, len, 0); + ttyinq_canonicalize(&tp->t_inq); + if (ret < len) + tty_hiwat_in_block(tp); + + return (ret); +} + +void ttydisc_rint_done(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, rint_done)) + ttyhook_rint_done(tp); +#endif + + /* Wake up readers. */ + tty_wakeup(tp, FREAD); + /* Wake up driver for echo. */ + ttydevsw_outwakeup(tp); +} + +size_t ttydisc_rint_poll(struct lwp_tty *tp) +{ + size_t l; + + tty_assert_locked(tp); + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, rint_poll)) + return ttyhook_rint_poll(tp); +#endif + + /* + * XXX: Still allow character input when there's no space in the + * buffers, but we haven't entered the high watermark. This is + * to allow backspace characters to be inserted when in + * canonical mode. + */ + l = ttyinq_bytesleft(&tp->t_inq); + if (l == 0 && (tp->t_flags & TF_HIWAT_IN) == 0) + return (1); + + return (l); +} + +static void ttydisc_wakeup_watermark(struct lwp_tty *tp) +{ + size_t c; + + c = ttyoutq_bytesleft(&tp->t_outq); + if (tp->t_flags & TF_HIWAT_OUT) + { + /* Only allow us to run when we're below the watermark. */ + if (c < tp->t_outlow) + return; + + /* Reset the watermark. */ + tp->t_flags &= ~TF_HIWAT_OUT; + } + else + { + /* Only run when we have data at all. */ + if (c == 0) + return; + } + tty_wakeup(tp, FWRITE); +} + +size_t ttydisc_getc(struct lwp_tty *tp, void *buf, size_t len) +{ + tty_assert_locked(tp); + + if (tp->t_flags & TF_STOPPED) + return 0; + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, getc_inject)) + return ttyhook_getc_inject(tp, buf, len); +#endif + + len = ttyoutq_read(&tp->t_outq, buf, len); + +#ifdef USING_BSD_HOOK + if (ttyhook_hashook(tp, getc_capture)) + ttyhook_getc_capture(tp, buf, len); +#endif + + ttydisc_wakeup_watermark(tp); + rt_atomic_add(&tty_nout, len); + + return (len); +} + +int ttydisc_getc_uio(struct lwp_tty *tp, struct uio *uio) +{ + int error = 0; + ssize_t obytes = uio->uio_resid; + size_t len; + char buf[TTY_STACKBUF]; + + tty_assert_locked(tp); + + if (tp->t_flags & TF_STOPPED) + return 0; + + /* + * When a TTY hook is attached, we cannot perform unbuffered + * copying to userspace. Just call ttydisc_getc() and + * temporarily store data in a shadow buffer. + */ + if (ttyhook_hashook(tp, getc_capture) || ttyhook_hashook(tp, getc_inject)) + { + while (uio->uio_resid > 0) + { + /* Read to shadow buffer. */ + len = ttydisc_getc(tp, buf, MIN(uio->uio_resid, sizeof buf)); + if (len == 0) + break; + + /* Copy to userspace. */ + tty_unlock(tp); + error = uiomove(buf, len, uio); + tty_lock(tp); + + if (error != 0) + break; + } + } + else + { + error = ttyoutq_read_uio(&tp->t_outq, tp, uio); + + ttydisc_wakeup_watermark(tp); + rt_atomic_add(&tty_nout, obytes - uio->uio_resid); + } + + return error; +} + +size_t ttydisc_getc_poll(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + + if (tp->t_flags & TF_STOPPED) + return 0; + +#if USING_BSD_HOOK + if (ttyhook_hashook(tp, getc_poll)) + return ttyhook_getc_poll(tp); +#endif + + return ttyoutq_bytesused(&tp->t_outq); +} diff --git a/components/lwp/terminal/terminal.h b/components/lwp/terminal/terminal.h new file mode 100644 index 0000000000..df0dc04128 --- /dev/null +++ b/components/lwp/terminal/terminal.h @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#ifndef __LWP_TERMINAL_H__ +#define __LWP_TERMINAL_H__ + +#include "bsd_ttyqueue.h" +#include "bsd_ttydisc.h" + +#ifdef USING_BSD_HOOK +#include "bsd_ttyhook.h" +#endif + +#include +#include + +/* include kernel header for termios base definitions */ +#include +/* for _POSIX_VDISABLE */ +#include + +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2008 Ed Schouten + * All rights reserved. + * + * Portions of this software were developed under sponsorship from Snow + * B.V., the Netherlands. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +struct lwp_tty; + +/* + * Driver routines that are called from the line discipline to adjust + * hardware parameters and such. + */ +typedef int tsw_open_t(struct lwp_tty *tp); +typedef void tsw_close_t(struct lwp_tty *tp); +typedef void tsw_outwakeup_t(struct lwp_tty *tp); +typedef void tsw_inwakeup_t(struct lwp_tty *tp); +typedef int tsw_ioctl_t(struct lwp_tty *tp, rt_ubase_t cmd, rt_caddr_t data, + struct rt_thread *td); +typedef int tsw_cioctl_t(struct lwp_tty *tp, int unit, rt_ubase_t cmd, rt_caddr_t data, + struct rt_thread *td); +typedef int tsw_param_t(struct lwp_tty *tp, struct termios *t); +typedef int tsw_modem_t(struct lwp_tty *tp, int sigon, int sigoff); +typedef int tsw_mmap_t(struct lwp_tty *tp, vm_ooffset_t offset, + vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr); + +typedef void tsw_pktnotify_t(struct lwp_tty *tp, char event); +typedef void tsw_free_t(void *softc); +typedef rt_bool_t tsw_busy_t(struct lwp_tty *tp); + +struct lwp_ttydevsw +{ + unsigned int tsw_flags; /* Default TTY flags. */ + + tsw_open_t *tsw_open; /* Device opening. */ + tsw_close_t *tsw_close; /* Device closure. */ + + tsw_outwakeup_t *tsw_outwakeup; /* Output available. */ + tsw_inwakeup_t *tsw_inwakeup; /* Input can be stored again. */ + + tsw_ioctl_t *tsw_ioctl; /* ioctl() hooks. */ + tsw_cioctl_t *tsw_cioctl; /* ioctl() on control devices. */ + tsw_param_t *tsw_param; /* TIOCSETA device parameter setting. */ + tsw_modem_t *tsw_modem; /* Modem sigon/sigoff. */ + + tsw_mmap_t *tsw_mmap; /* mmap() hooks. */ + tsw_pktnotify_t *tsw_pktnotify; /* TIOCPKT events. */ + + tsw_free_t *tsw_free; /* Destructor. */ + + tsw_busy_t *tsw_busy; /* Draining output. */ + + void *tsw_spare[3]; /* For future use. */ +}; +typedef struct lwp_ttydevsw *lwp_ttydevsw_t; + +struct lwp_tty +{ + struct rt_device parent; /* inherit from Class:RT_Device */ + struct rt_mutex *t_mtx; /* TTY lock. */ + struct rt_mutex t_mtxobj; /* Per-TTY lock (when not borrowing). */ + rt_list_t t_list; /* (l) TTY list entry. */ + int t_drainwait; /* (t) TIOCDRAIN timeout seconds. */ + unsigned int t_flags; /* (t) Terminal option flags. */ +/* Keep flags in sync with db_show_tty and pstat(8). */ +#define TF_NOPREFIX 0x00001 /* Don't prepend "tty" to device name. */ +#define TF_INITLOCK 0x00002 /* Create init/lock state devices. */ +#define TF_CALLOUT 0x00004 /* Create "cua" devices. */ +#define TF_OPENED_IN 0x00008 /* "tty" node is in use. */ +#define TF_OPENED_OUT 0x00010 /* "cua" node is in use. */ +#define TF_OPENED_CONS 0x00020 /* Device in use as console. */ +#define TF_OPENED (TF_OPENED_IN | TF_OPENED_OUT | TF_OPENED_CONS) +#define TF_GONE 0x00040 /* Device node is gone. */ +#define TF_OPENCLOSE 0x00080 /* Device is in open()/close(). */ +#define TF_ASYNC 0x00100 /* Asynchronous I/O enabled. */ +#define TF_LITERAL 0x00200 /* Accept the next character literally. */ +#define TF_HIWAT_IN 0x00400 /* We've reached the input watermark. */ +#define TF_HIWAT_OUT 0x00800 /* We've reached the output watermark. */ +#define TF_HIWAT (TF_HIWAT_IN | TF_HIWAT_OUT) +#define TF_STOPPED 0x01000 /* Output flow control - stopped. */ +#define TF_EXCLUDE 0x02000 /* Exclusive access. */ +#define TF_BYPASS 0x04000 /* Optimized input path. */ +#define TF_ZOMBIE 0x08000 /* Modem disconnect received. */ +#define TF_HOOK 0x10000 /* TTY has hook attached. */ +#define TF_BUSY_IN 0x20000 /* Process busy in read() -- not supported. */ +#define TF_BUSY_OUT 0x40000 /* Process busy in write(). */ +#define TF_BUSY (TF_BUSY_IN | TF_BUSY_OUT) + unsigned int t_revokecnt; /* (t) revoke() count. */ + + /* Buffering mechanisms. */ + struct ttyinq t_inq; /* (t) Input queue. */ + size_t t_inlow; /* (t) Input low watermark. */ + struct ttyoutq t_outq; /* (t) Output queue. */ + size_t t_outlow; /* (t) Output low watermark. */ + + /* Sleeping mechanisms. */ + struct rt_condvar t_inwait; /* (t) Input wait queue. */ + struct rt_condvar t_outwait; /* (t) Output wait queue. */ + struct rt_condvar t_outserwait; /* (t) Serial output wait queue. */ + struct rt_condvar t_bgwait; /* (t) Background wait queue. */ + struct rt_condvar t_dcdwait; /* (t) Carrier Detect wait queue. */ + + struct rt_wqueue t_inpoll; /* (t) Input poll queue. */ + struct rt_wqueue t_outpoll; /* (t) Output poll queue. */ + +#ifdef USING_BSD_AIO + struct sigio *t_sigio; /* (t) Asynchronous I/O. */ +#endif + + struct termios t_termios; /* (t) I/O processing flags. */ + struct winsize t_winsize; /* (t) Window size. */ + unsigned int t_column; /* (t) Current cursor position. */ + unsigned int t_writepos; /* (t) Where input was interrupted. */ + int t_compatflags; /* (t) COMPAT_43TTY flags. */ + + /* Init/lock-state devices. */ + struct termios t_termios_init_in; /* tty%s.init. */ + struct termios t_termios_lock_in; /* tty%s.lock. */ +#ifdef USING_BSD_INIT_LOCK_DEVICE + struct termios t_termios_init_out; /* cua%s.init. */ + struct termios t_termios_lock_out; /* cua%s.lock. */ + +#endif /* USING_BSD_INIT_LOCK_DEVICE */ + + struct lwp_ttydevsw *t_devsw; /* (c) Driver hooks. */ +#ifdef USING_BSD_HOOK + struct lwp_ttyhook *t_hook; /* (t) Capture/inject hook. */ +#endif + + /* Process signal delivery. */ + struct rt_processgroup *t_pgrp; /* (t) Foreground process group. */ + struct rt_session *t_session; /* (t) Associated session. */ + unsigned int t_sessioncnt; /* (t) Backpointing sessions. */ + + void *t_devswsoftc; /* (c) Soft config, for drivers. */ +#ifdef USING_BSD_HOOK + void *t_hooksoftc; /* (t) Soft config, for hooks. */ +#endif +#ifdef USING_BSD_CHAR_DEVICE + struct cdev *t_dev; /* (c) Primary character device. */ +#endif /* USING_BSD_CHAR_DEVICE */ +#ifdef USING_BSD_SIGINFO + size_t t_prbufsz; /* (t) SIGINFO buffer size. */ + char t_prbuf[]; /* (t) SIGINFO buffer. */ +#endif /* USING_BSD_SIGINFO */ +}; +typedef struct lwp_tty *lwp_tty_t; + +/* Allocation and deallocation. */ +void tty_rel_pgrp(struct lwp_tty *tp, struct rt_processgroup *pgrp); +void tty_rel_sess(struct lwp_tty *tp, struct rt_session *sess); +void tty_rel_gone(struct lwp_tty *tp); + +/* tty locking mechanism */ +#define tty_getlock(tp) ((tp)->t_mtx) +#define tty_lock(tp) rt_mutex_take(tty_getlock(tp), RT_WAITING_FOREVER); +#define tty_unlock(tp) rt_mutex_release(tty_getlock(tp)) +#define tty_lock_owned(tp) \ + (rt_mutex_get_owner(tty_getlock(tp)) == rt_thread_self()) +#define tty_lock_notrecused(tp) (rt_mutex_get_hold(tty_getlock(tp)) == 1) +#define tty_assert_locked(tp) RT_ASSERT(tty_lock_owned(tp)) +#define tty_lock_assert(tp, option) \ + (((option) == (MA_OWNED | MA_NOTRECURSED)) \ + ? (tty_lock_owned(tp) && tty_lock_notrecused(tp)) \ + : rt_assert_handler("Operation not allowed", __func__, __LINE__)) + +/* System messages. */ +int tty_checkoutq(struct lwp_tty *tp); +int tty_putchar(struct lwp_tty *tp, char c); +int tty_putstrn(struct lwp_tty *tp, const char *p, size_t n); + +int tty_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, int fflag, + struct rt_thread *td); +int tty_ioctl_compat(struct lwp_tty *tp, rt_ubase_t cmd, rt_caddr_t data, int fflag, + struct rt_thread *td); +void tty_set_winsize(struct lwp_tty *tp, const struct winsize *wsz); +void tty_init_console(struct lwp_tty *tp, speed_t speed); +void tty_flush(struct lwp_tty *tp, int flags); +void tty_hiwat_in_block(struct lwp_tty *tp); +void tty_hiwat_in_unblock(struct lwp_tty *tp); +dev_t tty_udev(struct lwp_tty *tp); + +/* tesing on tty */ +#define tty_opened(tp) ((tp)->t_flags & TF_OPENED) +#define tty_gone(tp) ((tp)->t_flags & TF_GONE) +#define tty_softc(tp) ((tp)->t_devswsoftc) +#define tty_devname(tp) ((tp)->parent.parent.name) + +/** + * @brief TTY registeration on device subsystem + * + * @warning It's the duty of the caller to ensure that the name is not + * identical to any existed registered devices. + * + * @param terminal the target tty device + * @param name name of the device (must be exclusive) + * @return rt_err_t RT_EOK on success + */ +rt_err_t lwp_tty_register(lwp_tty_t terminal, const char *name); + +/** + * @brief TTY allocation and deallocation. TTY devices can be deallocated when + * the driver doesn't use it anymore, when the TTY isn't a session's + * controlling TTY and when the device node isn't opened through devfs. + * + * @param handle device handle of tty + * @param softc device configuration binding on tty + * @param prefix device name prefix + * @param cutom_mtx the lock provided to protect tty + * @return lwp_tty_t NULL on failure + */ +lwp_tty_t lwp_tty_create_ext(lwp_ttydevsw_t handle, void *softc, + rt_mutex_t custom_mtx); + +/** + * @brief Handful version of lwp_tty_create_ext + * + * @param softc device configuration binding on tty + * @param cutom_mtx the lock provided to protect tty + * @param prefix device name prefix + * @return lwp_tty_t NULL on failure + */ +lwp_tty_t lwp_tty_create(lwp_ttydevsw_t handle, void *softc); + +void lwp_tty_delete(lwp_tty_t tp); + +void lwp_tty_signal_sessleader(struct lwp_tty *tp, int sig); + +void lwp_tty_signal_pgrp(struct lwp_tty *tp, int sig); + +/** + * @brief Create a new pseudo-terminal multiplexer + * + * @param root_path path of root mount point of ptyfs + * @return rt_device_t new device object if succeed, otherwise NULL + */ +rt_err_t lwp_ptmx_init(rt_device_t ptmx_device, const char *root_path); + +#define LWP_CONSOLE_LOWEST_PRIOR 0 +#define LWP_CONSOLE_HIGHEST_PRIO INT_MAX +/** + * @brief Register an alternative backend tty device as console + */ +rt_err_t lwp_console_register_backend(struct rt_device *bakdev, int prio); + +rt_inline int ttydevsw_open(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + return (tp->t_devsw->tsw_open(tp)); +} + +rt_inline void ttydevsw_close(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + tp->t_devsw->tsw_close(tp); +} + +rt_inline void ttydevsw_outwakeup(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + /* Prevent spurious wakeups. */ + if (ttydisc_getc_poll(tp) == 0) + return; + + tp->t_devsw->tsw_outwakeup(tp); +} + +rt_inline void ttydevsw_inwakeup(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + /* Prevent spurious wakeups. */ + if (tp->t_flags & TF_HIWAT_IN) + return; + + tp->t_devsw->tsw_inwakeup(tp); +} + +rt_inline int ttydevsw_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, rt_caddr_t data, + struct rt_thread *td) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + return (tp->t_devsw->tsw_ioctl(tp, cmd, data, td)); +} + +rt_inline int ttydevsw_cioctl(struct lwp_tty *tp, int unit, rt_ubase_t cmd, + rt_caddr_t data, struct rt_thread *td) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + return (tp->t_devsw->tsw_cioctl(tp, unit, cmd, data, td)); +} + +rt_inline int ttydevsw_param(struct lwp_tty *tp, struct termios *t) +{ + MPASS(!tty_gone(tp)); + + return (tp->t_devsw->tsw_param(tp, t)); +} + +rt_inline int ttydevsw_modem(struct lwp_tty *tp, int sigon, int sigoff) +{ + MPASS(!tty_gone(tp)); + + return (tp->t_devsw->tsw_modem(tp, sigon, sigoff)); +} + +rt_inline int ttydevsw_mmap(struct lwp_tty *tp, vm_ooffset_t offset, + vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) +{ + MPASS(!tty_gone(tp)); + + return (tp->t_devsw->tsw_mmap(tp, offset, paddr, nprot, memattr)); +} + +rt_inline void ttydevsw_pktnotify(struct lwp_tty *tp, char event) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + tp->t_devsw->tsw_pktnotify(tp, event); +} + +rt_inline void ttydevsw_free(struct lwp_tty *tp) +{ + MPASS(tty_gone(tp)); + + tp->t_devsw->tsw_free(tty_softc(tp)); +} + +rt_inline rt_bool_t ttydevsw_busy(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + MPASS(!tty_gone(tp)); + + return (tp->t_devsw->tsw_busy(tp)); +} + +rt_inline size_t ttydisc_read_poll(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + + return ttyinq_bytescanonicalized(&tp->t_inq); +} + +rt_inline size_t ttydisc_write_poll(struct lwp_tty *tp) +{ + tty_assert_locked(tp); + + return ttyoutq_bytesleft(&tp->t_outq); +} + +#endif /* __LWP_TERMINAL_H__ */ diff --git a/components/lwp/terminal/tty_config.h b/components/lwp/terminal/tty_config.h new file mode 100644 index 0000000000..a1fe75dc7a --- /dev/null +++ b/components/lwp/terminal/tty_config.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#ifndef __TTY_CONFIG_H__ +#define __TTY_CONFIG_H__ + +/* default buffer size of tty siginfo */ +#define LWP_TTY_PRBUF_SIZE 256 + +/* + * System wide defaults for terminal state. + */ + +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ttydefaults.h 8.4 (Berkeley) 1/21/94 + */ + +/* + * Defaults on "first" open. + */ +#define TTYDEF_IFLAG (BRKINT | ICRNL | IMAXBEL | IXON | IXANY | IUTF8) +#define TTYDEF_OFLAG (OPOST | ONLCR) +#define TTYDEF_LFLAG_NOECHO (ICANON | ISIG | IEXTEN) +#define TTYDEF_LFLAG_ECHO \ + (TTYDEF_LFLAG_NOECHO | ECHO | ECHOE | ECHOKE | ECHOCTL) +#define TTYDEF_LFLAG TTYDEF_LFLAG_ECHO +#define TTYDEF_CFLAG (CREAD | CS8 | HUPCL) +#define TTYDEF_SPEED (B9600) + +/* + * Control Character Defaults + */ +/* + * XXX: A lot of code uses lowercase characters, but control-character + * conversion is actually only valid when applied to uppercase + * characters. We just treat lowercase characters as if they were + * inserted as uppercase. + */ +#define _CONTROL(c) \ + ((c) >= 'a' && (c) <= 'z' ? ((c) - 'a' + 1) : (((c) - 'A' + 1) & 0x7f)) +#define CEOF _CONTROL('D') +#define CEOL 0xff /* XXX avoid _POSIX_VDISABLE */ +#define CERASE 0x7f +#define CERASE2 _CONTROL('H') +#define CINTR _CONTROL('C') +#define CSTATUS _CONTROL('T') +#define CKILL _CONTROL('U') +#define CMIN 1 +#define CQUIT _CONTROL('\\') +#define CSUSP _CONTROL('Z') +#define CTIME 0 +#define CDSUSP _CONTROL('Y') +#define CSTART _CONTROL('Q') +#define CSTOP _CONTROL('S') +#define CLNEXT _CONTROL('V') +#define CDISCARD _CONTROL('O') +#define CWERASE _CONTROL('W') +#define CREPRINT _CONTROL('R') +#define CEOT CEOF +/* compat */ +#define CBRK CEOL +#define CRPRNT CREPRINT +#define CFLUSH CDISCARD + +/* PROTECTED INCLUSION ENDS HERE */ +#endif /* !__TTY_CONFIG_H__ */ + +/* + * #define TTY_CONF_INCLUDE_CCHARS to include an array of default control + * characters. + */ +#ifdef TTY_CONF_INCLUDE_CCHARS +#include +#include +#include + +static const cc_t tty_ctrl_charset[NCCS] = { + [VINTR] = CINTR, + [VQUIT] = CQUIT, + [VERASE] = CERASE, + [VKILL] = CKILL, + [VEOF] = CEOF, + [VSTART] = CSTART, + [VSTOP] = CSTOP, + [VSUSP] = CSUSP, + [VREPRINT] = CREPRINT, + [VDISCARD] = CDISCARD, + [VWERASE] = CWERASE, + [VLNEXT] = CLNEXT, + [VMIN] = CMIN +#undef _CONTROL +}; + +#undef TTY_CONF_INCLUDE_CCHARS +#endif /* __TTY_CONFIG_H__ */ diff --git a/components/lwp/terminal/tty_cons.c b/components/lwp/terminal/tty_cons.c new file mode 100644 index 0000000000..42434f3568 --- /dev/null +++ b/components/lwp/terminal/tty_cons.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-12-11 Shell init ver. + */ + +#define DBG_TAG "lwp.tty" +#define DBG_LVL DBG_INFO +#include + +#include "tty_config.h" +#include "tty_internal.h" +#include "bsd_porting.h" +#include "terminal.h" + +#include + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops cons_rtdev_ops; +#endif + +struct backend_entry +{ + rt_list_t bakend_list_node; + int prio; + + rt_device_t bakdev; +}; + +static rt_list_t _bakend_list; + +static void _bent_enqueue(struct backend_entry *bent) +{ + struct backend_entry *idx; + rt_bool_t inserted = RT_FALSE; + rt_list_for_each_entry(idx, &_bakend_list, bakend_list_node) + { + if (idx->prio < bent->prio) + { + rt_list_insert_before(&idx->bakend_list_node, &bent->bakend_list_node); + inserted = RT_TRUE; + break; + } + } + + if (!inserted) + rt_list_insert_after(&_bakend_list, &bent->bakend_list_node); + return ; +} + +rt_err_t lwp_console_register_backend(struct rt_device *bakdev, int prio) +{ + rt_err_t ret = RT_EOK; + struct backend_entry *bent; + bent = rt_malloc(sizeof(struct backend_entry)); + if (bent) + { + rt_list_init(&bent->bakend_list_node); + bent->prio = prio; + bent->bakdev = bakdev; + + _bent_enqueue(bent); + } + else + { + ret = -RT_ENOMEM; + } + return ret; +} + +static struct rt_device _cons_rtdev; + +static int fops_open(struct dfs_file *file) +{ + return -EINVAL; +} + +static struct dfs_file_ops _cons_fops = { + .open = fops_open, +}; + +static rt_err_t _cons_readlink(struct rt_device *dev, char *buf, int len) +{ + int rc = -EIO; + struct backend_entry *bent; + if (!rt_list_isempty(&_bakend_list)) + { + bent = rt_list_first_entry(&_bakend_list, struct backend_entry, bakend_list_node); + if (bent) + { + RT_ASSERT(bent->bakdev); + strncpy(buf, bent->bakdev->parent.name, MIN(len, RT_NAME_MAX)); + LOG_D("%s: backend device %s", __func__, buf); + rc = 0; + } + } + + if (rc != 0) + { + LOG_W("%s: No backend device", __func__); + } + + return rc; +} + +static int _cons_init(void) +{ + rt_err_t rc; + + rt_list_init(&_bakend_list); + + /* setup system level device */ + _cons_rtdev.type = RT_Device_Class_Char; + _cons_rtdev.ops = &cons_rtdev_ops; + rc = rt_device_register(&_cons_rtdev, "console", RT_DEVICE_FLAG_DYNAMIC); + if (rc == RT_EOK) + { + _cons_rtdev.readlink = &_cons_readlink; + _cons_rtdev.fops = &_cons_fops; + } + + return rc; +} +INIT_DEVICE_EXPORT(_cons_init); diff --git a/components/lwp/terminal/tty_ctty.c b/components/lwp/terminal/tty_ctty.c new file mode 100644 index 0000000000..f0de0001f8 --- /dev/null +++ b/components/lwp/terminal/tty_ctty.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-30 Shell init ver. + */ +#define DBG_TAG "lwp.ctty" +#define DBG_LVL DBG_INFO +#include + +#define TTY_CONF_INCLUDE_CCHARS +#include "tty_config.h" +#include "tty_internal.h" +#include "terminal.h" + +static int fops_open(struct dfs_file *file) +{ + return -EINVAL; +} + +static rt_err_t ctty_readlink(struct rt_device *dev, char *buf, int len) +{ + int rc = -ENXIO; + lwp_tty_t tp; + rt_session_t sess; + rt_processgroup_t pgrp; + rt_lwp_t lwp; + + lwp = lwp_self(); + if (lwp) + { + pgrp = lwp->pgrp; + if (pgrp) + { + sess = pgrp->session; + if (sess) + { + tp = sess->ctty; + if (tp) + { + tty_lock(tp); + + if (lwp->pgrp == pgrp && pgrp->session == sess && sess->ctty == tp) + { + rt_strncpy(buf, tp->parent.parent.name, len); + rc = RT_EOK; + } + + tty_unlock(tp); + } + } + } + } + + return rc; +} + +static struct dfs_file_ops ctty_file_ops = { + .open = fops_open, +}; + +/* character device for tty */ +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops tty_dev_ops = { + /* IO directly through device is not allowed */ +}; +#else +#error Must enable RT_USING_DEVICE_OPS in Kconfig +#endif + +rt_inline void device_setup(rt_device_t ctty) +{ + ctty->type = RT_Device_Class_Char; +#ifdef RT_USING_DEVICE_OPS + ctty->ops = &tty_dev_ops; +#else +#error Must enable RT_USING_DEVICE_OPS in Kconfig +#endif +} + +/* register device to DFS */ +static int lwp_ctty_register(rt_device_t ctty) +{ + rt_err_t rc = -RT_ENOMEM; + const char *tty_name = "tty"; + + device_setup(ctty); + rc = rt_device_register(ctty, tty_name, RT_DEVICE_FLAG_DYNAMIC); + if (rc == RT_EOK) + { + ctty->readlink = &ctty_readlink; + ctty->fops = &ctty_file_ops; + } + return rc; +} + +static struct rt_device ctty; + +static int lwp_ctty_init(void) +{ + return lwp_ctty_register(&ctty); +} +INIT_DEVICE_EXPORT(lwp_ctty_init); diff --git a/components/lwp/terminal/tty_device.c b/components/lwp/terminal/tty_device.c new file mode 100644 index 0000000000..17364fabc1 --- /dev/null +++ b/components/lwp/terminal/tty_device.c @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ + +#define DBG_TAG "lwp.tty" +#define DBG_LVL DBG_INFO +#include + +#define TTY_CONF_INCLUDE_CCHARS +#include "tty_config.h" +#include "tty_internal.h" +#include "terminal.h" + +/* configure option: timeout of tty drain wait */ +static int tty_drainwait = 5 * 60; + +#define TTY_NAME_PREFIX "tty" +static char *alloc_device_name(const char *name) +{ + char *tty_dev_name; + long name_buf_len = (sizeof(TTY_NAME_PREFIX) - 1) /* raw prefix */ + + rt_strlen(name) /* custom name */ + + 1; /* tailing \0 */ + + tty_dev_name = rt_malloc(name_buf_len); + if (tty_dev_name) + sprintf(tty_dev_name, "%s%s", TTY_NAME_PREFIX, name); + return tty_dev_name; +} + +/* character device for tty */ +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops tty_dev_ops = { + /* IO directly through device is not allowed */ +}; +#else +#error Must enable RT_USING_DEVICE_OPS in Kconfig +#endif + +static int tty_fops_open(struct dfs_file *file) +{ + int rc; + lwp_tty_t tp; + rt_device_t device; + int devtype = 0; /* unused */ + + if (file->vnode && file->vnode->data) + { + if (file->vnode->ref_count != 1) + { + rc = 0; + } + else + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + rc = bsd_ttydev_methods.d_open(tp, file->flags, devtype, + rt_thread_self()); + } + } + else + { + rc = -EINVAL; + } + + return rc; +} + +static int tty_fops_close(struct dfs_file *file) +{ + int rc; + lwp_tty_t tp; + rt_device_t device; + int fflags = FFLAGS(file->flags); + int devtype = 0; /* unused */ + + if (file->vnode && file->vnode->data) + { + if (file->vnode->ref_count != 1) + { + rc = 0; + } + else + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + rc = bsd_ttydev_methods.d_close(tp, fflags, devtype, rt_thread_self()); + } + } + else + { + rc = -EINVAL; + } + + return rc; +} + +static int tty_fops_ioctl(struct dfs_file *file, int cmd, void *arg) +{ + int rc; + lwp_tty_t tp; + rt_device_t device; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + rc = lwp_tty_ioctl_adapter(tp, cmd, file->flags, arg, rt_thread_self()); + } + else + { + rc = -EINVAL; + } + + return rc; +} + +static ssize_t tty_fops_read(struct dfs_file *file, void *buf, size_t count, + off_t *pos) +{ + ssize_t rc = 0; + int error; + struct uio uio; + struct iovec iov; + rt_device_t device; + struct lwp_tty *tp; + int ioflags; + int oflags = file->flags; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + + /* setup ioflags */ + ioflags = 0; + if (oflags & O_NONBLOCK) + ioflags |= IO_NDELAY; + + /* setup uio parameters */ + iov.iov_base = (void *)buf; + iov.iov_len = count; + uio.uio_offset = file->fpos; + uio.uio_resid = count; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_rw = UIO_READ; + + rc = count; + error = bsd_ttydev_methods.d_read(tp, &uio, ioflags); + rc -= uio.uio_resid; + if (error) + { + LOG_D("%s: failed to write %d bytes of data. error code %d", + __func__, uio.uio_resid, error); + rc = error; + } + + /* reset file context */ + file->fpos = uio.uio_offset; + } + + if (rc) + LOG_D("%s(len=%d, buf=%c \"%d\")", __func__, rc, *((char *)buf), + *((char *)buf)); + return rc; +} + +static ssize_t tty_fops_write(struct dfs_file *file, const void *buf, + size_t count, off_t *pos) +{ + ssize_t rc = 0; + int error; + struct uio uio; + struct iovec iov; + rt_device_t device; + struct lwp_tty *tp; + int ioflags; + int oflags = file->flags; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + + /* setup ioflags */ + ioflags = 0; + if (oflags & O_NONBLOCK) + ioflags |= IO_NDELAY; + + /* setup uio parameters */ + iov.iov_base = (void *)buf; + iov.iov_len = count; + uio.uio_offset = file->fpos; + uio.uio_resid = count; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_rw = UIO_WRITE; + + rc = count; + error = bsd_ttydev_methods.d_write(tp, &uio, ioflags); + if (error) + { + rc = error; + LOG_D("%s: failed to write %d bytes of data. error code %d", + __func__, uio.uio_resid, error); + } + else + { + rc -= uio.uio_resid; + } + + /* reset file context */ + file->fpos = uio.uio_offset; + } + return rc; +} + +static int tty_fops_flush(struct dfs_file *file) +{ + return -EINVAL; +} + +static off_t tty_fops_lseek(struct dfs_file *file, off_t offset, int wherece) +{ + return -EINVAL; +} + +static int tty_fops_truncate(struct dfs_file *file, off_t offset) +{ + /** + * regarding to POSIX.1, TRUNC is not supported for tty device. + * return 0 always to make filesystem happy + */ + return 0; +} + +static int tty_fops_poll(struct dfs_file *file, struct rt_pollreq *req) +{ + int rc; + rt_device_t device; + struct lwp_tty *tp; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + + rc = bsd_ttydev_methods.d_poll(tp, req, rt_thread_self()); + } + else + { + rc = -1; + } + + return rc; +} + +static int tty_fops_mmap(struct dfs_file *file, struct lwp_avl_struct *mmap) +{ + return -EINVAL; +} + +static int tty_fops_lock(struct dfs_file *file, struct file_lock *flock) +{ + return -EINVAL; +} + +static int tty_fops_flock(struct dfs_file *file, int operation, struct file_lock *flock) +{ + return -EINVAL; +} + +static struct dfs_file_ops tty_file_ops = { + .open = tty_fops_open, + .close = tty_fops_close, + .ioctl = tty_fops_ioctl, + .read = tty_fops_read, + .write = tty_fops_write, + .flush = tty_fops_flush, + .lseek = tty_fops_lseek, + .truncate = tty_fops_truncate, + .poll = tty_fops_poll, + .mmap = tty_fops_mmap, + .lock = tty_fops_lock, + .flock = tty_fops_flock, +}; + +rt_inline void device_setup(lwp_tty_t terminal) +{ + terminal->parent.type = RT_Device_Class_Char; +#ifdef RT_USING_DEVICE_OPS + terminal->parent.ops = &tty_dev_ops; +#else +#error Must enable RT_USING_DEVICE_OPS in Kconfig +#endif +} + +/* register TTY device */ +rt_err_t lwp_tty_register(lwp_tty_t terminal, const char *name) +{ + rt_err_t rc = -RT_ENOMEM; + const char *tty_name; + char *alloc_name; + + if (terminal->t_devsw->tsw_flags & TF_NOPREFIX) + { + alloc_name = RT_NULL; + tty_name = name; + } + else + { + alloc_name = alloc_device_name(name); + tty_name = alloc_name; + } + + if (tty_name) + { + device_setup(terminal); + rc = rt_device_register(&terminal->parent, tty_name, 0); + if (rc == RT_EOK) + { + terminal->parent.fops = &tty_file_ops; + + LOG_D("%s() /dev/%s device registered", __func__, tty_name); + } + + rt_free(alloc_name); + } + return rc; +} + +static void tty_init_termios(lwp_tty_t tp) +{ + struct termios *t = &tp->t_termios_init_in; + + t->c_cflag = TTYDEF_CFLAG; + t->c_iflag = TTYDEF_IFLAG; + t->c_lflag = TTYDEF_LFLAG; + t->c_oflag = TTYDEF_OFLAG; + t->__c_ispeed = TTYDEF_SPEED; + t->__c_ospeed = TTYDEF_SPEED; + + memcpy(&t->c_cc, tty_ctrl_charset, + sizeof(tty_ctrl_charset) / sizeof(tty_ctrl_charset[0])); + +#ifdef USING_BSD_INIT_LOCK_DEVICE + tp->t_termios_init_out = *t; +#endif /* USING_BSD_INIT_LOCK_DEVICE */ +} + +lwp_tty_t lwp_tty_create_ext(lwp_ttydevsw_t handle, void *softc, + rt_mutex_t custom_mtx) +{ + lwp_tty_t tp; + + tp = rt_calloc(1, sizeof(struct lwp_tty) + #ifdef USING_BSD_SIGINFO + + LWP_TTY_PRBUF_SIZE + #endif + ); + + if (!tp) + return tp; + + bsd_devsw_init(handle); + +#ifdef USING_BSD_SIGINFO + tp->t_prbufsz = LWP_TTY_PRBUF_SIZE; +#endif + tp->t_devsw = handle; + tp->t_devswsoftc = softc; + tp->t_flags = handle->tsw_flags; + tp->t_drainwait = tty_drainwait; + + tty_init_termios(tp); + + cv_init(&tp->t_inwait, "ttyin"); + cv_init(&tp->t_outwait, "ttyout"); + cv_init(&tp->t_outserwait, "ttyosr"); + cv_init(&tp->t_bgwait, "ttybg"); + cv_init(&tp->t_dcdwait, "ttydcd"); + + rt_wqueue_init(&tp->t_inpoll); + rt_wqueue_init(&tp->t_outpoll); + + /* Allow drivers to use a custom mutex to lock the TTY. */ + if (custom_mtx != NULL) + { + tp->t_mtx = custom_mtx; + } + else + { + tp->t_mtx = &tp->t_mtxobj; + rt_mutex_init(&tp->t_mtxobj, "ttydev", RT_IPC_FLAG_PRIO); + } + +#ifdef USING_BSD_POLL + knlist_init_mtx(&tp->t_inpoll.si_note, tp->t_mtx); + knlist_init_mtx(&tp->t_outpoll.si_note, tp->t_mtx); +#endif + + return tp; +} + +lwp_tty_t lwp_tty_create(lwp_ttydevsw_t handle, void *softc) +{ + return lwp_tty_create_ext(handle, softc, NULL); +} + +void lwp_tty_delete(lwp_tty_t tp) +{ + /* + * ttyydev_leave() usually frees the i/o queues earlier, but it is + * not always called between queue allocation and here. The queues + * may be allocated by ioctls on a pty control device without the + * corresponding pty slave device ever being open, or after it is + * closed. + */ + ttyinq_free(&tp->t_inq); + ttyoutq_free(&tp->t_outq); + rt_wqueue_wakeup_all(&tp->t_inpoll, (void *)POLLHUP); + rt_wqueue_wakeup_all(&tp->t_outpoll, (void *)POLLHUP); + +#ifdef USING_BSD_POLL + knlist_destroy(&tp->t_inpoll.si_note); + knlist_destroy(&tp->t_outpoll.si_note); +#endif + + cv_destroy(&tp->t_inwait); + cv_destroy(&tp->t_outwait); + cv_destroy(&tp->t_bgwait); + cv_destroy(&tp->t_dcdwait); + cv_destroy(&tp->t_outserwait); + + if (tp->t_mtx == &tp->t_mtxobj) + rt_mutex_detach(&tp->t_mtxobj); + ttydevsw_free(tp); + rt_device_unregister(&tp->parent); + rt_free(tp); +} + +/* + * Report on state of foreground process group. + */ +void tty_info(struct lwp_tty *tp) +{ + /* TODO */ + return; +} diff --git a/components/lwp/terminal/tty_internal.h b/components/lwp/terminal/tty_internal.h new file mode 100644 index 0000000000..8bf336cb89 --- /dev/null +++ b/components/lwp/terminal/tty_internal.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-11-13 Shell init ver. + */ +#ifndef __LWP_TTY_INTERNAL_H__ +#define __LWP_TTY_INTERNAL_H__ + +#include "lwp.h" +#include "terminal.h" + +extern struct cdevsw bsd_ttydev_methods; + +extern struct bsd_fileops bsd_ptsdev_methods; + +/* bsd devsw porting */ +void bsd_devsw_init(struct lwp_ttydevsw *tsw); + +/** + * Do not assert RTS or DTR automatically. If CNO_RTSDTR is set then the RTS and + * DTR lines will not be asserted when the device is opened. As a result, this + * flag is only useful on initial-state devices. + * + * Note: this feature is not using on smart system, so this flag is always 0. + */ +#define CNO_RTSDTR 0 + +/* Waking up readers/writers. */ +int tty_wait(struct lwp_tty *tp, struct rt_condvar *cv); +int tty_wait_background(struct lwp_tty *tp, struct rt_thread *td, int sig); +int tty_timedwait(struct lwp_tty *tp, struct rt_condvar *cv, rt_tick_t timeout); +void tty_wakeup(struct lwp_tty *tp, int flags); + +void tty_info(struct lwp_tty *tp); + +void pts_set_lock(lwp_tty_t pts, rt_bool_t is_lock); +rt_bool_t pts_is_locked(lwp_tty_t pts); +int pts_get_pktmode(lwp_tty_t pts); +int pts_alloc(int fflags, struct rt_thread *td, struct dfs_file *ptm_file); + +int lwp_tty_ioctl_adapter(lwp_tty_t tp, int cmd, int oflags, void *args, rt_thread_t td); + +int lwp_tty_set_ctrl_proc(lwp_tty_t tp, rt_thread_t td); +int lwp_tty_assign_foreground(lwp_tty_t tp, rt_thread_t td, int pgid); +int lwp_tty_bg_stop(struct lwp_tty *tp, struct rt_condvar *cv); + +rt_inline rt_bool_t is_sess_leader(rt_lwp_t p) +{ + /** + * Note: a pgrp leader is never lose its group, so once it's + * true then it's always true + */ + return p->pid == p->sid; +} + +rt_inline int tty_is_ctty(struct lwp_tty *tp, struct rt_lwp *p) +{ + tty_assert_locked(tp); + + return p->pgrp->session == tp->t_session && p->term_ctrlterm; +} + +#endif /* __LWP_TTY_INTERNAL_H__ */ diff --git a/components/lwp/terminal/tty_ptmx.c b/components/lwp/terminal/tty_ptmx.c new file mode 100644 index 0000000000..4158292251 --- /dev/null +++ b/components/lwp/terminal/tty_ptmx.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2006-2023, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2023-12-07 Shell init ver. + */ + +#define DBG_TAG "lwp.tty" +#define DBG_LVL DBG_INFO +#include + +#include "tty_config.h" +#include "tty_internal.h" +#include "bsd_porting.h" +#include "terminal.h" + +#include + +static struct dfs_file_ops ptm_fops; + +static int ptm_fops_open(struct dfs_file *file) +{ + int rc; + rt_uint32_t oflags = file->flags; + rt_thread_t cur_thr = rt_thread_self(); + + /* we don't check refcnt because each open will create a new device */ + if (file->vnode && file->vnode->data) + { + /** + * Filter out illegal flags + */ + if ((oflags & ~(O_RDWR | O_NOCTTY | O_CLOEXEC | O_LARGEFILE)) == 0) + { + rc = pts_alloc(FFLAGS(oflags & O_ACCMODE), cur_thr, file); + + /* detached operation from devfs */ + if (rc == 0) + file->vnode->fops = &ptm_fops; + } + else + { + rc = -EINVAL; + } + } + else + { + rc = -EINVAL; + } + + return rc; +} + +static int ptm_fops_close(struct dfs_file *file) +{ + int rc; + lwp_tty_t tp; + rt_device_t device; + + if (file->vnode && file->vnode->data) + { + if (file->vnode->ref_count != 1) + { + rc = 0; + } + else + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + rc = bsd_ptsdev_methods.fo_close(tp, rt_thread_self()); + } + } + else + { + rc = -EINVAL; + } + + return rc; +} + +static ssize_t ptm_fops_read(struct dfs_file *file, void *buf, size_t count, + off_t *pos) +{ + ssize_t rc = 0; + int error; + struct uio uio; + struct iovec iov; + rt_device_t device; + struct lwp_tty *tp; + int oflags = file->flags; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + + /* setup uio parameters */ + iov.iov_base = (void *)buf; + iov.iov_len = count; + uio.uio_offset = file->fpos; + uio.uio_resid = count; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_rw = UIO_READ; + + rc = count; + error = + bsd_ptsdev_methods.fo_read(tp, &uio, 0, oflags, rt_thread_self()); + rc -= uio.uio_resid; + if (error) + { + rc = error; + } + + /* reset file context */ + file->fpos = uio.uio_offset; + } + + return rc; +} + +static ssize_t ptm_fops_write(struct dfs_file *file, const void *buf, + size_t count, off_t *pos) +{ + ssize_t rc = 0; + int error; + struct uio uio; + struct iovec iov; + rt_device_t device; + struct lwp_tty *tp; + int oflags = file->flags; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + + /* setup uio parameters */ + iov.iov_base = (void *)buf; + iov.iov_len = count; + uio.uio_offset = file->fpos; + uio.uio_resid = count; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_rw = UIO_WRITE; + + rc = count; + error = + bsd_ptsdev_methods.fo_write(tp, &uio, 0, oflags, rt_thread_self()); + if (error) + { + rc = error; + } + else + { + rc -= uio.uio_resid; + } + + /* reset file context */ + file->fpos = uio.uio_offset; + } + return rc; +} + +static int ptm_fops_ioctl(struct dfs_file *file, int cmd, void *arg) +{ + int rc; + lwp_tty_t tp; + rt_device_t device; + rt_ubase_t cmd_normal = (unsigned int)cmd; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + + switch (cmd_normal) + { + case TIOCSPTLCK: + { + int is_lock; + if (lwp_get_from_user(&is_lock, arg, sizeof(int)) != sizeof(int)) + return -EFAULT; + pts_set_lock(tp, is_lock); + rc = 0; + } + break; + case TIOCGPTLCK: + { + int is_lock = pts_is_locked(tp); + if (lwp_put_to_user(arg, &is_lock, sizeof(int)) != sizeof(int)) + return -EFAULT; + rc = 0; + } + break; + case TIOCGPKT: + { + int pktmode = pts_get_pktmode(tp); + if (lwp_put_to_user(arg, &pktmode, sizeof(int)) != sizeof(int)) + return -EFAULT; + rc = 0; + } + break; + default: + rc = bsd_ptsdev_methods.fo_ioctl( + tp, cmd_normal, arg, 0, FFLAGS(file->flags), rt_thread_self()); + break; + } + } + else + { + rc = -EINVAL; + } + + return rc; +} + +static int ptm_fops_flush(struct dfs_file *file) +{ + return -EINVAL; +} + +static off_t ptm_fops_lseek(struct dfs_file *file, off_t offset, int wherece) +{ + return -EINVAL; +} + +static int ptm_fops_truncate(struct dfs_file *file, off_t offset) +{ + return -EINVAL; +} + +static int ptm_fops_poll(struct dfs_file *file, struct rt_pollreq *req) +{ + int rc; + rt_device_t device; + struct lwp_tty *tp; + + if (file->vnode && file->vnode->data) + { + device = (rt_device_t)file->vnode->data; + tp = rt_container_of(device, struct lwp_tty, parent); + + rc = bsd_ptsdev_methods.fo_poll(tp, req, 0, rt_thread_self()); + } + else + { + rc = -1; + } + + return rc; +} + +static int ptm_fops_mmap(struct dfs_file *file, struct lwp_avl_struct *mmap) +{ + return -EINVAL; +} + +static int ptm_fops_lock(struct dfs_file *file, struct file_lock *flock) +{ + return -EINVAL; +} + +static int ptm_fops_flock(struct dfs_file *file, int operation, struct file_lock *flock) +{ + return -EINVAL; +} + +static struct dfs_file_ops ptm_fops = { + .open = ptm_fops_open, + .close = ptm_fops_close, + .ioctl = ptm_fops_ioctl, + .read = ptm_fops_read, + .write = ptm_fops_write, + .flush = ptm_fops_flush, + .lseek = ptm_fops_lseek, + .truncate = ptm_fops_truncate, + .poll = ptm_fops_poll, + .mmap = ptm_fops_mmap, + .lock = ptm_fops_lock, + .flock = ptm_fops_flock, +}; + +rt_err_t lwp_ptmx_init(rt_device_t ptmx_device, const char *root_path) +{ + char *device_name; + int root_len; + const char *dev_rel_path; + rt_err_t rc; + + root_len = strlen(root_path); + dev_rel_path = "/ptmx"; + device_name = rt_malloc(root_len + sizeof("/ptmx")); + + if (device_name) + { + /* Register device */ + sprintf(device_name, "%s%s", root_path, dev_rel_path); + rt_device_register(ptmx_device, device_name, 0); + + /* Setup fops */ + ptmx_device->fops = &ptm_fops; + + rt_free(device_name); + + rc = RT_EOK; + } + else + { + rc = -RT_ENOMEM; + } + + return rc; +} + +/* system level ptmx */ +static struct rt_device sysptmx; + +static struct dfs_file_ops sysptmx_file_ops; + +static rt_err_t sysptmx_readlink(struct rt_device *dev, char *buf, int len) +{ + int rc = 0; + + /* TODO: support multi-root ? */ + strncpy(buf, "pts/ptmx", len); + + return rc; +} + +static int _sys_ptmx_init(void) +{ + rt_err_t rc; + rt_device_t sysptmx_rtdev = &sysptmx; + + /* setup system level device */ + sysptmx_rtdev->type = RT_Device_Class_Char; + sysptmx_rtdev->ops = RT_NULL; + rc = rt_device_register(sysptmx_rtdev, "ptmx", RT_DEVICE_FLAG_DYNAMIC); + if (rc == RT_EOK) + { + sysptmx_rtdev->readlink = &sysptmx_readlink; + sysptmx_rtdev->fops = &sysptmx_file_ops; + } + return rc; +} +INIT_DEVICE_EXPORT(_sys_ptmx_init); -- cgit v1.3.1