1 /*
2  * Copyright (c) 2006-2025 RT-Thread Development Team
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  *
6  * Change Logs:
7  * Date           Author       Notes
8  * 2005-02-22     Bernard      The first version.
9  * 2017-12-11     Bernard      Use rt_free to instead of free in fd_is_open().
10  * 2018-03-20     Heyuanjie    dynamic allocation FD
11  */
12 
13 #include <dfs.h>
14 #include <dfs_fs.h>
15 #include <dfs_dentry.h>
16 #include <dfs_file.h>
17 #include <dfs_mnt.h>
18 
19 #include <rtservice.h>
20 
21 #include "dfs_private.h"
22 
23 #define DBG_TAG    "DFS"
24 #define DBG_LVL    DBG_INFO
25 #include <rtdbg.h>
26 
27 #ifdef RT_USING_SMART
28 #include <lwp.h>
29 #endif
30 
31 #ifdef DFS_USING_WORKDIR
32 char working_directory[DFS_PATH_MAX] = {"/"};
33 #endif
34 
35 static rt_bool_t _dfs_init_ok = RT_FALSE;
36 
37 /* device filesystem lock */
38 static struct rt_mutex fslock;
39 static struct rt_mutex fdlock;
40 static struct dfs_fdtable _fdtab = {0};
41 
42 /**
43  * @brief Expand the file descriptor table to accommodate a specified file descriptor
44  *
45  * This function ensures that the file descriptor table in the given `dfs_fdtable` structure
46  * has sufficient capacity to include the specified file descriptor `fd`. If the table
47  * needs to be expanded, it reallocates memory and initializes new slots to `NULL`.
48  *
49  * @param[in,out] fdt Pointer to the file descriptor table to be expanded
50  * @param[in] fd The file descriptor that needs to be accommodated
51  *
52  * @return int The input file descriptor if successful (fd >= 0),
53  *         -1 if expansion failed or fd exceeds maximum allowed value (DFS_FD_MAX)
54  *
55  * @note Expand table size to next multiple of 4 (but not exceeding DFS_FD_MAX)
56  */
_fdt_slot_expand(struct dfs_fdtable * fdt,int fd)57 static int _fdt_slot_expand(struct dfs_fdtable *fdt, int fd)
58 {
59     int nr;
60     int index;
61     struct dfs_file **fds = NULL;
62 
63     if (fd < fdt->maxfd)
64     {
65         return fd;
66     }
67     if (fd >= DFS_FD_MAX)
68     {
69         return -1;
70     }
71 
72     nr = ((fd + 4) & ~3);
73     if (nr > DFS_FD_MAX)
74     {
75         nr = DFS_FD_MAX;
76     }
77     fds = (struct dfs_file **)rt_realloc(fdt->fds, nr * sizeof(struct dfs_file *));
78     if (!fds)
79     {
80         return -1;
81     }
82 
83     /* clean the new allocated fds */
84     for (index = fdt->maxfd; index < nr; index++)
85     {
86         fds[index] = NULL;
87     }
88     fdt->fds = fds;
89     fdt->maxfd = nr;
90 
91     return fd;
92 }
93 
94 /**
95  * @brief Allocate an available file descriptor slot in the file descriptor table
96  *
97  * @param[in,out] fdt Pointer to the file descriptor table to allocate from
98  * @param[in] startfd The starting file descriptor index to begin searching
99  *
100  * @return int The allocated file descriptor index if successful (>= 0),
101  *         -1 if allocation failed (table expansion failed)
102  *
103  * @note If no empty slot found, expand the table and return the new index.
104  */
_fdt_slot_alloc(struct dfs_fdtable * fdt,int startfd)105 static int _fdt_slot_alloc(struct dfs_fdtable *fdt, int startfd)
106 {
107     int idx;
108 
109     /* find an empty fd slot */
110     for (idx = startfd; idx < (int)fdt->maxfd; idx++)
111     {
112         if (fdt->fds[idx] == RT_NULL)
113         {
114             return idx;
115         }
116     }
117 
118     idx = fdt->maxfd;
119     if (idx < startfd)
120     {
121         idx = startfd;
122     }
123 
124     if (_fdt_slot_expand(fdt, idx) < 0)
125     {
126         return -1;
127     }
128 
129     return idx;
130 }
131 
132 /**
133  * @brief Allocate a file descriptor from the file descriptor table
134  *
135  * @param[in,out] fdt Pointer to the file descriptor table to allocate from
136  * @param[in] startfd The starting file descriptor index to begin searching
137  *
138  * @return int The allocated file descriptor index if successful (>= 0),
139  *         -1 if allocation failed
140  *
141  * @note This is a wrapper function that calls _fdt_slot_alloc() to perform
142  *       the actual allocation. It maintains the same behavior as _fdt_slot_alloc().
143  */
_fdt_fd_alloc(struct dfs_fdtable * fdt,int startfd)144 static int _fdt_fd_alloc(struct dfs_fdtable *fdt, int startfd)
145 {
146     int idx;
147 
148     idx = _fdt_slot_alloc(fdt, startfd);
149 
150     return idx;
151 }
152 
153 /**
154  * this function will lock device file system.
155  *
156  * @note please don't invoke it on ISR.
157  */
dfs_lock(void)158 rt_err_t dfs_lock(void)
159 {
160     rt_err_t result = -RT_EBUSY;
161 
162     while (result == -RT_EBUSY)
163     {
164         result = rt_mutex_take(&fslock, RT_WAITING_FOREVER);
165     }
166 
167     return result;
168 }
169 
170 /**
171  * this function will unlock device file system.
172  *
173  * @note please don't invoke it on ISR.
174  */
dfs_unlock(void)175 void dfs_unlock(void)
176 {
177     rt_mutex_release(&fslock);
178 }
179 
180 /** @addtogroup group_device_virtual_file_system
181  *
182  *
183  *  @{
184  */
185 
186 /**
187  * @brief Lock the file descriptor table mutex
188  *
189  * @return rt_err_t RT_EOK if lock acquired successfully,
190  *         -RT_ENOSYS if filesystem not initialized,
191  *         -RT_EBUSY if mutex is already locked (retries until acquired)
192  *
193  * @note This function will block indefinitely until the lock is acquired.
194  *       Should not be called from interrupt service routines.
195  */
dfs_file_lock(void)196 rt_err_t dfs_file_lock(void)
197 {
198     rt_err_t result = -RT_EBUSY;
199 
200     if (!_dfs_init_ok)
201     {
202         return -RT_ENOSYS;
203     }
204 
205     while (result == -RT_EBUSY)
206     {
207         result = rt_mutex_take(&fdlock, RT_WAITING_FOREVER);
208     }
209 
210     return result;
211 }
212 
213 /**
214  * @brief Unlock the file descriptor table mutex
215  *
216  * @note This function releases the lock acquired by dfs_file_lock().
217  *       Should be called in the same context as the corresponding lock.
218  *       Should not be called from interrupt service routines.
219  */
dfs_file_unlock(void)220 void dfs_file_unlock(void)
221 {
222     rt_mutex_release(&fdlock);
223 }
224 
225 /**
226  * this function will initialize device file system.
227  */
dfs_init(void)228 int dfs_init(void)
229 {
230     if (_dfs_init_ok)
231     {
232         LOG_E("DFS was already initialized.\n");
233         return 0;
234     }
235 
236     /* create device filesystem lock */
237     rt_mutex_init(&fslock, "fslock", RT_IPC_FLAG_FIFO);
238     rt_mutex_init(&fdlock, "fdlock", RT_IPC_FLAG_FIFO);
239 
240     /* Initialize dentry system */
241     dfs_dentry_init();
242 
243     _dfs_init_ok = RT_TRUE;
244 
245     return 0;
246 }
247 INIT_PREV_EXPORT(dfs_init);
248 
249 /**
250  * @brief Create and initialize a new file descriptor structure
251  *
252  * @return struct dfs_file* Pointer to the newly created file descriptor structure,
253  *         NULL if memory allocation failed
254  */
dfs_file_create(void)255 struct dfs_file* dfs_file_create(void)
256 {
257     struct dfs_file *file;
258 
259     file = (struct dfs_file *)rt_calloc(1, sizeof(struct dfs_file));
260     if (file)
261     {
262         file->magic = DFS_FD_MAGIC;
263         file->ref_count = 1;
264         rt_mutex_init(&file->pos_lock, "fpos", RT_IPC_FLAG_PRIO);
265     }
266 
267     return file;
268 }
269 
270 /**
271  * @brief Destroy and free a file descriptor structure
272  *
273  * @param[in] file Pointer to the file descriptor structure to be destroyed
274  */
dfs_file_destroy(struct dfs_file * file)275 void dfs_file_destroy(struct dfs_file *file)
276 {
277     rt_mutex_detach(&file->pos_lock);
278 
279     if (file->mmap_context)
280     {
281         rt_free(file->mmap_context);
282     }
283 
284     rt_free(file);
285 }
286 
287 /**
288  * @ingroup group_fs_file_descriptor
289  *
290  * @brief Allocate a new file descriptor in the file descriptor table
291  *
292  * @param[in,out] fdt Pointer to the file descriptor table to allocate from
293  *
294  * @return int The allocated file descriptor index if successful (>= 0),
295  *         -RT_ENOSYS if filesystem lock failed,
296  *         -1 if allocation failed (no empty slots or memory allocation failed)
297  */
fdt_fd_new(struct dfs_fdtable * fdt)298 int fdt_fd_new(struct dfs_fdtable *fdt)
299 {
300     int idx = -1;
301 
302     /* lock filesystem */
303     if (dfs_file_lock() != RT_EOK)
304     {
305         return -RT_ENOSYS;
306     }
307 
308     /* find an empty fd entry */
309     idx = _fdt_fd_alloc(fdt, (fdt == &_fdtab) ? DFS_STDIO_OFFSET : 0);
310     /* can't find an empty fd entry */
311     if (idx < 0)
312     {
313         LOG_E("DFS fd new is failed! Could not found an empty fd entry.");
314     }
315     else if (!fdt->fds[idx])
316     {
317         struct dfs_file *file;
318 
319         file = dfs_file_create();
320 
321         if (file)
322         {
323             fdt->fds[idx] = file;
324 
325             LOG_D("allocate a new fd @ %d", idx);
326         }
327         else
328         {
329             fdt->fds[idx] = RT_NULL;
330             idx = -1;
331         }
332     }
333     else
334     {
335         LOG_E("DFS not found an empty fds entry.");
336         idx = -1;
337     }
338 
339     dfs_file_unlock();
340 
341     return idx;
342 }
343 
344 /**
345  * @brief Release a file descriptor from the file descriptor table
346  *
347  * @param[in,out] fdt Pointer to the file descriptor table
348  * @param[in] fd The file descriptor to be released
349  */
fdt_fd_release(struct dfs_fdtable * fdt,int fd)350 void fdt_fd_release(struct dfs_fdtable *fdt, int fd)
351 {
352     if (fd < fdt->maxfd)
353     {
354         struct dfs_file *file;
355 
356         file = fdt_get_file(fdt, fd);
357 
358         if (file && file->ref_count == 1)
359         {
360             dfs_file_destroy(file);
361         }
362         else
363         {
364             rt_atomic_sub(&(file->ref_count), 1);
365         }
366 
367         fdt->fds[fd] = RT_NULL;
368     }
369 }
370 
371 /**
372  * @ingroup group_fs_file_descriptor
373  *
374  * This function will return a file descriptor structure according to file
375  * descriptor.
376  *
377  * @return NULL on on this file descriptor or the file descriptor structure
378  * pointer.
379  */
380 
fdt_get_file(struct dfs_fdtable * fdt,int fd)381 struct dfs_file *fdt_get_file(struct dfs_fdtable *fdt, int fd)
382 {
383     struct dfs_file *f;
384 
385     if (fd < 0 || fd >= (int)fdt->maxfd)
386     {
387         return NULL;
388     }
389 
390     f = fdt->fds[fd];
391 
392     /* check file valid or not */
393     if ((f == NULL) || (f->magic != DFS_FD_MAGIC))
394     {
395         return NULL;
396     }
397 
398     return f;
399 }
400 
401 /**
402  * @brief Associate a file structure with a file descriptor in the file descriptor table
403  *
404  * @param[in,out] fdt Pointer to the file descriptor table
405  * @param[in] fd The file descriptor to associate with
406  * @param[in] file Pointer to the file structure to associate
407  *
408  * @return int The file descriptor if successful (>= 0),
409  *         -1 if association failed (invalid parameters or fd already in use),
410  *         -RT_ENOSYS if filesystem lock failed
411  */
fdt_fd_associate_file(struct dfs_fdtable * fdt,int fd,struct dfs_file * file)412 int fdt_fd_associate_file(struct dfs_fdtable *fdt, int fd, struct dfs_file *file)
413 {
414     int retfd = -1;
415 
416     if (!file)
417     {
418         return retfd;
419     }
420     if (!fdt)
421     {
422         return retfd;
423     }
424 
425     if (dfs_file_lock() != RT_EOK)
426     {
427         return -RT_ENOSYS;
428     }
429 
430     /* check old fd */
431     if ((fd < 0) || (fd >= fdt->maxfd))
432     {
433         goto exit;
434     }
435 
436     if (fdt->fds[fd])
437     {
438         goto exit;
439     }
440 
441     /* inc ref_count */
442     rt_atomic_add(&(file->ref_count), 1);
443     fdt->fds[fd] = file;
444     retfd = fd;
445 
446 exit:
447     dfs_file_unlock();
448     return retfd;
449 }
450 
451 /**
452  * @brief Allocate a new file descriptor in current process's file descriptor table
453  *
454  * @return int The allocated file descriptor index if successful (>= 0),
455  *         -1 if allocation failed
456  */
fd_new(void)457 int fd_new(void)
458 {
459     struct dfs_fdtable *fdt;
460 
461     fdt = dfs_fdtable_get();
462 
463     return fdt_fd_new(fdt);
464 }
465 
466 /**
467  * @ingroup group_fs_file_descriptor
468  *
469  * This function will put the file descriptor.
470  */
fd_release(int fd)471 void fd_release(int fd)
472 {
473     struct dfs_fdtable *fdt;
474 
475     fdt = dfs_fdtable_get();
476     fdt_fd_release(fdt, fd);
477 }
478 
479 /**
480  * @brief Get file structure by file descriptor from current process's fd table
481  *
482  * @param[in] fd The file descriptor to lookup
483  *
484  * @return struct dfs_file* Pointer to the file structure if found,
485  *         NULL if invalid fd or file not found
486  */
fd_get(int fd)487 struct dfs_file *fd_get(int fd)
488 {
489     struct dfs_fdtable *fdt;
490 
491     fdt = dfs_fdtable_get();
492 
493     return fdt_get_file(fdt, fd);
494 }
495 
496 /**
497  * This function will get the file descriptor table of current process.
498  */
dfs_fdtable_get(void)499 struct dfs_fdtable *dfs_fdtable_get(void)
500 {
501     struct dfs_fdtable *fdt;
502 #ifdef RT_USING_SMART
503     struct rt_lwp *lwp = NULL;
504     rt_thread_t thread = rt_thread_self();
505 
506     if (thread)
507     {
508         lwp = (struct rt_lwp *)thread->lwp;
509     }
510 
511     if (lwp)
512         fdt = &lwp->fdt;
513     else
514         fdt = &_fdtab;
515 #else
516     fdt = &_fdtab;
517 #endif
518 
519     return fdt;
520 }
521 
522 #ifdef RT_USING_SMART
523 /**
524  * @brief Get file structure by file descriptor from current process's fd table
525  *
526  * @param[in] fd The file descriptor to lookup
527  *
528  * @return struct dfs_file* Pointer to the file structure if found,
529  *         NULL if invalid fd or file not found
530  */
dfs_fdtable_get_from_pid(int pid)531 struct dfs_fdtable *dfs_fdtable_get_from_pid(int pid)
532 {
533     struct rt_lwp *lwp = RT_NULL;
534     struct dfs_fdtable *fdt = RT_NULL;
535 
536     lwp_pid_lock_take();
537     lwp = lwp_from_pid_locked(pid);
538     if (lwp)
539     {
540         fdt = &lwp->fdt;
541     }
542     lwp_pid_lock_release();
543 
544     return fdt;
545 }
546 #endif
547 
548 /**
549  * @brief Get the global file descriptor table
550  *
551  * @return struct dfs_fdtable* Pointer to the global file descriptor table
552  */
dfs_fdtable_get_global(void)553 struct dfs_fdtable *dfs_fdtable_get_global(void)
554 {
555     return &_fdtab;
556 }
557 
558 /**
559  * @brief  Dup the specified fd_src from fdt_src to fdt_dst.
560  *
561  * @param[out]  fdt_dst is the fd table for destination, if empty, use global (_fdtab).
562  *
563  * @param[in]  fdt_src is the fd table for source, if empty, use global (_fdtab).
564  *
565  * @param[in]  fd_src is the fd in the designate fdt_src table.
566  *
567  * @return -1 on failed or the allocated file descriptor.
568  */
dfs_fdtable_dup(struct dfs_fdtable * fdt_dst,struct dfs_fdtable * fdt_src,int fd_src)569 int dfs_fdtable_dup(struct dfs_fdtable *fdt_dst, struct dfs_fdtable *fdt_src, int fd_src)
570 {
571     int newfd = -1;
572 
573     if (dfs_file_lock() != RT_EOK)
574     {
575         return -RT_ENOSYS;
576     }
577 
578     if (fdt_src == NULL)
579     {
580         fdt_src = &_fdtab;
581     }
582 
583     if (fdt_dst == NULL)
584     {
585         fdt_dst = &_fdtab;
586     }
587 
588     /* check fd */
589     if ((fd_src < 0) || (fd_src >= fdt_src->maxfd))
590     {
591         goto _EXIT;
592     }
593     if (!fdt_src->fds[fd_src])
594     {
595         goto _EXIT;
596     }
597 
598     /* get a new fd*/
599     newfd = fdt_fd_new(fdt_dst);
600     if (newfd >= 0)
601     {
602         fdt_dst->fds[newfd]->mode = fdt_src->fds[fd_src]->mode;
603         fdt_dst->fds[newfd]->flags = fdt_src->fds[fd_src]->flags;
604         fdt_dst->fds[newfd]->fops = fdt_src->fds[fd_src]->fops;
605         fdt_dst->fds[newfd]->dentry = dfs_dentry_ref(fdt_src->fds[fd_src]->dentry);
606         fdt_dst->fds[newfd]->vnode = fdt_src->fds[fd_src]->vnode;
607         fdt_dst->fds[newfd]->mmap_context = RT_NULL;
608         fdt_dst->fds[newfd]->data = fdt_src->fds[fd_src]->data;
609 
610         /*
611         * dma-buf/socket fd is without dentry, so should used the vnode reference.
612         */
613         if (!fdt_dst->fds[newfd]->dentry)
614         {
615             rt_atomic_add(&(fdt_dst->fds[newfd]->vnode->ref_count), 1);
616         }
617     }
618 
619 _EXIT:
620     dfs_file_unlock();
621 
622     return newfd;
623 }
624 
625 /**
626  * @brief  drop fd from the fd table.
627  *
628  * @param  fdt is the fd table, if empty, use global (_fdtab).
629  *
630  * @param  fd is the fd in the designate fd table.
631  *
632  * @return -1 on failed the drop file descriptor.
633  */
dfs_fdtable_drop_fd(struct dfs_fdtable * fdt,int fd)634 int dfs_fdtable_drop_fd(struct dfs_fdtable *fdt, int fd)
635 {
636     int err = 0;
637 
638     if (fdt == NULL)
639     {
640         fdt = &_fdtab;
641     }
642 
643     if (dfs_file_lock() != RT_EOK)
644     {
645         return -RT_ENOSYS;
646     }
647 
648     err = dfs_file_close(fdt->fds[fd]);
649     if (!err)
650     {
651         fdt_fd_release(fdt, fd);
652     }
653 
654     dfs_file_unlock();
655 
656     return err;
657 }
658 
659 /**
660  * @brief Duplicate a file descriptor in the current process's file descriptor table
661  *
662  * @param[in] oldfd The file descriptor to duplicate
663  * @param[in] startfd The starting index to search for an available file descriptor slot
664  *
665  * @return int The new file descriptor if successful (>=0),
666  *         -1 if failed (invalid fd or allocation failed),
667  *         -RT_ENOSYS if filesystem lock failed
668  */
dfs_dup(int oldfd,int startfd)669 int dfs_dup(int oldfd, int startfd)
670 {
671     int newfd = -1;
672     struct dfs_fdtable *fdt = NULL;
673 
674     if (dfs_file_lock() != RT_EOK)
675     {
676         return -RT_ENOSYS;
677     }
678 
679     /* check old fd */
680     fdt = dfs_fdtable_get();
681     if ((oldfd < 0) || (oldfd >= fdt->maxfd))
682     {
683         rt_set_errno(-EBADF);
684         goto exit;
685     }
686     if (!fdt->fds[oldfd])
687     {
688         goto exit;
689     }
690     /* get a new fd */
691     newfd = _fdt_slot_alloc(fdt, startfd);
692     if (newfd >= 0)
693     {
694         fdt->fds[newfd] = fdt->fds[oldfd];
695 
696         /* inc ref_count */
697         rt_atomic_add(&(fdt->fds[newfd]->ref_count), 1);
698     }
699 exit:
700     dfs_file_unlock();
701     return newfd;
702 }
703 
704 /**
705  * @brief Duplicate a file descriptor from current process to target file descriptor table
706  *
707  * @param[in]  oldfd is the fd in current process.
708  * @param[in,out]  fdtab is the fd table to dup, if empty, use global (_fdtab).
709  *
710  * @return -1 on failed or the allocated file descriptor.
711  */
dfs_dup_to(int oldfd,struct dfs_fdtable * fdtab)712 int dfs_dup_to(int oldfd, struct dfs_fdtable *fdtab)
713 {
714     int newfd = -1;
715     struct dfs_fdtable *fdt = NULL;
716 
717     if (dfs_file_lock() != RT_EOK)
718     {
719         return -RT_ENOSYS;
720     }
721 
722     if (fdtab == NULL)
723     {
724         fdtab = &_fdtab;
725     }
726 
727     /* check old fd */
728     fdt = dfs_fdtable_get();
729     if ((oldfd < 0) || (oldfd >= fdt->maxfd))
730     {
731         goto exit;
732     }
733     if (!fdt->fds[oldfd])
734     {
735         goto exit;
736     }
737     /* get a new fd*/
738     newfd = _fdt_slot_alloc(fdtab, DFS_STDIO_OFFSET);
739     if (newfd >= 0)
740     {
741         fdtab->fds[newfd] = fdt->fds[oldfd];
742 
743         /* inc ref_count */
744         rt_atomic_add(&(fdtab->fds[newfd]->ref_count), 1);
745     }
746 exit:
747     dfs_file_unlock();
748 
749     return newfd;
750 }
751 
752 /**
753  * @brief  Duplicate a file descriptor from source table to current process
754  *
755  * @param[in]  oldfd is the fd in the designate fd table.
756  * @param[in,out]  fdtab is the fd table for oldfd, if empty, use global (_fdtab).
757  *
758  * @return -1 on failed or the allocated file descriptor.
759  */
dfs_dup_from(int oldfd,struct dfs_fdtable * fdtab)760 int dfs_dup_from(int oldfd, struct dfs_fdtable *fdtab)
761 {
762     int newfd = -1;
763     struct dfs_file *file;
764 
765     if (dfs_file_lock() != RT_EOK)
766     {
767         return -RT_ENOSYS;
768     }
769 
770     if (fdtab == NULL)
771     {
772         fdtab = &_fdtab;
773     }
774 
775     /* check old fd */
776     if ((oldfd < 0) || (oldfd >= fdtab->maxfd))
777     {
778         goto exit;
779     }
780     if (!fdtab->fds[oldfd])
781     {
782         goto exit;
783     }
784     /* get a new fd*/
785     newfd = fd_new();
786     file = fd_get(newfd);
787     if (newfd >= 0 && file)
788     {
789         file->mode = fdtab->fds[oldfd]->mode;
790         file->flags = fdtab->fds[oldfd]->flags;
791         file->fops = fdtab->fds[oldfd]->fops;
792         file->dentry = dfs_dentry_ref(fdtab->fds[oldfd]->dentry);
793         file->vnode = fdtab->fds[oldfd]->vnode;
794         file->mmap_context = RT_NULL;
795         file->data = fdtab->fds[oldfd]->data;
796     }
797 
798     dfs_file_close(fdtab->fds[oldfd]);
799 
800 exit:
801     fdt_fd_release(fdtab, oldfd);
802     dfs_file_unlock();
803 
804     return newfd;
805 }
806 
807 /**
808  * @brief System call to duplicate a file descriptor
809  *
810  * @param[in] oldfd The file descriptor to duplicate
811  *
812  * @return sysret_t/int The new file descriptor if successful (>=0),
813  *         negative error code if failed
814  */
815 #ifdef RT_USING_SMART
sys_dup(int oldfd)816 sysret_t sys_dup(int oldfd)
817 #else
818 int sys_dup(int oldfd)
819 #endif
820 {
821     int err = 0;
822     int newfd = dfs_dup(oldfd, (dfs_fdtable_get() == &_fdtab) ? DFS_STDIO_OFFSET : 0);
823     if(newfd < 0)
824     {
825         err = rt_get_errno();
826     }
827 
828 #ifdef RT_USING_SMART
829     return err < 0 ? err : newfd;
830 #else
831     return err < 0 ? err : newfd;
832 #endif
833 }
834 
835 /**
836  * @brief System call to duplicate a file descriptor to a specific descriptor number
837  *
838  * @param[in] oldfd The file descriptor to duplicate
839  * @param[in] newfd The desired file descriptor number
840  *
841  * @return rt_err_t The new file descriptor number if successful (>=0),
842  *         -RT_ENOSYS if filesystem lock failed,
843  *         -1 if operation failed (invalid descriptors or allocation failed)
844  */
sys_dup2(int oldfd,int newfd)845 rt_err_t sys_dup2(int oldfd, int newfd)
846 {
847     struct dfs_fdtable *fdt = NULL;
848     int ret = 0;
849     int retfd = -1;
850 
851     if (dfs_file_lock() != RT_EOK)
852     {
853         return -RT_ENOSYS;
854     }
855 
856     /* check old fd */
857     fdt = dfs_fdtable_get();
858     if ((oldfd < 0) || (oldfd >= fdt->maxfd))
859     {
860         goto exit;
861     }
862     if (!fdt->fds[oldfd])
863     {
864         goto exit;
865     }
866     if (newfd < 0)
867     {
868         goto exit;
869     }
870     if (newfd >= fdt->maxfd)
871     {
872         newfd = _fdt_slot_expand(fdt, newfd);
873         if (newfd < 0)
874         {
875             goto exit;
876         }
877     }
878     if (fdt->fds[newfd] == fdt->fds[oldfd])
879     {
880         /* ok, return newfd */
881         retfd = newfd;
882         goto exit;
883     }
884 
885     if (fdt->fds[newfd])
886     {
887         ret = dfs_file_close(fdt->fds[newfd]);
888         if (ret < 0)
889         {
890             goto exit;
891         }
892         fd_release(newfd);
893     }
894 
895     fdt->fds[newfd] = fdt->fds[oldfd];
896     /* inc ref_count */
897     rt_atomic_add(&(fdt->fds[newfd]->ref_count), 1);
898     retfd = newfd;
899 exit:
900     dfs_file_unlock();
901     return retfd;
902 }
903 
904 /**
905  * @brief Get the subdirectory path relative to a parent directory
906  *
907  * @param[in] directory The parent directory path
908  * @param[in] filename The full path including parent directory and subpath
909  *
910  * @return const char* Pointer to the subdirectory portion of filename,
911  *         NULL if paths are identical or invalid
912  */
dfs_subdir(const char * directory,const char * filename)913 const char *dfs_subdir(const char *directory, const char *filename)
914 {
915     const char *dir;
916 
917     if (strlen(directory) == strlen(filename)) /* it's a same path */
918         return NULL;
919 
920     dir = filename + strlen(directory);
921     if ((*dir != '/') && (dir != filename))
922     {
923         dir--;
924     }
925 
926     return dir;
927 }
928 RTM_EXPORT(dfs_subdir);
929 
930 /**
931  * @brief Normalize a path by combining directory and filename into an absolute path
932  *
933  * @param[in] directory The parent directory path (NULL means use working directory)
934  * @param[in] filename The filename or relative path to be normalized
935  *
936  * @return char* The normalized absolute path (must be freed by caller),
937  *         NULL if path is invalid or memory allocation fails
938  *
939  * @note This function will:
940  *       - Handle working directory when directory is NULL
941  *       - Join directory and filename with proper separators
942  *       - Resolve . and .. in paths
943  *       - Remove redundant slashes
944  *       - Ensure path starts with /
945  *       - Allocate memory for the returned path string
946  */
dfs_normalize_path(const char * directory,const char * filename)947 char *dfs_normalize_path(const char *directory, const char *filename)
948 {
949     char *fullpath;
950     char *dst0, *dst, *src;
951 
952     /* check parameters */
953     RT_ASSERT(filename != NULL);
954 
955 #ifdef DFS_USING_WORKDIR
956     if (directory == NULL) /* shall use working directory */
957     {
958 #ifdef RT_USING_SMART
959         directory = lwp_getcwd();
960 #else
961         directory = &working_directory[0];
962 #endif
963     }
964 #else
965     if ((directory == NULL) && (filename[0] != '/'))
966     {
967         rt_kprintf(NO_WORKING_DIR);
968 
969         return NULL;
970     }
971 #endif
972 
973     if (filename[0] != '/')
974     {
975         int path_len;
976 
977         path_len = strlen(directory) + strlen(filename) + 2;
978         if (path_len > DFS_PATH_MAX)
979         {
980             return NULL;
981         }
982 
983         fullpath = (char *)rt_malloc(path_len);
984         if (fullpath == NULL)
985         {
986             return NULL;
987         }
988 
989         /* join path and file name */
990         rt_snprintf(fullpath, strlen(directory) + strlen(filename) + 2,
991                     "%s/%s", directory, filename);
992     }
993     else /* it's a absolute path, use it directly */
994     {
995         fullpath = rt_strdup(filename); /* copy string */
996 
997         if (fullpath == NULL)
998             return NULL;
999     }
1000 
1001     /* Initialize source and destination pointers to start of path */
1002     src = fullpath;
1003     dst = fullpath;
1004 
1005     /* Save initial position for boundary checking */
1006     dst0 = dst;
1007     while (1)
1008     {
1009         char c = *src;
1010 
1011         /* Handle '.' and '..' path components */
1012         if (c == '.')
1013         {
1014             /* Single dot at end of path */
1015             if (!src[1])
1016                 src++; /* '.' and ends, Skip single dot */
1017             else if (src[1] == '/')
1018             {
1019                 /* './' case */
1020                 src += 2;
1021 
1022                 /* Skip consecutive slashes */
1023                 while ((*src == '/') && (*src != '\0'))
1024                     src++;
1025                 continue;
1026             }
1027             else if (src[1] == '.')
1028             {
1029                 /* Parent directory reference */
1030                 if (!src[2])
1031                 {
1032                     /* '..' and ends case */
1033                     src += 2;
1034                     goto up_one;
1035                 }
1036                 else if (src[2] == '/')
1037                 {
1038                     /* '../' case: parent directory reference */
1039                     src += 3;
1040 
1041                     /* Skip consecutive slashes */
1042                     while ((*src == '/') && (*src != '\0'))
1043                         src++;
1044                     goto up_one;
1045                 }
1046             }
1047         }
1048 
1049         /* copy up the next '/' and erase all '/' */
1050         while ((c = *src++) != '\0' && c != '/')
1051             *dst++ = c;
1052 
1053         if (c == '/')
1054         {
1055             /* Add single slash */
1056             *dst++ = '/';
1057             while (c == '/')
1058                 c = *src++;
1059 
1060             src--;
1061         }
1062         else if (!c)
1063             break; /* End of string */
1064 
1065         continue;
1066 
1067     /* Handle parent directory reference */
1068     up_one:
1069         /* Move back one directory level */
1070         dst--;
1071         if (dst < dst0) /* Check for path traversal underflow */
1072         {
1073             rt_free(fullpath);
1074             return NULL;
1075         }
1076         /* Find previous directory separator */
1077         while (dst0 < dst && dst[-1] != '/')
1078             dst--;
1079     }
1080 
1081     /* Null-terminate the path */
1082     *dst = '\0';
1083 
1084     /* remove '/' in the end of path if exist */
1085     dst--;
1086     if (dst > fullpath && (*dst == '/'))
1087         *dst = '\0';
1088 
1089     /* final check fullpath is not empty, for the special path of lwext "/.." */
1090     if ('\0' == fullpath[0])
1091     {
1092         fullpath[0] = '/';
1093         fullpath[1] = '\0';
1094     }
1095 
1096     return fullpath;
1097 }
1098 RTM_EXPORT(dfs_normalize_path);
1099 
1100 #ifdef RT_USING_FINSH
1101 #include <finsh.h>
1102 /**
1103  * @brief List all open file descriptors in the current process
1104  *
1105  * @return int 0 on success, -1 if failed to get file descriptor table
1106  */
list_fd(void)1107 int list_fd(void)
1108 {
1109     int index;
1110     struct dfs_fdtable *fd_table;
1111 
1112     fd_table = dfs_fdtable_get();
1113     if (!fd_table) return -1;
1114 
1115     rt_enter_critical();
1116 
1117     rt_kprintf("fd type    ref magic  path\n");
1118     rt_kprintf("-- ------  --- ----- ------\n");
1119     for (index = 0; index < (int)fd_table->maxfd; index++)
1120     {
1121         struct dfs_file *file = fd_table->fds[index];
1122 
1123         if (file && file->vnode)
1124         {
1125             rt_kprintf("%2d ", index);
1126             if (file->vnode->type == FT_DIRECTORY)    rt_kprintf("%-7.7s ", "dir");
1127             else if (file->vnode->type == FT_REGULAR) rt_kprintf("%-7.7s ", "file");
1128             else if (file->vnode->type == FT_SOCKET)  rt_kprintf("%-7.7s ", "socket");
1129             else if (file->vnode->type == FT_USER)    rt_kprintf("%-7.7s ", "user");
1130             else if (file->vnode->type == FT_DEVICE)  rt_kprintf("%-7.7s ", "device");
1131             else rt_kprintf("%-8.8s ", "unknown");
1132             rt_kprintf("%3d ", file->ref_count);
1133             rt_kprintf("%04x  ", file->magic);
1134 
1135             if (file->dentry)
1136             {
1137                 rt_kprintf("%s%s\n", file->dentry->mnt->fullpath, file->dentry->pathname);
1138             }
1139             else
1140             {
1141                 rt_kprintf("\n");
1142             }
1143         }
1144     }
1145     rt_exit_critical();
1146 
1147     return 0;
1148 }
1149 MSH_CMD_EXPORT(list_fd, list file descriptor);
1150 
1151 /**
1152  * @brief Dump all file descriptors information in the global file descriptor table
1153  *
1154  * @param[in] argc Number of command line arguments (unused)
1155  * @param[in] argv Array of command line arguments (unused)
1156  *
1157  * @return int 0 on success,
1158  *         -RT_ENOSYS if failed to acquire file system lock
1159  */
dfs_fd_dump(int argc,char ** argv)1160 int dfs_fd_dump(int argc, char** argv)
1161 {
1162     int index;
1163 
1164     if (dfs_file_lock() != RT_EOK)
1165     {
1166         return -RT_ENOSYS;
1167     }
1168 
1169     for (index = 0; index < _fdtab.maxfd; index++)
1170     {
1171         struct dfs_file *file = _fdtab.fds[index];
1172         if (file)
1173         {
1174             char* fullpath = dfs_dentry_full_path(file->dentry);
1175             if (fullpath)
1176             {
1177                 printf("[%d] - %s, ref_count %zd\n", index,
1178                     fullpath, (size_t)rt_atomic_load(&(file->ref_count)));
1179                 rt_free(fullpath);
1180             }
1181             else
1182             {
1183                 printf("[%d] - %s, ref_count %zd\n", index,
1184                     file->dentry->pathname, (size_t)rt_atomic_load(&(file->ref_count)));
1185             }
1186         }
1187     }
1188     dfs_file_unlock();
1189 
1190     return 0;
1191 }
1192 MSH_CMD_EXPORT_ALIAS(dfs_fd_dump, fd_dump, fd dump);
1193 
1194 #ifdef PKG_USING_DLOG
1195 /**
1196  * @brief Control the DFS (Device File System) debug logging functionality
1197  *
1198  * @param[in] argc Number of command line arguments (must be 2)
1199  * @param[in] argv Array of command line arguments:
1200  *                - argv[1]: "on" to enable logging, "off" to disable logging
1201  *
1202  * @return int Always returns 0 (success)
1203  *
1204  * @note When enabled, this function activates logging for multiple DFS components:
1205  *       - dfs: Core DFS functionality
1206  *       - dfs_file: File operations
1207  *       - dentry: Directory entries
1208  *       - vnode: Virtual nodes
1209  *       - mnt: Mount points
1210  *       - rom: ROM filesystem
1211  *       - devfs: Device filesystem
1212  */
dfs_dlog(int argc,char ** argv)1213 int dfs_dlog(int argc, char** argv)
1214 {
1215     if (argc == 2)
1216     {
1217         if (strcmp(argv[1], "on") == 0)
1218         {
1219             dlog_session_start();
1220             dlog_participant("dfs");
1221             dlog_participant("dfs_file");
1222             dlog_participant("dentry");
1223             dlog_participant("vnode");
1224             dlog_participant("mnt");
1225             dlog_participant("rom");
1226             dlog_participant("devfs");
1227         }
1228         else if (strcmp(argv[1], "off") == 0)
1229         {
1230             dlog_session_stop();
1231         }
1232     }
1233 
1234     return 0;
1235 }
1236 MSH_CMD_EXPORT(dfs_dlog, dfs dlog on|off);
1237 
1238 #endif
1239 
1240 #endif
1241 /** @} */