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-12-02     Shell        init ver.
9  */
10 #define DBG_TAG "filesystem.ptyfs"
11 #define DBG_LVL DBG_INFO
12 #include <rtdbg.h>
13 
14 #include "ptyfs.h"
15 
16 #include <dfs.h>
17 #include <dfs_fs.h>
18 #include <dfs_dentry.h>
19 #include <dfs_file.h>
20 #include <dfs_mnt.h>
21 #include <devfs.h>
22 #include <rid_bitmap.h>
23 #include <rthw.h>
24 #include <rtthread.h>
25 #include <terminal/terminal.h>
26 
27 #include <dirent.h>
28 #include <unistd.h>
29 
30 #ifndef S_IRWXUGO
31 #define S_IRWXUGO (S_IRWXU | S_IRWXG | S_IRWXO)
32 #endif /* S_IRWXUGO */
33 #ifndef S_IALLUGO
34 #define S_IALLUGO (S_ISUID | S_ISGID | S_ISVTX | S_IRWXUGO)
35 #endif /* S_IALLUGO */
36 #ifndef S_IRUGO
37 #define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH)
38 #endif /* S_IRUGO */
39 #ifndef S_IWUGO
40 #define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH)
41 #endif /* S_IWUGO */
42 #ifndef S_IXUGO
43 #define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH)
44 #endif /* S_IXUGO */
45 
46 #define PTYFS_MAGIC           0x9D94A07D
47 #define PTYFS_TYPE_DIR        0x00
48 #define PTYFS_TYPE_FILE_PTMX  0x01
49 #define PTYFS_TYPE_FILE_SLAVE 0x02
50 
51 /* TODO: using Symbolic permission, but not ours */
52 #define PTMX_DEFAULT_FILE_MODE (S_IFCHR | 0666)
53 #define PTS_DEFAULT_FILE_MODE  (S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP)
54 #define ROOT_DEFUALT_FILE_MODE (S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR)
55 
56 struct ptyfs_sb;
57 
58 struct ptyfs_file
59 {
60     char basename[DIRENT_NAME_MAX]; /* file name */
61     rt_uint32_t mode;               /* file modes allowed */
62     rt_uint32_t type;               /* file type */
63     rt_list_t subdirs;              /* file subdir list */
64     rt_list_t ent_node;             /* entry node in subdir list */
65     struct ptyfs_sb *sb;            /* superblock ptr */
66     rt_device_t device;             /* device binding on this file */
67 };
68 
69 struct ptyfs_sb
70 {
71     struct rt_device ptmx_device; /* ptmx device */
72     struct rt_mutex lock;         /* tmpfs lock */
73     struct ptyfs_file root_file;  /* root dir */
74     struct ptyfs_file ptmx_file;  /* `/ptmx` file */
75     struct rid_bitmap ptsno_pool; /* pts number pool */
76     rt_uint32_t magic;            /* PTYFS_MAGIC */
77     rt_size_t df_size;            /* df size */
78     rt_list_t sibling;            /* sb sibling list */
79     struct dfs_mnt *mount;        /* mount data */
80 
81     /**
82      * Note: This upper limit is set to protect kernel memory from draining
83      * out by the application if it keeps allocating pty devices.
84      *
85      * Still, current implementation of bitmap can not efficiently use the
86      * memory
87      */
88     rt_bitmap_t
89         ptsno_pool_bitset[LWP_PTY_MAX_PARIS_LIMIT / (sizeof(rt_bitmap_t) * 8)];
90 };
91 
92 static struct dfs_file_ops _default_fops;
93 
_split_out_subdir(const char * path,char * name)94 static int _split_out_subdir(const char *path, char *name)
95 {
96     const char *subpath = path;
97 
98     while (*subpath == '/' && *subpath)
99     {
100         subpath++;
101     }
102 
103     while (*subpath != '/' && *subpath)
104     {
105         *name++ = *subpath++;
106     }
107 
108     *name = '\0';
109     return 0;
110 }
111 
ptyfile_init(struct ptyfs_file * file,struct ptyfs_sb * sb,const char * name,rt_uint32_t type,rt_uint32_t mode,rt_device_t device)112 static rt_err_t ptyfile_init(struct ptyfs_file *file, struct ptyfs_sb *sb,
113                              const char *name, rt_uint32_t type,
114                              rt_uint32_t mode, rt_device_t device)
115 {
116     if (name)
117         strncpy(file->basename, name, sizeof(file->basename));
118 
119     file->type = type;
120     file->mode = mode;
121     rt_list_init(&file->subdirs);
122     rt_list_init(&file->ent_node);
123     file->sb = sb;
124     file->device = device;
125 
126     return 0;
127 }
128 
ptyfile_add_to_root(struct ptyfs_sb * sb,struct ptyfs_file * new_file)129 static rt_err_t ptyfile_add_to_root(struct ptyfs_sb *sb,
130                                     struct ptyfs_file *new_file)
131 {
132     struct ptyfs_file *root_file = &sb->root_file;
133 
134     /* update super block */
135     sb->df_size += sizeof(struct ptyfs_file);
136 
137     rt_mutex_take(&sb->lock, RT_WAITING_FOREVER);
138     rt_list_insert_after(&(root_file->subdirs), &(new_file->ent_node));
139     rt_mutex_release(&sb->lock);
140 
141     return 0;
142 }
143 
ptyfile_remove_from_root(struct ptyfs_sb * sb,struct ptyfs_file * rm_file)144 static rt_err_t ptyfile_remove_from_root(struct ptyfs_sb *sb,
145                                          struct ptyfs_file *rm_file)
146 {
147     /* update super block */
148     sb->df_size -= sizeof(struct ptyfs_file);
149 
150     rt_mutex_take(&sb->lock, RT_WAITING_FOREVER);
151     rt_list_remove(&(rm_file->ent_node));
152     rt_mutex_release(&sb->lock);
153 
154     return 0;
155 }
156 
ptyfile_lookup(struct ptyfs_sb * superblock,const char * path)157 static struct ptyfs_file *ptyfile_lookup(struct ptyfs_sb *superblock,
158                                          const char *path)
159 {
160     const char *subpath_iter, *curpath_iter, *basename = RT_NULL;
161     char subdir_name[DIRENT_NAME_MAX];
162     struct ptyfs_file *curfile, *found_file = RT_NULL;
163     rt_list_t *list;
164     int do_path_resolve = 1;
165 
166     subpath_iter = path;
167 
168     /* skip starting "/" */
169     while (*subpath_iter == '/') subpath_iter++;
170     if (!*subpath_iter)
171     {
172         return &(superblock->root_file);
173     }
174 
175     curpath_iter = subpath_iter;
176     curfile = &superblock->root_file;
177 
178     /* resolve chain of files splited from path one by one */
179     while (do_path_resolve)
180     {
181         do_path_resolve = 0;
182 
183         /* splitout sub-directory or basename */
184         while (*subpath_iter != '/' && *subpath_iter) subpath_iter++;
185         if (!*subpath_iter)
186         {
187             basename = curpath_iter;
188         }
189         else
190         {
191             _split_out_subdir(curpath_iter, subdir_name);
192 
193             /* skip "/" for next search */
194             subpath_iter++;
195         }
196 
197         rt_mutex_take(&superblock->lock, RT_WAITING_FOREVER);
198         rt_list_for_each(list, &curfile->subdirs)
199         {
200             struct ptyfs_file *file_iter;
201             file_iter = rt_list_entry(list, struct ptyfs_file, ent_node);
202             if (basename)
203             {
204                 if (strcmp(file_iter->basename, basename) == 0)
205                 {
206                     found_file = file_iter;
207                     break;
208                 }
209             }
210             else if (strcmp(file_iter->basename, subdir_name) == 0)
211             {
212                 curpath_iter = subpath_iter;
213                 curfile = file_iter;
214                 do_path_resolve = 1;
215                 break;
216             }
217         }
218         rt_mutex_release(&superblock->lock);
219     }
220 
221     return found_file;
222 }
223 
ptyfs_get_rootpath(rt_device_t ptmx)224 const char *ptyfs_get_rootpath(rt_device_t ptmx)
225 {
226     const char *rc;
227     struct ptyfs_sb *sb;
228     /* allocate id for it and register file */
229     sb = rt_container_of(ptmx, struct ptyfs_sb, ptmx_device);
230     if (sb->magic != PTYFS_MAGIC)
231     {
232         rc = 0;
233     }
234     else
235     {
236         /* fullpath is always started with /dev/ */
237         return sb->mount->fullpath + 5;
238     }
239 
240     return rc;
241 }
242 
ptyfs_register_pts(rt_device_t ptmx,rt_device_t pts)243 ptsno_t ptyfs_register_pts(rt_device_t ptmx, rt_device_t pts)
244 {
245     ptsno_t rc;
246     struct ptyfs_sb *sb;
247     struct ptyfs_file *pts_file;
248     struct rid_bitmap *ptsno_pool;
249 
250     /* allocate id for it and register file */
251     sb = rt_container_of(ptmx, struct ptyfs_sb, ptmx_device);
252     if (sb->magic != PTYFS_MAGIC)
253     {
254         rc = -1;
255     }
256     else
257     {
258         ptsno_pool = &sb->ptsno_pool;
259         rc = rid_bitmap_get(ptsno_pool);
260         if (rc >= 0)
261         {
262             pts_file = rt_calloc(1, sizeof(struct ptyfs_file));
263             if (pts_file)
264             {
265                 snprintf(pts_file->basename, DIRENT_NAME_MAX, "%lu", (unsigned long)rc);
266                 ptyfile_init(pts_file, sb, 0, PTYFS_TYPE_FILE_SLAVE,
267                              PTS_DEFAULT_FILE_MODE, pts);
268                 ptyfile_add_to_root(sb, pts_file);
269             }
270             else
271             {
272                 rid_bitmap_put(ptsno_pool, rc);
273                 rc = -1;
274             }
275         }
276         /* else rc == -1 */
277     }
278 
279     return rc;
280 }
281 
ptyfs_unregister_pts(rt_device_t ptmx,ptsno_t ptsno)282 rt_err_t ptyfs_unregister_pts(rt_device_t ptmx, ptsno_t ptsno)
283 {
284     ptsno_t rc;
285     struct ptyfs_sb *sb;
286     struct ptyfs_file *pts_file;
287     struct rid_bitmap *ptsno_pool;
288     char path_buf[DIRENT_NAME_MAX];
289 
290     /* allocate id for it and register file */
291     sb = rt_container_of(ptmx, struct ptyfs_sb, ptmx_device);
292     if (sb->magic != PTYFS_MAGIC || ptsno < 0)
293     {
294         rc = -EINVAL;
295     }
296     else
297     {
298         /* get path and findout device */
299         snprintf(path_buf, sizeof(path_buf), "%lu", (unsigned long)ptsno);
300         pts_file = ptyfile_lookup(sb, path_buf);
301         if (pts_file)
302         {
303             ptyfile_remove_from_root(sb, pts_file);
304             ptsno_pool = &sb->ptsno_pool;
305             rid_bitmap_put(ptsno_pool, ptsno);
306             rc = 0;
307         }
308         else
309         {
310             rc = -ENOENT;
311         }
312     }
313 
314     return rc;
315 }
316 
317 #define DEVFS_PREFIX     "/dev/"
318 #define DEVFS_PREFIX_LEN (sizeof(DEVFS_PREFIX) - 1)
319 
320 /**
321  * Create an new instance of ptyfs, and mount on target point
322  * 2 basic files are created: root, ptmx.
323  *
324  * todo: support of mount options?
325  */
ptyfs_ops_mount(struct dfs_mnt * mnt,unsigned long rwflag,const void * data)326 static int ptyfs_ops_mount(struct dfs_mnt *mnt, unsigned long rwflag,
327                            const void *data)
328 {
329     struct ptyfs_sb *sb;
330     rt_device_t ptmx_device;
331     rt_err_t rc;
332 
333     if (strncmp(mnt->fullpath, DEVFS_PREFIX, DEVFS_PREFIX_LEN) != 0)
334     {
335         LOG_I("%s() Not mounted on `/dev/'", __func__);
336         return -EINVAL;
337     }
338 
339     sb = rt_calloc(1, sizeof(struct ptyfs_sb));
340     if (sb)
341     {
342         rt_mutex_init(&sb->lock, "ptyfs", RT_IPC_FLAG_PRIO);
343 
344         /* setup the ptmx device */
345         ptmx_device = &sb->ptmx_device;
346         rc = lwp_ptmx_init(ptmx_device, mnt->fullpath + DEVFS_PREFIX_LEN);
347         if (rc == RT_EOK)
348         {
349             /* setup 2 basic files */
350             ptyfile_init(&sb->root_file, sb, "/", PTYFS_TYPE_DIR,
351                          ROOT_DEFUALT_FILE_MODE, 0);
352             ptyfile_init(&sb->ptmx_file, sb, "ptmx", PTYFS_TYPE_FILE_PTMX,
353                          PTMX_DEFAULT_FILE_MODE, ptmx_device);
354             ptyfile_add_to_root(sb, &sb->ptmx_file);
355 
356             /* setup rid */
357             rid_bitmap_init(&sb->ptsno_pool, 0, LWP_PTY_MAX_PARIS_LIMIT,
358                             sb->ptsno_pool_bitset, &sb->lock);
359 
360             /* setup properties and members */
361             sb->magic = PTYFS_MAGIC;
362 
363             sb->df_size = sizeof(struct ptyfs_sb);
364             rt_list_init(&sb->sibling);
365 
366             /* binding superblocks and mount point */
367             mnt->data = sb;
368             sb->mount = mnt;
369             rc = 0;
370         }
371         /* else just return rc */
372     }
373     else
374     {
375         rc = -ENOMEM;
376     }
377 
378     return rc;
379 }
380 
ptyfs_ops_umount(struct dfs_mnt * mnt)381 static int ptyfs_ops_umount(struct dfs_mnt *mnt)
382 {
383     /* Not supported yet */
384     return -1;
385 }
386 
ptyfs_ops_setattr(struct dfs_dentry * dentry,struct dfs_attr * attr)387 static int ptyfs_ops_setattr(struct dfs_dentry *dentry, struct dfs_attr *attr)
388 {
389     struct ptyfs_file *pty_file;
390     struct ptyfs_sb *superblock;
391 
392     RT_ASSERT(dentry);
393     RT_ASSERT(dentry->mnt);
394 
395     superblock = (struct ptyfs_sb *)dentry->mnt->data;
396     RT_ASSERT(superblock);
397 
398     /* find the device related to current pts slave device */
399     pty_file = ptyfile_lookup(superblock, dentry->pathname);
400     if (pty_file && pty_file->type == PTYFS_TYPE_FILE_SLAVE)
401     {
402         pty_file->mode &= ~0xFFF;
403         pty_file->mode |= attr->st_mode & 0xFFF;
404         return 0;
405     }
406 
407     return -1;
408 }
409 
410 #define OPTIMAL_BSIZE 1024
411 
ptyfs_ops_statfs(struct dfs_mnt * mnt,struct statfs * buf)412 static int ptyfs_ops_statfs(struct dfs_mnt *mnt, struct statfs *buf)
413 {
414     struct ptyfs_sb *superblock;
415 
416     RT_ASSERT(mnt != NULL);
417     RT_ASSERT(buf != NULL);
418 
419     superblock = (struct ptyfs_sb *)mnt->data;
420     RT_ASSERT(superblock != NULL);
421 
422     buf->f_bsize = OPTIMAL_BSIZE;
423     buf->f_blocks = (superblock->df_size + OPTIMAL_BSIZE - 1) / OPTIMAL_BSIZE;
424     buf->f_bfree = 1;
425     buf->f_bavail = buf->f_bfree;
426 
427     return RT_EOK;
428 }
429 
ptyfs_ops_stat(struct dfs_dentry * dentry,struct stat * st)430 static int ptyfs_ops_stat(struct dfs_dentry *dentry, struct stat *st)
431 {
432     struct dfs_vnode *vnode;
433 
434     if (dentry && dentry->vnode)
435     {
436         vnode = dentry->vnode;
437 
438         /* device id ? */
439         st->st_dev = (dev_t)(long)(dentry->mnt->dev_id);
440         st->st_ino = (ino_t)dfs_dentry_full_path_crc32(dentry);
441 
442         st->st_gid = vnode->gid;
443         st->st_uid = vnode->uid;
444         st->st_mode = vnode->mode;
445         st->st_nlink = vnode->nlink;
446         st->st_size = vnode->size;
447         st->st_mtim.tv_nsec = vnode->mtime.tv_nsec;
448         st->st_mtim.tv_sec = vnode->mtime.tv_sec;
449         st->st_ctim.tv_nsec = vnode->ctime.tv_nsec;
450         st->st_ctim.tv_sec = vnode->ctime.tv_sec;
451         st->st_atim.tv_nsec = vnode->atime.tv_nsec;
452         st->st_atim.tv_sec = vnode->atime.tv_sec;
453     }
454 
455     return 0;
456 }
457 
ptyfs_ops_lookup(struct dfs_dentry * dentry)458 static struct dfs_vnode *ptyfs_ops_lookup(struct dfs_dentry *dentry)
459 {
460     struct dfs_vnode *vnode = RT_NULL;
461     struct ptyfs_sb *superblock;
462     struct ptyfs_file *pty_file;
463 
464     if (dentry == NULL || dentry->mnt == NULL || dentry->mnt->data == NULL)
465     {
466         return NULL;
467     }
468 
469     superblock = (struct ptyfs_sb *)dentry->mnt->data;
470 
471     pty_file = ptyfile_lookup(superblock, dentry->pathname);
472     if (pty_file)
473     {
474         vnode = dfs_vnode_create();
475         if (vnode)
476         {
477             vnode->data = pty_file->device;
478             vnode->nlink = 1;
479             vnode->size = 0;
480             vnode->mnt = dentry->mnt;
481             /* if it's root directory */
482             vnode->fops = &_default_fops;
483             vnode->mode = pty_file->mode;
484             vnode->type = pty_file->type == PTYFS_TYPE_DIR ? FT_DIRECTORY : FT_DEVICE;
485         }
486     }
487 
488     return vnode;
489 }
490 
ptyfs_ops_create_vnode(struct dfs_dentry * dentry,int type,mode_t mode)491 static struct dfs_vnode *ptyfs_ops_create_vnode(struct dfs_dentry *dentry,
492                                                 int type, mode_t mode)
493 {
494     struct dfs_vnode *vnode = RT_NULL;
495     struct ptyfs_sb *sb;
496     struct ptyfs_file *pty_file;
497     char *vnode_path;
498 
499     if (dentry == NULL || dentry->mnt == NULL || dentry->mnt->data == NULL)
500     {
501         return NULL;
502     }
503 
504     sb = (struct ptyfs_sb *)dentry->mnt->data;
505     RT_ASSERT(sb != NULL);
506 
507     vnode = dfs_vnode_create();
508     if (vnode)
509     {
510         vnode_path = dentry->pathname;
511 
512         /* Query if file existed. Filter out illegal open modes */
513         pty_file = ptyfile_lookup(sb, vnode_path);
514         if (!pty_file || (~pty_file->mode & mode))
515         {
516             dfs_vnode_destroy(vnode);
517             return NULL;
518         }
519 
520         vnode->data = pty_file->device;
521         vnode->nlink = 1;
522         vnode->size = 0;
523         vnode->mnt = dentry->mnt;
524         vnode->fops = pty_file->device ? pty_file->device->fops : RT_NULL;
525         vnode->mode &= pty_file->mode;
526 
527         if (type == FT_DIRECTORY)
528         {
529             vnode->mode |= S_IFDIR;
530             vnode->type = FT_DIRECTORY;
531             LOG_I("%s: S_IFDIR created", __func__);
532         }
533         else if (type == FT_REGULAR)
534         {
535             vnode->mode |= S_IFCHR;
536             vnode->type = FT_DEVICE;
537             LOG_I("%s: S_IFDIR created", __func__);
538         }
539         else
540         {
541             /* unsupported types */
542             dfs_vnode_destroy(vnode);
543             return NULL;
544         }
545     }
546 
547     return vnode;
548 }
549 
ptyfs_ops_free_vnode(struct dfs_vnode * vnode)550 static int ptyfs_ops_free_vnode(struct dfs_vnode *vnode)
551 {
552     return RT_EOK;
553 }
554 
devpty_deffops_getdents(struct dfs_file * file,struct dirent * dirp,uint32_t count)555 static int devpty_deffops_getdents(struct dfs_file *file, struct dirent *dirp,
556                                    uint32_t count)
557 {
558     struct ptyfs_file *d_file;
559     struct ptyfs_sb *superblock;
560 
561     RT_ASSERT(file);
562     RT_ASSERT(file->dentry);
563     RT_ASSERT(file->dentry->mnt);
564 
565     superblock = (struct ptyfs_sb *)file->dentry->mnt->data;
566     RT_ASSERT(superblock);
567 
568     d_file = ptyfile_lookup(superblock, file->dentry->pathname);
569     if (d_file)
570     {
571         rt_size_t index, end;
572         struct dirent *d;
573         struct ptyfs_file *n_file;
574         rt_list_t *list;
575 
576         /* make integer count */
577         count = (count / sizeof(struct dirent));
578         if (count == 0)
579         {
580             return -EINVAL;
581         }
582 
583         end = file->fpos + count;
584         index = 0;
585         count = 0;
586 
587         rt_list_for_each(list, &d_file->subdirs)
588         {
589             if (index >= (rt_size_t)file->fpos)
590             {
591                 n_file = rt_list_entry(list, struct ptyfs_file, ent_node);
592 
593                 d = dirp + count;
594                 if (n_file->type == PTYFS_TYPE_DIR)
595                 {
596                     d->d_type = DT_DIR;
597                 }
598                 else
599                 {
600                     /* ptmx(5,2) or slave(136,[0,1048575]) device, on Linux */
601                     d->d_type = DT_CHR;
602                 }
603 
604                 d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
605                 rt_strncpy(d->d_name, n_file->basename, DIRENT_NAME_MAX);
606                 d->d_namlen = rt_strlen(d->d_name);
607 
608                 count += 1;
609                 file->fpos += 1;
610             }
611             index += 1;
612             if (index >= end)
613             {
614                 break;
615             }
616         }
617     }
618 
619     return count * sizeof(struct dirent);
620 }
621 
622 static const struct dfs_filesystem_ops _ptyfs_ops = {
623     .name = "ptyfs",
624     .flags = DFS_FS_FLAG_DEFAULT,
625     .default_fops = &_default_fops,
626 
627     .mount = ptyfs_ops_mount,
628     .umount = ptyfs_ops_umount,
629 
630     /* don't allow to create symbolic link */
631     .symlink = RT_NULL,
632     .readlink = RT_NULL,
633     .unlink = RT_NULL,
634 
635     .setattr = ptyfs_ops_setattr,
636     .statfs = ptyfs_ops_statfs,
637     .stat = ptyfs_ops_stat,
638 
639     .lookup = ptyfs_ops_lookup,
640     .create_vnode = ptyfs_ops_create_vnode,
641     .free_vnode = ptyfs_ops_free_vnode,
642 };
643 
644 static struct dfs_filesystem_type _devptyfs = {
645     .fs_ops = &_ptyfs_ops,
646 };
647 
_ptyfs_init(void)648 static int _ptyfs_init(void)
649 {
650     _default_fops = *dfs_devfs_fops();
651     _default_fops.getdents = devpty_deffops_getdents;
652 
653     /* register file system */
654     dfs_register(&_devptyfs);
655 
656     return 0;
657 }
658 INIT_COMPONENT_EXPORT(_ptyfs_init);
659