fat文件系统查找、删除、创建目录

简介: fat文件系统查找、删除、创建目录

Fat文件系统查找过程

查找一个文件是通过文件名查找的,对于fat文件系统,可以直接从vfat_lookup函数看

主体流程如下:

vfat_lookup
    vfat_find
      fat_search_long
        while (1)
          fat_get_entry(inode, &cpos, &bh, &de)
          fat_name_match(sbi, name, name_len, bufname, len)
    fat_build_inode
    inode = new_inode(sb);
      inode->i_ino = iunique(sb, MSDOS_ROOT_INO);
      fat_fill_inode(inode, de)

vfat_lookup调了fat_search_long查找文件,其中qname->name就是文件名,比如说我们打开file.txt,就会先查找文件,这里传入的参数qname->name就是文件名,打印出来就是file.txt。

static int vfat_find(struct inode *dir, const struct qstr *qname,
         struct fat_slot_info *sinfo)
{
  unsignedint len = vfat_striptail_len(qname);
  if (len == 0)
    return -ENOENT;
  return fat_search_long(dir, qname->name, len, sinfo);
}

fat_search_long里面有个死循环,里面先调用fat_get_entry(inode, &cpos, &bh, &de)获取目录项,也就是de变量,msdos_dir_entry这个结构体,这个就是fdt表,也是fat文件系统的目录项。

先判断文件名是否等于DELETED_FLAG,就是0xe5,如果是,代码这个是删除文件,直接continue获取下一个目录项继续解析。一般短文件名,通过fat_name_match(sbi, name, name_len, bufname, len)函数,匹配,匹配上的话,说明找到该文件,直接跳转到found,

int fat_search_long(struct inode *inode, const unsigned char *name,
        int name_len, struct fat_slot_info *sinfo)
{
  struct super_block *sb = inode->i_sb;
  struct msdos_sb_info *sbi = MSDOS_SB(sb);
  struct buffer_head *bh = NULL;
  struct msdos_dir_entry *de;
  unsignedchar nr_slots;
  wchar_t *unicode = NULL;
  unsignedchar bufname[FAT_MAX_SHORT_SIZE];
  loff_t cpos = 0;
  int err, len;
  err = -ENOENT;
  while (1) {
    if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
      goto end_of_dir;
parse_record:
    nr_slots = 0;
    if (de->name[0] == DELETED_FLAG)
      continue;
    if (de->attr != ATTR_EXT && (de->attr & ATTR_VOLUME))
      continue;
    if (de->attr != ATTR_EXT && IS_FREE(de->name))
      continue;
    if (de->attr == ATTR_EXT) {
      int status = fat_parse_long(inode, &cpos, &bh, &de,
                &unicode, &nr_slots);
      if (status < 0) {
        err = status;
        goto end_of_dir;
      } elseif (status == PARSE_INVALID)
        continue;
      elseif (status == PARSE_NOT_LONGNAME)
        goto parse_record;
      elseif (status == PARSE_EOF)
        goto end_of_dir;
    }
    /* Never prepend '.' to hidden files here.
     * That is done only for msdos mounts (and only when
     * 'dotsOK=yes'); if we are executing here, it is in the
     * context of a vfat mount.
     */
    len = fat_parse_short(sb, de, bufname, 0);
    if (len == 0)
      continue;
    /* Compare shortname */
    if (fat_name_match(sbi, name, name_len, bufname, len))
      goto found;
    if (nr_slots) {
      void *longname = unicode + FAT_MAX_UNI_CHARS;
      int size = PATH_MAX - FAT_MAX_UNI_SIZE;
      /* Compare longname */
      len = fat_uni_to_x8(sb, unicode, longname, size);
      if (fat_name_match(sbi, name, name_len, longname, len))
        goto found;
    }
  }
found:
  nr_slots++; /* include the de */
  sinfo->slot_off = cpos - nr_slots * sizeof(*de);
  sinfo->nr_slots = nr_slots;
  sinfo->de = de;
  sinfo->bh = bh;
  sinfo->i_pos = fat_make_i_pos(sb, sinfo->bh, sinfo->de);
  err = 0;
end_of_dir:
  if (unicode)
    __putname(unicode);
  return err;
}

找到文件后,会调用fat_build_inode建立索引节点信息,因为索引节点不是一直存在的,在我们嵌入式场景中,内存通常比较小,内存回收的时候会释放掉之前建立过的索引节点,所以这里大部分情况下都是走new_inode(sb)分配新的inode,然后调iunique(sb, MSDOS_ROOT_INO)查找一个没使用过的编号填充到inode->i_ino,通过都是从1开始往后分配,inode->i_ino就像是身份证一样的信息,区别每个inode,这个我们调试一些文件系统问题的时候通常会用到它。

struct inode *fat_build_inode(struct super_block *sb,
      struct msdos_dir_entry *de, loff_t i_pos)
{
  struct inode *inode;
  int err;
  fat_lock_build_inode(MSDOS_SB(sb));
  inode = fat_iget(sb, i_pos);
  if (inode)
    goto out;
  inode = new_inode(sb);
  if (!inode) {
    inode = ERR_PTR(-ENOMEM);
    goto out;
  }
  inode->i_ino = iunique(sb, MSDOS_ROOT_INO);
  inode->i_version = 1;
  err = fat_fill_inode(inode, de);
  if (err) {
    iput(inode);
    inode = ERR_PTR(err);
    goto out;
  }
  fat_attach(inode, i_pos);
  insert_inode_hash(inode);
out:
  fat_unlock_build_inode(MSDOS_SB(sb));
  return inode;
}

然后调用fat_fill_inode填充inode信息,可以看到第一个判断,如果这个目录项是目录,这里填充的一些信息,如:

MSDOS_I(inode)->i_start = fat_get_start(sbi, de); // 就是获取簇号

MSDOS_I(inode)->i_logstart = MSDOS_I(inode)->i_start;

其实就是簇号,可以参考创建目录时的处理,参考函数fat_alloc_new_dir的实现。

set_nlink的作用是设置i_nlink,它代表的是该目录的子目录数量,fat_subdirs就是计算子目录数量,一个目录至少有2个子目录,当前目录和上级目录。我的代码是4.14版本的,这里还加了个判断fat_validate_dir判断该目录是否为有效目录,据我所知,内核4.4版本是没有该函数判断的,这会导致一些问题不能及时发现。

int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de)
{
  struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
  int error;
  MSDOS_I(inode)->i_pos = 0;
  inode->i_uid = sbi->options.fs_uid;
  inode->i_gid = sbi->options.fs_gid;
  inode->i_version++;
  inode->i_generation = get_seconds();
  if ((de->attr & ATTR_DIR) && !IS_FREE(de->name)) {
    inode->i_generation &= ~1;
    inode->i_mode = fat_make_mode(sbi, de->attr, S_IRWXUGO);
    inode->i_op = sbi->dir_ops;
    inode->i_fop = &fat_dir_operations;
    MSDOS_I(inode)->i_start = fat_get_start(sbi, de);
    MSDOS_I(inode)->i_logstart = MSDOS_I(inode)->i_start;
    error = fat_calc_dir_size(inode);
    if (error < 0)
      return error;
    MSDOS_I(inode)->mmu_private = inode->i_size;
    set_nlink(inode, fat_subdirs(inode));
    error = fat_validate_dir(inode);
    if (error < 0)
      return error;
  } else { /* not a directory */
    inode->i_generation |= 1;
    inode->i_mode = fat_make_mode(sbi, de->attr,
      ((sbi->options.showexec && !is_exec(de->name + 8))
       ? S_IRUGO|S_IWUGO : S_IRWXUGO));
    MSDOS_I(inode)->i_start = fat_get_start(sbi, de);
    MSDOS_I(inode)->i_logstart = MSDOS_I(inode)->i_start;
    inode->i_size = le32_to_cpu(de->size);
    inode->i_op = &fat_file_inode_operations;
    inode->i_fop = &fat_file_operations;
    inode->i_mapping->a_ops = &fat_aops;
    MSDOS_I(inode)->mmu_private = inode->i_size;
  }
  if (de->attr & ATTR_SYS) {
    if (sbi->options.sys_immutable)
      inode->i_flags |= S_IMMUTABLE;
  }
  fat_save_attrs(inode, de->attr);
  inode->i_blocks = ((inode->i_size + (sbi->cluster_size - 1))
         & ~((loff_t)sbi->cluster_size - 1)) >> 9;
  fat_time_fat2unix(sbi, &inode->i_mtime, de->time, de->date, 0);
  if (sbi->options.isvfat) {
    fat_time_fat2unix(sbi, &inode->i_ctime, de->ctime,
          de->cdate, de->ctime_cs);
    fat_time_fat2unix(sbi, &inode->i_atime, 0, de->adate, 0);
  } else
    inode->i_ctime = inode->i_atime = inode->i_mtime;
  return0;
}

关于fat文件系统查找就介绍这几个函数,主要是要理解几个关键的结构体、inode,目录项这几个重要的东西在查找中的作用。

fat文件系统删除

对于文件系统删除,有2个系统调用,入口函数一个是do_unlinkat,一个是do_rmdir,它们大体流程一致,这里主要有2件事,一个是释放目录项,给fdt表文件名首字节设置成0xe5,那么查找的时候先判断de->name[0]如果是DELETED_FLAG就跳过了,不记得可以往上看回查找过程,一个是释放簇(在fat表写为0),数据区不变。

do_unlinkat
    vfs_unlink
        dir->i_op->unlink
            vfat_unlink
                vfat_find
                fat_remove_entries
                    while (nr_slots && de >= (struct msdos_dir_entry *)bh->b_data) {
                        de->name[0] = DELETED_FLAG; // 删除标志0xe5
                        de--;
                        nr_slots--;
                    }
    dput
        dentry_kill
            __dentry_kill
                iput
                    iput_final
                        evict
                            op->evict_inode
                                fat_evict_inode
                                    // 这里进入到fat文件系统层,往下就是释放簇

fat文件系统创建目录

fat_alloc_new_dir就是创建目录的函数,可以看到,创建的时候先在fat表找到一个未使用的簇号,分配出来,然后fat_clus_to_blknr函数通过该簇号计算出扇区编号,然后用sb_getblk对应的扇区读到内存上,紧接着就是填充fdt表,de[0]就是当前目录,de[1]就是上级目录,还有就是填充一些其他信息,如修改时间,簇号等,fat_zeroed_cluster就是把该簇其他扇区数据清0。

memcpy(de[0].name, MSDOS_DOT, MSDOS_NAME);
memcpy(de[1].name, MSDOS_DOTDOT, MSDOS_NAME);


int fat_alloc_new_dir(struct inode *dir, struct timespec *ts)
{
  struct super_block *sb = dir->i_sb;
  struct msdos_sb_info *sbi = MSDOS_SB(sb);
  struct buffer_head *bhs[MAX_BUF_PER_PAGE];
  struct msdos_dir_entry *de;
  sector_t blknr;
  __le16 date, time;
  u8 time_cs;
  int err, cluster;
  err = fat_alloc_clusters(dir, &cluster, 1);
  if (err)
    goto error;
  blknr = fat_clus_to_blknr(sbi, cluster);
  bhs[0] = sb_getblk(sb, blknr);
  if (!bhs[0]) {
    err = -ENOMEM;
    goto error_free;
  }
  fat_time_unix2fat(sbi, ts, &time, &date, &time_cs);
  de = (struct msdos_dir_entry *)bhs[0]->b_data;
  /* filling the new directory slots ("." and ".." entries) */
  memcpy(de[0].name, MSDOS_DOT, MSDOS_NAME);
  memcpy(de[1].name, MSDOS_DOTDOT, MSDOS_NAME);
  de->attr = de[1].attr = ATTR_DIR;
  de[0].lcase = de[1].lcase = 0;
  de[0].time = de[1].time = time;
  de[0].date = de[1].date = date;
  if (sbi->options.isvfat) {
    /* extra timestamps */
    de[0].ctime = de[1].ctime = time;
    de[0].ctime_cs = de[1].ctime_cs = time_cs;
    de[0].adate = de[0].cdate = de[1].adate = de[1].cdate = date;
  } else {
    de[0].ctime = de[1].ctime = 0;
    de[0].ctime_cs = de[1].ctime_cs = 0;
    de[0].adate = de[0].cdate = de[1].adate = de[1].cdate = 0;
  }
  fat_set_start(&de[0], cluster);
  fat_set_start(&de[1], MSDOS_I(dir)->i_logstart);
  de[0].size = de[1].size = 0;
  memset(de + 2, 0, sb->s_blocksize - 2 * sizeof(*de));
  set_buffer_uptodate(bhs[0]);
  mark_buffer_dirty_inode(bhs[0], dir);
  err = fat_zeroed_cluster(dir, blknr, 1, bhs, MAX_BUF_PER_PAGE);
  if (err)
    goto error_free;
  return cluster;
error_free:
  fat_free_clusters(dir, cluster);
error:
  return err;
}

号主:一枚机械专业本科生,经历了转行,从外包逆袭到芯片原厂的Linux驱动开发工程师,深入操作系统的世界,贯彻终身学习、终身成长的理念。平时喜欢折腾,寒冬之下,抱团取暖,期待你来一起探讨技术、搞自媒体副业,程序员接单和投资理财。【对了,不定期送闲置开发板、书籍、键盘等等】。

如果你想了解我的转行经验,欢迎找我交流~gongzhong号【哆哆jarvis】

一起不断探索自我、走出迷茫、找到热爱,希望和你成为朋友,一起成长~

相关文章
|
Linux Shell
umount卸载根目录,xfs_repair修复根分区
umount卸载根目录,xfs_repair修复根分区
1113 0
|
Linux
LINUX删除指定子目录下所有指定文件名
LINUX删除指定子目录下所有指定文件名
92 0
|
IDE Unix Linux
linux中的tar打包、压缩多个文件、磁盘查看和分区类、du查看文件和目录占用的磁盘空间、df查看磁盘空间使用情况、lsblk查看设备挂载情况、fdisk分区、mount/umount挂载/卸载、设置开机自动挂载
tar [选项] XXX.tar.gz 将要打包进去的内容 (功能描述:打包目录,压缩后的 文件格式.tar.gz)du 目录/文件 (功能描述:显示目录下每个子目录的磁盘使用情况)du: disk usage 磁盘占用情况。详细的请看我之前发的博客。linux常用命令下。linux常用命令中。linux常用命令上。...............对于Linux用户来讲,不论有几个分区,分别分给哪一个目录使用,它总归就是一个根 目录、一个独立且唯一的文件结构。Linux中每个分区都是用来组成整个文件系统的一部分,它在用一种叫做“挂载”的处理 方法,它整个文件系统中包含了一整套的文件和目录,并将一
290 1
linux中的tar打包、压缩多个文件、磁盘查看和分区类、du查看文件和目录占用的磁盘空间、df查看磁盘空间使用情况、lsblk查看设备挂载情况、fdisk分区、mount/umount挂载/卸载、设置开机自动挂载
|
Linux
10.17 Linux mkfs命令详解格式化分区(为分区写入文件系统)
分区完成后,如果不格式化写入文件系统,则是不能正常使用的。这时就需要使用 mkfs 命令对硬盘分区进行格式化。
357 0
10.17 Linux mkfs命令详解格式化分区(为分区写入文件系统)