/* * 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