#!/usr/bin/perl
#########################################################################
# ROSA Hardware Probe Tool 0.6
# 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
#  cURL
#  hwinfo
#  dmidecode
#  pciutils (lspci)
#  usbutils (lsusb)
#  pnputils (lspnp)
#
# SUGGESTIONS
# ===========
#  hplip (hp-probe)
#  avahi
#  edid-decode
#  monitor-edid
#
# 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);

use strict;

my $TOOL_VERSION = "0.6";
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;

# IDs
my %PNP_IDs;

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(updateHost($TMP_DIR."/hw.info", "id", $PC_Name)) {
                        $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");
            updateHost($TMP_DIR."/hw.info", "id", $PC_Name);
            
            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);
            }
            
            updateHost($DATA_DIR, "id", $PC_Name);
            $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 updateHost($$$)
{
    my ($Path, $Attr, $Val) = @_;
    
    if($Val)
    {
        if(not -f $Path."/host")
        { # internal error
            return 0;
        }
        
        my $Content = readFile($Path."/host");
        
        my $Chg = 0;
        
        if($Content!~/(\A|\n)$Attr:/)
        {
            if($Content!~/\n\Z/) {
                $Content .= "\n";
            }
            $Content .= $Attr.":".$Val."\n";
            $Chg = 1;
        }
        elsif($Content=~/(\A|\n)$Attr:(.*)/)
        {
            if($2 ne $Val)
            {
                $Content=~s/(\A|\n)$Attr:(.*)/$1$Attr:$Val/;
                $Chg = 1;
            }
        }
        
        if($Chg)
        {
            writeFile($Path."/host", $Content);
            print "Added \'$Attr\' to host info\n";
            return 1;
        }
    }
    
    return 0;
}

sub fmtVal($)
{
    my $Val = $_[0];
    
    if(not defined $Val or $Val eq "" or $Val=~/\A\s+\Z/) {
        return "";
    }
    
    if($Val!~/[a-z0-9]/i) { # invalid
        return "";
    }
    
    $Val=~s/\((R|TM)\)/ /ig;
    
    $Val=~s/\A[_\- ]//ig;
    $Val=~s/[_\- ]\Z//ig;
    
    $Val=~s/[ ]{2,}/ /g;
    
    return $Val;
}

sub bytesToHuman($)
{
    my $Bytes = $_[0];
    
    $Bytes/=1000000; # MB
    
    if($Bytes>=1000)
    {
        $Bytes/=1000; # GB
        $Bytes = round_to_nearest($Bytes);
        if($Bytes>=1000)
        {
            $Bytes/=1000; # TB
            $Bytes = round_to_nearest($Bytes);
            return $Bytes."TB";
        }
        
        return $Bytes."GB";
    }
    
    return $Bytes."MB";
}

sub getVendor($)
{
    my $V = $_[0];
    
    my %MonVendor = (
        "HSD" => "HannStar",
        "CMN" => "Chimei Innolux",
        "LGD" => "LG Display",
        "CMO" => "Chi Mei Optoelectronics",
        "BNQ" => "BenQ",
        "AUO" => "AU Optronics",
        "ACR" => "Acer",
        "SAM" => "Samsung",
        "LEN" => "Lenovo",
        "LPL" => "LG Philips",
        "VSC" => "ViewSonic",
        "SNY" => "Sony"
    );
    
    if(defined $MonVendor{$V}) {
        return $MonVendor{$V};
    }
    
    if(not keys(%PNP_IDs)) {
        readPNP_IDs();
    }
    
    if(defined $PNP_IDs{$V}) {
        return $PNP_IDs{$V};
    }
    
    return undef;
}

sub readPNP_IDs()
{
    my $Path = "/usr/share/hwdata/pnp.ids"; # RELS
    
    if(not -e $Path) {
        $Path = "/usr/share/misc/pnp.ids"; # Marathon
    }
    
    foreach (split(/\n/, readFile($Path)))
    {
        if(/\A([A-Z]+)\s+(.*?)\Z/) {
            $PNP_IDs{$1} = $2;
        }
    }
}

sub getDefaultType($$)
{
    my ($Bus, $Device) = @_;
    
    foreach my $Name ($Device->{"Device"}, $Device->{"SDevice"})
    {
        if(not $Name) {
            next;
        }
        
        if($Bus eq "usb")
        {
            if($Name=~/camera|webcam/i) {
                return "camera";
            }
            elsif($Name=~/card reader/i) {
                return "card reader";
            }
            elsif($Name=~/fingerprint (reader|scanner|sensor)/i) {
                return "fingerprint reader";
            }
        }
    }
    
    return "";
}

sub addCapacity($$)
{
    my ($Device, $Capacity) = @_;
    
    if($Capacity)
    {
        if($Device!~/(\A|\s)[\d\.]+\s*(MB|GB|TB)(\s|\Z)/ and $Device!~/reader|bridge|\/sd\/|adapter/i) {
            return " ".$Capacity;
        }
    }
    
    return "";
}

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 %Mon = ();
    
    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 = "";
        
        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(lc($Val) ne "unknown") {
                    $Device{"Type"} = $Val;
                }
            }
            elsif($Key eq "Driver")
            {
                my @Dr = ();
                while($Val=~s/\"([\w\-]+)\"//)
                {
                    my $Dr = $1;
                    $Dr=~s/\-/_/g;
                    $Device{"ActiveDriver_Common"}{$Dr} = keys(%{$Device{"ActiveDriver_Common"}});
                }
            }
            elsif($Key eq "Driver Modules")
            {
                my @Dr = ();
                while($Val=~s/\"([\w\-]+)\"//) {
                    push(@Dr, $1);
                }
                # $Device{"Driver"} = join(",", @Dr);
            }
            elsif($Key eq "Driver Status")
            {
                if($Val=~/(.*) is active/)
                {
                    my $Dr = $1;
                    $Dr=~s/\-/_/g;
                    $Device{"ActiveDriver"}{$Dr} = keys(%{$Device{"ActiveDriver"}});
                }
            }
            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 eq "Resolution"
            and $Device{"Type"} eq "monitor")
            { # monitor
                if($Val=~s/\@.*//)
                {
                    $Device{$Key} = $Val;
                    $Device{$Key}=~s/ //g;
                }
            }
            elsif($Key eq "Size"
            and $Device{"Type"} eq "monitor")
            { # monitor
                $Device{$Key} = $Val;
                $Device{$Key}=~s/ //g;
            }
            elsif($Key eq "Size")
            { # disk
                if($Val=~/(\d+) sectors a (\d+) bytes/) {
                    $Device{"Capacity"} = bytesToHuman($1*$2);
                }
            }
            elsif($Key eq "Capacity")
            { # disk
                if($Val=~s/\s*\((.*?)\)//)
                {
                    my $Bytes = $1;
                    $Bytes=~s/ bytes//g;
                    
                    $Val = bytesToHuman($Bytes);
                }
                $Val=~s/ //g;
                $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;
        }
        
        if(defined $Device{"ActiveDriver"}) {
            $Device{"Driver"} = join(", ", sort {$Device{"ActiveDriver"}{$a} <=> $Device{"ActiveDriver"}{$b}} keys(%{$Device{"ActiveDriver"}}));
        }
        elsif(defined $Device{"ActiveDriver_Common"}) {
            $Device{"Driver"} = join(", ", sort {$Device{"ActiveDriver_Common"}{$a} <=> $Device{"ActiveDriver_Common"}{$b}} keys(%{$Device{"ActiveDriver_Common"}}));
        }
        
        if(not $Device{"Type"}) {
            $Device{"Type"} = getDefaultType($Bus, \%Device);
        }
        
        # fix type
        if($Device{"Type"} eq "keyboard")
        {
            if($Device{"Device"}=~/mouse/i and $Device{"Device"}!~/keyboard/i) {
                $Device{"Type"} = "mouse";
            }
        }
        elsif($Device{"Type"} eq "mouse")
        {
            if($Device{"Device"}=~/keyboard/i and $Device{"Device"}!~/mouse/i) {
                $Device{"Type"} = "keyboard";
            }
        }
        
        # 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|TrackPoint)/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"}=~s/\A([a-z]{3,})[\-\_ ]//i)
                { # Crucial_CT240M500SSD3
                    $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") {
                $Device{"Device"} .= addCapacity($Device{"Device"}, $Device{"Capacity"});
            }
        }
        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($V)
                    {
                        if(my $Vendor = getVendor($V))
                        {
                            $Device{"Vendor"} = $Vendor;
                            $Device{"Device"}=~s/\A\Q$Vendor\E(\s+|\-)//ig;
                        }
                        elsif(not $Device{"Vendor"}) {
                            $Device{"Vendor"} = $V;
                        }
                    }
                }
                
                # 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);
                }
                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") {
                    $Device{"Device"} .= addCapacity($Device{"Device"}, $Device{"Capacity"});
                }
                elsif($Device{"Type"} eq "cpu")
                {
                    $Device{"Status"} = "works";
                }
            }
            else {
                next;
            }
        }
        
        # delete unused fields
        delete($Device{"ActiveDriver_Common"});
        delete($Device{"ActiveDriver"});
        
        delete($Device{"FsId"});
        delete($Device{"Serial"});
        delete($Device{"Model"});
        
        $Device{"Class"} = $C;
        
        cleanValues(\%Device);
        
        $ID = fmtID($ID);
        
        if($Device{"Type"} eq "monitor") {
            $Mon{uc($V.$D)} = $ID;
        }
        
        if(not $HW{$Bus.":".$ID}) {
            $HW{$Bus.":".$ID} = \%Device;
        }
        else
        { # double entry
            if($Device{"Type"} and not $HW{$Bus.":".$ID}{"Type"}) {
                $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(defined $HW{"pci:".$ID}{"Class"}) {
        #    delete($Device{"Class"});
        #}
        
        if(my $Dr = $Device{"Driver"})
        {
            $Dr=~s/\-/_/g;
            if(not defined $HW{"pci:".$ID}{"Driver"}) {
                $HW{"pci:".$ID}{"Driver"} = $Dr;
            }
        }
        
        foreach (keys(%Device))
        {
            if(my $Val = $Device{$_})
            {
                if($_ eq "Driver") {
                    next;
                }
                $HW{"pci:".$ID}{$_} = $Val;
            }
        }
    }
    
    # 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"} = fmtVal($2);
            }
        }
        
        if($Info=~/idProduct[ ]+0x(\w{4})[ ]+(.*)/)
        {
            $D = $1;
            
            if($2) {
                $Device{"Device"} = fmtVal($2);
            }
        }
        
        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(defined $HW{"usb:".$ID}{"Class"}) {
        #    delete($Device{"Class"});
        #}
        
        my $Vendor = $Device{"Vendor"};
        my $OldName = $HW{"usb:".$ID}{"Device"};
        
        if($Device{"Device"}=~/\AFlash (Drive|Disk)\Z/i and $OldName)
        {
            $Device{"Device"} = $OldName;
        }
        else
        {
            if($Device{"Device"}) {
                $Device{"Device"} .= addCapacity($Device{"Device"}, $HW{"usb:".$ID}{"Capacity"});
            }
        }
        
        my $FinalName = $Device{"Device"};
        
        if(not $FinalName) {
            $FinalName = $OldName;
        }
        
        if($FinalName!~/root hub/i)
        {
            if($Info=~/iProduct[ ]+\w+[ ]+(.+)/)
            {
                if(my $SubDevice = fmtVal($1))
                {
                    if($FinalName)
                    {
                        my $N1 = $FinalName;
                        my $N2 = $SubDevice;
                        
                        $N2=~s/\AUSB //g;
                        
                        if($N1!~/\Q$N2\E/i and $N2!~/\Q$N1\E/i) {
                            $Device{"SDevice"} = $SubDevice;
                        }
                    }
                    else {
                        $Device{"Device"} = $SubDevice;
                    }
                }
            }
            elsif($OldName)
            {
                if($FinalName!~/\Q$OldName\E/i and $OldName!~/\Q$FinalName\E/i) {
                    $Device{"SDevice"} = $OldName;
                }
            }
            
            if($Info=~/iManufacturer[ ]+\w+[ ]+(.+)/)
            {
                if(my $SubVendor = fmtVal($1))
                {
                    $SubVendor=~s/\AManufacturer\s+//ig;
                    
                    my $V1 = nameID($Vendor);
                    my $V2 = nameID($SubVendor);
                    
                    if($Vendor and $V1!~/\Q$V2\E/i and $V2!~/\Q$V1\E/i
                    and $SubVendor!~/usb/i and $SubVendor!~/generic/
                    and $SubVendor ne $Device{"SDevice"}
                    and $SubVendor ne $FinalName)
                    {
                        $Device{"SVendor"} = $SubVendor;
                    }
                }
            }
        }
        
        if(not $HW{"usb:".$ID}{"Type"}) {
            $Device{"Type"} = getDefaultType("usb", \%Device);
        }
        
        cleanValues(\%Device);
        
        foreach (keys(%Device))
        {
            if($Device{$_}) {
                $HW{"usb:".$ID}{$_} = $Device{$_};
            }
        }
    }
    
    my $Usb_devices = "";
    
    if($FixProbe) {
        $Usb_devices = readFile($FixProbe_Logs."/usb-devices");
    }
    else
    {
        $Usb_devices = `usb-devices -v 2>&1`;
        
        if($Logs) {
            writeFile($LOG_DIR."/usb-devices", $Usb_devices);
        }
    }
    
    foreach my $Info (split(/\n\n/, $Usb_devices))
    {
        my ($V, $D) = ();
        
        if($Info=~/Vendor=([^ ]+)/) {
            $V = $1;
        }
        
        if($Info=~/ProdID=([^ ]+)/) {
            $D = $1;
        }
        
        my %Driver = ();
        
        while($Info=~s/Driver=([\w\-]+)//)
        {
            my $Dr = $1;
            $Dr=~s/\-/_/g;
            
            if(not defined $Driver{$Dr}) {
                $Driver{$Dr} = keys(%Driver);
            }
        }
        
        if($V and $D)
        {
            my $ID = devID($V, $D);
            
            if(my @Dr = keys(%Driver))
            {
                @Dr = sort {$Driver{$a} <=> $Driver{$b}} @Dr;
                $HW{"usb:".$ID}{"Driver"} = join(", ", @Dr);
            }
        }
    }
    
    # DMI
    my $Dmidecode = "";
    
    if($FixProbe) {
        $Dmidecode = readFile($FixProbe_Logs."/dmidecode");
    }
    else
    {
        $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=~/Chassis Information\n/)
        { # notebook or desktop
            if($Info=~/Type:[ ]*(.+?)[ ]*(\n|\Z)/)
            {
                $Sys{"Type"} = lc($1);
                $Sys{"Type"}=~s/ chassis//i;
            }
        }
        elsif($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 2>&1`;
            
            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;
            }
        }
    }
    
    # Monitors
    my $Edid = "";
    
    if($FixProbe) {
        $Edid = readFile($FixProbe_Logs."/edid");
    }
    else
    { # NOTE: works for KMS video drivers only
        my $EdidDecode = (-x "/usr/bin/edid-decode");
        my $MonEdid = (-x "/usr/sbin/monitor-get-edid");
        
        if($EdidDecode)
        {
            my $MDir = "/sys/class/drm";
            foreach my $Dir (listDir($MDir))
            {
                my $Path = $MDir."/".$Dir."/edid";
                
                if(-f $Path)
                {
                    my $Dec = `edid-decode \"$Path\"`;
                    
                    if($Dec!~/No header found/i)
                    {
                        $Edid .= "edid-decode \"$Path\"\n".$Dec."\n\n";
                    }
                }
            }
        }
        
        if(not $Edid)
        { # for nvidia, fglrx
            if($MonEdid)
            {
                if($EdidDecode) {
                    $Edid .= `monitor-get-edid|edid-decode`;
                }
                else
                { # LTS
                    $Edid .= `monitor-get-edid|monitor-parse-edid 2>/dev/null`;
                }
                $Edid=~s/\n\n/\n/g;
            }
        }
        
        if($Logs) {
            writeFile($LOG_DIR."/edid", $Edid);
        }
    }
    
    foreach my $Info (split(/\n\n/, $Edid))
    {
        my ($V, $D) = ();
        my %Device = ();
        
        if($Info=~/Manufacturer:\s*(.+?)\s+Model\s+(.+?)\s+Serial/)
        {
            ($V, $D) = ($1, $2);
            if(length($D)<4)
            {
                foreach (1 .. 4 - length($D)) {
                    $D = "0".$D;
                }
            }
        }
        elsif($Info=~/EISA ID:\s*(\w{3})(\w+)/)
        {
            ($V, $D) = (uc($1), uc($2));
        }

        if(not $V or not $D) {
            next;
        }
        
        if($Info=~/Monitor name:\s*(.*?)(\n|\Z)/) {
            $Device{"Device"} = $1;
        }
        else
        {
            # if($Info=~s/ASCII string:\s*(.*?)(\n|\Z)//)
            # { # broken data
            #     if($Info=~s/ASCII string:\s*(.*?)(\n|\Z)//)
            #     {
            #         $Device{"Device"} = $1;
            #     }
            # }
        }
        
        if($Info=~/([\d\.]+)\s*cm\s*x\s*([\d\.]+)\s*cm/) {
            $Device{"Size"} = $1."x".$2."cm";
        }
        elsif($Info=~/(\d+)\s*mm\s*x\s*(\d+)\s*mm/) {
            $Device{"Size"} = $1."x".$2."mm";
        }
        
        while($Info=~s/(\d+x\d+)\@\d+//) {
            $Device{"Resolution"} = $1;
        }
        
        if(not $Device{"Resolution"})
        { # monitor-parse-edid
            if($Info=~s/"(\d+x\d+)"//) {
                $Device{"Resolution"} = $1;
            }
        }
        
        if($V)
        {
            if(my $Vendor = getVendor($V))
            {
                $Device{"Vendor"} = $Vendor;
                $Device{"Device"}=~s/\A\Q$Vendor\E(\s+|\-)//ig;
            }
            elsif(not $Device{"Vendor"}) {
                $Device{"Vendor"} = $V;
            }
        }
        
        my $MName = $Device{"Device"};
        
        if(not $MName or $Device{"Vendor"}=~/\Q$MName\E/i) {
            $Device{"Device"} = "LCD Monitor";
        }
        
        my $ID = undef;
        
        if($Device{"Vendor"})
        {
            if($Device{"Vendor"} ne $V) {
                $ID = devID(nameID($Device{"Vendor"}));
            }
        }
        $ID = devID($ID, $V.$D);
        $ID = fmtID($ID);
        
        if(my $OldID = $Mon{uc($V.$D)})
        {
            my $Name = $Device{"Device"};
            if($Name ne "LCD Monitor")
            {
                if($HW{"eisa:".$OldID}{"Vendor"}!~/\Q$Name\E/i) {
                    $HW{"eisa:".$OldID}{"Device"}=~s/LCD Monitor/$Name/;
                }
            }
            next;
        }
        
        if($D) {
            $Device{"Device"} .= " ".uc($V.$D);
        }
        
        if($Device{"Resolution"}) {
            $Device{"Device"} .= " ".$Device{"Resolution"};
        }
        
        if($Device{"Size"}) {
            $Device{"Device"} .= " ".$Device{"Size"};
        }
        
        $Device{"Type"} = "monitor";
        
        if($ID)
        {
            if(not defined $HW{"eisa:".$ID}) {
                $HW{"eisa:".$ID} = \%Device;
            }
        }
    }
    
    # Battery
    my $Upower = "";
    
    if($FixProbe) {
        $Upower = readFile($FixProbe_Logs."/upower");
    }
    else
    {
        $Upower = `upower -d 2>/dev/null`;
        if($Logs) {
            writeFile($LOG_DIR."/upower", $Upower);
        }
    }
    
    # PNP
    my $Lspnp = "";
    if($FixProbe) {
        $Lspnp = readFile($FixProbe_Logs."/lspnp");
    }
    else
    {
        $Lspnp = `lspnp -vv 2>&1`;
        if($Logs) {
            writeFile($LOG_DIR."/lspnp", $Lspnp);
        }
    }
    
    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
        or $Val=~/\b(to be filled|unclassified device)\b/i) {
            delete($Hash->{$_});
        }
        
        if($Val=~/\A(vendor|device|unknown vendor)\Z/i)
        {
            delete($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;
    
    while ($Name=~s/,?\s+(Inc|Ltd|Co|GmbH|Corp|LLC|Sdn|Bhd|PLC|s\.r\.l\.|srl|S\.P\.A\.|B\.V\.)(\.|\Z)//ig){};
    $Name=~s/,?\s+[a-z]{2,4}\.//ig;
    $Name=~s/,(.+)\Z//ig;
    
    while ($Name=~s/\s+(Corporation|Computer|Electric|Company|Electronics|Electronic|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!~/\b(System manufacturer|to be filled)\b/i) {
            $Sys{"Vendor"} = $Vendor;
        }
    }
    
    if(my $Model = readFile("/sys/class/dmi/id/product_name"))
    {
        if($Model!~/\b(Name|to be filled)\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()
{
    my $LSB_Rel = "";
    
    if($FixProbe) {
        $LSB_Rel = readFile($FixProbe_Logs."/lsb_release");
    }else {
        $LSB_Rel = `lsb_release -a 2>/dev/null`;
    }
    
    if($LSB_Rel)
    { # Desktop
        my ($Name, $Release) = ();
        
        if($LSB_Rel=~/ID:\s*(.*)/) {
            $Name = $1;
        }
        if($LSB_Rel=~/Release:\s*(.*)/) {
            $Release = $1;
        }
        
        if($Name=~/\AROSAEnterprise/i)
        {
            $Release=~s/\.//g;
            return "rosa-server".lc($Release);
        }
        elsif($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);
    }
    
    my $Issue = "";
    
    if($FixProbe) {
        $Issue = readFile($FixProbe_Logs."/issue");
    }
    else {
        $Issue = readFile("/etc/issue");
    }
    
    if($Issue)
    { # RELS
        if($Issue=~/ROSA/i and $Issue=~/Server/i)
        {
            if($Issue=~/release ([\w\.]+)/i)
            {
                my $Release = $1;
                $Release=~s/\.//g;
                
                return "rosa-server".lc($Release);
            }
        }
    }
    
    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()
{
    writeDevs();
    writeHost();
    
    if($Logs) {
        writeLogs();
    }
    
    if($Key) {
        writeFile($DATA_DIR."/key", $Key);
    }
}

sub writeDevs()
{
    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);
    }
}

sub writeHost()
{
    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";
    }
    if($Sys{"Type"}) {
        $Host .= "type:".$Sys{"Type"}."\n";
    }
    
    # Host Info
    writeFile($DATA_DIR."/host", $Host);
}

sub writeLogs()
{
    # Init Logs
    # Kernel
    my $Dmesg = `dmesg 2>&1`;
    writeFile($LOG_DIR."/dmesg", $Dmesg);
    
    my $Lsmod = `/sbin/lsmod 2>&1`;
    writeFile($LOG_DIR."/lsmod", $Lsmod);
    
    my $Journal = `journalctl -ab 2>&1`;
    writeFile($LOG_DIR."/journalctl", $Journal);
    
    # X11
    my $XLog = readFile("/var/log/Xorg.0.log");
    writeFile($LOG_DIR."/xorg.log", $XLog);
    
    # X11 (old)
    my $XLog_Old = readFile("/var/log/Xorg.0.log.old");
    writeFile($LOG_DIR."/xorg.log.1", $XLog_Old);
    
    my $XRandr = `xrandr --verbose 2>&1`;
    writeFile($LOG_DIR."/xrandr", $XRandr);
    
    my $Glx = `glxinfo 2>&1`;
    writeFile($LOG_DIR."/glxinfo", $Glx);
    
    my $Uname = `uname -a`;
    writeFile($LOG_DIR."/uname", $Uname);
    
    my $Lsb = `lsb_release -a 2>&1`;
    writeFile($LOG_DIR."/lsb_release", $Lsb);
    
    my $Issue = readFile("/etc/issue");
    writeFile($LOG_DIR."/issue", $Issue);
    
    my $Rpms = `rpm -qa 2>&1`;
    writeFile($LOG_DIR."/rpms", $Rpms);
    
    my $SysMod = `ls -1 /sys/module`;
    writeFile($LOG_DIR."/sys_module", $SysMod);
    
    my $Media = `urpmq --list-url 2>/dev/null`;
    writeFile($LOG_DIR."/media_urls", $Media);
    
    my $Media_A = `urpmq --list-media active 2>/dev/null`;
    writeFile($LOG_DIR."/media_active", $Media_A);
    
    my $Sctl = `systemctl 2>/dev/null`;
    writeFile($LOG_DIR."/systemctl", $Sctl);
    
    my $Sctl_S = `systemctl status 2>/dev/null`;
    writeFile($LOG_DIR."/systemctl_status", $Sctl_S);
    
    my $KRel = $Sys{"Kernel"};
    my $Initrd = "";
    if(-e "/boot/initrd-$KRel.img") {
        $Initrd = `lsinitrd /boot/initrd-$KRel.img 2>/dev/null`;
    }
    elsif(-e "/boot/initramfs-$KRel.img") {
        $Initrd = `lsinitrd /boot/initramfs-$KRel.img 2>/dev/null`;
    }
    writeFile($LOG_DIR."/lsinitrd", $Initrd);
    
    my $DevFiles = `find /dev -maxdepth 5 | sort`;
    writeFile($LOG_DIR."/dev", $DevFiles);
    
    # my $Ldconfig = `ldconfig -p 2>/dev/null`;
    # writeFile($LOG_DIR."/ldconfig", $Ldconfig);
    
    # my $Libs = `find /lib /usr/lib -maxdepth 5 2>/dev/null | sort`;
    # writeFile($LOG_DIR."/lib", $Libs);
    
    if($Printers)
    {
        my $Lpstat = `lpstat 2>&1`;
        writeFile($LOG_DIR."/lpstat", $Lpstat);
    }
}

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($All)
    {
        $Probe = 1;
        $Printers = 1;
        $Logs = 1;
        $Check = 1;
    }
    
    if(not $Sudo)
    {
        if($Probe) {
            print STDERR "WARNING: you should run as root for better results\n";
        }
    }
    
    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($Probe or $Check)
    {
        probeHW();
        probeSys();
        
        if($Check) {
            checkHW();
        }
        
        writeInfo();
    }
    elsif($FixProbe)
    {
        probeHW();
        writeDevs();
        
        updateHost($FixProbe, "system", getSysName());
        updateHost($FixProbe, "type", $Sys{"Type"});
        updateHost($FixProbe, "id", $PC_Name);
        
        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();
