1 /*
2  * Copyright (c) 2006-2018, RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2022-10-24     flybreak     the first version
9  * 2023-02-01     xqyjlj       fix cannot open the same file repeatedly in 'w' mode
10  */
11 
12 #include <rthw.h>
13 #include <rtthread.h>
14 #include <dfs.h>
15 #include <dfs_fs.h>
16 #include <dfs_file.h>
17 
18 #ifdef RT_USING_SMART
19 #include <lwp.h>
20 #include <lwp_user_mm.h>
21 #endif
22 
23 #include "dfs_tmpfs.h"
24 
25 #define DBG_TAG              "tmpfs"
26 #define DBG_LVL              DBG_INFO
27 #include <rtdbg.h>
28 
_path_separate(const char * path,char * parent_path,char * file_name)29 static int _path_separate(const char *path, char *parent_path, char *file_name)
30 {
31     const char *path_p, *path_q;
32 
33     RT_ASSERT(path[0] == '/');
34 
35     file_name[0] = '\0';
36     path_p = path_q = &path[1];
37 __next_dir:
38     while (*path_q != '/' && *path_q != '\0')
39     {
40         path_q++;
41     }
42     if (path_q != path_p) /*sub dir*/
43     {
44         if (*path_q != '\0')
45         {
46             path_q++;
47             path_p = path_q;
48             goto __next_dir;
49         }
50         else /* Last level dir */
51         {
52             rt_memcpy(parent_path, path, path_p - path - 1);
53             parent_path[path_p - path - 1] = '\0';
54             rt_memcpy(file_name, path_p, path_q - path_p);
55             file_name[path_q - path_p] = '\0';
56         }
57     }
58     if (parent_path[0] == 0)
59     {
60         parent_path[0] = '/';
61         parent_path[1] = '\0';
62     }
63     LOG_D("parent_path: %s", parent_path);
64     LOG_D("file_name: %s", file_name);
65 
66     return 0;
67 }
68 
_get_subdir(const char * path,char * name)69 static int _get_subdir(const char *path, char *name)
70 {
71     const char *subpath = path;
72     while (*subpath == '/' && *subpath)
73         subpath ++;
74     while (*subpath != '/' && *subpath)
75     {
76         *name = *subpath;
77         name ++;
78         subpath ++;
79     }
80     return 0;
81 }
82 
_free_subdir(struct tmpfs_file * dfile)83 static int _free_subdir(struct tmpfs_file *dfile)
84 {
85     struct tmpfs_file *file;
86     rt_list_t *list, *temp_list;
87     struct tmpfs_sb *superblock;
88 
89     RT_ASSERT(dfile->type == TMPFS_TYPE_DIR);
90 
91     rt_list_for_each_safe(list, temp_list, &dfile->subdirs)
92     {
93         file = rt_list_entry(list, struct tmpfs_file, sibling);
94         if (file->type == TMPFS_TYPE_DIR)
95         {
96             _free_subdir(file);
97         }
98         if (file->data != NULL)
99         {
100             /* TODO: fix for rt-smart */
101             rt_free(file->data);
102         }
103 
104         superblock = file->sb;
105         RT_ASSERT(superblock != NULL);
106 
107         rt_spin_lock(&superblock->lock);
108         rt_list_remove(&(file->sibling));
109         rt_spin_unlock(&superblock->lock);
110 
111         rt_free(file);
112     }
113     return 0;
114 }
115 
dfs_tmpfs_mount(struct dfs_filesystem * fs,unsigned long rwflag,const void * data)116 int dfs_tmpfs_mount(struct dfs_filesystem *fs,
117                     unsigned long          rwflag,
118                     const void            *data)
119 {
120     struct tmpfs_sb *superblock;
121 
122     superblock = rt_calloc(1, sizeof(struct tmpfs_sb));
123     if (superblock)
124     {
125         superblock->df_size = sizeof(struct tmpfs_sb);
126         superblock->magic = TMPFS_MAGIC;
127         rt_list_init(&superblock->sibling);
128 
129         superblock->root.name[0] = '/';
130         superblock->root.sb = superblock;
131         superblock->root.type = TMPFS_TYPE_DIR;
132         rt_list_init(&superblock->root.sibling);
133         rt_list_init(&superblock->root.subdirs);
134 
135         rt_spin_lock_init(&superblock->lock);
136 
137         fs->data = superblock;
138     }
139     else
140     {
141         return -1;
142     }
143 
144     return RT_EOK;
145 }
146 
dfs_tmpfs_unmount(struct dfs_filesystem * fs)147 int dfs_tmpfs_unmount(struct dfs_filesystem *fs)
148 {
149     struct tmpfs_sb *superblock;
150 
151     superblock = (struct tmpfs_sb *)fs->data;
152     RT_ASSERT(superblock != NULL);
153 
154     _free_subdir(&(superblock->root));
155     rt_free(superblock);
156 
157     fs->data = NULL;
158 
159     return RT_EOK;
160 }
161 
dfs_tmpfs_statfs(struct dfs_filesystem * fs,struct statfs * buf)162 int dfs_tmpfs_statfs(struct dfs_filesystem *fs, struct statfs *buf)
163 {
164     struct tmpfs_sb *superblock;
165 
166     superblock = (struct tmpfs_sb *)fs->data;
167     RT_ASSERT(superblock != NULL);
168     RT_ASSERT(buf != NULL);
169 
170     buf->f_bsize  = 512;
171     buf->f_blocks = (superblock->df_size + 511) / 512;
172     buf->f_bfree  = 1;
173     buf->f_bavail = buf->f_bfree;
174 
175     return RT_EOK;
176 }
177 
dfs_tmpfs_ioctl(struct dfs_file * file,int cmd,void * args)178 int dfs_tmpfs_ioctl(struct dfs_file *file, int cmd, void *args)
179 {
180     struct tmpfs_file *d_file;
181     struct tmpfs_sb *superblock;
182 
183     d_file = (struct tmpfs_file *)file->vnode->data;
184     RT_ASSERT(d_file != NULL);
185 
186     superblock = d_file->sb;
187     RT_ASSERT(superblock != NULL);
188 
189     switch (cmd)
190     {
191 #ifdef RT_USING_SMART
192     case RT_FIOMMAP2:
193     {
194         struct dfs_mmap2_args *mmap2 = (struct dfs_mmap2_args *)args;
195         if (mmap2)
196         {
197             if (mmap2->length > file->vnode->size)
198             {
199                 return -RT_ENOMEM;
200             }
201             else if (mmap2->lwp == RT_NULL)
202                 return -RT_EINVAL;
203 
204             LOG_D("tmpfile mmap ptr:%x , size:%d\n", d_file->data, mmap2->length);
205             mmap2->ret = lwp_map_user_phy(mmap2->lwp, mmap2->addr, d_file->data, mmap2->length, 0);
206         }
207         return RT_EOK;
208         break;
209     }
210 #endif
211     default:
212         break;
213     }
214     return -EIO;
215 }
216 
dfs_tmpfs_lookup(struct tmpfs_sb * superblock,const char * path,rt_size_t * size)217 struct tmpfs_file *dfs_tmpfs_lookup(struct tmpfs_sb  *superblock,
218                                       const char       *path,
219                                       rt_size_t        *size)
220 {
221     const char *subpath, *curpath, *filename = RT_NULL;
222     char subdir_name[TMPFS_NAME_MAX];
223     struct tmpfs_file *file, *curfile;
224     rt_list_t *list;
225 
226     subpath = path;
227     while (*subpath == '/' && *subpath)
228         subpath ++;
229     if (! *subpath) /* is root directory */
230     {
231         *size = 0;
232         return &(superblock->root);
233     }
234 
235     curpath = subpath;
236     curfile = &superblock->root;
237 
238 find_subpath:
239     while (*subpath != '/' && *subpath)
240         subpath ++;
241 
242     if (! *subpath) /* is last directory */
243         filename = curpath;
244     else
245         subpath ++; /* skip '/' */
246 
247     memset(subdir_name, 0, TMPFS_NAME_MAX);
248     _get_subdir(curpath, subdir_name);
249 
250     rt_spin_lock(&superblock->lock);
251 
252     rt_list_for_each(list, &curfile->subdirs)
253     {
254         file = rt_list_entry(list, struct tmpfs_file, sibling);
255         if (filename) /* find file */
256         {
257             if (rt_strcmp(file->name, filename) == 0)
258             {
259                 *size = file->size;
260 
261                 rt_spin_unlock(&superblock->lock);
262                 return file;
263             }
264         }
265         else if (rt_strcmp(file->name, subdir_name) == 0)
266         {
267             *size = file->size;
268             curpath = subpath;
269             curfile = file;
270             rt_spin_unlock(&superblock->lock);
271             goto find_subpath;
272         }
273     }
274     rt_spin_unlock(&superblock->lock);
275     /* not found */
276     return NULL;
277 }
278 
dfs_tmpfs_read(struct dfs_file * file,void * buf,size_t count)279 ssize_t dfs_tmpfs_read(struct dfs_file *file, void *buf, size_t count)
280 {
281     rt_size_t length;
282     struct tmpfs_file *d_file;
283 
284     d_file = (struct tmpfs_file *)file->vnode->data;
285     RT_ASSERT(d_file != NULL);
286 
287     if (count < file->vnode->size - file->pos)
288         length = count;
289     else
290         length = file->vnode->size - file->pos;
291 
292     if (length > 0)
293         memcpy(buf, &(d_file->data[file->pos]), length);
294 
295     /* update file current position */
296     file->pos += length;
297 
298     return length;
299 }
300 
301 
dfs_tmpfs_write(struct dfs_file * fd,const void * buf,size_t count)302 ssize_t dfs_tmpfs_write(struct dfs_file *fd, const void *buf, size_t count)
303 {
304     struct tmpfs_file *d_file;
305     struct tmpfs_sb *superblock;
306 
307     d_file = (struct tmpfs_file *)fd->vnode->data;
308     RT_ASSERT(d_file != NULL);
309 
310     superblock = d_file->sb;
311     RT_ASSERT(superblock != NULL);
312 
313     if (count + fd->pos > fd->vnode->size)
314     {
315         rt_uint8_t *ptr;
316         ptr = rt_realloc(d_file->data, fd->pos + count);
317         if (ptr == NULL)
318         {
319             rt_set_errno(-ENOMEM);
320             return 0;
321         }
322 
323         superblock->df_size += (fd->pos - d_file->size + count);
324         /* update d_file and file size */
325         d_file->data = ptr;
326         d_file->size = fd->pos + count;
327         fd->vnode->size = d_file->size;
328         LOG_D("tmpfile ptr:%x, size:%d", ptr, d_file->size);
329     }
330 
331     if (count > 0)
332         memcpy(d_file->data + fd->pos, buf, count);
333 
334     /* update file current position */
335     fd->pos += count;
336 
337     return count;
338 }
339 
dfs_tmpfs_lseek(struct dfs_file * file,off_t offset)340 off_t dfs_tmpfs_lseek(struct dfs_file *file, off_t offset)
341 {
342     if (offset <= (off_t)file->vnode->size)
343     {
344         file->pos = offset;
345 
346         return file->pos;
347     }
348 
349     return -EIO;
350 }
351 
dfs_tmpfs_close(struct dfs_file * file)352 int dfs_tmpfs_close(struct dfs_file *file)
353 {
354     RT_ASSERT(file->vnode->ref_count > 0);
355     if (file->vnode->ref_count > 1)
356     {
357         return 0;
358     }
359 
360     file->vnode->data = NULL;
361 
362     return RT_EOK;
363 }
364 
dfs_tmpfs_open(struct dfs_file * file)365 int dfs_tmpfs_open(struct dfs_file *file)
366 {
367     rt_size_t size;
368     struct tmpfs_sb *superblock;
369     struct tmpfs_file *d_file, *p_file;
370     struct dfs_filesystem *fs;
371     char parent_path[DFS_PATH_MAX],file_name[TMPFS_NAME_MAX];
372 
373     RT_ASSERT(file->vnode->ref_count > 0);
374     if (file->vnode->ref_count > 1)
375     {
376         if (file->vnode->type == FT_DIRECTORY
377                 && !(file->flags & O_DIRECTORY))
378         {
379             return -ENOENT;
380         }
381         file->pos = 0;
382         return 0;
383     }
384 
385     fs = file->vnode->fs;
386 
387     superblock = (struct tmpfs_sb *)fs->data;
388     RT_ASSERT(superblock != NULL);
389 
390     /* find file */
391     d_file = dfs_tmpfs_lookup(superblock, file->vnode->path, &size);
392     if (d_file == NULL && !(file->flags & O_CREAT))
393         return -ENOENT;
394 
395     /* Creates a new file. */
396     if (file->flags & O_CREAT)
397     {
398         if (d_file == NULL)
399         {
400             /* find parent file */
401             _path_separate(file->vnode->path, parent_path, file_name);
402             if (file_name[0] == '\0') /* it's root dir */
403                 return -ENOENT;
404 
405             /* open parent directory */
406             p_file = dfs_tmpfs_lookup(superblock, parent_path, &size);
407             if (p_file == NULL)
408                 return -ENOENT;
409 
410             /* create a file entry */
411             d_file = (struct tmpfs_file *)rt_calloc(1, sizeof(struct tmpfs_file));
412             if (d_file == NULL)
413             {
414                 return -ENOMEM;
415             }
416             superblock->df_size += sizeof(struct tmpfs_file);
417 
418             strncpy(d_file->name, file_name, TMPFS_NAME_MAX);
419 
420             rt_list_init(&(d_file->subdirs));
421             rt_list_init(&(d_file->sibling));
422             d_file->data = NULL;
423             d_file->size = 0;
424             d_file->sb   = superblock;
425             if (file->flags & O_DIRECTORY)
426             {
427                 d_file->type = TMPFS_TYPE_DIR;
428             }
429             else
430             {
431                 d_file->type = TMPFS_TYPE_FILE;
432             }
433             rt_spin_lock(&superblock->lock);
434             rt_list_insert_after(&(p_file->subdirs), &(d_file->sibling));
435             rt_spin_unlock(&superblock->lock);
436         }
437     }
438     /* Creates a new file.
439         * If the file is existing, it is truncated and overwritten.
440         */
441     if (file->flags & O_TRUNC)
442     {
443         d_file->size = 0;
444         if (d_file->data != NULL)
445         {
446             /* ToDo: fix for rt-smart. */
447             rt_free(d_file->data);
448             d_file->data = NULL;
449         }
450     }
451 
452     /* fill file */
453     if (d_file->type == TMPFS_TYPE_DIR)
454     {
455         if (file->flags & O_DIRECTORY)
456             file->vnode->type = FT_DIRECTORY;
457         else
458             return -ENOMEM;
459     }
460     else
461     {
462         if (file->flags & O_DIRECTORY)
463         {
464             return -ENOMEM;
465         }
466         file->vnode->type = FT_DEVICE;
467     }
468 
469     file->vnode->data = d_file;
470     file->vnode->size = d_file->size;
471     if (file->flags & O_APPEND)
472     {
473         file->pos = file->vnode->size;
474     }
475     else
476     {
477         file->pos = 0;
478     }
479 
480     return 0;
481 }
482 
dfs_tmpfs_stat(struct dfs_filesystem * fs,const char * path,struct stat * st)483 int dfs_tmpfs_stat(struct dfs_filesystem *fs,
484                    const char            *path,
485                    struct stat           *st)
486 {
487     rt_size_t size;
488     struct tmpfs_file *d_file;
489     struct tmpfs_sb *superblock;
490 
491     superblock = (struct tmpfs_sb *)fs->data;
492     d_file = dfs_tmpfs_lookup(superblock, path, &size);
493 
494     if (d_file == NULL)
495         return -ENOENT;
496 
497     st->st_dev = (dev_t)dfs_filesystem_lookup(fs->path);
498     st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH |
499                   S_IWUSR | S_IWGRP | S_IWOTH;
500     if (d_file->type == TMPFS_TYPE_DIR)
501     {
502         st->st_mode &= ~S_IFREG;
503         st->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
504     }
505 
506     st->st_size = d_file->size;
507     st->st_mtime = 0;
508 
509     return RT_EOK;
510 }
511 
dfs_tmpfs_getdents(struct dfs_file * file,struct dirent * dirp,uint32_t count)512 int dfs_tmpfs_getdents(struct dfs_file *file,
513                        struct dirent *dirp,
514                        uint32_t    count)
515 {
516     rt_size_t index, end;
517     struct dirent *d;
518     struct tmpfs_file *d_file, *n_file;
519     rt_list_t *list;
520     struct tmpfs_sb *superblock;
521 
522     d_file = (struct tmpfs_file *)file->vnode->data;
523 
524     superblock  = d_file->sb;
525     RT_ASSERT(superblock != RT_NULL);
526 
527     /* make integer count */
528     count = (count / sizeof(struct dirent));
529     if (count == 0)
530         return -EINVAL;
531 
532     end = file->pos + count;
533     index = 0;
534     count = 0;
535 
536     rt_list_for_each(list, &d_file->subdirs)
537     {
538         n_file = rt_list_entry(list, struct tmpfs_file, sibling);
539         if (index >= (rt_size_t)file->pos)
540         {
541             d = dirp + count;
542             if (d_file->type == TMPFS_TYPE_FILE)
543             {
544                 d->d_type = DT_REG;
545             }
546             if (d_file->type == TMPFS_TYPE_DIR)
547             {
548                 d->d_type = DT_DIR;
549             }
550             d->d_namlen = RT_NAME_MAX;
551             d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
552             rt_strncpy(d->d_name, n_file->name, TMPFS_NAME_MAX);
553 
554             count += 1;
555             file->pos += 1;
556         }
557         index += 1;
558         if (index >= end)
559         {
560             break;
561         }
562     }
563 
564     return count * sizeof(struct dirent);
565 }
566 
dfs_tmpfs_unlink(struct dfs_filesystem * fs,const char * path)567 int dfs_tmpfs_unlink(struct dfs_filesystem *fs, const char *path)
568 {
569     rt_size_t size;
570     struct tmpfs_sb *superblock;
571     struct tmpfs_file *d_file;
572 
573     superblock = (struct tmpfs_sb *)fs->data;
574     RT_ASSERT(superblock != NULL);
575 
576     d_file = dfs_tmpfs_lookup(superblock, path, &size);
577     if (d_file == NULL)
578         return -ENOENT;
579 
580     rt_spin_lock(&superblock->lock);
581     rt_list_remove(&(d_file->sibling));
582     rt_spin_unlock(&superblock->lock);
583 
584     if (d_file->data != NULL)
585         rt_free(d_file->data);
586     rt_free(d_file);
587 
588     return RT_EOK;
589 }
590 
dfs_tmpfs_rename(struct dfs_filesystem * fs,const char * oldpath,const char * newpath)591 int dfs_tmpfs_rename(struct dfs_filesystem *fs,
592                      const char            *oldpath,
593                      const char            *newpath)
594 {
595     struct tmpfs_file *d_file, *p_file;
596     struct tmpfs_sb *superblock;
597     rt_size_t size;
598     char parent_path[DFS_PATH_MAX],file_name[TMPFS_NAME_MAX];
599 
600     superblock = (struct tmpfs_sb *)fs->data;
601     RT_ASSERT(superblock != NULL);
602 
603     d_file = dfs_tmpfs_lookup(superblock, newpath, &size);
604     if (d_file != NULL)
605         return -EEXIST;
606 
607     d_file = dfs_tmpfs_lookup(superblock, oldpath, &size);
608     if (d_file == NULL)
609         return -ENOENT;
610 
611     /* find parent file */
612     _path_separate(newpath, parent_path, file_name);
613     if (file_name[0] == '\0') /* it's root dir */
614         return -ENOENT;
615     /* open parent directory */
616     p_file = dfs_tmpfs_lookup(superblock, parent_path, &size);
617     RT_ASSERT(p_file != NULL);
618 
619     rt_spin_lock(&superblock->lock);
620     rt_list_remove(&(d_file->sibling));
621     rt_spin_unlock(&superblock->lock);
622 
623     strncpy(d_file->name, file_name, TMPFS_NAME_MAX);
624 
625     rt_spin_lock(&superblock->lock);
626     rt_list_insert_after(&(p_file->subdirs), &(d_file->sibling));
627     rt_spin_unlock(&superblock->lock);
628 
629     return RT_EOK;
630 }
631 
632 static const struct dfs_file_ops _tmp_fops =
633 {
634     dfs_tmpfs_open,
635     dfs_tmpfs_close,
636     dfs_tmpfs_ioctl,
637     dfs_tmpfs_read,
638     dfs_tmpfs_write,
639     NULL, /* flush */
640     dfs_tmpfs_lseek,
641     dfs_tmpfs_getdents,
642 };
643 
644 static const struct dfs_filesystem_ops _tmpfs =
645 {
646     "tmp",
647     DFS_FS_FLAG_DEFAULT,
648     &_tmp_fops,
649 
650     dfs_tmpfs_mount,
651     dfs_tmpfs_unmount,
652     NULL, /* mkfs */
653     dfs_tmpfs_statfs,
654 
655     dfs_tmpfs_unlink,
656     dfs_tmpfs_stat,
657     dfs_tmpfs_rename,
658 };
659 
dfs_tmpfs_init(void)660 int dfs_tmpfs_init(void)
661 {
662     /* register tmp file system */
663     dfs_register(&_tmpfs);
664 
665     return 0;
666 }
667