使用 readdir 时重命名文件是否安全? [英] Is it safe to rename files while using readdir?

查看:55
本文介绍了使用 readdir 时重命名文件是否安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用 readdir 扫描目录时,您可以安全地重命名文件而不必担心进入无限递归?例如:

While scanning a directory with readdir, can you safely rename files without worrying about entering an infinite recursion? For example:

use v5.12;  # make readdir set $_ in while loops
use strict;
use warnings;

use File::Spec;

my $dir = 'tdir';    
opendir ( my $dh, $dir ) or die "Could not open dir '$dir': $!";
while (readdir $dh) {
    next if /^\.\.?\z/;
    my $filename = File::Spec->catfile( $dir, $_ );
    if ( -f $filename) {
        my $newname = File::Spec->catfile( $dir, "prefix_$_" );
        rename ($filename, $newname) or warn $!;
    }
}

closedir $dh;

因此,例如将 file 重命名为 prefix_file 后,readdir 在以后的迭代中将找不到 prefix_filewhile 循环(然后再次将其重命名为 prefix_prefix_file 等等?可能很明显它不会这样做,但是由于我在文档中找不到它无论如何都会问这个问题.

So after renaming for example file to prefix_file, readdir will not find prefix_file in a later iteration of the while loop (and then rename it again to prefix_prefix_file and so on? Probably obvious that it will not do this, but since I could not find it mentioned in the documentation I'll ask the question anyway.

推荐答案

答案

底层系统调用是POSIX的readdir(),并且规范说:

Answer

The underlying system call is POSIX's readdir(), and the specification says:

如果在最近一次调用 opendir()rewinddir() 之后从目录中删除或添加文件,是否是对 的后续调用readdir() 返回该文件的条目未指定.

If a file is removed from or added to the directory after the most recent call to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry for that file is unspecified.

这只是意味着您可能会或可能不会看到这些文件.您可能会发现特定平台确实指定了发生的情况,但它可能无法移植到其他系统.

It simply means that you might or might not get to see the files. You might find that a particular platform does specify what happens, but it probably isn't portable to other systems.

ikegami 询问:

rename 既不添加也不删除任何目录条目.它只是编辑一个.

rename neither adds nor removes any directory entries, though. It just edits one.

我回复了:

它(rename())改变了目录中的一个条目;会发生什么取决于 [文件系统] 的实现方式.如果您将文件名从 a 更改为 humongous-long-name-that-is-too-boring-to-be-believable,则条目很有可能将在磁盘上的目录中移动,导致未指定的行为 [如主要答案中所述].... rename() 是否真的搞砸了使用 readdir() 的扫描取决于系统(操作系统和文件系统),这就是我所声称的.

It (rename()) changes an entry in the directory; what happens depends on how [the file system] is implemented. If you change the file name from a to humongous-long-name-that-is-too-boring-to-be-believable, there's a decent chance that the entry will move in the directory on disk, leading to unspecified behaviour [as noted in the main answer]. … Whether … rename() actually screws up a scan with readdir() depends on the system (operating system and file system), which is all that I claimed.

经过进一步讨论,我创建了这个示例,说明在一个特定系统上可以发生和确实发生的事情.我使用的步骤:

After further discussion, I created this example of what can and does happen on one specific system. I used the steps:

  • 创建一个目录 - 其名称无关紧要.
  • 进入那个目录.
  • readdir.cmake.files.sh复制到目录中.
  • 从源 readdir.c 创建程序 readdir(例如,使用 make readdir).
    • 代码假设 struct dirent 包含成员 d_namlen,这不是 POSIX 强制要求的.
    • 没有它也是可行的(但需要做一些小改动).
    • Create a directory — its name does not matter.
    • Change into that directory.
    • Copy readdir.c and make.files.sh into the directory.
    • Create program readdir from source readdir.c (use make readdir, for example).
      • The code assumes that the struct dirent includes member d_namlen which is not mandated by POSIX.
      • It would be feasible to do without it (but minor changes would be needed).
          $ ./readdir
          44249044: (  1) .
          42588881: (  2) ..
          44260959: ( 10) .gitignore
          44398380: (  1) a
          Found entry 'a' - hit return to continue: 
          Continuing...
          44398371: ( 10) make.files
          44398280: ( 13) make.files.sh
          44398338: (  8) makefile
          44398351: (  7) readdir
          44260963: (  9) readdir.c
          44398352: ( 12) readdir.dSYM
          44260960: (  9) README.md
          44398364: (  6) rename
          44260964: (  8) rename.c
          44398365: ( 11) rename.dSYM
          $
      

      • 运行sh make.files.sh.这将创建文件 moderately-long-file-name.000 .. moderately-long-file-name.999.
      • 再次运行 ./readdir.不要按回车键.
      • 切换到不同的终端窗口.
      • 将目录更改为正在运行测试的目录.
      • 运行:mv a zzz-let-sleeping-file-renames-lie-unperturbed
      • 切换回运行 readdir 的终端窗口.
      • 点击返回.您可能会看到类似于以下内容的输出:
        • Run sh make.files.sh. This will create files moderately-long-file-name.000 .. moderately-long-file-name.999.
        • Run ./readdir again. Do not hit return yet.
        • Switch to a different terminal window.
        • Change directory to the one where the test is being run.
        • Run: mv a zzz-let-sleeping-file-renames-lie-unperturbed
        • Switch back to the terminal window running readdir.
        • Hit return. You will probably see output similar to:
        •     $ ./readdir
              44249044: (  1) .
              42588881: (  2) ..
              44260959: ( 10) .gitignore
              44398380: (  1) a
              Found entry 'a' - hit return to continue: 
              Continuing...
              44398371: ( 10) make.files
              44398280: ( 13) make.files.sh
              44398338: (  8) makefile
              44431473: ( 29) moderately-long-file-name.000
              44431474: ( 29) moderately-long-file-name.001
              44431475: ( 29) moderately-long-file-name.002
              ...
              44432470: ( 29) moderately-long-file-name.997
              44432471: ( 29) moderately-long-file-name.998
              44432472: ( 29) moderately-long-file-name.999
              44398351: (  7) readdir
              44260963: (  9) readdir.c
              44398352: ( 12) readdir.dSYM
              44260960: (  9) README.md
              44398364: (  6) rename
              44260964: (  8) rename.c
              44398365: ( 11) rename.dSYM
              44398380: ( 45) zzz-let-sleeping-file-renames-lie-unperturbed
              $
          

          这是我在 Mac OS X 10.11.6 El Capitan 和默认 HFS+ 上得到的文件系统.当目录很小(没有中等长度的文件名),则重命名的文件没有出现.当额外创建的文件使目录大小约为 34 KiB,然后重命名的文件确实出现了.

          This is what I got on Mac OS X 10.11.6 El Capitan with the default HFS+ file system. When the directory was small (without the moderately long file names), then the renamed file did not show up. When the extra files were created so that the directory size was around 34 KiB, then the renamed file did show up.

          这表明在某些文件系统上(特别是 Apple 的 HFS+)在某些情况下,目录的 readdir() 扫描是受文件重命名操作的影响.如果您想编写和使用一个 rename 命令而不是使用 mv,所以就这样 —当我尝试时这对结果没有影响.

          This demonstrates that on some file systems (specifically, Apple's HFS+) and under some circumstances, the readdir() scan of a directory is affected by a rename operation on a file. If you wish to write and use a rename command instead of using mv, so be it — when I tried, it made no difference to the result.

          在其他文件系统或其他操作系统上,YMMV.然而,这足以证明在某些系统上,重命名文件而readdir() 正在进行扫描可能会出现相同的文件"在输出中两次.

          On other file systems or other operating systems, YMMV. However, this suffices to demonstrate that on some systems, renaming a file while a readdir() scan is in progress can end up with the same 'file' appearing twice in the output.

          #!/bin/sh
          
          for file in $(seq -f 'moderately-long-file-name.%03.0f' 0 999)
          do > "$file"
          done
          

          readdir.c

          /* SO 3901-5527 - attempt to demonstrate renaming moving entries */
          #include <dirent.h>
          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          #include <unistd.h>
          
          static const char *stop_after = "a";
          
          static void process_directory(const char *dirname)
          {
              DIR *dp = opendir(dirname);
          
              if (dp == 0)
                  fprintf(stderr, "Failed to open directory %s\n", dirname);
              else
              {
                  struct dirent *entry;
                  while ((entry = readdir(dp)) != 0)
                  {
                      /* Ignore current and parent directory */
                      printf("%8d: (%3d) %s\n", (int)entry->d_ino, entry->d_namlen, entry->d_name);
                      if (strcmp(entry->d_name, stop_after) == 0)
                      {
                          printf("Found entry '%s' - hit return to continue: ", stop_after);
                          fflush(stdout);
                          char *buffer = 0;
                          size_t buflen = 0;
                          getline(&buffer, &buflen, stdin);
                          free(buffer);
                          printf("Continuing...\n");
                      }
                  }
                  closedir(dp);
              }
          }
          
          int main(int argc, char **argv)
          {
              int opt;
              while ((opt = getopt(argc, argv, "s:")) != -1)
              {
                  switch (opt)
                  {
                  case 's':
                      stop_after = optarg;
                      break;;
                  default:
                      fprintf(stderr, "%s: Unrecognized option '-%c'\n", argv[0], optopt);
                      fprintf(stderr, "Usage: %s [-s stop_after] [directory ...]\n", argv[0]);
                      return(EXIT_FAILURE);
                  }
              }
              if (optind == argc)
                  process_directory(".");
              else
              {
                  for (int i = optind; i < argc; i++)
                      process_directory(argv[i]);
              }
              return(0);
          }
          

          这篇关于使用 readdir 时重命名文件是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆