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