GetAttributes использует неверный рабочий каталог в подпотоке

Я использовал File::Find пройти через дерево каталогов и Win32::File"s GetAttributes функция, чтобы посмотреть на атрибуты файлов, найденных в нем. Это работало в однопоточной программе.

Затем я переместил обход каталога в отдельный поток, и он перестал работать. GetAttributes ошибка в каждом файле с "Системой не удается найти указанный файл" в качестве сообщения об ошибке в $^E,

Я проследил проблему до того, что File::Find использования chdirи, видимо, GetAttributes не использует текущий каталог. Я мог бы обойти это, передав ему абсолютный путь, но затем я мог бы столкнуться с ограничениями длины пути, и длинные пути определенно будут присутствовать там, где будет запускаться этот скрипт, поэтому мне действительно нужно воспользоваться chdir и относительные пути.

Чтобы продемонстрировать проблему, вот скрипт, который создает файл в текущем каталоге, другой файл в подкаталоге, chdir в подкаталог и ищет файл 3 способами: system("dir"), open, а также GetAttributes,

Когда скрипт запускается без аргументов, dir показывает подкаталог, open находит файл в подкаталоге и GetAttributes возвращает свои атрибуты успешно. Когда бегать с --threadвсе тесты выполняются в подзадаче, а dir а также open все еще работает, но GetAttributes выходит из строя. Тогда это вызывает GetAttributes в файле, который находится в исходном каталоге (из которого мы удалили chdir), и он находит его! как-то GetAttributes использует исходный рабочий каталог процесса - или, возможно, рабочий каталог основного потока - в отличие от всех других файловых операций.

Как я могу это исправить? Я могу гарантировать, что основной поток не будет выполнять chdir'ing, если это имеет значение.

use strict;
use warnings;

use threads;
use Data::Dumper;
use Win32::File qw/GetAttributes/;
sub doit
{
  chdir("testdir") or die "chdir: $!\n";
  system "dir";
  my $attribs;
  open F, '<', "file.txt" or die "open: $!\n";
  print "open succeeded. File contents:\n-------\n", <F>, "\n--------\n";
  close F;
  my $x = GetAttributes("file.txt", $attribs);
  print Dumper [$x, $attribs, $!, $^E];
  if(!$x) {
    # If we didn't find the file we were supposed to find, how about the
    # bad one?
    $x = GetAttributes("badfile.txt", $attribs);
    if($x) {
      print "GetAttributes found the bad file!\n";
      if(open F, '<', "badfile.txt") {
        print "opened the bad file\n";
        close F;
      } else {
        print "But open didn't open it. Error: $! ($^E)\n";
      }
    }
  }
}

# Setup
-d "testdir" or mkdir "testdir" or die "mkdir testdir: $!\n";
if(!-f "badfile.txt") {
  open F, '>', "badfile.txt" or die "create badfile.txt: $!\n";
  print F "bad\n";
  close F;
}
if(!-f "testdir/file.txt") {
  open F, '>', "testdir/file.txt" or die "create testdir/file.txt: $!\n";
  print F "hello\n";
  close F;
}

# Option 1: do it in the main thread - works fine
if(!(@ARGV && $ARGV[0] eq '--thread')) {
  doit();
}

# Option 2: do it in a secondary thread - GetAttributes fails
if(@ARGV && $ARGV[0] eq '--thread') {
  my $thr = threads->create(\&doit);
  $thr->join();
}

1 ответ

В конце концов я понял, что perl поддерживает какой-то вторичный cwd, который применяется только к встроенным операторам perl, в то время как GetAttributes использует родной cwd. Я не знаю, почему это происходит или почему это происходит только во вторичном потоке; мое лучшее предположение, что perl пытается эмулировать правило unix одного cwd на процесс, и терпит неудачу, потому что Win32::* модули не играют вместе.

Безотносительно причины, можно обойти это, заставляя родной cwd быть тем же самым как cwd perl всякий раз, когда вы собираетесь сделать Win32::* операция, как это:

use Cwd;
use Win32::FindFile qw/SetCurrentDirectory/;

...

SetCurrentDirectory(getcwd());

спорно File::Find должен сделать это при запуске на Win32.

Конечно, это только усугубляет проблему "слишком длинного пути", потому что теперь каждый каталог, который вы посещаете, будет являться целью абсолютного пути. SetCurrentDirectory; попытаться обойти это с рядом меньших SetCurrentDirectory звонки, и вы должны найти способ вернуться туда, откуда вы пришли, что трудно, даже если у вас нет fchdir,

Другие вопросы по тегам