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