Почему perl qx зависает в Mojolicious::Lite, а не в обычной программе?

(Это cperl 5, версия 24, subversion 4 (v5.24.4c), созданная для x86_64-linux) Ubuntu 18.04.

Ниже приведена работающая программа. Однако когда я запускаю эту программу из Mojolicious::Lite (версия 6.04), она зависает. Используя top, я вижу, что tr съедает весь ЦП. Я попытался использовать cat вместо tr, и он все еще зависает. Если я управляю-C кодом Mojo, он печатает пароль, а затем выходит. Это похоже на то, что tr принимает случайные байты, но не переходит к сгибу, пока я не прерву его. Но это работает в обычном скрипте, а не в моджо...

У кого-нибудь есть идеи, почему?

Тепло

Джон

Скрипт который работает:

#! /usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my $pass_length = 3;
my $exec = qq{tr -cd "[:alnum:]" < /dev/urandom | fold -w$pass_length | head -n1};
print Dumper $exec;
my $pass = qx{$exec};
chomp $pass;
print Dumper $pass;

Код Mojolicious Lite, который висит:

use Mojolicious::Lite;

use strict;
use warnings;

use Data::Dumper;

post 'testit' => sub {
    my $c = shift;

    my $pass_length = 3;
    # tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1
    my $exec = qq{tr -cd '[:alnum:]' < /dev/urandom | fold -w$pass_length | head -n1};
    warn Dumper $exec;
    my $pass = qx{$exec};
    chomp $pass;
    warn Dumper $pass;
    return $c->render( json => { foo => 'bar'} );
};

app->secrets('foobar');
app->start;

person ftumsh    schedule 30.07.2020    source источник
comment
Да, я могу воспроизвести это. head -1 /dev/urandom работает (выдает одну строку), но не head -1 /dev/urandom | tr-cd '[:alnum:]' (зависает). Интересно, что head -1 /dev/urandom | echo дает SIGPIPE: write error: Broken pipe, но head -1 /dev/urandom | cat работает нормально   -  person Håkon Hægland    schedule 30.07.2020
comment
@HåkonHægland Ни один из них не блокирует меня в Ubuntu 18.04.4 LTS. Блокировать тоже нет смысла.   -  person ikegami    schedule 30.07.2020
comment
Ожидается @HåkonHægland SIGPIPE. Это означает, что echo вышел до того, как head написал в канал, что вполне разумно.   -  person ikegami    schedule 30.07.2020


Ответы (2)


sub gen_password {
   my ($pass_len) = @_;

   # We use sysread to avoid wasting entropy by over-reading.
   # We use :raw because we use sysread.

   state $bad_syms = {
      map { $_ => 1 }
         qw( 0 O I 1 l )
   };
   state $ok_syms = {
      map { $_ => 1 }
         grep !$bad_syms->{$_},
            'a'..'z', 'A'..'Z', '0'..'9'
   };

   my $qfn = '/dev/urandom';
   open(my $fh, '<:raw', $qfn)
      or die("Can't open $qfn: $!\n");

   my $password = '';
   while (length($password) < $pass_len) {
      my $rv = sysread($fh, my $ch, 1);
      die("Can't read $qfn: $!\n") if !defined($rv);
      die("Can't read $qfn: Premature EOF\n") if !$rv;
      redo if !$ok_syms->{$ch};

      $password .= $ch;
   }

   return $password;
}

Преимущества:

  • Более эффективным.
  • Меньше режимов отказа.
  • Улучшенная обработка ошибок.
  • Гарантируется запрошенная длина.
  • Избегает сходства символов до степени смешения.

Следующая версия тратит еще меньше энтропии, но требует набора ровно из 64 символов:

use MIME::Base64 qw( encode_base64 );

sub gen_password {
   my ($pass_len) = @_;

   my $qfn = '/dev/urandom';
   open(my $fh, '<:raw', $qfn)
      or die("Can't open $qfn: $!\n");

   my $bytes = int( ($pass_len+3) * (3/4) );
   my $buf = '';
   while ($bytes) {
      my $rv = sysread($fh, $buf, $bytes, length($buf));
      die("Can't read $qfn: $!\n") if !defined($rv);
      die("Can't read $qfn: Premature EOF\n") if !$rv;
      $bytes -= $rv;
   }

   return substr(
      encode_base64($buf, '') =~
         tr/a-zA-Z0-9+\//a-km-zA-HJ-NP-Z2-9!%^&*()/r,
      0, $pass_len,
   );
}
person ikegami    schedule 30.07.2020
comment
Как обычно, отличный материал @ikegami. Не могли бы вы рискнуть предположить, почему зависает материал Mojo? Он использовал морбо или гипножабу, между прочим. - person ftumsh; 31.07.2020

Я не уверен, почему tr зависает при непосредственном вводе данных от /dev/urandom. Я смог обойти это, установив промежуточный канал cat перед каналом tr. Для меня работает следующее:

my $exec = qq{head -1 /dev/urandom | cat | tr -cd '[:alnum:'] | fold -w$pass_length}

Изменить:

Если вы хотите сгенерировать пароль из $pass_length символов, вы можете использовать head -c следующим образом:

my $exec = qq{head -1 /dev/urandom | cat | tr -cd '[:alnum:'] | head -c $pass_length}
person Håkon Hægland    schedule 30.07.2020
comment
Интересно. Однако это не совсем работает. Вывод, который я получаю: { "foo" : "ZF6\nnL4\nzwc\nSkM\nGuz\nXXC\nu86\n1Yb\nbVu\nFVj\nhpv\nhOa\niDP\nJAa\nicG\nynV\n4f1\nNi1\nOTD\nE2e\nwDs\n6nT\nx2B\ndc0\nFkN\nI" } - person ftumsh; 30.07.2020
comment
Я также попытался запустить первую программу из материала Mojo, и она все еще зависает. Все любопытнее и любопытнее. Ну, я говорю любопытнее, для тех, кто знает, что происходит, это, вероятно, не проблема :) - person ftumsh; 30.07.2020
comment
Хм... Я думал, что команда fold разделит его на такие строки. Каков ваш ожидаемый результат тогда? - person Håkon Hægland; 30.07.2020
comment
Я обновил свой ответ, пожалуйста, проверьте, это то, что вы хотели - person Håkon Hægland; 30.07.2020
comment
Точно. Спасибо. Я начал сворачивать его в Perl, но вышеизложенное работает нормально. Я все же хотел бы знать, почему один и тот же код работает по-разному. Спасибо за ваше время! - person ftumsh; 30.07.2020
comment
Обратите внимание, что вы не гарантируете получение `$pass_length alnum символов - person ikegami; 30.07.2020