Подтвердить что ты не робот

Как создать, затем использовать длинные пути Windows из Perl?

У меня есть часть процесса сборки, которая создает ужасно длинные пути в 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 script, поскольку оно все еще не работает:

 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, а Jan Dubois дает немного больше деталей в Re: "длинные" имена файлов в Windows 2K/XP, хотя я не уверен, что это применимо (и очень старо). perlrun упоминает, что это используется для работы переключателя -C, но, видимо, эта часть была оставлена. В очереди Perl RT есть более новая ошибка 60888: Win32: поддержка полного юникода в именах файлов (использование широкоэкранных вызовов).

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

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

4b9b3361

Ответ 1

Прогресс:

Работает следующий script: он записывает строку в файл в каталоге с длинным путем и может считывать одну и ту же строку. (Успешный запуск не дает консольного вывода). Я также сделал громоздкую попытку переопределить 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(*;[email protected]) {
    $_[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 не работает: Возвращенный путь слишком длинный.

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

Ответ 2

Следующий код фактически создает довольно глубокую (более 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 );

Ответ 3

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

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

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

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

Ответ 4

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

Пути 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

Ответ 5

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

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

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

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