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  */
9 
10 #include "proc.h"
11 #include "procfs.h"
12 
13 #include <rthw.h>
14 #include <rtdbg.h>
15 
16 #include <fcntl.h>
17 #include <errno.h>
18 
19 /*
20  * This is the root in the proc tree..
21  */
22 static struct proc_dentry _proc_root = {
23     .mode       = S_IFDIR | (S_IRUSR | S_IRGRP | S_IROTH) | (S_IXUSR | S_IXGRP | S_IXOTH),
24     .ref_count  = 1,
25 
26     .parent = &_proc_root,
27     .node.sibling = RT_LIST_OBJECT_INIT(_proc_root.node.sibling),
28     .node.subnode = RT_LIST_OBJECT_INIT(_proc_root.node.subnode),
29 
30     .fops = RT_NULL,
31 
32     .name = "/",
33     .data = RT_NULL,
34 };
35 
_proc_find(struct proc_dentry ** parent,const char * name)36 static int _proc_find(struct proc_dentry **parent, const char *name)
37 {
38     struct proc_dentry *dentry = RT_NULL, *tmp;
39 
40     dfs_vfs_for_each_subnode(dentry, tmp, (*parent), node)
41     {
42         if (dentry == RT_NULL)
43         {
44             break;
45         }
46 
47         if (rt_strcmp(dentry->name, name) == 0)
48         {
49             *parent = dentry;
50             return 0;
51         }
52     }
53 
54     return -1;
55 }
56 
proc_find(struct proc_dentry ** parent,const char ** name,rt_bool_t force_lookup)57 static int proc_find(struct proc_dentry **parent, const char **name, rt_bool_t force_lookup)
58 {
59     int ret = 0;
60     char *tmp = RT_NULL;
61 
62     if (!(*parent))
63     {
64         *parent = &_proc_root;
65     }
66 
67     tmp = rt_strdup(*name);
68     if (tmp)
69     {
70         char *begin = tmp, *end = RT_NULL;
71         if (*begin == '/')
72         {
73             begin++;
74             if (*begin == '\0')
75             {
76                 rt_free(tmp);
77                 *parent = proc_acquire(*parent);
78                 return ret;
79             }
80         }
81 
82         while (1)
83         {
84             end = rt_strstr(begin, "/");
85             if (end)
86             {
87                 *end = '\0';
88                 ret = _proc_find(parent, begin);
89                 if (ret < 0 || !S_ISDIR((*parent)->mode))
90                 {
91                     *parent = RT_NULL;
92                     ret = -1;
93                     break;
94                 }
95                 begin = end + 1;
96             }
97             else if (force_lookup)
98             {
99                 ret = _proc_find(parent, begin);
100                 if (ret < 0)
101                 {
102                     if ((*parent)->ops && (*parent)->ops->lookup)
103                     {
104                         *parent = (*parent)->ops->lookup(*parent, begin);
105                         if (*parent == RT_NULL)
106                         {
107                             ret = -1;
108                         }
109                     }
110                     else
111                     {
112                         *parent = RT_NULL;
113                     }
114                 }
115                 else
116                 {
117                     *parent = proc_acquire(*parent);
118                 }
119                 break;
120             }
121             else
122             {
123                 *parent = proc_acquire(*parent);
124                 break;
125             }
126         }
127 
128         *name = *name + (begin - tmp);
129 
130         rt_free(tmp);
131     }
132 
133     return ret;
134 }
135 
single_start(struct dfs_seq_file * seq,off_t * index)136 static void *single_start(struct dfs_seq_file *seq, off_t *index)
137 {
138     return NULL + (*index == 0);
139 }
140 
single_next(struct dfs_seq_file * seq,void * data,off_t * index)141 static void *single_next(struct dfs_seq_file *seq, void *data, off_t *index)
142 {
143     ++*index;
144     return NULL;
145 }
146 
single_stop(struct dfs_seq_file * seq,void * data)147 static void single_stop(struct dfs_seq_file *seq, void *data)
148 {
149 }
150 
proc_open(struct dfs_file * file)151 static int proc_open(struct dfs_file *file)
152 {
153     struct proc_dentry *entry = (struct proc_dentry *)file->vnode->data;
154 
155     if (entry->single_show)
156     {
157         struct dfs_seq_ops *seq_ops = (struct dfs_seq_ops *)rt_calloc(1, sizeof(struct dfs_seq_ops));
158         if (seq_ops)
159         {
160             int ret = 0;
161 
162             seq_ops->start = single_start;
163             seq_ops->next = single_next;
164             seq_ops->stop = single_stop;
165             seq_ops->show = entry->single_show;
166 
167             ret = dfs_seq_open(file, seq_ops);
168             if (ret != 0)
169             {
170                 rt_free(seq_ops);
171             }
172             return ret;
173         }
174     }
175 
176     return dfs_seq_open(file, entry->seq_ops);
177 }
178 
proc_close(struct dfs_file * file)179 static int proc_close(struct dfs_file *file)
180 {
181     struct dfs_seq_file *seq = file->data;
182     struct proc_dentry *entry = (struct proc_dentry *)file->vnode->data;
183 
184     if (seq && entry->single_show && seq->ops)
185     {
186         rt_free((void *)seq->ops);
187         seq->ops = RT_NULL;
188     }
189 
190     return dfs_seq_release(file);
191 }
192 
193 static const struct dfs_file_ops proc_file_ops = {
194     .open   = proc_open,
195     .read   = dfs_seq_read,
196     .lseek  = dfs_seq_lseek,
197     .close  = proc_close,
198 };
199 
proc_create(struct proc_dentry ** parent,const char * name,mode_t mode)200 static struct proc_dentry *proc_create(struct proc_dentry **parent, const char *name, mode_t mode)
201 {
202     int ret = 0;
203     struct proc_dentry *dentry = RT_NULL;
204 
205     ret = proc_find(parent, &name, 0);
206     if (ret >= 0)
207     {
208         dentry = *parent;
209         ret = proc_find(&dentry, &name, 1);
210         if (ret < 0)
211         {
212             dentry = rt_calloc(1, sizeof(struct proc_dentry));
213             if (dentry)
214             {
215                 dentry->mode = mode;
216                 dentry->ref_count = 1;
217                 dentry->name = rt_strdup(name);
218                 dfs_vfs_init_node(&dentry->node);
219             }
220         }
221         else
222         {
223             proc_release(dentry);
224             dentry = RT_NULL;
225         }
226     }
227 
228     return dentry;
229 }
230 
231 /**
232  * @brief    The dentry reference count is incremented by one
233  *
234  * @param    dentry
235  *
236  * @return   dentry
237  */
proc_acquire(struct proc_dentry * dentry)238 struct proc_dentry *proc_acquire(struct proc_dentry *dentry)
239 {
240     if (dentry)
241     {
242         dentry->ref_count += 1;
243     }
244 
245     return dentry;
246 }
247 
248 /**
249  * @brief    The dentry reference count is minus one, or release
250  *
251  * @param    dentry
252  *
253  * @return   none
254  */
proc_release(struct proc_dentry * dentry)255 void proc_release(struct proc_dentry *dentry)
256 {
257     if (dentry)
258     {
259         if (dentry->ref_count == 1)
260         {
261             if (dentry->name)
262             {
263                 rt_free(dentry->name);
264             }
265 
266             if (S_ISLNK(dentry->mode) && dentry->data)
267             {
268                 rt_free(dentry->data);
269             }
270 
271             rt_free(dentry);
272         }
273         else
274         {
275             dentry->ref_count -= 1;
276         }
277     }
278 }
279 
proc_register(struct proc_dentry * parent,struct proc_dentry * child)280 static struct proc_dentry *proc_register(struct proc_dentry *parent, struct proc_dentry *child)
281 {
282     child->parent = parent;
283     dfs_vfs_append_node(&parent->node, &child->node);
284     child->ref_count += 1;
285     child->pid = parent->pid;
286 
287     return child;
288 }
289 
290 /**
291  * @brief    Make a dir
292  *
293  * @param    name fullpath based on _proc_root or parent
294  * @param    mode permission configuration
295  * @param    parent can be empty
296  * @param    fops
297  * @param    data
298  *
299  * @return   dentry
300  */
proc_mkdir_data(const char * name,mode_t mode,struct proc_dentry * parent,const struct dfs_file_ops * fops,void * data)301 struct proc_dentry *proc_mkdir_data(const char *name, mode_t mode, struct proc_dentry *parent,
302                                     const struct dfs_file_ops *fops, void *data)
303 {
304     struct proc_dentry *dentry, *_parent = parent;
305 
306     if (mode == 0)
307         mode = (S_IRUSR | S_IRGRP | S_IROTH) | (S_IXUSR | S_IXGRP | S_IXOTH);
308 
309     dentry = proc_create(&_parent, name, S_IFDIR | mode);
310     if (dentry)
311     {
312         dentry->fops = fops;
313         dentry->data = data;
314 
315         dentry = proc_register(_parent, dentry);
316     }
317     proc_release(_parent);
318 
319     return dentry;
320 }
321 
322 /**
323  * @brief    Make a dir
324  *
325  * @param    name fullpath based on _proc_root or parent
326  * @param    mode permission configuration
327  * @param    parent can be empty
328  *
329  * @return   dentry
330  */
proc_mkdir_mode(const char * name,mode_t mode,struct proc_dentry * parent)331 struct proc_dentry *proc_mkdir_mode(const char *name, mode_t mode, struct proc_dentry *parent)
332 {
333     return proc_mkdir_data(name, mode, parent, NULL, NULL);
334 }
335 
336 /**
337  * @brief    Make a dir
338  *
339  * @param    name fullpath based on _proc_root or parent
340  * @param    parent can be empty
341  *
342  * @return   dentry
343  */
proc_mkdir(const char * name,struct proc_dentry * parent)344 struct proc_dentry *proc_mkdir(const char *name, struct proc_dentry *parent)
345 {
346     return proc_mkdir_data(name, 0, parent, NULL, NULL);
347 }
348 
proc_create_reg(const char * name,mode_t mode,struct proc_dentry ** parent)349 static struct proc_dentry *proc_create_reg(const char *name, mode_t mode, struct proc_dentry **parent)
350 {
351     struct proc_dentry *dentry = RT_NULL;
352 
353     if ((mode & S_IFMT) == 0)
354         mode |= S_IFREG;
355     if ((mode & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)) == 0)
356         mode |= S_IRUSR | S_IRGRP | S_IROTH;
357 
358     if (!S_ISREG(mode))
359     {
360         *parent = RT_NULL;
361         return dentry;
362     }
363 
364     return proc_create(parent, name, mode);
365 }
366 
367 /**
368  * @brief    Make a file
369  *
370  * @param    name fullpath based on _proc_root or parent
371  * @param    mode permission configuration
372  * @param    parent can be empty
373  * @param    fops
374  * @param    data
375  *
376  * @return   dentry
377  */
proc_create_data(const char * name,mode_t mode,struct proc_dentry * parent,const struct dfs_file_ops * fops,void * data)378 struct proc_dentry *proc_create_data(const char *name, mode_t mode, struct proc_dentry *parent,
379                                      const struct dfs_file_ops *fops, void *data)
380 {
381     struct proc_dentry *dentry, *_parent = parent;
382 
383     dentry = proc_create_reg(name, mode, &_parent);
384     if (dentry)
385     {
386         dentry->fops = fops ? fops : &proc_file_ops;
387         dentry->data = data;
388 
389         dentry = proc_register(_parent, dentry);
390     }
391     proc_release(_parent);
392 
393     return dentry;
394 }
395 
396 /**
397  * @brief    Make a file
398  *
399  * @param    name fullpath based on _proc_root or parent
400  * @param    mode permission configuration
401  * @param    parent can be empty
402  * @param    show
403  * @param    data
404  *
405  * @return   dentry
406  */
proc_create_single_data(const char * name,mode_t mode,struct proc_dentry * parent,int (* show)(struct dfs_seq_file *,void *),void * data)407 struct proc_dentry *proc_create_single_data(const char *name, mode_t mode, struct proc_dentry *parent,
408                                             int (*show)(struct dfs_seq_file *, void *), void *data)
409 {
410     struct proc_dentry *dentry, *_parent = parent;
411 
412     dentry = proc_create_reg(name, mode, &_parent);
413     if (dentry)
414     {
415         dentry->fops = &proc_file_ops;
416         dentry->single_show = show;
417         dentry->data = data;
418 
419         dentry = proc_register(_parent, dentry);
420     }
421     proc_release(_parent);
422 
423     return dentry;
424 }
425 
426 /**
427  * @brief    Make a symlink
428  *
429  * @param    name fullpath based on _proc_root or parent
430  * @param    parent can be empty
431  * @param    dest  link file fullpath
432  *
433  * @return   dentry
434  */
proc_symlink(const char * name,struct proc_dentry * parent,const char * dest)435 struct proc_dentry *proc_symlink(const char *name, struct proc_dentry *parent, const char *dest)
436 {
437     struct proc_dentry *dentry, *_parent = parent;
438 
439     dentry = proc_create(&_parent, name, (S_IFLNK | (S_IRUSR | S_IRGRP | S_IROTH)
440                          | (S_IWUSR | S_IWGRP | S_IWOTH) | (S_IXUSR | S_IXGRP | S_IXOTH)));
441     if (dentry)
442     {
443         dentry->data = (void *)rt_strdup(dest);
444         if (dentry->data)
445         {
446             dentry = proc_register(_parent, dentry);
447         }
448         else
449         {
450             proc_release(dentry);
451             dentry = NULL;
452         }
453     }
454     proc_release(_parent);
455 
456     return dentry;
457 }
458 
remove_proc_subtree(struct proc_dentry * dentry)459 static void remove_proc_subtree(struct proc_dentry *dentry)
460 {
461     struct proc_dentry *iter = RT_NULL, *iter_tmp, *tmp = RT_NULL;
462 
463     dfs_vfs_for_each_subnode(iter, iter_tmp, dentry, node)
464     {
465         if (iter == RT_NULL)
466         {
467             break;
468         }
469 
470         if (tmp)
471         {
472             proc_release(tmp);
473             tmp = RT_NULL;
474         }
475 
476         tmp = iter;
477 
478         if (S_ISDIR(dentry->mode))
479         {
480             remove_proc_subtree(iter);
481         }
482     }
483 
484     if (tmp)
485     {
486         proc_release(tmp);
487         tmp = RT_NULL;
488     }
489 }
490 
491 /**
492  * @brief    remove a dentry
493  *
494  * @param    dentry
495  *
496  * @return   none
497  */
proc_remove(struct proc_dentry * dentry)498 void proc_remove(struct proc_dentry *dentry)
499 {
500     if (dentry && dentry != &_proc_root)
501     {
502         if (S_ISDIR(dentry->mode))
503         {
504             remove_proc_subtree(dentry);
505         }
506 
507         dfs_vfs_remove_node(&dentry->node);
508         proc_release(dentry);
509     }
510 }
511 
512 /**
513  * @brief    find dentry exist
514  *
515  * @param    name fullpath based on _proc_root
516  *
517  * @return   dentry
518  */
dfs_proc_find(const char * name)519 struct proc_dentry *dfs_proc_find(const char *name)
520 {
521     struct proc_dentry *dentry = RT_NULL;
522 
523     proc_find(&dentry, &name, 1);
524 
525     return dentry;
526 }
527 
528 /**
529  * @brief    remove a dentry on parent
530  *
531  * @param    name fullpath based on parent
532  * @param    parent
533  *
534  * @return   none
535  */
proc_remove_dentry(const char * name,struct proc_dentry * parent)536 void proc_remove_dentry(const char *name, struct proc_dentry *parent)
537 {
538     struct proc_dentry *dentry = parent;
539 
540     if (proc_find(&dentry, &name, 1) >= 0)
541     {
542         proc_remove(dentry);
543         proc_release(dentry);
544     }
545 }
546 
547 #define _COLOR_RED      "\033[31m"
548 #define _COLOR_GREEN    "\033[32m"
549 #define _COLOR_BLUE     "\033[34m"
550 #define _COLOR_CYAN     "\033[36m"
551 #define _COLOR_WHITE    "\033[37m"
552 #define _COLOR_NORMAL   "\033[0m"
553 
dump_proc_subtree(struct proc_dentry * dentry,int tab)554 static void dump_proc_subtree(struct proc_dentry *dentry, int tab)
555 {
556     struct proc_dentry *iter = RT_NULL, *tmp;
557 
558     dfs_vfs_for_each_subnode(iter, tmp, dentry, node)
559     {
560         if (iter == RT_NULL)
561         {
562             break;
563         }
564 
565         for(int i = 0; i < tab; i ++)
566         {
567             rt_kprintf("%-4s", i + 1 >= tab ? "|-" : " ");
568         }
569 
570         if (S_ISDIR(iter->mode))
571         {
572             rt_kprintf(_COLOR_BLUE "%-20s" _COLOR_NORMAL " %d\n", iter->name, iter->ref_count);
573             dump_proc_subtree(iter, tab + 1);
574         }
575         else if (S_ISLNK(iter->mode))
576         {
577             rt_kprintf(_COLOR_CYAN "%-20s" _COLOR_NORMAL " %d\n", iter->name, iter->ref_count);
578         }
579         else
580         {
581             rt_kprintf("%-20s %d\n", iter->name, iter->ref_count);
582         }
583     }
584 }
585 
proc_dump(struct proc_dentry * dentry)586 static void proc_dump(struct proc_dentry *dentry)
587 {
588     if (dentry)
589     {
590         if (S_ISDIR(dentry->mode))
591         {
592             rt_kprintf(_COLOR_BLUE "%-20s" _COLOR_NORMAL " %d\n", dentry->name, dentry->ref_count);
593             dump_proc_subtree(dentry, 1);
594         }
595         else if (S_ISLNK(dentry->mode))
596         {
597             rt_kprintf(_COLOR_CYAN "%-20s" _COLOR_NORMAL " %d\n", dentry->name, dentry->ref_count);
598         }
599         else
600         {
601             rt_kprintf("%-20s %d\n", dentry->name, dentry->ref_count);
602         }
603     }
604 }
605 
msh_proc_dump(int argc,char ** argv)606 static int msh_proc_dump(int argc, char** argv)
607 {
608     const char *name = argc > 1 ? argv[1] : "/";
609     struct proc_dentry *dentry = RT_NULL;
610 
611     int ret = proc_find(&dentry, &name, 1);
612     if (ret >= 0)
613     {
614         proc_dump(dentry);
615     }
616     proc_release(dentry);
617 
618     return 0;
619 }
620 MSH_CMD_EXPORT_ALIAS(msh_proc_dump, proc_dump, proc dump);
621 
msh_proc_remove(int argc,char ** argv)622 static int msh_proc_remove(int argc, char** argv)
623 {
624     if (argc > 1)
625     {
626         const char *name = argv[1];
627         struct proc_dentry *dentry = RT_NULL;
628 
629         int ret = proc_find(&dentry, &name, 1);
630         if (ret >= 0)
631         {
632             if (dentry != &_proc_root)
633             {
634                 proc_remove(dentry);
635             }
636             else
637             {
638                 struct proc_dentry *iter = RT_NULL, *iter_tmp, *tmp = RT_NULL;
639 
640                 dfs_vfs_for_each_subnode(iter, iter_tmp, dentry, node)
641                 {
642                     if (iter == RT_NULL)
643                     {
644                         break;
645                     }
646 
647                     if (tmp)
648                     {
649                         proc_remove(tmp);
650                     }
651 
652                     tmp = iter;
653                 }
654 
655                 if (tmp)
656                 {
657                     proc_remove(tmp);
658                 }
659             }
660         }
661         proc_release(dentry);
662     }
663     else
664     {
665         rt_kprintf("proc_remove path\n");
666     }
667 
668     return 0;
669 }
670 MSH_CMD_EXPORT_ALIAS(msh_proc_remove, proc_remove, proc remove);
671 
msh_proc_symlink(int argc,char ** argv)672 static int msh_proc_symlink(int argc, char** argv)
673 {
674     if (argc > 2)
675     {
676         struct proc_dentry *entry = proc_symlink(argv[1], 0, argv[2]);
677         if (entry)
678         {
679             proc_release(entry);
680         }
681     }
682     else
683     {
684         rt_kprintf("proc_symlink path dest\n");
685     }
686 
687     return 0;
688 }
689 MSH_CMD_EXPORT_ALIAS(msh_proc_symlink, proc_symlink, proc symlink);
690 
msh_proc_echo(int argc,char ** argv)691 static int msh_proc_echo(int argc, char** argv)
692 {
693     if (argc > 1)
694     {
695         for(int i = 1; i <= argc - 1; i ++)
696         {
697             struct proc_dentry *entry = proc_create_data(argv[i], 0, 0, 0, 0);
698             if (entry)
699             {
700                 proc_release(entry);
701             }
702         }
703     }
704     else
705     {
706         rt_kprintf("proc_echo path\n");
707     }
708 
709     return 0;
710 }
711 MSH_CMD_EXPORT_ALIAS(msh_proc_echo, proc_echo, proc echo);
712 
msh_proc_mkdir(int argc,char ** argv)713 static int msh_proc_mkdir(int argc, char** argv)
714 {
715     if (argc > 1)
716     {
717         for(int i = 1; i <= argc - 1; i ++)
718         {
719             struct proc_dentry *entry = proc_mkdir(argv[i], 0);
720             if (entry)
721             {
722                 proc_release(entry);
723             }
724         }
725     }
726     else
727     {
728         rt_kprintf("proc_mkdir path\n");
729     }
730 
731     return 0;
732 }
733 MSH_CMD_EXPORT_ALIAS(msh_proc_mkdir, proc_mkdir, proc mkdir);
734