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

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

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

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

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

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

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

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();
}

person Community    schedule 19.03.2014    source источник


Ответы (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.

person Community    schedule 01.04.2014