Как создать, а затем использовать длинные пути Windows из Perl? - PullRequest
19 голосов
/ 12 ноября 2009

У меня есть часть процесса сборки, которая создает ужасно длинные пути в Windows. Это не моя вина. Это несколько каталогов глубиной, и ни одно из имен каталогов не слишком длинное; они просто длинные и достаточно многочисленные, чтобы сделать его более MAX_PATH (260 символов). Я не использую ничего кроме ASCII в этих именах.

Большая проблема заключается в том, что взрыв происходит глубоко в кишечнике Module :: Build во время цели dist, хотя я считаю, что система сборки не имеет значения, потому что они те же каталоги.

Создание одного из этих чрезмерно длинных каталогов с File::Path завершается неудачей:

 use File::Path qw( make_path );

 make_path( 'C:\\.....' ); # fails if path is over 260 chars

Аналогично, создание каждого уровня каталога вручную завершается неудачно, если абсолютный путь будет превышен MAX_PATH.

Это не ново, это не ошибка Perl, и Microsoft документирует это в Именование файлов, путей и пространств имен . Их исправление предполагает добавление \\?\ перед любым путем для доступа к API имени файла Unicode. Тем не менее, это не является полным исправлением для сценария Perl, потому что он все еще не работает:

 use File::Path qw( make_path );

 make_path( '\\\\?\\C:\\.....' );  # still fails if path is over MAX_PATH, works otherwise

Это может быть связано с тем, что make_path разбирает свой аргумент и затем проходит по каталогам по одному уровню за раз, поэтому \\?\ применяется только к верхнему уровню, который находится в пределах MAX_PATH.

Я откопал отчет об ошибке в ActiveState , который предполагает, что мне нужно исправить кое-что еще, чтобы добраться до имен файлов Unicode, и Ян Дюбуа дает немного больше подробностей в Re: "long msgstr "имена файлов в Windows 2K / XP , хотя я не уверен, что это применимо (и очень старое). perlrun упоминает, что это используется для работы переключателя -C, но, очевидно, эта часть была заброшена. В очереди perl RT имеется более поздняя ошибка 60888: Win32: поддержка полного юникода в именах файлов (используйте системные вызовы) .

Miyagawa отмечает некоторые проблемы с именами файлов Unicode и Win32API :: File без указания длинных путей. Однако запись в Win32API :: File CPAN Forum , похоже, указывает только на страх, который приводит к гневу, к ненависти и так далее. Есть пример в Perlmonks post Как определить в файле файл с именем Unicode (UTF16-LE) в Windows? . Кажется, Win32::CreateDirectory - это ответ, и я попробую это сделать в следующий раз, когда буду рядом с Windows-машиной.

Тогда, предположим, я могу создать длинный путь. Теперь я должен научить Module :: Build и, возможно, другим вещам справляться с этим. Это может быть легко с обезьянами, если Win32::GetANSIPathName() делает то, что говорит на жестяной банке.

Ответы [ 5 ]

9 голосов
/ 12 ноября 2009

Прогресс:

Работает следующий скрипт: Он записывает строку в файл в каталоге с длинным путем и может читать ту же строку. (Успешный запуск не приводит к выводу консоли). Я также приложил громоздкие усилия, чтобы переопределить open.

#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Encode qw( encode );
use Symbol;

use Win32;

use Win32API::File qw(
    CreateFileW OsFHandleOpen
    FILE_GENERIC_READ FILE_GENERIC_WRITE
    OPEN_EXISTING CREATE_ALWAYS FILE_SHARE_READ
);

use Win32::API;
use File::Spec::Functions qw(catfile);

Win32::API->Import(
    Kernel32 => qq{BOOL CreateDirectoryW(LPWSTR lpPathNameW, VOID *p)}
);

my %modes = (
    '<' => {
        access => FILE_GENERIC_READ,
        create => OPEN_EXISTING,
        mode   => 'r',
    },
    '>' => {
        access => FILE_GENERIC_WRITE,
        create => CREATE_ALWAYS,
        mode   => 'w',
    },
    # and the rest ...
);

use ex::override open => sub(*;$@) {
    $_[0] = gensym;

    my %mode = %{ $modes{$_[1]} };

    my $os_fh = CreateFileW(
        encode('UCS-2le', "$_[2]\0"),
        $mode{access},
        FILE_SHARE_READ,
        [],
        $mode{create},
        0,
        [],
    ) or do {$! = $^E; return };

    OsFHandleOpen($_[0], $os_fh, $mode{mode}) or return;
    return 1;
};

my $path = '\\\\?\\' . Win32::GetLongPathName($ENV{TEMP});
my @comps = ('0123456789') x 30;

my $dir = mk_long_dir($path, \@comps);
my $file = 'test.txt';
my $str = "This is a test\n";

write_test_file($dir, $file, $str);

$str eq read_test_file($dir, $file) or die "Read failure\n";

sub write_test_file {
    my ($dir, $file, $str) = @_,

    my $path = catfile $dir, $file;

    open my $fh, '>', $path
        or croak "Cannot open '$path':$!";

    print $fh $str or die "Cannot print: $!";
    close $fh or die "Cannot close: $!";
    return;
}

sub read_test_file {
    my ($dir, $file) = @_,

    my $path = catfile $dir, $file;

    open my $fh, '<', $path
        or croak "Cannot open '$path': $!";

    my $contents = do { local $/; <$fh> };
    close $fh or die "Cannot close: $!";
    return $contents;
}

sub mk_long_dir {
    my ($path, $comps) = @_;

    for my $comp ( @$comps ) {
        $path = catfile $path, $comp;
        my $ucs_path = encode('UCS-2le', "$path\0");
        CreateDirectoryW($ucs_path, undef)
            or croak "Failed to create directory: '$path': $^E";
    }
    return $path;
}

Использование Win32::GetANSIPathName() со встроенным open не работает: возвращенный путь слишком длинный.

См. Историю изменений для неудачных экспериментов.

6 голосов
/ 12 ноября 2009

Следующий код фактически создает довольно глубокую (более 260 символов) структуру каталогов. По крайней мере, на моей машине:

use Win32::API;

$cd = Win32::API->new('kernel32', 'CreateDirectoryW', 'PP', 'N');

$dir = '\\\\?\\c:\\!experiments';

$res = 1;

do
{
    print 'path length: ' . length($dir) . "\n";
    $dirname = pack('S*', unpack('C*', "$dir\0"));  #dirty way to produce UTF-16LE string

    $res = $cd->Call($dirname, 0);
    print "$res\n";

    $dir .= '\\abcde';

} while ( $res );
2 голосов
/ 13 ноября 2009

Это действительно должен быть комментарий, но размещение кода в комментариях вряд ли полезно.

UNC-пути также не работают:

C:\> net share
perlbuild    e:\home\src
#!/usr/bin/perl

use strict;
use warnings;

use File::Path qw(make_path);
use File::Slurp;
use Path::Class;

my $top = dir('//Computer/perlbuild');
my @comps = ('0123456789') x 30;

my $path = dir($top, @comps);

make_path $path, { verbose => 1 };

my $file = file($path, 'test.txt');

write_file "$file" => 'This is a test';

print read_file "$file";

Результат:

mkdir \\Computer\perlbuild\0123456789\0123456789\0123456789\0123456789\0123456
789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789
\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\0123456789\01
23456789\0123456789: No such file or directory; The filename or extension is too
 long at C:\Temp\k.pl line 15
2 голосов
/ 12 ноября 2009

Я понимаю, что это не решение вашей конкретной проблемы. Однако существует множество сценариев, в которых возможность сопоставления очень длинного пути с буквой диска позволит обойти проблему и, следовательно, будет полезна при работе с очень длинными именами путей без необходимости проходить через множество Специфичный для Windows код и документы.

Несмотря на все усилия, которые я приложил, чтобы выяснить, как это сделать, я собираюсь как-то порекомендовать использовать SUBST. Win32 :: FileOp обеспечивает Subst и Unsubst. Затем вы можете сопоставить рабочий каталог верхнего уровня с неиспользуемой буквой диска (которую можно найти с помощью Substed). Я бы начал проверять с Z и работать в обратном направлении.

Или, вы можете выложить, вызвать утилиту subst без параметров, чтобы получить список текущих замен, выберите ту, которой нет.

Ничто из этого не является полностью безопасным, поскольку замены могут измениться в процессе сборки.

0 голосов
/ 12 ноября 2009

У меня было три мысли, все они вроде хаки:

  1. Начните с некоторых коротких имен каталогов (C: \ data_directory \ a \ b \ c \ d \ 4 \ 5 \ 6 \ ...), а затем переименуйте каталоги (сначала, конечно, с самого глубокого каталога) ).

  2. Создать ярлык Windows с умеренно длинным путем и создать файлы и подкаталоги оттуда? (Или установить Cygwin и использовать символические ссылки?)

  3. Создайте нужные файлы в каталоге с коротким именем, zip / tar их и распакуйте в каталог с более длинным именем. Или создайте zip / tar-файлы «вручную» и распакуйте их в нужном месте.

...