【问题标题】:Linux pseudo directory triggers the error "no such file or directory"Linux 伪目录触发错误“没有这样的文件或目录”
【发布时间】:2018-04-19 06:41:11
【问题描述】:

总结

我正在为 Linux 内核 4.13 创建一个伪文件系统,但我的目录无法使用 ls 列出。我不断收到消息:

ls: cannot access 'mountedfs/data': No such file or directory.

详情

super_block 和根dentry 结构的创建和注册进展顺利。我可以毫无问题地挂载文件系统,但我无法列出文件系统的内容。当我尝试时,我收到错误“没有这样的文件或目录”。

创建super_block 和根dentry 后,我调用womfs_create_files() 来填充树。这是模块的完整源代码。如您所见,我什至还没有为文件操作而烦恼。我仍然卡在 inode 操作上。

#include <linux/kernel.h>
#include <linux/fs.h>       /* libfs and most file-related headers. */
#include <linux/dcache.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pagemap.h>  /* PAGE_SIZE */
#include <linux/atomic.h>
#include <linux/time.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/magic.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "wombat.h"
/*
 *  Wombat directories are all basic.  They just contain stuff, you can't link to them,
 *  you can't delete them, and you can't modify them.  Anyone can list them, and
 *  they're owned by root.
 *
 *  The file inodes are more specialized: they have to be linked to information about the
 *  keys they represent and the operations that can be performed on those keys.
 *  The same key data will be used by several inodes, but each inode needs to understand
 *  its particular purpose.  (eg: "<key>/pub_key" lets you retrieve the public key;
 *  "<key>/sign" lets you sign data with the key.)
 *
 *  Open file nodes need state information for processing reads, writes, ioctl,
 *  etc..
 */

/*
 * Boilerplate stuff.
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CJ Holmes");

#define WOMFS_NAME "womfs"
#define WOMFS_MAGIC 0x00ff0019 // Lear Red

/*
 * Operations keygen file that uses the machine-specific KEK to create
 * a shrouded key for data encryption.
 */

int womfs_keygen_open(struct inode *inode, struct file *filp)
{
    return 0;
}

ssize_t womfs_keygen_read(struct file *filp, char __user *buf,
        size_t count, loff_t *offset)
{
  return 0;
}

int womfs_keygen_release(struct inode *inode, struct file *filp)
{
  return 0;
}

static struct file_operations keygen_ops = {
  .open = womfs_keygen_open,
  .release = womfs_keygen_release,
  .read = womfs_keygen_read,
};

/*
 *  Operations for encryption endpoints.  The key and semantics depend on
 *  the key info in i_private and calls to ioctl.
 */

int womfs_encrypt_open(struct inode *inode, struct file *filp)
{
  return 0;
}

ssize_t womfs_encrypt_read(struct file *filp, char *buf,
        size_t count, loff_t *offset)
{
   return 0;
}

ssize_t womfs_encrypt_write(struct file *filp, const char __user * buf, size_t len, loff_t *pos) {
    return 0;
}

int womfs_encrypt_release(struct inode *inode, struct file *filp)
{
   return 0;
}

long womfs_encrypt_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{
   return 0;
}

static struct file_operations encrypt_ops = {
  .open = womfs_encrypt_open,
  .release = womfs_encrypt_release,
  .read = womfs_encrypt_read,
  .write = womfs_encrypt_write,
  .unlocked_ioctl = womfs_encrypt_ioctl,
};

/*
 *  Stuff for building our FS structure.
 */

// Implementation borrowed from fs/stat.c:vsf_getattr_nosec()
int womfs_getattr(const struct path *path, struct kstat *stat, u32 request_mask,
    unsigned int query_flags) {
        struct inode *inode;

        inode = d_inode(path->dentry);
        printk(KERN_WARNING WOMFS_NAME " womfs_getattr(%pd4) --> %p\n", path->dentry, inode);

        memset(stat, 0, sizeof(*stat));
        stat->result_mask |= STATX_BASIC_STATS;
        if(inode != NULL)
            generic_fillattr(inode, stat); // fs/stat.c
        return 0;
}

const struct inode_operations womfs_dir_inode_operations = {
    .lookup     = simple_lookup,
    .getattr  = womfs_getattr,
};

const struct inode_operations womfs_inode_operations = {
    .getattr = womfs_getattr,
};

 static struct inode *womfs_make_inode(struct super_block *sb, struct wombat_key_info *key,
   kgid_t group, char *domain, int mode, struct file_operations *fops)
{
    struct inode *ret = new_inode(sb);
    struct timespec now = current_kernel_time();

    if (ret) {
        ret->i_mode = mode;
        ret->i_uid = KUIDT_INIT(0);
        ret->i_gid = group;
        ret->i_blocks = 0;
        ret->i_atime = now;
        ret->i_mtime = now;
        ret->i_ctime = now;

    if (mode & S_IFDIR) {
      ret->i_op = &womfs_dir_inode_operations;
      ret->i_fop = (fops == NULL) ? &simple_dir_operations : fops ;
      inc_nlink(ret);
    } else {
            ret->i_op = &womfs_inode_operations;
      ret->i_fop = fops;
    }

    ret->i_private = key;
  }

  return ret;
}


struct dentry *womfs_add_node_to_dir(struct dentry *parent, struct inode *node, const char *name)
{
  struct dentry *child;
  struct qstr qname;

  qname.name = name;
  qname.len = strlen(name);
  qname.hash = full_name_hash(NULL, name, qname.len);

  child = d_alloc(parent, &qname);
  if (child != NULL)
  {
        // d_instantiate(child, node);
    d_add(child, node);
        inode_inc_link_count(node);
  }

  return child;
}


static void womfs_create_files (struct super_block *sb, struct dentry *root)
{
  struct dentry *subdir;
  struct dentry *fentry;
  struct inode *node;
  kgid_t group;

  /*  This will eventually be a loop through all of the slots provided by the SNVS.
      For now, we can just add the KEK entry. */

  struct wombat_key_info *ki = kzalloc(sizeof(struct wombat_key_info), GFP_KERNEL);
  ki->type = wombat_key_kek;
  ki->slot = 0;
  strcpy(ki->name, "data");
  // leave group and domain blank, pending further implementation.
  group = KGIDT_INIT(0);

  /*  This can be cleaned up, perhaps by combining womfs_make_inode()
      and womfs_add_node_to_dir()
  */
  node = womfs_make_inode(sb, ki, group, NULL, S_IFDIR | 0555, NULL);
  if(node != NULL) {

    subdir = womfs_add_node_to_dir(root, node, ki->name);
    if (subdir != NULL) {
      printk(KERN_WARNING WOMFS_NAME " %pd4", subdir);
      switch(ki->type) {
        case wombat_key_kek:
          /* create the data/keygen file */
          node = womfs_make_inode(sb, ki, group, NULL, S_IFREG | 0444, &keygen_ops);
          if(node != NULL) {
            fentry = womfs_add_node_to_dir(subdir, node, "keygen");
            if(fentry != NULL)
              printk(KERN_WARNING WOMFS_NAME " %pd4", fentry);
            else
              iput(node);
          }
          /*  create the data/encrypt file */
          node = womfs_make_inode(sb, ki, group, NULL, S_IFREG | 0666, &encrypt_ops);
          if( node != NULL) {
            fentry = womfs_add_node_to_dir(subdir, node, "encrypt");
            if(fentry != NULL)
              printk(KERN_WARNING WOMFS_NAME " %pd4", fentry);
            else
              iput(node);
          }
          break;
        default:
          /* Show some error here. */
          break;
      }
    } else {
      iput(node);
    }
  }
}

/*
 * Superblock stuff.  This is all boilerplate to give the vfs something
 * that looks like a filesystem to work with.
 */

/*
 * Our superblock operations, both of which are generic kernel ops
 * that we don't have to write ourselves.
 */
static struct super_operations womfs_s_ops = {
    .statfs     = simple_statfs,
    .drop_inode = generic_delete_inode,
};

/*
 * "Fill" a superblock with mundane stuff.
 */
static int womfs_fill_super (struct super_block *sb, void *data, int silent)
{
  int retval = 0;
    struct inode *root = NULL;
    struct dentry *root_dentry = NULL;
  kgid_t gid = KGIDT_INIT(0);

  /*
   * Basic parameters.
   */
    sb->s_blocksize = PAGE_SIZE;
    sb->s_blocksize_bits = PAGE_SHIFT;
    sb->s_magic = WOMFS_MAGIC;
    sb->s_op = &womfs_s_ops;
  sb->s_time_gran = 1;

/*
 * We need to conjure up an inode to represent the root directory
 * of this filesystem.  Its operations all come from libfs, so we
 * don't have to mess with actually *doing* things inside this
 * directory.
 */

  root = womfs_make_inode(sb, NULL, gid, NULL, S_IFDIR | 0555, NULL);
    if (root != NULL) {

    // make the root directory entry.
    root_dentry = d_make_root(root);
    if (root_dentry != NULL) {
      sb->s_root = root_dentry;
      womfs_create_files (sb, root_dentry);
      printk(KERN_WARNING WOMFS_NAME " setup complete\n");
    } else {
      retval = -ENOMEM;
    }
  } else {
    retval = -ENOMEM;
  }

  if (retval != 0) {
    // clean up our inode and dirent
    if (root != NULL) {
      iput(root);
    }
    if (root_dentry != NULL) {
      dput(root_dentry);
    }
  }

    return retval;
}


/*
 * Stuff to pass in when registering the filesystem.
 */
struct dentry *womfs_mount(struct file_system_type *fst,
        int flags, const char *devname, void *data)
{
    return mount_nodev(fst, flags, data, womfs_fill_super);
}


static struct file_system_type womfs_type = {
    .owner    = THIS_MODULE,
    .name         = WOMFS_NAME,
    .mount    = womfs_mount,
    .kill_sb    = kill_litter_super,
};


/*
 * Get things set up.
 */
static int __init womfs_init(void)
{
    return register_filesystem(&womfs_type);
}

static void __exit womfs_exit(void)
{
    unregister_filesystem(&womfs_type);
}

module_init(womfs_init);
module_exit(womfs_exit);

当我加载模块并挂载我的文件系统时,我在 syslog 中看到以下行:

Apr 17 11:43:59 felix kernel: [ 7024.360872] womfs data
Apr 17 11:43:59 felix kernel: [ 7024.360873] womfs data/keygen
Apr 17 11:43:59 felix kernel: [ 7024.360874] womfs data/encrypt
Apr 17 11:43:59 felix kernel: [ 7024.360875] womfs setup complete

ls 命令显示我挂载的文件系统:

cholmes@felix:~/leardev/womfs$ ls -l
total 668
-rw-rw-r-- 1 cholmes cholmes    317 Apr 12 10:38 Makefile
-rw-rw-r-- 1 cholmes cholmes     46 Apr 17 11:28 modules.order
-rw-rw-r-- 1 cholmes cholmes      0 Apr 16 15:50 Module.symvers
-rw-rw-r-- 1 cholmes cholmes   6860 Apr 12 11:39 README.md
    ... etc ...
dr-xr-xr-x 2 root    root         0 Apr 17 11:28 mountedfs

但是列出mountedfs的内容是一场灾难:

cholmes@felix:~/leardev/wombat$ ls -l mountedfs
ls: cannot access 'mountedfs/data': No such file or directory
total 0
d????????? ? ? ? ?            ? data

似乎很明显我忘记了一些非常简单的事情。对来说还不是很明显。

更新 1

data 目录使用来自libfssimple_dir_inode_operationssimple_dir_operations 指针。

这两个常规文件的操作结构非常少,但我会在删除所有真正有趣的部分后发布它们;-)

strace ls -l mountedfs 命令显示:

... all the usual linking to run ls ...
open("mountedfs/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
getdents(3, /* 3 entries */, 32768)     = 72
lstat("mountedfs/data", 0xaa01a0)       = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
... the rest is all about printing the error message ...

更新 2

缺少的getattr() 钩子似乎不是问题所在。 VFS 提供了一个从 inode 复制属性信息的默认实现,因此您实际上不需要实现 getattr(),除非您必须处理同步到磁盘或类似问题。

我发现我的 getattr() 方法甚至没有被子目录调用。这是我实现的新部分:

// Implementation borrowed from fs/stat.c:vsf_getattr_nosec()
int womfs_getattr(const struct path *path, struct kstat *stat, u32 request_mask,
    unsigned int query_flags) {
        struct inode *inode;

        inode = d_inode(path->dentry);
        printk(KERN_WARNING WOMFS_NAME " womfs_getattr(%pd4) --> %p\n", path->dentry, inode);

        memset(stat, 0, sizeof(*stat));
        stat->result_mask |= STATX_BASIC_STATS;
        if(inode != NULL)
            generic_fillattr(inode, stat); // fs/stat.c
        return 0;
}

const struct inode_operations womfs_dir_inode_operations = {
    .lookup     = simple_lookup,
    .getattr  = womfs_getattr,
};

const struct inode_operations womfs_inode_operations = {
    .getattr = womfs_getattr,
};

我以预期的方式将这些操作添加到 inode 中。当我挂载我的文件系统时,我会按预期收到 printk() 消息:

Apr 18 14:19:21 felix kernel: [20777.116214] womfs /data
Apr 18 14:19:21 felix kernel: [20777.116216] womfs /data/keygen
Apr 18 14:19:21 felix kernel: [20777.116217] womfs /data/encrypt
Apr 18 14:19:21 felix kernel: [20777.116218] womfs setup complete

当我在文件系统上执行ls 时,我收到与以前相同的错误消息,以及一些系统日志消息告诉我womfs_getattr() 已被调用。

Apr 18 14:19:27 felix kernel: [20782.880473] womfs womfs_getattr(/) --> ffff9e957c902960
Apr 18 14:19:27 felix kernel: [20782.880696] womfs womfs_getattr(/) --> ffff9e957c902960

所以只有文件系统的根被统计了。似乎我的 inode 没有正确添加到 dentry 中。

在阅读了更多示例后,我将呼叫转至 d_add(),而改为使用 d_instantiate()。现在错误信息消失了,但文件系统显示为空:

cholmes@felix:~/leardev/wombat$ ls -l mountedfs/
total 0

现在我的系统日志显示 5 次访问:

 Apr 18 14:45:06 felix kernel: [22321.799115] womfs womfs_getattr(/) --> ffff9e95f95a04b0
 Apr 18 14:45:06 felix kernel: [22321.800553] womfs womfs_getattr(/) --> ffff9e95f95a04b0
 Apr 18 14:45:06 felix kernel: [22321.800554] womfs womfs_getattr(/) --> ffff9e95f95a04b0
 Apr 18 14:45:06 felix kernel: [22322.406112] womfs womfs_getattr(/) --> ffff9e95f95a04b0
 Apr 18 14:45:06 felix kernel: [22322.406341] womfs womfs_getattr(/) --> ffff9e95f95a04b0

strace 说:

open("mountedfs/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
getdents(3, /* 2 entries */, 32768)     = 48
getdents(3, /* 0 entries */, 32768)     = 0
close(3)                                = 0

此时我完全感到困惑,只是尝试半随机的事情来看看会发生什么。

【问题讨论】:

  • 你的操作是什么(带有函数指针的数组)?以目前的形式,这不容易回答,因为我们无法重现您的设置并得到相同的错误。您可以从strace ls -l 开始获取失败的系统调用的名称和确切的错误代码,然后检查 vfs 和 fs 中的相应操作。一些内核级跟踪(ftrace / trace-cmd)可能会有所帮助。使用 readdir 检查此示例:github.com/krinkinmu/aufs/blob/master/kern/dir.c const struct file_operations aufs_dir_ops = ... .iterate = aufs_readdir,geocities.ws/ravikiran_uvs/articles/rkfs.html for readdir 文件操作
  • ls 能够获取名称 data,但不能获取有关目录 inode。你有没有实现任何方法来填充struct kstat?像fuse_fillattr elixir.bootlin.com/linux/v3.18/source/fs/fuse/dir.c#L891 / fuse_do_getattr 间接引用自struct inode_operations ... .getattr = fuse_getattr,
  • 请为“$ ls -l mountedfs/ total 0”的情况添加新的strace日志
  • 在我使用 d_instantiate 的情况下,ls 没有任何错误。 strace的输出已经修复(我之前加错输出了)

标签: linux-kernel linux-device-driver


【解决方案1】:
wombat$ ls -l mountedfs
ls: cannot access 'mountedfs/data': No such file or directory
total 0
d????????? ? ? ? ?            ? data

ls 这样的输出显示子目录名称,但不显示文件模式、uid、gid 或日期/时间。这可能是当有一些正确的系统调用返回目录名称时;另一个系统调用在哪里获取有关 inode 的更多信息,但失败了。

您应该检查strace ls -l 输出以获取确切的系统调用名称和返回值,但我认为有readdir/getdents 可以读取data 并且stat 失败(stat64,fstat,lstat , ...)

在您的代码中,您为此片段中的 inode(目录类型)设置 fops:

   if (mode & S_IFDIR) {
     ret->i_op = &simple_dir_inode_operations;
     ret->i_fop = &simple_dir_operations;

但简单的文件/inode 操作没有定义任何方法来提供 stat 实现:

https://elixir.bootlin.com/linux/v4.4/source/fs/libfs.c#L189

const struct file_operations simple_dir_operations = {
    .open       = dcache_dir_open,
    .release    = dcache_dir_close,
    .llseek     = dcache_dir_lseek,
    .read       = generic_read_dir,
    .iterate    = dcache_readdir,
    .fsync      = noop_fsync,
};

const struct inode_operations simple_dir_inode_operations = {
    .lookup     = simple_lookup,
};

我认为,struct inode_operationsgetattr 字段用于实现stat。如果没有实现(使用 simple_inode_operations)stat 系统调用无法将任何有用的数据填充到struct kstat

getattr 的一些例子:fuse_getattr in fs/fuse/dir.c, http://pages.cpsc.ucalgary.ca/~crwth/programming/VFS/inodes.php, https://www.win.tue.nl/~aeb/linux/lk/lk-8.html ...

文档:https://www.kernel.org/doc/Documentation/filesystems/vfs.txt

struct inode_operations

    int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);

  getattr: called by the VFS to get attributes of a file. This method
    is called by stat(2) and related system calls.

ls来源https://github.com/coreutils/coreutils/blob/master/src/ls.c证明stat的无效返回码导致ls: cannot access消息

/* If true, the file listing format requires that stat be called on
   each file.  */
...
format_needs_stat =  ... || format == long_format || ...
...
 dereference = ... DEREF_NEVER;
...
gobble_file (char const *name, enum filetype type, ino_t inode,
...
if ( ... || format_needs_stat  || ...) {
...
        default: /* DEREF_NEVER */
          err = lstat (full_name, &f->stat);
          do_deref = false;
          break;
        }

      if (err != 0)
        {
          /* Failure to stat a command line argument leads to
             an exit status of 2.  For other files, stat failure
             provokes an exit status of 1.  */
          file_failure (command_line_arg,
                        _("cannot access %s"), full_name);

【讨论】:

  • 我不知道我以前是如何设法不找到 vfs.txt 文档并为其添加书签的——尤其是当它位于我的 linux 源代码树中时!编码完成后我会发布解决方案。
  • 未标记为答案,因为这似乎不是问题。查看问题的更新。还在努力……
  • @slashingweapon,为什么是-15?新的strace 输出和更新源在哪里?为什么不使用 ftrace / trace-cmd(甚至 lttng)跟踪器来获取有关调用内容和时间的更多信息?调用 getattr 并不神奇;它是ls 执行 stat 和 vfs 执行 stat。 (你也可以从 fuse 的例子开始……)
  • 我只是取消将此答案标记为解决方案,因为 getattr() 似乎不是问题。如果我证明是错误的,那么我会恢复它。看起来 inode 数据确实没有被 dentry 保留。 dentry 在那里并且有一个名字,但是 inode 数据丢失了。我已将 OP 中的代码更新为更完整。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
  • 1970-01-01
  • 2022-07-12
  • 2021-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多