Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/linux/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Linux 如何在功能测试中模拟INotify故障?_Linux_Testing_Filesystems_Functional Testing_Inotify - Fatal编程技术网

Linux 如何在功能测试中模拟INotify故障?

Linux 如何在功能测试中模拟INotify故障?,linux,testing,filesystems,functional-testing,inotify,Linux,Testing,Filesystems,Functional Testing,Inotify,我有一个Linux应用程序,它使用inotify跟踪文件系统更改。我想为它编写一个功能测试套件,从最终用户的角度测试应用程序,作为其中的一部分,我想测试文件系统出现故障的情况,特别是我想测试inotify故障。 具体地说,我想让inotify文件描述符的inotify_init(),inotify_add_watch(),inotify_rm_watch()调用和调用read(),在测试中需要时返回一个错误 但问题是我找不到如何模拟inotify失败的方法。我想知道是否有人已经遇到过这样的问题,

我有一个Linux应用程序,它使用inotify跟踪文件系统更改。我想为它编写一个功能测试套件,从最终用户的角度测试应用程序,作为其中的一部分,我想测试文件系统出现故障的情况,特别是我想测试inotify故障。
具体地说,我想让inotify文件描述符的
inotify_init()
inotify_add_watch()
inotify_rm_watch()
调用和调用
read()
,在测试中需要时返回一个错误


但问题是我找不到如何模拟inotify失败的方法。我想知道是否有人已经遇到过这样的问题,并且知道一些解决方案。

如果你想避免任何嘲弄,最好的办法就是直接达到操作系统的极限,从而挑起错误。例如,
inotify_init
如果调用进程已达到其打开的文件描述符的数量限制,则会因
EMFILE
errno而失败。要以100%的精度达到这些条件,您可以使用两个技巧:

  • 通过
  • 将您的应用程序进程分配给专用的cgroup,并通过cgroup API为其提供~0%的CPU时间来“暂停”它(这就是Android如何限制后台应用程序并实现其节能的“打盹”模式)
  • inotify的所有可能错误条件都记录在
    inotify
    inotify\u init
    inotify\u add\u watch
    的手册页中(我不认为
    inotify\u rm\u watch
    会失败,除了代码中纯粹的编程错误)

    除了常见的错误(如检查
    /proc/sys/fs/inotify/max_user_watches
    )之外,inotify还有几种故障模式(队列空间耗尽、手表ID重用),但这些并不是严格意义上的“故障”

    当某人执行文件系统更改的速度快于您的反应时,就会发生队列耗尽。它很容易复制:在程序打开inotify描述符时,使用cgroups暂停程序(这样事件队列就不会耗尽),并通过修改观察到的文件/目录快速生成大量通知。一旦您有了未处理事件的
    /proc/sys/fs/inotify/max_queued_events
    ,并取消暂停您的程序,它将在_Q_OVERFLOW中接收
    (并且可能会错过一些不适合队列的事件)

    手表ID的重复使用是一件乏味的事情,因为现代内核已经从类似文件描述符的行为转变为类似PID的手表ID行为。您应该使用与测试PID重用时相同的方法—创建和销毁大量inotify手表,直到整数手表ID结束

    Inotify还有几个棘手的问题,在正常操作过程中很少发生(例如,我知道的所有Java绑定,包括Android和OpenJDK,都不能正确处理它们):相同的inode问题和卸载时的处理

    同样的inode问题在inotify文档中有很好的解释:

    成功调用inotify_add_watch()将为此inotify实例返回一个唯一的监视描述符,用于对应于路径名的文件系统对象(inode)。如果此inotify实例以前没有监视文件系统对象,则新分配了监视描述符。如果已经在监视文件系统对象(可能通过指向同一对象的不同链接),则返回现有监视的描述符

    简而言之:如果您观看指向同一文件的两个硬链接,它们的数字观看ID将是相同的。如果您将手表存储在hashmap之类的文件中,并使用整数手表ID进行键控,则此行为很容易导致丢失第二个inotify手表的轨迹

    第二个问题更难观察,因此即使不是错误模式,也很少得到正确支持:卸载分区,目前通过inotify观察到。棘手的部分是:Linux文件系统不允许您在打开文件描述符时自行卸载,但通过inotify观察文件并不能阻止文件系统卸载。如果您的应用程序在单独的文件系统上观察文件,并且用户卸载了该文件系统,那么您必须准备好处理由此产生的
    IN_UNMOUNT
    事件


    上述所有测试都应该可以在tmpfs文件系统上执行。

    经过一番思考,我想出了另一个解决方案。您可以使用Linux“seccomp”工具来“模拟”单个inotify相关系统调用的结果。这种方法的优点是简单、健壮和完全非侵入性。在其他情况下,您可以有条件地调整系统调用的行为,同时仍使用原始操作系统行为。从技术上讲,这仍然算是模拟,但模拟层非常深入,位于内核代码和用户空间syscall接口之间

    您不需要修改程序的代码,只需编写一个包装器,在
    exec
    -ing应用程序之前安装一个合适的seccomp过滤器(下面的代码使用):

    Seccomp本质上是一个有限的解释器,运行BPF字节码的扩展版本,因此它的功能非常广泛。libseccomp允许您安装有限的条件筛选器(例如,将系统调用的整数参数与常量值进行比较)。如果您想实现更令人印象深刻的条件行为(例如将传递到inotify_add_watch
    的文件路径与预定义值进行比较),可以将直接使用seccomp()syscall与结合起来,以eBPF方言编写复杂的筛选程序

    编写系统调用过滤器可能会很枯燥,并且程序在seccomp作用下的行为实际上并不依赖于内核实现(在将控件传递给内核系统调用处理程序之前,内核会调用seccomp过滤器)。因此,您可能希望将seccomp的稀疏使用与更有机的方法结合起来,如中所述
     // pass control to kernel syscall code by default
     scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
     if (!ctx) exit(1);
    
     // modify behavior of specific system call to return `EMFILE` error
     seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EMFILE), __NR_inotify_init, 0));
    
     execve(...
    
    from inotify_simple.inotify_simple import INotify
    
    class WrapINotify(object):
    
        init_error_list      = []
        add_watch_error_list = []
        rm_watch_error_list  = []
        read_error_list      = []
    
        def raise_if_error(self, error_list):
    
            if not error_list:
                return
    
            # Simulate INotify raising an exception
            exception = error_list.pop(0)
    
            raise exception
    
        def __init__(self):
    
            self.raise_if_error(WrapINotify.init_error_list)
            self.inotify = INotify()
    
        def add_watch(self, path, mask):
    
            self.raise_if_error(WrapINotify.add_watch_error_list)
            self.inotify.add_watch(path, mask)
    
        def rm_watch(self, wd):
    
            self.raise_if_error(WrapINotify.rm_watch_error_list)
            return self.inotify.rm_watch(wd)
    
        def read(self, timeout=None, read_delay=None):
    
            self.raise_if_error(WrapINotify.read_error_list)
            return self.inotify.read(timeout, read_delay)
    
        def close(self):
    
            self.inotify.close()
    
        def __enter__(self):
    
            return self.inotify.__enter__()
    
        def __exit__(self, exc_type, exc_value, traceback):
    
            self.inotify.__exit__(exc_type, exc_value, traceback)
    
    WrapINotify.add_watch_error_list.append(OSError(28, 'No space left on disk'))