#!/usr/bin/perl
#########################################################################
# ROSA Hardware Probe Tool 0.4
# A tool to probe for hardware and load result to the ROSA hardware DB
#
# Copyright (C) 2013-2014 NTC IT ROSA
#
# Written by Andrey Ponomarenko
#
# PLATFORMS
# =========
#  Linux
#
# REQUIREMENTS
# ============
#  Perl 5 (5.8 or newer)
#  cURL
#  pciutils
#  usbutils
#  hwinfo
#  dmidecode
#  hplip (hp-probe)
#  avahi
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License or the GNU Lesser
# General Public License as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# and the GNU Lesser General Public License along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#########################################################################
use Getopt::Long;
Getopt::Long::Configure ("posix_default", "no_ignore_case");
use File::Path qw(mkpath rmtree);
use File::Temp qw(tempdir);
use File::Copy qw(copy move);
use File::Basename qw(basename dirname);
use Cwd qw(abs_path cwd);

my $TOOL_VERSION = "0.4";
my $CmdName = basename($0);

my $URL = "http://hw.rosalinux.ru";

my $DATA_DIR = "hw.info";
my $LOG_DIR = $DATA_DIR."/logs";

my $ORIG_DIR = cwd();

my ($Help, $Probe, $Check, $Logs, $Show, $All, $PC_Name, $Key,
$Upload, $FixProbe, $Printers, $DumpVersion, $Source);

my $TMP_DIR = tempdir(CLEANUP=>1);

my $ShortUsage = "ROSA Hardware Probe Tool $TOOL_VERSION
A tool to probe for hardware and load result to the ROSA hardware DB
Copyright (C) 2014 NTC IT ROSA
License: GNU GPL or GNU LGPL

Usage: sudo $CmdName [options]
Example: sudo $CmdName -all -upload -id PC_NAME\n";

if($#ARGV==-1)
{
    print $ShortUsage;
    exit(0);
}

GetOptions("h|help!" => \$Help,
  "dumpversion!" => \$DumpVersion,
# general options
  "probe!" => \$Probe,
  "check!" => \$Check,
  "printers!" => \$Printers,
  "logs!" => \$Logs,
  "all!" => \$All,
  "show!" => \$Show,
  "id|name=s" => \$PC_Name,
  "upload!" => \$Upload,
  "fix|update=s" => \$FixProbe,
  "src|source=s" => \$Source,
# security
  "key=s" => \$Key
) or errMsg();

sub errMsg()
{
    print "\n".$ShortUsage;
    exit(1);
}

my $HelpMessage="
NAME:
  ROSA Hardware Probe Tool ($CmdName)
  A tool to probe for hardware and load result to the ROSA hardware DB

DESCRIPTION:
  ROSA Hardware Probe Tool (HPT) is a tool to probe for hardware, check
  its operability and load result to the ROSA hardware DB.

  This tool is free software: you can redistribute it and/or modify it
  under the terms of the GNU GPL or GNU LGPL.

USAGE:
  sudo $CmdName [options]

EXAMPLES:
  sudo $CmdName -all -upload -id PC_NAME

INFORMATION OPTIONS:
  -h|-help
      Print this help.

  -dumpversion
      Print the tool version ($TOOL_VERSION) and don't do anything else.

GENERAL OPTIONS:
  -all
      Enable all probes.
  
  -probe
      Probe for hardware.
  
  -logs
      Collect system logs.
  
  -printers
      Probe for printers.
  
  -check
      Check devices operability.
  
  -show
      Show devices info.
  
  -id|-name PC_NAME
      The name of the PC to sign the result.
  
  -upload
      Upload result to the ROSA hardware DB.
  
  
OTHER OPTIONS
  -src|-source PATH
      A probe to upload.
  
  -fix|-update PATH
      Fix old-version probe.
  
SECURITY OPTIONS:
  -key KEY
      Security key.
  
";

sub helpMsg() {
    print $HelpMessage;
}

# Hardware
my %HW;

# System
my %Sys;

# Settings
my $Sudo = ($>==0);

# Fixing
my $FixProbe_Pkg;
my $FixProbe_Logs;

sub uploadData()
{
    if(my $Pkg = createPackage())
    {
        # upload package
        system("curl -s -f -POST -F file=\@\"".$Pkg."\" ".$URL."/upload_result.php");
        if($?)
        {
            print STDERR "ERROR: failed to upload data, curl error \"".$?."\"\n";
            exit(1);
        }
    }
}

sub createPackage()
{
    my $Pkg = "";
    
    if($Source)
    {
        if(-f $Source)
        {
            if(isPkg($Source))
            {
                $Pkg = $Source;
                
                system("tar --directory \"".$TMP_DIR."\" -xf \"".$Pkg."\"");
                if($?)
                {
                    print STDERR "ERROR: failed to extract package (".$?.")\n";
                    exit(1);
                }
                
                if(my @Dirs = listDir($TMP_DIR))
                {
                    my $Dir = $Dirs[0];
                    
                    my $Chg = 0;
                    
                    if($Dir ne "hw.info")
                    { # packaged  by user
                        move($TMP_DIR."/".$Dir, $TMP_DIR."/hw.info");
                        $Chg = 1;
                    }
                    
                    if(addID($TMP_DIR."/hw.info")) {
                        $Chg = 1;
                    }
                    
                    if($Chg)
                    {
                        chdir($TMP_DIR);
                        system("tar -czf hw.info.tar.gz hw.info");
                        chdir($ORIG_DIR);
                        
                        if($?)
                        {
                            print STDERR "ERROR: failed to create a package (".$?.")\n";
                            exit(1);
                        }
                        
                        $Pkg = $TMP_DIR."/hw.info.tar.gz";
                    }
                }
            }
            else
            {
                print STDERR "ERROR: not a package\n";
                exit(1);
            }
        }
        elsif(-d $Source)
        {
            copyFiles($Source, $TMP_DIR."/hw.info");
            addID($TMP_DIR."/hw.info");
            
            chdir($TMP_DIR);
            system("tar -czf hw.info.tar.gz hw.info");
            chdir($ORIG_DIR);
            
            if($?)
            {
                print STDERR "ERROR: failed to create a package (".$?.")\n";
                exit(1);
            }
            
            $Pkg = $TMP_DIR."/hw.info.tar.gz";
        }
        else
        {
            print STDERR "ERROR: can't access \'$Source\'\n";
            exit(1);
        }
    }
    else
    {
        if(-d $DATA_DIR)
        {
            if(not -f $DATA_DIR."/devices")
            {
                print STDERR "ERROR: \'./".$DATA_DIR."/devices\' file is not found, please make probe first\n";
                exit(1);
            }
            
            addID($DATA_DIR);
            $Pkg = $TMP_DIR."/hw.info.tar.gz";
            
            system("tar -czf \"".$Pkg."\" \"".$DATA_DIR."\"");
        }
        else
        {
            print STDERR "ERROR: \'./".$DATA_DIR."\' directory is not found, please make probe first\n";
            exit(1);
        }
    }
    
    return $Pkg;
}

sub copyFiles($$)
{
    my ($P1, $P2) = @_;
    
    mkpath($P2);
    
    foreach my $Top (listDir($P1))
    {
        if(-d $P1."/".$Top)
        { # copy subdirectory
            foreach my $Sub (listDir($P1."/".$Top))
            {
                if($Sub=~/~\Z/) {
                    next;
                }
                mkpath($P2."/".$Top);
                copy($P1."/".$Top."/".$Sub, $P2."/".$Top."/".$Sub);
            }
        }
        else
        { # copy file
            if($Top=~/~\Z/) {
                next;
            }
            copy($P1."/".$Top, $P2."/".$Top);
        }
    }
}

sub isPkg($) {
    return ($_[0]=~/\.(tar\.gz|tgz)\Z/);
}

sub addID($)
{
    my $Path = $_[0];
    
    if($PC_Name)
    {
        if(not -f $Path."/host")
        { # internal error
            return 0;
        }
        
        my $Content = readFile($Path."/host");
        
        my $Chg = 0;
        
        if($Content!~/(\A|\n)id:/)
        { # probed without "-id" option
            if($Content!~/\n\Z/) {
                $Content .= "\n";
            }
            $Content .= "id:".$PC_Name."\n";
            $Chg = 1;
        }
        elsif($Content=~/(\A|\n)id:(.*)/)
        { # probed with other PC id
            if($2 ne $PC_Name)
            {
                $Content=~s/(\A|\n)id:(.*)/$1id:$PC_Name/;
                $Chg = 1;
            }
        }
        
        if($Chg)
        {
            writeFile($Path."/host", $Content);
            print "Added PC id\n";
            return 1;
        }
    }
    
    return 0;
}

sub fmtVal($)
{
    my $Val = $_[0];
    
    $Val=~s/\((R|TM)\)/ /ig;
    
    $Val=~s/_/-/ig;
    
    $Val=~s/\A[_\- ]//ig;
    $Val=~s/[_\- ]\Z//ig;
    
    $Val=~s/[ ]{2,}/ /g;
    
    return $Val;
}

sub probeHW()
{
    if(not -e "/usr/sbin/hwinfo")
    {
        if(not $FixProbe) {
            print STDERR "ERROR: hwinfo is not installed\n";
        }
    }
    
    if($FixProbe) {
        print "Fixing probe ... ";
    }
    else {
        print "Probe for hardware ... ";
    }
    
    my $Cpu_ID = undef;
    
    # HW Info
    my $HWInfo = "";
    
    if($FixProbe) {
        $HWInfo = readFile($FixProbe_Logs."/hwinfo");
    }
    else
    {
        my @Items = qw(block bluetooth bridge
        camera cdrom chipcard cpu disk dvb fingerprint floppy
        framebuffer gfxcard hub ide isapnp isdn joystick keyboard
        modem monitor mouse netcard network pci
        pcmcia printer scanner scsi smp sound storage-ctrl
        tape tv usb usb-ctrl vbe wlan zip);
        
        my $Items = "--".join(" --", @Items);
        
        $HWInfo = `/usr/sbin/hwinfo $Items 2>/dev/null`;
        
        if(not $HWInfo)
        { # incorrect option
            $HWInfo = `/usr/sbin/hwinfo --all 2>&1`;
        }
        
        if($Logs) {
            writeFile($LOG_DIR."/hwinfo", $HWInfo);
        }
    }
    
    my %LongID = ();
    
    foreach my $Info (split(/\n\n/, $HWInfo))
    {
        my %Device = ();
        my ($Num, $Bus) = ();
        
        my ($V, $D, $SV, $SD, $C) = ();
        
        if($Info=~s/(\d+):\s*([^ ]+)//)
        { # 37: PCI 700.0: 0200 Ethernet controller
            $Num = $1;
            $Bus = lc($2);
        }
        
        $Bus=~s/\(.*?\)//g;
        
        if($Info=~s/:\s*\w+\s+(.*)//)
        { # 37: PCI 700.0: 0200 Ethernet controller
            if($1 ne "Unclassified device")
            {
                $Device{"Type"} = lc($1);
                $Device{"GeneralType"} = $1;
            }
        }
        
        if($Device{"Type"} eq "partition") {
            next;
        }
        
        $Info=~s/[ ]{2,}/ /g;
        
        my $ID = "";
        
        my $ActiveDriver = 0;
        
        while($Info=~s/[ \t]*([\w ]+?):[ \t]*(.*)//)
        {
            my ($Key, $Val) = ($1, $2);
            
            if($Key eq "Device" or $Key eq "Vendor" or $Key eq "SubVendor" or $Key eq "SubDevice")
            {
                $Key=~s/\ASub/S/; # name mapping
                
                if($Val=~s/\A(\w+) 0x/0x/) {
                    $Bus = $1;
                }
                
                if($Val=~s/0x(\w{4})\s*//)
                {
                    if($Key eq "Vendor") {
                        $V = $1;
                    }
                    elsif($Key eq "Device") {
                        $D = $1;
                    }
                    elsif($Key eq "SVendor") {
                        $SV = $1;
                    }
                    elsif($Key eq "SDevice") {
                        $SD = $1;
                    }
                }
                
                if($Val=~/\"(.*)\"/) {
                    $Device{$Key} = fmtVal($1);
                }
                
                if($Device{"Type"} eq "cpu")
                { # fix cpu
                    if($Key eq "Vendor")
                    {
                        if($Device{$Key}=~/Intel/) {
                            $Device{$Key} = "Intel";
                        }
                        elsif($Device{$Key}=~/AMD/) {
                            $Device{$Key} = "AMD";
                        }
                    }
                }
                
                if(not $V)
                {
                    if($Val=~/(\w+)\s+\".*\"/) {
                        $V = $1;
                    }
                    elsif($Val!~/\".*\"/) {
                        $V = fmtVal($Val);
                    }
                }
            }
            elsif($Key eq "Model")
            {
                if($Val=~/\A(.*?)\s*\"(.*)\"/)
                {
                    $D = $1;
                    $Device{"Model"} = fmtVal($2);
                }
            }
            elsif($Key eq "Hardware Class")
            {
                if($Val ne "unknown") {
                    $Device{"Type"} = $Val;
                }
            }
            elsif($Key eq "Driver")
            {
                my @Dr = ();
                while($Val=~s/\"([\w\-]+)\"//) {
                    push(@Dr, $1);
                }
                $Device{"Driver"} = join(",", @Dr);
            }
            elsif($Key eq "Driver Modules")
            {
                my @Dr = ();
                while($Val=~s/\"([\w\-]+)\"//) {
                    push(@Dr, $1);
                }
                $Device{"Driver"} = join(",", @Dr);
            }
            elsif($Key eq "Driver Status")
            {
                if(not $ActiveDriver)
                {
                    if($Val=~/(.*) is active/) {
                        $Device{"Driver"} = $1;
                    }
                    
                    $ActiveDriver = 1; # get first active driver
                }
            }
            elsif($Key eq "Module Alias")
            {
                if($Val=~/\"(.*)\"/)
                {
                    $Val = $1;
                    
                    if($Val=~/ic(\w\w)isc(\w\w)ip(\w\w)\Z/)
                    { # usb
                        $C = devID($1, $2, $3);
                    }
                    elsif($Val=~/bc(\w\w)sc(\w\w)i(\w\w)\Z/)
                    { # pci
                        $C = devID($1, $2, $3);
                    }
                }
            }
            elsif($Key=~/\A(Resolution|Size)\Z/)
            { # monitor
                $Device{$Key} = $Val;
                $Device{$Key}=~s/ //g;
            }
            elsif($Key eq "Capacity")
            { # disk
                my $Bytes = undef;
                if($Val=~s/\s*\((.*?)\)//)
                {
                    $Bytes = $1;
                    $Bytes=~s/ bytes//g;
                    $Bytes/=1000000000; # GB
                    $Bytes = round_to_nearest($Bytes);
                }
                $Val=~s/ //g;
                
                if($Bytes)
                {
                    if($Bytes>=1000) {
                        $Val = ($Bytes/1000)."TB";
                    }
                    else {
                        $Val = $Bytes."GB";
                    }
                }
                
                $Device{$Key} = $Val;
            }
            elsif($Key eq "Attached to")
            {
                if($Bus eq "ide")
                {
                    # FIXME: check for PATA
                    if($Val=~/SATA/)
                    {
                        # $Bus = "sata";
                    }
                }
            }
            elsif($Key eq "Device Files")
            {
                if($Val=~/by-id\/(.*?),/) {
                    $Device{"FsId"} = $1;
                }
            }
            elsif($Key eq "Serial ID")
            { # disk
                if($Val=~/\"(.*)\"/) {
                    $Device{"Serial"} = $1;
                }
            }
        }
        
        cleanValues(\%Device);
        
        if(my $Model = $Device{"Model"})
        {
            if(not $Device{"Device"}) {
                $Device{"Device"} = $Device{"Model"};
            }
        }
        
        if($Device{"Type"} eq "cpu") {
            $Bus = "cpu";
        }
        
        if($Bus eq "none") {
            next;
        }
        
        # fix vendor
        if($V eq "1d6b") {
            $Device{"Vendor"} = "Linux Foundation";
        }
        
        if(not $Device{"Vendor"})
        {
            if($Bus eq "ps/2")
            {
                if($Device{"Type"} eq "mouse")
                {
                    if($Device{"Device"}=~/(\w+) Touchpad/i) {
                        $Device{"Vendor"} = $1;
                    }
                }
            }
            elsif($Bus eq "sata" or $Bus eq "ide")
            {
                my %DiskVendor = (
                    "HT" => "Hitachi",
                    "ST" => "Seagate",
                    "WD" => "WDC",
                    "CT" => "Crucial",
                    "TS" => "Transcend"
                );
                
                if($Device{"Device"}=~/\A([A-Z]{2})[A-Z\d]+/
                and defined $DiskVendor{$1}) {
                    $Device{"Vendor"} = $DiskVendor{$1};
                }
                elsif($Device{"Device"}=~/\A([a-z]{3,})\-/i) {
                    $Device{"Vendor"} = $1;
                }
            }
        }
        
        if(my $Vendor = $Device{"Vendor"})
        { # do not duplicate vendor name
            $Device{"Device"}=~s/\A\Q$Vendor\E(\s+|\-)//ig;
        }
        
        if($Bus eq "usb" or $Bus eq "pci")
        {
            $ID = devID($V, $D, $SV, $SD);
            
            if($SV, $SD) {
                $LongID{devID($V, $D)}{$ID} = 1;
            }
            
            if($Device{"Type"} eq "disk")
            {
                if(my $Size = $Device{"Capacity"})
                {
                    if($Device{"Device"}!~/(\A|\s)\d+(MB|GB|TB)(\s|\Z)/) {
                        $Device{"Device"} .= " ".$Size;
                    }
                }
            }
        }
        else
        {
            if($Device{"Device"} eq "Unclassified device")
            { # PNP devices, etc.
                next;
            }
            
            if($Device{"Device"})
            {
                if($Device{"Type"} eq "disk")
                {
                    if(my $FsId = $Device{"FsId"})
                    {
                        if(my $Serial = $Device{"Serial"})
                        {
                            my $N = $Device{"Device"};
                            $N=~s/ /_/g;
                            
                            if($FsId=~/\Q$N\E(.*?)_\Q$Serial\E/)
                            {
                                my $Suffix = $1;
                                $Suffix=~s/[_]+/ /g;
                                $Device{"Device"} .= $Suffix;
                            }
                        }
                    }
                }
                
                if($Device{"Type"} eq "monitor")
                {
                    if(lc($Device{"Device"}) eq lc($Device{"Vendor"})) {
                        $Device{"Device"} = $Device{"GeneralType"};
                    }
                    
                    if(not $Device{"Vendor"}) {
                        $Device{"Vendor"} = $V;
                    }
                    
                    my %MonVendor = (
                        "HSD" => "HannStar",
                        "CMN" => "Chimei Innolux",
                        "LGD" => "LG Display",
                        "CMO" => "Chi Mei Optoelectronics",
                        "BNQ" => "BenQ",
                        "AUO" => "AU Optronics",
                        "ACR" => "Acer"
                    );
                    
                    if(my $Vendor = $MonVendor{$V})
                    {
                        $Device{"Vendor"} = $Vendor;
                        $Device{"Device"}=~s/\A\Q$Vendor\E(\s+|\-)//ig;
                    }
                }
                
                # create id
                if($Device{"Type"} eq "monitor" and $V)
                { # do not add vendor name to id
                    if($Device{"Vendor"} ne $V) {
                        $ID = devID(nameID($Device{"Vendor"}));
                    }
                    $ID = devID($ID, $V.$D, devSuffix(\%Device));
                }
                else
                {
                    if(my $Vendor = $Device{"Vendor"}) {
                        $ID = devID(nameID($Vendor));
                    }
                    else
                    {
                        $ID = devID($V);
                    }
                    $ID = devID($ID, $D, devSuffix(\%Device));
                }
                
                # additionals to device attributes
                if($Device{"Type"} eq "monitor")
                {
                    if($D) {
                        $Device{"Device"} .= " ".uc($V.$D);
                    }
                    
                    if($Device{"Resolution"}) {
                        $Device{"Device"} .= " ".$Device{"Resolution"};
                    }
                    
                    if($Device{"Size"}) {
                        $Device{"Device"} .= " ".$Device{"Size"};
                    }
                }
                elsif($Device{"Type"} eq "disk")
                {
                    if(my $Size = $Device{"Capacity"})
                    {
                        if($Device{"Device"}!~/(\A|\s)\d+(MB|GB|TB)(\s|\Z)/) {
                            $Device{"Device"} .= " ".$Size;
                        }
                    }
                }
                elsif($Device{"Type"} eq "cpu")
                {
                    $Device{"Status"} = "works";
                }
            }
            else {
                next;
            }
        }
        $Device{"Class"} = $C;
        
        $ID = fmtID($ID);
        
        if(not $HW{$Bus.":".$ID}) {
            $HW{$Bus.":".$ID} = \%Device;
        }
        
        if($Device{"Type"} eq "cpu") {
            $Cpu_ID = $ID;
        }
    }
    
    # PCI
    my $Lspci = "";
    
    if($FixProbe) {
        $Lspci = readFile($FixProbe_Logs."/lspci");
    }
    else
    {
        $Lspci = `lspci -vmnnk 2>&1`;
        
        if($Logs) {
            writeFile($LOG_DIR."/lspci", $Lspci);
        }
    }
    
    foreach my $Info (split(/\n\n/, $Lspci))
    {
        my %Device = ();
        my (@ID, @Class) = ();
        
        while($Info=~s/(\w+):\s*(.*)//) {
            $Device{$1} = $2;
        }
        
        foreach ("Vendor", "Device", "SVendor", "SDevice")
        {
            if($Device{$_}=~s/\s*\[(\w{4})\]//) {
                push(@ID, $1);
            }
        }
        
        if($Device{"Class"}=~/.*\[(\w{2})(\w{2})\]/)
        {
            push(@Class, $1, $2);
            
            if($Device{"ProgIf"}) {
                push(@Class, $Device{"ProgIf"});
            }
        }
        
        delete($Device{"Class"});
        delete($Device{"ProgIf"});
        
        cleanValues(\%Device);
        
        $Device{"Class"} = devID(@Class);
        
        my $ID = devID(@ID);
        my @L_IDs = keys(%{$LongID{$ID}});
        
        if($#L_IDs==0) {
            $ID = $L_IDs[0];
        }
        
        if($Device{"Module"}) {
            $Device{"Driver"} = $Device{"Module"};
        }
        
        foreach (keys(%Device))
        {
            if($Device{$_}) {
                $HW{"pci:".$ID}{$_} = $Device{$_};
            }
        }
    }
    
    # PCI - more info
    if($Logs)
    {
        if(my $Lspci_A = `lspci -vvnn 2>&1`) {
            writeFile($LOG_DIR."/lspci_all", $Lspci_A);
        }
    }
    
    # USB
    my $Lsusb = "";
    
    if($FixProbe) {
        $Lsusb = readFile($FixProbe_Logs."/lsusb");
    }
    else
    {
        $Lsusb = `lsusb -v 2>&1`;
        
        if($Logs) {
            writeFile($LOG_DIR."/lsusb", $Lsusb);
        }
    }
    
    foreach my $Info (split(/\n\n/, $Lsusb))
    {
        my %Device = ();
        my ($V, $D, @Class) = ();
        
        if($Info=~/idVendor[ ]+0x(\w{4})[ ]+(.*)/)
        {
            $V = $1;
            
            if($2) {
                $Device{"Vendor"} = $2;
            }
        }
        
        if($Info=~/idProduct[ ]+0x(\w{4})[ ]+(.*)/)
        {
            $D = $1;
            
            if($2) {
                $Device{"Device"} = $2;
            }
        }
        
        # if($Device{"Device"}!~/root hub/)
        # {
        #     if($Info=~/iManufacturer[ ]+\w+[ ]+(.*)/) {
        #         $Device{"SVendor"} = $1;
        #     }
        # }
        
        # if($Info=~/iProduct[ ]+\w+[ ]+(.*)/) {
        #     $Device{"SDevice"} = $1;
        # }
        
        cleanValues(\%Device);
        
        if($Info=~/bInterfaceClass\s+(\w+)\s+/) {
            push(@Class, fNum(sprintf('%x',$1)));
        }
        if($Info=~/bInterfaceSubClass\s+(\w+)\s+/) {
            push(@Class, fNum(sprintf('%x',$1)));
        }
        if($Info=~/bInterfaceProtocol\s+(\w+)\s+/) {
            push(@Class, fNum(sprintf('%x',$1)));
        }
        
        $Device{"Class"} = devID(@Class);
        
        if(not $V)
        { # Couldn't open device
            next;
        }
        
        my $ID = devID($V, $D);
        
        if($Device{"Device"})
        {
            if(my $Size = $HW{"usb:".$ID}{"Capacity"})
            {
                if($Device{"Device"}!~/(\A|\s)\d+(MB|GB|TB)(\s|\Z)/) {
                    $Device{"Device"} .= " ".$Size;
                }
            }
        }
        
        foreach (keys(%Device))
        {
            if($Device{$_}) {
                $HW{"usb:".$ID}{$_} = $Device{$_};
            }
        }
    }
    
    # DMI
    my $Dmidecode = "";
    
    if($FixProbe) {
        $Dmidecode = readFile($FixProbe_Logs."/dmidecode");
    }
    else
    {
        if($Sudo)
        {
            $Dmidecode = `dmidecode 2>&1`;
            
            if($Logs) {
                writeFile($LOG_DIR."/dmidecode", $Dmidecode);
            }
        }
    }
    
    my $MemIndex = 0;
    
    foreach my $Info (split(/\n\n/, $Dmidecode))
    {
        my %Device = ();
        my $D = "";
        my $ID = "";
        
        $Info=~s/[ ]{2,}/ /g;
        
        if($Info=~/Memory Device\n/)
        {
            my @Add = ();
            
            while($Info=~s/([\w ]+):[ \t]*(.+?)[ \t]*(\n|\Z)//)
            {
                my ($Key, $Val) = ($1, $2);
                
                if($Val eq "Unknown") {
                    next;
                }
                
                if($Key eq "Manufacturer")
                {
                    $Device{"Vendor"} = fmtVal($Val);
                    $Device{"Vendor"}=~s/0{4,}/0/;
                }
                elsif($Key eq "Part Number") {
                    $Device{"Device"} = $Val;
                }
                elsif($Key eq "Serial Number") {
                    $Device{"Serial"} = $Val;
                }
                elsif($Key eq "Type") {
                    push(@Add, $Val);
                }
                elsif($Key eq "Size")
                {
                    $Device{"Size"} = $Val;
                    
                    $Val=~s/ //g;
                    push(@Add, $Val);
                }
                elsif($Key eq "Speed")
                {
                    $Val=~s/ //g;
                    push(@Add, $Val);
                }
            }
            
            cleanValues(\%Device);
            
            if($Device{"Size"} eq "No Module Installed") {
                next;
            }
            
            $Device{"Type"} = "memory";
            $Device{"Status"} = "works";
            
            if(not $Device{"Vendor"}) {
                $Device{"Vendor"} = "Manufacturer".$MemIndex;
            }
            
            if(not $Device{"Device"}) {
                $Device{"Device"} = "Partnum".$MemIndex;
            }
            
            if(not $Device{"Serial"}) {
                $Device{"Serial"} = "Sernum".$MemIndex;
            }
            $MemIndex++;
            
            $ID = devID(nameID($Device{"Vendor"}), devSuffix(\%Device));
            $ID = fmtID($ID);
            
            if(@Add)
            { # additionals
                $Device{"Device"} .= " ".join(" ", @Add);
                $Device{"Device"}=~s/\A\s+//g;
            }
            
            $Device{"Device"} = "RAM ".$Device{"Device"};
            
            if($ID) {
                $HW{"mem:".$ID} = \%Device;
            }
        }
        elsif($Info=~/Base Board Information\n/)
        {
            while($Info=~s/([\w ]+):[ \t]*(.+?)[ \t]*(\n|\Z)//)
            {
                my ($Key, $Val) = ($1, $2);
                
                if($Key eq "Manufacturer") {
                    $Device{"Vendor"} = fmtVal($Val);
                }
                elsif($Key eq "Product Name") {
                    $Device{"Device"} = fmtVal($Val);
                }
                elsif($Key eq "Version")
                {
                    if($Val!~/\b(n\/a|Not)\b/i) {
                        $Device{"Version"} = $Val;
                    }
                }
            }
            
            cleanValues(\%Device);
            
            if($Device{"Device"}=~/\bName\d*\b/i)
            { # no info
                next;
            }
            
            if(my $Ver = $Device{"Version"}) {
                $Device{"Device"} .= " ".$Device{"Version"};
            }
            $Device{"Type"} = "motherboard";
            $Device{"Status"} = "works";
            
            $ID = devID(nameID($Device{"Vendor"}), devSuffix(\%Device));
            $ID = fmtID($ID);
            
            $Device{"Device"} = "Motherboard ".$Device{"Device"};
            
            if($ID) {
                $HW{"board:".$ID} = \%Device;
            }
        }
        elsif($Info=~/BIOS Information\n/)
        {
            while($Info=~s/([\w ]+):[ \t]*(.+?)[ \t]*(\n|\Z)//)
            {
                my ($Key, $Val) = ($1, $2);
                
                if($Key eq "Vendor") {
                    $Device{$Key} = fmtVal($Val);
                }
                elsif($Key eq "Version" or $Key eq "Release Date") {
                    $Device{$Key} = $Val;
                }
            }
            
            cleanValues(\%Device);
            
            my @Name = ();
            
            if($Device{"Version"}) {
                push(@Name, $Device{"Version"});
            }
            
            if($Device{"Release Date"}) {
                push(@Name, $Device{"Release Date"});
            }
            
            $Device{"Device"} = join(" ", @Name);
            $Device{"Type"} = "bios";
            $Device{"Status"} = "works";
            
            $ID = devID(nameID($Device{"Vendor"}), devSuffix(\%Device));
            $ID = fmtID($ID);
            
            $Device{"Device"} = "BIOS ".$Device{"Device"};
            
            if($ID) {
                $HW{"bios:".$ID} = \%Device;
            }
        }
        elsif($Info=~/Processor Information\n/)
        {
            while($Info=~s/([\w ]+):[ \t]*(.+?)[ \t]*(\n|\Z)//)
            {
                my ($Key, $Val) = ($1, $2);
                
                if($Key eq "Manufacturer")
                {
                    $Device{"Vendor"} = fmtVal($Val);
                    
                    if($Device{"Vendor"}=~/Intel/) {
                        $Device{"Vendor"} = "Intel";
                    }
                    elsif($Device{"Vendor"}=~/AMD/) {
                        $Device{"Vendor"} = "AMD";
                    }
                }
                elsif($Key eq "Signature")
                { # Family 6, Model 42, Stepping 7
                    my @Model = ();
                    
                    if($Val=~/Family\s+(\w+),/) {
                        push(@Model, $1);
                    }
                    
                    if($Val=~/Model\s+(\w+),/) {
                        push(@Model, $1);
                    }
                    
                    if($Val=~/Stepping\s+(\w+)/) {
                        push(@Model, $1);
                    }
                    
                    $D = join(".", @Model);
                }
                elsif($Key eq "Version") {
                    $Device{"Device"} = fmtVal($Val);
                }
            }
            
            cleanValues(\%Device);
            
            if(my $Vendor = $Device{"Vendor"})
            { # do not duplicate vendor name
                $Device{"Device"}=~s/\A\Q$Vendor\E(\s+|\-)//ig;
            }
            
            $Device{"Type"} = "cpu";
            $Device{"Status"} = "works";
            
            if(not $Cpu_ID)
            {
                $ID = devID(nameID($Device{"Vendor"}), $D, devSuffix(\%Device));
                $ID = fmtID($ID);
                
                if($ID) {
                    $HW{"cpu:".$ID} = \%Device;
                }
            }
            else
            { # add info
                foreach (keys(%Device))
                {
                    my $Val1 = $HW{"cpu:".$Cpu_ID}{$_};
                    my $Val2 = $Device{$_};
                    
                    if($Val2
                    and not $Val1) {
                        $HW{"cpu:".$Cpu_ID}{$_} = $Val2;
                    }
                }
            }
        }
    }
    
    # Printers
    my %Pr;
    
    my $Hpprobe = "";
    
    if($FixProbe) {
        $Hpprobe = readFile($FixProbe_Logs."/hp-probe");
    }
    else
    {
        if($Printers)
        {
            $Hpprobe = `hp-probe -bnet -g 2>&1`;
            
            my $Sc = chr(27);
            $Hpprobe=~s/$Sc\[.*?m//g;
            
            if($Logs) {
                writeFile($LOG_DIR."/hp-probe", $Hpprobe);
            }
        }
    }
    
    foreach my $Line (split(/\n/, $Hpprobe))
    {
        if($Line=~/Found device/)
        {
            my %Device = ();
            
            my %Attr = ();
            
            while($Line=~s/\'([^']*?)\'\s*:\s*\'([^']*?)\'//)
            {
                my ($Key, $Val) = ($1, $2);
                
                if($Val=~/MDL:(.*?);/) {
                    $Device{"Device"} = $1;
                }
                #if($Val=~/MFG:(.*?);/) {
                #    $Device{"Vendor"} = $1;
                #}
                
                $Attr{$Key} = $Val;
            }
            
            $Pr{$Device{"Device"}} = 1;
            
            if(not $Device{"Vendor"})
            {
                if($Device{"Device"}=~/\A(HP|Hewlett\-Packard|Epson) /) {
                    $Device{"Vendor"} = $1;
                }
            }
            
            if(my $Vendor = $Device{"Vendor"})
            {
                $Device{"Device"}=~s/\A\Q$Vendor\E(\s+|\-)//ig;
                $Pr{$Device{"Device"}} = 1;
            }
            
            $Device{"Type"} = "printer";
            $Device{"Driver"} = "hplip";
            
            my $ID = devID($Device{"Vendor"}, $Device{"Device"});
            $ID = fmtID($ID);
            
            # additional info
            if(my $D = $Attr{"product_id"}) {
                $Device{"Device"} .= " ".$D;
            }
            
            if($ID) {
                $HW{"net:".$ID} = \%Device;
            }
        }
    }
    
    my $Avahi = "";
    
    if($FixProbe) {
        $Avahi = readFile($FixProbe_Logs."/avahi");
    }
    else
    {
        if($Printers)
        {
            $Avahi = `avahi-browse -a -t`;
            
            if($Logs) {
                writeFile($LOG_DIR."/avahi", $Avahi);
            }
        }
    }
    
    foreach my $Line (split(/\n/, $Avahi))
    {
        if($Line=~/IPv\d\s+(.*?)\s+PDL Printer/)
        {
            my %Device;
            
            $Device{"Device"} = $1;
            $Device{"Device"}=~s/\s*\(.+\)//g;
            
            if($Pr{$Device{"Device"}})
            { # already registered
                next;
            }
            
            if(not $Device{"Vendor"})
            {
                if($Device{"Device"}=~/\A(HP|Hewlett\-Packard|Epson) /) {
                    $Device{"Vendor"} = $1;
                }
            }
            
            if(my $Vendor = $Device{"Vendor"}) {
                $Device{"Device"}=~s/\A\Q$Vendor\E(\s+|\-)//ig;
            }
            
            $Device{"Type"} = "printer";
            
            my $ID = devID($Device{"Vendor"}, $Device{"Device"});
            $ID = fmtID($ID);
            
            if($ID) {
                $HW{"net:".$ID} = \%Device;
            }
        }
    }
    
    print "Ok\n";
}

sub round_to_nearest($)
{
    my $Num = $_[0];
    
    my $Delta = $Num - int($Num);
    
    if($Delta*10>5) {
        return int($Num)+1;
    }
    
    return int($Num);
}

sub cleanValues($)
{
    my $Hash = $_[0];
    foreach (keys(%{$Hash}))
    {
        my $Val = $Hash->{$_};
        
        if($Val=~/\A[\[\(]*(not specified|error|unknown|empty)[\)\]]*\Z/i) {
            $Hash->{$_} = "";
        }
    }
}

sub devSuffix($)
{
    my $Device = $_[0];
    
    my $Suffix = $Device->{"Device"};
    
    if($Device->{"Type"} eq "cpu")
    {
        if($Device->{"Vendor"} eq "Intel")
        { # short suffix
            my @Parts = ();
            
            if($Device->{"Device"}=~/ CPU /)
            {
                if($Device->{"Device"}=~/\A(.+?)\s+CPU/) {
                    push(@Parts, $1);
                }
                
                if($Device->{"Device"}=~/ CPU\s+(.+?)\s*\@/) {
                    push(@Parts, $1);
                }
            }
            elsif($Device->{"Device"}=~/ processor /)
            {
                if($Device->{"Device"}=~/\A(.+?)\s+processor/i) {
                    push(@Parts, $1);
                }
            }
            
            $Suffix = join("-", @Parts);
        }
        elsif($Device->{"Vendor"} eq "AMD") {
            $Suffix=~s/X2 Dual Core Processor/X2/;
        }
    }
    elsif($Device->{"Type"} eq "memory")
    {
        if($Device->{"Serial"}) {
            $Suffix .= "-serial-".$Device->{"Serial"};
        }
    }
    
    return $Suffix;
}

sub fmtID($)
{
    my $ID = $_[0];
    $ID=~s/[\W]+\Z//g;
    $ID=~s/[\W\_]+/-/g;
    return $ID;
}

sub nameID($)
{
    my $Name = $_[0];
    
    $Name=~s/\s*\([^()]*\)//g;
    $Name=~s/\s*\[[^\[\]]*\]//g;
    
    $Name=~s/,?\s+(Inc|Ltd|Co|GmbH|Corp|LLC)(\.|\Z)//ig;
    $Name=~s/,?\s+[a-z]{2,4}\.//ig;
    $Name=~s/,(.+)\Z//ig;
    
    $Name=~s/\s+(Corporation|Computer|Electronics|Technologies)\Z//ig;
    
    $Name=~s/[\.\,]/ /g;
    $Name=~s/\s*\Z//g;
    $Name=~s/\A\s*//g;
    
    return $Name;
}

sub probeSys()
{
    $Sys{"System"} = getSysName();
    $Sys{"Arch"} = `uname -p`;
    $Sys{"Kernel"} = `uname -r`;
    $Sys{"Node"} = `uname -n`;
    $Sys{"User"} = $ENV{"USER"};
    
    if($PC_Name) {
        $Sys{"Name"} = $PC_Name;
    }
    
    if(my $Vendor = readFile("/sys/class/dmi/id/sys_vendor"))
    {
        if($Vendor!~/System manufacturer/i) {
            $Sys{"Vendor"} = $Vendor;
        }
    }
    
    if(my $Model = readFile("/sys/class/dmi/id/product_name"))
    {
        if($Model!~/\bName\b/i) {
            $Sys{"Model"} = $Model;
        }
    }
    
    if(my $Serial = readFile("/sys/class/dmi/id/board_serial"))
    { # need admin rights
        if($Serial!~/\b(Number|to be filled)\b/i) {
            $Sys{"Board"} = $Serial;
        }
    }
    
    if(my $IFConfig = `/sbin/ifconfig -a`)
    {
        if($Logs) {
            writeFile($LOG_DIR."/ifconfig", $IFConfig);
        }
        if($IFConfig=~/ether\s+([^\s]+)/)
        { # Fresh
            $Sys{"HWaddr"} = lc($1);
        }
        elsif($IFConfig=~/HWaddr\s+([^\s]+)/)
        { # Marathon
            $Sys{"HWaddr"} = lc($1);
        }
        
        $Sys{"HWaddr"}=~s/:/-/g;
    }
    
    foreach (keys(%Sys)) {
        chomp($Sys{$_});
    }
}

sub readFile($)
{
    my $Path = $_[0];
    open(FILE, $Path);
    local $/ = undef;
    my $Content = <FILE>;
    close(FILE);
    return $Content;
}

sub getSysName()
{
    if(my $Info = `lsb_release -a 2>/dev/null`)
    { # Desktop
        my ($Name, $Release) = ();
        
        if($Info=~/ID:\s*(.*)/i) {
            $Name = $1;
        }
        if($Info=~/Release:\s*(.*)/i) {
            $Release = $1;
        }
                
        if($Name=~/\AROSA/i)
        {
            if($Release eq "2012.0")
            { # ROSA Linux 2012.0 (Marathon)
                return "rosa2012lts";
            }
            elsif($Release eq "2012.1")
            { # ROSA Desktop.Fresh 2012.1
                return "rosa2012.1";
            }
            
            # 2014.1, etc.
            return "rosa".lc($Release);
        }
        
        return lc($Name.$Release);
    }
    elsif(my $Issue = readFile("/etc/issue"))
    { # RELS
        if($Issue=~/ROSA/i and $Issue=~/Server/i)
        {
            if($Issue=~/release (\w+)/i)
            {
                my $Rel = $1;
                $Rel=~s/\.//g;
                
                return "rosa-server".$Rel;
            }
        }
    }
    
    return "";
}

sub devID(@)
{
    my @ID = ();
    
    foreach (@_)
    {
        if($_) {
            push(@ID,  $_);
        }
    }
    
    return lc(join("-", @ID));
}

sub fNum($)
{
    my $N = $_[0];
    
    if(length($N)==1) {
        $N = "0".$N;
    }
    
    return $N;
}

sub devSort($$) {
    return ($_[0]=~/\A(pci|usb)/ cmp $_[1]=~/\A(pci|usb)/);
}

sub writeInfo()
{
    my $HWData = "";
    foreach my $ID (sort {devSort($b, $a)} sort keys(%HW))
    {
        foreach (keys(%{$HW{$ID}})) {
            $HW{$ID}{$_}=~s/;/ /g;
        }
        
        my @D = ();
        
        push(@D, $ID);
        push(@D, $HW{$ID}{"Class"});
        
        if(not $HW{$ID}{"Status"}) {
            $HW{$ID}{"Status"} = "detected";
        }
        
        push(@D, $HW{$ID}{"Status"}); # test result
        
        push(@D, $HW{$ID}{"Type"});
        push(@D, $HW{$ID}{"Driver"});
        
        
        push(@D, $HW{$ID}{"Vendor"});
        push(@D, $HW{$ID}{"Device"});
        
        if($HW{$ID}{"SVendor"} or $HW{$ID}{"SDevice"})
        {
            push(@D, $HW{$ID}{"SVendor"});
            
            if($HW{$ID}{"SDevice"} ne "Device") {
                push(@D, $HW{$ID}{"SDevice"});
            }
        }
        
        $HWData .= join(";", @D)."\n";
    }
    
    if($FixProbe) {
        writeFile($FixProbe."/devices", $HWData);
    }
    else {
        writeFile($DATA_DIR."/devices", $HWData);
    }
    
    if($FixProbe)
    { # renew devices only
        return;
    }
    
    my $Host = "";
    $Host .= "system:".$Sys{"System"}."\n";
    $Host .= "user:".$Sys{"User"}."\n";
    $Host .= "node:".$Sys{"Node"}."\n";
    $Host .= "arch:".$Sys{"Arch"}."\n";
    $Host .= "kernel:".$Sys{"Kernel"}."\n";
    
    if($Sys{"Vendor"}) {
        $Host .= "vendor:".$Sys{"Vendor"}."\n";
    }
    if($Sys{"Model"}) {
        $Host .= "model:".$Sys{"Model"}."\n";
    }
    if($Sys{"Board"}) {
        $Host .= "board:".$Sys{"Board"}."\n";
    }
    if($Sys{"HWaddr"}) {
        $Host .= "hwaddr:".$Sys{"HWaddr"}."\n";
    }
    if($Sys{"Name"}) {
        $Host .= "id:".$Sys{"Name"}."\n";
    }
    
    # Host Info
    writeFile($DATA_DIR."/host", $Host);
    
    if($Logs)
    {
        # Init Logs
        # Kernel
        if(my $Dmesg = `dmesg 2>&1`) {
            writeFile($LOG_DIR."/dmesg", $Dmesg);
        }
        if(my $Lsmod = `/sbin/lsmod 2>&1`) {
            writeFile($LOG_DIR."/lsmod", $Lsmod);
        }
        if(my $Journal = `journalctl -ab 2>&1`) {
            writeFile($LOG_DIR."/journalctl", $Journal);
        }
        
        # X11
        if(my $XLog = readFile("/var/log/Xorg.0.log")) {
            writeFile($LOG_DIR."/xorg.log", $XLog);
        }
        
        # X11 (old)
        if(my $Old = readFile("/var/log/Xorg.0.log.old")) {
            writeFile($LOG_DIR."/xorg.log.1", $Old);
        }
        
        # GL
        if(my $Glx = `glxinfo 2>&1`) {
            writeFile($LOG_DIR."/glxinfo", $Glx);
        }
        
        # System Info
        if(my $Uname = `uname -a`) {
            writeFile($LOG_DIR."/uname", $Uname);
        }
        if(my $Lsb = `lsb_release -a 2>&1`) {
            writeFile($LOG_DIR."/lsb_release", $Lsb);
        }
        if(my $Issue = readFile("/etc/issue")) {
            writeFile($LOG_DIR."/issue", $Issue);
        }
        if(my $Rpms = `rpm -qa 2>&1`) {
            writeFile($LOG_DIR."/rpms", $Rpms);
        }
        
        if($Printers)
        {
            if(my $Lpstat = `lpstat 2>&1`) {
                writeFile($LOG_DIR."/lpstat", $Lpstat);
            }
        }
    }
    
    if($Key) {
        writeFile($DATA_DIR."/key", $Key);
    }
}

sub showInfo()
{
    if(not -d $DATA_DIR)
    {
        print STDERR "ERROR: \'./".$DATA_DIR."\' is not found, please make probe first\n";
        exit(1);
    }
    
    my %Tbl;
    my %STbl;
    
    foreach (split(/\s*\n\s*/, readFile($DATA_DIR."/devices")))
    {
        my @Info = split(";", $_);
        
        my %Dev = (
            "ID"      => $Info[0],
            "Class"   => $Info[1],
            "Status"  => $Info[2],
            "Type"    => $Info[3],
            "Driver"  => $Info[4],
            "Vendor"  => $Info[5],
            "Device"  => $Info[6],
            "SVendor" => $Info[7],
            "SDevice" => $Info[8]
        );
        
        foreach my $Attr (keys(%Dev))
        {
            if(not defined $Tbl{$Attr}) {
                $Tbl{$Attr} = [];
            }
            push(@{$Tbl{$Attr}}, $Dev{$Attr});
        }
    }
    
    foreach (split(/\s*\n\s*/, readFile($DATA_DIR."/host")))
    {
        if(/(\w+):(.*)/) {
            $STbl{$1} = $2;
        }
    }
    
    my $Rows = $#{$Tbl{"ID"}};
    
    print "\n";
    print "Total devices: $Rows\n";
    
    showTable(\%Tbl, $Rows, "ID", "Class", "Vendor", "Device");
    
    print "\n";
    print "Host Info\n";
    showHash(\%STbl, "system", "user", "node", "arch", "kernel", "vendor", "model", "board", "hwaddr", "id");
}

sub showTable(@)
{
    my $Tbl = shift(@_);
    my $Num = shift(@_);
    
    my %Max;
    
    foreach my $Col (sort keys(%{$Tbl}))
    {
        if(not defined $Max{$Col}) {
            $Max{$Col} = 0;
        }
        
        foreach my $El (@{$Tbl->{$Col}})
        {
            if(length($El) > $Max{$Col}) {
                $Max{$Col} = length($El);
            }
        }
    }
    
    my $Br = "";
    my $Hd = "";
    
    foreach my $Col (@_)
    {
        my $Hd_T = $Col;
        if(not $Hd) {
            $Hd_T = "| ".$Hd_T;
        }
        $Hd .= "| ".$Col;
        $Hd .= mulCh(" ", $Max{$Col} - length($Col) + 1);
        
        $Br .= "+";
        $Br .= mulCh("-", $Max{$Col} + 2);
    }
    
    $Br .= "+";
    $Hd .= "|";
    
    print $Br."\n";
    print $Hd."\n";
    print $Br."\n";
    
    foreach my $Row (0 .. $Num)
    {
        foreach my $Col (@_)
        {
            my $El = $Tbl->{$Col}[$Row];
            print "| ".$El;
            print alignStr($El, $Max{$Col} + 1);
        }
        print "|\n";
    }
    
    print $Br."\n";
}

sub showHash(@)
{
    my $Hash = shift(@_);
    
    my $KMax = 0;
    my $VMax = 0;
    
    foreach my $Key (sort keys(%{$Hash}))
    {
        my $Val = $Hash->{$Key};
        if(length($Val) > $VMax) {
            $VMax = length($Val);
        }
        if(length($Key) > $KMax) {
            $KMax = length($Key);
        }
    }
    
    my $Br = "+";
    $Br .= mulCh("-", $KMax + 2);
    $Br .= "+";
    $Br .= mulCh("-", $VMax + 2);
    $Br .= "+";
    
    foreach my $Key (@_)
    {
        my $Val = $Hash->{$Key};
        
        print $Br."\n";
        
        print "| ";
        print ucfirst($Key);
        print alignStr($Key, $KMax + 1);
        print "| ";
        print $Val;
        print alignStr($Val, $VMax + 1);
        print "|\n";
    }
    
    print $Br."\n";
    print "\n";
}

sub mulCh($$)
{
    my $Str = "";
    foreach (1 .. $_[1]) {
        $Str .= $_[0];
    }
    return $Str;
}

sub alignStr($$)
{
    my $Align = "";
    
    foreach (1 .. $_[1] - length($_[0]))
    {
        $Align .= " ";
    }
    
    return $Align;
}

sub listDir($)
{
    my $Path = $_[0];
    return () if(not $Path);
    opendir(my $DH, $Path);
    return () if(not $DH);
    my @Contents = grep { $_ ne "." && $_ ne ".." } readdir($DH);
    return @Contents;
}

sub checkHW()
{
    # TODO: test operability, set status to "works" or "failed"
}

sub writeFile($$)
{
    my ($Path, $Content) = @_;
    return if(not $Path);
    if(my $Dir = dirname($Path)) {
        mkpath($Dir);
    }
    open(FILE, ">", $Path) || die ("can't open file \'$Path\': $!\n");
    print FILE $Content;
    close(FILE);
}

sub scenario()
{
    if($Help)
    {
        helpMsg();
        exit(0);
    }
    
    if($DumpVersion)
    {
        print $TOOL_VERSION."\n";
        exit(0);
    }
    
    if(not $Sudo)
    {
        if(not $FixProbe and not $Source and not $Show) {
            print STDERR "WARNING: you should run as root for better results\n";
        }
    }
    
    if($All)
    {
        $Probe = 1;
        $Printers = 1;
        $Logs = 1;
        $Check = 1;
    }
    
    if($FixProbe)
    {
        if(not -e $FixProbe)
        {
            print STDERR "ERROR: can't access \'$FixProbe\'\n";
            exit(1);
        }
        
        if($FixProbe=~/\.tar\.gz\Z/)
        { # package
            $FixProbe_Pkg = abs_path($FixProbe);
            $FixProbe = $FixProbe_Pkg;
            
            chdir($TMP_DIR);
            system("tar -m -xf \"".$FixProbe."\"");
            chdir($ORIG_DIR);
            
            $FixProbe = $TMP_DIR."/hw.info";
        }
        
        $FixProbe=~s/[\/]+\Z//g;
        $FixProbe_Logs = $FixProbe."/logs";
        
        if(-d $FixProbe)
        {
            if(not -d $FixProbe_Logs)
            { # v0.1
                mkpath($FixProbe_Logs);
                foreach (listDir($FixProbe))
                {
                    if(not /\A(devices|host)\Z/) {
                        move($FixProbe."/".$_, $FixProbe_Logs."/".$_);
                    }
                }
            }
            
            if(not -e $FixProbe_Logs."/hwinfo")
            {
                print STDERR "ERROR: can't find logs in \'$FixProbe\'\n";
                exit(1);
            }
        }
        else
        {
            print STDERR "ERROR: can't access \'$FixProbe\'\n";
            exit(1);
        }
        
        $Logs = 0;
        
        if(-e $FixProbe_Logs."/dmidecode")
        {
            $Sudo = 1;
        }
    }
    
    if($Probe or $Check)
    {
        probeHW();
        probeSys();
        
        if($Check) {
            checkHW();
        }
        
        writeInfo();
    }
    elsif($FixProbe)
    {
        probeHW();
        writeInfo();
        
        if($FixProbe_Pkg)
        { # package
            chdir($TMP_DIR);
            system("tar -czf \"$FixProbe_Pkg\" hw.info");
            chdir($ORIG_DIR);
            
            if($?) {
                print STDERR "ERROR: can't create a package\n";
            }
            
            rmtree($TMP_DIR."/hw.info");
        }
    }
    
    if($Show) {
        showInfo();
    }
    
    if($Upload) {
        uploadData();
    }
    
    exit(0);
}

scenario();
