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  * 2022-10-10     Bernard      The first version of rewrite dfs
9  */
10 #include <rtthread.h>
11 
12 #include "dfs.h"
13 #include "dfs_file.h"
14 #include "dfs_private.h"
15 #include "dfs_dentry.h"
16 #include "dfs_mnt.h"
17 
18 #define DBG_TAG "DFS.dentry"
19 #define DBG_LVL DBG_WARNING
20 #include <rtdbg.h>
21 
22 #define DFS_DENTRY_HASH_NR 32
23 struct dentry_hash_head
24 {
25     rt_list_t head[DFS_DENTRY_HASH_NR];
26 };
27 static struct dentry_hash_head hash_head;
28 
29 /**
30  * @brief Calculate hash value for a dentry based on mount point and path
31  *
32  * @param[in] mnt Pointer to the mount point structure
33  * @param[in] path Path string to be hashed (can be NULL)
34  *
35  * @return uint32_t Calculated hash value within range [0, DFS_DENTRY_HASH_NR-1]
36  */
_dentry_hash(struct dfs_mnt * mnt,const char * path)37 static uint32_t _dentry_hash(struct dfs_mnt *mnt, const char *path)
38 {
39     uint32_t val = 0;
40 
41     if (path)
42     {
43         while (*path)
44         {
45             val = ((val << 5) + val) + *path++;
46         }
47     }
48     return (val ^ (unsigned long) mnt) & (DFS_DENTRY_HASH_NR - 1);
49 }
50 
51 /**
52  * @brief Create a new directory entry (dentry) structure
53  *
54  * @param[in] mnt Pointer to the mount point structure
55  * @param[in] path Path string for the dentry (absolute or relative)
56  * @param[in] is_rela_path Flag indicating if path is relative (RT_TRUE) or absolute (RT_FALSE)
57  *
58  * @return struct dfs_dentry* Pointer to newly created dentry, or NULL if creation failed
59  *
60  * @note The created dentry will have its ref_count initialized to 1 and DENTRY_IS_ALLOCED flag set
61  */
_dentry_create(struct dfs_mnt * mnt,char * path,rt_bool_t is_rela_path)62 static struct dfs_dentry *_dentry_create(struct dfs_mnt *mnt, char *path, rt_bool_t is_rela_path)
63 {
64     struct dfs_dentry *dentry = RT_NULL;
65 
66     if (mnt == RT_NULL || path == RT_NULL)
67     {
68         return dentry;
69     }
70 
71     dentry = (struct dfs_dentry *)rt_calloc(1, sizeof(struct dfs_dentry));
72     if (dentry)
73     {
74         char *dentry_path = path;
75         if (!is_rela_path)
76         {
77             int mntpoint_len = strlen(mnt->fullpath);
78 
79             if (rt_strncmp(mnt->fullpath, dentry_path, mntpoint_len) == 0)
80             {
81                 dentry_path += mntpoint_len;
82             }
83         }
84 
85         dentry->pathname = strlen(dentry_path) ? rt_strdup(dentry_path) : rt_strdup(path);
86         dentry->mnt = dfs_mnt_ref(mnt);
87 
88         rt_atomic_store(&(dentry->ref_count), 1);
89         dentry->flags |= DENTRY_IS_ALLOCED;
90 
91         LOG_I("create a dentry:%p for %s", dentry, mnt->fullpath);
92     }
93 
94     return dentry;
95 }
96 
97 /**
98  * @brief Create a new directory entry (dentry) with absolute path
99  *
100  * @param[in] mnt Pointer to the mount point structure
101  * @param[in] fullpath Absolute path string for the dentry
102  *
103  * @return struct dfs_dentry* Pointer to newly created dentry, or NULL if creation failed
104  *
105  * @note This is a wrapper for _dentry_create() with is_rela_path set to RT_FALSE
106  * @see _dentry_create()
107  */
dfs_dentry_create(struct dfs_mnt * mnt,char * fullpath)108 struct dfs_dentry *dfs_dentry_create(struct dfs_mnt *mnt, char *fullpath)
109 {
110     return _dentry_create(mnt, fullpath, RT_FALSE);
111 }
112 
113 /**
114  * @brief Create a new directory entry (dentry) with relative path
115  *
116  * @param[in] mnt Pointer to the mount point structure
117  * @param[in] rela_path Relative path string for the dentry
118  *
119  * @return struct dfs_dentry* Pointer to newly created dentry, or NULL if creation failed
120  *
121  * @note This is a wrapper for _dentry_create() with is_rela_path set to RT_TRUE
122  * @see _dentry_create()
123  */
dfs_dentry_create_rela(struct dfs_mnt * mnt,char * rela_path)124 struct dfs_dentry *dfs_dentry_create_rela(struct dfs_mnt *mnt, char *rela_path)
125 {
126     return _dentry_create(mnt, rela_path, RT_TRUE);;
127 }
128 
129 /**
130  * @brief Increase reference count for a directory entry (dentry)
131  *
132  * @param[in,out] dentry Pointer to the directory entry structure to be referenced
133  *
134  * @return struct dfs_dentry* The same dentry pointer that was passed in
135  *
136  * @note This function will also increase reference count for associated vnode if exists
137  */
dfs_dentry_ref(struct dfs_dentry * dentry)138 struct dfs_dentry * dfs_dentry_ref(struct dfs_dentry *dentry)
139 {
140     if (dentry)
141     {
142         int ret = dfs_file_lock();
143         if (ret == RT_EOK)
144         {
145             rt_atomic_add(&(dentry->ref_count), 1);
146             if (dentry->vnode)
147             {
148                 rt_atomic_add(&(dentry->vnode->ref_count), 1);
149             }
150             dfs_file_unlock();
151         }
152     }
153 
154     return dentry;
155 }
156 
157 /**
158  * @brief Decrease reference count for a directory entry (dentry) and free if count reaches zero
159  *
160  * @param[in,out] dentry Pointer to the directory entry structure to be unreferenced
161  *
162  * @return struct dfs_dentry* The same dentry pointer if ref_count > 0, NULL if freed
163  */
dfs_dentry_unref(struct dfs_dentry * dentry)164 struct dfs_dentry *dfs_dentry_unref(struct dfs_dentry *dentry)
165 {
166     rt_err_t ret = RT_EOK;
167 
168     if (dentry)
169     {
170         ret = dfs_file_lock();
171         if (ret == RT_EOK)
172         {
173             if (dentry->flags & DENTRY_IS_ALLOCED)
174             {
175                 rt_atomic_sub(&(dentry->ref_count), 1);
176             }
177 
178             if (rt_atomic_load(&(dentry->ref_count)) == 0)
179             {
180                 DLOG(msg, "dentry", "dentry", DLOG_MSG, "free dentry, ref_count=0");
181                 if (dentry->flags & DENTRY_IS_ADDHASH)
182                 {
183                     rt_list_remove(&dentry->hashlist);
184                 }
185 
186                 /* release vnode */
187                 if (dentry->vnode)
188                 {
189                     dfs_vnode_unref(dentry->vnode);
190                 }
191 
192                 /* release mnt */
193                 DLOG(msg, "dentry", "mnt", DLOG_MSG, "dfs_mnt_unref(dentry->mnt)");
194                 if (dentry->mnt)
195                 {
196                     dfs_mnt_unref(dentry->mnt);
197                 }
198 
199                 dfs_file_unlock();
200 
201                 LOG_I("free a dentry: %p", dentry);
202                 rt_free(dentry->pathname);
203                 rt_free(dentry);
204                 dentry = RT_NULL;
205             }
206             else
207             {
208                 if (dentry->vnode)
209                 {
210                     rt_atomic_sub(&(dentry->vnode->ref_count), 1);
211                 }
212                 dfs_file_unlock();
213                 DLOG(note, "dentry", "dentry ref_count=%d", rt_atomic_load(&(dentry->ref_count)));
214             }
215         }
216     }
217 
218     return dentry;
219 }
220 
221 /**
222  * @brief Look up a directory entry (dentry) in hash table by mount point and path
223  *
224  * @param[in] mnt Pointer to the mount point structure to search for
225  * @param[in] path Path string to search for
226  *
227  * @return struct dfs_dentry* Pointer to found dentry (with increased ref_count), or NULL if not found
228  */
_dentry_hash_lookup(struct dfs_mnt * mnt,const char * path)229 static struct dfs_dentry *_dentry_hash_lookup(struct dfs_mnt *mnt, const char *path)
230 {
231     rt_err_t ret = RT_EOK;
232     struct dfs_dentry *entry = RT_NULL;
233 
234     ret = dfs_file_lock();
235     if (ret == RT_EOK)
236     {
237         rt_list_for_each_entry(entry, &hash_head.head[_dentry_hash(mnt, path)], hashlist)
238         {
239             if (entry->mnt == mnt && !strcmp(entry->pathname, path))
240             {
241                 dfs_dentry_ref(entry);
242                 dfs_file_unlock();
243                 return entry;
244             }
245         }
246 
247         dfs_file_unlock();
248     }
249 
250     return RT_NULL;
251 }
252 
253 /**
254  * @brief Insert a directory entry (dentry) into the hash table
255  *
256  * @param[in,out] dentry Pointer to the directory entry to be inserted
257  */
dfs_dentry_insert(struct dfs_dentry * dentry)258 void dfs_dentry_insert(struct dfs_dentry *dentry)
259 {
260     dfs_file_lock();
261     rt_list_insert_after(&hash_head.head[_dentry_hash(dentry->mnt, dentry->pathname)], &dentry->hashlist);
262     dentry->flags |= DENTRY_IS_ADDHASH;
263     dfs_file_unlock();
264 }
265 
266 /**
267  * @brief Look up a directory entry (dentry) in the filesystem
268  *
269  * @param[in] mnt Pointer to the mount point structure
270  * @param[in] path Path string to look up
271  * @param[in] flags Additional lookup flags (currently unused)
272  *
273  * @return struct dfs_dentry* Pointer to found/created dentry (with increased ref_count), or NULL if not found
274  *
275  * @note This function first searches for dentry in hash table,
276  *       If not found and filesystem supports lookup operation:
277  *          - Creates new dentry
278  *          - Calls filesystem's lookup operation to get vnode
279  *          - If vnode is successfully obtained, adds dentry to hash table
280  */
dfs_dentry_lookup(struct dfs_mnt * mnt,const char * path,uint32_t flags)281 struct dfs_dentry *dfs_dentry_lookup(struct dfs_mnt *mnt, const char *path, uint32_t flags)
282 {
283     struct dfs_dentry *dentry;
284     struct dfs_vnode *vnode = RT_NULL;
285     int mntpoint_len = strlen(mnt->fullpath);
286 
287     if (rt_strncmp(mnt->fullpath, path, mntpoint_len) == 0)
288     {
289         path += mntpoint_len;
290         if ((*path) == '\0')
291         {
292             /* root */
293             path = "/";
294         }
295     }
296     dfs_file_lock();
297     dentry = _dentry_hash_lookup(mnt, path);
298     if (!dentry)
299     {
300         if (mnt->fs_ops->lookup)
301         {
302             DLOG(activate, "dentry");
303             /* not in hash table, create it */
304             DLOG(msg, "dentry", "dentry", DLOG_MSG, "dfs_dentry_create_rela(mnt=%s, path=%s)", mnt->fullpath, path);
305             dentry = dfs_dentry_create_rela(mnt, (char*)path);
306             if (dentry)
307             {
308                 DLOG(msg, "dentry", mnt->fs_ops->name, DLOG_MSG, "vnode=fs_ops->lookup(dentry)");
309 
310                 if (dfs_is_mounted(mnt) == 0)
311                 {
312                     vnode = mnt->fs_ops->lookup(dentry);
313                 }
314 
315                 if (vnode)
316                 {
317                     DLOG(msg, mnt->fs_ops->name, "dentry", DLOG_MSG_RET, "return vnode");
318                     dentry->vnode = vnode; /* the refcount of created vnode is 1. no need to reference */
319                     dfs_file_lock();
320                     rt_list_insert_after(&hash_head.head[_dentry_hash(mnt, path)], &dentry->hashlist);
321                     dentry->flags |= DENTRY_IS_ADDHASH;
322                     dfs_file_unlock();
323 
324                     if (dentry->flags & (DENTRY_IS_ALLOCED | DENTRY_IS_ADDHASH)
325                         && !(dentry->flags & DENTRY_IS_OPENED))
326                     {
327                         rt_err_t ret = dfs_file_lock();
328                         if (ret == RT_EOK)
329                         {
330                             dentry->flags |= DENTRY_IS_OPENED;
331                             dfs_file_unlock();
332                         }
333                     }
334                 }
335                 else
336                 {
337                     DLOG(msg, mnt->fs_ops->name, "dentry", DLOG_MSG_RET, "no dentry");
338 
339                     DLOG(msg, "dentry", "dentry", DLOG_MSG, "dfs_dentry_unref(dentry)");
340                     dfs_dentry_unref(dentry);
341                     dentry = RT_NULL;
342                 }
343             }
344             DLOG(deactivate, "dentry");
345         }
346     }
347     else
348     {
349         DLOG(note, "dentry", "found dentry");
350     }
351     dfs_file_unlock();
352     return dentry;
353 }
354 
355 /**
356  * @brief Get the full path of a directory entry by combining mount point and relative path
357  *
358  * @param[in] dentry Pointer to the directory entry structure
359  *
360  * @return char* Newly allocated string containing full path, or NULL if allocation failed
361  *
362  * @note The caller is responsible for freeing the returned string using rt_free()
363  * @note Handles path concatenation with or without additional '/' separator
364  */
dfs_dentry_full_path(struct dfs_dentry * dentry)365 char* dfs_dentry_full_path(struct dfs_dentry* dentry)
366 {
367     char *path = NULL;
368 
369     if (dentry && dentry->mnt)
370     {
371         int mnt_len = strlen(dentry->mnt->fullpath);
372         int path_len = strlen(dentry->pathname);
373 
374         path = (char *) rt_malloc(mnt_len + path_len + 3);
375         if (path)
376         {
377             if (dentry->pathname[0] == '/' || dentry->mnt->fullpath[mnt_len - 1] == '/')
378             {
379                 rt_snprintf(path, mnt_len + path_len + 2, "%s%s", dentry->mnt->fullpath,
380                     dentry->pathname);
381             }
382             else
383             {
384                 rt_snprintf(path, mnt_len + path_len + 2, "%s/%s", dentry->mnt->fullpath,
385                     dentry->pathname);
386             }
387         }
388     }
389 
390     return path;
391 }
392 
393 /**
394  * @brief Get the parent directory path of a dentry by combining mount point and path
395  *
396  * @param[in] dentry Pointer to the directory entry structure
397  *
398  * @return char* Newly allocated string containing parent path, or NULL if allocation failed
399  *
400  * @note The caller is responsible for freeing the returned string using rt_free()
401  * @note Handles both absolute and relative paths correctly
402  * @note Returns mount point path if dentry is at root directory
403  */
dfs_dentry_pathname(struct dfs_dentry * dentry)404 char* dfs_dentry_pathname(struct dfs_dentry* dentry)
405 {
406     char *pathname = RT_NULL;
407     char *index = RT_NULL;
408 
409     index = strrchr(dentry->pathname, '/');
410     if (index)
411     {
412         int length = index - dentry->pathname;
413         int path_length = strlen(dentry->mnt->fullpath) + length + 3;
414 
415         pathname = (char*) rt_malloc(path_length);
416         if (pathname)
417         {
418             if (dentry->pathname[0] == '/')
419             {
420                 rt_snprintf(pathname, path_length - 1, "%s%.*s", dentry->mnt->fullpath,
421                     length, dentry->pathname);
422             }
423             else
424             {
425                 rt_snprintf(pathname, path_length - 1, "%s/%.*s", dentry->mnt->fullpath,
426                     length, dentry->pathname);
427             }
428         }
429     }
430     else
431     {
432         pathname = rt_strdup(dentry->mnt->fullpath);
433     }
434 
435     return pathname;
436 }
437 
438 /**
439  * @brief Calculate CRC32 checksum for the full path of a directory entry
440  *
441  * @param[in] dentry Pointer to the directory entry structure
442  *
443  * @return uint32_t CRC32 checksum value of the full path
444  *
445  * @note Uses standard CRC32 polynomial 0xEDB88320
446  */
dfs_dentry_full_path_crc32(struct dfs_dentry * dentry)447 uint32_t dfs_dentry_full_path_crc32(struct dfs_dentry* dentry)
448 {
449     uint32_t crc32 = 0xFFFFFFFF;
450     char *fullpath = dfs_dentry_full_path(dentry);
451     if (fullpath)
452     {
453         int i = 0;
454 
455         while(fullpath[i] != '\0')
456         {
457             for (uint8_t b = 1; b; b <<= 1)
458             {
459                 crc32 ^= (fullpath[i] & b) ? 1 : 0;
460                 crc32 = (crc32 & 1) ? crc32 >> 1 ^ 0xEDB88320 : crc32 >> 1;
461             }
462             i ++;
463         }
464         rt_free(fullpath);
465     }
466     return crc32;
467 }
468 
469 /**
470  * @brief Initialize the dentry hash table
471  *
472  * @return int Always returns 0 indicating success
473  *
474  * @note Initializes all hash buckets in the dentry hash table
475  */
dfs_dentry_init(void)476 int dfs_dentry_init(void)
477 {
478     int i = 0;
479 
480     for(i = 0; i < DFS_DENTRY_HASH_NR; i++)
481     {
482         rt_list_init(&hash_head.head[i]);
483     }
484 
485     return 0;
486 }
487 
488 /**
489  * @brief Dump all directory entries in the hash table for debugging
490  *
491  * @param[in] argc Number of command line arguments (unused)
492  * @param[in] argv Array of command line arguments (unused)
493  *
494  * @return int Always returns 0 indicating success
495  *
496  * @note Prints each dentry's full path, memory address and reference count
497  */
dfs_dentry_dump(int argc,char ** argv)498 int dfs_dentry_dump(int argc, char** argv)
499 {
500     int index = 0;
501     struct dfs_dentry *entry = RT_NULL;
502 
503     dfs_lock();
504     for (index = 0; index < DFS_DENTRY_HASH_NR; index ++)
505     {
506         rt_list_for_each_entry(entry, &hash_head.head[index], hashlist)
507         {
508             printf("dentry: %s%s @ %p, ref_count = %zd\n", entry->mnt->fullpath, entry->pathname, entry, (size_t)rt_atomic_load(&entry->ref_count));
509         }
510     }
511     dfs_unlock();
512 
513     return 0;
514 }
515 MSH_CMD_EXPORT_ALIAS(dfs_dentry_dump, dentry_dump, dump dentry in the system);
516