Files

605 lines
15 KiB
Perl

# Copyright (c) 2017-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
package mods::misc;
#
# File: misc.pm
#
#
use Exporter;
our (@ISA, @EXPORT);
@ISA = qw/Exporter/;
@EXPORT_OK = qw/ ss_cmd keygen check_project await_prompt
login ping_check look_for_tmp ssess
shess @Ptbl poff parallel_pon ssh_login
$P2fn tsess check_pon set_pwr
get_pwr power_off pon fix_logfn
ping_safe mk_net_ping port_22_available/;
use strict;
use Cwd;
use Expect;
use Time::HiRes qw/usleep/;
use POSIX qw/:sys_wait_h/;
use Term::ANSIColor qw/:constants/;
use mods::walker qw/get_user pwr_this/;
use Net::Ping;
use IO::Socket::INET;
use v5.14;
no warnings 'experimental::smartmatch';
use Carp;
sub await_prompt {
my $eh = shift;
my %info = @_;
my $stdre = qr/\S\$ $/;
my $stdto = 9;
my $to = $info{to} // $stdto;
my $re = $info{re} // $stdre;
my @rets = $eh->expect($to, -re => $re);
return ($rets[1], $rets[2]);
}
sub fix_logfn {
my $lfn = shift;
my $dir = shift;
my $old = shift;
mkdir $dir unless (-d $dir);
croak("Cannot write to $dir.\n") unless (-w -d $dir);
unlink $old if (-e $old);
rename $lfn, $old if (-e $lfn);
return 1;
}
sub ssh_login {
my $hr = shift;
my $ip = $hr->{ip};
my $act = $hr->{acct};
my $pw = $hr->{pw};
my $dir = $hr->{ldir};
my $lfn = $hr->{lfn};
my $old = "$lfn.old";
# Given a log file name, make its path and rename the previous
# log file, only if not already done.
my $k = 'ldone' . $lfn;
$hr->{$k} = fix_logfn($lfn, $dir, $old)
unless (defined $hr->{$k});
ping_check($ip);
return ssess($hr); # Run logged in.
}
sub look_for_tmp {
my $hr = shift;
my $ip = $hr->{ip} // '192.168.0.20';
my $act = $hr->{acct} // croak "Need acct defined.\n";
my $pw = $hr->{pw} // croak "Need pw defined.\n";
my $lfn = $hr->{lfn} // croak "look_for_tmp needs log file name.\n";
my $arg = [ "root\@$ip", qw"test -d /usr/local/tmp" ];
my %cfg = ( sscmd => '/usr/bin/ssh',
args => $arg,
acct => $act,
pw => $pw,
eok => 1, lfn => $lfn,
tgtaddr => $ip);
my $ret = ss_cmd(%cfg);
croak "No /usr/local/tmp dir found.\n" if ($ret);
}
sub ping_check {
my $ip = shift;
my $prog = '/bin/ping';
my @args = (qw/-c 3 -i 0.5/, $ip);
if (my $pid = fork) { # Parent
waitpid($pid, 0);
croak "Cannot find ip addr ", RED, $ip, RESET, ".\n"
if ($? >> 8);
}
else {
open(STDOUT, '>', '/dev/null');
open(STDERR, '>', '/dev/null');
exec($prog, @args);
}
}
sub mk_net_ping {
my $lip = shift;
my $p = Net::Ping->new();
$p->hires(); # Use high resolution timer.
$p->bind($lip); # I only look through local nic to UUT.
return $p;
}
# This version of ping simply returns true if $ip answers.
sub ping_safe {
my $p = shift;
my $ip = shift;
return $p->ping($ip, 1.0);
}
sub port_22_available {
my $ip = shift;
my $tpl = "$ip:22";
my $soc = IO::Socket::INET->new(PeerAddr => $tpl, Timeout => 0.15);
if ($soc) { $soc->close(); return 1; }
else { return 0; }
}
sub keygen {
my $tgt = shift;
my $prog = '/usr/bin/ssh-keygen';
my @args = ( '-f', "$ENV{HOME}/.ssh/known_hosts",
'-R', $tgt);
if (my $pid = fork) { # Parent
waitpid($pid, 0);
croak RED, "keygen error", RESET, ".\n" if ($? >> 8);
}
else {
open(STDOUT, '>', '/dev/null');
open(STDERR, '>', '/dev/null');
exec($prog, @args);
}
}
sub ssess {
# In %{$hr} I need these keys: lfn, tgtaddr, acct, pw.
# I return the expect handle.
my $hr = shift;
my $lfn = $hr->{lfn} // croak "Need lfn defined.\n";
my $act = $hr->{acct} // croak "Need acct defined.\n";
my $pw = $hr->{pw} // croak "Need password defined.\n";
my $ip = $hr->{ip} // croak "Need tgtaddr defined.\n";
my $poke = $hr->{poke} // croak "Need prompt defined.\n";
my ($err, $mat, $eh, @args);
try_more:
$eh = Expect->new();
@args = ('/usr/bin/ssh', "$act\@$ip");
# $eh->slave->clone_winsize_from(\*STDIN);
$eh->log_stdout(0);
$eh->log_file($lfn);
$eh->spawn(@args);
($err, $mat) = await_prompt($eh, re => 'password: ');
if (defined $err) {
# carp "Got $err, loop $loop.\n"; $loop++;
my $bef = $eh->before();
# carp "My bef is $bef.\n";
if ($err =~ m/3:/ && $bef =~ m/NASTY/) {
# OS has been updated. We need to keygen.
$eh->hard_close();
keygen($ip);
goto try_more;
}
elsif ($err =~ m/2:/ && $bef =~ m{remove with: ssh-keygen}) {
# Quit because of known_host problem.
$eh->hard_close();
keygen($ip);
goto try_more;
}
elsif ($err =~ m/1:/ && $bef =~ m{yes/no\)}) {
$eh->print("yes\n");
($err, $mat) = await_prompt($eh, re => 'password: ');
croak "Could not get password prompt.\n"
if (defined $err);
}
else {
croak "Error from scp: $err.\n";
}
}
$eh->print("$pw\n");
($err, $mat) = await_prompt($eh, re => $poke);
croak "Never got shell prompt: $err.\n" if (defined $err);
$eh->print_log_file("\nssh session established.\n");
return $eh;
}
sub shess {
# shess opens an Expect handle to the local shell.
# In %{$hr} I need these keys: lfn, poke.
# I return the expect handle.
my $hr = shift;
my $lfn = $hr->{plfn} // $hr->{lfn}
// croak "Need [p]lfn defined.\n";
my $dir = $hr->{ldir} // croak "Need log dir.\n";
my $fn = $hr->{rcfn} // croak "Need the rcfn defined.\n";
# Take ppoke if defined.
my $poke = $hr->{ppoke} //
$hr->{poke} //
croak "Need prompt defined.\n";
my $old = "$lfn.old";
croak("Cannot find $fn.\n") unless (-e $fn);
my ($err, $mat, $eh, @args);
# Given a log file name, make its path and rename the previous
# log file, only if not already done.
# print "From shess(), my log file name is $lfn.\n";
my $k = 'ldone' . $lfn;
$hr->{$k} = fix_logfn($lfn, $dir, $old)
unless (defined $hr->{$k});
$eh = Expect->new();
@args = ('/bin/bash', '--rcfile', $fn);
$eh->log_stdout(0);
$eh->log_file($lfn);
$eh->spawn(@args);
# print "My poke is $poke.\n";
($err, $mat) = await_prompt($eh, re => $poke);
croak "shess() never got shell prompt: $err.\n" if (defined $err);
$eh->print('echo $$' . "\n");
($err, $mat) = await_prompt($eh, re => $poke);
if (defined $hr->{peh}) {
my $pid = $hr->{peh}->pid();
# print "In shess, first pid is $pid.\n";
$pid = $eh->pid();
# print "In shess, second pid is $pid.\n";
}
return $eh;
}
sub ss_cmd {
# In %cfg I need these keys: sscmd, args, tgtaddr, acct, pw.
# The keys lfn and eok are optional.
my %cfg = @_;
my $act = $cfg{acct};
my $pw = $cfg{pw};
my ($err, $mat, $eh, @args);
try_again:
$eh = Expect->new();
@args = ($cfg{sscmd}, @{$cfg{args}});
$eh->slave->clone_winsize_from(\*STDIN);
$eh->log_stdout(0);
$eh->log_file($cfg{lfn}) if (defined $cfg{lfn});
$eh->spawn(@args);
($err, $mat) = await_prompt($eh, re => 'password: ');
if (defined $err) {
# carp "Got $err, loop $loop.\n"; $loop++;
my $bef = $eh->before();
# carp "My bef is $bef.\n";
if ($err =~ m/3:/ && $bef =~ m/NASTY/) {
# OS has been updated. We need to keygen.
$eh->hard_close();
keygen($cfg{tgtaddr});
goto try_again;
}
elsif ($err =~ m/2:/ && $bef =~ m{remove with: ssh-keygen}) {
# Quit because of known_host problem.
$eh->hard_close();
keygen($cfg{tgtaddr});
goto try_again;
}
elsif ($err =~ m/1:/ && $bef =~ m{yes/no\)}) {
$eh->print("yes\n");
($err, $mat) = await_prompt($eh, re => 'password: ');
croak "Could not get password prompt.\n"
if (defined $err);
}
else {
croak "Error from scp: $err.\n";
}
}
$eh->print("$pw\n");
if (defined $cfg{wpid}) {
$eh->log_stdout(1);
$eh->log_file(undef);
my $pid = $eh->pid();
$eh->interact();
waitpid($pid, 0);
return $? >> 8;
}
($err, $mat) = await_prompt($eh, re => 'wait for death', to => 90);
croak "Did not finish ss_cmd: $err.\n" unless ($err =~ m/(2|3):/);
# Errors are ok for this command if $cfg{eok} defined.
croak "Bad return from $cfg{sscmd}: $!.\n"
if (($err = $eh->exitstatus()) && (!defined $cfg{eok}));
$eh->hard_close();
return $err;
}
sub check_project {
my $dir = shift // getcwd;
my $re = qr/ build | components | hardware |
hw-description | images |
pre-built | subsystems/x;
chdir $dir or croak "Cannot cd to $dir: $!.\n";
opendir my $dh, $dir or croak "Cannot read dir $dir:$!.\n";
my @pdirs = grep { /$re/ && -d $_ } readdir($dh);
closedir $dh;
croak "$dir does not appear to be PetaLinux project dir.\n"
unless(@pdirs == 7);
return {pdir => $dir, hdfdir => "$dir/../hdf",
imagedir => "$dir/images/linux"};
}
sub login {
my %cfg = @_;
my $eh = $cfg{eh};
my $acct = $cfg{acct} // croak "Need acct defined.\n";
my $pw = $cfg{pw} // croak "Need pw defined.\n";
my $prompt = qr/(?:login: )|(?::~# )/;
my $passw = qr/Password: /;
my $lfn = $cfg{lfn};
my ($err, $mat);
$eh->print("\n");
($err, $mat) = await_prompt($eh, re => $prompt, to => 3);
croak "Unexpected $err in login().1.\n" if (defined $err);
return if ($mat =~ m/:~# /);
$eh->print("$acct\n");
($err, $mat) = await_prompt($eh, re => $passw, to => 3);
$eh->print("$pw\n");
($err, $mat) = await_prompt($eh, re => $prompt, to => 3);
croak "Unexpected $err in login().2.\n" if (defined $err);
croak "Could not login, check $lfn.\n" unless ($mat =~ m/:~# /);
return;
}
# Here is a note on pon and poff. I want the fbt program to run
# even if there is no way to program the power supply automatically.
# If either pon or poff return an exit code of 22, that means that
# the device /dev/dc_ps_1 cannot be found. This is not a problem, we'll
# just assume that someone has provided power to the UUT another way.
sub poff { # Turn off power supply.
my $n = shift;
my @pn = ('/usr/local/fbin/p1off', '/usr/local/fbin/p2off');
# A temp patch is to poff only one PS.
pop @pn if (defined $n);
foreach my $pn (@pn) {
croak "Cannot find $pn.\n" unless (-x $pn);
my $pid = fork;
if ($pid) {
waitpid($pid, 0);
my $err = $? >> 8;
return if ($err == 22);
croak "Bad exit code from $pn: $err.\n" if ($err);
}
else {
open(STDOUT, '>', '/dev/null');
open(STDERR, '>', '/dev/null');
exec $pn;
}
}
}
sub pon { # Turn on power supply.
my $hr = shift;
my $num = 0;
my $scomm = $hr->{scomm};
foreach my $pr (@{$hr->{pon}}) {
$num++;
$pr->{tnum} = int($num);
$pr->{tid} = 'PWR';
$pr->{scomm} = $scomm;
$pr->{peh} = $hr->{peh};
$pr->{ppoke} = $hr->{ppoke};
my $nam = $pr->{name};
pwr_this($pr);
# print "pon() num is $num, tnum is $pr->{tnum}.\n";
# use Data::Dump qw/dump/;
# print '$pr is ', dump($pr), ";\n";
croak "Power sequence $num, $nam failure.\n" unless ($pr->{pf});
my $ap = $pr->{res};
foreach my $rhr (@{$ap}) {
$nam = $rhr->{name};
$num = $rhr->{tnum};
croak "Power residual $num, $nam failure.\n"
unless ($rhr->{pf});
# my $fp = $rhr->{func};
# my $ret = $fp->($pr->{bef}, $rhr);
}
}
}
sub rotate_in_sleep {
my $dur = shift;
my $ltime = int($dur / 10);
my $wait = int(($ltime / 4.0) * 1_000_000);
for (my $i = 0; $i < 10; $i++) {
syswrite STDOUT, "\b|";
usleep($wait);
syswrite STDOUT, "\b/";
usleep($wait);
syswrite STDOUT, "\b-";
usleep($wait);
syswrite STDOUT, "\b\\";
usleep($wait);
}
syswrite STDOUT, "\b+";
}
sub parallel_pon {
my $bin = shift;
my $ver = shift;
my $hr = shift;
my $pon = $hr->{pon} // 0;
my $ip = $hr->{ip} // 'na';
my $tip = $hr->{tip} // 'na';
my $pw = $hr->{pw};
my $act = $hr->{acct};
my $dir = $hr->{dir} // '/var/log/fbt';
my ($uhr, $pthrd, $ret);
if ($pon) {
$hr->{plfn} = $hr->{plfn} // "$dir/fbt.pwr.log";
$hr->{ppoke} = $hr->{ppoke} // qr/xyz\$ /;
$hr->{peh} = shess($hr);
my $ip = $hr->{ip} // 'na';
my $n = 0;
my $s = 0;
my $msg = "Waiting network connection ";
my $ehr;
my $ret;
my $pok;
my $p22ok;
pon($hr);
# I want the message from pon to get printed first
# if it finds a problem quickly.
# usleep(1_500_000);
$uhr = get_user($bin, $ver);
goto JOIN_ONLY if ($ip eq 'na');
my $p = mk_net_ping($tip);
syswrite STDOUT, $msg;
until ($n++ > 14) {
syswrite STDOUT, ' ';
rotate_in_sleep(10);
$pok = ping_safe($p, $ip);
$p22ok = port_22_available($ip);
$ret = $pok && $p22ok;
if ($ret) {
$s++;
last if ($s == 2); # Ping and 22 twice.
}
}
$msg = "\r\e[0K";
syswrite STDOUT, $msg;
$p->close();
unless ($ret) {
if ($pok) {
croak "I can ping $ip, but no ssh answers.\n";
}
else {
croak "Could not ping $ip.\n"
}
}
JOIN_ONLY:
}
else {
$uhr = get_user($bin, $ver);
}
return $uhr;
}
sub tsess {
my $hr = shift;
my $prog = $hr->{prog} // croak "No prog defined in tsess().\n";
my $ip = $hr->{ip} // croak "No ip defined in tsess().\n";
my $to = $hr->{to} // croak "No to defined in tsess().\n";
my $lfn = $hr->{lfn} // croak "No log file in tsess().\n";
my $poke = $hr->{poke1} // croak "No prompt in tsess().\n";
my $eh = Expect->new();
my @args = ($prog, $ip);
my ($mat, $err);
$eh->log_stdout(0);
unlink $lfn if (-e $lfn);
$eh->log_file($lfn);
$eh->spawn("@args");
($err, $mat) = await_prompt($eh, re => $poke, to => $to);
croak "Could not get first prompt. Error: $err.\n" if (defined $err);
$hr->{eh} = $eh;
return $eh;
}
# The pwr_get and pwr_set routines report and maintain a hash used
# by the interrupt signal handler to turn off known on power supplies.
my %Pwr;
sub set_pwr {
my $prog = shift;
return if ($prog =~m{/acpwr_});
my ($pnam) = $prog =~ m{/([^/]+)$};
croak("In set_pwr(), cannot find in $prog.\n") unless defined $pnam;
my ($n, $oper) = $pnam =~ m/^p(\d)(.+)$/;
croak("In set_pwr(), cannot parse $pnam.\n")
unless(defined $n and defined $oper);
for ($oper) {
when (/on$/) { $Pwr{$n}++; }
when (/off$/) { delete $Pwr{$n} if (defined $Pwr{$n}); }
when (/apply$/) { $Pwr{$n}++; }
}
}
sub get_pwr { return \%Pwr; }
sub power_off {
my $ret = shift;
my $hr = get_pwr();
for my $k (keys %{$hr}) {
usleep(200_000);
my $prog = '/usr/local/fbin/p' . $k . 'off';
my $pid = fork;
if ($pid) {
waitpid($pid, 0); # I ignore exit codes.
}
else {
# warn("I want to run $prog.\n");
open(STDOUT, '>', '/dev/null');
open(STDERR, '>', '/dev/null');
exec($prog);
}
}
return if (defined $ret and $ret eq 'return');
exit 22;
}
# This routine checks to see if the two Keysight power supplies are
# connected and turned on. Both should return 0 from the stat program.
# If not, an undef is returned. On success, the original pon key is returned.
sub check_pon {
my $pf = shift;
return unless (defined $pf);
my $pprog = '/usr/local/fbin/pxstat';
for my $n (1 .. 4) {
my $lprog = $pprog;
$lprog =~ s/x/$n/;
my $pid = fork;
if ($pid) {
waitpid($pid, 0);
return if ($? >> 8);
}
else {
open(STDOUT, '>', '/dev/null');
open(STDERR, '>', '/dev/null');
exec($lprog);
}
}
return $pf;
}
our $P2fn = '/dev/dc_ps_2';
our @Ptbl = (
{
dev => '/dev/dc_ps_1',
del => 200_000, # n useconds/cmd
v => 18.0,
i => 3.5,
vtol => 10, # +- %
imin => 4, # % of i
range => "HIGH",
type => qr/E3632A/,
trmio => '9600,8,n,1',
ldir => '/var/log/fbt',
lfn => 'pon_poff.log',
stty => [ qw/
cs8 clocal -hupcl -icrnl -ixon -opost
-isig -icanon -iexten -echo
-echoe -echonl -echok
/ ],
},
);
1;