项目场景:
系统:RT-Thread 5.0.2
硬件:STM32H743
问题描述
1.文件系统打开文件夹再关闭后,申请的内存没有释放
2.elm-fatFs文件系统重复操作同一个文件夹,如复制,会引起系统崩溃
原因分析:
DFS虚拟文件系统文件打开关闭逻辑错误,文件系统版本升级更新后,dfs_file结构体改变,使用dfs_file_open()打开文件夹,描述符引用计数使用dfs_vnode的ref_count,但在dfs_file_close()中判断了dfs_file的ref_count的值,导致文件夹打开后无法正常关闭;
文件类型、设备类型等文件打开时会设置和计算dfs_file的ref_count的值,但文件夹只设置了dfs_vnode的ref_count;
1.文件相关结构体
struct dfs_vnode
{
uint16_t type; /* Type (regular or socket) */
char *path; /* Name (below mount point) */
char *fullpath; /* Full path is hash key */
int ref_count; /* Descriptor reference count */
rt_list_t list; /* The node of vnode hash table */
struct dfs_filesystem *fs;
const struct dfs_file_ops *fops;
uint32_t flags; /* self flags, is dir etc.. */
size_t size; /* Size in bytes */
void *data; /* Specific file system data */
};
struct dfs_file
{
uint16_t magic; /* file descriptor magic number */
uint32_t flags; /* Descriptor flags */
int ref_count; /* Descriptor reference count */
off_t pos; /* Current file position */
struct dfs_vnode *vnode; /* file node struct */
void *data; /* Specific fd data */
};
2.文件打开函数
int dfs_file_open(struct dfs_file *fd, const char *path, int flags)
{
struct dfs_filesystem *fs;
char *fullpath;
int result;
struct dfs_vnode *vnode = NULL;
rt_list_t *hash_head;
/* parameter check */
if (fd == NULL)
return -EINVAL;
/* make sure we have an absolute path */
fullpath = dfs_normalize_path(NULL, path);
if (fullpath == NULL)
{
return -ENOMEM;
}
RT_LOG_D("open file:%s", fullpath);
dfs_fm_lock();
/* vnode find */
vnode = dfs_vnode_find(fullpath, &hash_head);
if (vnode)
{
vnode->ref_count++;
fd->pos = 0;
fd->vnode = vnode;
dfs_fm_unlock();
rt_free(fullpath); /* release path */
}
else
{
/* find filesystem */
fs = dfs_filesystem_lookup(fullpath);
if (fs == NULL)
{
dfs_fm_unlock();
rt_free(fullpath); /* release path */
return -ENOENT;
}
vnode = rt_calloc(1, sizeof(struct dfs_vnode));
if (!vnode)
{
dfs_fm_unlock();
rt_free(fullpath); /* release path */
return -ENOMEM;
}
vnode->ref_count = 1;
RT_LOG_D("open in filesystem:%s", fs->ops->name);
vnode->fs = fs; /* set file system */
vnode->fops = fs->ops->fops; /* set file ops */
/* initialize the fd item */
vnode->type = FT_REGULAR;
vnode->flags = 0;
if (!(fs->ops->flags & DFS_FS_FLAG_FULLPATH))
{
if (dfs_subdir(fs->path, fullpath) == NULL)
vnode->path = rt_strdup("/");
else
vnode->path = rt_strdup(dfs_subdir(fs->path, fullpath));
RT_LOG_D("Actual file path: %s", vnode->path);
}
else
{
vnode->path = fullpath;
}
vnode->fullpath = fullpath;
/* specific file system open routine */
if (vnode->fops->open == NULL)
{
dfs_fm_unlock();
/* clear fd */
if (vnode->path != vnode->fullpath)
{
rt_free(vnode->fullpath);
}
rt_free(vnode->path);
rt_free(vnode);
return -ENOSYS;
}
fd->pos = 0;
fd->vnode = vnode;
/* insert vnode to hash */
rt_list_insert_after(hash_head, &vnode->list);
}
fd->flags = flags;
if ((result = vnode->fops->open(fd)) < 0)
{
vnode->ref_count--;
if (vnode->ref_count == 0)
{
/* remove from hash */
rt_list_remove(&vnode->list);
/* clear fd */
if (vnode->path != vnode->fullpath)
{
rt_free(vnode->fullpath);
}
rt_free(vnode->path);
fd->vnode = NULL;
rt_free(vnode);
}
dfs_fm_unlock();
RT_LOG_D("%s open failed", fullpath);
return result;
}
fd->flags |= DFS_F_OPEN;
if (flags & O_DIRECTORY)
{
fd->vnode->type = FT_DIRECTORY;
fd->flags |= DFS_F_DIRECTORY;
}
dfs_fm_unlock();
RT_LOG_D("open successful");
return 0;
}
在文件打开函数中,会设置描述符引用计数dfs_vnode的ref_count,打开文件类型和设备类型会相应类型的打开处理中对dfs_file的ref_count进行计数
3.文件关闭函数
int dfs_file_close(struct dfs_file *fd)
{
struct dfs_vnode *vnode = NULL;
int result = 0;
if (fd == NULL)
{
return -ENXIO;
}
if (fd->ref_count == 1)
{
dfs_fm_lock();
vnode = fd->vnode;
if (vnode->ref_count <= 0)
{
dfs_fm_unlock();
return -ENXIO;
}
if (vnode->fops->close != NULL)
{
result = vnode->fops->close(fd);
}
if (vnode->ref_count == 1)
{
/* remove from hash */
rt_list_remove(&vnode->list);
fd->vnode = NULL;
if (vnode->path != vnode->fullpath)
{
rt_free(vnode->fullpath);
}
rt_free(vnode->path);
rt_free(vnode);
}
dfs_fm_unlock();
}
return result;
}
文件关闭函数判断了dfs_file的ref_count的值,会导致文件夹直接跳过不关闭,dfs_vnode也得不到清理,再次打开会对dfs_vnode的ref_count进行累加;
4. elm-fatFs文件打开错误
打开函数int dfs_elm_open(struct dfs_file *file)会对dfs_vnode的ref_count的值进行判断
if (file->vnode->ref_count > 1)
{
if (file->vnode->type == FT_DIRECTORY
&& !(file->flags & O_DIRECTORY))
{
return -ENOENT;
}
file->pos = 0;
return 0;
}
文件夹没有正常关闭,再次打开后,会在此处直接return 0,没有给file->data复制,后续的文件夹遍历查找、复制等操作会使用data的值,就会出现访问非法地址引起的崩溃;
解决方案:
方式1:修改DFS虚拟文件系统中文件关闭函数判断逻辑,使文件夹关闭能正常进入关闭释放资源处理;
方式2:增加文件夹打开中对dfs_file的ref_count的处理