dfs_dentry.c 15 KB

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