mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-25 17:27:18 +00:00
605 lines
15 KiB
Perl
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;
|