1 /*
2  * Copyright (c) 2006-2023, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2023-11-17     xqyjlj       the first version
9  * 2023-11-28     Shell        Add reference management for pgrp;
10  *                             Using lwp lock API and fix the dead lock problem
11  */
12 
13 #include "lwp.h"
14 #include "lwp_internal.h"
15 #include "lwp_syscall.h"
16 
17 #define DBG_TAG "lwp.pgrp"
18 #define DBG_LVL DBG_WARNING
19 #include <rtdbg.h>
20 
lwp_pgrp_dec_ref(rt_processgroup_t pgrp)21 void lwp_pgrp_dec_ref(rt_processgroup_t pgrp)
22 {
23     if (rt_atomic_add(&pgrp->ref, -1) == 1)
24     {
25         rt_mutex_detach(&(pgrp->mutex));
26 
27         /* clear self pgid */
28         pgrp->pgid = 0;
29         rt_free(pgrp);
30     }
31 }
32 
lwp_pgrp_find_and_inc_ref(pid_t pgid)33 rt_processgroup_t lwp_pgrp_find_and_inc_ref(pid_t pgid)
34 {
35     rt_processgroup_t group;
36 
37     group = lwp_pgrp_find(pgid);
38     if (group)
39     {
40         rt_atomic_add(&(group->ref), 1);
41     }
42 
43     return group;
44 }
45 
lwp_pgrp_find(pid_t pgid)46 rt_processgroup_t lwp_pgrp_find(pid_t pgid)
47 {
48     rt_base_t level;
49     rt_processgroup_t group = RT_NULL;
50     rt_list_t *node = RT_NULL;
51     struct rt_object_information *information = RT_NULL;
52 
53     information = rt_object_get_information(RT_Object_Class_ProcessGroup);
54 
55     /* parameter check */
56     if ((pgid < 0) || (information == RT_NULL))
57     {
58         return RT_NULL;
59     }
60 
61     if (pgid == 0)
62     {
63         pgid = lwp_getpid();
64     }
65 
66     /* enter critical */
67     level = rt_spin_lock_irqsave(&(information->spinlock));
68 
69     /* try to find process group */
70     rt_list_for_each(node, &(information->object_list))
71     {
72         group = (rt_processgroup_t)rt_list_entry(node, struct rt_object, list);
73         if (group->pgid == pgid)
74         {
75             rt_spin_unlock_irqrestore(&(information->spinlock), level);
76 
77             return group;
78         }
79     }
80 
81     rt_spin_unlock_irqrestore(&(information->spinlock), level);
82 
83     LOG_I("cannot find(pgid:%d)() by (pid:%d, pgid:%d)", pgid, lwp_getpid(), lwp_pgid_get_byprocess(lwp_self()));
84 
85     return RT_NULL;
86 }
87 
lwp_pgrp_create(rt_lwp_t leader)88 rt_processgroup_t lwp_pgrp_create(rt_lwp_t leader)
89 {
90     rt_processgroup_t group = RT_NULL;
91 
92     /* parameter check */
93     if (leader == RT_NULL)
94     {
95         return RT_NULL;
96     }
97 
98     group = rt_malloc(sizeof(struct rt_processgroup));
99     if (group != RT_NULL)
100     {
101         rt_object_init(&(group->object), RT_Object_Class_ProcessGroup, "pgrp");
102         rt_list_init(&(group->process));
103         rt_list_init(&(group->pgrp_list_node));
104         rt_mutex_init(&(group->mutex), "pgrp", RT_IPC_FLAG_PRIO);
105         group->leader = leader;
106         group->sid = 0;
107         group->session = RT_NULL;
108         group->is_orphaned = 0;
109         group->pgid = lwp_to_pid(leader);
110         rt_atomic_store(&group->ref, 1);
111     }
112 
113     LOG_I("create(ptr:%p, pgid:%d)() by pid:%d", group, group->pgid, lwp_getpid());
114 
115     return group;
116 }
117 
118 #include <terminal/terminal.h>
119 
lwp_pgrp_delete(rt_processgroup_t group)120 int lwp_pgrp_delete(rt_processgroup_t group)
121 {
122     int retry = 1;
123     rt_session_t session = RT_NULL;
124     int is_session_free = 0;
125     lwp_tty_t ctty;
126 
127     /* parameter check */
128     if (group == RT_NULL)
129     {
130         return -EINVAL;
131     }
132 
133     LOG_I("delete(ptr:%p, pgid:%d)() by pid:%d", group, group->pgid, lwp_getpid());
134 
135     while (retry)
136     {
137         retry = 0;
138         session = lwp_session_find(lwp_sid_get_bypgrp(group));
139         if (session)
140         {
141             ctty = session->ctty;
142             if (ctty)
143             {
144                 /**
145                  * Note: it's safe to release pgrp even we do this multiple,
146                  * the neccessary check is done before the tty actually detach
147                  */
148                 tty_lock(ctty);
149                 tty_rel_pgrp(ctty, group); // tty_unlock
150             }
151 
152             SESS_LOCK(session);
153             PGRP_LOCK_NESTED(group);
154             if (group->session == session && session->ctty == ctty)
155             {
156                 rt_object_detach(&(group->object));
157                 is_session_free = lwp_session_remove(session, group);
158             }
159             else
160             {
161                 retry = 1;
162 
163             }
164 
165             PGRP_UNLOCK(group);
166             if (is_session_free != 1)
167                 SESS_UNLOCK(session);
168         }
169         else
170         {
171             rt_object_detach(&(group->object));
172         }
173     }
174 
175     lwp_pgrp_dec_ref(group);
176     return 0;
177 }
178 
lwp_pgrp_insert(rt_processgroup_t group,rt_lwp_t process)179 int lwp_pgrp_insert(rt_processgroup_t group, rt_lwp_t process)
180 {
181     /* parameter check */
182     if (group == RT_NULL || process == RT_NULL)
183     {
184         return -EINVAL;
185     }
186 
187     PGRP_LOCK_NESTED(group);
188     LWP_LOCK_NESTED(process);
189     RT_ASSERT(rt_mutex_get_hold(&process->lwp_lock) <= rt_mutex_get_hold(&group->mutex));
190 
191     process->pgid = group->pgid;
192     process->pgrp = group;
193     process->sid = group->sid;
194     rt_list_insert_after(&(group->process), &(process->pgrp_node));
195 
196     LWP_UNLOCK(process);
197     PGRP_UNLOCK(group);
198 
199     return 0;
200 }
201 
lwp_pgrp_remove(rt_processgroup_t group,rt_lwp_t process)202 int lwp_pgrp_remove(rt_processgroup_t group, rt_lwp_t process)
203 {
204     rt_bool_t is_empty = RT_FALSE;
205 
206     /* parameter check */
207     if (group == RT_NULL || process == RT_NULL)
208     {
209         return -EINVAL;
210     }
211 
212     PGRP_LOCK_NESTED(group);
213     LWP_LOCK_NESTED(process);
214     RT_ASSERT(rt_mutex_get_hold(&process->lwp_lock) <= rt_mutex_get_hold(&group->mutex));
215 
216     rt_list_remove(&(process->pgrp_node));
217     /* clear children sid and pgid */
218     process->pgrp = RT_NULL;
219     process->pgid = 0;
220     process->sid = 0;
221 
222     LWP_UNLOCK(process);
223 
224     is_empty = rt_list_isempty(&(group->process));
225 
226     PGRP_UNLOCK(group);
227 
228     if (is_empty)
229     {
230         lwp_pgrp_delete(group);
231         return 1;
232     }
233 
234     return 0;
235 }
236 
lwp_pgrp_move(rt_processgroup_t group,rt_lwp_t process)237 int lwp_pgrp_move(rt_processgroup_t group, rt_lwp_t process)
238 {
239     int retry = 1;
240     rt_processgroup_t old_group;
241 
242     /* parameter check */
243     if (group == RT_NULL || process == RT_NULL)
244     {
245         return -EINVAL;
246     }
247 
248     if (lwp_pgid_get_bypgrp(group) == lwp_pgid_get_byprocess(process))
249     {
250         return 0;
251     }
252 
253     PGRP_LOCK(group);
254     while (retry)
255     {
256         retry = 0;
257         old_group = lwp_pgrp_find_and_inc_ref(lwp_pgid_get_byprocess(process));
258 
259         PGRP_LOCK(old_group);
260         LWP_LOCK(process);
261 
262         if (process->pgrp == old_group)
263         {
264             lwp_pgrp_remove(old_group, process);
265             lwp_pgrp_insert(group, process);
266         }
267         else
268         {
269             retry = 1;
270         }
271         PGRP_UNLOCK(old_group);
272         LWP_UNLOCK(process);
273 
274         lwp_pgrp_dec_ref(old_group);
275     }
276     PGRP_UNLOCK(group);
277 
278     return 0;
279 }
280 
lwp_pgrp_update_children_info(rt_processgroup_t group,pid_t sid,pid_t pgid)281 int lwp_pgrp_update_children_info(rt_processgroup_t group, pid_t sid, pid_t pgid)
282 {
283     rt_list_t *node = RT_NULL;
284     rt_lwp_t process = RT_NULL;
285 
286     if (group == RT_NULL)
287     {
288         return -EINVAL;
289     }
290 
291     PGRP_LOCK_NESTED(group);
292 
293     /* try to find process group */
294     rt_list_for_each(node, &(group->process))
295     {
296         process = (rt_lwp_t)rt_list_entry(node, struct rt_lwp, pgrp_node);
297         LWP_LOCK(process);
298         if (sid != -1)
299         {
300             process->sid = sid;
301         }
302         if (pgid != -1)
303         {
304             process->pgid = pgid;
305             process->pgrp = group;
306         }
307         LWP_UNLOCK(process);
308     }
309 
310     PGRP_UNLOCK(group);
311     return 0;
312 }
313 
314 /**
315  * setpgid() sets the PGID of the process specified by pid to pgid.
316  *  If pid is zero, then the process ID of the calling process is used.
317  *  If pgid is zero, then the PGID of the process specified by pid is made the same as its process ID.
318  *  If setpgid() is used to move a process from one process group to another (as is done by some shells when
319  *      creating pipelines), both process groups must be part of the same session (see setsid(2) and credentials(7)).
320  *      In this case, the pgid specifies an existing process group to be joined and the session ID of that group must
321  *      match the session ID of the joining process.
322  */
sys_setpgid(pid_t pid,pid_t pgid)323 sysret_t sys_setpgid(pid_t pid, pid_t pgid)
324 {
325     rt_lwp_t process, self_process;
326     pid_t sid;
327     rt_processgroup_t group;
328     rt_session_t session;
329     sysret_t err = 0;
330 
331     if (pgid == 0)
332     {
333         pgid = pid;
334     }
335     if (pgid < 0)
336     {
337         return -EINVAL;
338     }
339 
340     self_process = lwp_self();
341 
342     if (pid == 0)
343     {
344         pid = self_process->pid;
345         process = self_process;
346     }
347     else
348     {
349         lwp_pid_lock_take();
350         process = lwp_from_pid_locked(pid);
351         lwp_pid_lock_release();
352 
353         if (process == RT_NULL)
354         {
355             return -ESRCH;
356         }
357     }
358 
359     LWP_LOCK(process);
360 
361     if (process->parent == self_process)
362     {
363         /**
364          * change the process group ID of one of the children of the calling process and the child was in
365          *  a different session
366          */
367         if (lwp_sid_get_byprocess(process) != lwp_sid_get_byprocess(self_process))
368         {
369             err = -EPERM;
370             LWP_UNLOCK(process);
371             goto exit;
372         }
373         /**
374          * An attempt was made to change the process group ID of one of the children of the calling process
375          *  and the child had already performed an execve(2)
376          */
377         if (process->did_exec)
378         {
379             err = -EACCES;
380             LWP_UNLOCK(process);
381             goto exit;
382         }
383     }
384     else
385     {
386         /**
387          * pid is not the calling process and not a child of the calling process.
388          */
389         if (process != self_process)
390         {
391             err = -ESRCH;
392             LWP_UNLOCK(process);
393             goto exit;
394         }
395     }
396 
397     LWP_UNLOCK(process);
398 
399     sid = lwp_sid_get_byprocess(self_process);
400     if (pgid != pid)
401     {
402         group = lwp_pgrp_find(pgid);
403         if (group == RT_NULL)
404         {
405             group = lwp_pgrp_create(process);
406             lwp_pgrp_move(group, process);
407             session = lwp_session_find(sid);
408             if (session == RT_NULL)
409             {
410                 LOG_E("the session of sid: %d cannot be found", sid);
411                 err = -EPERM;
412                 goto exit;
413             }
414             else
415             {
416                 lwp_session_insert(session, group);
417             }
418         }
419         else
420         {
421             /**
422              * An attempt was made to move a process into a process group in a different session
423              */
424             if (sid != lwp_sid_get_bypgrp(group))
425             {
426                 err = -EPERM;
427                 goto exit;
428             }
429             /**
430              * or to change the process group ID of a session leader
431              */
432             if (sid == lwp_to_pid(process))
433             {
434                 err = -EPERM;
435                 goto exit;
436             }
437             lwp_pgrp_move(group, process);
438         }
439     }
440     else
441     {
442         group = lwp_pgrp_find(pgid);
443         if (group == RT_NULL)
444         {
445             group = lwp_pgrp_create(process);
446             lwp_pgrp_move(group, process);
447             session = lwp_session_find(sid);
448             if (session == RT_NULL)
449             {
450                 LOG_E("the session of sid: %d cannot be found", sid);
451                 err = -EPERM;
452                 goto exit;
453             }
454             else
455             {
456                 lwp_session_insert(session, group);
457             }
458         }
459         else // this represents repeated calls
460         {
461             /**
462              * or to change the process group ID of a session leader
463              */
464             if (lwp_sid_get_bypgrp(group) == lwp_pgid_get_bypgrp(group))
465             {
466                 err = -EPERM;
467                 goto exit;
468             }
469             else
470             {
471                 err = 0;
472             }
473         }
474     }
475 
476 exit:
477     return err;
478 }
479 
480 /**
481  * getpgid() returns the PGID of the process specified by pid.
482  *  If pid is zero, the process ID of the calling process is used. (Retrieving the PGID of a process other
483  *      than the caller is rarely necessary, and the POSIX.1 getpgrp() is preferred for that task.)
484  */
sys_getpgid(pid_t pid)485 sysret_t sys_getpgid(pid_t pid)
486 {
487     rt_lwp_t process;
488 
489     lwp_pid_lock_take();
490     process = lwp_from_pid_locked(pid);
491     lwp_pid_lock_release();
492 
493     if (process == RT_NULL)
494     {
495         return -ESRCH;
496     }
497 
498     return lwp_pgid_get_byprocess(process);
499 }
500 
501 #ifdef RT_USING_FINSH
502 
503 #include "finsh.h"
504 
list_processgroup(void)505 long list_processgroup(void)
506 {
507     int count = 0, index;
508     rt_processgroup_t *groups;
509     rt_processgroup_t group;
510     rt_thread_t thread;
511     char name[RT_NAME_MAX];
512 
513     rt_kprintf("PGID SID  leader process\n");
514     rt_kprintf("---- ---- ----------------\n");
515 
516     count = rt_object_get_length(RT_Object_Class_ProcessGroup);
517     if (count > 0)
518     {
519         /* get  pointers */
520         groups = (rt_processgroup_t *)rt_calloc(count, sizeof(rt_processgroup_t));
521         if (groups)
522         {
523             index = rt_object_get_pointers(RT_Object_Class_ProcessGroup, (rt_object_t *)groups, count);
524             if (index > 0)
525             {
526                 for (index = 0; index < count; index++)
527                 {
528                     struct rt_processgroup pgrp;
529                     group = groups[index];
530                     PGRP_LOCK(group);
531                     rt_memcpy(&pgrp, group, sizeof(struct rt_processgroup));
532                     PGRP_UNLOCK(group);
533 
534                     if (pgrp.leader)
535                     {
536                         thread = rt_list_entry(pgrp.leader->t_grp.prev, struct rt_thread, sibling);
537                         rt_strncpy(name, thread->parent.name, RT_NAME_MAX);
538                     }
539                     else
540                     {
541                         rt_strncpy(name, "nil", RT_NAME_MAX);
542                     }
543 
544                     rt_kprintf("%4d %4d %-*.*s\n", pgrp.pgid, pgrp.sid, RT_NAME_MAX, RT_NAME_MAX, name);
545                 }
546             }
547             rt_free(groups);
548         }
549     }
550 
551     return 0;
552 }
553 MSH_CMD_EXPORT(list_processgroup, list process group);
554 #endif
555