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 * 2023-05-05 Bernard Implement file APIs in dfs v2.0
9 */
10
11 #include "errno.h"
12 #include "fcntl.h"
13
14 #include <dfs.h>
15
16 #include "dfs_file.h"
17 #include "dfs_dentry.h"
18 #include "dfs_fs.h"
19 #include "dfs_mnt.h"
20 #include "dfs_private.h"
21
22 #ifdef RT_USING_PAGECACHE
23 #include "dfs_pcache.h"
24 #endif
25
26 #define DBG_TAG "DFS.file"
27 #define DBG_LVL DBG_WARNING
28 #include <rtdbg.h>
29
30 #define MAX_RW_COUNT 0xfffc0000
31
32 /**
33 * @brief Get the length of the first path component
34 *
35 * This function calculates the length of the first path component in a given path string.
36 * For absolute paths (starting with '/'), it returns the length from the first '/' to
37 * the next '/' or end of string. For relative paths, it returns 0.
38 *
39 * @param[in] path The input path string to analyze
40 *
41 * @return int The length of the first path component, or 0 if not found
42 */
_first_path_len(const char * path)43 rt_inline int _first_path_len(const char *path)
44 {
45 int i = 0;
46
47 if (path[i] == '/')
48 {
49 i++;
50 while (path[i] != '\0' && path[i] != '/')
51 {
52 i++;
53 }
54 }
55
56 return i;
57 }
58
59 /**
60 * @brief Get the parent directory path from a given full path
61 *
62 * This function extracts the parent directory path from a given full path string.
63 * It handles paths ending with '/' correctly by skipping the trailing slash.
64 *
65 * @param[in] fullpath The input full path string to analyze
66 * @param[out] path Buffer to store the extracted parent directory path
67 *
68 * @return int Length of the parent directory path, or 0 if no parent found
69 */
_get_parent_path(const char * fullpath,char * path)70 static int _get_parent_path(const char *fullpath, char *path)
71 {
72 int len = 0;
73 char *str = 0;
74
75 char *full_path = rt_strdup(fullpath);
76 if (full_path == NULL)
77 {
78 rt_set_errno(ENOMEM);
79 return -1;
80 }
81
82 str = strrchr(full_path, '/');
83
84 /* skip last '/' */
85 if (str && *(str + 1) == '\0')
86 {
87 *str = '\0';
88 str = strrchr(full_path, '/');
89 }
90
91 if (str)
92 {
93 len = str - full_path;
94 if (len > 0)
95 {
96 rt_memcpy(path, full_path, len);
97 path[len] = '\0';
98 }
99 else if (len == 0) /* parent path is root path. */
100 {
101 path[0] = '/';
102 path[1] = '\0';
103 len = 1;
104 }
105 }
106
107 rt_free(full_path);
108 return len;
109 }
110
111 /**
112 * @brief Attempt to read the target of a symbolic link
113 *
114 * This function tries to read the contents of a symbolic link file. It first looks up
115 * the dentry for the given path, checks if it's a symlink, and then calls the filesystem's
116 * readlink operation if available.
117 *
118 * @param[in] path The path of the symbolic link to read
119 * @param[in] mnt The mount point containing the symbolic link
120 * @param[out] link Buffer to store the link target (contents of the symlink)
121 *
122 * @return int Length of the link target on success, -1 on failure
123 */
_try_readlink(const char * path,struct dfs_mnt * mnt,char * link)124 static int _try_readlink(const char *path, struct dfs_mnt *mnt, char *link)
125 {
126 int ret = -1;
127 struct dfs_dentry *dentry = dfs_dentry_lookup(mnt, path, 0);
128
129 if (dentry && dentry->vnode->type == FT_SYMLINK)
130 {
131 if (mnt->fs_ops->readlink)
132 {
133 if (dfs_is_mounted(mnt) == 0)
134 {
135 ret = mnt->fs_ops->readlink(dentry, link, DFS_PATH_MAX);
136 }
137 }
138 }
139 dfs_dentry_unref(dentry);
140
141 return ret;
142 }
143
144 /**
145 * @brief Normalize a path by combining base path and link path
146 *
147 * This function creates a temporary path by combining the base path and link path,
148 * then normalizes it using dfs_normalize_path(). It handles memory allocation and
149 * cleanup internally.
150 *
151 * @param[in] path The base path to combine with the link
152 * @param[in] path_len Length of the base path
153 * @param[in] link_fn The link path to combine with the base path
154 * @param[in] link_len Length of the link path
155 *
156 * @return char* Normalized path string on success, RT_NULL on failure
157 *
158 * @note The caller is responsible for freeing the returned path
159 */
_dfs_normalize_path(const char * path,int path_len,const char * link_fn,int link_len)160 static char *_dfs_normalize_path(const char *path, int path_len, const char *link_fn, int link_len)
161 {
162 char *tmp_path, *fp;
163
164 tmp_path = (char *)rt_malloc(path_len + link_len + 2);
165 if (!tmp_path)
166 {
167 return RT_NULL;
168 }
169
170 memcpy(tmp_path, path, path_len);
171 tmp_path[path_len] = '/';
172 memcpy(tmp_path + path_len + 1, link_fn, link_len);
173 tmp_path[path_len + 1 + link_len] = '\0';
174
175 fp = dfs_normalize_path(NULL, tmp_path);
176 rt_free(tmp_path);
177
178 return fp;
179 }
180
181 /**
182 * @brief Insert a link path into temporary path buffer
183 *
184 * This function inserts a symbolic link path into a temporary path buffer before
185 * the specified index position. It handles both relative and absolute paths.
186 *
187 * @param[in] link_fn The link path to insert
188 * @param[in] link_len Length of the link path
189 * @param[in,out] tmp_path The temporary path buffer to insert into
190 * @param[in,out] index Pointer to the insertion position index (updated after insertion)
191 *
192 * @return int 0 for relative path, 1 for absolute path, -1 on failure
193 * @note The index is modified to reflect the new insertion position
194 */
_insert_link_path(const char * link_fn,int link_len,char * tmp_path,int * index)195 static int _insert_link_path(const char *link_fn, int link_len, char *tmp_path, int *index)
196 {
197 int ret = -1;
198
199 if (link_fn[0] != '/')
200 {
201 if (link_len + 1 <= *index)
202 {
203 *index -= link_len;
204 rt_memcpy(tmp_path + *index, link_fn, link_len);
205 *index -= 1;
206 tmp_path[*index] = '/';
207 ret = 0;
208 }
209 }
210 else if (link_len <= *index)
211 {
212 *index -= link_len;
213 rt_memcpy(tmp_path + *index, link_fn, link_len);
214 ret = 1;
215 }
216
217 return ret;
218 }
219
220 /**
221 * @brief Verify read/write area parameters and limit count size
222 *
223 * This function checks the validity of read/write parameters and limits the count
224 * to a maximum value (MAX_RW_COUNT) to prevent overflow. It ensures the position
225 * and count values are within valid ranges.
226 *
227 * @param[in] file Pointer to the file structure (unused in current implementation)
228 * @param[in] ppos Pointer to the position offset (input/output)
229 * @param[in] count Requested read/write count (input)
230 *
231 * @return ssize_t The verified count value (limited to MAX_RW_COUNT) or negative error code:
232 * -EINVAL for invalid parameters
233 * -EOVERFLOW if position + count would overflow
234 *
235 * @note rw_verify_area doesn't like huge counts. We limit them to something that fits in "int"
236 * so that others won't have to do range checks all the time.
237 */
rw_verify_area(struct dfs_file * file,off_t * ppos,size_t count)238 ssize_t rw_verify_area(struct dfs_file *file, off_t *ppos, size_t count)
239 {
240 off_t pos;
241 ssize_t retval = -EINVAL;
242
243 if ((size_t)count < 0)
244 return retval;
245 pos = *ppos;
246 if (pos < 0)
247 {
248 if (count >= -pos) /* both values are in 0..LLONG_MAX */
249 return -EOVERFLOW;
250 }
251
252 return count > MAX_RW_COUNT ? MAX_RW_COUNT : count;
253 }
254
255 /**
256 * @brief Get the current file position
257 *
258 * This function retrieves the current file position (offset) from the file structure.
259 * For regular files, it acquires a mutex lock before accessing the position to ensure
260 * thread safety. For other file types, it directly returns the position without locking.
261 *
262 * @param[in] file Pointer to the file structure containing position information
263 *
264 * @return off_t Current file position, or 0 if file pointer is NULL
265 */
dfs_file_get_fpos(struct dfs_file * file)266 off_t dfs_file_get_fpos(struct dfs_file *file)
267 {
268 if (file)
269 {
270 if (file->vnode->type == FT_REGULAR)
271 {
272 rt_mutex_take(&file->pos_lock, RT_WAITING_FOREVER);
273 }
274 return file->fpos;
275 }
276
277 return 0;
278 }
279
280 /**
281 * @brief Set the current file position
282 *
283 * This function sets the file position (offset) in the file structure.
284 * It must be used as a pair of dfs_file_get_fpos(). For regular files, pos lock is acquared
285 * in dfs_file_get_fpos(), so it can be released directly after setting the position.
286 * Otherwise, pos lock should be acquired first to avoid releasing it without being acquired.
287 *
288 * @param[in] file Pointer to the file structure to modify
289 * @param[in] fpos The new file position to set
290 */
dfs_file_set_fpos(struct dfs_file * file,off_t fpos)291 void dfs_file_set_fpos(struct dfs_file *file, off_t fpos)
292 {
293 if (file)
294 {
295 if (file->vnode->type != FT_REGULAR)
296 {
297 rt_mutex_take(&file->pos_lock, RT_WAITING_FOREVER);
298 }
299 file->fpos = fpos;
300 rt_mutex_release(&file->pos_lock);
301 }
302 }
303
304 /**
305 * @brief Initialize a file structure
306 *
307 * @param[in,out] file Pointer to the file structure to be initialized
308 *
309 * @note This function must be called before using any file operations
310 * on a newly allocated file structure
311 */
dfs_file_init(struct dfs_file * file)312 void dfs_file_init(struct dfs_file *file)
313 {
314 if (file)
315 {
316 rt_memset(file, 0x00, sizeof(struct dfs_file));
317 file->magic = DFS_FD_MAGIC;
318 rt_mutex_init(&file->pos_lock, "fpos", RT_IPC_FLAG_PRIO);
319 rt_atomic_store(&(file->ref_count), 1);
320 }
321 }
322
323 /**
324 * @brief Deinitialize a file structure
325 *
326 * @param[in,out] file Pointer to the file structure to be deinitialized
327 */
dfs_file_deinit(struct dfs_file * file)328 void dfs_file_deinit(struct dfs_file *file)
329 {
330 if (file)
331 {
332 rt_mutex_detach(&file->pos_lock);
333 }
334 }
335
336 /**
337 * @brief Decrement reference count and release file resources when count reaches zero
338 *
339 * This function safely decrements the reference count of a file structure and releases
340 * associated resources (dentry or vnode) when the reference count drops to zero.
341 *
342 * @param[in,out] file Pointer to the file structure to be unreferenced
343 */
dfs_file_unref(struct dfs_file * file)344 static void dfs_file_unref(struct dfs_file *file)
345 {
346 rt_err_t ret = RT_EOK;
347
348 ret = dfs_file_lock();
349 if (ret == RT_EOK)
350 {
351 if (rt_atomic_load(&(file->ref_count)) == 1)
352 {
353 /* should release this file */
354 if (file->dentry)
355 {
356 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_unref(dentry(%s))", file->dentry->pathname);
357 dfs_dentry_unref(file->dentry);
358 file->dentry = RT_NULL;
359 }
360 else if (file->vnode)
361 {
362 if (file->vnode->ref_count > 1)
363 {
364 rt_atomic_sub(&(file->vnode->ref_count), 1);
365 }
366 else if (file->vnode->ref_count == 1)
367 {
368 rt_free(file->vnode);
369 file->vnode = RT_NULL;
370 }
371 }
372
373 LOG_I("release a file: %p", file);
374 }
375
376 dfs_file_unlock();
377 }
378 }
379
380 /**
381 * @brief Resolve the real path by resolving symbolic links and normalizing the path
382 *
383 * This function resolves the real path of a given file path by handling symbolic links
384 * and normalizing the path components. It supports two modes of operation:
385 * - DFS_REALPATH_EXCEPT_LAST: Resolve all path components except the last one
386 * - DFS_REALPATH_ONLY_LAST: Resolve only the last path component
387 * - DFS_REALPATH_EXCEPT_NONE: Resolve all path components
388 *
389 * @param[in,out] mnt Pointer to the mount point structure (updated if path changes)
390 * @param[in] fullpath The input path to resolve
391 * @param[in] mode Resolution mode (DFS_REALPATH_EXCEPT_LAST or DFS_REALPATH_ONLY_LAST)
392 *
393 * @return char* The resolved real path on success, RT_NULL on failure
394 */
dfs_file_realpath(struct dfs_mnt ** mnt,const char * fullpath,int mode)395 char *dfs_file_realpath(struct dfs_mnt **mnt, const char *fullpath, int mode)
396 {
397 int path_len = 0, index = 0;
398 char *path = RT_NULL, *link_fn, *tmp_path;
399 struct dfs_mnt *tmp_mnt;
400
401 if (*mnt && fullpath)
402 {
403 int len, link_len;
404
405 path = (char *)rt_malloc((DFS_PATH_MAX * 3) + 3); /* path + \0 + link_fn + \0 + tmp_path + \0 */
406 if (!path)
407 {
408 return RT_NULL;
409 }
410
411 link_fn = path + DFS_PATH_MAX + 1;
412 tmp_path = link_fn + (DFS_PATH_MAX + 1);
413
414 len = rt_strlen(fullpath);
415 if (len > DFS_PATH_MAX)
416 {
417 goto _ERR_RET;
418 }
419
420 index = (DFS_PATH_MAX - len);
421 rt_strcpy(tmp_path + index, fullpath);
422
423 if (mode == DFS_REALPATH_ONLY_LAST)
424 {
425 path_len = _get_parent_path(fullpath, path);
426 index += path_len;
427 }
428
429 while ((len = _first_path_len(tmp_path + index)) > 0)
430 {
431 if (len + path_len > DFS_PATH_MAX)
432 {
433 goto _ERR_RET;
434 }
435
436 rt_memcpy(path + path_len, tmp_path + index, len);
437 path[path_len + len] = '\0';
438 index += len;
439
440 tmp_mnt = dfs_mnt_lookup(path);
441 if (tmp_mnt == RT_NULL)
442 {
443 goto _ERR_RET;
444 }
445
446 *mnt = tmp_mnt;
447
448 /* the last should by mode process. */
449 if ((tmp_path[index] == '\0') && (mode == DFS_REALPATH_EXCEPT_LAST))
450 {
451 break;
452 }
453
454 /* Process symbolic links if found */
455 link_len = _try_readlink(path, *mnt, link_fn);
456 if (link_len > 0)
457 {
458 if (link_fn[0] == '/') /* Handle absolute path symlinks */
459 {
460 int ret = _insert_link_path(link_fn, link_len, tmp_path, &index);
461 if (ret < 0)
462 {
463 goto _ERR_RET;
464 }
465 path_len = 0;
466 }
467 else /* Handle relative path symlinks */
468 {
469 char *fp = _dfs_normalize_path(path, path_len, link_fn, link_len);
470 if (fp)
471 {
472 int pos = rt_strncmp(path, fp, path_len);
473 if (pos == 0)
474 {
475 int ret = _insert_link_path(fp + path_len, rt_strlen(fp + path_len), tmp_path, &index);
476 if (ret < 0)
477 {
478 rt_free(fp);
479 goto _ERR_RET;
480 }
481 }
482 else
483 {
484 int pos;
485
486 while(1)
487 {
488 while(path_len > 0 && path[path_len] != '/')
489 {
490 path_len--;
491 }
492
493 if (path_len > 0)
494 {
495 pos = rt_strncmp(path, fp, path_len);
496 }
497 else
498 {
499 pos = -1;
500 }
501
502 if (pos == 0 || path_len == 0)
503 {
504 int ret;
505
506 ret = _insert_link_path(fp + path_len, rt_strlen(fp + path_len), tmp_path, &index);
507 if (ret < 0)
508 {
509 rt_free(fp);
510 goto _ERR_RET;
511 }
512 else
513 {
514 break;
515 }
516 }
517 else
518 {
519 path_len--;
520 }
521 }
522 }
523 rt_free(fp);
524 }
525 }
526 }
527 else
528 {
529 path_len += len; /* Not a symlink, just advance path length */
530 }
531 }
532
533 return path;
534
535 _ERR_RET:
536 rt_free(path);
537 path = RT_NULL;
538 }
539
540 return path;
541 }
542
543 /**
544 * @brief Open a file which specified by path with specified oflags.
545 *
546 * This function opens or creates a file with given path and flags. It handles:
547 * - Path normalization and resolution (including symbolic links)
548 * - File creation when O_CREAT is specified
549 * - Permission checking
550 * - Directory vs regular file validation
551 * - Symbolic link following (unless O_NOFOLLOW is set)
552 *
553 * @param[in,out] file Pointer to file structure to be initialized
554 * @param[in] path the specified file path.
555 * @param[in] oflags the oflags for open operator. (O_RDONLY, O_WRONLY, O_CREAT, etc)
556 * @param[in] mode File permission mode (used when O_CREAT is specified)
557 *
558 * @return 0 on successful, -1 on failure:
559 * -ENOENT if file doesn't exist and O_CREAT not set
560 * -EEXIST if file exists and O_EXCL|O_CREAT set
561 * -EPERM if permission denied
562 * -ENOTDIR if path is not a directory when O_DIRECTORY set
563 * -EISDIR if path is directory when opening as regular file
564 */
dfs_file_open(struct dfs_file * file,const char * path,int oflags,mode_t mode)565 int dfs_file_open(struct dfs_file *file, const char *path, int oflags, mode_t mode)
566 {
567 int ret = -RT_ERROR;
568 char *fullpath = RT_NULL;
569 struct dfs_dentry *dentry = RT_NULL;
570 int fflags = dfs_fflags(oflags);
571
572 if (mode == 0)
573 {
574 mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); /* 0666 */
575 }
576
577 if (file && path)
578 {
579 fullpath = dfs_normalize_path(NULL, path);
580 if (fullpath)
581 {
582 struct dfs_mnt *mnt = RT_NULL;
583
584 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
585 mnt = dfs_mnt_lookup(fullpath);
586 if (mnt)
587 {
588 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST);
589 if (tmp)
590 {
591 rt_free(fullpath);
592 fullpath = tmp;
593 }
594
595 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_lookup(mnt, %s)", fullpath);
596 dentry = dfs_dentry_lookup(mnt, fullpath, oflags);
597 if (dentry && dentry->vnode->type == FT_SYMLINK)
598 {
599 /* it's a symbol link but not follow */
600 if (oflags & O_NOFOLLOW)
601 {
602 /* no follow symbol link */
603 dfs_dentry_unref(dentry);
604 dentry = RT_NULL;
605 }
606 else
607 {
608 struct dfs_dentry *target_dentry = RT_NULL;
609 char *path = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_ONLY_LAST);
610 if (path)
611 {
612 target_dentry = dfs_dentry_lookup(mnt, path, oflags);
613 rt_free(path);
614 }
615 dfs_dentry_unref(dentry);
616 dentry = target_dentry;
617 }
618 }
619
620 if (dentry)
621 {
622 if (oflags & O_DIRECTORY)
623 {
624 if (dentry->vnode->type != FT_DIRECTORY)
625 {
626 dfs_dentry_unref(dentry);
627 dentry = RT_NULL;
628 }
629 }
630 else if (dentry->vnode->type == FT_DIRECTORY)
631 {
632 if (fflags & (DFS_F_FWRITE))
633 {
634 dfs_dentry_unref(dentry);
635 dentry = RT_NULL;
636 }
637 else
638 {
639 oflags |= O_DIRECTORY;
640 }
641 }
642 }
643
644 if (oflags & O_CREAT)
645 {
646 if (dentry)
647 {
648 oflags &= ~O_CREAT;
649
650 if (oflags & O_EXCL)
651 {
652 oflags &= ~O_EXCL;
653 /* the dentry already exists */
654 dfs_dentry_unref(dentry);
655 ret = -EEXIST;
656 goto _ERR_RET;
657 }
658 }
659 else
660 {
661 /* create file/directory */
662 if (mnt->fs_ops->create_vnode)
663 {
664 struct dfs_vnode *vnode = RT_NULL;
665
666 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_create(%s)", fullpath);
667 dfs_file_lock();
668 dentry = dfs_dentry_create(mnt, fullpath);
669 if (dentry)
670 {
671 DLOG(msg, "dfs_file", mnt->fs_ops->name, DLOG_MSG, "fs_ops->create_vnode");
672
673 if (dfs_is_mounted(mnt) == 0)
674 {
675 vnode = mnt->fs_ops->create_vnode(dentry, oflags & O_DIRECTORY ? FT_DIRECTORY:FT_REGULAR, mode);
676 }
677
678 if (vnode)
679 {
680 /* set vnode */
681 dentry->vnode = vnode; /* the refcount of created vnode is 1. no need to reference */
682 dfs_dentry_insert(dentry);
683 }
684 else
685 {
686 DLOG(msg, mnt->fs_ops->name, "dfs_file", DLOG_MSG_RET, "create failed.");
687 dfs_dentry_unref(dentry);
688 dentry = RT_NULL;
689 }
690 }
691 dfs_file_unlock();
692 }
693 }
694 }
695
696 if (dentry)
697 {
698 rt_bool_t permission = RT_TRUE;
699 file->dentry = dentry;
700 file->vnode = dentry->vnode;
701 file->fops = dentry->mnt->fs_ops->default_fops;
702 file->flags = oflags;
703
704 /* check permission */
705 if (!(oflags & O_CREAT))
706 {
707 if (fflags & DFS_F_FWRITE)
708 {
709 if (!(file->vnode->mode & S_IWUSR))
710 {
711 permission = RT_FALSE;
712 }
713 }
714
715 if (fflags & DFS_F_FREAD)
716 {
717 if (!(file->vnode->mode & S_IRUSR))
718 {
719 permission = RT_FALSE;
720 }
721 }
722
723 if (oflags & O_EXEC)
724 {
725 if (!(file->vnode->mode & S_IXUSR))
726 {
727 permission = RT_FALSE;
728 }
729 }
730 }
731
732 if (permission && file->fops->open)
733 {
734 DLOG(msg, "dfs_file", mnt->fs_ops->name, DLOG_MSG, "fops->open(file)");
735
736 if (dfs_is_mounted(file->vnode->mnt) == 0)
737 {
738 dfs_file_lock();
739 ret = file->fops->open(file);
740 dfs_file_unlock();
741 }
742 else
743 {
744 ret = -EINVAL;
745 }
746
747 if (ret < 0)
748 {
749 LOG_I("open %s failed in file system: %s", path, dentry->mnt->fs_ops->name);
750 DLOG(msg, mnt->fs_ops->name, "dfs_file", DLOG_MSG_RET, "open failed.");
751 dfs_file_unref(file);
752 }
753 else
754 {
755 /* for char/block device */
756 if ((S_ISCHR(file->vnode->mode)) || (S_ISBLK(file->vnode->mode)))
757 {
758 file->fops = file->vnode->fops;
759 }
760 }
761 }
762 else
763 {
764 DLOG(msg, "dfs_file", mnt->fs_ops->name, DLOG_MSG, "no permission or fops->open");
765 dfs_file_unref(file);
766 ret = -EPERM;
767 }
768 }
769 else
770 {
771 LOG_I("lookup file:%s failed in file system", path);
772 ret = -ENOENT;
773 }
774 }
775 }
776
777 if (ret >= 0 && (oflags & O_TRUNC))
778 {
779 /* trunc file */
780 if (!(fflags & DFS_F_FWRITE) || file->vnode->type == FT_DIRECTORY)
781 {
782 /* truncate on read a only file or a directory */
783 DLOG(msg, "dfs_file", "dfs_file", DLOG_MSG, "dfs_file_unref(file), trunc on RDOnly or directory");
784 ret = -RT_ERROR;
785 }
786 else
787 {
788 if (file->fops->truncate)
789 {
790 DLOG(msg, "dfs_file", dentry->mnt->fs_ops->name, DLOG_MSG, "fops->truncate(file, 0)");
791
792 if (dfs_is_mounted(file->vnode->mnt) == 0)
793 {
794 #ifdef RT_USING_PAGECACHE
795 if (file->vnode->aspace)
796 {
797 dfs_aspace_clean(file->vnode->aspace);
798 }
799 #endif
800 ret = file->fops->truncate(file, 0);
801 }
802 else
803 {
804 ret = -EINVAL;
805 }
806 }
807 }
808
809 if (ret < 0)
810 {
811 dfs_file_unref(file);
812 }
813
814 file->flags &= ~O_TRUNC;
815 }
816 }
817
818 _ERR_RET:
819 if (fullpath != NULL)
820 {
821 rt_free(fullpath);
822 }
823 return ret;
824 }
825
826 /**
827 * @brief Close a file and release associated resources
828 *
829 * This function closes a file and performs necessary cleanup operations:
830 * - Flushes page cache if enabled (RT_USING_PAGECACHE)
831 * - Calls filesystem-specific close operation if available
832 * - Decrements reference count and releases resources when count reaches zero
833 *
834 * @param[in,out] file Pointer to the file structure to close
835 *
836 * @return int Operation result:
837 * - 0 on success
838 * - Negative error code on failure
839 */
dfs_file_close(struct dfs_file * file)840 int dfs_file_close(struct dfs_file *file)
841 {
842 int ret = -RT_ERROR;
843
844 if (file)
845 {
846 if (dfs_file_lock() == RT_EOK)
847 {
848 rt_atomic_t ref_count = rt_atomic_load(&(file->ref_count));
849
850 if (ref_count == 1 && file->fops && file->fops->close)
851 {
852 DLOG(msg, "dfs_file", file->dentry->mnt->fs_ops->name, DLOG_MSG, "fops->close(file)");
853 #ifdef RT_USING_PAGECACHE
854 if (file->vnode->aspace)
855 {
856 dfs_aspace_flush(file->vnode->aspace);
857 }
858 #endif
859 ret = file->fops->close(file);
860
861 if (ret == 0) /* close file sucessfully */
862 {
863 DLOG(msg, "dfs_file", "dfs_file", DLOG_MSG, "dfs_file_unref(file)");
864 dfs_file_unref(file);
865 }
866 else
867 {
868 LOG_W("close file:%s failed on low level file system", file->dentry->pathname);
869 }
870 }
871 else
872 {
873 DLOG(msg, "dfs_file", "dfs_file", DLOG_MSG, "dfs_file_unref(file)");
874 dfs_file_unref(file);
875 ret = 0;
876 }
877 dfs_file_unlock();
878 }
879 }
880
881 return ret;
882 }
883
884 /**
885 * @brief Read data from a file at specified offset
886 *
887 * @param[in] file Pointer to the file structure to read from
888 * @param[out] buf Buffer to store the read data
889 * @param[in] len Number of bytes to read
890 * @param[in] offset Offset in the file to start reading from
891 *
892 * @return ssize_t Number of bytes read on success, or negative error code:
893 * -EBADF if invalid file descriptor
894 * -EPERM if read permission denied
895 * -ENOSYS if read operation not supported
896 * -EINVAL if invalid parameters or not mounted
897 */
dfs_file_pread(struct dfs_file * file,void * buf,size_t len,off_t offset)898 ssize_t dfs_file_pread(struct dfs_file *file, void *buf, size_t len, off_t offset)
899 {
900 ssize_t ret = -EBADF;
901
902 if (file)
903 {
904 /* check whether read */
905 if (!(dfs_fflags(file->flags) & DFS_F_FREAD))
906 {
907 ret = -EPERM;
908 }
909 else if (!file->fops || !file->fops->read)
910 {
911 ret = -ENOSYS;
912 }
913 else if (file->vnode && file->vnode->type != FT_DIRECTORY)
914 {
915 off_t pos = offset;
916
917 ret = rw_verify_area(file, &pos, len);
918 if (ret > 0)
919 {
920 len = ret;
921
922 if (dfs_is_mounted(file->vnode->mnt) == 0)
923 {
924 #ifdef RT_USING_PAGECACHE
925 if (file->vnode->aspace && !(file->flags & O_DIRECT))
926 {
927 ret = dfs_aspace_read(file, buf, len, &pos);
928 }
929 else
930 #endif
931 {
932 ret = file->fops->read(file, buf, len, &pos);
933 }
934 }
935 else
936 {
937 ret = -EINVAL;
938 }
939 }
940 }
941 }
942
943 return ret;
944 }
945
946 /**
947 * @brief Read data from a file at current position
948 *
949 * @param[in] file Pointer to the file structure to read from
950 * @param[out] buf Buffer to store the read data
951 * @param[in] len Number of bytes to read
952 *
953 * @return ssize_t Number of bytes read on success, or negative error code:
954 * -EBADF if invalid file descriptor
955 * -EPERM if read permission denied
956 * -ENOSYS if read operation not supported
957 * -EINVAL if invalid parameters or not mounted
958 */
dfs_file_read(struct dfs_file * file,void * buf,size_t len)959 ssize_t dfs_file_read(struct dfs_file *file, void *buf, size_t len)
960 {
961 ssize_t ret = -EBADF;
962
963 if (file)
964 {
965 /* check whether read */
966 if (!(dfs_fflags(file->flags) & DFS_F_FREAD))
967 {
968 ret = -EPERM;
969 }
970 else if (!file->fops || !file->fops->read)
971 {
972 ret = -ENOSYS;
973 }
974 else if (file->vnode && file->vnode->type != FT_DIRECTORY)
975 {
976 /* fpos lock */
977 off_t pos = dfs_file_get_fpos(file);
978
979 ret = rw_verify_area(file, &pos, len);
980 if (ret > 0)
981 {
982 len = ret;
983
984 if (dfs_is_mounted(file->vnode->mnt) == 0)
985 {
986 #ifdef RT_USING_PAGECACHE
987 if (file->vnode->aspace && !(file->flags & O_DIRECT))
988 {
989 ret = dfs_aspace_read(file, buf, len, &pos);
990 }
991 else
992 #endif
993 {
994 ret = file->fops->read(file, buf, len, &pos);
995 }
996 }
997 else
998 {
999 ret = -EINVAL;
1000 }
1001 }
1002 /* fpos unlock */
1003 dfs_file_set_fpos(file, pos);
1004 }
1005 }
1006
1007 return ret;
1008 }
1009
1010 /**
1011 * @brief Write data to a file at specified offset
1012 *
1013 * @param[in] file Pointer to the file structure to write to
1014 * @param[in] buf Buffer containing data to write
1015 * @param[in] len Number of bytes to write
1016 * @param[in] offset Offset in the file to start writing from
1017 *
1018 * @return ssize_t Number of bytes written on success, or negative error code:
1019 * -EBADF if invalid file descriptor or bad write flags
1020 * -ENOSYS if write operation not supported
1021 * -EINVAL if invalid parameters or not mounted
1022 *
1023 * @note If O_SYNC flag is set, the data will be immediately flushed to storage device
1024 * after write operation.
1025 */
dfs_file_pwrite(struct dfs_file * file,const void * buf,size_t len,off_t offset)1026 ssize_t dfs_file_pwrite(struct dfs_file *file, const void *buf, size_t len, off_t offset)
1027 {
1028 ssize_t ret = -EBADF;
1029
1030 if (file)
1031 {
1032 if (!(dfs_fflags(file->flags) & DFS_F_FWRITE))
1033 {
1034 LOG_W("bad write flags.");
1035 ret = -EBADF;
1036 }
1037 else if (!file->fops || !file->fops->write)
1038 {
1039 LOG_W("no fops write.");
1040 ret = -ENOSYS;
1041 }
1042 else if (file->vnode && file->vnode->type != FT_DIRECTORY)
1043 {
1044 off_t pos = offset;
1045
1046 ret = rw_verify_area(file, &pos, len);
1047 if (ret > 0)
1048 {
1049 len = ret;
1050 DLOG(msg, "dfs_file", file->dentry->mnt->fs_ops->name, DLOG_MSG,
1051 "dfs_file_write(fd, buf, %d)", len);
1052
1053 if (dfs_is_mounted(file->vnode->mnt) == 0)
1054 {
1055 #ifdef RT_USING_PAGECACHE
1056 if (file->vnode->aspace && !(file->flags & O_DIRECT))
1057 {
1058 ret = dfs_aspace_write(file, buf, len, &pos);
1059 }
1060 else
1061 #endif
1062 {
1063 ret = file->fops->write(file, buf, len, &pos);
1064 }
1065
1066 if (file->flags & O_SYNC)
1067 {
1068 file->fops->flush(file);
1069 }
1070 }
1071 else
1072 {
1073 ret = -EINVAL;
1074 }
1075 }
1076 }
1077 }
1078
1079 return ret;
1080 }
1081
1082 /**
1083 * @brief Write data to a file at current position
1084 *
1085 * This function writes data to a file at the current position or at the end if O_APPEND flag is set.
1086 *
1087 * @param[in,out] file Pointer to the file structure to write to
1088 * @param[in] buf Buffer containing data to write
1089 * @param[in] len Number of bytes to write
1090 *
1091 * @return ssize_t Number of bytes written on success, or negative error code:
1092 * -EBADF if invalid file descriptor or bad write flags
1093 * -ENOSYS if write operation not supported
1094 * -EINVAL if invalid parameters or not mounted
1095 *
1096 * @note If O_SYNC flag is set, the data will be immediately flushed to storage device
1097 * @note In append mode (O_APPEND), data is always written at the end of file
1098 */
dfs_file_write(struct dfs_file * file,const void * buf,size_t len)1099 ssize_t dfs_file_write(struct dfs_file *file, const void *buf, size_t len)
1100 {
1101 ssize_t ret = -EBADF;
1102
1103 if (file)
1104 {
1105 if (!(dfs_fflags(file->flags) & DFS_F_FWRITE))
1106 {
1107 LOG_W("bad write flags.");
1108 ret = -EBADF;
1109 }
1110 else if (!file->fops || !file->fops->write)
1111 {
1112 LOG_W("no fops write.");
1113 ret = -ENOSYS;
1114 }
1115 else if (file->vnode && file->vnode->type != FT_DIRECTORY)
1116 {
1117 off_t pos;
1118
1119 if (!(file->flags & O_APPEND))
1120 {
1121 /* fpos lock */
1122 pos = dfs_file_get_fpos(file);
1123 }
1124 else
1125 {
1126 pos = file->vnode->size;
1127 }
1128
1129 ret = rw_verify_area(file, &pos, len);
1130 if (ret > 0)
1131 {
1132 len = ret;
1133 DLOG(msg, "dfs_file", file->dentry->mnt->fs_ops->name, DLOG_MSG,
1134 "dfs_file_write(fd, buf, %d)", len);
1135
1136 if (dfs_is_mounted(file->vnode->mnt) == 0)
1137 {
1138 #ifdef RT_USING_PAGECACHE
1139 if (file->vnode->aspace && !(file->flags & O_DIRECT))
1140 {
1141 ret = dfs_aspace_write(file, buf, len, &pos);
1142 }
1143 else
1144 #endif
1145 {
1146 ret = file->fops->write(file, buf, len, &pos);
1147 }
1148
1149 if (file->flags & O_SYNC)
1150 {
1151 file->fops->flush(file);
1152 }
1153 }
1154 else
1155 {
1156 ret = -EINVAL;
1157 }
1158 }
1159 if (!(file->flags & O_APPEND))
1160 {
1161 /* fpos unlock */
1162 dfs_file_set_fpos(file, pos);
1163 }
1164 }
1165 }
1166
1167 return ret;
1168 }
1169
1170 /**
1171 * @brief Generic file seek implementation
1172 *
1173 * This function calculates the new file position based on the specified offset and whence parameter.
1174 * It supports three seek modes:
1175 * - SEEK_SET: Set position relative to start of file
1176 * - SEEK_CUR: Set position relative to current position
1177 * - SEEK_END: Set position relative to end of file
1178 *
1179 * @param[in] file Pointer to the file structure containing current position
1180 * @param[in] offset Offset value to seek
1181 * @param[in] whence Seek mode (SEEK_SET/SEEK_CUR/SEEK_END)
1182 *
1183 * @return off_t The calculated new file position, or -EINVAL for invalid whence
1184 */
generic_dfs_lseek(struct dfs_file * file,off_t offset,int whence)1185 off_t generic_dfs_lseek(struct dfs_file *file, off_t offset, int whence)
1186 {
1187 off_t foffset;
1188
1189 if (whence == SEEK_SET)
1190 foffset = offset;
1191 else if (whence == SEEK_CUR)
1192 foffset = file->fpos + offset;
1193 else if (whence == SEEK_END)
1194 foffset = file->vnode->size + offset;
1195 else
1196 return -EINVAL;
1197
1198 return foffset;
1199 }
1200
1201 /**
1202 * @brief Change the file position indicator
1203 *
1204 * This function sets the file position indicator for the file referenced by the file descriptor
1205 * based on the offset and whence parameters.
1206 *
1207 * @param[in,out] file Pointer to the file structure (position will be modified)
1208 * @param[in] offset Number of bytes to offset from position
1209 * @param[in] whence Reference position (SEEK_SET/SEEK_CUR/SEEK_END)
1210 *
1211 * @return off_t New file position on success, or negative error code:
1212 * -EINVAL if invalid parameters or not mounted
1213 */
dfs_file_lseek(struct dfs_file * file,off_t offset,int wherece)1214 off_t dfs_file_lseek(struct dfs_file *file, off_t offset, int wherece)
1215 {
1216 off_t retval = -EINVAL;
1217
1218 if (file && file->fops->lseek)
1219 {
1220 if (dfs_is_mounted(file->vnode->mnt) == 0)
1221 {
1222 /* fpos lock */
1223 off_t pos = dfs_file_get_fpos(file);
1224 retval = file->fops->lseek(file, offset, wherece);
1225 if (retval >= 0)
1226 {
1227 pos = retval;
1228 }
1229 /* fpos unlock */
1230 dfs_file_set_fpos(file, pos);
1231 }
1232 }
1233
1234 return retval;
1235 }
1236
1237 /**
1238 * @brief Get file status information
1239 *
1240 * @param[in] path The file path to get status for
1241 * @param[out] buf Pointer to stat structure to store the status information
1242 *
1243 * @return int Operation result:
1244 * - 0 on success
1245 * -ENOENT if file not found
1246 * -ENOMEM if memory allocation failed
1247 * Other negative error codes from filesystem operations
1248 */
dfs_file_stat(const char * path,struct stat * buf)1249 int dfs_file_stat(const char *path, struct stat *buf)
1250 {
1251 int ret = -ENOENT;
1252 char *fullpath = RT_NULL;
1253 struct dfs_mnt *mnt = RT_NULL;
1254 struct dfs_dentry *dentry = RT_NULL;
1255
1256 fullpath = dfs_normalize_path(NULL, path);
1257 if (fullpath)
1258 {
1259 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
1260 mnt = dfs_mnt_lookup(fullpath);
1261 if (mnt)
1262 {
1263 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_NONE);
1264 if (tmp)
1265 {
1266 rt_free(fullpath);
1267 fullpath = tmp;
1268 }
1269
1270 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dentry = dfs_dentry_lookup(mnt, %s)", fullpath);
1271 dentry = dfs_dentry_lookup(mnt, fullpath, 0);
1272 if (dentry)
1273 {
1274 DLOG(msg, "dentry", "dfs_file", DLOG_MSG_RET, "return dentry");
1275 if (mnt->fs_ops->stat)
1276 {
1277 DLOG(msg, "dfs_file", mnt->fs_ops->name, DLOG_MSG, "fs_ops->stat(dentry, buf)");
1278
1279 if (dfs_is_mounted(mnt) == 0)
1280 {
1281 ret = mnt->fs_ops->stat(dentry, buf);
1282 }
1283 }
1284
1285 /* unref dentry */
1286 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_unref(dentry)");
1287 dfs_dentry_unref(dentry);
1288 dentry = RT_NULL;
1289 }
1290 }
1291
1292 rt_free(fullpath);
1293 fullpath = RT_NULL;
1294 }
1295 else
1296 {
1297 ret = -ENOMEM;
1298 }
1299
1300 return ret;
1301 }
1302
1303 /**
1304 * @brief Get file status information without following symbolic links
1305 *
1306 * @param[in] path The file path to get status for (does not follow symlinks)
1307 * @param[out] buf Pointer to stat structure to store the status information
1308 *
1309 * @return int Operation result:
1310 * - 0 on success
1311 * -ENOENT if file not found
1312 * -ENOMEM if memory allocation failed
1313 * Other negative error codes from filesystem operations
1314 *
1315 * @note Unlike dfs_file_stat(), this function does not follow symbolic links
1316 * @see dfs_file_stat()
1317 */
dfs_file_lstat(const char * path,struct stat * buf)1318 int dfs_file_lstat(const char *path, struct stat *buf)
1319 {
1320 int ret = -ENOENT;
1321 char *fullpath = RT_NULL;
1322 struct dfs_mnt *mnt = RT_NULL;
1323 struct dfs_dentry *dentry = RT_NULL;
1324
1325 fullpath = dfs_normalize_path(NULL, path);
1326 if (fullpath)
1327 {
1328 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
1329 mnt = dfs_mnt_lookup(fullpath);
1330 if (mnt)
1331 {
1332 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST);
1333 if (tmp)
1334 {
1335 rt_free(fullpath);
1336 fullpath = tmp;
1337 }
1338
1339 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dentry = dfs_dentry_lookup(mnt, %s)", fullpath);
1340 dentry = dfs_dentry_lookup(mnt, fullpath, 0);
1341 if (dentry)
1342 {
1343 DLOG(msg, "dentry", "dfs_file", DLOG_MSG_RET, "return dentry");
1344 if (mnt->fs_ops->stat)
1345 {
1346 DLOG(msg, "dfs_file", mnt->fs_ops->name, DLOG_MSG, "fs_ops->stat(dentry, buf)");
1347
1348 if (dfs_is_mounted(mnt) == 0)
1349 {
1350 ret = mnt->fs_ops->stat(dentry, buf);
1351 }
1352 }
1353
1354 /* unref dentry */
1355 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_unref(dentry)");
1356 dfs_dentry_unref(dentry);
1357 dentry = RT_NULL;
1358 }
1359 }
1360
1361 rt_free(fullpath);
1362 fullpath = RT_NULL;
1363 }
1364 else
1365 {
1366 ret = -ENOMEM;
1367 }
1368
1369 rt_set_errno(-ret);
1370
1371 return ret;
1372 }
1373
1374 /**
1375 * @brief Get file status information using file descriptor
1376 *
1377 * @param[in] file Pointer to the open file structure
1378 * @param[out] buf Pointer to stat structure to store status information
1379 *
1380 * @return int Operation result:
1381 * - 0 on success
1382 * -EBADF if invalid file descriptor
1383 * -ENOSYS if operation not supported
1384 *
1385 * @note Currently unimplemented (returns -ENOSYS)
1386 */
dfs_file_fstat(struct dfs_file * file,struct stat * buf)1387 int dfs_file_fstat(struct dfs_file *file, struct stat *buf)
1388 {
1389 size_t ret = -EBADF;
1390
1391 if (file)
1392 {
1393 if (file->fops && file->fops->ioctl)
1394 {
1395 /* ret = file->fops->fstat(file, buf); */
1396 }
1397 else
1398 {
1399 ret = -ENOSYS;
1400 }
1401 }
1402 else
1403 {
1404 ret = -EBADF;
1405 }
1406
1407 return ret;
1408 }
1409
1410 /**
1411 * @brief Set file attributes for the specified path
1412 *
1413 * This function sets file attributes (permissions, ownership, timestamps, etc.)
1414 * for the file specified by path.
1415 *
1416 * @param[in] path The file path to set attributes for
1417 * @param[in] attr Pointer to attribute structure containing new attributes
1418 *
1419 * @return int Operation result:
1420 * - 0 on success
1421 * -RT_ERROR if general error occurred
1422 * -ENOENT if file not found
1423 * Other negative error codes from filesystem operations
1424 *
1425 * @note The actual supported attributes depend on the underlying filesystem
1426 */
dfs_file_setattr(const char * path,struct dfs_attr * attr)1427 int dfs_file_setattr(const char *path, struct dfs_attr *attr)
1428 {
1429 int ret = -RT_ERROR;
1430 char *fullpath = RT_NULL;
1431 struct dfs_mnt *mnt = RT_NULL;
1432 struct dfs_dentry *dentry = RT_NULL;
1433
1434 fullpath = dfs_normalize_path(NULL, path);
1435 if (fullpath)
1436 {
1437 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
1438 mnt = dfs_mnt_lookup(fullpath);
1439 if (mnt)
1440 {
1441 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST);
1442 if (tmp)
1443 {
1444 rt_free(fullpath);
1445 fullpath = tmp;
1446 }
1447
1448 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dentry = dfs_dentry_lookup(mnt, %s)", fullpath);
1449 dentry = dfs_dentry_lookup(mnt, fullpath, 0);
1450 if (dentry)
1451 {
1452 DLOG(msg, "dentry", "dfs_file", DLOG_MSG_RET, "return dentry");
1453 if (mnt->fs_ops->setattr)
1454 {
1455 DLOG(msg, "dfs_file", mnt->fs_ops->name, DLOG_MSG, "fs_ops->setattr(dentry, attr)");
1456
1457 if (dfs_is_mounted(mnt) == 0)
1458 {
1459 ret = mnt->fs_ops->setattr(dentry, attr);
1460 }
1461 }
1462
1463 /* unref dentry */
1464 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_unref(dentry)");
1465 dfs_dentry_unref(dentry);
1466 dentry = RT_NULL;
1467 }
1468 }
1469
1470 rt_free(fullpath);
1471 fullpath = RT_NULL;
1472 }
1473
1474 return ret;
1475 }
1476
1477 /**
1478 * @brief Perform device-specific control operations
1479 *
1480 * This function performs device-specific control operations on an open file descriptor.
1481 * It is typically used for operations that cannot be expressed by regular file operations.
1482 *
1483 * @param[in] file Pointer to the file structure to perform ioctl on
1484 * @param[in] cmd Device-dependent request code
1485 * @param[in,out] args Pointer to optional argument buffer (input/output depends on cmd)
1486 *
1487 * @return int Operation result:
1488 * - 0 or positive value on success (meaning depends on cmd)
1489 * -EBADF if invalid file descriptor
1490 * -ENOSYS if ioctl operation not supported
1491 * -EINVAL if invalid parameters or not mounted
1492 *
1493 * @note The actual supported commands and their semantics depend on the underlying device driver
1494 */
dfs_file_ioctl(struct dfs_file * file,int cmd,void * args)1495 int dfs_file_ioctl(struct dfs_file *file, int cmd, void *args)
1496 {
1497 size_t ret = 0;
1498
1499 if (file)
1500 {
1501 if (file->fops && file->fops->ioctl)
1502 {
1503 if (dfs_is_mounted(file->vnode->mnt) == 0)
1504 {
1505 ret = file->fops->ioctl(file, cmd, args);
1506 }
1507 else
1508 {
1509 ret = -EINVAL;
1510 }
1511 }
1512 else
1513 {
1514 ret = -ENOSYS;
1515 }
1516 }
1517 else
1518 {
1519 ret = -EBADF;
1520 }
1521
1522 return ret;
1523 }
1524
1525 /**
1526 * @brief Perform file control operations
1527 *
1528 * This function performs various control operations on an open file descriptor.
1529 * It supports the following operations:
1530 * - F_DUPFD: Duplicate file descriptor
1531 * - F_GETFD: Get file descriptor flags
1532 * - F_SETFD: Set file descriptor flags
1533 * - F_GETFL: Get file status flags
1534 * - F_SETFL: Set file status flags
1535 * - F_GETLK/F_SETLK/F_SETLKW: File locking operations (unimplemented)
1536 * - F_DUPFD_CLOEXEC: Duplicate file descriptor with close-on-exec flag (if supported)
1537 *
1538 * @param[in] fd File descriptor to operate on
1539 * @param[in] cmd Control command (F_DUPFD/F_GETFD/F_SETFD/F_GETFL/F_SETFL/etc)
1540 * @param[in,out] arg Command-specific argument (input/output depends on cmd)
1541 *
1542 * @return int Operation result:
1543 * - For F_DUPFD: new file descriptor on success
1544 * - For F_GETFD/F_GETFL: current flags on success
1545 * - 0 on success for other commands
1546 * -EBADF if invalid file descriptor
1547 * -EINVAL if invalid command (F_DUPFD_CLOEXEC when not supported)
1548 * -EPERM for unsupported commands
1549 *
1550 * @note Not all commands may be supported by all filesystems
1551 * @note File locking operations (F_GETLK/F_SETLK/F_SETLKW) are currently unimplemented
1552 */
dfs_file_fcntl(int fd,int cmd,unsigned long arg)1553 int dfs_file_fcntl(int fd, int cmd, unsigned long arg)
1554 {
1555 int ret = 0;
1556 struct dfs_file *file;
1557
1558 file = fd_get(fd);
1559 if (file)
1560 {
1561 switch (cmd)
1562 {
1563 case F_DUPFD:
1564 ret = dfs_dup(fd, arg);
1565 break;
1566 case F_GETFD:
1567 ret = file->mode;
1568 break;
1569 case F_SETFD:
1570 file->mode = arg;
1571 break;
1572 case F_GETFL:
1573 ret = file->flags;
1574 break;
1575 case F_SETFL:
1576 {
1577 int flags = (int)(rt_base_t)arg;
1578 int mask =
1579 #ifdef O_ASYNC
1580 O_ASYNC |
1581 #endif
1582 #ifdef O_DIRECT
1583 O_DIRECT |
1584 #endif
1585 #ifdef O_NOATIME
1586 O_NOATIME |
1587 #endif
1588 O_APPEND | O_NONBLOCK;
1589
1590 flags &= mask;
1591 file->flags &= ~mask;
1592 file->flags |= flags;
1593 break;
1594 }
1595 case F_GETLK:
1596 break;
1597 case F_SETLK:
1598 case F_SETLKW:
1599 break;
1600 #ifdef RT_USING_MUSLLIBC
1601 case F_DUPFD_CLOEXEC:
1602 ret = -EINVAL;
1603 break;
1604 #endif
1605 default:
1606 ret = -EPERM;
1607 break;
1608 }
1609 }
1610 else
1611 {
1612 ret = -EBADF;
1613 }
1614
1615 return ret;
1616 }
1617
1618 /**
1619 * @brief Synchronize file data to storage device
1620 *
1621 * This function flushes all modified file data and metadata to the underlying storage device.
1622 * It ensures data integrity by:
1623 * - Flushing page cache if enabled (RT_USING_PAGECACHE)
1624 * - Calling filesystem-specific flush operation
1625 *
1626 * @param[in] file Pointer to the file structure to synchronize
1627 *
1628 * @return int Operation result:
1629 * - 0 on success
1630 * -EBADF if invalid file descriptor
1631 * -EINVAL if not mounted or invalid parameters
1632 *
1633 * @note This function provides stronger guarantees than regular writes
1634 * about data persistence on storage media
1635 */
dfs_file_fsync(struct dfs_file * file)1636 int dfs_file_fsync(struct dfs_file *file)
1637 {
1638 int ret = -EBADF;
1639
1640 if (file)
1641 {
1642 if (file->fops->flush)
1643 {
1644 if (dfs_is_mounted(file->vnode->mnt) == 0)
1645 {
1646 #ifdef RT_USING_PAGECACHE
1647 if (file->vnode->aspace)
1648 {
1649 dfs_aspace_flush(file->vnode->aspace);
1650 }
1651 #endif
1652 ret = file->fops->flush(file);
1653 }
1654 else
1655 {
1656 ret = -EINVAL;
1657 }
1658 }
1659 }
1660
1661 return ret;
1662 }
1663
1664 /**
1665 * @brief Delete a file or directory entry from the filesystem
1666 *
1667 * This function removes a filesystem entry (file or empty directory) specified by path.
1668 *
1669 * @param[in] path The filesystem path to be deleted
1670 *
1671 * @return int Operation result:
1672 * - 0 on success
1673 * -RT_ERROR if general error occurred
1674 * -ENOENT if file not found
1675 * -ENOMEM if memory allocation failed
1676 * -EBUSY if file is in use (mount point or has child mounts)
1677 * Other negative error codes from filesystem operations
1678 *
1679 * @note This function cannot remove non-empty directories
1680 * @note Mount points cannot be removed while mounted
1681 */
dfs_file_unlink(const char * path)1682 int dfs_file_unlink(const char *path)
1683 {
1684 int ret = -RT_ERROR;
1685 char *fullpath = RT_NULL;
1686 struct dfs_mnt *mnt = RT_NULL;
1687 struct dfs_dentry *dentry = RT_NULL;
1688
1689 fullpath = dfs_normalize_path(NULL, path);
1690 if (fullpath)
1691 {
1692 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
1693 mnt = dfs_mnt_lookup(fullpath);
1694 if (mnt)
1695 {
1696 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST);
1697 if (tmp)
1698 {
1699 rt_free(fullpath);
1700 fullpath = tmp;
1701 }
1702
1703 if (strcmp(mnt->fullpath, fullpath) != 0)
1704 {
1705 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_lookup(mnt, %s)", fullpath);
1706 dentry = dfs_dentry_lookup(mnt, fullpath, 0);
1707 if (dentry)
1708 {
1709 rt_bool_t has_child = RT_FALSE;
1710
1711 has_child = dfs_mnt_has_child_mnt(mnt, fullpath);
1712 #ifdef RT_USING_PAGECACHE
1713 if (dentry->vnode->aspace)
1714 {
1715 dfs_aspace_clean(dentry->vnode->aspace);
1716 }
1717 #endif
1718 dfs_file_lock();
1719
1720 if (has_child == RT_FALSE)
1721 {
1722 /* no child mnt point, unlink it */
1723 ret = -RT_ERROR;
1724
1725 if (mnt->fs_ops->unlink)
1726 {
1727 if (dfs_is_mounted(mnt) == 0)
1728 {
1729 ret = mnt->fs_ops->unlink(dentry);
1730 }
1731 }
1732 }
1733 else
1734 {
1735 ret = -EBUSY;
1736 }
1737 dfs_file_unlock();
1738
1739 /* release this dentry */
1740 dfs_dentry_unref(dentry);
1741 }
1742 else
1743 {
1744 /* no this entry */
1745 ret = -ENOENT;
1746 }
1747 }
1748 else
1749 {
1750 /* it's a mount point, failed for busy */
1751 ret = -EBUSY;
1752 }
1753 }
1754 else
1755 {
1756 ret = -ENOENT;
1757 }
1758
1759 /* release fullpath */
1760 rt_free(fullpath);
1761 }
1762 else
1763 {
1764 ret = -ENOMEM;
1765 }
1766
1767 return ret;
1768 }
1769
1770 /**
1771 * @brief Create a hard link between files
1772 *
1773 * This function creates a hard link named 'newname' which refers to the same file as 'oldname'.
1774 *
1775 * @param[in] oldname Path to the existing file to link from
1776 * @param[in] newname Path to the new link to be created
1777 *
1778 * @return int Operation result:
1779 * - 0 on success
1780 * -1 on general error
1781 * -EPERM if oldname is a directory
1782 * Other negative error codes from filesystem operations
1783 *
1784 * @note Both files must reside on the same filesystem
1785 * @note The function will fail if newname already exists
1786 */
dfs_file_link(const char * oldname,const char * newname)1787 int dfs_file_link(const char *oldname, const char *newname)
1788 {
1789 int ret = -1;
1790 struct stat stat;
1791 struct dfs_mnt *mnt = RT_NULL;
1792 char *old_fullpath, *new_fullpath;
1793
1794 if (dfs_file_isdir(oldname) == 0)
1795 {
1796 rt_set_errno(-EPERM);
1797 return ret;
1798 }
1799
1800 if (dfs_file_lstat(newname, &stat) >= 0)
1801 {
1802 return ret;
1803 }
1804
1805 old_fullpath = dfs_normalize_path(NULL, oldname);
1806 if (old_fullpath)
1807 {
1808 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", old_fullpath);
1809 mnt = dfs_mnt_lookup(old_fullpath);
1810 if (mnt == RT_NULL)
1811 {
1812 rt_free(old_fullpath);
1813 return -1;
1814 }
1815
1816 char *tmp = dfs_file_realpath(&mnt, old_fullpath, DFS_REALPATH_EXCEPT_LAST);
1817 if (tmp)
1818 {
1819 rt_free(old_fullpath);
1820 old_fullpath = tmp;
1821 }
1822 }
1823
1824 new_fullpath = dfs_normalize_path(NULL, newname);
1825 if (new_fullpath)
1826 {
1827 char *tmp = dfs_file_realpath(&mnt, new_fullpath, DFS_REALPATH_EXCEPT_LAST);
1828 if (tmp)
1829 {
1830 rt_free(new_fullpath);
1831 new_fullpath = tmp;
1832 }
1833 }
1834
1835 if (old_fullpath && new_fullpath)
1836 {
1837 struct dfs_dentry *old_dentry, *new_dentry;
1838
1839 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_lookup(mnt, %s)", old_fullpath);
1840 old_dentry = dfs_dentry_lookup(mnt, old_fullpath, 0);
1841 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_create(%s)", new_fullpath);
1842 new_dentry = dfs_dentry_create(mnt, new_fullpath);
1843
1844 if (old_dentry && new_dentry)
1845 {
1846 if (mnt->fs_ops->link)
1847 {
1848 if (dfs_is_mounted(mnt) == 0)
1849 {
1850 ret = mnt->fs_ops->link(old_dentry, new_dentry);
1851 }
1852 }
1853 }
1854
1855 dfs_dentry_unref(old_dentry);
1856 dfs_dentry_unref(new_dentry);
1857 }
1858
1859 if (old_fullpath)
1860 {
1861 rt_free(old_fullpath);
1862 }
1863
1864 if (new_fullpath)
1865 {
1866 rt_free(new_fullpath);
1867 }
1868
1869 return ret;
1870 }
1871
1872 /**
1873 * @brief Create a symbolic link named 'linkpath' containing the string 'target'
1874 *
1875 * This function creates a symbolic link which refers to the specified target path.
1876 * The linkpath should not exist before calling this function.
1877 *
1878 * @param[in] target The path string that the symbolic link will point to
1879 * @param[in] linkpath The path where the symbolic link will be created
1880 *
1881 * @return int Operation status:
1882 * - 0 on success
1883 * -ENOSYS if symlink operation not supported by filesystem
1884 * -ENOENT if parent directory doesn't exist
1885 * -EPERM if linkpath already exists
1886 * -EINVAL if invalid parameters
1887 * -RT_ERROR for general errors
1888 */
dfs_file_symlink(const char * target,const char * linkpath)1889 int dfs_file_symlink(const char *target, const char *linkpath)
1890 {
1891 int ret = -RT_ERROR;
1892 char *fullpath = RT_NULL, *parent = RT_NULL;
1893 struct dfs_mnt *mnt = RT_NULL;
1894 struct dfs_dentry *dentry = RT_NULL;
1895
1896 if (target && linkpath)
1897 {
1898 if (linkpath[0] != '/')
1899 {
1900 fullpath = dfs_normalize_path(NULL, linkpath);
1901 }
1902 else
1903 {
1904 fullpath = (char*)linkpath;
1905 }
1906
1907 /* linkpath should be not exist */
1908 if (dfs_file_access(fullpath, O_RDONLY) != 0)
1909 {
1910 char *index;
1911
1912 /* get parent path */
1913 index = strrchr(fullpath, '/');
1914 if (index)
1915 {
1916 int length = index - fullpath;
1917 if (length > 0)
1918 {
1919 parent = (char*) rt_malloc (length + 1);
1920 if (parent)
1921 {
1922 memcpy(parent, fullpath, length);
1923 parent[length] = '\0';
1924 }
1925 }
1926 else
1927 {
1928 parent = (char*) rt_malloc (1 + 1);
1929 if (parent)
1930 {
1931 parent[0] = '/';
1932 parent[1] = '\0';
1933 }
1934 }
1935 }
1936
1937 if (parent)
1938 {
1939 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
1940 mnt = dfs_mnt_lookup(parent);
1941 if (mnt)
1942 {
1943 char *tmp = dfs_file_realpath(&mnt, parent, DFS_REALPATH_EXCEPT_LAST);
1944 if (tmp)
1945 {
1946 rt_free(parent);
1947 parent = tmp;
1948 }
1949
1950 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_lookup(mnt, %s)", fullpath);
1951 dentry = dfs_dentry_lookup(mnt, parent, DFS_REALPATH_EXCEPT_LAST);
1952 if (dentry)
1953 {
1954 if (dentry->mnt->fs_ops->symlink)
1955 {
1956 char *path = dfs_normalize_path(parent, target);
1957 if (path)
1958 {
1959 ret = rt_strncmp(parent, path, strlen(parent));
1960 if (ret == 0)
1961 {
1962 tmp = path + strlen(parent);
1963 if (*tmp == '/')
1964 {
1965 tmp ++;
1966 }
1967 }
1968 else
1969 {
1970 tmp = path;
1971 }
1972
1973 if (dfs_is_mounted(mnt) == 0)
1974 {
1975 ret = mnt->fs_ops->symlink(dentry, tmp, index + 1);
1976 }
1977
1978 rt_free(path);
1979 }
1980 }
1981 else
1982 {
1983 ret = -ENOSYS;
1984 }
1985
1986 dfs_dentry_unref(dentry);
1987 }
1988 else
1989 {
1990 ret = -ENOENT;
1991 }
1992 }
1993 else
1994 {
1995 ret = -ENOENT;
1996 }
1997
1998 rt_free(parent);
1999 }
2000 }
2001 else
2002 {
2003 rt_set_errno(-EPERM);
2004 }
2005
2006 if (fullpath != linkpath)
2007 rt_free(fullpath);
2008 }
2009 else
2010 {
2011 ret = -EINVAL;
2012 }
2013
2014 return ret;
2015 }
2016
2017 /**
2018 * @brief Read the contents of a symbolic link
2019 *
2020 * This function reads the contents of the symbolic link specified by path into
2021 * the buffer provided.
2022 *
2023 * @param[in] path The path to the symbolic link to be read
2024 * @param[out] buf Buffer to store the link contents
2025 * @param[in] bufsize Size of the buffer in bytes
2026 *
2027 * @return int Number of bytes placed in buffer on success, or negative error code:
2028 * -ENOSYS if readlink operation not supported by filesystem
2029 * -ENOENT if symbolic link does not exist
2030 * -ENOMEM if memory allocation failed
2031 * -EINVAL if invalid parameters
2032 * -RT_ERROR for general errors
2033 */
dfs_file_readlink(const char * path,char * buf,int bufsize)2034 int dfs_file_readlink(const char *path, char *buf, int bufsize)
2035 {
2036 int ret = -RT_ERROR;
2037 char *fullpath = RT_NULL;
2038 struct dfs_mnt *mnt = RT_NULL;
2039 struct dfs_dentry *dentry = RT_NULL;
2040
2041 fullpath = dfs_normalize_path(NULL, path);
2042 if (fullpath)
2043 {
2044 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
2045 mnt = dfs_mnt_lookup(fullpath);
2046 if (mnt)
2047 {
2048 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST);
2049 if (tmp)
2050 {
2051 rt_free(fullpath);
2052 fullpath = tmp;
2053 }
2054
2055 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_lookup(mnt, %s)", fullpath);
2056 dentry = dfs_dentry_lookup(mnt, fullpath, 0);
2057 if (dentry)
2058 {
2059 if (mnt->fs_ops->readlink)
2060 {
2061 if (dfs_is_mounted(mnt) == 0)
2062 {
2063 ret = mnt->fs_ops->readlink(dentry, buf, bufsize);
2064 }
2065 }
2066 else
2067 {
2068 ret = -ENOSYS;
2069 }
2070
2071 /* release this dentry */
2072 dfs_dentry_unref(dentry);
2073 }
2074 else
2075 {
2076 /* no this entry */
2077 ret = -ENOENT;
2078 }
2079 }
2080 else
2081 {
2082 ret = -ENOENT;
2083 }
2084
2085 /* release fullpath */
2086 rt_free(fullpath);
2087 }
2088 else
2089 {
2090 ret = -ENOMEM;
2091 }
2092
2093 return ret;
2094 }
2095
2096 /**
2097 * @brief Rename a file/directory
2098 *
2099 * This function renames a filesystem entry from old_file to new_file.
2100 *
2101 * @param[in] old_file Path to the existing file/directory to be renamed
2102 * @param[in] new_file New path for the file/directory
2103 *
2104 * @return int Operation result:
2105 * - 0 on success
2106 * -1 on general error
2107 * -ENOMEM if memory allocation failed
2108 * -ENOSYS if rename operation not supported
2109 * -EINVAL if invalid parameters or not mounted
2110 *
2111 * @note Page cache will be cleaned if RT_USING_PAGECACHE is enabled
2112 */
dfs_file_rename(const char * old_file,const char * new_file)2113 int dfs_file_rename(const char *old_file, const char *new_file)
2114 {
2115 int ret = -1;
2116 struct dfs_mnt *mnt = RT_NULL;
2117 char *old_fullpath, *new_fullpath;
2118
2119 old_fullpath = dfs_normalize_path(NULL, old_file);
2120 if (old_fullpath)
2121 {
2122 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", old_fullpath);
2123 mnt = dfs_mnt_lookup(old_fullpath);
2124 if (mnt == RT_NULL)
2125 {
2126 rt_free(old_fullpath);
2127 return -1;
2128 }
2129
2130 char *tmp = dfs_file_realpath(&mnt, old_fullpath, DFS_REALPATH_EXCEPT_LAST);
2131 if (tmp)
2132 {
2133 rt_free(old_fullpath);
2134 old_fullpath = tmp;
2135 }
2136 }
2137
2138 new_fullpath = dfs_normalize_path(NULL, new_file);
2139 if (new_fullpath)
2140 {
2141 char *tmp = dfs_file_realpath(&mnt, new_fullpath, DFS_REALPATH_EXCEPT_LAST);
2142 if (tmp)
2143 {
2144 rt_free(new_fullpath);
2145 new_fullpath = tmp;
2146 }
2147 }
2148
2149 if (old_fullpath && new_fullpath)
2150 {
2151 struct dfs_dentry *old_dentry, *new_dentry;
2152
2153 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_lookup(mnt, %s)", old_fullpath);
2154 old_dentry = dfs_dentry_lookup(mnt, old_fullpath, 0);
2155 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_create(%s)", new_fullpath);
2156 new_dentry = dfs_dentry_create(mnt, new_fullpath);
2157
2158 if (old_dentry && new_dentry)
2159 {
2160 if (mnt->fs_ops->rename)
2161 {
2162 if (dfs_is_mounted(mnt) == 0)
2163 {
2164 #ifdef RT_USING_PAGECACHE
2165 if (old_dentry->vnode->aspace)
2166 {
2167 dfs_aspace_clean(old_dentry->vnode->aspace);
2168 }
2169 #endif
2170 ret = mnt->fs_ops->rename(old_dentry, new_dentry);
2171 }
2172 }
2173 }
2174
2175 dfs_dentry_unref(old_dentry);
2176 dfs_dentry_unref(new_dentry);
2177 }
2178
2179 if (old_fullpath)
2180 {
2181 rt_free(old_fullpath);
2182 }
2183
2184 if (new_fullpath)
2185 {
2186 rt_free(new_fullpath);
2187 }
2188
2189 return ret;
2190 }
2191
2192 /**
2193 * @brief Truncate or extend a file to the specified length
2194 *
2195 * This function changes the size of the file referenced by the file descriptor.
2196 * If the new size is smaller than current size, the file is truncated.
2197 * If larger, the file is extended and the extended area is filled with zeros.
2198 *
2199 * @param[in] file Pointer to the file structure to truncate
2200 * @param[in] length New length of the file in bytes
2201 *
2202 * @return int Operation result:
2203 * - 0 on success
2204 * -EBADF if invalid file descriptor
2205 * -ENOSYS if truncate operation not supported
2206 * -EINVAL if invalid parameters or not mounted
2207 *
2208 * @note If RT_USING_PAGECACHE is enabled, the page cache will be cleaned
2209 * before truncation to ensure data consistency
2210 */
dfs_file_ftruncate(struct dfs_file * file,off_t length)2211 int dfs_file_ftruncate(struct dfs_file *file, off_t length)
2212 {
2213 int ret = 0;
2214
2215 if (file)
2216 {
2217 if (file->fops->truncate)
2218 {
2219 if (dfs_is_mounted(file->vnode->mnt) == 0)
2220 {
2221 #ifdef RT_USING_PAGECACHE
2222 if (file->vnode->aspace)
2223 {
2224 dfs_aspace_clean(file->vnode->aspace);
2225 }
2226 #endif
2227 ret = file->fops->truncate(file, length);
2228 }
2229 else
2230 {
2231 ret = -EINVAL;
2232 }
2233 }
2234 else
2235 {
2236 ret = -ENOSYS;
2237 }
2238 }
2239 else
2240 {
2241 ret = -EBADF;
2242 }
2243
2244 return ret;
2245 }
2246
2247 /**
2248 * @brief Flush file buffers to storage device
2249 *
2250 * This function forces any buffered data to be written to the underlying storage device.
2251 *
2252 * @param[in,out] file Pointer to the file structure to flush (both input and output)
2253 *
2254 * @return int Operation result:
2255 * - 0 on success
2256 * -EBADF if invalid file descriptor
2257 * -ENOSYS if flush operation not supported
2258 * -EINVAL if invalid parameters or not mounted
2259 *
2260 * @note This function provides stronger guarantees than regular writes
2261 * about data persistence on storage media
2262 * @note If RT_USING_PAGECACHE is enabled, the page cache will be flushed first
2263 */
dfs_file_flush(struct dfs_file * file)2264 int dfs_file_flush(struct dfs_file *file)
2265 {
2266 int ret = 0;
2267
2268 if (file)
2269 {
2270 if (file->fops->flush)
2271 {
2272 if (dfs_is_mounted(file->vnode->mnt) == 0)
2273 {
2274 #ifdef RT_USING_PAGECACHE
2275 if (file->vnode->aspace)
2276 {
2277 dfs_aspace_flush(file->vnode->aspace);
2278 }
2279 #endif
2280 ret = file->fops->flush(file);
2281 }
2282 else
2283 {
2284 ret = -EINVAL;
2285 }
2286 }
2287 else
2288 {
2289 ret = -ENOSYS;
2290 }
2291 }
2292 else
2293 {
2294 ret = -EBADF;
2295 }
2296
2297 return ret;
2298 }
2299
2300 /**
2301 * @brief Read directory entries
2302 *
2303 * This function reads directory entries from the directory file descriptor into
2304 * the buffer provided. Each entry is stored as a struct dirent.
2305 *
2306 * @param[in] file Pointer to the directory file structure
2307 * @param[out] dirp Buffer to store directory entries
2308 * @param[in] nbytes Size of the buffer in bytes
2309 *
2310 * @return int Number of bytes read on success, or negative error code:
2311 * -EBADF if invalid file descriptor
2312 * -ENOTDIR if not a directory
2313 * -EINVAL if not mounted or invalid parameters
2314 * -RT_ERROR for general errors
2315 */
dfs_file_getdents(struct dfs_file * file,struct dirent * dirp,size_t nbytes)2316 int dfs_file_getdents(struct dfs_file *file, struct dirent *dirp, size_t nbytes)
2317 {
2318 int ret = -RT_ERROR;
2319
2320 if (file)
2321 {
2322 if (file->vnode && S_ISDIR(file->vnode->mode))
2323 {
2324 if (file->fops && file->fops->getdents)
2325 {
2326 DLOG(msg, "dfs_file", file->dentry->mnt->fs_ops->name, DLOG_MSG, "fops->getdents()");
2327
2328 if (dfs_is_mounted(file->vnode->mnt) == 0)
2329 {
2330 ret = file->fops->getdents(file, dirp, nbytes);
2331 }
2332 else
2333 {
2334 ret = -EINVAL;
2335 }
2336 }
2337 }
2338 }
2339 else
2340 {
2341 ret = -EBADF;
2342 }
2343
2344 return ret;
2345 }
2346
2347 /**
2348 * @brief Check if a path refers to a directory
2349 *
2350 * This function checks whether the specified path exists and is a directory.
2351 *
2352 * @param[in] path The filesystem path to check
2353 *
2354 * @return int Operation result:
2355 * - 0 if path exists and is a directory
2356 * -RT_ERROR if path doesn't exist or isn't a directory
2357 */
dfs_file_isdir(const char * path)2358 int dfs_file_isdir(const char *path)
2359 {
2360 int ret = -RT_ERROR;
2361 char *fullpath = RT_NULL;
2362 struct dfs_mnt *mnt = RT_NULL;
2363 struct dfs_dentry *dentry = RT_NULL;
2364
2365 fullpath = dfs_normalize_path(NULL, path);
2366 if (fullpath)
2367 {
2368 DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
2369 mnt = dfs_mnt_lookup(fullpath);
2370 if (mnt)
2371 {
2372 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_NONE);
2373 if (tmp)
2374 {
2375 rt_free(fullpath);
2376 fullpath = tmp;
2377 }
2378
2379 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dentry = dfs_dentry_lookup(mnt, %s)", fullpath);
2380 dentry = dfs_dentry_lookup(mnt, fullpath, 0);
2381 if (dentry)
2382 {
2383 DLOG(msg, "dentry", "dfs_file", DLOG_MSG_RET, "return dentry");
2384 if (mnt->fs_ops->stat)
2385 {
2386 struct stat stat = {0};
2387 DLOG(msg, "dfs_file", mnt->fs_ops->name, DLOG_MSG, "fs_ops->stat(dentry, buf)");
2388
2389 if (dfs_is_mounted(mnt) == 0)
2390 {
2391 ret = mnt->fs_ops->stat(dentry, &stat);
2392 }
2393
2394 if (ret == RT_EOK && S_ISDIR(stat.st_mode))
2395 {
2396 ret = RT_EOK;
2397 }
2398 else
2399 {
2400 ret = -RT_ERROR;
2401 }
2402 }
2403
2404 /* unref dentry */
2405 DLOG(msg, "dfs_file", "dentry", DLOG_MSG, "dfs_dentry_unref(dentry)");
2406 dfs_dentry_unref(dentry);
2407 dentry = RT_NULL;
2408 }
2409 }
2410
2411 rt_free(fullpath);
2412 fullpath = RT_NULL;
2413 }
2414
2415 return ret;
2416 }
2417
2418 /**
2419 * @brief Check file accessibility with specified mode
2420 *
2421 * This function checks whether the file specified by path can be accessed
2422 * with the given mode.
2423 *
2424 * @param[in] path The file path to check accessibility
2425 * @param[in] mode The access mode to check (read/write/execute permissions)
2426 *
2427 * @return int Access status:
2428 * - 0 if file is accessible with specified mode
2429 * - -1 if file is not accessible
2430 */
dfs_file_access(const char * path,mode_t mode)2431 int dfs_file_access(const char *path, mode_t mode)
2432 {
2433 int ret;
2434 struct dfs_file file;
2435
2436 dfs_file_init(&file);
2437
2438 if (dfs_file_open(&file, path, O_RDONLY, mode) >= 0)
2439 {
2440 ret = 0;
2441 dfs_file_close(&file);
2442 }
2443 else
2444 {
2445 ret = -1;
2446 }
2447
2448 dfs_file_deinit(&file);
2449
2450 return ret;
2451 }
2452
2453 #ifdef RT_USING_SMART
2454 /**
2455 * @brief Memory map a file or device into process address space
2456 *
2457 * This function maps a file or device into the calling process's address space.
2458 * It handles both regular files and device files differently:
2459 * - For regular files: uses standard mmap operation
2460 * - For device files: uses device-specific ioctl with RT_FIOMMAP2 command
2461 *
2462 * @param[in] file Pointer to the file structure to be mapped
2463 * @param[in,out] mmap2 Pointer to mmap arguments structure (both input and output)
2464 *
2465 * @return int Operation result:
2466 * - RT_EOK on success
2467 * - EINVAL if invalid parameters or operation not supported
2468 * - Other error codes from underlying mmap/ioctl operations
2469 *
2470 * @note For device files, the actual mapping behavior depends on the device driver
2471 */
dfs_file_mmap2(struct dfs_file * file,struct dfs_mmap2_args * mmap2)2472 int dfs_file_mmap2(struct dfs_file *file, struct dfs_mmap2_args *mmap2)
2473 {
2474 int ret = RT_EOK;
2475
2476 if (file && mmap2)
2477 {
2478 if (file->vnode->type == FT_REGULAR)
2479 {
2480 ret = dfs_file_mmap(file, mmap2);
2481 if (ret != 0)
2482 {
2483 ret = ret > 0 ? ret : -ret;
2484 rt_set_errno(ret);
2485 }
2486 }
2487 else if (file->vnode->type != FT_DEVICE || !file->vnode->fops->ioctl)
2488 {
2489 rt_set_errno(EINVAL);
2490 }
2491 else if (file->vnode->type == FT_DEVICE && file->vnode->fops->ioctl)
2492 {
2493 if (dfs_is_mounted(file->vnode->mnt) == 0)
2494 {
2495 ret = file->vnode->fops->ioctl(file, RT_FIOMMAP2, mmap2);
2496 }
2497 else
2498 {
2499 ret = EINVAL;
2500 }
2501
2502 if (ret != 0)
2503 {
2504 ret = ret > 0 ? ret : -ret;
2505 rt_set_errno(ret);
2506 }
2507 }
2508 }
2509
2510 return ret;
2511 }
2512 #endif
2513
2514 #ifdef RT_USING_FINSH
2515
2516 #define _COLOR_RED "\033[31m"
2517 #define _COLOR_GREEN "\033[32m"
2518 #define _COLOR_YELLOW "\033[33m"
2519 #define _COLOR_BLUE "\033[34m"
2520 #define _COLOR_CYAN "\033[36m"
2521 #define _COLOR_WHITE "\033[37m"
2522 #define _COLOR_NORMAL "\033[0m"
2523
2524 /**
2525 * @brief List directory contents with colored output
2526 *
2527 * This function lists all entries in the specified directory with colored output
2528 * that distinguishes different file types. It handles:
2529 * - Directories (blue)
2530 * - Symbolic links (cyan with target path)
2531 * - Executable files (green)
2532 * - Character devices (yellow)
2533 * - Regular files (default color)
2534 *
2535 * @param[in] pathname The directory path to list (NULL for current directory)
2536 */
ls(const char * pathname)2537 void ls(const char *pathname)
2538 {
2539 struct dirent dirent;
2540 struct stat stat;
2541 int length;
2542 char *fullpath, *path;
2543 struct dfs_file file;
2544
2545 if (pathname == NULL)
2546 {
2547 #ifdef DFS_USING_WORKDIR
2548 /* open current working directory */
2549 path = rt_strdup(working_directory);
2550 #else
2551 path = rt_strdup("/");
2552 #endif
2553 if (path == NULL)
2554 {
2555 return; /* out of memory */
2556 }
2557 }
2558 else
2559 {
2560 path = dfs_normalize_path(NULL, (char *)pathname);
2561 if (path == NULL)
2562 {
2563 return; /* out of memory */
2564 }
2565 }
2566
2567 dfs_file_init(&file);
2568
2569 /* list directory */
2570 DLOG(msg, "dfs", "dfs_file", DLOG_MSG, "dfs_file_open(%s, O_DIRECTORY, 0)", path);
2571 if (dfs_file_open(&file, path, O_DIRECTORY, 0) >= 0)
2572 {
2573 char *link_fn = (char *)rt_malloc(DFS_PATH_MAX);
2574 if (link_fn)
2575 {
2576 rt_kprintf("Directory %s:\n", path);
2577 do
2578 {
2579 memset(&dirent, 0, sizeof(struct dirent));
2580
2581 DLOG(group, "foreach_item");
2582 DLOG(msg, "dfs", "dfs_file", DLOG_MSG, "dfs_file_getdents(&dirent)");
2583 length = dfs_file_getdents(&file, &dirent, sizeof(struct dirent));
2584 if (length > 0)
2585 {
2586 DLOG(msg, "dfs_file", "dfs", DLOG_MSG_RET, "dirent.d_name=%s", dirent.d_name);
2587 memset(&stat, 0, sizeof(struct stat));
2588
2589 /* build full path for each file */
2590 fullpath = dfs_normalize_path(path, dirent.d_name);
2591 if (fullpath == NULL)
2592 break;
2593
2594 DLOG(msg, "dfs", "dfs_file", DLOG_MSG, "dfs_file_lstat(%s, &stat)", fullpath);
2595 if (dfs_file_lstat(fullpath, &stat) == 0)
2596 {
2597 if (S_ISDIR(stat.st_mode))
2598 {
2599 rt_kprintf(_COLOR_BLUE "%-20s" _COLOR_NORMAL, dirent.d_name);
2600 rt_kprintf("%-25s\n", "<DIR>");
2601 }
2602 else if (S_ISLNK(stat.st_mode))
2603 {
2604 int ret = 0;
2605
2606 rt_kprintf(_COLOR_CYAN "%-20s" _COLOR_NORMAL, dirent.d_name);
2607
2608 ret = dfs_file_readlink(fullpath, link_fn, DFS_PATH_MAX);
2609 if (ret > 0)
2610 {
2611 char *link_path = link_fn;
2612 struct dfs_mnt *mnt = RT_NULL;
2613
2614 mnt = dfs_mnt_lookup(fullpath);
2615 if (mnt)
2616 {
2617 char *tmp = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST);
2618 if (tmp)
2619 {
2620 char *index;
2621
2622 index = strrchr(fullpath, '/');
2623 if (index)
2624 {
2625 int length = index - fullpath;
2626 char *parent = (char*) rt_malloc (length + 1);
2627 if (parent)
2628 {
2629 rt_memcpy(parent, fullpath, length);
2630 parent[length] = '\0';
2631
2632 ret = rt_strncmp(parent, link_fn, length);
2633 if (ret == 0)
2634 {
2635 link_path = link_fn + length;
2636 if (*link_path == '/')
2637 {
2638 link_path ++;
2639 }
2640 }
2641 rt_free(parent);
2642 }
2643 }
2644 rt_free(tmp);
2645 }
2646 }
2647
2648 rt_kprintf("-> %s\n", link_path);
2649 }
2650 else
2651 {
2652 rt_kprintf(_COLOR_RED "-> link_error\n" _COLOR_NORMAL);
2653 }
2654 }
2655 else if (stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
2656 {
2657 rt_kprintf(_COLOR_GREEN "%-20s" _COLOR_NORMAL, dirent.d_name);
2658 rt_kprintf("%-25lu\n", (unsigned long)stat.st_size);
2659 }
2660 else if (S_ISCHR(stat.st_mode))
2661 {
2662 rt_kprintf(_COLOR_YELLOW "%-20s" _COLOR_NORMAL, dirent.d_name);
2663 rt_kprintf("%-25s\n", "<CHR>");
2664 }
2665 else
2666 {
2667 rt_kprintf("%-20s", dirent.d_name);
2668 rt_kprintf("%-25lu\n", (unsigned long)stat.st_size);
2669 }
2670 }
2671 else
2672 {
2673 rt_kprintf(_COLOR_RED "%-20s\n" _COLOR_NORMAL, dirent.d_name);
2674 }
2675
2676 rt_free(fullpath);
2677 }
2678 else
2679 {
2680 DLOG(msg, "dfs_file", "dfs", DLOG_MSG_RET, "return NULL");
2681 }
2682
2683 DLOG(group_end);
2684 } while (length > 0);
2685
2686 rt_free(link_fn);
2687 }
2688
2689 DLOG(msg, "dfs", "dfs_file", DLOG_MSG, "dfs_file_close()");
2690 dfs_file_close(&file);
2691 }
2692 else
2693 {
2694 rt_kprintf("No such directory\n");
2695 }
2696
2697 dfs_file_deinit(&file);
2698
2699 DLOG(msg, "dfs_file", "dfs", DLOG_MSG_RET, "return");
2700 rt_free(path);
2701 }
2702
2703 /**
2704 * @brief Display file contents to standard output
2705 *
2706 * This function reads and prints the contents of the specified file to the console.
2707 * It handles both text and binary files by reading in chunks and printing the output.
2708 *
2709 * @param[in] filename Path to the file to be displayed
2710 */
cat(const char * filename)2711 void cat(const char *filename)
2712 {
2713 int length = 0;
2714 char buffer[81];
2715 struct dfs_file file;
2716
2717 if (filename && dfs_file_isdir(filename) == 0)
2718 {
2719 rt_kprintf("cat: %s Is a directory\n", filename);
2720 return;
2721 }
2722
2723 dfs_file_init(&file);
2724
2725 DLOG(msg, "dfs", "dfs_file", DLOG_MSG, "dfs_file_open(%s, O_RDONLY, 0)", filename);
2726 if (dfs_file_open(&file, filename, O_RDONLY, 0) < 0)
2727 {
2728 rt_kprintf("Open %s failed\n", filename);
2729 dfs_file_deinit(&file);
2730 return;
2731 }
2732
2733 do
2734 {
2735 rt_memset(buffer, 0x0, sizeof(buffer));
2736 DLOG(msg, "dfs", "dfs_file", DLOG_MSG, "dfs_file_read(fd, buffer, %d)", sizeof(buffer) - 1);
2737 length = dfs_file_read(&file, (void *)buffer, sizeof(buffer) - 1);
2738 if (length > 0)
2739 {
2740 buffer[length] = '\0';
2741 rt_kprintf("%s", buffer);
2742 }
2743 } while (length > 0);
2744 rt_kprintf("\n");
2745
2746 DLOG(msg, "dfs", "dfs_file", DLOG_MSG, "dfs_file_close()");
2747 dfs_file_close(&file);
2748 dfs_file_deinit(&file);
2749 }
2750
2751 #define BUF_SZ 4096
2752 /**
2753 * @brief Copy file contents from source to destination
2754 *
2755 * This function copies the contents of a source file to a destination file.
2756 * It handles memory allocation, file operations, and error checking.
2757 *
2758 * @param[in] src Path to the source file to be copied
2759 * @param[in] dst Path to the destination file to be created/overwritten
2760 */
copyfile(const char * src,const char * dst)2761 static void copyfile(const char *src, const char *dst)
2762 {
2763 int ret;
2764 struct dfs_file src_file, dst_file;
2765 rt_uint8_t *block_ptr;
2766 rt_int32_t read_bytes;
2767
2768 block_ptr = (rt_uint8_t *)rt_malloc(BUF_SZ);
2769 if (block_ptr == NULL)
2770 {
2771 rt_kprintf("out of memory\n");
2772 return;
2773 }
2774
2775 dfs_file_init(&src_file);
2776
2777 ret = dfs_file_open(&src_file, src, O_RDONLY, 0);
2778 if (ret < 0)
2779 {
2780 dfs_file_deinit(&src_file);
2781 rt_free(block_ptr);
2782 rt_kprintf("Read %s failed\n", src);
2783 return;
2784 }
2785
2786 dfs_file_init(&dst_file);
2787
2788 ret = dfs_file_open(&dst_file, dst, O_WRONLY | O_CREAT | O_TRUNC, 0);
2789 if (ret < 0)
2790 {
2791 dfs_file_deinit(&dst_file);
2792 dfs_file_close(&src_file);
2793 dfs_file_deinit(&src_file);
2794 rt_free(block_ptr);
2795 rt_kprintf("Write %s failed\n", dst);
2796 return;
2797 }
2798
2799 do
2800 {
2801 read_bytes = dfs_file_read(&src_file, block_ptr, BUF_SZ);
2802 if (read_bytes > 0)
2803 {
2804 int length;
2805
2806 length = dfs_file_write(&dst_file, block_ptr, read_bytes);
2807 if (length != read_bytes)
2808 {
2809 /* write failed. */
2810 rt_kprintf("Write file data failed, errno=%d\n", length);
2811 break;
2812 }
2813 }
2814 } while (read_bytes > 0);
2815
2816 dfs_file_close(&dst_file);
2817 dfs_file_deinit(&dst_file);
2818 dfs_file_close(&src_file);
2819 dfs_file_deinit(&src_file);
2820 rt_free(block_ptr);
2821 }
2822
2823 extern int mkdir(const char *path, mode_t mode);
2824
2825 /**
2826 * @brief Recursively copy directory contents from source to destination
2827 *
2828 * This function recursively copies all files and subdirectories from the source
2829 * directory to the destination directory. It handles both files and directories
2830 * appropriately.
2831 *
2832 * @param[in] src Path to the source directory to be copied
2833 * @param[in] dst Path to the destination directory to be created
2834 */
copydir(const char * src,const char * dst)2835 static void copydir(const char *src, const char *dst)
2836 {
2837 struct dirent dirent;
2838 struct stat stat;
2839 int length;
2840 struct dfs_file file;
2841
2842 dfs_file_init(&file);
2843
2844 if (dfs_file_open(&file, src, O_DIRECTORY, 0) < 0)
2845 {
2846 rt_kprintf("open %s failed\n", src);
2847 dfs_file_deinit(&file);
2848 return ;
2849 }
2850
2851 do
2852 {
2853 rt_memset(&dirent, 0, sizeof(struct dirent));
2854
2855 length = dfs_file_getdents(&file, &dirent, sizeof(struct dirent));
2856 if (length > 0)
2857 {
2858 char *src_entry_full = NULL;
2859 char *dst_entry_full = NULL;
2860
2861 if (strcmp(dirent.d_name, "..") == 0 || strcmp(dirent.d_name, ".") == 0)
2862 continue;
2863
2864 /* build full path for each file */
2865 if ((src_entry_full = dfs_normalize_path(src, dirent.d_name)) == NULL)
2866 {
2867 rt_kprintf("out of memory!\n");
2868 break;
2869 }
2870 if ((dst_entry_full = dfs_normalize_path(dst, dirent.d_name)) == NULL)
2871 {
2872 rt_kprintf("out of memory!\n");
2873 rt_free(src_entry_full);
2874 break;
2875 }
2876
2877 rt_memset(&stat, 0, sizeof(struct stat));
2878 if (dfs_file_lstat(src_entry_full, &stat) != 0)
2879 {
2880 rt_kprintf("open file: %s failed\n", dirent.d_name);
2881 continue;
2882 }
2883
2884 if (S_ISDIR(stat.st_mode))
2885 {
2886 mkdir(dst_entry_full, 0);
2887 copydir(src_entry_full, dst_entry_full);
2888 }
2889 else
2890 {
2891 copyfile(src_entry_full, dst_entry_full);
2892 }
2893 rt_free(src_entry_full);
2894 rt_free(dst_entry_full);
2895 }
2896 }
2897 while (length > 0);
2898
2899 dfs_file_close(&file);
2900 dfs_file_deinit(&file);
2901 }
2902
2903 /**
2904 * @brief Extract the last component from a path string
2905 *
2906 * This function extracts the filename or last directory name from a given path.
2907 * It searches for the last '/' character and returns the substring after it.
2908 *
2909 * @param[in] path The input path string to process
2910 *
2911 * @return const char* Pointer to:
2912 * - The last path component if '/' is found
2913 * - The original path if no '/' is found
2914 * - NULL if input path is NULL
2915 */
_get_path_lastname(const char * path)2916 static const char *_get_path_lastname(const char *path)
2917 {
2918 char *ptr;
2919 if ((ptr = (char *)strrchr(path, '/')) == NULL)
2920 return path;
2921
2922 /* skip the '/' then return */
2923 return ++ptr;
2924 }
2925
2926 /**
2927 * @brief Copy files or directories from source to destination
2928 *
2929 * This function handles copying operations between files and directories with
2930 * various combinations of source and destination types. It supports:
2931 * - File to file copy
2932 * - File to directory copy (copies into directory with original filename)
2933 * - Directory to directory copy (recursive)
2934 * - Directory to new directory creation and copy
2935 *
2936 * @param[in] src Path to the source file/directory to copy
2937 * @param[in] dst Path to the destination file/directory
2938 */
copy(const char * src,const char * dst)2939 void copy(const char *src, const char *dst)
2940 {
2941 #define FLAG_SRC_TYPE 0x03
2942 #define FLAG_SRC_IS_DIR 0x01
2943 #define FLAG_SRC_IS_FILE 0x02
2944 #define FLAG_SRC_NON_EXSIT 0x00
2945
2946 #define FLAG_DST_TYPE 0x0C
2947 #define FLAG_DST_IS_DIR 0x04
2948 #define FLAG_DST_IS_FILE 0x08
2949 #define FLAG_DST_NON_EXSIT 0x00
2950
2951 struct stat stat;
2952 uint32_t flag = 0;
2953
2954 /* check the staus of src and dst */
2955 if (dfs_file_lstat(src, &stat) < 0)
2956 {
2957 rt_kprintf("copy failed, bad %s\n", src);
2958 return;
2959 }
2960 if (S_ISDIR(stat.st_mode))
2961 flag |= FLAG_SRC_IS_DIR;
2962 else
2963 flag |= FLAG_SRC_IS_FILE;
2964
2965 if (dfs_file_stat(dst, &stat) < 0)
2966 {
2967 flag |= FLAG_DST_NON_EXSIT;
2968 }
2969 else
2970 {
2971 if (S_ISDIR(stat.st_mode))
2972 flag |= FLAG_DST_IS_DIR;
2973 else
2974 flag |= FLAG_DST_IS_FILE;
2975 }
2976
2977 /* 2. check status */
2978 if ((flag & FLAG_SRC_IS_DIR) && (flag & FLAG_DST_IS_FILE))
2979 {
2980 rt_kprintf("cp faild, cp dir to file is not permitted!\n");
2981 return ;
2982 }
2983
2984 /* 3. do copy */
2985 if (flag & FLAG_SRC_IS_FILE)
2986 {
2987 if (flag & FLAG_DST_IS_DIR)
2988 {
2989 char *fdst;
2990 fdst = dfs_normalize_path(dst, _get_path_lastname(src));
2991 if (fdst == NULL)
2992 {
2993 rt_kprintf("out of memory\n");
2994 return;
2995 }
2996 copyfile(src, fdst);
2997 rt_free(fdst);
2998 }
2999 else
3000 {
3001 copyfile(src, dst);
3002 }
3003 }
3004 else /* flag & FLAG_SRC_IS_DIR */
3005 {
3006 if (flag & FLAG_DST_IS_DIR)
3007 {
3008 char *fdst;
3009 fdst = dfs_normalize_path(dst, _get_path_lastname(src));
3010 if (fdst == NULL)
3011 {
3012 rt_kprintf("out of memory\n");
3013 return;
3014 }
3015 mkdir(fdst, 0);
3016 copydir(src, fdst);
3017 rt_free(fdst);
3018 }
3019 else if ((flag & FLAG_DST_TYPE) == FLAG_DST_NON_EXSIT)
3020 {
3021 mkdir(dst, 0);
3022 copydir(src, dst);
3023 }
3024 else
3025 {
3026 copydir(src, dst);
3027 }
3028 }
3029 }
3030 FINSH_FUNCTION_EXPORT(copy, copy file or dir)
3031
3032 #endif